ctxloom-pro 1.2.5 → 1.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -166,12 +166,12 @@ var init_VectorStore = __esm({
166
166
  // server/index.ts
167
167
  import express from "express";
168
168
  import cors from "cors";
169
- import path43 from "path";
169
+ import path44 from "path";
170
170
  import fs32 from "fs";
171
171
  import { fileURLToPath as fileURLToPath2 } from "url";
172
172
 
173
173
  // server/loader.ts
174
- import path37 from "path";
174
+ import path38 from "path";
175
175
 
176
176
  // ../../packages/core/src/graph/DependencyGraph.ts
177
177
  import fs7 from "fs";
@@ -327,7 +327,7 @@ var GrammarLoader = class {
327
327
  const url = entry.downloadUrl?.trim() ? entry.downloadUrl : `${this.cdn}/${entry.npmPackage}@${entry.version}/${entry.wasmFile}`;
328
328
  const dest = path.join(this.cacheDir, entry.wasmFile);
329
329
  logger.info("Downloading grammar", { language, url, source: entry.downloadUrl?.trim() ? "custom" : "cdn" });
330
- fs.mkdirSync(this.cacheDir, { recursive: true });
330
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
331
331
  await this.download(url, dest);
332
332
  if (entry.sha256 && !this.skipVerify) {
333
333
  await this.verifyHash(dest, entry.sha256, language);
@@ -341,6 +341,12 @@ var GrammarLoader = class {
341
341
  return new Promise((resolve, reject) => {
342
342
  const tmp = dest + ".tmp";
343
343
  const file = fs.createWriteStream(tmp);
344
+ const onFileError = (err) => {
345
+ file.destroy();
346
+ fs.rmSync(tmp, { force: true });
347
+ reject(err);
348
+ };
349
+ file.on("error", onFileError);
344
350
  const request = https.get(url, (response) => {
345
351
  if (response.statusCode === 301 || response.statusCode === 302) {
346
352
  const location = response.headers.location;
@@ -370,11 +376,6 @@ var GrammarLoader = class {
370
376
  fs.rmSync(tmp, { force: true });
371
377
  reject(err);
372
378
  });
373
- file.on("error", (err) => {
374
- file.destroy();
375
- fs.rmSync(tmp, { force: true });
376
- reject(err);
377
- });
378
379
  file.on("finish", () => {
379
380
  fs.renameSync(tmp, dest);
380
381
  resolve();
@@ -1001,15 +1002,14 @@ var ASTParser = class {
1001
1002
  return;
1002
1003
  }
1003
1004
  case "import_declaration": {
1005
+ const specs = [];
1004
1006
  const walkImport = (n) => {
1005
1007
  if (n.type === "import_spec") {
1006
1008
  const pathNode = n.childForFieldName?.("path");
1007
1009
  if (pathNode) {
1008
1010
  const spec = pathNode.text.replace(/^"|"$/g, "");
1009
- nodes.push({
1010
- type: "import",
1011
+ specs.push({
1011
1012
  name: spec,
1012
- source: spec,
1013
1013
  startLine: n.startPosition.row + 1,
1014
1014
  endLine: n.endPosition.row + 1
1015
1015
  });
@@ -1020,6 +1020,25 @@ var ASTParser = class {
1020
1020
  }
1021
1021
  };
1022
1022
  walkImport(node);
1023
+ if (specs.length > 0) {
1024
+ const firstSpec = specs[0];
1025
+ nodes.push({
1026
+ type: "import",
1027
+ name: firstSpec.name,
1028
+ source: firstSpec.name,
1029
+ startLine: node.startPosition.row + 1,
1030
+ endLine: node.endPosition.row + 1
1031
+ });
1032
+ }
1033
+ for (const spec of specs) {
1034
+ nodes.push({
1035
+ type: "import",
1036
+ name: spec.name,
1037
+ source: spec.name,
1038
+ startLine: spec.startLine,
1039
+ endLine: spec.endLine
1040
+ });
1041
+ }
1023
1042
  return;
1024
1043
  }
1025
1044
  }
@@ -1341,6 +1360,24 @@ var ASTParser = class {
1341
1360
  const lines = source.split("\n");
1342
1361
  const walk = (node) => {
1343
1362
  switch (node.type) {
1363
+ case "call": {
1364
+ const methodNode = node.childForFieldName?.("method") ?? node.children.find((c) => c?.type === "identifier");
1365
+ const name = methodNode?.text ?? "";
1366
+ if (name === "require" || name === "require_relative" || name === "load" || name === "autoload") {
1367
+ const argsNode = node.childForFieldName?.("arguments") ?? node.children.find((c) => c?.type === "argument_list");
1368
+ const firstStringArg = argsNode?.children.find((c) => c?.type === "string" || c?.type === "simple_symbol");
1369
+ const spec = firstStringArg?.text.replace(/^['":]+|['"]+$/g, "") ?? "";
1370
+ nodes.push({
1371
+ type: "import",
1372
+ name: spec,
1373
+ source: spec,
1374
+ startLine: node.startPosition.row + 1,
1375
+ endLine: node.endPosition.row + 1
1376
+ });
1377
+ return;
1378
+ }
1379
+ break;
1380
+ }
1344
1381
  case "method":
1345
1382
  case "singleton_method": {
1346
1383
  const nameNode = node.childForFieldName?.("name") ?? node.children.find((c) => c?.type === "identifier");
@@ -1628,9 +1665,18 @@ var ASTParser = class {
1628
1665
  const walk = (node) => {
1629
1666
  switch (node.type) {
1630
1667
  case "import_or_export": {
1631
- const uriNode = node.children.find((c) => c?.type === "uri");
1668
+ const findUri = (n) => {
1669
+ if (n.type === "uri") return n;
1670
+ for (const c of n.children) {
1671
+ if (!c) continue;
1672
+ const hit = findUri(c);
1673
+ if (hit) return hit;
1674
+ }
1675
+ return void 0;
1676
+ };
1677
+ const uriNode = findUri(node);
1632
1678
  const uri = uriNode?.text?.replace(/['"]/g, "") ?? "";
1633
- if (uri.startsWith(".")) {
1679
+ if (uri) {
1634
1680
  nodes.push({
1635
1681
  type: "import",
1636
1682
  name: uri,
@@ -3135,8 +3181,8 @@ var CoChangeIndex = class _CoChangeIndex {
3135
3181
  if (event.isBulk || event.isMerge) return;
3136
3182
  const paths = event.files.map((f) => f.path);
3137
3183
  if (paths.length === 0) return;
3138
- for (const path44 of paths) {
3139
- this.nodeCounts.set(path44, (this.nodeCounts.get(path44) ?? 0) + 1);
3184
+ for (const path45 of paths) {
3185
+ this.nodeCounts.set(path45, (this.nodeCounts.get(path45) ?? 0) + 1);
3140
3186
  }
3141
3187
  for (let i = 0; i < paths.length; i++) {
3142
3188
  for (let j = i + 1; j < paths.length; j++) {
@@ -3283,8 +3329,8 @@ var ChurnIndex = class _ChurnIndex {
3283
3329
  */
3284
3330
  snapshot() {
3285
3331
  const nodes = {};
3286
- for (const [path44, raw] of this.nodes) {
3287
- nodes[path44] = {
3332
+ for (const [path45, raw] of this.nodes) {
3333
+ nodes[path45] = {
3288
3334
  commits: raw.commits,
3289
3335
  churnLines: raw.churnLines,
3290
3336
  bugCommits: raw.bugCommits,
@@ -3299,8 +3345,8 @@ var ChurnIndex = class _ChurnIndex {
3299
3345
  */
3300
3346
  static load(s) {
3301
3347
  const idx = new _ChurnIndex();
3302
- for (const [path44, raw] of Object.entries(s.nodes)) {
3303
- idx.nodes.set(path44, {
3348
+ for (const [path45, raw] of Object.entries(s.nodes)) {
3349
+ idx.nodes.set(path45, {
3304
3350
  commits: raw.commits,
3305
3351
  churnLines: raw.churnLines,
3306
3352
  bugCommits: raw.bugCommits,
@@ -3313,8 +3359,8 @@ var ChurnIndex = class _ChurnIndex {
3313
3359
  // -------------------------------------------------------------------------
3314
3360
  // Private helpers
3315
3361
  // -------------------------------------------------------------------------
3316
- getOrCreate(path44) {
3317
- const existing = this.nodes.get(path44);
3362
+ getOrCreate(path45) {
3363
+ const existing = this.nodes.get(path45);
3318
3364
  if (existing !== void 0) return existing;
3319
3365
  const fresh = {
3320
3366
  commits: 0,
@@ -3323,7 +3369,7 @@ var ChurnIndex = class _ChurnIndex {
3323
3369
  authorCounts: {},
3324
3370
  lastTouch: 0
3325
3371
  };
3326
- this.nodes.set(path44, fresh);
3372
+ this.nodes.set(path45, fresh);
3327
3373
  return fresh;
3328
3374
  }
3329
3375
  };
@@ -3404,12 +3450,12 @@ var OwnershipIndex = class _OwnershipIndex {
3404
3450
  */
3405
3451
  snapshot() {
3406
3452
  const nodes = {};
3407
- for (const [path44, raw] of this.nodes) {
3453
+ for (const [path45, raw] of this.nodes) {
3408
3454
  const authorWeights = {};
3409
3455
  for (const [email, entry] of Object.entries(raw.authorWeights)) {
3410
3456
  authorWeights[email] = { ...entry };
3411
3457
  }
3412
- nodes[path44] = { authorWeights, lastTouch: raw.lastTouch };
3458
+ nodes[path45] = { authorWeights, lastTouch: raw.lastTouch };
3413
3459
  }
3414
3460
  return { version: 1, nodes };
3415
3461
  }
@@ -3418,23 +3464,23 @@ var OwnershipIndex = class _OwnershipIndex {
3418
3464
  */
3419
3465
  static load(s) {
3420
3466
  const idx = new _OwnershipIndex();
3421
- for (const [path44, raw] of Object.entries(s.nodes)) {
3467
+ for (const [path45, raw] of Object.entries(s.nodes)) {
3422
3468
  const authorWeights = {};
3423
3469
  for (const [email, entry] of Object.entries(raw.authorWeights)) {
3424
3470
  authorWeights[email] = { ...entry };
3425
3471
  }
3426
- idx.nodes.set(path44, { authorWeights, lastTouch: raw.lastTouch });
3472
+ idx.nodes.set(path45, { authorWeights, lastTouch: raw.lastTouch });
3427
3473
  }
3428
3474
  return idx;
3429
3475
  }
3430
3476
  // -------------------------------------------------------------------------
3431
3477
  // Private helpers
3432
3478
  // -------------------------------------------------------------------------
3433
- getOrCreate(path44) {
3434
- const existing = this.nodes.get(path44);
3479
+ getOrCreate(path45) {
3480
+ const existing = this.nodes.get(path45);
3435
3481
  if (existing !== void 0) return existing;
3436
3482
  const fresh = { authorWeights: {}, lastTouch: 0 };
3437
- this.nodes.set(path44, fresh);
3483
+ this.nodes.set(path45, fresh);
3438
3484
  return fresh;
3439
3485
  }
3440
3486
  };
@@ -4672,8 +4718,8 @@ function getErrorMap() {
4672
4718
 
4673
4719
  // ../../node_modules/zod/v3/helpers/parseUtil.js
4674
4720
  var makeIssue = (params) => {
4675
- const { data, path: path44, errorMaps, issueData } = params;
4676
- const fullPath = [...path44, ...issueData.path || []];
4721
+ const { data, path: path45, errorMaps, issueData } = params;
4722
+ const fullPath = [...path45, ...issueData.path || []];
4677
4723
  const fullIssue = {
4678
4724
  ...issueData,
4679
4725
  path: fullPath
@@ -4789,11 +4835,11 @@ var errorUtil;
4789
4835
 
4790
4836
  // ../../node_modules/zod/v3/types.js
4791
4837
  var ParseInputLazyPath = class {
4792
- constructor(parent, value, path44, key) {
4838
+ constructor(parent, value, path45, key) {
4793
4839
  this._cachedPath = [];
4794
4840
  this.parent = parent;
4795
4841
  this.data = value;
4796
- this._path = path44;
4842
+ this._path = path45;
4797
4843
  this._key = key;
4798
4844
  }
4799
4845
  get path() {
@@ -11268,15 +11314,15 @@ import os2 from "os";
11268
11314
  import { readFileSync as readFileSync2 } from "fs";
11269
11315
 
11270
11316
  // ../../packages/core/src/license/index.ts
11271
- import os3 from "os";
11317
+ import os5 from "os";
11272
11318
 
11273
11319
  // ../../packages/core/src/license/DistinctIdStore.ts
11274
11320
  import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync as existsSync2 } from "fs";
11275
11321
  import path32 from "path";
11276
- import os4 from "os";
11322
+ import os3 from "os";
11277
11323
  var UUID_V4_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
11278
11324
  function distinctIdPath(home) {
11279
- return path32.join(home ?? os4.homedir(), ".ctxloom", "distinct_id");
11325
+ return path32.join(home ?? os3.homedir(), ".ctxloom", "distinct_id");
11280
11326
  }
11281
11327
  function isValidV4(id) {
11282
11328
  return typeof id === "string" && UUID_V4_REGEX.test(id);
@@ -11295,7 +11341,7 @@ function getOrCreateDistinctId(home) {
11295
11341
  }
11296
11342
  const record = {
11297
11343
  id: crypto.randomUUID(),
11298
- alias_pending: os4.hostname()
11344
+ alias_pending: os3.hostname()
11299
11345
  };
11300
11346
  mkdirSync2(path32.dirname(filePath), { recursive: true });
11301
11347
  writeFileSync2(filePath, JSON.stringify(record), { mode: 384 });
@@ -11323,7 +11369,7 @@ function resolveTelemetryLevel() {
11323
11369
  }
11324
11370
  var TELEMETRY_LEVEL = resolveTelemetryLevel();
11325
11371
  var TELEMETRY_DISABLED = TELEMETRY_LEVEL === "off";
11326
- var CTXLOOM_VERSION = "1.2.5".length > 0 ? "1.2.5" : "dev";
11372
+ var CTXLOOM_VERSION = "1.2.6".length > 0 ? "1.2.6" : "dev";
11327
11373
  var POSTHOG_HOST = "https://eu.i.posthog.com";
11328
11374
  var POSTHOG_KEY = process.env["POSTHOG_API_KEY"] ?? (true ? "phc_CiDkmFLcZ2K6uCpcoSUQLmFrnnUvsyXGhSxopX5TVKE6" : "");
11329
11375
  var SENTRY_DSN = process.env["SENTRY_DSN"] ?? (true ? "https://81c94a0f04a8e242dee493ac1e17f733@o4508531702497280.ingest.de.sentry.io/4511256875368528" : "");
@@ -11454,28 +11500,33 @@ function parseStack(stack) {
11454
11500
  }).filter((f) => f !== null).slice(0, 20);
11455
11501
  }
11456
11502
 
11457
- // ../../packages/core/src/license/TelemetryNotice.ts
11503
+ // ../../packages/core/src/license/FunnelMilestones.ts
11458
11504
  import { existsSync as existsSync3, mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "fs";
11459
11505
  import path33 from "path";
11460
- import os5 from "os";
11506
+ import os4 from "os";
11507
+
11508
+ // ../../packages/core/src/license/TelemetryNotice.ts
11509
+ import { existsSync as existsSync4, mkdirSync as mkdirSync4, writeFileSync as writeFileSync4 } from "fs";
11510
+ import path34 from "path";
11511
+ import os6 from "os";
11461
11512
 
11462
11513
  // ../../packages/core/src/server/ProjectState.ts
11463
- import path35 from "path";
11514
+ import path36 from "path";
11464
11515
 
11465
11516
  // ../../packages/core/src/server/projectId.ts
11466
11517
  import crypto5 from "crypto";
11467
- import path34 from "path";
11518
+ import path35 from "path";
11468
11519
 
11469
11520
  // ../../packages/core/src/server/ProjectStateManager.ts
11470
11521
  init_logger();
11471
11522
 
11472
11523
  // ../../packages/core/src/server/resolveProjectRoot.ts
11473
11524
  import fs29 from "fs";
11474
- import path36 from "path";
11525
+ import path37 from "path";
11475
11526
 
11476
11527
  // server/loader.ts
11477
11528
  async function loadContext(root) {
11478
- const absRoot = path37.resolve(root);
11529
+ const absRoot = path38.resolve(root);
11479
11530
  const overlay = new GitOverlayStore(absRoot);
11480
11531
  const gitEnabled = await overlay.loadSnapshot();
11481
11532
  const graph = new DependencyGraph();
@@ -11729,20 +11780,20 @@ function buildOwnershipRouter(ctx) {
11729
11780
  // server/routes/file.ts
11730
11781
  import { Router as Router7 } from "express";
11731
11782
  import fs30 from "fs/promises";
11732
- import path38 from "path";
11783
+ import path39 from "path";
11733
11784
  function buildFileRouter(ctx) {
11734
11785
  const router = Router7();
11735
11786
  router.get("/", async (req, res) => {
11736
11787
  const rel = req.query.path;
11737
11788
  if (!rel) return res.status(400).json({ error: "missing path" });
11738
- const abs = path38.resolve(ctx.root, rel);
11739
- const rootBoundary = ctx.root.endsWith(path38.sep) ? ctx.root : ctx.root + path38.sep;
11789
+ const abs = path39.resolve(ctx.root, rel);
11790
+ const rootBoundary = ctx.root.endsWith(path39.sep) ? ctx.root : ctx.root + path39.sep;
11740
11791
  if (abs !== ctx.root && !abs.startsWith(rootBoundary)) {
11741
11792
  return res.status(403).json({ error: "forbidden" });
11742
11793
  }
11743
11794
  try {
11744
11795
  const content = await fs30.readFile(abs, "utf-8");
11745
- const ext = path38.extname(abs).slice(1);
11796
+ const ext = path39.extname(abs).slice(1);
11746
11797
  res.json({ content, lines: content.split("\n").length, ext });
11747
11798
  } catch {
11748
11799
  res.status(404).json({ error: "not found" });
@@ -11754,7 +11805,7 @@ function buildFileRouter(ctx) {
11754
11805
  // server/routes/open.ts
11755
11806
  import { Router as Router8 } from "express";
11756
11807
  import { execFile as execFile2 } from "child_process";
11757
- import path39 from "path";
11808
+ import path40 from "path";
11758
11809
  function tryOpen(bin, abs) {
11759
11810
  return new Promise((resolve) => {
11760
11811
  execFile2(bin, [abs], { timeout: 5e3 }, (err) => resolve(!err));
@@ -11765,8 +11816,8 @@ function buildOpenRouter(ctx) {
11765
11816
  router.post("/", async (req, res) => {
11766
11817
  const rel = req.body?.path;
11767
11818
  if (!rel || typeof rel !== "string") return res.status(400).json({ error: "missing path" });
11768
- const abs = path39.resolve(ctx.root, rel);
11769
- const rootBoundary = ctx.root.endsWith(path39.sep) ? ctx.root : ctx.root + path39.sep;
11819
+ const abs = path40.resolve(ctx.root, rel);
11820
+ const rootBoundary = ctx.root.endsWith(path40.sep) ? ctx.root : ctx.root + path40.sep;
11770
11821
  if (abs !== ctx.root && !abs.startsWith(rootBoundary)) {
11771
11822
  return res.status(403).json({ error: "forbidden" });
11772
11823
  }
@@ -11778,7 +11829,7 @@ function buildOpenRouter(ctx) {
11778
11829
 
11779
11830
  // server/routes/tokens.ts
11780
11831
  import { Router as Router9 } from "express";
11781
- import path40 from "path";
11832
+ import path41 from "path";
11782
11833
  import fs31 from "fs";
11783
11834
  var CHARS_PER_TOKEN = 4;
11784
11835
  var cache = null;
@@ -11794,7 +11845,7 @@ function buildTokensRouter(ctx) {
11794
11845
  let fullChars = 0;
11795
11846
  let skeletonChars = 0;
11796
11847
  for (const file of files) {
11797
- const absPath = path40.join(ctx.root, file);
11848
+ const absPath = path41.join(ctx.root, file);
11798
11849
  try {
11799
11850
  const content = fs31.readFileSync(absPath, "utf-8");
11800
11851
  fullChars += content.length;
@@ -11897,21 +11948,21 @@ function buildFileTrendsRouter(ctx) {
11897
11948
 
11898
11949
  // server/routes/projects.ts
11899
11950
  import { Router as Router12 } from "express";
11900
- import path42 from "path";
11951
+ import path43 from "path";
11901
11952
 
11902
11953
  // server/projects.ts
11903
- import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
11904
- import os6 from "os";
11905
- import path41 from "path";
11954
+ import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
11955
+ import os7 from "os";
11956
+ import path42 from "path";
11906
11957
  import crypto6 from "crypto";
11907
- var HOME = os6.homedir();
11908
- var REGISTRY_PATH = path41.join(HOME, ".ctxloom", "repos.json");
11958
+ var HOME = os7.homedir();
11959
+ var REGISTRY_PATH = path42.join(HOME, ".ctxloom", "repos.json");
11909
11960
  function slugFor(root) {
11910
- const abs = path41.resolve(root);
11961
+ const abs = path42.resolve(root);
11911
11962
  return crypto6.createHash("sha1").update(abs).digest("hex").slice(0, 12);
11912
11963
  }
11913
11964
  function readRegistry() {
11914
- if (!existsSync4(REGISTRY_PATH)) return [];
11965
+ if (!existsSync5(REGISTRY_PATH)) return [];
11915
11966
  try {
11916
11967
  const raw = readFileSync4(REGISTRY_PATH, "utf-8");
11917
11968
  const parsed = JSON.parse(raw);
@@ -11922,27 +11973,27 @@ function readRegistry() {
11922
11973
  }
11923
11974
  }
11924
11975
  function listProjects(defaultRoot) {
11925
- const absDefault = path41.resolve(defaultRoot);
11976
+ const absDefault = path42.resolve(defaultRoot);
11926
11977
  const out = [
11927
11978
  {
11928
11979
  slug: slugFor(absDefault),
11929
- name: path41.basename(absDefault) || absDefault,
11980
+ name: path42.basename(absDefault) || absDefault,
11930
11981
  root: absDefault,
11931
11982
  isDefault: true,
11932
- hasSnapshot: existsSync4(path41.join(absDefault, ".ctxloom"))
11983
+ hasSnapshot: existsSync5(path42.join(absDefault, ".ctxloom"))
11933
11984
  }
11934
11985
  ];
11935
11986
  const seen = /* @__PURE__ */ new Set([absDefault]);
11936
11987
  for (const entry of readRegistry()) {
11937
- const abs = path41.resolve(entry.root);
11988
+ const abs = path42.resolve(entry.root);
11938
11989
  if (seen.has(abs)) continue;
11939
11990
  seen.add(abs);
11940
11991
  const item = {
11941
11992
  slug: slugFor(abs),
11942
- name: entry.name ?? (path41.basename(abs) || abs),
11993
+ name: entry.name ?? (path42.basename(abs) || abs),
11943
11994
  root: abs,
11944
11995
  isDefault: false,
11945
- hasSnapshot: existsSync4(path41.join(abs, ".ctxloom"))
11996
+ hasSnapshot: existsSync5(path42.join(abs, ".ctxloom"))
11946
11997
  };
11947
11998
  if (entry.alias !== void 0) item.alias = entry.alias;
11948
11999
  out.push(item);
@@ -11995,7 +12046,7 @@ function buildProjectsRouter(deps) {
11995
12046
  } catch (err) {
11996
12047
  const detail = err instanceof Error ? err.message : String(err);
11997
12048
  res.status(500).json({
11998
- error: `failed to switch to ${path42.basename(target.root)}: ${detail}`
12049
+ error: `failed to switch to ${path43.basename(target.root)}: ${detail}`
11999
12050
  });
12000
12051
  }
12001
12052
  });
@@ -12052,7 +12103,7 @@ function buildTelemetryRouter() {
12052
12103
  }
12053
12104
 
12054
12105
  // server/index.ts
12055
- var __dirname2 = path43.dirname(fileURLToPath2(import.meta.url));
12106
+ var __dirname2 = path44.dirname(fileURLToPath2(import.meta.url));
12056
12107
  async function startDashboard(options) {
12057
12108
  const { root, port, open } = options;
12058
12109
  console.log(`ctxloom dashboard \u2014 loading context from ${root}...`);
@@ -12111,7 +12162,7 @@ async function startDashboard(options) {
12111
12162
  }
12112
12163
  activeWatcher = null;
12113
12164
  }
12114
- const snapshotDir = path43.join(targetRoot, ".ctxloom");
12165
+ const snapshotDir = path44.join(targetRoot, ".ctxloom");
12115
12166
  try {
12116
12167
  activeWatcher = fs32.watch(snapshotDir, (_event, filename) => {
12117
12168
  if (!filename || !filename.includes("snapshot")) return;
@@ -12142,12 +12193,12 @@ async function startDashboard(options) {
12142
12193
  attachSnapshotWatcher(newRoot);
12143
12194
  }
12144
12195
  }));
12145
- const clientDist = path43.join(__dirname2, "../dashboard/client");
12146
- const clientDistExists = fs32.existsSync(path43.join(clientDist, "index.html"));
12196
+ const clientDist = path44.join(__dirname2, "../dashboard/client");
12197
+ const clientDistExists = fs32.existsSync(path44.join(clientDist, "index.html"));
12147
12198
  if (clientDistExists) {
12148
12199
  app.use(express.static(clientDist, { dotfiles: "allow" }));
12149
12200
  app.get(/.*/, (_req, res) => {
12150
- res.sendFile(path43.join(clientDist, "index.html"), { dotfiles: "allow" });
12201
+ res.sendFile(path44.join(clientDist, "index.html"), { dotfiles: "allow" });
12151
12202
  });
12152
12203
  } else {
12153
12204
  app.get(/^\/(?!api\/).*/, (_req, res) => {
@@ -4,7 +4,7 @@ import {
4
4
  import {
5
5
  collectFiles,
6
6
  generateEmbedding
7
- } from "./chunk-NMXQC5CG.js";
7
+ } from "./chunk-UVR65QBJ.js";
8
8
  import {
9
9
  logger
10
10
  } from "./chunk-TYDMSHV7.js";
@@ -165,7 +165,7 @@ var GrammarLoader = class {
165
165
  const url = entry.downloadUrl?.trim() ? entry.downloadUrl : `${this.cdn}/${entry.npmPackage}@${entry.version}/${entry.wasmFile}`;
166
166
  const dest = path.join(this.cacheDir, entry.wasmFile);
167
167
  logger.info("Downloading grammar", { language, url, source: entry.downloadUrl?.trim() ? "custom" : "cdn" });
168
- fs.mkdirSync(this.cacheDir, { recursive: true });
168
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
169
169
  await this.download(url, dest);
170
170
  if (entry.sha256 && !this.skipVerify) {
171
171
  await this.verifyHash(dest, entry.sha256, language);
@@ -179,6 +179,12 @@ var GrammarLoader = class {
179
179
  return new Promise((resolve, reject) => {
180
180
  const tmp = dest + ".tmp";
181
181
  const file = fs.createWriteStream(tmp);
182
+ const onFileError = (err) => {
183
+ file.destroy();
184
+ fs.rmSync(tmp, { force: true });
185
+ reject(err);
186
+ };
187
+ file.on("error", onFileError);
182
188
  const request = https.get(url, (response) => {
183
189
  if (response.statusCode === 301 || response.statusCode === 302) {
184
190
  const location = response.headers.location;
@@ -208,11 +214,6 @@ var GrammarLoader = class {
208
214
  fs.rmSync(tmp, { force: true });
209
215
  reject(err);
210
216
  });
211
- file.on("error", (err) => {
212
- file.destroy();
213
- fs.rmSync(tmp, { force: true });
214
- reject(err);
215
- });
216
217
  file.on("finish", () => {
217
218
  fs.renameSync(tmp, dest);
218
219
  resolve();
@@ -843,15 +844,14 @@ var ASTParser = class {
843
844
  return;
844
845
  }
845
846
  case "import_declaration": {
847
+ const specs = [];
846
848
  const walkImport = (n) => {
847
849
  if (n.type === "import_spec") {
848
850
  const pathNode = n.childForFieldName?.("path");
849
851
  if (pathNode) {
850
852
  const spec = pathNode.text.replace(/^"|"$/g, "");
851
- nodes.push({
852
- type: "import",
853
+ specs.push({
853
854
  name: spec,
854
- source: spec,
855
855
  startLine: n.startPosition.row + 1,
856
856
  endLine: n.endPosition.row + 1
857
857
  });
@@ -862,6 +862,25 @@ var ASTParser = class {
862
862
  }
863
863
  };
864
864
  walkImport(node);
865
+ if (specs.length > 0) {
866
+ const firstSpec = specs[0];
867
+ nodes.push({
868
+ type: "import",
869
+ name: firstSpec.name,
870
+ source: firstSpec.name,
871
+ startLine: node.startPosition.row + 1,
872
+ endLine: node.endPosition.row + 1
873
+ });
874
+ }
875
+ for (const spec of specs) {
876
+ nodes.push({
877
+ type: "import",
878
+ name: spec.name,
879
+ source: spec.name,
880
+ startLine: spec.startLine,
881
+ endLine: spec.endLine
882
+ });
883
+ }
865
884
  return;
866
885
  }
867
886
  }
@@ -1183,6 +1202,24 @@ var ASTParser = class {
1183
1202
  const lines = source.split("\n");
1184
1203
  const walk = (node) => {
1185
1204
  switch (node.type) {
1205
+ case "call": {
1206
+ const methodNode = node.childForFieldName?.("method") ?? node.children.find((c) => c?.type === "identifier");
1207
+ const name = methodNode?.text ?? "";
1208
+ if (name === "require" || name === "require_relative" || name === "load" || name === "autoload") {
1209
+ const argsNode = node.childForFieldName?.("arguments") ?? node.children.find((c) => c?.type === "argument_list");
1210
+ const firstStringArg = argsNode?.children.find((c) => c?.type === "string" || c?.type === "simple_symbol");
1211
+ const spec = firstStringArg?.text.replace(/^['":]+|['"]+$/g, "") ?? "";
1212
+ nodes.push({
1213
+ type: "import",
1214
+ name: spec,
1215
+ source: spec,
1216
+ startLine: node.startPosition.row + 1,
1217
+ endLine: node.endPosition.row + 1
1218
+ });
1219
+ return;
1220
+ }
1221
+ break;
1222
+ }
1186
1223
  case "method":
1187
1224
  case "singleton_method": {
1188
1225
  const nameNode = node.childForFieldName?.("name") ?? node.children.find((c) => c?.type === "identifier");
@@ -1470,9 +1507,18 @@ var ASTParser = class {
1470
1507
  const walk = (node) => {
1471
1508
  switch (node.type) {
1472
1509
  case "import_or_export": {
1473
- const uriNode = node.children.find((c) => c?.type === "uri");
1510
+ const findUri = (n) => {
1511
+ if (n.type === "uri") return n;
1512
+ for (const c of n.children) {
1513
+ if (!c) continue;
1514
+ const hit = findUri(c);
1515
+ if (hit) return hit;
1516
+ }
1517
+ return void 0;
1518
+ };
1519
+ const uriNode = findUri(node);
1474
1520
  const uri = uriNode?.text?.replace(/['"]/g, "") ?? "";
1475
- if (uri.startsWith(".")) {
1521
+ if (uri) {
1476
1522
  nodes.push({
1477
1523
  type: "import",
1478
1524
  name: uri,
@@ -3411,8 +3457,8 @@ var CoChangeIndex = class _CoChangeIndex {
3411
3457
  if (event.isBulk || event.isMerge) return;
3412
3458
  const paths = event.files.map((f) => f.path);
3413
3459
  if (paths.length === 0) return;
3414
- for (const path35 of paths) {
3415
- this.nodeCounts.set(path35, (this.nodeCounts.get(path35) ?? 0) + 1);
3460
+ for (const path36 of paths) {
3461
+ this.nodeCounts.set(path36, (this.nodeCounts.get(path36) ?? 0) + 1);
3416
3462
  }
3417
3463
  for (let i = 0; i < paths.length; i++) {
3418
3464
  for (let j = i + 1; j < paths.length; j++) {
@@ -3559,8 +3605,8 @@ var ChurnIndex = class _ChurnIndex {
3559
3605
  */
3560
3606
  snapshot() {
3561
3607
  const nodes = {};
3562
- for (const [path35, raw] of this.nodes) {
3563
- nodes[path35] = {
3608
+ for (const [path36, raw] of this.nodes) {
3609
+ nodes[path36] = {
3564
3610
  commits: raw.commits,
3565
3611
  churnLines: raw.churnLines,
3566
3612
  bugCommits: raw.bugCommits,
@@ -3575,8 +3621,8 @@ var ChurnIndex = class _ChurnIndex {
3575
3621
  */
3576
3622
  static load(s) {
3577
3623
  const idx = new _ChurnIndex();
3578
- for (const [path35, raw] of Object.entries(s.nodes)) {
3579
- idx.nodes.set(path35, {
3624
+ for (const [path36, raw] of Object.entries(s.nodes)) {
3625
+ idx.nodes.set(path36, {
3580
3626
  commits: raw.commits,
3581
3627
  churnLines: raw.churnLines,
3582
3628
  bugCommits: raw.bugCommits,
@@ -3589,8 +3635,8 @@ var ChurnIndex = class _ChurnIndex {
3589
3635
  // -------------------------------------------------------------------------
3590
3636
  // Private helpers
3591
3637
  // -------------------------------------------------------------------------
3592
- getOrCreate(path35) {
3593
- const existing = this.nodes.get(path35);
3638
+ getOrCreate(path36) {
3639
+ const existing = this.nodes.get(path36);
3594
3640
  if (existing !== void 0) return existing;
3595
3641
  const fresh = {
3596
3642
  commits: 0,
@@ -3599,7 +3645,7 @@ var ChurnIndex = class _ChurnIndex {
3599
3645
  authorCounts: {},
3600
3646
  lastTouch: 0
3601
3647
  };
3602
- this.nodes.set(path35, fresh);
3648
+ this.nodes.set(path36, fresh);
3603
3649
  return fresh;
3604
3650
  }
3605
3651
  };
@@ -3680,12 +3726,12 @@ var OwnershipIndex = class _OwnershipIndex {
3680
3726
  */
3681
3727
  snapshot() {
3682
3728
  const nodes = {};
3683
- for (const [path35, raw] of this.nodes) {
3729
+ for (const [path36, raw] of this.nodes) {
3684
3730
  const authorWeights = {};
3685
3731
  for (const [email, entry] of Object.entries(raw.authorWeights)) {
3686
3732
  authorWeights[email] = { ...entry };
3687
3733
  }
3688
- nodes[path35] = { authorWeights, lastTouch: raw.lastTouch };
3734
+ nodes[path36] = { authorWeights, lastTouch: raw.lastTouch };
3689
3735
  }
3690
3736
  return { version: 1, nodes };
3691
3737
  }
@@ -3694,23 +3740,23 @@ var OwnershipIndex = class _OwnershipIndex {
3694
3740
  */
3695
3741
  static load(s) {
3696
3742
  const idx = new _OwnershipIndex();
3697
- for (const [path35, raw] of Object.entries(s.nodes)) {
3743
+ for (const [path36, raw] of Object.entries(s.nodes)) {
3698
3744
  const authorWeights = {};
3699
3745
  for (const [email, entry] of Object.entries(raw.authorWeights)) {
3700
3746
  authorWeights[email] = { ...entry };
3701
3747
  }
3702
- idx.nodes.set(path35, { authorWeights, lastTouch: raw.lastTouch });
3748
+ idx.nodes.set(path36, { authorWeights, lastTouch: raw.lastTouch });
3703
3749
  }
3704
3750
  return idx;
3705
3751
  }
3706
3752
  // -------------------------------------------------------------------------
3707
3753
  // Private helpers
3708
3754
  // -------------------------------------------------------------------------
3709
- getOrCreate(path35) {
3710
- const existing = this.nodes.get(path35);
3755
+ getOrCreate(path36) {
3756
+ const existing = this.nodes.get(path36);
3711
3757
  if (existing !== void 0) return existing;
3712
3758
  const fresh = { authorWeights: {}, lastTouch: 0 };
3713
- this.nodes.set(path35, fresh);
3759
+ this.nodes.set(path36, fresh);
3714
3760
  return fresh;
3715
3761
  }
3716
3762
  };
@@ -6804,7 +6850,7 @@ function registerFullTextSearchTool(registry, ctx) {
6804
6850
  const { query, mode, case_sensitive, limit, context_lines, project_root } = Schema23.parse(args);
6805
6851
  if (mode === "semantic") {
6806
6852
  try {
6807
- const { generateEmbedding: generateEmbedding2 } = await import("./embedder-5LMEYY4M.js");
6853
+ const { generateEmbedding: generateEmbedding2 } = await import("./embedder-R4KCXSGO.js");
6808
6854
  const store = await ctx.getStore(project_root);
6809
6855
  const embedding = await generateEmbedding2(query);
6810
6856
  const results = await store.search(embedding, limit);
@@ -6841,7 +6887,7 @@ function registerFullTextSearchTool(registry, ctx) {
6841
6887
  let merged = keywordResults.slice(0, limit);
6842
6888
  if (mode === "hybrid") {
6843
6889
  try {
6844
- const { generateEmbedding: generateEmbedding2 } = await import("./embedder-5LMEYY4M.js");
6890
+ const { generateEmbedding: generateEmbedding2 } = await import("./embedder-R4KCXSGO.js");
6845
6891
  const store = await ctx.getStore(project_root);
6846
6892
  const embedding = await generateEmbedding2(query);
6847
6893
  const vectorResults = await store.search(embedding, Math.ceil(limit / 2));
@@ -8717,113 +8763,15 @@ function maybePrintExpiryWarning(expiresAt) {
8717
8763
  }
8718
8764
 
8719
8765
  // packages/core/src/license/index.ts
8720
- import os3 from "os";
8721
- var REVALIDATION_DAYS = 7;
8722
- var GRACE_HOURS = 72;
8723
- function defaultHome() {
8724
- return os3.homedir();
8725
- }
8726
- async function isActive(opts = {}) {
8727
- const home = opts.home ?? defaultHome();
8728
- const store = new LicenseStore(home);
8729
- const license = await store.read();
8730
- if (!license) return false;
8731
- if (new Date(license.expiresAt).getTime() <= Date.now()) return false;
8732
- const lastValidated = new Date(license.lastValidatedAt).getTime();
8733
- const msSinceValidation = Date.now() - lastValidated;
8734
- const revalidationMs = REVALIDATION_DAYS * 24 * 60 * 60 * 1e3;
8735
- if (msSinceValidation <= revalidationMs) {
8736
- maybePrintExpiryWarning(license.expiresAt);
8737
- return true;
8738
- }
8739
- const client = new ApiClient(opts.apiBase);
8740
- try {
8741
- const result = await client.validate(license.key, license.instanceId);
8742
- if (result.status === "revoked" || result.status === "expired") return false;
8743
- const refreshed = {
8744
- ...license,
8745
- lastValidatedAt: (/* @__PURE__ */ new Date()).toISOString(),
8746
- expiresAt: result.expiresAt || license.expiresAt
8747
- };
8748
- await store.write(refreshed);
8749
- maybePrintExpiryWarning(refreshed.expiresAt);
8750
- return true;
8751
- } catch (err) {
8752
- if (err instanceof LicenseRevokedError) return false;
8753
- if (err instanceof NetworkError || err instanceof TypeError) {
8754
- const graceMs = GRACE_HOURS * 60 * 60 * 1e3;
8755
- if (msSinceValidation <= revalidationMs + graceMs) {
8756
- process.stderr.write(
8757
- `\u26A0 ctxloom is running offline. License will be reverified when network is available.
8758
-
8759
- `
8760
- );
8761
- maybePrintExpiryWarning(license.expiresAt);
8762
- return true;
8763
- }
8764
- return false;
8765
- }
8766
- return false;
8767
- }
8768
- }
8769
- async function requireActive(opts = {}) {
8770
- const active = await isActive(opts);
8771
- if (!active) throw new LicenseRequiredError();
8772
- }
8773
- async function getLicenseInfo(opts = {}) {
8774
- const home = opts.home ?? defaultHome();
8775
- const store = new LicenseStore(home);
8776
- return store.read();
8777
- }
8778
- async function activateLicense(key, opts = {}) {
8779
- const home = opts.home ?? defaultHome();
8780
- const fingerprint = await Fingerprint.compute();
8781
- const hostname = os3.hostname();
8782
- const platform = `${os3.platform()}-${os3.arch()}`;
8783
- const client = new ApiClient(opts.apiBase);
8784
- const result = await client.activate(key, fingerprint, hostname, platform);
8785
- const license = {
8786
- schemaVersion: 1,
8787
- key,
8788
- tier: result.tier,
8789
- status: "active",
8790
- fingerprint,
8791
- seats: result.seatsTotal,
8792
- issuedAt: (/* @__PURE__ */ new Date()).toISOString(),
8793
- expiresAt: result.expiresAt,
8794
- lastValidatedAt: (/* @__PURE__ */ new Date()).toISOString(),
8795
- licenseId: result.licenseId,
8796
- instanceId: result.instanceId
8797
- };
8798
- const store = new LicenseStore(home);
8799
- await store.write(license);
8800
- return license;
8801
- }
8802
- async function deactivateLicense(opts = {}) {
8803
- const home = opts.home ?? defaultHome();
8804
- const store = new LicenseStore(home);
8805
- const license = await store.read();
8806
- if (!license) return;
8807
- const client = new ApiClient(opts.apiBase);
8808
- await client.deactivate(license.key, license.instanceId);
8809
- await store.clear();
8810
- }
8811
- async function startTrial(email, opts = {}) {
8812
- const home = opts.home ?? defaultHome();
8813
- const fingerprint = await Fingerprint.compute();
8814
- const client = new ApiClient(opts.apiBase);
8815
- const result = await client.startTrial(email, fingerprint);
8816
- void home;
8817
- return result;
8818
- }
8766
+ import os5 from "os";
8819
8767
 
8820
8768
  // packages/core/src/license/DistinctIdStore.ts
8821
8769
  import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync as existsSync2 } from "fs";
8822
8770
  import path30 from "path";
8823
- import os4 from "os";
8771
+ import os3 from "os";
8824
8772
  var UUID_V4_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
8825
8773
  function distinctIdPath(home) {
8826
- return path30.join(home ?? os4.homedir(), ".ctxloom", "distinct_id");
8774
+ return path30.join(home ?? os3.homedir(), ".ctxloom", "distinct_id");
8827
8775
  }
8828
8776
  function isValidV4(id) {
8829
8777
  return typeof id === "string" && UUID_V4_REGEX.test(id);
@@ -8842,7 +8790,7 @@ function getOrCreateDistinctId(home) {
8842
8790
  }
8843
8791
  const record = {
8844
8792
  id: crypto.randomUUID(),
8845
- alias_pending: os4.hostname()
8793
+ alias_pending: os3.hostname()
8846
8794
  };
8847
8795
  mkdirSync2(path30.dirname(filePath), { recursive: true });
8848
8796
  writeFileSync2(filePath, JSON.stringify(record), { mode: 384 });
@@ -8873,7 +8821,7 @@ var TELEMETRY_DISABLED = TELEMETRY_LEVEL === "off";
8873
8821
  function getTelemetryLevel() {
8874
8822
  return TELEMETRY_LEVEL;
8875
8823
  }
8876
- var CTXLOOM_VERSION = "1.2.5".length > 0 ? "1.2.5" : "dev";
8824
+ var CTXLOOM_VERSION = "1.2.6".length > 0 ? "1.2.6" : "dev";
8877
8825
  var POSTHOG_HOST = "https://eu.i.posthog.com";
8878
8826
  var POSTHOG_KEY = process.env["POSTHOG_API_KEY"] ?? (true ? "phc_CiDkmFLcZ2K6uCpcoSUQLmFrnnUvsyXGhSxopX5TVKE6" : "");
8879
8827
  var SENTRY_DSN = process.env["SENTRY_DSN"] ?? (true ? "https://81c94a0f04a8e242dee493ac1e17f733@o4508531702497280.ingest.de.sentry.io/4511256875368528" : "");
@@ -9004,32 +8952,169 @@ function parseStack(stack) {
9004
8952
  }).filter((f) => f !== null).slice(0, 20);
9005
8953
  }
9006
8954
 
9007
- // packages/core/src/license/TelemetryNotice.ts
8955
+ // packages/core/src/license/FunnelMilestones.ts
9008
8956
  import { existsSync as existsSync3, mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "fs";
9009
8957
  import path31 from "path";
9010
- import os5 from "os";
8958
+ import os4 from "os";
8959
+ var INSTALL_MARKER = "installed_at";
8960
+ var FIRST_REVIEW_MARKER = "first_review_at";
8961
+ function writeMarker(filePath) {
8962
+ mkdirSync3(path31.dirname(filePath), { recursive: true });
8963
+ writeFileSync3(filePath, (/* @__PURE__ */ new Date()).toISOString(), { mode: 384 });
8964
+ }
8965
+ function shouldEmitInstallCompleted(home) {
8966
+ const root = home ?? os4.homedir();
8967
+ const marker = path31.join(root, ".ctxloom", INSTALL_MARKER);
8968
+ if (existsSync3(marker)) return false;
8969
+ try {
8970
+ writeMarker(marker);
8971
+ } catch {
8972
+ }
8973
+ return true;
8974
+ }
8975
+ function shouldEmitFirstReviewRun(projectRoot) {
8976
+ const marker = path31.join(projectRoot, ".ctxloom", FIRST_REVIEW_MARKER);
8977
+ if (existsSync3(marker)) return false;
8978
+ try {
8979
+ writeMarker(marker);
8980
+ } catch {
8981
+ }
8982
+ return true;
8983
+ }
8984
+
8985
+ // packages/core/src/license/index.ts
8986
+ var REVALIDATION_DAYS = 7;
8987
+ var GRACE_HOURS = 72;
8988
+ function defaultHome() {
8989
+ return os5.homedir();
8990
+ }
8991
+ async function isActive(opts = {}) {
8992
+ const home = opts.home ?? defaultHome();
8993
+ const store = new LicenseStore(home);
8994
+ const license = await store.read();
8995
+ if (!license) return false;
8996
+ if (new Date(license.expiresAt).getTime() <= Date.now()) return false;
8997
+ const lastValidated = new Date(license.lastValidatedAt).getTime();
8998
+ const msSinceValidation = Date.now() - lastValidated;
8999
+ const revalidationMs = REVALIDATION_DAYS * 24 * 60 * 60 * 1e3;
9000
+ if (msSinceValidation <= revalidationMs) {
9001
+ maybePrintExpiryWarning(license.expiresAt);
9002
+ return true;
9003
+ }
9004
+ const client = new ApiClient(opts.apiBase);
9005
+ try {
9006
+ const result = await client.validate(license.key, license.instanceId);
9007
+ if (result.status === "revoked" || result.status === "expired") return false;
9008
+ const refreshed = {
9009
+ ...license,
9010
+ lastValidatedAt: (/* @__PURE__ */ new Date()).toISOString(),
9011
+ expiresAt: result.expiresAt || license.expiresAt
9012
+ };
9013
+ await store.write(refreshed);
9014
+ if (result.expiresAt && result.expiresAt !== license.expiresAt) {
9015
+ track("renewal", {
9016
+ tier: license.tier,
9017
+ previousExpiresAt: license.expiresAt,
9018
+ newExpiresAt: result.expiresAt
9019
+ });
9020
+ }
9021
+ maybePrintExpiryWarning(refreshed.expiresAt);
9022
+ return true;
9023
+ } catch (err) {
9024
+ if (err instanceof LicenseRevokedError) return false;
9025
+ if (err instanceof NetworkError || err instanceof TypeError) {
9026
+ const graceMs = GRACE_HOURS * 60 * 60 * 1e3;
9027
+ if (msSinceValidation <= revalidationMs + graceMs) {
9028
+ process.stderr.write(
9029
+ `\u26A0 ctxloom is running offline. License will be reverified when network is available.
9030
+
9031
+ `
9032
+ );
9033
+ maybePrintExpiryWarning(license.expiresAt);
9034
+ return true;
9035
+ }
9036
+ return false;
9037
+ }
9038
+ return false;
9039
+ }
9040
+ }
9041
+ async function requireActive(opts = {}) {
9042
+ const active = await isActive(opts);
9043
+ if (!active) throw new LicenseRequiredError();
9044
+ }
9045
+ async function getLicenseInfo(opts = {}) {
9046
+ const home = opts.home ?? defaultHome();
9047
+ const store = new LicenseStore(home);
9048
+ return store.read();
9049
+ }
9050
+ async function activateLicense(key, opts = {}) {
9051
+ const home = opts.home ?? defaultHome();
9052
+ const fingerprint = await Fingerprint.compute();
9053
+ const hostname = os5.hostname();
9054
+ const platform = `${os5.platform()}-${os5.arch()}`;
9055
+ const client = new ApiClient(opts.apiBase);
9056
+ const result = await client.activate(key, fingerprint, hostname, platform);
9057
+ const license = {
9058
+ schemaVersion: 1,
9059
+ key,
9060
+ tier: result.tier,
9061
+ status: "active",
9062
+ fingerprint,
9063
+ seats: result.seatsTotal,
9064
+ issuedAt: (/* @__PURE__ */ new Date()).toISOString(),
9065
+ expiresAt: result.expiresAt,
9066
+ lastValidatedAt: (/* @__PURE__ */ new Date()).toISOString(),
9067
+ licenseId: result.licenseId,
9068
+ instanceId: result.instanceId
9069
+ };
9070
+ const store = new LicenseStore(home);
9071
+ await store.write(license);
9072
+ return license;
9073
+ }
9074
+ async function deactivateLicense(opts = {}) {
9075
+ const home = opts.home ?? defaultHome();
9076
+ const store = new LicenseStore(home);
9077
+ const license = await store.read();
9078
+ if (!license) return;
9079
+ const client = new ApiClient(opts.apiBase);
9080
+ await client.deactivate(license.key, license.instanceId);
9081
+ await store.clear();
9082
+ }
9083
+ async function startTrial(email, opts = {}) {
9084
+ const home = opts.home ?? defaultHome();
9085
+ const fingerprint = await Fingerprint.compute();
9086
+ const client = new ApiClient(opts.apiBase);
9087
+ const result = await client.startTrial(email, fingerprint);
9088
+ void home;
9089
+ return result;
9090
+ }
9091
+
9092
+ // packages/core/src/license/TelemetryNotice.ts
9093
+ import { existsSync as existsSync4, mkdirSync as mkdirSync4, writeFileSync as writeFileSync4 } from "fs";
9094
+ import path32 from "path";
9095
+ import os6 from "os";
9011
9096
  function noticePath(home) {
9012
- return path31.join(home ?? os5.homedir(), ".ctxloom", "telemetry_notice_shown");
9097
+ return path32.join(home ?? os6.homedir(), ".ctxloom", "telemetry_notice_shown");
9013
9098
  }
9014
9099
  function shouldShowTelemetryNotice(home) {
9015
9100
  const filePath = noticePath(home);
9016
- if (existsSync3(filePath)) return false;
9101
+ if (existsSync4(filePath)) return false;
9017
9102
  try {
9018
- mkdirSync3(path31.dirname(filePath), { recursive: true });
9019
- writeFileSync3(filePath, (/* @__PURE__ */ new Date()).toISOString(), { mode: 384 });
9103
+ mkdirSync4(path32.dirname(filePath), { recursive: true });
9104
+ writeFileSync4(filePath, (/* @__PURE__ */ new Date()).toISOString(), { mode: 384 });
9020
9105
  } catch {
9021
9106
  }
9022
9107
  return true;
9023
9108
  }
9024
9109
 
9025
9110
  // packages/core/src/server/ProjectState.ts
9026
- import path33 from "path";
9111
+ import path34 from "path";
9027
9112
 
9028
9113
  // packages/core/src/server/projectId.ts
9029
9114
  import crypto5 from "crypto";
9030
- import path32 from "path";
9115
+ import path33 from "path";
9031
9116
  function hashProjectRoot(absPath) {
9032
- const canonical = path32.resolve(absPath);
9117
+ const canonical = path33.resolve(absPath);
9033
9118
  return crypto5.createHash("sha256").update(canonical).digest("hex").slice(0, 16);
9034
9119
  }
9035
9120
 
@@ -9037,7 +9122,7 @@ function hashProjectRoot(absPath) {
9037
9122
  function createProjectState(projectRoot, opts = {}) {
9038
9123
  return {
9039
9124
  projectRoot,
9040
- dbPath: path33.join(projectRoot, ".ctxloom", "vectors.lancedb"),
9125
+ dbPath: path34.join(projectRoot, ".ctxloom", "vectors.lancedb"),
9041
9126
  pinned: opts.pinned ?? false,
9042
9127
  lastTouchedAt: Date.now(),
9043
9128
  vectorsInitialized: false,
@@ -9190,7 +9275,7 @@ var ProjectStateManager = class {
9190
9275
 
9191
9276
  // packages/core/src/server/resolveProjectRoot.ts
9192
9277
  import fs27 from "fs";
9193
- import path34 from "path";
9278
+ import path35 from "path";
9194
9279
  var PATH_SEPARATOR_PATTERN = /[/\\~]|^[A-Za-z]:/;
9195
9280
  function looksLikePath(value) {
9196
9281
  return PATH_SEPARATOR_PATTERN.test(value);
@@ -9215,9 +9300,9 @@ function resolvePathSafely(p, cwd) {
9215
9300
  let expanded = p;
9216
9301
  if (p === "~" || p.startsWith("~/")) {
9217
9302
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
9218
- expanded = p === "~" ? home : path34.join(home, p.slice(2));
9303
+ expanded = p === "~" ? home : path35.join(home, p.slice(2));
9219
9304
  }
9220
- return path34.isAbsolute(expanded) ? path34.resolve(expanded) : path34.resolve(cwd, expanded);
9305
+ return path35.isAbsolute(expanded) ? path35.resolve(expanded) : path35.resolve(cwd, expanded);
9221
9306
  }
9222
9307
  function realpathOrSame(p) {
9223
9308
  try {
@@ -9289,7 +9374,7 @@ function validateDefaultRoot(candidate) {
9289
9374
  } catch {
9290
9375
  return false;
9291
9376
  }
9292
- return PROJECT_MARKERS.some((m) => fs27.existsSync(path34.join(candidate, m)));
9377
+ return PROJECT_MARKERS.some((m) => fs27.existsSync(path35.join(candidate, m)));
9293
9378
  }
9294
9379
 
9295
9380
  // packages/core/src/server/structuredErrors.ts
@@ -9436,17 +9521,19 @@ export {
9436
9521
  ApiClient,
9437
9522
  Fingerprint,
9438
9523
  maybePrintExpiryWarning,
9524
+ getOrCreateDistinctId,
9525
+ markAliasSent,
9526
+ getTelemetryLevel,
9527
+ track,
9528
+ captureError,
9529
+ shouldEmitInstallCompleted,
9530
+ shouldEmitFirstReviewRun,
9439
9531
  isActive,
9440
9532
  requireActive,
9441
9533
  getLicenseInfo,
9442
9534
  activateLicense,
9443
9535
  deactivateLicense,
9444
9536
  startTrial,
9445
- getOrCreateDistinctId,
9446
- markAliasSent,
9447
- getTelemetryLevel,
9448
- track,
9449
- captureError,
9450
9537
  shouldShowTelemetryNotice,
9451
9538
  hashProjectRoot,
9452
9539
  createProjectState,
@@ -9464,4 +9551,4 @@ export {
9464
9551
  FirstTouchTracker,
9465
9552
  EmittedOnceTracker
9466
9553
  };
9467
- //# sourceMappingURL=chunk-6S3ZF2YS.js.map
9554
+ //# sourceMappingURL=chunk-PVKQ5UQS.js.map
@@ -211,4 +211,4 @@ export {
211
211
  collectFiles,
212
212
  indexDirectory
213
213
  };
214
- //# sourceMappingURL=chunk-NMXQC5CG.js.map
214
+ //# sourceMappingURL=chunk-UVR65QBJ.js.map
@@ -3,7 +3,7 @@ import {
3
3
  collectFiles,
4
4
  generateEmbedding,
5
5
  indexDirectory
6
- } from "./chunk-NMXQC5CG.js";
6
+ } from "./chunk-UVR65QBJ.js";
7
7
  import "./chunk-TYDMSHV7.js";
8
8
  export {
9
9
  EMBEDDING_DIMENSION,
@@ -11,4 +11,4 @@ export {
11
11
  generateEmbedding,
12
12
  indexDirectory
13
13
  };
14
- //# sourceMappingURL=embedder-5LMEYY4M.js.map
14
+ //# sourceMappingURL=embedder-R4KCXSGO.js.map
package/dist/index.js CHANGED
@@ -41,20 +41,22 @@ import {
41
41
  resolveProjectRoot,
42
42
  resolveViaGitHubApi,
43
43
  scoreReviewers,
44
+ shouldEmitFirstReviewRun,
45
+ shouldEmitInstallCompleted,
44
46
  shouldShowTelemetryNotice,
45
47
  startTrial,
46
48
  track,
47
49
  validateDefaultRoot,
48
50
  wrapWithIndexingEnvelope,
49
51
  writeCODEOWNERS
50
- } from "./chunk-6S3ZF2YS.js";
52
+ } from "./chunk-PVKQ5UQS.js";
51
53
  import {
52
54
  VectorStore
53
55
  } from "./chunk-DVI2RWJR.js";
54
56
  import {
55
57
  generateEmbedding,
56
58
  indexDirectory
57
- } from "./chunk-NMXQC5CG.js";
59
+ } from "./chunk-UVR65QBJ.js";
58
60
  import {
59
61
  logger
60
62
  } from "./chunk-TYDMSHV7.js";
@@ -1016,7 +1018,7 @@ try {
1016
1018
  } catch {
1017
1019
  }
1018
1020
  var args = process.argv.slice(2);
1019
- var ctxloomVersion = "1.2.5".length > 0 ? "1.2.5" : "dev";
1021
+ var ctxloomVersion = "1.2.6".length > 0 ? "1.2.6" : "dev";
1020
1022
  if (args.includes("--version") || args.includes("-v")) {
1021
1023
  process.stdout.write(`ctxloom ${ctxloomVersion}
1022
1024
  `);
@@ -1089,7 +1091,7 @@ async function checkLicense() {
1089
1091
  if (command !== void 0 && LICENSE_GATE_BYPASS_COMMANDS.has(command)) return;
1090
1092
  const ciKey = process.env["CTXLOOM_LICENSE_KEY"];
1091
1093
  if (ciKey) {
1092
- const { ApiClient } = await import("./src-JZWAESJU.js");
1094
+ const { ApiClient } = await import("./src-GQPCKS5B.js");
1093
1095
  const client = new ApiClient(process.env["CTXLOOM_API_BASE"]);
1094
1096
  try {
1095
1097
  const result = await client.validate(ciKey, "ci-ephemeral");
@@ -1313,6 +1315,9 @@ ${style.dim("\u2500".repeat(60))}
1313
1315
  }
1314
1316
  async function main() {
1315
1317
  maybePrintTelemetryNotice();
1318
+ if (command !== void 0 && shouldEmitInstallCompleted()) {
1319
+ track("install_completed", { command });
1320
+ }
1316
1321
  await checkLicense();
1317
1322
  switch (command) {
1318
1323
  case "trial": {
@@ -1491,7 +1496,7 @@ async function main() {
1491
1496
  process.exit(1);
1492
1497
  }
1493
1498
  if (alias !== void 0) {
1494
- const { validateAlias } = await import("./src-JZWAESJU.js");
1499
+ const { validateAlias } = await import("./src-GQPCKS5B.js");
1495
1500
  const v = validateAlias(alias);
1496
1501
  if (!v.ok) {
1497
1502
  console.error(`[ctxloom] Invalid alias: ${v.reason}`);
@@ -1597,6 +1602,9 @@ async function main() {
1597
1602
  console.error("[ctxloom] No files specified and no staged changes found.");
1598
1603
  process.exit(1);
1599
1604
  }
1605
+ if (shouldEmitFirstReviewRun(root)) {
1606
+ track("first_review_run", { source: "cli", fileCount: files.length });
1607
+ }
1600
1608
  const config = await loadReviewConfig(root);
1601
1609
  if (excludeFlags.length > 0) {
1602
1610
  config.exclude = [...config.exclude, ...excludeFlags];
@@ -1735,7 +1743,7 @@ Suggested reviewers for ${files.length} file(s):`);
1735
1743
  process.stderr.write("[ctxloom] --limit must be a non-negative integer (0 for unlimited)\n");
1736
1744
  process.exit(2);
1737
1745
  }
1738
- const { loadRulesConfig, RulesChecker, formatText, formatJson, RulesConfigError } = await import("./src-JZWAESJU.js");
1746
+ const { loadRulesConfig, RulesChecker, formatText, formatJson, RulesConfigError } = await import("./src-GQPCKS5B.js");
1739
1747
  let config;
1740
1748
  try {
1741
1749
  config = await loadRulesConfig(root);
@@ -1759,7 +1767,7 @@ Suggested reviewers for ${files.length} file(s):`);
1759
1767
  }
1760
1768
  let graph;
1761
1769
  if (useSnapshot) {
1762
- const { DependencyGraph: DG } = await import("./src-JZWAESJU.js");
1770
+ const { DependencyGraph: DG } = await import("./src-GQPCKS5B.js");
1763
1771
  graph = new DG();
1764
1772
  const loaded = await graph.loadSnapshotOnly(root);
1765
1773
  if (!loaded) {
@@ -1768,7 +1776,7 @@ Suggested reviewers for ${files.length} file(s):`);
1768
1776
  }
1769
1777
  } else {
1770
1778
  process.stderr.write("[ctxloom] Building dependency graph...\n");
1771
- const { ASTParser: ASTParser2, DependencyGraph: DependencyGraph2 } = await import("./src-JZWAESJU.js");
1779
+ const { ASTParser: ASTParser2, DependencyGraph: DependencyGraph2 } = await import("./src-GQPCKS5B.js");
1772
1780
  let parser;
1773
1781
  try {
1774
1782
  parser = new ASTParser2();
@@ -94,6 +94,8 @@ import {
94
94
  scoreAll,
95
95
  scoreFromBreakdown,
96
96
  scoreReviewers,
97
+ shouldEmitFirstReviewRun,
98
+ shouldEmitInstallCompleted,
97
99
  shouldShowTelemetryNotice,
98
100
  startTrial,
99
101
  track,
@@ -101,7 +103,7 @@ import {
101
103
  validateDefaultRoot,
102
104
  wrapWithIndexingEnvelope,
103
105
  writeCODEOWNERS
104
- } from "./chunk-6S3ZF2YS.js";
106
+ } from "./chunk-PVKQ5UQS.js";
105
107
  import {
106
108
  VectorStore
107
109
  } from "./chunk-DVI2RWJR.js";
@@ -110,7 +112,7 @@ import {
110
112
  collectFiles,
111
113
  generateEmbedding,
112
114
  indexDirectory
113
- } from "./chunk-NMXQC5CG.js";
115
+ } from "./chunk-UVR65QBJ.js";
114
116
  import {
115
117
  logger
116
118
  } from "./chunk-TYDMSHV7.js";
@@ -216,6 +218,8 @@ export {
216
218
  scoreAll,
217
219
  scoreFromBreakdown,
218
220
  scoreReviewers,
221
+ shouldEmitFirstReviewRun,
222
+ shouldEmitInstallCompleted,
219
223
  shouldShowTelemetryNotice,
220
224
  startTrial,
221
225
  track,
@@ -224,4 +228,4 @@ export {
224
228
  wrapWithIndexingEnvelope,
225
229
  writeCODEOWNERS
226
230
  };
227
- //# sourceMappingURL=src-JZWAESJU.js.map
231
+ //# sourceMappingURL=src-GQPCKS5B.js.map
@@ -3,7 +3,7 @@ import {
3
3
  } from "../chunk-DVI2RWJR.js";
4
4
  import {
5
5
  generateEmbedding
6
- } from "../chunk-NMXQC5CG.js";
6
+ } from "../chunk-UVR65QBJ.js";
7
7
  import "../chunk-TYDMSHV7.js";
8
8
 
9
9
  // packages/core/src/workers/indexerWorker.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ctxloom-pro",
3
- "version": "1.2.5",
3
+ "version": "1.2.6",
4
4
  "description": "ctxloom — The Universal Code Context Engine. A local-first MCP server providing intelligent code context via hybrid Vector + AST + Graph search with Skeletonization (92% token reduction).",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",