xray-code 0.1.4 → 0.2.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.
package/dist/cli.js CHANGED
@@ -312,4 +312,183 @@ queryOpts(program
312
312
  process.exit(1);
313
313
  }
314
314
  });
315
+ // ─── Call graph commands ────────────────────────────────────────
316
+ queryOpts(program
317
+ .command("callers <symbol>")
318
+ .description("Who calls this symbol?")
319
+ .option("-n, --limit <n>", "Max results", "20")
320
+ .option("--json", "Output as JSON"))
321
+ .action(async (symbol, opts) => {
322
+ try {
323
+ const client = getClient(opts);
324
+ const res = await client.callers(symbol, parseInt(opts.limit));
325
+ if (opts.json) {
326
+ console.log(JSON.stringify(res, null, 2));
327
+ return;
328
+ }
329
+ if (res.length === 0) {
330
+ console.log(`No callers found for '${symbol}'`);
331
+ return;
332
+ }
333
+ console.log(`\n ${res.length} callers of '${symbol}':\n`);
334
+ res.forEach((c) => {
335
+ console.log(` ${c.caller_file}:${c.call_site_line} ${c.caller_name} [${c.confidence}]`);
336
+ });
337
+ console.log("");
338
+ }
339
+ catch (e) {
340
+ console.error(`Query failed: ${e}`);
341
+ process.exit(1);
342
+ }
343
+ });
344
+ queryOpts(program
345
+ .command("callees <symbol>")
346
+ .description("What does this symbol call?")
347
+ .option("-n, --limit <n>", "Max results", "20")
348
+ .option("--json", "Output as JSON"))
349
+ .action(async (symbol, opts) => {
350
+ try {
351
+ const client = getClient(opts);
352
+ const res = await client.callees(symbol, parseInt(opts.limit));
353
+ if (opts.json) {
354
+ console.log(JSON.stringify(res, null, 2));
355
+ return;
356
+ }
357
+ if (res.length === 0) {
358
+ console.log(`'${symbol}' doesn't call anything`);
359
+ return;
360
+ }
361
+ console.log(`\n ${res.length} callees of '${symbol}':\n`);
362
+ res.forEach((c) => {
363
+ console.log(` ${c.callee_file} ${c.callee_name} (line ${c.call_site_line})`);
364
+ });
365
+ console.log("");
366
+ }
367
+ catch (e) {
368
+ console.error(`Query failed: ${e}`);
369
+ process.exit(1);
370
+ }
371
+ });
372
+ // ─── Analysis commands ─────────────────────────────────────────
373
+ queryOpts(program
374
+ .command("blast-radius <symbol>")
375
+ .description("Transitive impact — what breaks if I change this?")
376
+ .option("-d, --depth <n>", "Traversal depth", "2")
377
+ .option("--json", "Output as JSON"))
378
+ .action(async (symbol, opts) => {
379
+ try {
380
+ const client = getClient(opts);
381
+ const res = await client.transitiveImpact(symbol, parseInt(opts.depth));
382
+ if (opts.json) {
383
+ console.log(JSON.stringify(res, null, 2));
384
+ return;
385
+ }
386
+ console.log(JSON.stringify(res, null, 2));
387
+ }
388
+ catch (e) {
389
+ console.error(`Blast radius failed: ${e}`);
390
+ process.exit(1);
391
+ }
392
+ });
393
+ queryOpts(program
394
+ .command("plan-change <symbol>")
395
+ .description("Safe phased refactoring plan with risk assessment")
396
+ .option("-d, --depth <n>", "Traversal depth", "2")
397
+ .option("--json", "Output as JSON"))
398
+ .action(async (symbol, opts) => {
399
+ try {
400
+ const client = getClient(opts);
401
+ const res = await client.planChange(symbol, parseInt(opts.depth));
402
+ if (opts.json) {
403
+ console.log(JSON.stringify(res, null, 2));
404
+ return;
405
+ }
406
+ console.log(`\n Change Plan for '${symbol}'`);
407
+ console.log(` Risk: ${res.risk_level}\n`);
408
+ res.phases.forEach((p) => {
409
+ console.log(` Phase ${p.phase}: ${p.description}`);
410
+ p.files.forEach((f) => console.log(` - ${f}`));
411
+ });
412
+ console.log("");
413
+ }
414
+ catch (e) {
415
+ console.error(`Plan failed: ${e}`);
416
+ process.exit(1);
417
+ }
418
+ });
419
+ queryOpts(program
420
+ .command("audit")
421
+ .description("Project health — dead code, hotspots, coupling")
422
+ .option("--json", "Output as JSON"))
423
+ .action(async (opts) => {
424
+ try {
425
+ const client = getClient(opts);
426
+ const res = await client.audit();
427
+ if (opts.json) {
428
+ console.log(JSON.stringify(res, null, 2));
429
+ return;
430
+ }
431
+ console.log(`\n Project Health Audit`);
432
+ console.log(` ──────────────────────`);
433
+ if (res.overall_score != null)
434
+ console.log(` Score: ${res.overall_score}`);
435
+ console.log(` Dead code: ${res.dead_symbols.length} unused symbols`);
436
+ console.log(` Hotspots: ${res.hotspots.length}`);
437
+ console.log(` Coupling: ${res.coupling_warnings.length} warnings`);
438
+ console.log(`\n Use --json for full details.\n`);
439
+ }
440
+ catch (e) {
441
+ console.error(`Audit failed: ${e}`);
442
+ process.exit(1);
443
+ }
444
+ });
445
+ queryOpts(program
446
+ .command("manifest")
447
+ .description("What capabilities does this project offer?")
448
+ .option("--json", "Output as JSON"))
449
+ .action(async (opts) => {
450
+ try {
451
+ const client = getClient(opts);
452
+ const res = await client.manifest();
453
+ console.log(JSON.stringify(res, null, 2));
454
+ }
455
+ catch (e) {
456
+ console.error(`Manifest failed: ${e}`);
457
+ process.exit(1);
458
+ }
459
+ });
460
+ queryOpts(program
461
+ .command("context <target>")
462
+ .description("Get AI-ready markdown context for a symbol or file")
463
+ .option("--file", "Treat target as a file path instead of symbol name"))
464
+ .action(async (target, opts) => {
465
+ try {
466
+ const client = getClient(opts);
467
+ const res = opts.file
468
+ ? await client.context({ file: target })
469
+ : await client.context({ symbol: target });
470
+ console.log(res.markdown);
471
+ }
472
+ catch (e) {
473
+ console.error(`Context failed: ${e}`);
474
+ process.exit(1);
475
+ }
476
+ });
477
+ queryOpts(program
478
+ .command("prospect")
479
+ .description("Diamond prospecting — rank files by mining fertility")
480
+ .option("-n, --top <n>", "Top N results", "20")
481
+ .option("--prefix <prefix>", "Filter by path prefix")
482
+ .option("--json", "Output as JSON"))
483
+ .action(async (opts) => {
484
+ try {
485
+ const client = getClient(opts);
486
+ const res = await client.prospect(parseInt(opts.top), opts.prefix);
487
+ console.log(JSON.stringify(res, null, 2));
488
+ }
489
+ catch (e) {
490
+ console.error(`Prospect failed: ${e}`);
491
+ process.exit(1);
492
+ }
493
+ });
315
494
  program.parse();
package/dist/client.d.ts CHANGED
@@ -33,4 +33,17 @@ export declare class XRayClient {
33
33
  count: number;
34
34
  symbols: unknown[];
35
35
  }>;
36
+ callers(symbol: string, limit?: number): Promise<unknown[]>;
37
+ callees(symbol: string, limit?: number): Promise<unknown[]>;
38
+ transitiveImpact(symbol: string, depth?: number): Promise<unknown[]>;
39
+ planChange(symbol: string, depth?: number): Promise<unknown>;
40
+ audit(): Promise<unknown>;
41
+ manifest(): Promise<unknown>;
42
+ context(opts: {
43
+ symbol?: string;
44
+ file?: string;
45
+ }): Promise<{
46
+ markdown: string;
47
+ }>;
48
+ prospect(top?: number, prefix?: string): Promise<unknown>;
36
49
  }
package/dist/client.js CHANGED
@@ -66,5 +66,37 @@ class XRayClient {
66
66
  async symbolsIn(file) {
67
67
  return this.request("/symbols-in", { file });
68
68
  }
69
+ async callers(symbol, limit = 20) {
70
+ return this.request("/callers", { symbol, limit: String(limit) });
71
+ }
72
+ async callees(symbol, limit = 20) {
73
+ return this.request("/callees", { symbol, limit: String(limit) });
74
+ }
75
+ async transitiveImpact(symbol, depth = 2) {
76
+ return this.request("/transitive-impact", { symbol, depth: String(depth) });
77
+ }
78
+ async planChange(symbol, depth = 2) {
79
+ return this.request("/plan-change", { symbol, depth: String(depth) });
80
+ }
81
+ async audit() {
82
+ return this.request("/audit");
83
+ }
84
+ async manifest() {
85
+ return this.request("/manifest");
86
+ }
87
+ async context(opts) {
88
+ const params = {};
89
+ if (opts.symbol)
90
+ params.symbol = opts.symbol;
91
+ if (opts.file)
92
+ params.file = opts.file;
93
+ return this.request("/context", params);
94
+ }
95
+ async prospect(top = 20, prefix) {
96
+ const params = { top: String(top) };
97
+ if (prefix)
98
+ params.prefix = prefix;
99
+ return this.request("/prospect", params);
100
+ }
69
101
  }
70
102
  exports.XRayClient = XRayClient;
package/dist/onboard.js CHANGED
@@ -164,7 +164,20 @@ async function onboard(repoFilter) {
164
164
  await runCommand(foxrefBin, ["snapshot", "--repo", ".", "--out", ".fox/foxref/snapshot.json"], cloneDir);
165
165
  snapSpin.stop("done");
166
166
  // ─── Step 6: Save project config ──────────────────────────────
167
- const apiKey = (0, node_child_process_1.execSync)("openssl rand -hex 32").toString().trim();
167
+ // Preserve API key from prior `xray activate` — don't generate a new one
168
+ const existingConfig = (() => {
169
+ const p = (0, node_path_1.join)((0, node_os_1.homedir)(), ".xray", "config.json");
170
+ if ((0, node_fs_1.existsSync)(p)) {
171
+ try {
172
+ return JSON.parse((0, node_fs_1.readFileSync)(p, "utf-8"));
173
+ }
174
+ catch {
175
+ return {};
176
+ }
177
+ }
178
+ return {};
179
+ })();
180
+ const apiKey = existingConfig.apiKey || (0, node_child_process_1.execSync)("openssl rand -hex 32").toString().trim();
168
181
  const webhookSecret = (0, node_child_process_1.execSync)("openssl rand -hex 32").toString().trim();
169
182
  const configDir = (0, node_path_1.join)(cloneDir, ".xray");
170
183
  if (!(0, node_fs_1.existsSync)(configDir))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xray-code",
3
- "version": "0.1.4",
3
+ "version": "0.2.0",
4
4
  "description": "XRay — code intelligence CLI. X-ray your codebase with instant cross-references.",
5
5
  "bin": {
6
6
  "xray": "dist/cli.js"