@ctxo/cli 0.10.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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";
@@ -575,7 +577,53 @@ function resolveBinary(command) {
575
577
  return `${command}.cmd`;
576
578
  }
577
579
 
580
+ // src/core/detection/detect-java-runtime.ts
581
+ import { spawnSync } from "child_process";
582
+ import { existsSync as existsSync5 } from "fs";
583
+ import { join as join4 } from "path";
584
+ function parseJavaMajor(output) {
585
+ const m = output.match(/version "([^"]+)"/);
586
+ if (!m) return void 0;
587
+ const parts = m[1].split(".");
588
+ const major = parts[0] === "1" && parts[1] ? parseInt(parts[1], 10) : parseInt(parts[0], 10);
589
+ return Number.isNaN(major) ? void 0 : major;
590
+ }
591
+ var _javaMajorCache = void 0;
592
+ function detectJavaMajor() {
593
+ if (_javaMajorCache !== void 0) {
594
+ return _javaMajorCache === null ? void 0 : _javaMajorCache;
595
+ }
596
+ const home = process.env["CTXO_JAVA_HOME"] || process.env["JAVA_HOME"];
597
+ const bin = home ? join4(home, "bin", process.platform === "win32" ? "java.exe" : "java") : "java";
598
+ if (home && !existsSync5(bin)) {
599
+ _javaMajorCache = null;
600
+ return void 0;
601
+ }
602
+ const r = spawnSync(bin, ["-version"], {
603
+ encoding: "utf-8",
604
+ timeout: 1e4,
605
+ windowsHide: true
606
+ });
607
+ if (r.error || r.status === null) {
608
+ _javaMajorCache = null;
609
+ return void 0;
610
+ }
611
+ const major = parseJavaMajor(`${r.stderr ?? ""}${r.stdout ?? ""}`);
612
+ _javaMajorCache = major ?? null;
613
+ return major;
614
+ }
615
+ function javaRuntimeAvailable() {
616
+ return (detectJavaMajor() ?? 0) >= 17;
617
+ }
618
+
578
619
  // src/cli/install-command.ts
620
+ function resolveJavaPackages(opts) {
621
+ if (opts.fullTier && opts.syntaxOnly) throw new Error("Cannot combine --full-tier with --syntax-only.");
622
+ const base = ["@ctxo/lang-java"];
623
+ if (opts.syntaxOnly) return base;
624
+ if (opts.fullTier || opts.jreAvailable) return [...base, "@ctxo/lang-java-analyzer"];
625
+ return base;
626
+ }
579
627
  var InstallCommand = class {
580
628
  constructor(projectRoot) {
581
629
  this.projectRoot = projectRoot;
@@ -599,7 +647,7 @@ var InstallCommand = class {
599
647
  process.exitCode = 1;
600
648
  return;
601
649
  }
602
- if (!options.global && !existsSync5(join4(this.projectRoot, "package.json"))) {
650
+ if (!options.global && !existsSync6(join5(this.projectRoot, "package.json"))) {
603
651
  console.error("[ctxo] No package.json in the current project. Re-run with --global or create a package.json first.");
604
652
  process.exitCode = 1;
605
653
  return;
@@ -609,9 +657,28 @@ var InstallCommand = class {
609
657
  process.exitCode = 1;
610
658
  return;
611
659
  }
612
- const specifiers = resolvedLangs.map(
613
- (lang) => options.version ? `${officialPluginFor(lang)}@${options.version}` : officialPluginFor(lang)
614
- );
660
+ const specifiers = [];
661
+ for (const lang of resolvedLangs) {
662
+ if (lang === "java") {
663
+ const jreAvailable = javaRuntimeAvailable();
664
+ if (!jreAvailable && !options.fullTier && !options.syntaxOnly) {
665
+ 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.');
666
+ }
667
+ let javaPkgs;
668
+ try {
669
+ javaPkgs = resolveJavaPackages({ jreAvailable, fullTier: options.fullTier, syntaxOnly: options.syntaxOnly });
670
+ } catch (err) {
671
+ console.error(`[ctxo] ${err.message}`);
672
+ process.exitCode = 1;
673
+ return;
674
+ }
675
+ for (const pkg of javaPkgs) {
676
+ specifiers.push(options.version ? `${pkg}@${options.version}` : pkg);
677
+ }
678
+ } else {
679
+ specifiers.push(options.version ? `${officialPluginFor(lang)}@${options.version}` : officialPluginFor(lang));
680
+ }
681
+ }
615
682
  const invocation = buildInstallCommand(resolution.manager, specifiers, {
616
683
  global: options.global,
617
684
  workspaceRoot: !options.global && isWorkspaceRoot(this.projectRoot)
@@ -703,12 +770,12 @@ function isLockedCI(env) {
703
770
  }
704
771
 
705
772
  // 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";
773
+ import { mkdirSync, unlinkSync, existsSync as existsSync7 } from "fs";
774
+ import { dirname as dirname4, join as join6, resolve as resolve2, sep } from "path";
708
775
  var JsonIndexWriter = class {
709
776
  indexDir;
710
777
  constructor(ctxoRoot) {
711
- this.indexDir = join5(ctxoRoot, "index");
778
+ this.indexDir = join6(ctxoRoot, "index");
712
779
  }
713
780
  write(fileIndex) {
714
781
  if (!fileIndex.file) {
@@ -722,7 +789,7 @@ var JsonIndexWriter = class {
722
789
  }
723
790
  writeCoChanges(matrix) {
724
791
  mkdirSync(this.indexDir, { recursive: true });
725
- const targetPath = join5(this.indexDir, "co-changes.json");
792
+ const targetPath = join6(this.indexDir, "co-changes.json");
726
793
  this.atomicWrite(targetPath, JSON.stringify(matrix, null, 2));
727
794
  }
728
795
  atomicWrite(targetPath, content) {
@@ -730,7 +797,7 @@ var JsonIndexWriter = class {
730
797
  }
731
798
  delete(relativePath) {
732
799
  const targetPath = this.resolveIndexPath(relativePath);
733
- if (existsSync6(targetPath)) {
800
+ if (existsSync7(targetPath)) {
734
801
  unlinkSync(targetPath);
735
802
  }
736
803
  }
@@ -758,19 +825,19 @@ var JsonIndexWriter = class {
758
825
  };
759
826
 
760
827
  // 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";
828
+ import { readFileSync as readFileSync4, writeFileSync, existsSync as existsSync8, mkdirSync as mkdirSync2 } from "fs";
829
+ import { join as join7, dirname as dirname5 } from "path";
763
830
  var CURRENT_SCHEMA_VERSION = "1.0.0";
764
831
  var SchemaManager = class {
765
832
  versionFilePath;
766
833
  constructor(ctxoRoot) {
767
- this.versionFilePath = join6(ctxoRoot, "index", "schema-version");
834
+ this.versionFilePath = join7(ctxoRoot, "index", "schema-version");
768
835
  }
769
836
  currentVersion() {
770
837
  return CURRENT_SCHEMA_VERSION;
771
838
  }
772
839
  readStoredVersion() {
773
- if (!existsSync7(this.versionFilePath)) {
840
+ if (!existsSync8(this.versionFilePath)) {
774
841
  return void 0;
775
842
  }
776
843
  return readFileSync4(this.versionFilePath, "utf-8").trim();
@@ -787,8 +854,8 @@ var SchemaManager = class {
787
854
  };
788
855
 
789
856
  // 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";
857
+ import { existsSync as existsSync9, mkdirSync as mkdirSync3, readFileSync as readFileSync5 } from "fs";
858
+ import { join as join8, resolve as resolve3 } from "path";
792
859
  import { tmpdir } from "os";
793
860
  var log3 = createLogger("ctxo:architecture-artifacts");
794
861
  var DRIFT_FILE_NAME = "drift-events.json";
@@ -796,20 +863,20 @@ var VIOLATIONS_FILE_NAME = "boundary-violations.json";
796
863
  var ArchitectureArtifactsWriter = class {
797
864
  indexDir;
798
865
  constructor(ctxoRoot, options = {}) {
799
- this.indexDir = join7(ctxoRoot, "index");
866
+ this.indexDir = join8(ctxoRoot, "index");
800
867
  assertSafeWritePath(ctxoRoot, options.allowProductionPath ?? false);
801
868
  }
802
869
  writeDriftEvents(artifact) {
803
870
  mkdirSync3(this.indexDir, { recursive: true });
804
- atomicWrite(join7(this.indexDir, DRIFT_FILE_NAME), JSON.stringify(artifact, null, 2));
871
+ atomicWrite(join8(this.indexDir, DRIFT_FILE_NAME), JSON.stringify(artifact, null, 2));
805
872
  }
806
873
  writeBoundaryViolations(artifact) {
807
874
  mkdirSync3(this.indexDir, { recursive: true });
808
- atomicWrite(join7(this.indexDir, VIOLATIONS_FILE_NAME), JSON.stringify(artifact, null, 2));
875
+ atomicWrite(join8(this.indexDir, VIOLATIONS_FILE_NAME), JSON.stringify(artifact, null, 2));
809
876
  }
810
877
  readDriftEvents() {
811
- const path = join7(this.indexDir, DRIFT_FILE_NAME);
812
- if (!existsSync8(path)) return void 0;
878
+ const path = join8(this.indexDir, DRIFT_FILE_NAME);
879
+ if (!existsSync9(path)) return void 0;
813
880
  try {
814
881
  return JSON.parse(readFileSync5(path, "utf-8"));
815
882
  } catch (err) {
@@ -818,8 +885,8 @@ var ArchitectureArtifactsWriter = class {
818
885
  }
819
886
  }
820
887
  readBoundaryViolations() {
821
- const path = join7(this.indexDir, VIOLATIONS_FILE_NAME);
822
- if (!existsSync8(path)) return void 0;
888
+ const path = join8(this.indexDir, VIOLATIONS_FILE_NAME);
889
+ if (!existsSync9(path)) return void 0;
823
890
  try {
824
891
  return JSON.parse(readFileSync5(path, "utf-8"));
825
892
  } catch (err) {
@@ -1128,7 +1195,7 @@ var IndexCommand = class {
1128
1195
  supportedExtensions;
1129
1196
  constructor(projectRoot, ctxoRoot) {
1130
1197
  this.projectRoot = projectRoot;
1131
- this.ctxoRoot = ctxoRoot ?? join8(projectRoot, ".ctxo");
1198
+ this.ctxoRoot = ctxoRoot ?? join9(projectRoot, ".ctxo");
1132
1199
  this.supportedExtensions = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".go", ".cs"]);
1133
1200
  }
1134
1201
  loadIgnoreConfig() {
@@ -1156,7 +1223,7 @@ var IndexCommand = class {
1156
1223
  }
1157
1224
  const ignore = this.loadIgnoreConfig();
1158
1225
  const registry = new LanguageAdapterRegistry();
1159
- const { tsMorphLike, roslynAdapter, csharpTier, goTier } = await this.registerDiscoveredPlugins(registry, ignore.ignoreProjectPatterns);
1226
+ const { tsMorphLike, roslynAdapter, csharpTier, goTier, javaTier } = await this.registerDiscoveredPlugins(registry, ignore.ignoreProjectPatterns);
1160
1227
  this.supportedExtensions = registry.getSupportedExtensions();
1161
1228
  this.printLanguagePreview();
1162
1229
  const writer = new JsonIndexWriter(this.ctxoRoot);
@@ -1166,7 +1233,7 @@ var IndexCommand = class {
1166
1233
  const revertDetector = new RevertDetector();
1167
1234
  let files;
1168
1235
  if (options.file) {
1169
- const fullPath = join8(this.projectRoot, options.file);
1236
+ const fullPath = join9(this.projectRoot, options.file);
1170
1237
  files = [fullPath];
1171
1238
  console.error(`[ctxo] Incremental re-index: ${options.file}`);
1172
1239
  } else {
@@ -1179,8 +1246,8 @@ var IndexCommand = class {
1179
1246
  console.error(`[ctxo] Building codebase index... Found ${files.length} source files`);
1180
1247
  }
1181
1248
  try {
1182
- const { sweepStaleTmpFiles } = await import("./atomic-write-AQNYJFAG.js");
1183
- const swept = sweepStaleTmpFiles(join8(this.ctxoRoot, "index"));
1249
+ const { sweepStaleTmpFiles } = await import("./atomic-write-BAFH464Q.js");
1250
+ const swept = sweepStaleTmpFiles(join9(this.ctxoRoot, "index"));
1184
1251
  if (swept > 0) {
1185
1252
  console.error(`[ctxo] Cleaned ${swept} orphan .tmp file(s) from previous interrupted run`);
1186
1253
  }
@@ -1267,7 +1334,7 @@ var IndexCommand = class {
1267
1334
  }
1268
1335
  let communitySnapshot;
1269
1336
  if (options.skipCommunity && pendingIndices.length > 0) {
1270
- if (existsSync9(join8(this.ctxoRoot, "index", "communities.json"))) {
1337
+ if (existsSync10(join9(this.ctxoRoot, "index", "communities.json"))) {
1271
1338
  console.error(
1272
1339
  `[ctxo] WARN stale communities.json present \u2014 --skip-community preserved existing snapshot. Run \`ctxo index\` (full) to refresh.`
1273
1340
  );
@@ -1280,6 +1347,7 @@ var IndexCommand = class {
1280
1347
  {
1281
1348
  csharpTier,
1282
1349
  goTier,
1350
+ javaTier,
1283
1351
  tsCount: graphIndices.filter((idx) => /\.(ts|tsx|js|jsx)$/.test(idx.file)).length
1284
1352
  },
1285
1353
  gitAdapter
@@ -1317,10 +1385,12 @@ var IndexCommand = class {
1317
1385
  const csCount = pendingIndices.filter((e) => e.relativePath.endsWith(".cs")).length;
1318
1386
  const tsCount = pendingIndices.filter((e) => /\.(ts|tsx|js|jsx)$/.test(e.relativePath)).length;
1319
1387
  const goCount = pendingIndices.filter((e) => e.relativePath.endsWith(".go")).length;
1388
+ const javaCount = pendingIndices.filter((e) => e.relativePath.endsWith(".java")).length;
1320
1389
  console.error(`[ctxo] Index complete: ${processed} files indexed`);
1321
1390
  if (tsCount > 0) console.error(`[ctxo] TypeScript/JS: ${tsCount} files (full tier)`);
1322
1391
  if (csCount > 0) console.error(`[ctxo] C#: ${csCount} files (${csharpTier} tier${csharpTier === "syntax" ? " - .NET SDK 8+ for full analysis" : ""})`);
1323
1392
  if (goCount > 0) console.error(`[ctxo] Go: ${goCount} files (${goTier} tier${goTier === "syntax" ? " - Go 1.22+ for full analysis" : ""})`);
1393
+ if (javaCount > 0) console.error(`[ctxo] Java: ${javaCount} files (${javaTier} tier${javaTier === "syntax" ? ' - JRE 17+ & "ctxo install java --full-tier" for full analysis' : ""})`);
1324
1394
  if (roslynAdapter) await roslynAdapter.dispose();
1325
1395
  }
1326
1396
  async autoInstallMissingPlugins() {
@@ -1356,6 +1426,7 @@ var IndexCommand = class {
1356
1426
  let roslynAdapter = null;
1357
1427
  let csharpTier = "unavailable";
1358
1428
  let goTier = "unavailable";
1429
+ let javaTier = "unavailable";
1359
1430
  for (const { plugin, adapter } of loaded) {
1360
1431
  if (typeof adapter.initialize === "function") {
1361
1432
  try {
@@ -1379,11 +1450,15 @@ var IndexCommand = class {
1379
1450
  if (plugin.id === "go" && typeof goCandidate.getTier === "function") {
1380
1451
  goTier = goCandidate.getTier();
1381
1452
  }
1382
- const activeTier = plugin.id === "csharp" ? csharpTier : plugin.id === "go" ? goTier : plugin.tier;
1453
+ const javaCandidate = adapter;
1454
+ if (plugin.id === "java" && typeof javaCandidate.getTier === "function") {
1455
+ javaTier = javaCandidate.getTier();
1456
+ }
1457
+ const activeTier = plugin.id === "csharp" ? csharpTier : plugin.id === "go" ? goTier : plugin.id === "java" ? javaTier : plugin.tier;
1383
1458
  const tierSuffix = activeTier !== plugin.tier ? ` (active: ${activeTier})` : "";
1384
1459
  console.error(`[ctxo] Plugin ${plugin.id}@${plugin.version} (${plugin.tier} tier${tierSuffix}) \u2014 ${plugin.extensions.join(", ")}`);
1385
1460
  }
1386
- return { tsMorphLike, roslynAdapter, csharpTier, goTier };
1461
+ return { tsMorphLike, roslynAdapter, csharpTier, goTier, javaTier };
1387
1462
  }
1388
1463
  discoverFilesIn(root, ignoreFile = () => false) {
1389
1464
  try {
@@ -1392,18 +1467,18 @@ var IndexCommand = class {
1392
1467
  encoding: "utf-8",
1393
1468
  maxBuffer: 10 * 1024 * 1024
1394
1469
  });
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) => {
1470
+ 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
1471
  const rel = relative(this.projectRoot, abs).replace(/\\/g, "/");
1397
1472
  return !ignoreFile(rel);
1398
- }).filter((abs) => existsSync9(abs));
1473
+ }).filter((abs) => existsSync10(abs));
1399
1474
  } catch {
1400
1475
  console.error(`[ctxo] git ls-files failed for ${root}`);
1401
1476
  return [];
1402
1477
  }
1403
1478
  }
1404
1479
  discoverWorkspaces(ignoreProject = () => false) {
1405
- const pkgPath = join8(this.projectRoot, "package.json");
1406
- if (!existsSync9(pkgPath)) return [this.projectRoot];
1480
+ const pkgPath = join9(this.projectRoot, "package.json");
1481
+ if (!existsSync10(pkgPath)) return [this.projectRoot];
1407
1482
  try {
1408
1483
  const pkg = JSON.parse(readFileSync6(pkgPath, "utf-8"));
1409
1484
  const workspaces = Array.isArray(pkg.workspaces) ? pkg.workspaces : pkg.workspaces?.packages;
@@ -1411,17 +1486,17 @@ var IndexCommand = class {
1411
1486
  const resolved = [];
1412
1487
  for (const ws of workspaces) {
1413
1488
  if (ws.endsWith("/*") || ws.endsWith("\\*")) {
1414
- const parentDir = join8(this.projectRoot, ws.slice(0, -2));
1415
- if (existsSync9(parentDir)) {
1489
+ const parentDir = join9(this.projectRoot, ws.slice(0, -2));
1490
+ if (existsSync10(parentDir)) {
1416
1491
  for (const entry of readdirSync2(parentDir, { withFileTypes: true })) {
1417
1492
  if (entry.isDirectory()) {
1418
- resolved.push(join8(parentDir, entry.name));
1493
+ resolved.push(join9(parentDir, entry.name));
1419
1494
  }
1420
1495
  }
1421
1496
  }
1422
1497
  } else {
1423
- const wsPath = join8(this.projectRoot, ws);
1424
- if (existsSync9(wsPath)) {
1498
+ const wsPath = join9(this.projectRoot, ws);
1499
+ if (existsSync10(wsPath)) {
1425
1500
  resolved.push(wsPath);
1426
1501
  }
1427
1502
  }
@@ -1454,7 +1529,7 @@ var IndexCommand = class {
1454
1529
  if (_roslyn) await _roslyn.dispose();
1455
1530
  const hasher = new ContentHasher();
1456
1531
  const files = this.discoverFilesIn(this.projectRoot);
1457
- const reader = new (await import("./json-index-reader-XOEE6VMD.js")).JsonIndexReader(this.ctxoRoot);
1532
+ const reader = new (await import("./json-index-reader-UYS74USB.js")).JsonIndexReader(this.ctxoRoot);
1458
1533
  const indices = reader.readAll();
1459
1534
  const indexedMap = new Map(indices.map((i) => [i.file, i]));
1460
1535
  let staleCount = 0;
@@ -1467,7 +1542,7 @@ var IndexCommand = class {
1467
1542
  staleCount++;
1468
1543
  continue;
1469
1544
  }
1470
- if (!existsSync9(filePath)) {
1545
+ if (!existsSync10(filePath)) {
1471
1546
  console.error(`[ctxo] DELETED: ${relativePath}`);
1472
1547
  staleCount++;
1473
1548
  continue;
@@ -1493,13 +1568,13 @@ var IndexCommand = class {
1493
1568
  console.error("[ctxo] Index is up to date");
1494
1569
  }
1495
1570
  ensureGitignore() {
1496
- const gitignorePath = join8(this.projectRoot, ".gitignore");
1571
+ const gitignorePath = join9(this.projectRoot, ".gitignore");
1497
1572
  const cachePattern = ".ctxo/.cache/";
1498
1573
  const suffix = `
1499
1574
  # Ctxo local cache (never committed)
1500
1575
  ${cachePattern}
1501
1576
  `;
1502
- const existing = existsSync9(gitignorePath) ? readFileSync6(gitignorePath, "utf-8") : "";
1577
+ const existing = existsSync10(gitignorePath) ? readFileSync6(gitignorePath, "utf-8") : "";
1503
1578
  if (existing.includes(cachePattern)) return;
1504
1579
  writeFileSync2(gitignorePath, existing + suffix, "utf-8");
1505
1580
  console.error("[ctxo] Added .ctxo/.cache/ to .gitignore");
@@ -1618,8 +1693,8 @@ ${cachePattern}
1618
1693
  }
1619
1694
  };
1620
1695
  function pickEdgeQuality(tiers) {
1621
- const hasSyntaxOnly = tiers.csharpTier === "syntax" || tiers.goTier === "syntax";
1622
- const hasFull = tiers.tsCount > 0 || tiers.csharpTier === "full" || tiers.goTier === "full";
1696
+ const hasSyntaxOnly = tiers.csharpTier === "syntax" || tiers.goTier === "syntax" || tiers.javaTier === "syntax";
1697
+ const hasFull = tiers.tsCount > 0 || tiers.csharpTier === "full" || tiers.goTier === "full" || tiers.javaTier === "full";
1623
1698
  if (hasSyntaxOnly && hasFull) return "mixed";
1624
1699
  if (hasSyntaxOnly) return "syntax-only";
1625
1700
  return "full";
@@ -1644,11 +1719,11 @@ function countCommunities(snapshot) {
1644
1719
  }
1645
1720
 
1646
1721
  // src/cli/sync-command.ts
1647
- import { join as join9 } from "path";
1722
+ import { join as join10 } from "path";
1648
1723
  var SyncCommand = class {
1649
1724
  ctxoRoot;
1650
1725
  constructor(projectRoot) {
1651
- this.ctxoRoot = join9(projectRoot, ".ctxo");
1726
+ this.ctxoRoot = join10(projectRoot, ".ctxo");
1652
1727
  }
1653
1728
  async run() {
1654
1729
  console.error("[ctxo] Rebuilding SQLite cache from committed JSON index...");
@@ -1663,19 +1738,19 @@ var SyncCommand = class {
1663
1738
  };
1664
1739
 
1665
1740
  // src/cli/status-command.ts
1666
- import { join as join10 } from "path";
1667
- import { existsSync as existsSync10 } from "fs";
1741
+ import { join as join11 } from "path";
1742
+ import { existsSync as existsSync11 } from "fs";
1668
1743
  import { execFileSync as execFileSync3 } from "child_process";
1669
1744
  var StatusCommand = class {
1670
1745
  projectRoot;
1671
1746
  ctxoRoot;
1672
1747
  constructor(projectRoot) {
1673
1748
  this.projectRoot = projectRoot;
1674
- this.ctxoRoot = join10(projectRoot, ".ctxo");
1749
+ this.ctxoRoot = join11(projectRoot, ".ctxo");
1675
1750
  }
1676
1751
  run() {
1677
- const indexDir = join10(this.ctxoRoot, "index");
1678
- if (!existsSync10(indexDir)) {
1752
+ const indexDir = join11(this.ctxoRoot, "index");
1753
+ if (!existsSync11(indexDir)) {
1679
1754
  console.error('[ctxo] No index found. Run "ctxo index" first.');
1680
1755
  return;
1681
1756
  }
@@ -1690,7 +1765,7 @@ var StatusCommand = class {
1690
1765
  console.error(` Indexed files: ${indices.length}`);
1691
1766
  console.error(` Total symbols: ${totalSymbols}`);
1692
1767
  console.error(` Total edges: ${totalEdges}`);
1693
- const cacheExists = existsSync10(join10(this.ctxoRoot, ".cache", "symbols.db"));
1768
+ const cacheExists = existsSync11(join11(this.ctxoRoot, ".cache", "symbols.db"));
1694
1769
  console.error(` SQLite cache: ${cacheExists ? "present" : "missing (run ctxo sync)"}`);
1695
1770
  if (indices.length > 0) {
1696
1771
  console.error("");
@@ -1719,7 +1794,7 @@ var StatusCommand = class {
1719
1794
 
1720
1795
  // src/cli/verify-command.ts
1721
1796
  import { mkdtempSync, rmSync } from "fs";
1722
- import { join as join11 } from "path";
1797
+ import { join as join12 } from "path";
1723
1798
  import { tmpdir as tmpdir2 } from "os";
1724
1799
  var VerifyCommand = class {
1725
1800
  projectRoot;
@@ -1728,12 +1803,12 @@ var VerifyCommand = class {
1728
1803
  }
1729
1804
  async run() {
1730
1805
  console.error("[ctxo] Verifying index freshness...");
1731
- const tempDir = mkdtempSync(join11(tmpdir2(), "ctxo-verify-"));
1806
+ const tempDir = mkdtempSync(join12(tmpdir2(), "ctxo-verify-"));
1732
1807
  try {
1733
- const tempCtxo = join11(tempDir, ".ctxo");
1808
+ const tempCtxo = join12(tempDir, ".ctxo");
1734
1809
  const indexCmd = new IndexCommand(this.projectRoot, tempCtxo);
1735
1810
  await indexCmd.run({ skipSideEffects: true });
1736
- const committedReader = new JsonIndexReader(join11(this.projectRoot, ".ctxo"));
1811
+ const committedReader = new JsonIndexReader(join12(this.projectRoot, ".ctxo"));
1737
1812
  const freshReader = new JsonIndexReader(tempCtxo);
1738
1813
  const committedIndices = committedReader.readAll();
1739
1814
  const freshIndices = freshReader.readAll();
@@ -1766,20 +1841,20 @@ var VerifyCommand = class {
1766
1841
  };
1767
1842
 
1768
1843
  // 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";
1844
+ import { join as join14 } from "path";
1845
+ import { existsSync as existsSync13, readFileSync as readFileSync8, writeFileSync as writeFileSync4, mkdirSync as mkdirSync5, chmodSync } from "fs";
1771
1846
  import { createRequire as createRequire4 } from "module";
1772
1847
 
1773
1848
  // 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";
1849
+ import { existsSync as existsSync12, readFileSync as readFileSync7, writeFileSync as writeFileSync3, mkdirSync as mkdirSync4 } from "fs";
1850
+ import { join as join13, dirname as dirname6 } from "path";
1776
1851
  var CTXO_HOOK_COMMAND = "ctxo gate-hook";
1777
1852
  function ensureClaudeHook(projectRoot) {
1778
- const filePath = join12(projectRoot, ".claude", "settings.json");
1853
+ const filePath = join13(projectRoot, ".claude", "settings.json");
1779
1854
  const dir = dirname6(filePath);
1780
- if (!existsSync11(dir)) mkdirSync4(dir, { recursive: true });
1855
+ if (!existsSync12(dir)) mkdirSync4(dir, { recursive: true });
1781
1856
  const ctxoEntry = { matcher: "Edit", hooks: [{ type: "command", command: CTXO_HOOK_COMMAND }] };
1782
- if (!existsSync11(filePath)) {
1857
+ if (!existsSync12(filePath)) {
1783
1858
  writeFileSync3(filePath, JSON.stringify({ hooks: { PreToolUse: [ctxoEntry] } }, null, 2) + "\n", "utf-8");
1784
1859
  return { file: ".claude/settings.json", action: "created" };
1785
1860
  }
@@ -1815,7 +1890,7 @@ var PLATFORMS = [
1815
1890
  function detectPlatforms(projectRoot) {
1816
1891
  const detected = /* @__PURE__ */ new Set();
1817
1892
  for (const p of PLATFORMS) {
1818
- if (p.detectPaths.some((d) => existsSync11(join12(projectRoot, d)))) {
1893
+ if (p.detectPaths.some((d) => existsSync12(join13(projectRoot, d)))) {
1819
1894
  detected.add(p.id);
1820
1895
  }
1821
1896
  }
@@ -1877,15 +1952,15 @@ function generateRules(platformId) {
1877
1952
  function installRules(projectRoot, platformId) {
1878
1953
  const platform = PLATFORMS.find((p) => p.id === platformId);
1879
1954
  if (!platform) throw new Error(`Unknown platform: ${platformId}`);
1880
- const filePath = join12(projectRoot, platform.file);
1955
+ const filePath = join13(projectRoot, platform.file);
1881
1956
  const content = generateRules(platformId);
1882
1957
  const dir = dirname6(filePath);
1883
- if (!existsSync11(dir)) mkdirSync4(dir, { recursive: true });
1958
+ if (!existsSync12(dir)) mkdirSync4(dir, { recursive: true });
1884
1959
  if (platform.mode === "create") {
1885
1960
  writeFileSync3(filePath, content, "utf-8");
1886
- return { file: platform.file, action: existsSync11(filePath) ? "updated" : "created" };
1961
+ return { file: platform.file, action: existsSync12(filePath) ? "updated" : "created" };
1887
1962
  }
1888
- if (!existsSync11(filePath)) {
1963
+ if (!existsSync12(filePath)) {
1889
1964
  writeFileSync3(filePath, content, "utf-8");
1890
1965
  return { file: platform.file, action: "created" };
1891
1966
  }
@@ -1909,15 +1984,15 @@ function escapeRegExp(s) {
1909
1984
  }
1910
1985
  var SKILLS = ["ctxo-understand", "ctxo-safe-edit", "ctxo-review-pr"];
1911
1986
  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`)
1987
+ "claude-code": (root, skill) => join13(root, ".claude", "skills", skill, "SKILL.md"),
1988
+ cursor: (root, skill) => join13(root, ".cursor", "rules", `${skill}.mdc`)
1914
1989
  };
1915
1990
  function skillsSourceDir() {
1916
1991
  let dir = import.meta.dirname;
1917
1992
  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, "..");
1993
+ const candidate = join13(dir, "skills");
1994
+ if (existsSync12(join13(candidate, "ctxo-safe-edit", "SKILL.md"))) return candidate;
1995
+ const parent = join13(dir, "..");
1921
1996
  if (parent === dir) break;
1922
1997
  dir = parent;
1923
1998
  }
@@ -1929,10 +2004,10 @@ function installSkills(projectRoot, platformId) {
1929
2004
  const src = skillsSourceDir();
1930
2005
  const out = [];
1931
2006
  for (const skill of SKILLS) {
1932
- const from = join12(src, skill, "SKILL.md");
2007
+ const from = join13(src, skill, "SKILL.md");
1933
2008
  const to = target(projectRoot, skill);
1934
2009
  mkdirSync4(dirname6(to), { recursive: true });
1935
- const existed = existsSync11(to);
2010
+ const existed = existsSync12(to);
1936
2011
  writeFileSync3(to, readFileSync7(from, "utf-8"), "utf-8");
1937
2012
  out.push({ file: to, action: existed ? "updated" : "created" });
1938
2013
  }
@@ -1940,8 +2015,8 @@ function installSkills(projectRoot, platformId) {
1940
2015
  }
1941
2016
  var GITIGNORE_ENTRY = ".ctxo/.cache/";
1942
2017
  function ensureGitignore(projectRoot) {
1943
- const filePath = join12(projectRoot, ".gitignore");
1944
- if (!existsSync11(filePath)) {
2018
+ const filePath = join13(projectRoot, ".gitignore");
2019
+ if (!existsSync12(filePath)) {
1945
2020
  writeFileSync3(filePath, `# Ctxo local cache (auto-rebuilt, never committed)
1946
2021
  ${GITIGNORE_ENTRY}
1947
2022
  `, "utf-8");
@@ -1982,10 +2057,10 @@ gate:
1982
2057
  sensitivity: balanced # strict | balanced | lenient
1983
2058
  `;
1984
2059
  function ensureConfig(projectRoot) {
1985
- const filePath = join12(projectRoot, ".ctxo", "config.yaml");
2060
+ const filePath = join13(projectRoot, ".ctxo", "config.yaml");
1986
2061
  const dir = dirname6(filePath);
1987
- if (!existsSync11(dir)) mkdirSync4(dir, { recursive: true });
1988
- if (existsSync11(filePath)) {
2062
+ if (!existsSync12(dir)) mkdirSync4(dir, { recursive: true });
2063
+ if (existsSync12(filePath)) {
1989
2064
  return { file: ".ctxo/config.yaml", action: "skipped" };
1990
2065
  }
1991
2066
  writeFileSync3(filePath, DEFAULT_CONFIG, "utf-8");
@@ -2020,11 +2095,11 @@ function getMcpConfigTargets(selectedPlatformIds) {
2020
2095
  return [...targets.values()];
2021
2096
  }
2022
2097
  function ensureMcpConfig(projectRoot, target) {
2023
- const filePath = join12(projectRoot, target.file);
2098
+ const filePath = join13(projectRoot, target.file);
2024
2099
  const dir = dirname6(filePath);
2025
- if (!existsSync11(dir)) mkdirSync4(dir, { recursive: true });
2100
+ if (!existsSync12(dir)) mkdirSync4(dir, { recursive: true });
2026
2101
  const entry = target.extraFields ? { ...target.extraFields, ...CTXO_MCP_ENTRY } : { ...CTXO_MCP_ENTRY };
2027
- if (!existsSync11(filePath)) {
2102
+ if (!existsSync12(filePath)) {
2028
2103
  const config = { [target.serverKey]: { ctxo: entry } };
2029
2104
  writeFileSync3(filePath, JSON.stringify(config, null, 2) + "\n", "utf-8");
2030
2105
  return { file: target.file, action: "created" };
@@ -2140,13 +2215,13 @@ var InitCommand = class {
2140
2215
  this.projectRoot = projectRoot;
2141
2216
  }
2142
2217
  installHooks() {
2143
- const hooksDir = join13(this.projectRoot, ".git", "hooks");
2218
+ const hooksDir = join14(this.projectRoot, ".git", "hooks");
2144
2219
  mkdirSync5(hooksDir, { recursive: true });
2145
2220
  this.installHook(hooksDir, "post-commit", POST_COMMIT_CONTENT);
2146
2221
  this.installHook(hooksDir, "post-merge", POST_MERGE_CONTENT);
2147
2222
  }
2148
2223
  async run(options = {}) {
2149
- if (!existsSync12(join13(this.projectRoot, ".git"))) {
2224
+ if (!existsSync13(join14(this.projectRoot, ".git"))) {
2150
2225
  console.error('[ctxo] Not a git repository. Run "git init" first.');
2151
2226
  process.exit(1);
2152
2227
  }
@@ -2183,8 +2258,8 @@ var InitCommand = class {
2183
2258
  clack.cancel("Setup cancelled.");
2184
2259
  process.exit(0);
2185
2260
  }
2186
- const fullPath = join13(this.projectRoot, indexDir);
2187
- if (!existsSync12(fullPath)) mkdirSync5(fullPath, { recursive: true });
2261
+ const fullPath = join14(this.projectRoot, indexDir);
2262
+ if (!existsSync13(fullPath)) mkdirSync5(fullPath, { recursive: true });
2188
2263
  console.error("");
2189
2264
  }
2190
2265
  stepNum++;
@@ -2360,8 +2435,8 @@ var InitCommand = class {
2360
2435
  const gitignoreResult = ensureGitignore(this.projectRoot);
2361
2436
  const configResult = ensureConfig(this.projectRoot);
2362
2437
  if (!options.rulesOnly) {
2363
- const indexDir = join13(this.projectRoot, ".ctxo", "index");
2364
- if (!existsSync12(indexDir)) mkdirSync5(indexDir, { recursive: true });
2438
+ const indexDir = join14(this.projectRoot, ".ctxo", "index");
2439
+ if (!existsSync13(indexDir)) mkdirSync5(indexDir, { recursive: true });
2365
2440
  console.error("[ctxo] \u2713 .ctxo/index/ ready");
2366
2441
  }
2367
2442
  if (configResult.action !== "skipped") {
@@ -2420,7 +2495,7 @@ var InitCommand = class {
2420
2495
  for (const id of toolIds) {
2421
2496
  const platform = PLATFORMS.find((p) => p.id === id);
2422
2497
  if (platform) {
2423
- const exists = existsSync12(join13(this.projectRoot, platform.file));
2498
+ const exists = existsSync13(join14(this.projectRoot, platform.file));
2424
2499
  const action = platform.mode === "append" && exists ? "append section" : "create";
2425
2500
  console.error(` ${platform.file.padEnd(42)} (${action})`);
2426
2501
  }
@@ -2439,21 +2514,21 @@ var InitCommand = class {
2439
2514
  /* ---------------------------------------------------------------- */
2440
2515
  async getVersion() {
2441
2516
  try {
2442
- const pkgPath = join13(this.projectRoot, "node_modules", "@ctxo", "cli", "package.json");
2443
- if (existsSync12(pkgPath)) {
2517
+ const pkgPath = join14(this.projectRoot, "node_modules", "@ctxo", "cli", "package.json");
2518
+ if (existsSync13(pkgPath)) {
2444
2519
  return JSON.parse(readFileSync8(pkgPath, "utf-8")).version;
2445
2520
  }
2446
- const { createRequire: createRequire8 } = await import("module");
2447
- const require4 = createRequire8(import.meta.url);
2448
- return require4("../../package.json").version;
2521
+ const { createRequire: createRequire9 } = await import("module");
2522
+ const require5 = createRequire9(import.meta.url);
2523
+ return require5("../../package.json").version;
2449
2524
  } catch {
2450
2525
  return void 0;
2451
2526
  }
2452
2527
  }
2453
2528
  installHook(hooksDir, hookName, hookContent) {
2454
- const hookPath = join13(hooksDir, hookName);
2529
+ const hookPath = join14(hooksDir, hookName);
2455
2530
  let existing = "";
2456
- if (existsSync12(hookPath)) {
2531
+ if (existsSync13(hookPath)) {
2457
2532
  existing = readFileSync8(hookPath, "utf-8");
2458
2533
  if (existing.includes(CTXO_START)) return;
2459
2534
  } else {
@@ -2466,7 +2541,7 @@ var InitCommand = class {
2466
2541
  };
2467
2542
 
2468
2543
  // src/cli/watch-command.ts
2469
- import { join as join14, extname as extname4, relative as relative2 } from "path";
2544
+ import { join as join15, extname as extname4, relative as relative2 } from "path";
2470
2545
  import { readFileSync as readFileSync9 } from "fs";
2471
2546
 
2472
2547
  // src/adapters/watcher/chokidar-watcher-adapter.ts
@@ -2516,12 +2591,12 @@ var WatchCommand = class {
2516
2591
  ctxoRoot;
2517
2592
  constructor(projectRoot) {
2518
2593
  this.projectRoot = projectRoot;
2519
- this.ctxoRoot = join14(projectRoot, ".ctxo");
2594
+ this.ctxoRoot = join15(projectRoot, ".ctxo");
2520
2595
  }
2521
2596
  async run() {
2522
2597
  console.error("[ctxo] Starting file watcher...");
2523
2598
  const registry = new LanguageAdapterRegistry();
2524
- let roslynAdapter = null;
2599
+ const keepAliveByExt = /* @__PURE__ */ new Map();
2525
2600
  for (const { plugin, adapter } of await loadPlugins(this.projectRoot)) {
2526
2601
  if (typeof adapter.initialize === "function") {
2527
2602
  try {
@@ -2532,15 +2607,12 @@ var WatchCommand = class {
2532
2607
  }
2533
2608
  }
2534
2609
  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
- }
2610
+ const capable = adapter;
2611
+ if (typeof capable.getIncrementalReindex === "function") {
2612
+ const cap = capable.getIncrementalReindex();
2613
+ if (cap?.isReady() && await cap.startKeepAlive()) {
2614
+ for (const ext of plugin.extensions) keepAliveByExt.set(ext.toLowerCase(), cap);
2615
+ console.error(`[ctxo] ${plugin.id} watch: keep-alive active (full tier, fast incremental re-index)`);
2544
2616
  }
2545
2617
  }
2546
2618
  console.error(`[ctxo] Plugin ${plugin.id}@${plugin.version} (${plugin.tier} tier)`);
@@ -2561,8 +2633,9 @@ var WatchCommand = class {
2561
2633
  if (!adapter) return;
2562
2634
  const relativePath = relative2(this.projectRoot, filePath).replace(/\\/g, "/");
2563
2635
  try {
2564
- if (roslynAdapter?.isReady() && filePath.endsWith(".cs")) {
2565
- const result = await roslynAdapter.reindexFile(relativePath);
2636
+ const cap = keepAliveByExt.get(extname4(filePath).toLowerCase());
2637
+ if (cap?.isReady()) {
2638
+ const result = await cap.reindexFile(relativePath);
2566
2639
  if (result) {
2567
2640
  const commits2 = await gitAdapter.getCommitHistory(relativePath);
2568
2641
  const intent2 = commits2.map((c) => ({
@@ -2598,7 +2671,7 @@ var WatchCommand = class {
2598
2671
  };
2599
2672
  writer.write(fileIndex2);
2600
2673
  storage.writeSymbolFile(fileIndex2);
2601
- console.error(`[ctxo] Re-indexed (Roslyn): ${relativePath}`);
2674
+ console.error(`[ctxo] Re-indexed (keep-alive): ${relativePath}`);
2602
2675
  return;
2603
2676
  }
2604
2677
  }
@@ -2686,8 +2759,9 @@ var WatchCommand = class {
2686
2759
  clearTimeout(timeout);
2687
2760
  }
2688
2761
  if (communitySnapshotTimer) clearTimeout(communitySnapshotTimer);
2689
- const roslynShutdown = roslynAdapter ? roslynAdapter.dispose() : Promise.resolve();
2690
- roslynShutdown.then(() => {
2762
+ const uniques = new Set(keepAliveByExt.values());
2763
+ const shutdowns = Promise.all([...uniques].map((c) => c.dispose()));
2764
+ shutdowns.then(() => {
2691
2765
  watcher.stop().then(() => {
2692
2766
  storage.close();
2693
2767
  console.error("[ctxo] Watcher stopped");
@@ -2731,21 +2805,21 @@ function readHeadSha() {
2731
2805
  }
2732
2806
 
2733
2807
  // src/cli/stats-command.ts
2734
- import { join as join15 } from "path";
2735
- import { existsSync as existsSync13, readFileSync as readFileSync10 } from "fs";
2808
+ import { join as join16 } from "path";
2809
+ import { existsSync as existsSync14, readFileSync as readFileSync10 } from "fs";
2736
2810
  import initSqlJs from "sql.js";
2737
2811
  var StatsCommand = class {
2738
2812
  ctxoRoot;
2739
2813
  constructor(projectRoot) {
2740
- this.ctxoRoot = join15(projectRoot, ".ctxo");
2814
+ this.ctxoRoot = join16(projectRoot, ".ctxo");
2741
2815
  }
2742
2816
  async run(options) {
2743
2817
  if (!this.isStatsEnabled()) {
2744
2818
  console.error("[ctxo] Stats collection is disabled in .ctxo/config.yaml");
2745
2819
  return;
2746
2820
  }
2747
- const dbPath = join15(this.ctxoRoot, ".cache", "symbols.db");
2748
- if (!existsSync13(dbPath)) {
2821
+ const dbPath = join16(this.ctxoRoot, ".cache", "symbols.db");
2822
+ if (!existsSync14(dbPath)) {
2749
2823
  if (options.json) {
2750
2824
  process.stdout.write(JSON.stringify(this.emptyReport(options.days)) + "\n");
2751
2825
  } else {
@@ -2792,8 +2866,8 @@ var StatsCommand = class {
2792
2866
  }
2793
2867
  }
2794
2868
  isStatsEnabled() {
2795
- const configPath = join15(this.ctxoRoot, "config.yaml");
2796
- if (!existsSync13(configPath)) return true;
2869
+ const configPath = join16(this.ctxoRoot, "config.yaml");
2870
+ if (!existsSync14(configPath)) return true;
2797
2871
  try {
2798
2872
  const raw = readFileSync10(configPath, "utf-8");
2799
2873
  const match = raw.match(/stats:\s*\n\s*enabled:\s*(true|false)/);
@@ -2884,8 +2958,8 @@ var StatsCommand = class {
2884
2958
  };
2885
2959
 
2886
2960
  // src/cli/doctor-command.ts
2887
- import { join as join22 } from "path";
2888
- import { existsSync as existsSync20 } from "fs";
2961
+ import { join as join24 } from "path";
2962
+ import { existsSync as existsSync22 } from "fs";
2889
2963
 
2890
2964
  // src/adapters/diagnostics/health-checker.ts
2891
2965
  var log4 = createLogger("ctxo:doctor");
@@ -3220,10 +3294,111 @@ function pluginResolves(packageName) {
3220
3294
  }
3221
3295
  }
3222
3296
 
3297
+ // src/adapters/diagnostics/checks/java-tier-check.ts
3298
+ import { createRequire as createRequire7 } from "module";
3299
+ import { existsSync as existsSync15, readdirSync as readdirSync3 } from "fs";
3300
+ import { join as join17, dirname as dirname7 } from "path";
3301
+ var require4 = createRequire7(import.meta.url);
3302
+ function evaluateJavaTier(i) {
3303
+ const id = "java_tier";
3304
+ const title = "Java analysis tier";
3305
+ if (!i.hasJava) {
3306
+ return { id, title, status: "pass", message: "no Java sources detected" };
3307
+ }
3308
+ const jreOk = (i.jreMajor ?? 0) >= 17;
3309
+ if (jreOk && i.analyzerInstalled) {
3310
+ return { id, title, status: "pass", message: `full tier (JRE ${i.jreMajor}, analyzer installed)` };
3311
+ }
3312
+ if (jreOk && !i.analyzerInstalled) {
3313
+ return {
3314
+ id,
3315
+ title,
3316
+ status: "warn",
3317
+ message: `syntax tier (JRE ${i.jreMajor} present, analyzer missing)`,
3318
+ fix: 'Run "ctxo install java --full-tier" for resolved call/use edges'
3319
+ };
3320
+ }
3321
+ if (!jreOk && i.analyzerInstalled) {
3322
+ return {
3323
+ id,
3324
+ title,
3325
+ status: "warn",
3326
+ message: "syntax tier (analyzer installed but no JRE 17+)",
3327
+ fix: "Install a JRE 17+ and ensure it is on PATH (or set JAVA_HOME)"
3328
+ };
3329
+ }
3330
+ return {
3331
+ id,
3332
+ title,
3333
+ status: "pass",
3334
+ message: 'syntax tier (install JRE 17+ and run "ctxo install java --full-tier" for full tier)'
3335
+ };
3336
+ }
3337
+ function analyzerJarPresent() {
3338
+ try {
3339
+ const pkgJson = require4.resolve("@ctxo/lang-java-analyzer/package.json");
3340
+ const jarPath = join17(dirname7(pkgJson), "jar", "ctxo-jdt-analyzer.jar");
3341
+ return existsSync15(jarPath);
3342
+ } catch {
3343
+ return false;
3344
+ }
3345
+ }
3346
+ var SKIP_DIRS = /* @__PURE__ */ new Set([
3347
+ "node_modules",
3348
+ "target",
3349
+ "build",
3350
+ "out",
3351
+ "bin",
3352
+ ".git",
3353
+ ".ctxo",
3354
+ "dist",
3355
+ "coverage"
3356
+ ]);
3357
+ function findJavaFile(root, maxDepth) {
3358
+ function walk(dir, depth) {
3359
+ let entries;
3360
+ try {
3361
+ entries = readdirSync3(dir, { withFileTypes: true });
3362
+ } catch {
3363
+ return false;
3364
+ }
3365
+ for (const entry of entries) {
3366
+ if (entry.isSymbolicLink()) continue;
3367
+ if (entry.isFile() && entry.name.endsWith(".java")) return true;
3368
+ }
3369
+ if (depth >= maxDepth) return false;
3370
+ for (const entry of entries) {
3371
+ if (entry.isSymbolicLink()) continue;
3372
+ if (entry.isDirectory() && !SKIP_DIRS.has(entry.name)) {
3373
+ if (walk(join17(dir, entry.name), depth + 1)) return true;
3374
+ }
3375
+ }
3376
+ return false;
3377
+ }
3378
+ return walk(root, 0);
3379
+ }
3380
+ function projectHasJava(root) {
3381
+ for (const f of ["pom.xml", "build.gradle", "build.gradle.kts"]) {
3382
+ if (existsSync15(join17(root, f))) return true;
3383
+ }
3384
+ return findJavaFile(root, 4);
3385
+ }
3386
+ var JavaTierCheck = class {
3387
+ id = "java_tier";
3388
+ title = "Java analysis tier";
3389
+ async run(ctx) {
3390
+ return evaluateJavaTier({
3391
+ hasJava: projectHasJava(ctx.projectRoot),
3392
+ jreMajor: detectJavaMajor(),
3393
+ analyzerInstalled: analyzerJarPresent()
3394
+ });
3395
+ }
3396
+ };
3397
+
3223
3398
  // src/adapters/diagnostics/checks/git-check.ts
3224
3399
  import { execFileSync as execFileSync5 } from "child_process";
3225
- import { existsSync as existsSync14 } from "fs";
3226
- import { join as join16 } from "path";
3400
+ import { existsSync as existsSync16 } from "fs";
3401
+ import { join as join18 } from "path";
3227
3402
  var GitBinaryCheck = class {
3228
3403
  id = "git_binary";
3229
3404
  title = "Git available";
@@ -3240,7 +3415,7 @@ var GitRepoCheck = class {
3240
3415
  id = "git_repo";
3241
3416
  title = "Git repository";
3242
3417
  async run(ctx) {
3243
- if (existsSync14(join16(ctx.projectRoot, ".git"))) {
3418
+ if (existsSync16(join18(ctx.projectRoot, ".git"))) {
3244
3419
  return { id: this.id, title: this.title, status: "pass", message: ctx.projectRoot, value: ctx.projectRoot };
3245
3420
  }
3246
3421
  return { id: this.id, title: this.title, status: "fail", message: "Not a git repository", fix: 'Run "git init" or clone a repository' };
@@ -3248,9 +3423,9 @@ var GitRepoCheck = class {
3248
3423
  };
3249
3424
 
3250
3425
  // src/adapters/diagnostics/checks/index-check.ts
3251
- import { existsSync as existsSync15 } from "fs";
3426
+ import { existsSync as existsSync17 } from "fs";
3252
3427
  import { execFileSync as execFileSync6 } from "child_process";
3253
- import { join as join17 } from "path";
3428
+ import { join as join19 } from "path";
3254
3429
  function pass2(id, title, message, value) {
3255
3430
  return { id, title, status: "pass", message, value };
3256
3431
  }
@@ -3267,8 +3442,8 @@ var IndexDirectoryCheck = class {
3267
3442
  id = "index_directory";
3268
3443
  title = "Index directory";
3269
3444
  async run(ctx) {
3270
- const indexDir = join17(ctx.ctxoRoot, "index");
3271
- if (!existsSync15(indexDir)) {
3445
+ const indexDir = join19(ctx.ctxoRoot, "index");
3446
+ if (!existsSync17(indexDir)) {
3272
3447
  return fail(this.id, this.title, "No index directory", 'Run "ctxo index"');
3273
3448
  }
3274
3449
  const indices = getIndices(ctx);
@@ -3362,8 +3537,8 @@ var CoChangesCacheCheck = class {
3362
3537
  id = "co_changes_cache";
3363
3538
  title = "Co-changes cache";
3364
3539
  async run(ctx) {
3365
- const coChangesPath = join17(ctx.ctxoRoot, "index", "co-changes.json");
3366
- if (existsSync15(coChangesPath)) {
3540
+ const coChangesPath = join19(ctx.ctxoRoot, "index", "co-changes.json");
3541
+ if (existsSync17(coChangesPath)) {
3367
3542
  return pass2(this.id, this.title, "present");
3368
3543
  }
3369
3544
  return warn2(this.id, this.title, "missing (co-change analysis disabled)", 'Run "ctxo index" with git history');
@@ -3387,14 +3562,14 @@ var SchemaVersionCheck = class {
3387
3562
  };
3388
3563
 
3389
3564
  // src/adapters/diagnostics/checks/storage-check.ts
3390
- import { existsSync as existsSync16, readFileSync as readFileSync11 } from "fs";
3391
- import { join as join18 } from "path";
3565
+ import { existsSync as existsSync18, readFileSync as readFileSync11 } from "fs";
3566
+ import { join as join20 } from "path";
3392
3567
  var SqliteCacheCheck = class {
3393
3568
  id = "sqlite_cache";
3394
3569
  title = "SQLite cache";
3395
3570
  async run(ctx) {
3396
- const dbPath = join18(ctx.ctxoRoot, ".cache", "symbols.db");
3397
- if (!existsSync16(dbPath)) {
3571
+ const dbPath = join20(ctx.ctxoRoot, ".cache", "symbols.db");
3572
+ if (!existsSync18(dbPath)) {
3398
3573
  return { id: this.id, title: this.title, status: "warn", message: "SQLite cache missing", fix: 'Run "ctxo sync" to rebuild' };
3399
3574
  }
3400
3575
  try {
@@ -3416,14 +3591,14 @@ var SqliteCacheCheck = class {
3416
3591
  };
3417
3592
 
3418
3593
  // src/adapters/diagnostics/checks/config-check.ts
3419
- import { existsSync as existsSync17, readFileSync as readFileSync12 } from "fs";
3420
- import { join as join19 } from "path";
3594
+ import { existsSync as existsSync19, readFileSync as readFileSync12 } from "fs";
3595
+ import { join as join21 } from "path";
3421
3596
  var ConfigFileCheck = class {
3422
3597
  id = "config_file";
3423
3598
  title = "Config file";
3424
3599
  async run(ctx) {
3425
- const configPath = join19(ctx.ctxoRoot, "config.yaml");
3426
- if (!existsSync17(configPath)) {
3600
+ const configPath = join21(ctx.ctxoRoot, "config.yaml");
3601
+ if (!existsSync19(configPath)) {
3427
3602
  return { id: this.id, title: this.title, status: "warn", message: "No config.yaml (using defaults)" };
3428
3603
  }
3429
3604
  try {
@@ -3461,8 +3636,8 @@ var ConfigFileCheck = class {
3461
3636
  };
3462
3637
 
3463
3638
  // 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";
3639
+ import { existsSync as existsSync20, readdirSync as readdirSync4, statSync as statSync2 } from "fs";
3640
+ import { join as join22 } from "path";
3466
3641
  var DEFAULT_THRESHOLDS = { warnMb: 100, failMb: 500 };
3467
3642
  var DiskUsageCheck = class {
3468
3643
  id = "disk_usage";
@@ -3472,7 +3647,7 @@ var DiskUsageCheck = class {
3472
3647
  this.thresholds = thresholds;
3473
3648
  }
3474
3649
  async run(ctx) {
3475
- if (!existsSync18(ctx.ctxoRoot)) {
3650
+ if (!existsSync20(ctx.ctxoRoot)) {
3476
3651
  return { id: this.id, title: this.title, status: "pass", message: "no .ctxo/ directory", value: "0" };
3477
3652
  }
3478
3653
  const bytes = this.getDirSize(ctx.ctxoRoot);
@@ -3489,12 +3664,12 @@ var DiskUsageCheck = class {
3489
3664
  getDirSize(dirPath) {
3490
3665
  let total = 0;
3491
3666
  try {
3492
- const entries = readdirSync3(dirPath, { withFileTypes: true, recursive: true });
3667
+ const entries = readdirSync4(dirPath, { withFileTypes: true, recursive: true });
3493
3668
  for (const entry of entries) {
3494
3669
  if (entry.isFile()) {
3495
3670
  try {
3496
3671
  const parent = entry.parentPath ?? entry.path ?? dirPath;
3497
- total += statSync2(join20(parent, entry.name)).size;
3672
+ total += statSync2(join22(parent, entry.name)).size;
3498
3673
  } catch {
3499
3674
  }
3500
3675
  }
@@ -3507,9 +3682,9 @@ var DiskUsageCheck = class {
3507
3682
 
3508
3683
  // src/cli/doctor-fix.ts
3509
3684
  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";
3685
+ import { appendFileSync, existsSync as existsSync21, mkdirSync as mkdirSync6, rmSync as rmSync2, unlinkSync as unlinkSync2 } from "fs";
3686
+ import { createRequire as createRequire8 } from "module";
3687
+ import { dirname as dirname8, join as join23 } from "path";
3513
3688
  async function applyFixes(report, options) {
3514
3689
  if (isCI(process.env) && !options.yes && !options.dryRun) {
3515
3690
  return {
@@ -3644,8 +3819,8 @@ async function fixIndex(projectRoot, dryRun) {
3644
3819
  }
3645
3820
  async function pruneOrphans(projectRoot, ctxoRoot, dryRun) {
3646
3821
  if (dryRun) return;
3647
- const indexDir = join21(ctxoRoot, "index");
3648
- if (!existsSync19(indexDir)) return;
3822
+ const indexDir = join23(ctxoRoot, "index");
3823
+ if (!existsSync21(indexDir)) return;
3649
3824
  const sourceFiles = listGitFiles2(projectRoot);
3650
3825
  if (sourceFiles.size === 0) return;
3651
3826
  const reader = new JsonIndexReader(ctxoRoot);
@@ -3653,9 +3828,9 @@ async function pruneOrphans(projectRoot, ctxoRoot, dryRun) {
3653
3828
  let removed = 0;
3654
3829
  for (const idx of indices) {
3655
3830
  if (sourceFiles.has(idx.file)) continue;
3656
- const jsonPath = join21(indexDir, `${idx.file}.json`);
3831
+ const jsonPath = join23(indexDir, `${idx.file}.json`);
3657
3832
  try {
3658
- if (existsSync19(jsonPath)) {
3833
+ if (existsSync21(jsonPath)) {
3659
3834
  unlinkSync2(jsonPath);
3660
3835
  removed += 1;
3661
3836
  pruneEmptyParents(jsonPath, indexDir);
@@ -3666,14 +3841,14 @@ async function pruneOrphans(projectRoot, ctxoRoot, dryRun) {
3666
3841
  if (removed === 0) return;
3667
3842
  }
3668
3843
  function pruneEmptyParents(filePath, stopAt) {
3669
- let dir = dirname7(filePath);
3844
+ let dir = dirname8(filePath);
3670
3845
  while (dir !== stopAt && dir.startsWith(stopAt)) {
3671
3846
  try {
3672
3847
  rmSync2(dir, { recursive: false });
3673
3848
  } catch {
3674
3849
  return;
3675
3850
  }
3676
- dir = dirname7(dir);
3851
+ dir = dirname8(dir);
3677
3852
  }
3678
3853
  }
3679
3854
  function listGitFiles2(projectRoot) {
@@ -3689,12 +3864,12 @@ function listGitFiles2(projectRoot) {
3689
3864
  }
3690
3865
  }
3691
3866
  async function fixSqliteCache(projectRoot, ctxoRoot, dryRun) {
3692
- const cacheDir = join21(ctxoRoot, ".cache");
3867
+ const cacheDir = join23(ctxoRoot, ".cache");
3693
3868
  if (dryRun) return;
3694
- if (existsSync19(cacheDir)) rmSync2(cacheDir, { recursive: true, force: true });
3869
+ if (existsSync21(cacheDir)) rmSync2(cacheDir, { recursive: true, force: true });
3695
3870
  await new SyncCommand(projectRoot).run();
3696
3871
  }
3697
- var doctorRequire = createRequire7(import.meta.url);
3872
+ var doctorRequire = createRequire8(import.meta.url);
3698
3873
  function canResolvePlugin(lang) {
3699
3874
  try {
3700
3875
  doctorRequire.resolve(`${officialPluginFor(lang)}/package.json`);
@@ -3708,8 +3883,8 @@ function isCI(env) {
3708
3883
  }
3709
3884
  function writeLog(ctxoRoot, attempts, halted, reason) {
3710
3885
  try {
3711
- if (!existsSync19(ctxoRoot)) mkdirSync6(ctxoRoot, { recursive: true });
3712
- const path = join21(ctxoRoot, "doctor-fix.log");
3886
+ if (!existsSync21(ctxoRoot)) mkdirSync6(ctxoRoot, { recursive: true });
3887
+ const path = join23(ctxoRoot, "doctor-fix.log");
3713
3888
  const ts = (/* @__PURE__ */ new Date()).toISOString();
3714
3889
  const lines = [`# ${ts} doctor --fix${halted ? " (halted: " + reason + ")" : ""}`];
3715
3890
  for (const a of attempts) {
@@ -3742,10 +3917,10 @@ var DoctorCommand = class {
3742
3917
  ctxoRoot;
3743
3918
  constructor(projectRoot) {
3744
3919
  this.projectRoot = projectRoot;
3745
- this.ctxoRoot = join22(projectRoot, ".ctxo");
3920
+ this.ctxoRoot = join24(projectRoot, ".ctxo");
3746
3921
  }
3747
3922
  async run(options = {}) {
3748
- const { getVersion: getVersion2 } = await import("./cli-router-45W4IRYE.js");
3923
+ const { getVersion: getVersion2 } = await import("./cli-router-NFAGLJ46.js");
3749
3924
  console.error(`ctxo v${getVersion2()} \u2014 health check
3750
3925
  `);
3751
3926
  const checks = [
@@ -3760,6 +3935,7 @@ var DoctorCommand = class {
3760
3935
  new TsMorphCheck(),
3761
3936
  new TreeSitterCheck(),
3762
3937
  new LanguageCoverageCheck(),
3938
+ new JavaTierCheck(),
3763
3939
  new DiskUsageCheck(),
3764
3940
  new SymbolCountCheck(),
3765
3941
  new EdgeCountCheck(),
@@ -3767,8 +3943,8 @@ var DoctorCommand = class {
3767
3943
  new CoChangesCacheCheck(),
3768
3944
  new SchemaVersionCheck()
3769
3945
  ];
3770
- const indexDir = join22(this.ctxoRoot, "index");
3771
- const indices = existsSync20(indexDir) ? new JsonIndexReader(this.ctxoRoot).readAll() : [];
3946
+ const indexDir = join24(this.ctxoRoot, "index");
3947
+ const indices = existsSync22(indexDir) ? new JsonIndexReader(this.ctxoRoot).readAll() : [];
3772
3948
  const checker = new HealthChecker(checks, { quiet: Boolean(options.quiet) });
3773
3949
  const report = await checker.runAll({
3774
3950
  projectRoot: this.projectRoot,
@@ -3809,8 +3985,8 @@ var DoctorCommand = class {
3809
3985
  };
3810
3986
 
3811
3987
  // 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";
3988
+ import { readFileSync as readFileSync13, writeFileSync as writeFileSync5, existsSync as existsSync23 } from "fs";
3989
+ import { join as join25, basename } from "path";
3814
3990
  import { execFile } from "child_process";
3815
3991
 
3816
3992
  // src/cli/graph-loader.ts
@@ -3826,7 +4002,7 @@ function loadGraph(ctxoRoot) {
3826
4002
  var VisualizeCommand = class {
3827
4003
  constructor(projectRoot) {
3828
4004
  this.projectRoot = projectRoot;
3829
- this.ctxoRoot = join23(projectRoot, ".ctxo");
4005
+ this.ctxoRoot = join25(projectRoot, ".ctxo");
3830
4006
  }
3831
4007
  projectRoot;
3832
4008
  ctxoRoot;
@@ -3969,7 +4145,7 @@ var VisualizeCommand = class {
3969
4145
  const templatePath = this.getTemplatePath();
3970
4146
  const template = readFileSync13(templatePath, "utf-8");
3971
4147
  const html = template.replace("/*__CTXO_DATA__*/null", JSON.stringify(payload));
3972
- const outputPath = options.output ?? join23(this.ctxoRoot, "visualize.html");
4148
+ const outputPath = options.output ?? join25(this.ctxoRoot, "visualize.html");
3973
4149
  writeFileSync5(outputPath, html, "utf-8");
3974
4150
  console.error(`[ctxo] Wrote visualization to ${outputPath}`);
3975
4151
  console.error(`[ctxo] ${nodes.length} symbols, ${edges.length} edges, ${deadResult.deadSymbols.length} dead`);
@@ -3980,9 +4156,9 @@ var VisualizeCommand = class {
3980
4156
  getTemplatePath() {
3981
4157
  let dir = import.meta.dirname;
3982
4158
  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, "..");
4159
+ const candidate = join25(dir, "pages", "visualize-template.html");
4160
+ if (existsSync23(candidate)) return candidate;
4161
+ const parent = join25(dir, "..");
3986
4162
  if (parent === dir) break;
3987
4163
  dir = parent;
3988
4164
  }
@@ -4006,8 +4182,8 @@ var VisualizeCommand = class {
4006
4182
  };
4007
4183
 
4008
4184
  // src/cli/update-command.ts
4009
- import { existsSync as existsSync22, readFileSync as readFileSync14 } from "fs";
4010
- import { join as join24 } from "path";
4185
+ import { existsSync as existsSync24, readFileSync as readFileSync14 } from "fs";
4186
+ import { join as join26 } from "path";
4011
4187
 
4012
4188
  // src/core/update/registry-client.ts
4013
4189
  import { request } from "https";
@@ -4325,8 +4501,8 @@ var UpdateCommand = class {
4325
4501
  return this.projectHasCtxoDep() ? "execute" : "print";
4326
4502
  }
4327
4503
  projectHasCtxoDep() {
4328
- const pkg = join24(this.projectRoot, "package.json");
4329
- if (!existsSync22(pkg)) return false;
4504
+ const pkg = join26(this.projectRoot, "package.json");
4505
+ if (!existsSync24(pkg)) return false;
4330
4506
  try {
4331
4507
  const json = JSON.parse(readFileSync14(pkg, "utf-8"));
4332
4508
  const merged = { ...json.dependencies ?? {}, ...json.devDependencies ?? {} };
@@ -4363,11 +4539,11 @@ function isLockedCI2(env) {
4363
4539
  }
4364
4540
 
4365
4541
  // src/cli/blast-radius-command.ts
4366
- import { join as join25 } from "path";
4542
+ import { join as join27 } from "path";
4367
4543
  var BlastRadiusCommand = class {
4368
4544
  ctxoRoot;
4369
4545
  constructor(projectRoot) {
4370
- this.ctxoRoot = join25(projectRoot, ".ctxo");
4546
+ this.ctxoRoot = join27(projectRoot, ".ctxo");
4371
4547
  }
4372
4548
  run(options) {
4373
4549
  const { graph } = loadGraph(this.ctxoRoot);
@@ -4395,8 +4571,8 @@ var BlastRadiusCommand = class {
4395
4571
  };
4396
4572
 
4397
4573
  // 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";
4574
+ import { join as join29, relative as relative3, sep as sep2 } from "path";
4575
+ import { existsSync as existsSync26, readFileSync as readFileSync16 } from "fs";
4400
4576
 
4401
4577
  // src/core/gate/gate-config.ts
4402
4578
  var DIAL = {
@@ -4463,8 +4639,8 @@ function transcriptHasBlastCheck(lines, symbolId) {
4463
4639
  }
4464
4640
 
4465
4641
  // 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";
4642
+ import { existsSync as existsSync25, mkdirSync as mkdirSync7, readFileSync as readFileSync15, writeFileSync as writeFileSync6 } from "fs";
4643
+ import { join as join28 } from "path";
4468
4644
  var log7 = createLogger("ctxo:gate-store");
4469
4645
  var GateSessionStore = class {
4470
4646
  constructor(cacheDir) {
@@ -4473,11 +4649,11 @@ var GateSessionStore = class {
4473
4649
  cacheDir;
4474
4650
  file(sessionId) {
4475
4651
  const safe = sessionId.replace(/[^a-zA-Z0-9_-]/g, "_");
4476
- return join26(this.cacheDir, "gate", `${safe}.json`);
4652
+ return join28(this.cacheDir, "gate", `${safe}.json`);
4477
4653
  }
4478
4654
  read(sessionId) {
4479
4655
  const f = this.file(sessionId);
4480
- if (!existsSync23(f)) return /* @__PURE__ */ new Set();
4656
+ if (!existsSync25(f)) return /* @__PURE__ */ new Set();
4481
4657
  try {
4482
4658
  return new Set(JSON.parse(readFileSync15(f, "utf-8")));
4483
4659
  } catch (err) {
@@ -4493,7 +4669,7 @@ var GateSessionStore = class {
4493
4669
  const set = this.read(sessionId);
4494
4670
  set.add(symbolId);
4495
4671
  const f = this.file(sessionId);
4496
- mkdirSync7(join26(this.cacheDir, "gate"), { recursive: true });
4672
+ mkdirSync7(join28(this.cacheDir, "gate"), { recursive: true });
4497
4673
  writeFileSync6(f, JSON.stringify([...set]), "utf-8");
4498
4674
  } catch (err) {
4499
4675
  log7.error(`${err.message}`);
@@ -4532,8 +4708,8 @@ var RANK_ALL = 1e5;
4532
4708
  var GateHookCommand = class {
4533
4709
  constructor(projectRoot) {
4534
4710
  this.projectRoot = projectRoot;
4535
- this.ctxoRoot = join27(projectRoot, ".ctxo");
4536
- this.cacheDir = join27(this.ctxoRoot, ".cache");
4711
+ this.ctxoRoot = join29(projectRoot, ".ctxo");
4712
+ this.cacheDir = join29(this.ctxoRoot, ".cache");
4537
4713
  }
4538
4714
  projectRoot;
4539
4715
  ctxoRoot;
@@ -4549,11 +4725,11 @@ var GateHookCommand = class {
4549
4725
  const thresholds = resolveGateThresholds(loaded.config.gate);
4550
4726
  if (!thresholds.enabled) return { block: false };
4551
4727
  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 };
4728
+ const indexFile = join29(this.ctxoRoot, "index", `${relPath}.json`);
4729
+ if (!existsSync26(indexFile)) return { block: false };
4554
4730
  const fileIndex = new JsonIndexReader(this.ctxoRoot).readSingle(indexFile);
4555
4731
  if (!fileIndex) return { block: false };
4556
- if (!existsSync24(filePath)) return { block: false };
4732
+ if (!existsSync26(filePath)) return { block: false };
4557
4733
  const source = readFileSync16(filePath, "utf-8");
4558
4734
  const line = lineOfSnippet(source, oldString);
4559
4735
  if (line === null) return { block: false };
@@ -4561,7 +4737,7 @@ var GateHookCommand = class {
4561
4737
  if (!symbolId) return { block: false };
4562
4738
  const store = new GateSessionStore(this.cacheDir);
4563
4739
  if (store.hasWarned(payload.session_id, symbolId)) return { block: false };
4564
- if (payload.transcript_path && existsSync24(payload.transcript_path)) {
4740
+ if (payload.transcript_path && existsSync26(payload.transcript_path)) {
4565
4741
  const lines = readFileSync16(payload.transcript_path, "utf-8").split("\n").filter(Boolean);
4566
4742
  if (transcriptHasBlastCheck(lines, symbolId)) return { block: false };
4567
4743
  }
@@ -4627,12 +4803,12 @@ var GateHookCommand = class {
4627
4803
  };
4628
4804
 
4629
4805
  // src/cli/gate-command.ts
4630
- import { join as join28 } from "path";
4806
+ import { join as join30 } from "path";
4631
4807
  var RANK_ALL2 = 1e5;
4632
4808
  var GateCommand = class {
4633
4809
  ctxoRoot;
4634
4810
  constructor(projectRoot) {
4635
- this.ctxoRoot = join28(projectRoot, ".ctxo");
4811
+ this.ctxoRoot = join30(projectRoot, ".ctxo");
4636
4812
  }
4637
4813
  preview(options = {}) {
4638
4814
  const gate = loadConfig(this.ctxoRoot).config.gate;
@@ -4672,8 +4848,8 @@ var GateCommand = class {
4672
4848
  function getVersion() {
4673
4849
  let dir = import.meta.dirname;
4674
4850
  for (let i = 0; i < 10; i++) {
4675
- const pkg = join29(dir, "package.json");
4676
- if (existsSync25(pkg)) {
4851
+ const pkg = join31(dir, "package.json");
4852
+ if (existsSync27(pkg)) {
4677
4853
  try {
4678
4854
  const json = JSON.parse(readFileSync17(pkg, "utf-8"));
4679
4855
  return json.version ?? "unknown";
@@ -4681,7 +4857,7 @@ function getVersion() {
4681
4857
  break;
4682
4858
  }
4683
4859
  }
4684
- const parent = join29(dir, "..");
4860
+ const parent = join31(dir, "..");
4685
4861
  if (parent === dir) break;
4686
4862
  dir = parent;
4687
4863
  }
@@ -4690,8 +4866,8 @@ function getVersion() {
4690
4866
  function locateGitRoot(startDir) {
4691
4867
  let dir = startDir;
4692
4868
  for (let i = 0; i < 12; i++) {
4693
- if (existsSync25(join29(dir, ".git"))) return dir;
4694
- const parent = dirname8(dir);
4869
+ if (existsSync27(join31(dir, ".git"))) return dir;
4870
+ const parent = dirname9(dir);
4695
4871
  if (parent === dir) break;
4696
4872
  dir = parent;
4697
4873
  }
@@ -4808,6 +4984,8 @@ var CliRouter = class {
4808
4984
  else if (a === "--global" || a === "-g") flagValues["global"] = true;
4809
4985
  else if (a === "--dry-run") flagValues["dryRun"] = true;
4810
4986
  else if (a === "--force") flagValues["force"] = true;
4987
+ else if (a === "--full-tier") flagValues["fullTier"] = true;
4988
+ else if (a === "--syntax-only") flagValues["syntaxOnly"] = true;
4811
4989
  else if (a === "--pm") {
4812
4990
  const next = args[++i];
4813
4991
  if (!next) {
@@ -4838,6 +5016,8 @@ var CliRouter = class {
4838
5016
  global: flagValues["global"] === true,
4839
5017
  dryRun: flagValues["dryRun"] === true,
4840
5018
  force: flagValues["force"] === true,
5019
+ fullTier: flagValues["fullTier"] === true,
5020
+ syntaxOnly: flagValues["syntaxOnly"] === true,
4841
5021
  pm: typeof flagValues["pm"] === "string" ? flagValues["pm"] : void 0,
4842
5022
  version: typeof flagValues["version"] === "string" ? flagValues["version"] : void 0
4843
5023
  });
@@ -4972,4 +5152,4 @@ export {
4972
5152
  getVersion,
4973
5153
  locateGitRoot
4974
5154
  };
4975
- //# sourceMappingURL=cli-router-45W4IRYE.js.map
5155
+ //# sourceMappingURL=cli-router-NFAGLJ46.js.map