@ctxo/cli 0.10.0 → 0.11.1

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.
@@ -19,26 +19,28 @@ import {
19
19
  loadConfig,
20
20
  makeGlobMatcher,
21
21
  watchSnapshotMinFileChanges
22
- } from "./chunk-I6HLEK4G.js";
22
+ } from "./chunk-HZ4UGA6J.js";
23
23
  import {
24
- atomicWrite,
25
- createLogger
26
- } from "./chunk-6EKERTJ7.js";
24
+ atomicWrite
25
+ } from "./chunk-RFEXMBTG.js";
27
26
  import {
28
27
  JsonIndexReader
29
- } from "./chunk-2TVHKCCU.js";
28
+ } from "./chunk-CDGQGCVN.js";
29
+ import {
30
+ createLogger
31
+ } from "./chunk-5DSKZJNN.js";
30
32
  import {
31
33
  StalenessDetector
32
34
  } from "./chunk-PSKDDLSF.js";
33
35
 
34
36
  // src/cli/cli-router.ts
35
- import { readFileSync as readFileSync17, existsSync as existsSync25 } from "fs";
36
- import { dirname as dirname8, join as join29 } from "path";
37
+ import { readFileSync as readFileSync17, existsSync as existsSync27 } from "fs";
38
+ import { dirname as dirname9, join as join31 } from "path";
37
39
 
38
40
  // src/cli/index-command.ts
39
41
  import { execFileSync as execFileSync2 } from "child_process";
40
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync2, existsSync as existsSync9, statSync, readdirSync as readdirSync2 } from "fs";
41
- import { join as join8, relative, extname as extname3 } from "path";
42
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync2, existsSync as existsSync10, statSync, readdirSync as readdirSync2 } from "fs";
43
+ import { join as join9, relative, extname as extname3 } from "path";
42
44
  import { createRequire as createRequire3 } from "module";
43
45
 
44
46
  // src/core/staleness/content-hasher.ts
@@ -264,9 +266,9 @@ async function loadPlugins(projectRoot, options = {}) {
264
266
  }
265
267
 
266
268
  // src/cli/install-command.ts
267
- import { existsSync as existsSync5 } from "fs";
269
+ import { existsSync as existsSync6 } from "fs";
268
270
  import { createRequire as createRequire2 } from "module";
269
- import { join as join4 } from "path";
271
+ import { join as join5 } from "path";
270
272
 
271
273
  // src/core/detection/detect-languages.ts
272
274
  import { execFileSync } from "child_process";
@@ -562,20 +564,60 @@ function buildInstallCommand(manager, packages, options = {}) {
562
564
  // src/core/install/run-package-manager.ts
563
565
  import { spawn } from "child_process";
564
566
  function runPackageManager(command, args, cwd) {
565
- const resolved = resolveBinary(command);
566
567
  return new Promise((resolve4, reject) => {
567
- const child = spawn(resolved, [...args], { cwd, stdio: "inherit", shell: false });
568
+ const child = process.platform === "win32" ? spawn([command, ...args].join(" "), { cwd, stdio: "inherit", shell: true }) : spawn(command, [...args], { cwd, stdio: "inherit", shell: false });
568
569
  child.on("exit", (code) => resolve4(code ?? 0));
569
570
  child.on("error", (err) => reject(err));
570
571
  });
571
572
  }
572
- function resolveBinary(command) {
573
- if (process.platform !== "win32") return command;
574
- if (command === "bun") return "bun.exe";
575
- return `${command}.cmd`;
573
+
574
+ // src/core/detection/detect-java-runtime.ts
575
+ import { spawnSync } from "child_process";
576
+ import { existsSync as existsSync5 } from "fs";
577
+ import { join as join4 } from "path";
578
+ function parseJavaMajor(output) {
579
+ const m = output.match(/version "([^"]+)"/);
580
+ if (!m) return void 0;
581
+ const parts = m[1].split(".");
582
+ const major = parts[0] === "1" && parts[1] ? parseInt(parts[1], 10) : parseInt(parts[0], 10);
583
+ return Number.isNaN(major) ? void 0 : major;
584
+ }
585
+ var _javaMajorCache = void 0;
586
+ function detectJavaMajor() {
587
+ if (_javaMajorCache !== void 0) {
588
+ return _javaMajorCache === null ? void 0 : _javaMajorCache;
589
+ }
590
+ const home = process.env["CTXO_JAVA_HOME"] || process.env["JAVA_HOME"];
591
+ const bin = home ? join4(home, "bin", process.platform === "win32" ? "java.exe" : "java") : "java";
592
+ if (home && !existsSync5(bin)) {
593
+ _javaMajorCache = null;
594
+ return void 0;
595
+ }
596
+ const r = spawnSync(bin, ["-version"], {
597
+ encoding: "utf-8",
598
+ timeout: 1e4,
599
+ windowsHide: true
600
+ });
601
+ if (r.error || r.status === null) {
602
+ _javaMajorCache = null;
603
+ return void 0;
604
+ }
605
+ const major = parseJavaMajor(`${r.stderr ?? ""}${r.stdout ?? ""}`);
606
+ _javaMajorCache = major ?? null;
607
+ return major;
608
+ }
609
+ function javaRuntimeAvailable() {
610
+ return (detectJavaMajor() ?? 0) >= 17;
576
611
  }
577
612
 
578
613
  // src/cli/install-command.ts
614
+ function resolveJavaPackages(opts) {
615
+ if (opts.fullTier && opts.syntaxOnly) throw new Error("Cannot combine --full-tier with --syntax-only.");
616
+ const base = ["@ctxo/lang-java"];
617
+ if (opts.syntaxOnly) return base;
618
+ if (opts.fullTier || opts.jreAvailable) return [...base, "@ctxo/lang-java-analyzer"];
619
+ return base;
620
+ }
579
621
  var InstallCommand = class {
580
622
  constructor(projectRoot) {
581
623
  this.projectRoot = projectRoot;
@@ -599,7 +641,7 @@ var InstallCommand = class {
599
641
  process.exitCode = 1;
600
642
  return;
601
643
  }
602
- if (!options.global && !existsSync5(join4(this.projectRoot, "package.json"))) {
644
+ if (!options.global && !existsSync6(join5(this.projectRoot, "package.json"))) {
603
645
  console.error("[ctxo] No package.json in the current project. Re-run with --global or create a package.json first.");
604
646
  process.exitCode = 1;
605
647
  return;
@@ -609,9 +651,28 @@ var InstallCommand = class {
609
651
  process.exitCode = 1;
610
652
  return;
611
653
  }
612
- const specifiers = resolvedLangs.map(
613
- (lang) => options.version ? `${officialPluginFor(lang)}@${options.version}` : officialPluginFor(lang)
614
- );
654
+ const specifiers = [];
655
+ for (const lang of resolvedLangs) {
656
+ if (lang === "java") {
657
+ const jreAvailable = javaRuntimeAvailable();
658
+ if (!jreAvailable && !options.fullTier && !options.syntaxOnly) {
659
+ console.error('[ctxo] JRE 17+ not found; installing Java syntax tier. Install a JRE then run "ctxo install java --full-tier" for resolved call/use edges.');
660
+ }
661
+ let javaPkgs;
662
+ try {
663
+ javaPkgs = resolveJavaPackages({ jreAvailable, fullTier: options.fullTier, syntaxOnly: options.syntaxOnly });
664
+ } catch (err) {
665
+ console.error(`[ctxo] ${err.message}`);
666
+ process.exitCode = 1;
667
+ return;
668
+ }
669
+ for (const pkg of javaPkgs) {
670
+ specifiers.push(options.version ? `${pkg}@${options.version}` : pkg);
671
+ }
672
+ } else {
673
+ specifiers.push(options.version ? `${officialPluginFor(lang)}@${options.version}` : officialPluginFor(lang));
674
+ }
675
+ }
615
676
  const invocation = buildInstallCommand(resolution.manager, specifiers, {
616
677
  global: options.global,
617
678
  workspaceRoot: !options.global && isWorkspaceRoot(this.projectRoot)
@@ -703,12 +764,12 @@ function isLockedCI(env) {
703
764
  }
704
765
 
705
766
  // src/adapters/storage/json-index-writer.ts
706
- import { mkdirSync, unlinkSync, existsSync as existsSync6 } from "fs";
707
- import { dirname as dirname4, join as join5, resolve as resolve2, sep } from "path";
767
+ import { mkdirSync, unlinkSync, existsSync as existsSync7 } from "fs";
768
+ import { dirname as dirname4, join as join6, resolve as resolve2, sep } from "path";
708
769
  var JsonIndexWriter = class {
709
770
  indexDir;
710
771
  constructor(ctxoRoot) {
711
- this.indexDir = join5(ctxoRoot, "index");
772
+ this.indexDir = join6(ctxoRoot, "index");
712
773
  }
713
774
  write(fileIndex) {
714
775
  if (!fileIndex.file) {
@@ -722,7 +783,7 @@ var JsonIndexWriter = class {
722
783
  }
723
784
  writeCoChanges(matrix) {
724
785
  mkdirSync(this.indexDir, { recursive: true });
725
- const targetPath = join5(this.indexDir, "co-changes.json");
786
+ const targetPath = join6(this.indexDir, "co-changes.json");
726
787
  this.atomicWrite(targetPath, JSON.stringify(matrix, null, 2));
727
788
  }
728
789
  atomicWrite(targetPath, content) {
@@ -730,7 +791,7 @@ var JsonIndexWriter = class {
730
791
  }
731
792
  delete(relativePath) {
732
793
  const targetPath = this.resolveIndexPath(relativePath);
733
- if (existsSync6(targetPath)) {
794
+ if (existsSync7(targetPath)) {
734
795
  unlinkSync(targetPath);
735
796
  }
736
797
  }
@@ -758,19 +819,19 @@ var JsonIndexWriter = class {
758
819
  };
759
820
 
760
821
  // src/adapters/storage/schema-manager.ts
761
- import { readFileSync as readFileSync4, writeFileSync, existsSync as existsSync7, mkdirSync as mkdirSync2 } from "fs";
762
- import { join as join6, dirname as dirname5 } from "path";
822
+ import { readFileSync as readFileSync4, writeFileSync, existsSync as existsSync8, mkdirSync as mkdirSync2 } from "fs";
823
+ import { join as join7, dirname as dirname5 } from "path";
763
824
  var CURRENT_SCHEMA_VERSION = "1.0.0";
764
825
  var SchemaManager = class {
765
826
  versionFilePath;
766
827
  constructor(ctxoRoot) {
767
- this.versionFilePath = join6(ctxoRoot, "index", "schema-version");
828
+ this.versionFilePath = join7(ctxoRoot, "index", "schema-version");
768
829
  }
769
830
  currentVersion() {
770
831
  return CURRENT_SCHEMA_VERSION;
771
832
  }
772
833
  readStoredVersion() {
773
- if (!existsSync7(this.versionFilePath)) {
834
+ if (!existsSync8(this.versionFilePath)) {
774
835
  return void 0;
775
836
  }
776
837
  return readFileSync4(this.versionFilePath, "utf-8").trim();
@@ -787,8 +848,8 @@ var SchemaManager = class {
787
848
  };
788
849
 
789
850
  // src/adapters/storage/architecture-artifacts-writer.ts
790
- import { existsSync as existsSync8, mkdirSync as mkdirSync3, readFileSync as readFileSync5 } from "fs";
791
- import { join as join7, resolve as resolve3 } from "path";
851
+ import { existsSync as existsSync9, mkdirSync as mkdirSync3, readFileSync as readFileSync5 } from "fs";
852
+ import { join as join8, resolve as resolve3 } from "path";
792
853
  import { tmpdir } from "os";
793
854
  var log3 = createLogger("ctxo:architecture-artifacts");
794
855
  var DRIFT_FILE_NAME = "drift-events.json";
@@ -796,20 +857,20 @@ var VIOLATIONS_FILE_NAME = "boundary-violations.json";
796
857
  var ArchitectureArtifactsWriter = class {
797
858
  indexDir;
798
859
  constructor(ctxoRoot, options = {}) {
799
- this.indexDir = join7(ctxoRoot, "index");
860
+ this.indexDir = join8(ctxoRoot, "index");
800
861
  assertSafeWritePath(ctxoRoot, options.allowProductionPath ?? false);
801
862
  }
802
863
  writeDriftEvents(artifact) {
803
864
  mkdirSync3(this.indexDir, { recursive: true });
804
- atomicWrite(join7(this.indexDir, DRIFT_FILE_NAME), JSON.stringify(artifact, null, 2));
865
+ atomicWrite(join8(this.indexDir, DRIFT_FILE_NAME), JSON.stringify(artifact, null, 2));
805
866
  }
806
867
  writeBoundaryViolations(artifact) {
807
868
  mkdirSync3(this.indexDir, { recursive: true });
808
- atomicWrite(join7(this.indexDir, VIOLATIONS_FILE_NAME), JSON.stringify(artifact, null, 2));
869
+ atomicWrite(join8(this.indexDir, VIOLATIONS_FILE_NAME), JSON.stringify(artifact, null, 2));
809
870
  }
810
871
  readDriftEvents() {
811
- const path = join7(this.indexDir, DRIFT_FILE_NAME);
812
- if (!existsSync8(path)) return void 0;
872
+ const path = join8(this.indexDir, DRIFT_FILE_NAME);
873
+ if (!existsSync9(path)) return void 0;
813
874
  try {
814
875
  return JSON.parse(readFileSync5(path, "utf-8"));
815
876
  } catch (err) {
@@ -818,8 +879,8 @@ var ArchitectureArtifactsWriter = class {
818
879
  }
819
880
  }
820
881
  readBoundaryViolations() {
821
- const path = join7(this.indexDir, VIOLATIONS_FILE_NAME);
822
- if (!existsSync8(path)) return void 0;
882
+ const path = join8(this.indexDir, VIOLATIONS_FILE_NAME);
883
+ if (!existsSync9(path)) return void 0;
823
884
  try {
824
885
  return JSON.parse(readFileSync5(path, "utf-8"));
825
886
  } catch (err) {
@@ -1128,7 +1189,7 @@ var IndexCommand = class {
1128
1189
  supportedExtensions;
1129
1190
  constructor(projectRoot, ctxoRoot) {
1130
1191
  this.projectRoot = projectRoot;
1131
- this.ctxoRoot = ctxoRoot ?? join8(projectRoot, ".ctxo");
1192
+ this.ctxoRoot = ctxoRoot ?? join9(projectRoot, ".ctxo");
1132
1193
  this.supportedExtensions = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".go", ".cs"]);
1133
1194
  }
1134
1195
  loadIgnoreConfig() {
@@ -1156,7 +1217,7 @@ var IndexCommand = class {
1156
1217
  }
1157
1218
  const ignore = this.loadIgnoreConfig();
1158
1219
  const registry = new LanguageAdapterRegistry();
1159
- const { tsMorphLike, roslynAdapter, csharpTier, goTier } = await this.registerDiscoveredPlugins(registry, ignore.ignoreProjectPatterns);
1220
+ const { tsMorphLike, roslynAdapter, csharpTier, goTier, javaTier } = await this.registerDiscoveredPlugins(registry, ignore.ignoreProjectPatterns);
1160
1221
  this.supportedExtensions = registry.getSupportedExtensions();
1161
1222
  this.printLanguagePreview();
1162
1223
  const writer = new JsonIndexWriter(this.ctxoRoot);
@@ -1166,7 +1227,7 @@ var IndexCommand = class {
1166
1227
  const revertDetector = new RevertDetector();
1167
1228
  let files;
1168
1229
  if (options.file) {
1169
- const fullPath = join8(this.projectRoot, options.file);
1230
+ const fullPath = join9(this.projectRoot, options.file);
1170
1231
  files = [fullPath];
1171
1232
  console.error(`[ctxo] Incremental re-index: ${options.file}`);
1172
1233
  } else {
@@ -1179,8 +1240,8 @@ var IndexCommand = class {
1179
1240
  console.error(`[ctxo] Building codebase index... Found ${files.length} source files`);
1180
1241
  }
1181
1242
  try {
1182
- const { sweepStaleTmpFiles } = await import("./atomic-write-AQNYJFAG.js");
1183
- const swept = sweepStaleTmpFiles(join8(this.ctxoRoot, "index"));
1243
+ const { sweepStaleTmpFiles } = await import("./atomic-write-BAFH464Q.js");
1244
+ const swept = sweepStaleTmpFiles(join9(this.ctxoRoot, "index"));
1184
1245
  if (swept > 0) {
1185
1246
  console.error(`[ctxo] Cleaned ${swept} orphan .tmp file(s) from previous interrupted run`);
1186
1247
  }
@@ -1267,7 +1328,7 @@ var IndexCommand = class {
1267
1328
  }
1268
1329
  let communitySnapshot;
1269
1330
  if (options.skipCommunity && pendingIndices.length > 0) {
1270
- if (existsSync9(join8(this.ctxoRoot, "index", "communities.json"))) {
1331
+ if (existsSync10(join9(this.ctxoRoot, "index", "communities.json"))) {
1271
1332
  console.error(
1272
1333
  `[ctxo] WARN stale communities.json present \u2014 --skip-community preserved existing snapshot. Run \`ctxo index\` (full) to refresh.`
1273
1334
  );
@@ -1280,6 +1341,7 @@ var IndexCommand = class {
1280
1341
  {
1281
1342
  csharpTier,
1282
1343
  goTier,
1344
+ javaTier,
1283
1345
  tsCount: graphIndices.filter((idx) => /\.(ts|tsx|js|jsx)$/.test(idx.file)).length
1284
1346
  },
1285
1347
  gitAdapter
@@ -1317,10 +1379,12 @@ var IndexCommand = class {
1317
1379
  const csCount = pendingIndices.filter((e) => e.relativePath.endsWith(".cs")).length;
1318
1380
  const tsCount = pendingIndices.filter((e) => /\.(ts|tsx|js|jsx)$/.test(e.relativePath)).length;
1319
1381
  const goCount = pendingIndices.filter((e) => e.relativePath.endsWith(".go")).length;
1382
+ const javaCount = pendingIndices.filter((e) => e.relativePath.endsWith(".java")).length;
1320
1383
  console.error(`[ctxo] Index complete: ${processed} files indexed`);
1321
1384
  if (tsCount > 0) console.error(`[ctxo] TypeScript/JS: ${tsCount} files (full tier)`);
1322
1385
  if (csCount > 0) console.error(`[ctxo] C#: ${csCount} files (${csharpTier} tier${csharpTier === "syntax" ? " - .NET SDK 8+ for full analysis" : ""})`);
1323
1386
  if (goCount > 0) console.error(`[ctxo] Go: ${goCount} files (${goTier} tier${goTier === "syntax" ? " - Go 1.22+ for full analysis" : ""})`);
1387
+ if (javaCount > 0) console.error(`[ctxo] Java: ${javaCount} files (${javaTier} tier${javaTier === "syntax" ? ' - JRE 17+ & "ctxo install java --full-tier" for full analysis' : ""})`);
1324
1388
  if (roslynAdapter) await roslynAdapter.dispose();
1325
1389
  }
1326
1390
  async autoInstallMissingPlugins() {
@@ -1356,6 +1420,7 @@ var IndexCommand = class {
1356
1420
  let roslynAdapter = null;
1357
1421
  let csharpTier = "unavailable";
1358
1422
  let goTier = "unavailable";
1423
+ let javaTier = "unavailable";
1359
1424
  for (const { plugin, adapter } of loaded) {
1360
1425
  if (typeof adapter.initialize === "function") {
1361
1426
  try {
@@ -1379,11 +1444,15 @@ var IndexCommand = class {
1379
1444
  if (plugin.id === "go" && typeof goCandidate.getTier === "function") {
1380
1445
  goTier = goCandidate.getTier();
1381
1446
  }
1382
- const activeTier = plugin.id === "csharp" ? csharpTier : plugin.id === "go" ? goTier : plugin.tier;
1447
+ const javaCandidate = adapter;
1448
+ if (plugin.id === "java" && typeof javaCandidate.getTier === "function") {
1449
+ javaTier = javaCandidate.getTier();
1450
+ }
1451
+ const activeTier = plugin.id === "csharp" ? csharpTier : plugin.id === "go" ? goTier : plugin.id === "java" ? javaTier : plugin.tier;
1383
1452
  const tierSuffix = activeTier !== plugin.tier ? ` (active: ${activeTier})` : "";
1384
1453
  console.error(`[ctxo] Plugin ${plugin.id}@${plugin.version} (${plugin.tier} tier${tierSuffix}) \u2014 ${plugin.extensions.join(", ")}`);
1385
1454
  }
1386
- return { tsMorphLike, roslynAdapter, csharpTier, goTier };
1455
+ return { tsMorphLike, roslynAdapter, csharpTier, goTier, javaTier };
1387
1456
  }
1388
1457
  discoverFilesIn(root, ignoreFile = () => false) {
1389
1458
  try {
@@ -1392,18 +1461,18 @@ var IndexCommand = class {
1392
1461
  encoding: "utf-8",
1393
1462
  maxBuffer: 10 * 1024 * 1024
1394
1463
  });
1395
- return output.split("\n").map((line) => line.trim()).filter((line) => line.length > 0).filter((line) => this.isSupportedExtension(line)).map((line) => join8(root, line)).filter((abs) => {
1464
+ return output.split("\n").map((line) => line.trim()).filter((line) => line.length > 0).filter((line) => this.isSupportedExtension(line)).map((line) => join9(root, line)).filter((abs) => {
1396
1465
  const rel = relative(this.projectRoot, abs).replace(/\\/g, "/");
1397
1466
  return !ignoreFile(rel);
1398
- }).filter((abs) => existsSync9(abs));
1467
+ }).filter((abs) => existsSync10(abs));
1399
1468
  } catch {
1400
1469
  console.error(`[ctxo] git ls-files failed for ${root}`);
1401
1470
  return [];
1402
1471
  }
1403
1472
  }
1404
1473
  discoverWorkspaces(ignoreProject = () => false) {
1405
- const pkgPath = join8(this.projectRoot, "package.json");
1406
- if (!existsSync9(pkgPath)) return [this.projectRoot];
1474
+ const pkgPath = join9(this.projectRoot, "package.json");
1475
+ if (!existsSync10(pkgPath)) return [this.projectRoot];
1407
1476
  try {
1408
1477
  const pkg = JSON.parse(readFileSync6(pkgPath, "utf-8"));
1409
1478
  const workspaces = Array.isArray(pkg.workspaces) ? pkg.workspaces : pkg.workspaces?.packages;
@@ -1411,17 +1480,17 @@ var IndexCommand = class {
1411
1480
  const resolved = [];
1412
1481
  for (const ws of workspaces) {
1413
1482
  if (ws.endsWith("/*") || ws.endsWith("\\*")) {
1414
- const parentDir = join8(this.projectRoot, ws.slice(0, -2));
1415
- if (existsSync9(parentDir)) {
1483
+ const parentDir = join9(this.projectRoot, ws.slice(0, -2));
1484
+ if (existsSync10(parentDir)) {
1416
1485
  for (const entry of readdirSync2(parentDir, { withFileTypes: true })) {
1417
1486
  if (entry.isDirectory()) {
1418
- resolved.push(join8(parentDir, entry.name));
1487
+ resolved.push(join9(parentDir, entry.name));
1419
1488
  }
1420
1489
  }
1421
1490
  }
1422
1491
  } else {
1423
- const wsPath = join8(this.projectRoot, ws);
1424
- if (existsSync9(wsPath)) {
1492
+ const wsPath = join9(this.projectRoot, ws);
1493
+ if (existsSync10(wsPath)) {
1425
1494
  resolved.push(wsPath);
1426
1495
  }
1427
1496
  }
@@ -1454,7 +1523,7 @@ var IndexCommand = class {
1454
1523
  if (_roslyn) await _roslyn.dispose();
1455
1524
  const hasher = new ContentHasher();
1456
1525
  const files = this.discoverFilesIn(this.projectRoot);
1457
- const reader = new (await import("./json-index-reader-XOEE6VMD.js")).JsonIndexReader(this.ctxoRoot);
1526
+ const reader = new (await import("./json-index-reader-UYS74USB.js")).JsonIndexReader(this.ctxoRoot);
1458
1527
  const indices = reader.readAll();
1459
1528
  const indexedMap = new Map(indices.map((i) => [i.file, i]));
1460
1529
  let staleCount = 0;
@@ -1467,7 +1536,7 @@ var IndexCommand = class {
1467
1536
  staleCount++;
1468
1537
  continue;
1469
1538
  }
1470
- if (!existsSync9(filePath)) {
1539
+ if (!existsSync10(filePath)) {
1471
1540
  console.error(`[ctxo] DELETED: ${relativePath}`);
1472
1541
  staleCount++;
1473
1542
  continue;
@@ -1493,13 +1562,13 @@ var IndexCommand = class {
1493
1562
  console.error("[ctxo] Index is up to date");
1494
1563
  }
1495
1564
  ensureGitignore() {
1496
- const gitignorePath = join8(this.projectRoot, ".gitignore");
1565
+ const gitignorePath = join9(this.projectRoot, ".gitignore");
1497
1566
  const cachePattern = ".ctxo/.cache/";
1498
1567
  const suffix = `
1499
1568
  # Ctxo local cache (never committed)
1500
1569
  ${cachePattern}
1501
1570
  `;
1502
- const existing = existsSync9(gitignorePath) ? readFileSync6(gitignorePath, "utf-8") : "";
1571
+ const existing = existsSync10(gitignorePath) ? readFileSync6(gitignorePath, "utf-8") : "";
1503
1572
  if (existing.includes(cachePattern)) return;
1504
1573
  writeFileSync2(gitignorePath, existing + suffix, "utf-8");
1505
1574
  console.error("[ctxo] Added .ctxo/.cache/ to .gitignore");
@@ -1618,8 +1687,8 @@ ${cachePattern}
1618
1687
  }
1619
1688
  };
1620
1689
  function pickEdgeQuality(tiers) {
1621
- const hasSyntaxOnly = tiers.csharpTier === "syntax" || tiers.goTier === "syntax";
1622
- const hasFull = tiers.tsCount > 0 || tiers.csharpTier === "full" || tiers.goTier === "full";
1690
+ const hasSyntaxOnly = tiers.csharpTier === "syntax" || tiers.goTier === "syntax" || tiers.javaTier === "syntax";
1691
+ const hasFull = tiers.tsCount > 0 || tiers.csharpTier === "full" || tiers.goTier === "full" || tiers.javaTier === "full";
1623
1692
  if (hasSyntaxOnly && hasFull) return "mixed";
1624
1693
  if (hasSyntaxOnly) return "syntax-only";
1625
1694
  return "full";
@@ -1644,11 +1713,11 @@ function countCommunities(snapshot) {
1644
1713
  }
1645
1714
 
1646
1715
  // src/cli/sync-command.ts
1647
- import { join as join9 } from "path";
1716
+ import { join as join10 } from "path";
1648
1717
  var SyncCommand = class {
1649
1718
  ctxoRoot;
1650
1719
  constructor(projectRoot) {
1651
- this.ctxoRoot = join9(projectRoot, ".ctxo");
1720
+ this.ctxoRoot = join10(projectRoot, ".ctxo");
1652
1721
  }
1653
1722
  async run() {
1654
1723
  console.error("[ctxo] Rebuilding SQLite cache from committed JSON index...");
@@ -1663,19 +1732,19 @@ var SyncCommand = class {
1663
1732
  };
1664
1733
 
1665
1734
  // src/cli/status-command.ts
1666
- import { join as join10 } from "path";
1667
- import { existsSync as existsSync10 } from "fs";
1735
+ import { join as join11 } from "path";
1736
+ import { existsSync as existsSync11 } from "fs";
1668
1737
  import { execFileSync as execFileSync3 } from "child_process";
1669
1738
  var StatusCommand = class {
1670
1739
  projectRoot;
1671
1740
  ctxoRoot;
1672
1741
  constructor(projectRoot) {
1673
1742
  this.projectRoot = projectRoot;
1674
- this.ctxoRoot = join10(projectRoot, ".ctxo");
1743
+ this.ctxoRoot = join11(projectRoot, ".ctxo");
1675
1744
  }
1676
1745
  run() {
1677
- const indexDir = join10(this.ctxoRoot, "index");
1678
- if (!existsSync10(indexDir)) {
1746
+ const indexDir = join11(this.ctxoRoot, "index");
1747
+ if (!existsSync11(indexDir)) {
1679
1748
  console.error('[ctxo] No index found. Run "ctxo index" first.');
1680
1749
  return;
1681
1750
  }
@@ -1690,7 +1759,7 @@ var StatusCommand = class {
1690
1759
  console.error(` Indexed files: ${indices.length}`);
1691
1760
  console.error(` Total symbols: ${totalSymbols}`);
1692
1761
  console.error(` Total edges: ${totalEdges}`);
1693
- const cacheExists = existsSync10(join10(this.ctxoRoot, ".cache", "symbols.db"));
1762
+ const cacheExists = existsSync11(join11(this.ctxoRoot, ".cache", "symbols.db"));
1694
1763
  console.error(` SQLite cache: ${cacheExists ? "present" : "missing (run ctxo sync)"}`);
1695
1764
  if (indices.length > 0) {
1696
1765
  console.error("");
@@ -1719,7 +1788,7 @@ var StatusCommand = class {
1719
1788
 
1720
1789
  // src/cli/verify-command.ts
1721
1790
  import { mkdtempSync, rmSync } from "fs";
1722
- import { join as join11 } from "path";
1791
+ import { join as join12 } from "path";
1723
1792
  import { tmpdir as tmpdir2 } from "os";
1724
1793
  var VerifyCommand = class {
1725
1794
  projectRoot;
@@ -1728,12 +1797,12 @@ var VerifyCommand = class {
1728
1797
  }
1729
1798
  async run() {
1730
1799
  console.error("[ctxo] Verifying index freshness...");
1731
- const tempDir = mkdtempSync(join11(tmpdir2(), "ctxo-verify-"));
1800
+ const tempDir = mkdtempSync(join12(tmpdir2(), "ctxo-verify-"));
1732
1801
  try {
1733
- const tempCtxo = join11(tempDir, ".ctxo");
1802
+ const tempCtxo = join12(tempDir, ".ctxo");
1734
1803
  const indexCmd = new IndexCommand(this.projectRoot, tempCtxo);
1735
1804
  await indexCmd.run({ skipSideEffects: true });
1736
- const committedReader = new JsonIndexReader(join11(this.projectRoot, ".ctxo"));
1805
+ const committedReader = new JsonIndexReader(join12(this.projectRoot, ".ctxo"));
1737
1806
  const freshReader = new JsonIndexReader(tempCtxo);
1738
1807
  const committedIndices = committedReader.readAll();
1739
1808
  const freshIndices = freshReader.readAll();
@@ -1766,20 +1835,20 @@ var VerifyCommand = class {
1766
1835
  };
1767
1836
 
1768
1837
  // src/cli/init-command.ts
1769
- import { join as join13 } from "path";
1770
- import { existsSync as existsSync12, readFileSync as readFileSync8, writeFileSync as writeFileSync4, mkdirSync as mkdirSync5, chmodSync } from "fs";
1838
+ import { join as join14 } from "path";
1839
+ import { existsSync as existsSync13, readFileSync as readFileSync8, writeFileSync as writeFileSync4, mkdirSync as mkdirSync5, chmodSync } from "fs";
1771
1840
  import { createRequire as createRequire4 } from "module";
1772
1841
 
1773
1842
  // src/cli/ai-rules.ts
1774
- import { existsSync as existsSync11, readFileSync as readFileSync7, writeFileSync as writeFileSync3, mkdirSync as mkdirSync4 } from "fs";
1775
- import { join as join12, dirname as dirname6 } from "path";
1843
+ import { existsSync as existsSync12, readFileSync as readFileSync7, writeFileSync as writeFileSync3, mkdirSync as mkdirSync4 } from "fs";
1844
+ import { join as join13, dirname as dirname6 } from "path";
1776
1845
  var CTXO_HOOK_COMMAND = "ctxo gate-hook";
1777
1846
  function ensureClaudeHook(projectRoot) {
1778
- const filePath = join12(projectRoot, ".claude", "settings.json");
1847
+ const filePath = join13(projectRoot, ".claude", "settings.json");
1779
1848
  const dir = dirname6(filePath);
1780
- if (!existsSync11(dir)) mkdirSync4(dir, { recursive: true });
1849
+ if (!existsSync12(dir)) mkdirSync4(dir, { recursive: true });
1781
1850
  const ctxoEntry = { matcher: "Edit", hooks: [{ type: "command", command: CTXO_HOOK_COMMAND }] };
1782
- if (!existsSync11(filePath)) {
1851
+ if (!existsSync12(filePath)) {
1783
1852
  writeFileSync3(filePath, JSON.stringify({ hooks: { PreToolUse: [ctxoEntry] } }, null, 2) + "\n", "utf-8");
1784
1853
  return { file: ".claude/settings.json", action: "created" };
1785
1854
  }
@@ -1815,7 +1884,7 @@ var PLATFORMS = [
1815
1884
  function detectPlatforms(projectRoot) {
1816
1885
  const detected = /* @__PURE__ */ new Set();
1817
1886
  for (const p of PLATFORMS) {
1818
- if (p.detectPaths.some((d) => existsSync11(join12(projectRoot, d)))) {
1887
+ if (p.detectPaths.some((d) => existsSync12(join13(projectRoot, d)))) {
1819
1888
  detected.add(p.id);
1820
1889
  }
1821
1890
  }
@@ -1877,15 +1946,15 @@ function generateRules(platformId) {
1877
1946
  function installRules(projectRoot, platformId) {
1878
1947
  const platform = PLATFORMS.find((p) => p.id === platformId);
1879
1948
  if (!platform) throw new Error(`Unknown platform: ${platformId}`);
1880
- const filePath = join12(projectRoot, platform.file);
1949
+ const filePath = join13(projectRoot, platform.file);
1881
1950
  const content = generateRules(platformId);
1882
1951
  const dir = dirname6(filePath);
1883
- if (!existsSync11(dir)) mkdirSync4(dir, { recursive: true });
1952
+ if (!existsSync12(dir)) mkdirSync4(dir, { recursive: true });
1884
1953
  if (platform.mode === "create") {
1885
1954
  writeFileSync3(filePath, content, "utf-8");
1886
- return { file: platform.file, action: existsSync11(filePath) ? "updated" : "created" };
1955
+ return { file: platform.file, action: existsSync12(filePath) ? "updated" : "created" };
1887
1956
  }
1888
- if (!existsSync11(filePath)) {
1957
+ if (!existsSync12(filePath)) {
1889
1958
  writeFileSync3(filePath, content, "utf-8");
1890
1959
  return { file: platform.file, action: "created" };
1891
1960
  }
@@ -1909,15 +1978,15 @@ function escapeRegExp(s) {
1909
1978
  }
1910
1979
  var SKILLS = ["ctxo-understand", "ctxo-safe-edit", "ctxo-review-pr"];
1911
1980
  var SKILL_TARGET_DIR = {
1912
- "claude-code": (root, skill) => join12(root, ".claude", "skills", skill, "SKILL.md"),
1913
- cursor: (root, skill) => join12(root, ".cursor", "rules", `${skill}.mdc`)
1981
+ "claude-code": (root, skill) => join13(root, ".claude", "skills", skill, "SKILL.md"),
1982
+ cursor: (root, skill) => join13(root, ".cursor", "rules", `${skill}.mdc`)
1914
1983
  };
1915
1984
  function skillsSourceDir() {
1916
1985
  let dir = import.meta.dirname;
1917
1986
  for (let i = 0; i < 10; i++) {
1918
- const candidate = join12(dir, "skills");
1919
- if (existsSync11(join12(candidate, "ctxo-safe-edit", "SKILL.md"))) return candidate;
1920
- const parent = join12(dir, "..");
1987
+ const candidate = join13(dir, "skills");
1988
+ if (existsSync12(join13(candidate, "ctxo-safe-edit", "SKILL.md"))) return candidate;
1989
+ const parent = join13(dir, "..");
1921
1990
  if (parent === dir) break;
1922
1991
  dir = parent;
1923
1992
  }
@@ -1929,10 +1998,10 @@ function installSkills(projectRoot, platformId) {
1929
1998
  const src = skillsSourceDir();
1930
1999
  const out = [];
1931
2000
  for (const skill of SKILLS) {
1932
- const from = join12(src, skill, "SKILL.md");
2001
+ const from = join13(src, skill, "SKILL.md");
1933
2002
  const to = target(projectRoot, skill);
1934
2003
  mkdirSync4(dirname6(to), { recursive: true });
1935
- const existed = existsSync11(to);
2004
+ const existed = existsSync12(to);
1936
2005
  writeFileSync3(to, readFileSync7(from, "utf-8"), "utf-8");
1937
2006
  out.push({ file: to, action: existed ? "updated" : "created" });
1938
2007
  }
@@ -1940,8 +2009,8 @@ function installSkills(projectRoot, platformId) {
1940
2009
  }
1941
2010
  var GITIGNORE_ENTRY = ".ctxo/.cache/";
1942
2011
  function ensureGitignore(projectRoot) {
1943
- const filePath = join12(projectRoot, ".gitignore");
1944
- if (!existsSync11(filePath)) {
2012
+ const filePath = join13(projectRoot, ".gitignore");
2013
+ if (!existsSync12(filePath)) {
1945
2014
  writeFileSync3(filePath, `# Ctxo local cache (auto-rebuilt, never committed)
1946
2015
  ${GITIGNORE_ENTRY}
1947
2016
  `, "utf-8");
@@ -1982,10 +2051,10 @@ gate:
1982
2051
  sensitivity: balanced # strict | balanced | lenient
1983
2052
  `;
1984
2053
  function ensureConfig(projectRoot) {
1985
- const filePath = join12(projectRoot, ".ctxo", "config.yaml");
2054
+ const filePath = join13(projectRoot, ".ctxo", "config.yaml");
1986
2055
  const dir = dirname6(filePath);
1987
- if (!existsSync11(dir)) mkdirSync4(dir, { recursive: true });
1988
- if (existsSync11(filePath)) {
2056
+ if (!existsSync12(dir)) mkdirSync4(dir, { recursive: true });
2057
+ if (existsSync12(filePath)) {
1989
2058
  return { file: ".ctxo/config.yaml", action: "skipped" };
1990
2059
  }
1991
2060
  writeFileSync3(filePath, DEFAULT_CONFIG, "utf-8");
@@ -2020,11 +2089,11 @@ function getMcpConfigTargets(selectedPlatformIds) {
2020
2089
  return [...targets.values()];
2021
2090
  }
2022
2091
  function ensureMcpConfig(projectRoot, target) {
2023
- const filePath = join12(projectRoot, target.file);
2092
+ const filePath = join13(projectRoot, target.file);
2024
2093
  const dir = dirname6(filePath);
2025
- if (!existsSync11(dir)) mkdirSync4(dir, { recursive: true });
2094
+ if (!existsSync12(dir)) mkdirSync4(dir, { recursive: true });
2026
2095
  const entry = target.extraFields ? { ...target.extraFields, ...CTXO_MCP_ENTRY } : { ...CTXO_MCP_ENTRY };
2027
- if (!existsSync11(filePath)) {
2096
+ if (!existsSync12(filePath)) {
2028
2097
  const config = { [target.serverKey]: { ctxo: entry } };
2029
2098
  writeFileSync3(filePath, JSON.stringify(config, null, 2) + "\n", "utf-8");
2030
2099
  return { file: target.file, action: "created" };
@@ -2140,13 +2209,13 @@ var InitCommand = class {
2140
2209
  this.projectRoot = projectRoot;
2141
2210
  }
2142
2211
  installHooks() {
2143
- const hooksDir = join13(this.projectRoot, ".git", "hooks");
2212
+ const hooksDir = join14(this.projectRoot, ".git", "hooks");
2144
2213
  mkdirSync5(hooksDir, { recursive: true });
2145
2214
  this.installHook(hooksDir, "post-commit", POST_COMMIT_CONTENT);
2146
2215
  this.installHook(hooksDir, "post-merge", POST_MERGE_CONTENT);
2147
2216
  }
2148
2217
  async run(options = {}) {
2149
- if (!existsSync12(join13(this.projectRoot, ".git"))) {
2218
+ if (!existsSync13(join14(this.projectRoot, ".git"))) {
2150
2219
  console.error('[ctxo] Not a git repository. Run "git init" first.');
2151
2220
  process.exit(1);
2152
2221
  }
@@ -2183,8 +2252,8 @@ var InitCommand = class {
2183
2252
  clack.cancel("Setup cancelled.");
2184
2253
  process.exit(0);
2185
2254
  }
2186
- const fullPath = join13(this.projectRoot, indexDir);
2187
- if (!existsSync12(fullPath)) mkdirSync5(fullPath, { recursive: true });
2255
+ const fullPath = join14(this.projectRoot, indexDir);
2256
+ if (!existsSync13(fullPath)) mkdirSync5(fullPath, { recursive: true });
2188
2257
  console.error("");
2189
2258
  }
2190
2259
  stepNum++;
@@ -2360,8 +2429,8 @@ var InitCommand = class {
2360
2429
  const gitignoreResult = ensureGitignore(this.projectRoot);
2361
2430
  const configResult = ensureConfig(this.projectRoot);
2362
2431
  if (!options.rulesOnly) {
2363
- const indexDir = join13(this.projectRoot, ".ctxo", "index");
2364
- if (!existsSync12(indexDir)) mkdirSync5(indexDir, { recursive: true });
2432
+ const indexDir = join14(this.projectRoot, ".ctxo", "index");
2433
+ if (!existsSync13(indexDir)) mkdirSync5(indexDir, { recursive: true });
2365
2434
  console.error("[ctxo] \u2713 .ctxo/index/ ready");
2366
2435
  }
2367
2436
  if (configResult.action !== "skipped") {
@@ -2420,7 +2489,7 @@ var InitCommand = class {
2420
2489
  for (const id of toolIds) {
2421
2490
  const platform = PLATFORMS.find((p) => p.id === id);
2422
2491
  if (platform) {
2423
- const exists = existsSync12(join13(this.projectRoot, platform.file));
2492
+ const exists = existsSync13(join14(this.projectRoot, platform.file));
2424
2493
  const action = platform.mode === "append" && exists ? "append section" : "create";
2425
2494
  console.error(` ${platform.file.padEnd(42)} (${action})`);
2426
2495
  }
@@ -2439,21 +2508,21 @@ var InitCommand = class {
2439
2508
  /* ---------------------------------------------------------------- */
2440
2509
  async getVersion() {
2441
2510
  try {
2442
- const pkgPath = join13(this.projectRoot, "node_modules", "@ctxo", "cli", "package.json");
2443
- if (existsSync12(pkgPath)) {
2511
+ const pkgPath = join14(this.projectRoot, "node_modules", "@ctxo", "cli", "package.json");
2512
+ if (existsSync13(pkgPath)) {
2444
2513
  return JSON.parse(readFileSync8(pkgPath, "utf-8")).version;
2445
2514
  }
2446
- const { createRequire: createRequire8 } = await import("module");
2447
- const require4 = createRequire8(import.meta.url);
2448
- return require4("../../package.json").version;
2515
+ const { createRequire: createRequire9 } = await import("module");
2516
+ const require5 = createRequire9(import.meta.url);
2517
+ return require5("../../package.json").version;
2449
2518
  } catch {
2450
2519
  return void 0;
2451
2520
  }
2452
2521
  }
2453
2522
  installHook(hooksDir, hookName, hookContent) {
2454
- const hookPath = join13(hooksDir, hookName);
2523
+ const hookPath = join14(hooksDir, hookName);
2455
2524
  let existing = "";
2456
- if (existsSync12(hookPath)) {
2525
+ if (existsSync13(hookPath)) {
2457
2526
  existing = readFileSync8(hookPath, "utf-8");
2458
2527
  if (existing.includes(CTXO_START)) return;
2459
2528
  } else {
@@ -2466,7 +2535,7 @@ var InitCommand = class {
2466
2535
  };
2467
2536
 
2468
2537
  // src/cli/watch-command.ts
2469
- import { join as join14, extname as extname4, relative as relative2 } from "path";
2538
+ import { join as join15, extname as extname4, relative as relative2 } from "path";
2470
2539
  import { readFileSync as readFileSync9 } from "fs";
2471
2540
 
2472
2541
  // src/adapters/watcher/chokidar-watcher-adapter.ts
@@ -2516,12 +2585,12 @@ var WatchCommand = class {
2516
2585
  ctxoRoot;
2517
2586
  constructor(projectRoot) {
2518
2587
  this.projectRoot = projectRoot;
2519
- this.ctxoRoot = join14(projectRoot, ".ctxo");
2588
+ this.ctxoRoot = join15(projectRoot, ".ctxo");
2520
2589
  }
2521
2590
  async run() {
2522
2591
  console.error("[ctxo] Starting file watcher...");
2523
2592
  const registry = new LanguageAdapterRegistry();
2524
- let roslynAdapter = null;
2593
+ const keepAliveByExt = /* @__PURE__ */ new Map();
2525
2594
  for (const { plugin, adapter } of await loadPlugins(this.projectRoot)) {
2526
2595
  if (typeof adapter.initialize === "function") {
2527
2596
  try {
@@ -2532,15 +2601,12 @@ var WatchCommand = class {
2532
2601
  }
2533
2602
  }
2534
2603
  registry.register(plugin.extensions, adapter);
2535
- const csCandidate = adapter;
2536
- if (typeof csCandidate.getRoslynDelegate === "function") {
2537
- const roslyn = csCandidate.getRoslynDelegate();
2538
- if (roslyn?.isReady()) {
2539
- const started = await roslyn.startKeepAlive();
2540
- if (started) {
2541
- roslynAdapter = roslyn;
2542
- console.error("[ctxo] C# watch: Roslyn keep-alive active (full tier, <100ms per change)");
2543
- }
2604
+ const capable = adapter;
2605
+ if (typeof capable.getIncrementalReindex === "function") {
2606
+ const cap = capable.getIncrementalReindex();
2607
+ if (cap?.isReady() && await cap.startKeepAlive()) {
2608
+ for (const ext of plugin.extensions) keepAliveByExt.set(ext.toLowerCase(), cap);
2609
+ console.error(`[ctxo] ${plugin.id} watch: keep-alive active (full tier, fast incremental re-index)`);
2544
2610
  }
2545
2611
  }
2546
2612
  console.error(`[ctxo] Plugin ${plugin.id}@${plugin.version} (${plugin.tier} tier)`);
@@ -2561,8 +2627,9 @@ var WatchCommand = class {
2561
2627
  if (!adapter) return;
2562
2628
  const relativePath = relative2(this.projectRoot, filePath).replace(/\\/g, "/");
2563
2629
  try {
2564
- if (roslynAdapter?.isReady() && filePath.endsWith(".cs")) {
2565
- const result = await roslynAdapter.reindexFile(relativePath);
2630
+ const cap = keepAliveByExt.get(extname4(filePath).toLowerCase());
2631
+ if (cap?.isReady()) {
2632
+ const result = await cap.reindexFile(relativePath);
2566
2633
  if (result) {
2567
2634
  const commits2 = await gitAdapter.getCommitHistory(relativePath);
2568
2635
  const intent2 = commits2.map((c) => ({
@@ -2598,7 +2665,7 @@ var WatchCommand = class {
2598
2665
  };
2599
2666
  writer.write(fileIndex2);
2600
2667
  storage.writeSymbolFile(fileIndex2);
2601
- console.error(`[ctxo] Re-indexed (Roslyn): ${relativePath}`);
2668
+ console.error(`[ctxo] Re-indexed (keep-alive): ${relativePath}`);
2602
2669
  return;
2603
2670
  }
2604
2671
  }
@@ -2686,8 +2753,9 @@ var WatchCommand = class {
2686
2753
  clearTimeout(timeout);
2687
2754
  }
2688
2755
  if (communitySnapshotTimer) clearTimeout(communitySnapshotTimer);
2689
- const roslynShutdown = roslynAdapter ? roslynAdapter.dispose() : Promise.resolve();
2690
- roslynShutdown.then(() => {
2756
+ const uniques = new Set(keepAliveByExt.values());
2757
+ const shutdowns = Promise.all([...uniques].map((c) => c.dispose()));
2758
+ shutdowns.then(() => {
2691
2759
  watcher.stop().then(() => {
2692
2760
  storage.close();
2693
2761
  console.error("[ctxo] Watcher stopped");
@@ -2731,21 +2799,21 @@ function readHeadSha() {
2731
2799
  }
2732
2800
 
2733
2801
  // src/cli/stats-command.ts
2734
- import { join as join15 } from "path";
2735
- import { existsSync as existsSync13, readFileSync as readFileSync10 } from "fs";
2802
+ import { join as join16 } from "path";
2803
+ import { existsSync as existsSync14, readFileSync as readFileSync10 } from "fs";
2736
2804
  import initSqlJs from "sql.js";
2737
2805
  var StatsCommand = class {
2738
2806
  ctxoRoot;
2739
2807
  constructor(projectRoot) {
2740
- this.ctxoRoot = join15(projectRoot, ".ctxo");
2808
+ this.ctxoRoot = join16(projectRoot, ".ctxo");
2741
2809
  }
2742
2810
  async run(options) {
2743
2811
  if (!this.isStatsEnabled()) {
2744
2812
  console.error("[ctxo] Stats collection is disabled in .ctxo/config.yaml");
2745
2813
  return;
2746
2814
  }
2747
- const dbPath = join15(this.ctxoRoot, ".cache", "symbols.db");
2748
- if (!existsSync13(dbPath)) {
2815
+ const dbPath = join16(this.ctxoRoot, ".cache", "symbols.db");
2816
+ if (!existsSync14(dbPath)) {
2749
2817
  if (options.json) {
2750
2818
  process.stdout.write(JSON.stringify(this.emptyReport(options.days)) + "\n");
2751
2819
  } else {
@@ -2792,8 +2860,8 @@ var StatsCommand = class {
2792
2860
  }
2793
2861
  }
2794
2862
  isStatsEnabled() {
2795
- const configPath = join15(this.ctxoRoot, "config.yaml");
2796
- if (!existsSync13(configPath)) return true;
2863
+ const configPath = join16(this.ctxoRoot, "config.yaml");
2864
+ if (!existsSync14(configPath)) return true;
2797
2865
  try {
2798
2866
  const raw = readFileSync10(configPath, "utf-8");
2799
2867
  const match = raw.match(/stats:\s*\n\s*enabled:\s*(true|false)/);
@@ -2884,8 +2952,8 @@ var StatsCommand = class {
2884
2952
  };
2885
2953
 
2886
2954
  // src/cli/doctor-command.ts
2887
- import { join as join22 } from "path";
2888
- import { existsSync as existsSync20 } from "fs";
2955
+ import { join as join24 } from "path";
2956
+ import { existsSync as existsSync22 } from "fs";
2889
2957
 
2890
2958
  // src/adapters/diagnostics/health-checker.ts
2891
2959
  var log4 = createLogger("ctxo:doctor");
@@ -3220,10 +3288,111 @@ function pluginResolves(packageName) {
3220
3288
  }
3221
3289
  }
3222
3290
 
3291
+ // src/adapters/diagnostics/checks/java-tier-check.ts
3292
+ import { createRequire as createRequire7 } from "module";
3293
+ import { existsSync as existsSync15, readdirSync as readdirSync3 } from "fs";
3294
+ import { join as join17, dirname as dirname7 } from "path";
3295
+ var require4 = createRequire7(import.meta.url);
3296
+ function evaluateJavaTier(i) {
3297
+ const id = "java_tier";
3298
+ const title = "Java analysis tier";
3299
+ if (!i.hasJava) {
3300
+ return { id, title, status: "pass", message: "no Java sources detected" };
3301
+ }
3302
+ const jreOk = (i.jreMajor ?? 0) >= 17;
3303
+ if (jreOk && i.analyzerInstalled) {
3304
+ return { id, title, status: "pass", message: `full tier (JRE ${i.jreMajor}, analyzer installed)` };
3305
+ }
3306
+ if (jreOk && !i.analyzerInstalled) {
3307
+ return {
3308
+ id,
3309
+ title,
3310
+ status: "warn",
3311
+ message: `syntax tier (JRE ${i.jreMajor} present, analyzer missing)`,
3312
+ fix: 'Run "ctxo install java --full-tier" for resolved call/use edges'
3313
+ };
3314
+ }
3315
+ if (!jreOk && i.analyzerInstalled) {
3316
+ return {
3317
+ id,
3318
+ title,
3319
+ status: "warn",
3320
+ message: "syntax tier (analyzer installed but no JRE 17+)",
3321
+ fix: "Install a JRE 17+ and ensure it is on PATH (or set JAVA_HOME)"
3322
+ };
3323
+ }
3324
+ return {
3325
+ id,
3326
+ title,
3327
+ status: "pass",
3328
+ message: 'syntax tier (install JRE 17+ and run "ctxo install java --full-tier" for full tier)'
3329
+ };
3330
+ }
3331
+ function analyzerJarPresent() {
3332
+ try {
3333
+ const pkgJson = require4.resolve("@ctxo/lang-java-analyzer/package.json");
3334
+ const jarPath = join17(dirname7(pkgJson), "jar", "ctxo-jdt-analyzer.jar");
3335
+ return existsSync15(jarPath);
3336
+ } catch {
3337
+ return false;
3338
+ }
3339
+ }
3340
+ var SKIP_DIRS = /* @__PURE__ */ new Set([
3341
+ "node_modules",
3342
+ "target",
3343
+ "build",
3344
+ "out",
3345
+ "bin",
3346
+ ".git",
3347
+ ".ctxo",
3348
+ "dist",
3349
+ "coverage"
3350
+ ]);
3351
+ function findJavaFile(root, maxDepth) {
3352
+ function walk(dir, depth) {
3353
+ let entries;
3354
+ try {
3355
+ entries = readdirSync3(dir, { withFileTypes: true });
3356
+ } catch {
3357
+ return false;
3358
+ }
3359
+ for (const entry of entries) {
3360
+ if (entry.isSymbolicLink()) continue;
3361
+ if (entry.isFile() && entry.name.endsWith(".java")) return true;
3362
+ }
3363
+ if (depth >= maxDepth) return false;
3364
+ for (const entry of entries) {
3365
+ if (entry.isSymbolicLink()) continue;
3366
+ if (entry.isDirectory() && !SKIP_DIRS.has(entry.name)) {
3367
+ if (walk(join17(dir, entry.name), depth + 1)) return true;
3368
+ }
3369
+ }
3370
+ return false;
3371
+ }
3372
+ return walk(root, 0);
3373
+ }
3374
+ function projectHasJava(root) {
3375
+ for (const f of ["pom.xml", "build.gradle", "build.gradle.kts"]) {
3376
+ if (existsSync15(join17(root, f))) return true;
3377
+ }
3378
+ return findJavaFile(root, 4);
3379
+ }
3380
+ var JavaTierCheck = class {
3381
+ id = "java_tier";
3382
+ title = "Java analysis tier";
3383
+ async run(ctx) {
3384
+ return evaluateJavaTier({
3385
+ hasJava: projectHasJava(ctx.projectRoot),
3386
+ jreMajor: detectJavaMajor(),
3387
+ analyzerInstalled: analyzerJarPresent()
3388
+ });
3389
+ }
3390
+ };
3391
+
3223
3392
  // src/adapters/diagnostics/checks/git-check.ts
3224
3393
  import { execFileSync as execFileSync5 } from "child_process";
3225
- import { existsSync as existsSync14 } from "fs";
3226
- import { join as join16 } from "path";
3394
+ import { existsSync as existsSync16 } from "fs";
3395
+ import { join as join18 } from "path";
3227
3396
  var GitBinaryCheck = class {
3228
3397
  id = "git_binary";
3229
3398
  title = "Git available";
@@ -3240,7 +3409,7 @@ var GitRepoCheck = class {
3240
3409
  id = "git_repo";
3241
3410
  title = "Git repository";
3242
3411
  async run(ctx) {
3243
- if (existsSync14(join16(ctx.projectRoot, ".git"))) {
3412
+ if (existsSync16(join18(ctx.projectRoot, ".git"))) {
3244
3413
  return { id: this.id, title: this.title, status: "pass", message: ctx.projectRoot, value: ctx.projectRoot };
3245
3414
  }
3246
3415
  return { id: this.id, title: this.title, status: "fail", message: "Not a git repository", fix: 'Run "git init" or clone a repository' };
@@ -3248,9 +3417,9 @@ var GitRepoCheck = class {
3248
3417
  };
3249
3418
 
3250
3419
  // src/adapters/diagnostics/checks/index-check.ts
3251
- import { existsSync as existsSync15 } from "fs";
3420
+ import { existsSync as existsSync17 } from "fs";
3252
3421
  import { execFileSync as execFileSync6 } from "child_process";
3253
- import { join as join17 } from "path";
3422
+ import { join as join19 } from "path";
3254
3423
  function pass2(id, title, message, value) {
3255
3424
  return { id, title, status: "pass", message, value };
3256
3425
  }
@@ -3267,8 +3436,8 @@ var IndexDirectoryCheck = class {
3267
3436
  id = "index_directory";
3268
3437
  title = "Index directory";
3269
3438
  async run(ctx) {
3270
- const indexDir = join17(ctx.ctxoRoot, "index");
3271
- if (!existsSync15(indexDir)) {
3439
+ const indexDir = join19(ctx.ctxoRoot, "index");
3440
+ if (!existsSync17(indexDir)) {
3272
3441
  return fail(this.id, this.title, "No index directory", 'Run "ctxo index"');
3273
3442
  }
3274
3443
  const indices = getIndices(ctx);
@@ -3362,8 +3531,8 @@ var CoChangesCacheCheck = class {
3362
3531
  id = "co_changes_cache";
3363
3532
  title = "Co-changes cache";
3364
3533
  async run(ctx) {
3365
- const coChangesPath = join17(ctx.ctxoRoot, "index", "co-changes.json");
3366
- if (existsSync15(coChangesPath)) {
3534
+ const coChangesPath = join19(ctx.ctxoRoot, "index", "co-changes.json");
3535
+ if (existsSync17(coChangesPath)) {
3367
3536
  return pass2(this.id, this.title, "present");
3368
3537
  }
3369
3538
  return warn2(this.id, this.title, "missing (co-change analysis disabled)", 'Run "ctxo index" with git history');
@@ -3387,14 +3556,14 @@ var SchemaVersionCheck = class {
3387
3556
  };
3388
3557
 
3389
3558
  // src/adapters/diagnostics/checks/storage-check.ts
3390
- import { existsSync as existsSync16, readFileSync as readFileSync11 } from "fs";
3391
- import { join as join18 } from "path";
3559
+ import { existsSync as existsSync18, readFileSync as readFileSync11 } from "fs";
3560
+ import { join as join20 } from "path";
3392
3561
  var SqliteCacheCheck = class {
3393
3562
  id = "sqlite_cache";
3394
3563
  title = "SQLite cache";
3395
3564
  async run(ctx) {
3396
- const dbPath = join18(ctx.ctxoRoot, ".cache", "symbols.db");
3397
- if (!existsSync16(dbPath)) {
3565
+ const dbPath = join20(ctx.ctxoRoot, ".cache", "symbols.db");
3566
+ if (!existsSync18(dbPath)) {
3398
3567
  return { id: this.id, title: this.title, status: "warn", message: "SQLite cache missing", fix: 'Run "ctxo sync" to rebuild' };
3399
3568
  }
3400
3569
  try {
@@ -3416,14 +3585,14 @@ var SqliteCacheCheck = class {
3416
3585
  };
3417
3586
 
3418
3587
  // src/adapters/diagnostics/checks/config-check.ts
3419
- import { existsSync as existsSync17, readFileSync as readFileSync12 } from "fs";
3420
- import { join as join19 } from "path";
3588
+ import { existsSync as existsSync19, readFileSync as readFileSync12 } from "fs";
3589
+ import { join as join21 } from "path";
3421
3590
  var ConfigFileCheck = class {
3422
3591
  id = "config_file";
3423
3592
  title = "Config file";
3424
3593
  async run(ctx) {
3425
- const configPath = join19(ctx.ctxoRoot, "config.yaml");
3426
- if (!existsSync17(configPath)) {
3594
+ const configPath = join21(ctx.ctxoRoot, "config.yaml");
3595
+ if (!existsSync19(configPath)) {
3427
3596
  return { id: this.id, title: this.title, status: "warn", message: "No config.yaml (using defaults)" };
3428
3597
  }
3429
3598
  try {
@@ -3461,8 +3630,8 @@ var ConfigFileCheck = class {
3461
3630
  };
3462
3631
 
3463
3632
  // src/adapters/diagnostics/checks/disk-check.ts
3464
- import { existsSync as existsSync18, readdirSync as readdirSync3, statSync as statSync2 } from "fs";
3465
- import { join as join20 } from "path";
3633
+ import { existsSync as existsSync20, readdirSync as readdirSync4, statSync as statSync2 } from "fs";
3634
+ import { join as join22 } from "path";
3466
3635
  var DEFAULT_THRESHOLDS = { warnMb: 100, failMb: 500 };
3467
3636
  var DiskUsageCheck = class {
3468
3637
  id = "disk_usage";
@@ -3472,7 +3641,7 @@ var DiskUsageCheck = class {
3472
3641
  this.thresholds = thresholds;
3473
3642
  }
3474
3643
  async run(ctx) {
3475
- if (!existsSync18(ctx.ctxoRoot)) {
3644
+ if (!existsSync20(ctx.ctxoRoot)) {
3476
3645
  return { id: this.id, title: this.title, status: "pass", message: "no .ctxo/ directory", value: "0" };
3477
3646
  }
3478
3647
  const bytes = this.getDirSize(ctx.ctxoRoot);
@@ -3489,12 +3658,12 @@ var DiskUsageCheck = class {
3489
3658
  getDirSize(dirPath) {
3490
3659
  let total = 0;
3491
3660
  try {
3492
- const entries = readdirSync3(dirPath, { withFileTypes: true, recursive: true });
3661
+ const entries = readdirSync4(dirPath, { withFileTypes: true, recursive: true });
3493
3662
  for (const entry of entries) {
3494
3663
  if (entry.isFile()) {
3495
3664
  try {
3496
3665
  const parent = entry.parentPath ?? entry.path ?? dirPath;
3497
- total += statSync2(join20(parent, entry.name)).size;
3666
+ total += statSync2(join22(parent, entry.name)).size;
3498
3667
  } catch {
3499
3668
  }
3500
3669
  }
@@ -3507,9 +3676,9 @@ var DiskUsageCheck = class {
3507
3676
 
3508
3677
  // src/cli/doctor-fix.ts
3509
3678
  import { execFileSync as execFileSync7 } from "child_process";
3510
- import { appendFileSync, existsSync as existsSync19, mkdirSync as mkdirSync6, rmSync as rmSync2, unlinkSync as unlinkSync2 } from "fs";
3511
- import { createRequire as createRequire7 } from "module";
3512
- import { dirname as dirname7, join as join21 } from "path";
3679
+ import { appendFileSync, existsSync as existsSync21, mkdirSync as mkdirSync6, rmSync as rmSync2, unlinkSync as unlinkSync2 } from "fs";
3680
+ import { createRequire as createRequire8 } from "module";
3681
+ import { dirname as dirname8, join as join23 } from "path";
3513
3682
  async function applyFixes(report, options) {
3514
3683
  if (isCI(process.env) && !options.yes && !options.dryRun) {
3515
3684
  return {
@@ -3644,8 +3813,8 @@ async function fixIndex(projectRoot, dryRun) {
3644
3813
  }
3645
3814
  async function pruneOrphans(projectRoot, ctxoRoot, dryRun) {
3646
3815
  if (dryRun) return;
3647
- const indexDir = join21(ctxoRoot, "index");
3648
- if (!existsSync19(indexDir)) return;
3816
+ const indexDir = join23(ctxoRoot, "index");
3817
+ if (!existsSync21(indexDir)) return;
3649
3818
  const sourceFiles = listGitFiles2(projectRoot);
3650
3819
  if (sourceFiles.size === 0) return;
3651
3820
  const reader = new JsonIndexReader(ctxoRoot);
@@ -3653,9 +3822,9 @@ async function pruneOrphans(projectRoot, ctxoRoot, dryRun) {
3653
3822
  let removed = 0;
3654
3823
  for (const idx of indices) {
3655
3824
  if (sourceFiles.has(idx.file)) continue;
3656
- const jsonPath = join21(indexDir, `${idx.file}.json`);
3825
+ const jsonPath = join23(indexDir, `${idx.file}.json`);
3657
3826
  try {
3658
- if (existsSync19(jsonPath)) {
3827
+ if (existsSync21(jsonPath)) {
3659
3828
  unlinkSync2(jsonPath);
3660
3829
  removed += 1;
3661
3830
  pruneEmptyParents(jsonPath, indexDir);
@@ -3666,14 +3835,14 @@ async function pruneOrphans(projectRoot, ctxoRoot, dryRun) {
3666
3835
  if (removed === 0) return;
3667
3836
  }
3668
3837
  function pruneEmptyParents(filePath, stopAt) {
3669
- let dir = dirname7(filePath);
3838
+ let dir = dirname8(filePath);
3670
3839
  while (dir !== stopAt && dir.startsWith(stopAt)) {
3671
3840
  try {
3672
3841
  rmSync2(dir, { recursive: false });
3673
3842
  } catch {
3674
3843
  return;
3675
3844
  }
3676
- dir = dirname7(dir);
3845
+ dir = dirname8(dir);
3677
3846
  }
3678
3847
  }
3679
3848
  function listGitFiles2(projectRoot) {
@@ -3689,12 +3858,12 @@ function listGitFiles2(projectRoot) {
3689
3858
  }
3690
3859
  }
3691
3860
  async function fixSqliteCache(projectRoot, ctxoRoot, dryRun) {
3692
- const cacheDir = join21(ctxoRoot, ".cache");
3861
+ const cacheDir = join23(ctxoRoot, ".cache");
3693
3862
  if (dryRun) return;
3694
- if (existsSync19(cacheDir)) rmSync2(cacheDir, { recursive: true, force: true });
3863
+ if (existsSync21(cacheDir)) rmSync2(cacheDir, { recursive: true, force: true });
3695
3864
  await new SyncCommand(projectRoot).run();
3696
3865
  }
3697
- var doctorRequire = createRequire7(import.meta.url);
3866
+ var doctorRequire = createRequire8(import.meta.url);
3698
3867
  function canResolvePlugin(lang) {
3699
3868
  try {
3700
3869
  doctorRequire.resolve(`${officialPluginFor(lang)}/package.json`);
@@ -3708,8 +3877,8 @@ function isCI(env) {
3708
3877
  }
3709
3878
  function writeLog(ctxoRoot, attempts, halted, reason) {
3710
3879
  try {
3711
- if (!existsSync19(ctxoRoot)) mkdirSync6(ctxoRoot, { recursive: true });
3712
- const path = join21(ctxoRoot, "doctor-fix.log");
3880
+ if (!existsSync21(ctxoRoot)) mkdirSync6(ctxoRoot, { recursive: true });
3881
+ const path = join23(ctxoRoot, "doctor-fix.log");
3713
3882
  const ts = (/* @__PURE__ */ new Date()).toISOString();
3714
3883
  const lines = [`# ${ts} doctor --fix${halted ? " (halted: " + reason + ")" : ""}`];
3715
3884
  for (const a of attempts) {
@@ -3742,10 +3911,10 @@ var DoctorCommand = class {
3742
3911
  ctxoRoot;
3743
3912
  constructor(projectRoot) {
3744
3913
  this.projectRoot = projectRoot;
3745
- this.ctxoRoot = join22(projectRoot, ".ctxo");
3914
+ this.ctxoRoot = join24(projectRoot, ".ctxo");
3746
3915
  }
3747
3916
  async run(options = {}) {
3748
- const { getVersion: getVersion2 } = await import("./cli-router-45W4IRYE.js");
3917
+ const { getVersion: getVersion2 } = await import("./cli-router-2JVPBW4S.js");
3749
3918
  console.error(`ctxo v${getVersion2()} \u2014 health check
3750
3919
  `);
3751
3920
  const checks = [
@@ -3760,6 +3929,7 @@ var DoctorCommand = class {
3760
3929
  new TsMorphCheck(),
3761
3930
  new TreeSitterCheck(),
3762
3931
  new LanguageCoverageCheck(),
3932
+ new JavaTierCheck(),
3763
3933
  new DiskUsageCheck(),
3764
3934
  new SymbolCountCheck(),
3765
3935
  new EdgeCountCheck(),
@@ -3767,8 +3937,8 @@ var DoctorCommand = class {
3767
3937
  new CoChangesCacheCheck(),
3768
3938
  new SchemaVersionCheck()
3769
3939
  ];
3770
- const indexDir = join22(this.ctxoRoot, "index");
3771
- const indices = existsSync20(indexDir) ? new JsonIndexReader(this.ctxoRoot).readAll() : [];
3940
+ const indexDir = join24(this.ctxoRoot, "index");
3941
+ const indices = existsSync22(indexDir) ? new JsonIndexReader(this.ctxoRoot).readAll() : [];
3772
3942
  const checker = new HealthChecker(checks, { quiet: Boolean(options.quiet) });
3773
3943
  const report = await checker.runAll({
3774
3944
  projectRoot: this.projectRoot,
@@ -3809,8 +3979,8 @@ var DoctorCommand = class {
3809
3979
  };
3810
3980
 
3811
3981
  // src/cli/visualize-command.ts
3812
- import { readFileSync as readFileSync13, writeFileSync as writeFileSync5, existsSync as existsSync21 } from "fs";
3813
- import { join as join23, basename } from "path";
3982
+ import { readFileSync as readFileSync13, writeFileSync as writeFileSync5, existsSync as existsSync23 } from "fs";
3983
+ import { join as join25, basename } from "path";
3814
3984
  import { execFile } from "child_process";
3815
3985
 
3816
3986
  // src/cli/graph-loader.ts
@@ -3826,7 +3996,7 @@ function loadGraph(ctxoRoot) {
3826
3996
  var VisualizeCommand = class {
3827
3997
  constructor(projectRoot) {
3828
3998
  this.projectRoot = projectRoot;
3829
- this.ctxoRoot = join23(projectRoot, ".ctxo");
3999
+ this.ctxoRoot = join25(projectRoot, ".ctxo");
3830
4000
  }
3831
4001
  projectRoot;
3832
4002
  ctxoRoot;
@@ -3969,7 +4139,7 @@ var VisualizeCommand = class {
3969
4139
  const templatePath = this.getTemplatePath();
3970
4140
  const template = readFileSync13(templatePath, "utf-8");
3971
4141
  const html = template.replace("/*__CTXO_DATA__*/null", JSON.stringify(payload));
3972
- const outputPath = options.output ?? join23(this.ctxoRoot, "visualize.html");
4142
+ const outputPath = options.output ?? join25(this.ctxoRoot, "visualize.html");
3973
4143
  writeFileSync5(outputPath, html, "utf-8");
3974
4144
  console.error(`[ctxo] Wrote visualization to ${outputPath}`);
3975
4145
  console.error(`[ctxo] ${nodes.length} symbols, ${edges.length} edges, ${deadResult.deadSymbols.length} dead`);
@@ -3980,9 +4150,9 @@ var VisualizeCommand = class {
3980
4150
  getTemplatePath() {
3981
4151
  let dir = import.meta.dirname;
3982
4152
  for (let i = 0; i < 10; i++) {
3983
- const candidate = join23(dir, "pages", "visualize-template.html");
3984
- if (existsSync21(candidate)) return candidate;
3985
- const parent = join23(dir, "..");
4153
+ const candidate = join25(dir, "pages", "visualize-template.html");
4154
+ if (existsSync23(candidate)) return candidate;
4155
+ const parent = join25(dir, "..");
3986
4156
  if (parent === dir) break;
3987
4157
  dir = parent;
3988
4158
  }
@@ -4006,8 +4176,8 @@ var VisualizeCommand = class {
4006
4176
  };
4007
4177
 
4008
4178
  // src/cli/update-command.ts
4009
- import { existsSync as existsSync22, readFileSync as readFileSync14 } from "fs";
4010
- import { join as join24 } from "path";
4179
+ import { existsSync as existsSync24, readFileSync as readFileSync14 } from "fs";
4180
+ import { join as join26 } from "path";
4011
4181
 
4012
4182
  // src/core/update/registry-client.ts
4013
4183
  import { request } from "https";
@@ -4325,8 +4495,8 @@ var UpdateCommand = class {
4325
4495
  return this.projectHasCtxoDep() ? "execute" : "print";
4326
4496
  }
4327
4497
  projectHasCtxoDep() {
4328
- const pkg = join24(this.projectRoot, "package.json");
4329
- if (!existsSync22(pkg)) return false;
4498
+ const pkg = join26(this.projectRoot, "package.json");
4499
+ if (!existsSync24(pkg)) return false;
4330
4500
  try {
4331
4501
  const json = JSON.parse(readFileSync14(pkg, "utf-8"));
4332
4502
  const merged = { ...json.dependencies ?? {}, ...json.devDependencies ?? {} };
@@ -4363,11 +4533,11 @@ function isLockedCI2(env) {
4363
4533
  }
4364
4534
 
4365
4535
  // src/cli/blast-radius-command.ts
4366
- import { join as join25 } from "path";
4536
+ import { join as join27 } from "path";
4367
4537
  var BlastRadiusCommand = class {
4368
4538
  ctxoRoot;
4369
4539
  constructor(projectRoot) {
4370
- this.ctxoRoot = join25(projectRoot, ".ctxo");
4540
+ this.ctxoRoot = join27(projectRoot, ".ctxo");
4371
4541
  }
4372
4542
  run(options) {
4373
4543
  const { graph } = loadGraph(this.ctxoRoot);
@@ -4395,8 +4565,8 @@ var BlastRadiusCommand = class {
4395
4565
  };
4396
4566
 
4397
4567
  // src/cli/gate-hook-command.ts
4398
- import { join as join27, relative as relative3, sep as sep2 } from "path";
4399
- import { existsSync as existsSync24, readFileSync as readFileSync16 } from "fs";
4568
+ import { join as join29, relative as relative3, sep as sep2 } from "path";
4569
+ import { existsSync as existsSync26, readFileSync as readFileSync16 } from "fs";
4400
4570
 
4401
4571
  // src/core/gate/gate-config.ts
4402
4572
  var DIAL = {
@@ -4463,8 +4633,8 @@ function transcriptHasBlastCheck(lines, symbolId) {
4463
4633
  }
4464
4634
 
4465
4635
  // src/adapters/gate/gate-session-store.ts
4466
- import { existsSync as existsSync23, mkdirSync as mkdirSync7, readFileSync as readFileSync15, writeFileSync as writeFileSync6 } from "fs";
4467
- import { join as join26 } from "path";
4636
+ import { existsSync as existsSync25, mkdirSync as mkdirSync7, readFileSync as readFileSync15, writeFileSync as writeFileSync6 } from "fs";
4637
+ import { join as join28 } from "path";
4468
4638
  var log7 = createLogger("ctxo:gate-store");
4469
4639
  var GateSessionStore = class {
4470
4640
  constructor(cacheDir) {
@@ -4473,11 +4643,11 @@ var GateSessionStore = class {
4473
4643
  cacheDir;
4474
4644
  file(sessionId) {
4475
4645
  const safe = sessionId.replace(/[^a-zA-Z0-9_-]/g, "_");
4476
- return join26(this.cacheDir, "gate", `${safe}.json`);
4646
+ return join28(this.cacheDir, "gate", `${safe}.json`);
4477
4647
  }
4478
4648
  read(sessionId) {
4479
4649
  const f = this.file(sessionId);
4480
- if (!existsSync23(f)) return /* @__PURE__ */ new Set();
4650
+ if (!existsSync25(f)) return /* @__PURE__ */ new Set();
4481
4651
  try {
4482
4652
  return new Set(JSON.parse(readFileSync15(f, "utf-8")));
4483
4653
  } catch (err) {
@@ -4493,7 +4663,7 @@ var GateSessionStore = class {
4493
4663
  const set = this.read(sessionId);
4494
4664
  set.add(symbolId);
4495
4665
  const f = this.file(sessionId);
4496
- mkdirSync7(join26(this.cacheDir, "gate"), { recursive: true });
4666
+ mkdirSync7(join28(this.cacheDir, "gate"), { recursive: true });
4497
4667
  writeFileSync6(f, JSON.stringify([...set]), "utf-8");
4498
4668
  } catch (err) {
4499
4669
  log7.error(`${err.message}`);
@@ -4532,8 +4702,8 @@ var RANK_ALL = 1e5;
4532
4702
  var GateHookCommand = class {
4533
4703
  constructor(projectRoot) {
4534
4704
  this.projectRoot = projectRoot;
4535
- this.ctxoRoot = join27(projectRoot, ".ctxo");
4536
- this.cacheDir = join27(this.ctxoRoot, ".cache");
4705
+ this.ctxoRoot = join29(projectRoot, ".ctxo");
4706
+ this.cacheDir = join29(this.ctxoRoot, ".cache");
4537
4707
  }
4538
4708
  projectRoot;
4539
4709
  ctxoRoot;
@@ -4549,11 +4719,11 @@ var GateHookCommand = class {
4549
4719
  const thresholds = resolveGateThresholds(loaded.config.gate);
4550
4720
  if (!thresholds.enabled) return { block: false };
4551
4721
  const relPath = relative3(this.projectRoot, filePath).split(sep2).join("/");
4552
- const indexFile = join27(this.ctxoRoot, "index", `${relPath}.json`);
4553
- if (!existsSync24(indexFile)) return { block: false };
4722
+ const indexFile = join29(this.ctxoRoot, "index", `${relPath}.json`);
4723
+ if (!existsSync26(indexFile)) return { block: false };
4554
4724
  const fileIndex = new JsonIndexReader(this.ctxoRoot).readSingle(indexFile);
4555
4725
  if (!fileIndex) return { block: false };
4556
- if (!existsSync24(filePath)) return { block: false };
4726
+ if (!existsSync26(filePath)) return { block: false };
4557
4727
  const source = readFileSync16(filePath, "utf-8");
4558
4728
  const line = lineOfSnippet(source, oldString);
4559
4729
  if (line === null) return { block: false };
@@ -4561,7 +4731,7 @@ var GateHookCommand = class {
4561
4731
  if (!symbolId) return { block: false };
4562
4732
  const store = new GateSessionStore(this.cacheDir);
4563
4733
  if (store.hasWarned(payload.session_id, symbolId)) return { block: false };
4564
- if (payload.transcript_path && existsSync24(payload.transcript_path)) {
4734
+ if (payload.transcript_path && existsSync26(payload.transcript_path)) {
4565
4735
  const lines = readFileSync16(payload.transcript_path, "utf-8").split("\n").filter(Boolean);
4566
4736
  if (transcriptHasBlastCheck(lines, symbolId)) return { block: false };
4567
4737
  }
@@ -4627,12 +4797,12 @@ var GateHookCommand = class {
4627
4797
  };
4628
4798
 
4629
4799
  // src/cli/gate-command.ts
4630
- import { join as join28 } from "path";
4800
+ import { join as join30 } from "path";
4631
4801
  var RANK_ALL2 = 1e5;
4632
4802
  var GateCommand = class {
4633
4803
  ctxoRoot;
4634
4804
  constructor(projectRoot) {
4635
- this.ctxoRoot = join28(projectRoot, ".ctxo");
4805
+ this.ctxoRoot = join30(projectRoot, ".ctxo");
4636
4806
  }
4637
4807
  preview(options = {}) {
4638
4808
  const gate = loadConfig(this.ctxoRoot).config.gate;
@@ -4672,8 +4842,8 @@ var GateCommand = class {
4672
4842
  function getVersion() {
4673
4843
  let dir = import.meta.dirname;
4674
4844
  for (let i = 0; i < 10; i++) {
4675
- const pkg = join29(dir, "package.json");
4676
- if (existsSync25(pkg)) {
4845
+ const pkg = join31(dir, "package.json");
4846
+ if (existsSync27(pkg)) {
4677
4847
  try {
4678
4848
  const json = JSON.parse(readFileSync17(pkg, "utf-8"));
4679
4849
  return json.version ?? "unknown";
@@ -4681,7 +4851,7 @@ function getVersion() {
4681
4851
  break;
4682
4852
  }
4683
4853
  }
4684
- const parent = join29(dir, "..");
4854
+ const parent = join31(dir, "..");
4685
4855
  if (parent === dir) break;
4686
4856
  dir = parent;
4687
4857
  }
@@ -4690,8 +4860,8 @@ function getVersion() {
4690
4860
  function locateGitRoot(startDir) {
4691
4861
  let dir = startDir;
4692
4862
  for (let i = 0; i < 12; i++) {
4693
- if (existsSync25(join29(dir, ".git"))) return dir;
4694
- const parent = dirname8(dir);
4863
+ if (existsSync27(join31(dir, ".git"))) return dir;
4864
+ const parent = dirname9(dir);
4695
4865
  if (parent === dir) break;
4696
4866
  dir = parent;
4697
4867
  }
@@ -4808,6 +4978,8 @@ var CliRouter = class {
4808
4978
  else if (a === "--global" || a === "-g") flagValues["global"] = true;
4809
4979
  else if (a === "--dry-run") flagValues["dryRun"] = true;
4810
4980
  else if (a === "--force") flagValues["force"] = true;
4981
+ else if (a === "--full-tier") flagValues["fullTier"] = true;
4982
+ else if (a === "--syntax-only") flagValues["syntaxOnly"] = true;
4811
4983
  else if (a === "--pm") {
4812
4984
  const next = args[++i];
4813
4985
  if (!next) {
@@ -4838,6 +5010,8 @@ var CliRouter = class {
4838
5010
  global: flagValues["global"] === true,
4839
5011
  dryRun: flagValues["dryRun"] === true,
4840
5012
  force: flagValues["force"] === true,
5013
+ fullTier: flagValues["fullTier"] === true,
5014
+ syntaxOnly: flagValues["syntaxOnly"] === true,
4841
5015
  pm: typeof flagValues["pm"] === "string" ? flagValues["pm"] : void 0,
4842
5016
  version: typeof flagValues["version"] === "string" ? flagValues["version"] : void 0
4843
5017
  });
@@ -4972,4 +5146,4 @@ export {
4972
5146
  getVersion,
4973
5147
  locateGitRoot
4974
5148
  };
4975
- //# sourceMappingURL=cli-router-45W4IRYE.js.map
5149
+ //# sourceMappingURL=cli-router-2JVPBW4S.js.map