@neurynae/toolcairn-mcp 0.10.0 → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -336,8 +336,8 @@ var require_logger = __commonJS({
336
336
  return mod && mod.__esModule ? mod : { "default": mod };
337
337
  };
338
338
  Object.defineProperty(exports, "__esModule", { value: true });
339
- exports.createMcpLogger = createMcpLogger14;
340
- exports.createLogger = createMcpLogger14;
339
+ exports.createMcpLogger = createMcpLogger24;
340
+ exports.createLogger = createMcpLogger24;
341
341
  var node_os_1 = __require("os");
342
342
  var node_path_1 = __require("path");
343
343
  var pino_1 = __importDefault(__require("pino"));
@@ -361,7 +361,7 @@ var require_logger = __commonJS({
361
361
  "*.apiKey",
362
362
  "*.api_key"
363
363
  ];
364
- function createMcpLogger14(opts) {
364
+ function createMcpLogger24(opts) {
365
365
  const level = opts.level ?? process.env.LOG_LEVEL ?? (process.env.NODE_ENV !== "production" ? "debug" : "info");
366
366
  const pinoOpts = {
367
367
  name: opts.name,
@@ -407,14 +407,14 @@ var require_mcp_error_wrapper = __commonJS({
407
407
  exports.withErrorHandling = withErrorHandling2;
408
408
  var error_codes_js_1 = require_error_codes();
409
409
  var errors_js_1 = require_errors();
410
- function withErrorHandling2(toolName, logger14, handler) {
410
+ function withErrorHandling2(toolName, logger24, handler) {
411
411
  return async (args) => {
412
412
  try {
413
413
  return await handler(args);
414
414
  } catch (err) {
415
415
  if (err instanceof errors_js_1.AppError) {
416
416
  const logLevel = err.severity === "critical" || err.severity === "high" ? "error" : "warn";
417
- logger14[logLevel]({ err, tool: toolName }, `Tool ${toolName} failed: ${err.message}`);
417
+ logger24[logLevel]({ err, tool: toolName }, `Tool ${toolName} failed: ${err.message}`);
418
418
  return {
419
419
  content: [
420
420
  {
@@ -429,7 +429,7 @@ var require_mcp_error_wrapper = __commonJS({
429
429
  isError: true
430
430
  };
431
431
  }
432
- logger14.error({ err, tool: toolName }, `Unexpected error in tool ${toolName}`);
432
+ logger24.error({ err, tool: toolName }, `Unexpected error in tool ${toolName}`);
433
433
  return {
434
434
  content: [
435
435
  {
@@ -512,7 +512,7 @@ var require_dist2 = __commonJS({
512
512
  // src/index.prod.ts
513
513
  init_esm_shims();
514
514
  var import_config4 = __toESM(require_dist(), 1);
515
- var import_errors15 = __toESM(require_dist2(), 1);
515
+ var import_errors25 = __toESM(require_dist2(), 1);
516
516
  import { McpServer as McpServer2 } from "@modelcontextprotocol/sdk/server/mcp.js";
517
517
 
518
518
  // ../../packages/remote/dist/index.js
@@ -814,13 +814,13 @@ var import_errors2 = __toESM(require_dist2(), 1);
814
814
  async function openBrowser(url) {
815
815
  const { spawn } = await import("child_process");
816
816
  try {
817
- const platform2 = process.platform;
817
+ const platform3 = process.platform;
818
818
  let cmd;
819
819
  let args;
820
- if (platform2 === "win32") {
820
+ if (platform3 === "win32") {
821
821
  cmd = "cmd";
822
822
  args = ["/c", "start", "", url];
823
- } else if (platform2 === "darwin") {
823
+ } else if (platform3 === "darwin") {
824
824
  cmd = "open";
825
825
  args = [url];
826
826
  } else {
@@ -1393,7 +1393,7 @@ async function createIfAbsent(filePath, content, label) {
1393
1393
  // src/server.prod.ts
1394
1394
  init_esm_shims();
1395
1395
  var import_config2 = __toESM(require_dist(), 1);
1396
- var import_errors14 = __toESM(require_dist2(), 1);
1396
+ var import_errors24 = __toESM(require_dist2(), 1);
1397
1397
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
1398
1398
 
1399
1399
  // ../../packages/tools-local/dist/index.js
@@ -1602,7 +1602,7 @@ Respond with ONLY 0 or 1.`;
1602
1602
 
1603
1603
  // ../../packages/tools-local/dist/handlers/toolcairn-init.js
1604
1604
  init_esm_shims();
1605
- var import_errors10 = __toESM(require_dist2(), 1);
1605
+ var import_errors20 = __toESM(require_dist2(), 1);
1606
1606
 
1607
1607
  // ../../packages/tools-local/dist/config-store/index.js
1608
1608
  init_esm_shims();
@@ -1915,8 +1915,8 @@ init_esm_shims();
1915
1915
 
1916
1916
  // ../../packages/tools-local/dist/discovery/scan-project.js
1917
1917
  init_esm_shims();
1918
- var import_errors9 = __toESM(require_dist2(), 1);
1919
- import { readFile as readFile17 } from "fs/promises";
1918
+ var import_errors19 = __toESM(require_dist2(), 1);
1919
+ import { readFile as readFile27 } from "fs/promises";
1920
1920
  import { basename, resolve } from "path";
1921
1921
 
1922
1922
  // ../../packages/tools-local/dist/discovery/ecosystem-detect.js
@@ -3415,10 +3415,754 @@ var PARSERS = {
3415
3415
  "swift-pm": parseSwift
3416
3416
  };
3417
3417
 
3418
+ // ../../packages/tools-local/dist/discovery/resolvers/index.js
3419
+ init_esm_shims();
3420
+
3421
+ // ../../packages/tools-local/dist/discovery/resolvers/cargo.js
3422
+ init_esm_shims();
3423
+ var import_errors9 = __toESM(require_dist2(), 1);
3424
+ import { readFile as readFile16, readdir as readdir5 } from "fs/promises";
3425
+ import { homedir as homedir2 } from "os";
3426
+ import { join as join19 } from "path";
3427
+ import { parse as parseToml3 } from "smol-toml";
3428
+
3429
+ // ../../packages/tools-local/dist/discovery/resolvers/url-normalise.js
3430
+ init_esm_shims();
3431
+ function normaliseGitHubUrl(raw) {
3432
+ if (!raw)
3433
+ return void 0;
3434
+ let s = raw.trim();
3435
+ if (!s)
3436
+ return void 0;
3437
+ if (s.startsWith("git+"))
3438
+ s = s.slice(4);
3439
+ if (s.startsWith("github:")) {
3440
+ s = `https://github.com/${s.slice(7)}`;
3441
+ }
3442
+ if (/^[A-Za-z0-9_.\-]+\/[A-Za-z0-9_.\-]+$/.test(s)) {
3443
+ s = `https://github.com/${s}`;
3444
+ }
3445
+ s = s.replace(/^git@github\.com:/, "https://github.com/");
3446
+ s = s.replace(/^ssh:\/\/git@github\.com\//, "https://github.com/");
3447
+ s = s.replace(/^http:\/\//, "https://");
3448
+ if (!/^https:\/\/github\.com\//.test(s))
3449
+ return void 0;
3450
+ s = s.replace(/\.git$/, "");
3451
+ s = s.replace(/\/$/, "");
3452
+ const match = s.match(/^https:\/\/github\.com\/([^/]+)\/([^/]+)/);
3453
+ if (!match)
3454
+ return void 0;
3455
+ return `https://github.com/${match[1]}/${match[2]}`;
3456
+ }
3457
+
3458
+ // ../../packages/tools-local/dist/discovery/resolvers/cargo.js
3459
+ var logger7 = (0, import_errors9.createMcpLogger)({ name: "@toolcairn/tools:resolver:cargo" });
3460
+ async function findCachedCrate(name, preferredVersion) {
3461
+ const registryRoot = join19(homedir2(), ".cargo", "registry", "src");
3462
+ if (!await isDir(registryRoot))
3463
+ return null;
3464
+ let indexHosts;
3465
+ try {
3466
+ indexHosts = await readdir5(registryRoot);
3467
+ } catch {
3468
+ return null;
3469
+ }
3470
+ const matches = [];
3471
+ for (const host of indexHosts) {
3472
+ const hostDir = join19(registryRoot, host);
3473
+ if (!await isDir(hostDir))
3474
+ continue;
3475
+ let entries;
3476
+ try {
3477
+ entries = await readdir5(hostDir);
3478
+ } catch {
3479
+ continue;
3480
+ }
3481
+ for (const entry of entries) {
3482
+ if (!entry.startsWith(`${name}-`))
3483
+ continue;
3484
+ if (preferredVersion && entry !== `${name}-${preferredVersion}`)
3485
+ continue;
3486
+ const manifestPath = join19(hostDir, entry, "Cargo.toml");
3487
+ if (await fileExists(manifestPath))
3488
+ matches.push(manifestPath);
3489
+ }
3490
+ }
3491
+ if (matches.length === 0)
3492
+ return null;
3493
+ matches.sort();
3494
+ return matches[matches.length - 1] ?? null;
3495
+ }
3496
+ async function resolveCargoIdentity(_workspaceAbs, _projectRoot, depName, hints = {}) {
3497
+ const manifestPath = await findCachedCrate(depName, hints.resolved_version);
3498
+ if (!manifestPath)
3499
+ return {};
3500
+ try {
3501
+ const raw = await readFile16(manifestPath, "utf-8");
3502
+ const doc = parseToml3(raw);
3503
+ const pkg = doc.package;
3504
+ if (!pkg)
3505
+ return {};
3506
+ const out = {};
3507
+ if (pkg.name && pkg.name !== depName)
3508
+ out.canonical_package_name = pkg.name;
3509
+ if (pkg.version)
3510
+ out.resolved_version = pkg.version;
3511
+ const normalised = normaliseGitHubUrl(pkg.repository ?? pkg.homepage);
3512
+ if (normalised)
3513
+ out.github_url = normalised;
3514
+ return out;
3515
+ } catch (err) {
3516
+ logger7.debug({ err: err instanceof Error ? err.message : String(err), manifestPath }, "Failed to parse cached Cargo.toml");
3517
+ return {};
3518
+ }
3519
+ }
3520
+
3521
+ // ../../packages/tools-local/dist/discovery/resolvers/composer.js
3522
+ init_esm_shims();
3523
+ var import_errors10 = __toESM(require_dist2(), 1);
3524
+ import { readFile as readFile17 } from "fs/promises";
3525
+ import { join as join20 } from "path";
3526
+ var logger8 = (0, import_errors10.createMcpLogger)({ name: "@toolcairn/tools:resolver:composer" });
3527
+ async function resolveComposerIdentity(workspaceAbs, _projectRoot, depName) {
3528
+ const path2 = join20(workspaceAbs, "vendor", depName, "composer.json");
3529
+ if (!await fileExists(path2))
3530
+ return {};
3531
+ try {
3532
+ const pkg = JSON.parse(await readFile17(path2, "utf-8"));
3533
+ const out = {};
3534
+ if (pkg.name && pkg.name !== depName)
3535
+ out.canonical_package_name = pkg.name;
3536
+ if (pkg.version)
3537
+ out.resolved_version = pkg.version;
3538
+ const candidateUrl = pkg.source?.url ?? pkg.support?.source ?? pkg.homepage;
3539
+ const normalised = normaliseGitHubUrl(candidateUrl);
3540
+ if (normalised)
3541
+ out.github_url = normalised;
3542
+ return out;
3543
+ } catch (err) {
3544
+ logger8.debug({ err: err instanceof Error ? err.message : String(err), path: path2 }, "Failed to parse installed composer.json");
3545
+ return {};
3546
+ }
3547
+ }
3548
+
3549
+ // ../../packages/tools-local/dist/discovery/resolvers/go.js
3550
+ init_esm_shims();
3551
+ function resolveGoIdentity(_workspaceAbs, _projectRoot, depName) {
3552
+ if (!depName.startsWith("github.com/"))
3553
+ return {};
3554
+ const tail = depName.slice("github.com/".length);
3555
+ const parts = tail.split("/");
3556
+ if (parts.length < 2 || !parts[0] || !parts[1])
3557
+ return {};
3558
+ const owner = parts[0];
3559
+ let repo = parts[1];
3560
+ repo = repo.replace(/\.git$/, "");
3561
+ const url = normaliseGitHubUrl(`https://github.com/${owner}/${repo}`);
3562
+ return url ? { github_url: url } : {};
3563
+ }
3564
+
3565
+ // ../../packages/tools-local/dist/discovery/resolvers/gradle.js
3566
+ init_esm_shims();
3567
+ import { readdir as readdir6 } from "fs/promises";
3568
+ import { homedir as homedir3 } from "os";
3569
+ import { join as join21 } from "path";
3570
+
3571
+ // ../../packages/tools-local/dist/discovery/resolvers/pom-shared.js
3572
+ init_esm_shims();
3573
+ var import_errors11 = __toESM(require_dist2(), 1);
3574
+ import { readFile as readFile18 } from "fs/promises";
3575
+ import { XMLParser as XMLParser3 } from "fast-xml-parser";
3576
+ var logger9 = (0, import_errors11.createMcpLogger)({ name: "@toolcairn/tools:resolver:pom" });
3577
+ async function parsePomIdentity(path2, depName) {
3578
+ if (!await fileExists(path2))
3579
+ return {};
3580
+ try {
3581
+ const raw = await readFile18(path2, "utf-8");
3582
+ const parser = new XMLParser3({ ignoreAttributes: true, parseTagValue: true });
3583
+ const doc = parser.parse(raw);
3584
+ const project = doc.project;
3585
+ if (!project)
3586
+ return {};
3587
+ const out = {};
3588
+ const canonical = project.groupId && project.artifactId ? `${project.groupId}:${project.artifactId}` : void 0;
3589
+ if (canonical && canonical !== depName)
3590
+ out.canonical_package_name = canonical;
3591
+ if (project.version)
3592
+ out.resolved_version = project.version;
3593
+ const candidateUrls = [
3594
+ project.scm?.url,
3595
+ project.scm?.connection,
3596
+ project.scm?.developerConnection,
3597
+ project.url
3598
+ ];
3599
+ for (const u of candidateUrls) {
3600
+ const normalised = normaliseGitHubUrl(u);
3601
+ if (normalised) {
3602
+ out.github_url = normalised;
3603
+ break;
3604
+ }
3605
+ }
3606
+ return out;
3607
+ } catch (err) {
3608
+ logger9.debug({ err: err instanceof Error ? err.message : String(err), path: path2 }, "Failed to parse .pom");
3609
+ return {};
3610
+ }
3611
+ }
3612
+
3613
+ // ../../packages/tools-local/dist/discovery/resolvers/gradle.js
3614
+ async function findGradlePom(groupId, artifactId, preferredVersion) {
3615
+ const base = join21(homedir3(), ".gradle", "caches", "modules-2", "files-2.1", groupId, artifactId);
3616
+ if (!await isDir(base))
3617
+ return null;
3618
+ let versions;
3619
+ try {
3620
+ versions = await readdir6(base);
3621
+ } catch {
3622
+ return null;
3623
+ }
3624
+ const chosen = preferredVersion && versions.includes(preferredVersion) ? preferredVersion : versions.sort().at(-1);
3625
+ if (!chosen)
3626
+ return null;
3627
+ const versionDir = join21(base, chosen);
3628
+ let hashDirs;
3629
+ try {
3630
+ hashDirs = await readdir6(versionDir);
3631
+ } catch {
3632
+ return null;
3633
+ }
3634
+ for (const hash of hashDirs) {
3635
+ const candidate = join21(versionDir, hash, `${artifactId}-${chosen}.pom`);
3636
+ return candidate;
3637
+ }
3638
+ return null;
3639
+ }
3640
+ async function resolveGradleIdentity(_workspaceAbs, _projectRoot, depName, hints = {}) {
3641
+ const colon = depName.indexOf(":");
3642
+ if (colon < 0)
3643
+ return {};
3644
+ const groupId = depName.slice(0, colon);
3645
+ const artifactId = depName.slice(colon + 1);
3646
+ if (!groupId || !artifactId)
3647
+ return {};
3648
+ const pomPath = await findGradlePom(groupId, artifactId, hints.resolved_version);
3649
+ if (!pomPath)
3650
+ return {};
3651
+ return parsePomIdentity(pomPath, depName);
3652
+ }
3653
+
3654
+ // ../../packages/tools-local/dist/discovery/resolvers/hex.js
3655
+ init_esm_shims();
3656
+ var import_errors12 = __toESM(require_dist2(), 1);
3657
+ import { readFile as readFile19 } from "fs/promises";
3658
+ import { join as join22 } from "path";
3659
+ var logger10 = (0, import_errors12.createMcpLogger)({ name: "@toolcairn/tools:resolver:hex" });
3660
+ function extractHexMetadataUrl(raw) {
3661
+ let match = raw.match(/<<"GitHub">>\s*,\s*<<"([^"]+)">>/i);
3662
+ if (match?.[1])
3663
+ return match[1];
3664
+ match = raw.match(/<<"[^"]*github[^"]*">>\s*,\s*<<"(https?:\/\/[^"]+)">>/i);
3665
+ if (match?.[1])
3666
+ return match[1];
3667
+ return void 0;
3668
+ }
3669
+ function extractMixExsUrl(raw) {
3670
+ const atMatch = raw.match(/@source_url\s*\(?\s*["']([^"']+)["']/);
3671
+ if (atMatch?.[1])
3672
+ return atMatch[1];
3673
+ const kwMatch = raw.match(/\bsource_url\s*:\s*["']([^"']+)["']/);
3674
+ if (kwMatch?.[1])
3675
+ return kwMatch[1];
3676
+ return void 0;
3677
+ }
3678
+ async function resolveHexIdentity(workspaceAbs, _projectRoot, depName) {
3679
+ const depDir = join22(workspaceAbs, "deps", depName);
3680
+ const out = {};
3681
+ const metaPath = join22(depDir, "hex_metadata.config");
3682
+ if (await fileExists(metaPath)) {
3683
+ try {
3684
+ const raw = await readFile19(metaPath, "utf-8");
3685
+ const url = extractHexMetadataUrl(raw);
3686
+ const normalised = normaliseGitHubUrl(url);
3687
+ if (normalised)
3688
+ out.github_url = normalised;
3689
+ } catch (err) {
3690
+ logger10.debug({ err: err instanceof Error ? err.message : String(err), metaPath }, "Failed to read hex_metadata.config");
3691
+ }
3692
+ }
3693
+ if (!out.github_url) {
3694
+ const mixPath = join22(depDir, "mix.exs");
3695
+ if (await fileExists(mixPath)) {
3696
+ try {
3697
+ const raw = await readFile19(mixPath, "utf-8");
3698
+ const url = extractMixExsUrl(raw);
3699
+ const normalised = normaliseGitHubUrl(url);
3700
+ if (normalised)
3701
+ out.github_url = normalised;
3702
+ } catch {
3703
+ }
3704
+ }
3705
+ }
3706
+ return out;
3707
+ }
3708
+
3709
+ // ../../packages/tools-local/dist/discovery/resolvers/maven.js
3710
+ init_esm_shims();
3711
+ import { readdir as readdir7 } from "fs/promises";
3712
+ import { homedir as homedir4 } from "os";
3713
+ import { join as join23 } from "path";
3714
+ async function findMavenPom(groupId, artifactId, preferredVersion) {
3715
+ const groupPath = groupId.replace(/\./g, "/");
3716
+ const base = join23(homedir4(), ".m2", "repository", groupPath, artifactId);
3717
+ if (!await isDir(base))
3718
+ return null;
3719
+ let versions;
3720
+ try {
3721
+ versions = await readdir7(base);
3722
+ } catch {
3723
+ return null;
3724
+ }
3725
+ let chosen;
3726
+ if (preferredVersion && versions.includes(preferredVersion)) {
3727
+ chosen = preferredVersion;
3728
+ } else {
3729
+ versions.sort();
3730
+ chosen = versions[versions.length - 1];
3731
+ }
3732
+ if (!chosen)
3733
+ return null;
3734
+ return join23(base, chosen, `${artifactId}-${chosen}.pom`);
3735
+ }
3736
+ async function resolveMavenIdentity(_workspaceAbs, _projectRoot, depName, hints = {}) {
3737
+ const colon = depName.indexOf(":");
3738
+ if (colon < 0)
3739
+ return {};
3740
+ const groupId = depName.slice(0, colon);
3741
+ const artifactId = depName.slice(colon + 1);
3742
+ if (!groupId || !artifactId)
3743
+ return {};
3744
+ const pomPath = await findMavenPom(groupId, artifactId, hints.resolved_version);
3745
+ if (!pomPath)
3746
+ return {};
3747
+ return parsePomIdentity(pomPath, depName);
3748
+ }
3749
+
3750
+ // ../../packages/tools-local/dist/discovery/resolvers/npm.js
3751
+ init_esm_shims();
3752
+ var import_errors13 = __toESM(require_dist2(), 1);
3753
+ import { readFile as readFile20 } from "fs/promises";
3754
+ import { join as join24 } from "path";
3755
+ var logger11 = (0, import_errors13.createMcpLogger)({ name: "@toolcairn/tools:resolver:npm" });
3756
+ function extractRepoUrl(pkg) {
3757
+ const r = pkg.repository;
3758
+ if (!r)
3759
+ return void 0;
3760
+ if (typeof r === "string")
3761
+ return r;
3762
+ return r.url;
3763
+ }
3764
+ async function findInstalledManifest(workspaceAbs, projectRoot, depKey) {
3765
+ let cursor = workspaceAbs;
3766
+ const stopAt = projectRoot;
3767
+ for (let i = 0; i < 10; i++) {
3768
+ const candidate = join24(cursor, "node_modules", depKey, "package.json");
3769
+ if (await fileExists(candidate))
3770
+ return candidate;
3771
+ if (cursor === stopAt)
3772
+ break;
3773
+ const parent = join24(cursor, "..");
3774
+ if (parent === cursor)
3775
+ break;
3776
+ cursor = parent;
3777
+ }
3778
+ return null;
3779
+ }
3780
+ async function resolveNpmIdentity(workspaceAbs, projectRoot, depKey) {
3781
+ const manifestPath = await findInstalledManifest(workspaceAbs, projectRoot, depKey);
3782
+ if (!manifestPath)
3783
+ return {};
3784
+ let pkg;
3785
+ try {
3786
+ pkg = JSON.parse(await readFile20(manifestPath, "utf-8"));
3787
+ } catch (err) {
3788
+ logger11.debug({ err: err instanceof Error ? err.message : String(err), manifestPath }, "Failed to parse installed package.json \u2014 skipping url resolution");
3789
+ return {};
3790
+ }
3791
+ const out = {};
3792
+ if (pkg.name && pkg.name !== depKey) {
3793
+ out.canonical_package_name = pkg.name;
3794
+ }
3795
+ if (pkg.version) {
3796
+ out.resolved_version = pkg.version;
3797
+ }
3798
+ const url = extractRepoUrl(pkg);
3799
+ const normalised = normaliseGitHubUrl(url);
3800
+ if (normalised)
3801
+ out.github_url = normalised;
3802
+ return out;
3803
+ }
3804
+
3805
+ // ../../packages/tools-local/dist/discovery/resolvers/nuget.js
3806
+ init_esm_shims();
3807
+ var import_errors14 = __toESM(require_dist2(), 1);
3808
+ import { readFile as readFile21, readdir as readdir8 } from "fs/promises";
3809
+ import { homedir as homedir5 } from "os";
3810
+ import { join as join25 } from "path";
3811
+ import { XMLParser as XMLParser4 } from "fast-xml-parser";
3812
+ var logger12 = (0, import_errors14.createMcpLogger)({ name: "@toolcairn/tools:resolver:nuget" });
3813
+ async function findNuspec(depName, preferredVersion) {
3814
+ const pkgRoot = join25(homedir5(), ".nuget", "packages", depName.toLowerCase());
3815
+ if (!await isDir(pkgRoot))
3816
+ return null;
3817
+ let versions;
3818
+ try {
3819
+ versions = await readdir8(pkgRoot);
3820
+ } catch {
3821
+ return null;
3822
+ }
3823
+ const chosen = preferredVersion && versions.includes(preferredVersion) ? preferredVersion : versions.sort().at(-1);
3824
+ if (!chosen)
3825
+ return null;
3826
+ const path2 = join25(pkgRoot, chosen, `${depName.toLowerCase()}.nuspec`);
3827
+ return await fileExists(path2) ? path2 : null;
3828
+ }
3829
+ async function resolveNugetIdentity(_workspaceAbs, _projectRoot, depName, hints = {}) {
3830
+ const path2 = await findNuspec(depName, hints.resolved_version);
3831
+ if (!path2)
3832
+ return {};
3833
+ try {
3834
+ const raw = await readFile21(path2, "utf-8");
3835
+ const parser = new XMLParser4({ ignoreAttributes: false });
3836
+ const doc = parser.parse(raw);
3837
+ const meta = doc.package?.metadata;
3838
+ if (!meta)
3839
+ return {};
3840
+ const out = {};
3841
+ if (meta.id && meta.id !== depName)
3842
+ out.canonical_package_name = meta.id;
3843
+ if (meta.version)
3844
+ out.resolved_version = meta.version;
3845
+ const repoUrl = meta.repository?.["@_url"] ?? meta.repository?.url;
3846
+ const candidate = normaliseGitHubUrl(repoUrl ?? meta.projectUrl);
3847
+ if (candidate)
3848
+ out.github_url = candidate;
3849
+ return out;
3850
+ } catch (err) {
3851
+ logger12.debug({ err: err instanceof Error ? err.message : String(err), path: path2 }, "Failed to parse .nuspec");
3852
+ return {};
3853
+ }
3854
+ }
3855
+
3856
+ // ../../packages/tools-local/dist/discovery/resolvers/pub.js
3857
+ init_esm_shims();
3858
+ var import_errors15 = __toESM(require_dist2(), 1);
3859
+ import { readFile as readFile22 } from "fs/promises";
3860
+ import { homedir as homedir6, platform as platform2 } from "os";
3861
+ import { join as join26 } from "path";
3862
+ import { parse as parseYaml3 } from "yaml";
3863
+ var logger13 = (0, import_errors15.createMcpLogger)({ name: "@toolcairn/tools:resolver:pub" });
3864
+ function pubCacheRoot() {
3865
+ if (platform2() === "win32") {
3866
+ const local = process.env.LOCALAPPDATA;
3867
+ if (local)
3868
+ return join26(local, "Pub", "Cache", "hosted", "pub.dev");
3869
+ }
3870
+ return join26(homedir6(), ".pub-cache", "hosted", "pub.dev");
3871
+ }
3872
+ async function findPubspec(depName, version) {
3873
+ const root = pubCacheRoot();
3874
+ if (version) {
3875
+ const direct = join26(root, `${depName}-${version}`, "pubspec.yaml");
3876
+ return await fileExists(direct) ? direct : null;
3877
+ }
3878
+ try {
3879
+ const { readdir: readdir12 } = await import("fs/promises");
3880
+ const entries = await readdir12(root);
3881
+ const matches = entries.filter((e) => e.startsWith(`${depName}-`)).sort();
3882
+ const chosen = matches.at(-1);
3883
+ if (!chosen)
3884
+ return null;
3885
+ const candidate = join26(root, chosen, "pubspec.yaml");
3886
+ return await fileExists(candidate) ? candidate : null;
3887
+ } catch {
3888
+ return null;
3889
+ }
3890
+ }
3891
+ async function resolvePubIdentity(_workspaceAbs, _projectRoot, depName, hints = {}) {
3892
+ const path2 = await findPubspec(depName, hints.resolved_version);
3893
+ if (!path2)
3894
+ return {};
3895
+ try {
3896
+ const raw = await readFile22(path2, "utf-8");
3897
+ const pkg = parseYaml3(raw);
3898
+ const out = {};
3899
+ if (pkg.name && pkg.name !== depName)
3900
+ out.canonical_package_name = pkg.name;
3901
+ if (pkg.version)
3902
+ out.resolved_version = pkg.version;
3903
+ const candidate = normaliseGitHubUrl(pkg.repository ?? pkg.homepage);
3904
+ if (candidate)
3905
+ out.github_url = candidate;
3906
+ return out;
3907
+ } catch (err) {
3908
+ logger13.debug({ err: err instanceof Error ? err.message : String(err), path: path2 }, "Failed to parse pubspec.yaml");
3909
+ return {};
3910
+ }
3911
+ }
3912
+
3913
+ // ../../packages/tools-local/dist/discovery/resolvers/pypi.js
3914
+ init_esm_shims();
3915
+ var import_errors16 = __toESM(require_dist2(), 1);
3916
+ import { readFile as readFile23, readdir as readdir9 } from "fs/promises";
3917
+ import { join as join27 } from "path";
3918
+ var logger14 = (0, import_errors16.createMcpLogger)({ name: "@toolcairn/tools:resolver:pypi" });
3919
+ async function findSitePackagesDirs(workspaceAbs) {
3920
+ const candidates = [];
3921
+ const venvs = [".venv", "venv", ".virtualenv"];
3922
+ for (const venv of venvs) {
3923
+ const venvDir = join27(workspaceAbs, venv);
3924
+ if (!await isDir(venvDir))
3925
+ continue;
3926
+ const winSite = join27(venvDir, "Lib", "site-packages");
3927
+ if (await isDir(winSite))
3928
+ candidates.push(winSite);
3929
+ const libDir = join27(venvDir, "lib");
3930
+ if (await isDir(libDir)) {
3931
+ try {
3932
+ for (const entry of await readdir9(libDir)) {
3933
+ if (!entry.startsWith("python"))
3934
+ continue;
3935
+ const sp = join27(libDir, entry, "site-packages");
3936
+ if (await isDir(sp))
3937
+ candidates.push(sp);
3938
+ }
3939
+ } catch {
3940
+ }
3941
+ }
3942
+ }
3943
+ return candidates;
3944
+ }
3945
+ function normalisePypiName(name) {
3946
+ return name.toLowerCase().replace(/[._]+/g, "-");
3947
+ }
3948
+ async function findMetadataPath(siteDir, depName) {
3949
+ const normalised = normalisePypiName(depName);
3950
+ let entries;
3951
+ try {
3952
+ entries = await readdir9(siteDir);
3953
+ } catch {
3954
+ return null;
3955
+ }
3956
+ for (const entry of entries) {
3957
+ if (!entry.endsWith(".dist-info"))
3958
+ continue;
3959
+ const base = entry.replace(/-[^-]+\.dist-info$/, "");
3960
+ if (normalisePypiName(base) === normalised) {
3961
+ const metadataPath = join27(siteDir, entry, "METADATA");
3962
+ if (await fileExists(metadataPath))
3963
+ return metadataPath;
3964
+ }
3965
+ }
3966
+ return null;
3967
+ }
3968
+ function parseMetadata(raw) {
3969
+ const urls = [];
3970
+ let name;
3971
+ let version;
3972
+ const lines = raw.split("\n");
3973
+ for (const line of lines) {
3974
+ if (line.trim() === "")
3975
+ break;
3976
+ const colon = line.indexOf(":");
3977
+ if (colon < 0)
3978
+ continue;
3979
+ const key = line.slice(0, colon).trim();
3980
+ const val = line.slice(colon + 1).trim();
3981
+ if (key === "Name" && !name)
3982
+ name = val;
3983
+ else if (key === "Version" && !version)
3984
+ version = val;
3985
+ else if (key === "Home-page")
3986
+ urls.push(val);
3987
+ else if (key === "Project-URL") {
3988
+ const comma = val.indexOf(",");
3989
+ if (comma >= 0)
3990
+ urls.push(val.slice(comma + 1).trim());
3991
+ else
3992
+ urls.push(val);
3993
+ }
3994
+ }
3995
+ return { name, version, urls };
3996
+ }
3997
+ async function resolvePypiIdentity(workspaceAbs, _projectRoot, depName) {
3998
+ const siteDirs = await findSitePackagesDirs(workspaceAbs);
3999
+ for (const siteDir of siteDirs) {
4000
+ const path2 = await findMetadataPath(siteDir, depName);
4001
+ if (!path2)
4002
+ continue;
4003
+ try {
4004
+ const raw = await readFile23(path2, "utf-8");
4005
+ const { name, version, urls } = parseMetadata(raw);
4006
+ const out = {};
4007
+ if (name && normalisePypiName(name) !== normalisePypiName(depName)) {
4008
+ out.canonical_package_name = name;
4009
+ }
4010
+ if (version)
4011
+ out.resolved_version = version;
4012
+ for (const u of urls) {
4013
+ const normalised = normaliseGitHubUrl(u);
4014
+ if (normalised) {
4015
+ out.github_url = normalised;
4016
+ break;
4017
+ }
4018
+ }
4019
+ return out;
4020
+ } catch (err) {
4021
+ logger14.debug({ err: err instanceof Error ? err.message : String(err), path: path2 }, "Failed to parse METADATA");
4022
+ }
4023
+ }
4024
+ return {};
4025
+ }
4026
+
4027
+ // ../../packages/tools-local/dist/discovery/resolvers/ruby.js
4028
+ init_esm_shims();
4029
+ var import_errors17 = __toESM(require_dist2(), 1);
4030
+ import { readFile as readFile24, readdir as readdir10 } from "fs/promises";
4031
+ import { homedir as homedir7 } from "os";
4032
+ import { join as join28 } from "path";
4033
+ var logger15 = (0, import_errors17.createMcpLogger)({ name: "@toolcairn/tools:resolver:ruby" });
4034
+ async function findGemspec(workspaceAbs, depName, preferredVersion) {
4035
+ const specsDirs = [];
4036
+ const bundleRubyDir = join28(workspaceAbs, "vendor", "bundle", "ruby");
4037
+ if (await isDir(bundleRubyDir)) {
4038
+ try {
4039
+ for (const entry of await readdir10(bundleRubyDir)) {
4040
+ const dir = join28(bundleRubyDir, entry, "specifications");
4041
+ if (await isDir(dir))
4042
+ specsDirs.push(dir);
4043
+ }
4044
+ } catch {
4045
+ }
4046
+ }
4047
+ const homeSpecs = join28(homedir7(), ".gem", "specifications");
4048
+ if (await isDir(homeSpecs))
4049
+ specsDirs.push(homeSpecs);
4050
+ for (const dir of specsDirs) {
4051
+ let entries;
4052
+ try {
4053
+ entries = await readdir10(dir);
4054
+ } catch {
4055
+ continue;
4056
+ }
4057
+ const matches = entries.filter((e) => e.endsWith(".gemspec") && e.startsWith(`${depName}-`)).filter((e) => {
4058
+ if (!preferredVersion)
4059
+ return true;
4060
+ return e === `${depName}-${preferredVersion}.gemspec`;
4061
+ }).sort();
4062
+ const chosen = matches.at(-1);
4063
+ if (chosen) {
4064
+ const path2 = join28(dir, chosen);
4065
+ if (await fileExists(path2))
4066
+ return path2;
4067
+ }
4068
+ }
4069
+ return null;
4070
+ }
4071
+ function extractGemspecFields(raw) {
4072
+ const out = {};
4073
+ const pick = (pattern) => {
4074
+ const m = raw.match(pattern);
4075
+ return m ? m[1] : void 0;
4076
+ };
4077
+ out.name = pick(/(?:s|spec)\.name\s*=\s*(['"])([^'"]+)\1/) ? raw.match(/(?:s|spec)\.name\s*=\s*['"]([^'"]+)['"]/)?.[1] : void 0;
4078
+ out.version = raw.match(/(?:s|spec)\.version\s*=\s*['"]([^'"]+)['"]/)?.[1];
4079
+ out.homepage = raw.match(/(?:s|spec)\.homepage\s*=\s*['"]([^'"]+)['"]/)?.[1];
4080
+ out.source_code_uri = raw.match(/["']source_code_uri["']\s*=>\s*["']([^'"]+)["']/)?.[1];
4081
+ return out;
4082
+ }
4083
+ async function resolveRubyIdentity(workspaceAbs, _projectRoot, depName, hints = {}) {
4084
+ const path2 = await findGemspec(workspaceAbs, depName, hints.resolved_version);
4085
+ if (!path2)
4086
+ return {};
4087
+ try {
4088
+ const raw = await readFile24(path2, "utf-8");
4089
+ const fields = extractGemspecFields(raw);
4090
+ const out = {};
4091
+ if (fields.name && fields.name !== depName)
4092
+ out.canonical_package_name = fields.name;
4093
+ if (fields.version)
4094
+ out.resolved_version = fields.version;
4095
+ const candidate = normaliseGitHubUrl(fields.source_code_uri ?? fields.homepage);
4096
+ if (candidate)
4097
+ out.github_url = candidate;
4098
+ return out;
4099
+ } catch (err) {
4100
+ logger15.debug({ err: err instanceof Error ? err.message : String(err), path: path2 }, "Failed to read/parse gemspec");
4101
+ return {};
4102
+ }
4103
+ }
4104
+
4105
+ // ../../packages/tools-local/dist/discovery/resolvers/swift-pm.js
4106
+ init_esm_shims();
4107
+ var import_errors18 = __toESM(require_dist2(), 1);
4108
+ import { readFile as readFile25 } from "fs/promises";
4109
+ import { join as join29 } from "path";
4110
+ var logger16 = (0, import_errors18.createMcpLogger)({ name: "@toolcairn/tools:resolver:swift-pm" });
4111
+ async function resolveSwiftPmIdentity(workspaceAbs, _projectRoot, depName) {
4112
+ const path2 = join29(workspaceAbs, "Package.resolved");
4113
+ if (!await fileExists(path2))
4114
+ return {};
4115
+ try {
4116
+ const raw = await readFile25(path2, "utf-8");
4117
+ const doc = JSON.parse(raw);
4118
+ const out = {};
4119
+ for (const pin of doc.pins ?? []) {
4120
+ if (pin.identity === depName) {
4121
+ if (pin.state?.version)
4122
+ out.resolved_version = pin.state.version;
4123
+ const normalised = normaliseGitHubUrl(pin.location);
4124
+ if (normalised)
4125
+ out.github_url = normalised;
4126
+ return out;
4127
+ }
4128
+ }
4129
+ for (const pin of doc.object?.pins ?? []) {
4130
+ if (pin.package === depName) {
4131
+ if (pin.state?.version)
4132
+ out.resolved_version = pin.state.version;
4133
+ const normalised = normaliseGitHubUrl(pin.repositoryURL);
4134
+ if (normalised)
4135
+ out.github_url = normalised;
4136
+ return out;
4137
+ }
4138
+ }
4139
+ return {};
4140
+ } catch (err) {
4141
+ logger16.debug({ err: err instanceof Error ? err.message : String(err), path: path2 }, "Failed to parse Package.resolved during resolve");
4142
+ return {};
4143
+ }
4144
+ }
4145
+
4146
+ // ../../packages/tools-local/dist/discovery/resolvers/index.js
4147
+ var RESOLVERS = {
4148
+ npm: resolveNpmIdentity,
4149
+ pypi: resolvePypiIdentity,
4150
+ cargo: resolveCargoIdentity,
4151
+ go: (w, p, n) => resolveGoIdentity(w, p, n),
4152
+ rubygems: resolveRubyIdentity,
4153
+ maven: resolveMavenIdentity,
4154
+ gradle: resolveGradleIdentity,
4155
+ composer: resolveComposerIdentity,
4156
+ hex: resolveHexIdentity,
4157
+ pub: resolvePubIdentity,
4158
+ nuget: resolveNugetIdentity,
4159
+ "swift-pm": resolveSwiftPmIdentity
4160
+ };
4161
+
3418
4162
  // ../../packages/tools-local/dist/discovery/workspaces/glob.js
3419
4163
  init_esm_shims();
3420
- import { readdir as readdir5 } from "fs/promises";
3421
- import { join as join19, relative as relative3, sep as sep2 } from "path";
4164
+ import { readdir as readdir11 } from "fs/promises";
4165
+ import { join as join30, relative as relative3, sep as sep2 } from "path";
3422
4166
  async function expandWorkspaceGlobs(rootDir, patterns) {
3423
4167
  const excluded = /* @__PURE__ */ new Set();
3424
4168
  const included = /* @__PURE__ */ new Set();
@@ -3454,11 +4198,11 @@ async function walkPattern(rootDir, currentDir, parts, index, out) {
3454
4198
  if (segment === "**") {
3455
4199
  await walkPattern(rootDir, currentDir, parts, index + 1, out);
3456
4200
  try {
3457
- const entries = await readdir5(currentDir, { withFileTypes: true });
4201
+ const entries = await readdir11(currentDir, { withFileTypes: true });
3458
4202
  for (const entry of entries) {
3459
4203
  if (!entry.isDirectory() || IGNORED_DIRS.has(entry.name))
3460
4204
  continue;
3461
- await walkPattern(rootDir, join19(currentDir, entry.name), parts, index, out);
4205
+ await walkPattern(rootDir, join30(currentDir, entry.name), parts, index, out);
3462
4206
  }
3463
4207
  } catch {
3464
4208
  }
@@ -3467,19 +4211,19 @@ async function walkPattern(rootDir, currentDir, parts, index, out) {
3467
4211
  if (segment.includes("*")) {
3468
4212
  const re = globSegmentToRegex(segment);
3469
4213
  try {
3470
- const entries = await readdir5(currentDir, { withFileTypes: true });
4214
+ const entries = await readdir11(currentDir, { withFileTypes: true });
3471
4215
  for (const entry of entries) {
3472
4216
  if (!entry.isDirectory() || IGNORED_DIRS.has(entry.name))
3473
4217
  continue;
3474
4218
  if (re.test(entry.name)) {
3475
- await walkPattern(rootDir, join19(currentDir, entry.name), parts, index + 1, out);
4219
+ await walkPattern(rootDir, join30(currentDir, entry.name), parts, index + 1, out);
3476
4220
  }
3477
4221
  }
3478
4222
  } catch {
3479
4223
  }
3480
4224
  return;
3481
4225
  }
3482
- await walkPattern(rootDir, join19(currentDir, segment), parts, index + 1, out);
4226
+ await walkPattern(rootDir, join30(currentDir, segment), parts, index + 1, out);
3483
4227
  }
3484
4228
  function globSegmentToRegex(segment) {
3485
4229
  const escaped = segment.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
@@ -3492,10 +4236,10 @@ function toRelPosix(projectRoot, absPath) {
3492
4236
 
3493
4237
  // ../../packages/tools-local/dist/discovery/workspaces/walker.js
3494
4238
  init_esm_shims();
3495
- import { readFile as readFile16 } from "fs/promises";
3496
- import { join as join20 } from "path";
3497
- import { parse as parseToml3 } from "smol-toml";
3498
- import { parse as parseYaml3 } from "yaml";
4239
+ import { readFile as readFile26 } from "fs/promises";
4240
+ import { join as join31 } from "path";
4241
+ import { parse as parseToml4 } from "smol-toml";
4242
+ import { parse as parseYaml4 } from "yaml";
3499
4243
  async function discoverWorkspaces(projectRoot, maxDepth = 5) {
3500
4244
  const warnings = [];
3501
4245
  const discovered = /* @__PURE__ */ new Set([projectRoot]);
@@ -3521,10 +4265,10 @@ async function discoverWorkspaces(projectRoot, maxDepth = 5) {
3521
4265
  }
3522
4266
  async function readWorkspaceGlobs(dir, warnings) {
3523
4267
  const globs = [];
3524
- const pnpmPath = join20(dir, "pnpm-workspace.yaml");
4268
+ const pnpmPath = join31(dir, "pnpm-workspace.yaml");
3525
4269
  if (await fileExists(pnpmPath)) {
3526
4270
  try {
3527
- const doc = parseYaml3(await readFile16(pnpmPath, "utf-8"));
4271
+ const doc = parseYaml4(await readFile26(pnpmPath, "utf-8"));
3528
4272
  if (Array.isArray(doc.packages))
3529
4273
  globs.push(...doc.packages);
3530
4274
  } catch (err) {
@@ -3535,10 +4279,10 @@ async function readWorkspaceGlobs(dir, warnings) {
3535
4279
  });
3536
4280
  }
3537
4281
  }
3538
- const pkgPath = join20(dir, "package.json");
4282
+ const pkgPath = join31(dir, "package.json");
3539
4283
  if (await fileExists(pkgPath)) {
3540
4284
  try {
3541
- const doc = JSON.parse(await readFile16(pkgPath, "utf-8"));
4285
+ const doc = JSON.parse(await readFile26(pkgPath, "utf-8"));
3542
4286
  if (Array.isArray(doc.workspaces)) {
3543
4287
  globs.push(...doc.workspaces);
3544
4288
  } else if (doc.workspaces && Array.isArray(doc.workspaces.packages)) {
@@ -3552,10 +4296,10 @@ async function readWorkspaceGlobs(dir, warnings) {
3552
4296
  });
3553
4297
  }
3554
4298
  }
3555
- const cargoPath = join20(dir, "Cargo.toml");
4299
+ const cargoPath = join31(dir, "Cargo.toml");
3556
4300
  if (await fileExists(cargoPath)) {
3557
4301
  try {
3558
- const doc = parseToml3(await readFile16(cargoPath, "utf-8"));
4302
+ const doc = parseToml4(await readFile26(cargoPath, "utf-8"));
3559
4303
  if (Array.isArray(doc.workspace?.members))
3560
4304
  globs.push(...doc.workspace.members);
3561
4305
  } catch (err) {
@@ -3566,10 +4310,10 @@ async function readWorkspaceGlobs(dir, warnings) {
3566
4310
  });
3567
4311
  }
3568
4312
  }
3569
- const goWorkPath = join20(dir, "go.work");
4313
+ const goWorkPath = join31(dir, "go.work");
3570
4314
  if (await fileExists(goWorkPath)) {
3571
4315
  try {
3572
- const raw = await readFile16(goWorkPath, "utf-8");
4316
+ const raw = await readFile26(goWorkPath, "utf-8");
3573
4317
  const useMatch = raw.match(/use\s*\(([^)]*)\)/s);
3574
4318
  if (useMatch?.[1]) {
3575
4319
  for (const line of useMatch[1].split("\n")) {
@@ -3592,10 +4336,10 @@ async function readWorkspaceGlobs(dir, warnings) {
3592
4336
  });
3593
4337
  }
3594
4338
  }
3595
- const lernaPath = join20(dir, "lerna.json");
4339
+ const lernaPath = join31(dir, "lerna.json");
3596
4340
  if (await fileExists(lernaPath)) {
3597
4341
  try {
3598
- const doc = JSON.parse(await readFile16(lernaPath, "utf-8"));
4342
+ const doc = JSON.parse(await readFile26(lernaPath, "utf-8"));
3599
4343
  if (Array.isArray(doc.packages))
3600
4344
  globs.push(...doc.packages);
3601
4345
  } catch (err) {
@@ -3606,10 +4350,10 @@ async function readWorkspaceGlobs(dir, warnings) {
3606
4350
  });
3607
4351
  }
3608
4352
  }
3609
- const nxPath = join20(dir, "nx.json");
4353
+ const nxPath = join31(dir, "nx.json");
3610
4354
  if (await fileExists(nxPath)) {
3611
4355
  try {
3612
- const doc = JSON.parse(await readFile16(nxPath, "utf-8"));
4356
+ const doc = JSON.parse(await readFile26(nxPath, "utf-8"));
3613
4357
  const base = doc.workspaceLayout?.projectsDir ?? "packages";
3614
4358
  globs.push(`${base}/*`);
3615
4359
  } catch (err) {
@@ -3624,13 +4368,13 @@ async function readWorkspaceGlobs(dir, warnings) {
3624
4368
  }
3625
4369
 
3626
4370
  // ../../packages/tools-local/dist/discovery/scan-project.js
3627
- var logger7 = (0, import_errors9.createMcpLogger)({ name: "@toolcairn/tools:scan-project" });
4371
+ var logger17 = (0, import_errors19.createMcpLogger)({ name: "@toolcairn/tools:scan-project" });
3628
4372
  async function scanProject(projectRoot, options = {}) {
3629
4373
  const start = Date.now();
3630
4374
  const { batchResolve, maxDepth = 5 } = options;
3631
4375
  const absRoot = resolve(projectRoot);
3632
4376
  const warnings = [];
3633
- logger7.info({ projectRoot: absRoot }, "Starting project scan");
4377
+ logger17.info({ projectRoot: absRoot }, "Starting project scan");
3634
4378
  const { paths: workspaceAbs, warnings: wsWarnings } = await discoverWorkspaces(absRoot, maxDepth);
3635
4379
  warnings.push(...wsWarnings);
3636
4380
  const allDetected = [];
@@ -3688,9 +4432,41 @@ async function scanProject(projectRoot, options = {}) {
3688
4432
  mergedMap.set(key, { name: dep.name, ecosystem: dep.ecosystem, locations: [location] });
3689
4433
  }
3690
4434
  }
4435
+ await Promise.all([...mergedMap.values()].map(async (entry) => {
4436
+ const resolver = RESOLVERS[entry.ecosystem];
4437
+ if (!resolver)
4438
+ return;
4439
+ for (const loc of entry.locations) {
4440
+ const workspaceAbs2 = resolve(absRoot, loc.workspace_path);
4441
+ const hints = { resolved_version: loc.resolved_version };
4442
+ try {
4443
+ const identity = await resolver(workspaceAbs2, absRoot, entry.name, hints);
4444
+ if (identity.canonical_package_name) {
4445
+ entry.canonical_package_name = identity.canonical_package_name;
4446
+ }
4447
+ if (identity.github_url) {
4448
+ entry.local_github_url = identity.github_url;
4449
+ }
4450
+ if (identity.canonical_package_name || identity.github_url)
4451
+ break;
4452
+ } catch (err) {
4453
+ logger17.debug({
4454
+ ecosystem: entry.ecosystem,
4455
+ name: entry.name,
4456
+ workspace: loc.workspace_path,
4457
+ err: err instanceof Error ? err.message : String(err)
4458
+ }, "Resolver threw \u2014 skipping this location");
4459
+ }
4460
+ }
4461
+ }));
3691
4462
  const workspaceRels = workspaceAbs.map((abs) => toRelPosix(absRoot, abs));
3692
4463
  const languages = await detectLanguages(absRoot, workspaceRels);
3693
- const resolveInputs = [...mergedMap.values()].map(({ name: name2, ecosystem }) => ({ name: name2, ecosystem }));
4464
+ const resolveInputs = [...mergedMap.values()].map(({ name: name2, ecosystem, canonical_package_name, local_github_url }) => ({
4465
+ name: name2,
4466
+ ecosystem,
4467
+ canonical_package_name,
4468
+ github_url: local_github_url
4469
+ }));
3694
4470
  const resolved = /* @__PURE__ */ new Map();
3695
4471
  const methods = /* @__PURE__ */ new Map();
3696
4472
  const githubUrls = /* @__PURE__ */ new Map();
@@ -3722,7 +4498,7 @@ async function scanProject(projectRoot, options = {}) {
3722
4498
  const now = (/* @__PURE__ */ new Date()).toISOString();
3723
4499
  const confirmed = [];
3724
4500
  let toolsResolvedCount = 0;
3725
- for (const { name: name2, ecosystem, locations } of mergedMap.values()) {
4501
+ for (const { name: name2, ecosystem, locations, local_github_url } of mergedMap.values()) {
3726
4502
  const key = `${ecosystem}:${name2}`;
3727
4503
  const graph = resolved.get(key);
3728
4504
  const matchMethod = methods.get(key) ?? "none";
@@ -3732,7 +4508,7 @@ async function scanProject(projectRoot, options = {}) {
3732
4508
  const source = matched ? "toolcairn" : "non_oss";
3733
4509
  const canonical = graph?.tool?.canonical_name;
3734
4510
  const categories = graph?.tool?.categories;
3735
- const github_url = githubUrls.get(key);
4511
+ const github_url = githubUrls.get(key) ?? local_github_url;
3736
4512
  const version = locations.find((l) => l.resolved_version)?.resolved_version ?? locations[0]?.version_constraint;
3737
4513
  confirmed.push({
3738
4514
  name: name2,
@@ -3764,7 +4540,7 @@ async function scanProject(projectRoot, options = {}) {
3764
4540
  duration_ms: Date.now() - start,
3765
4541
  completed_at: now
3766
4542
  };
3767
- logger7.info({
4543
+ logger17.info({
3768
4544
  projectRoot: absRoot,
3769
4545
  workspaces: workspaceAbs.length,
3770
4546
  ecosystems: scan_metadata.ecosystems_scanned,
@@ -3816,7 +4592,7 @@ async function inferProjectName(projectRoot) {
3816
4592
  const pkgPath = resolve(projectRoot, "package.json");
3817
4593
  if (await fileExists(pkgPath)) {
3818
4594
  try {
3819
- const doc = JSON.parse(await readFile17(pkgPath, "utf-8"));
4595
+ const doc = JSON.parse(await readFile27(pkgPath, "utf-8"));
3820
4596
  if (doc.name)
3821
4597
  return doc.name;
3822
4598
  } catch {
@@ -3998,10 +4774,10 @@ function getOpenCodeMcpEntry(serverPath) {
3998
4774
  }
3999
4775
 
4000
4776
  // ../../packages/tools-local/dist/handlers/toolcairn-init.js
4001
- var logger8 = (0, import_errors10.createMcpLogger)({ name: "@toolcairn/tools:toolcairn-init" });
4777
+ var logger18 = (0, import_errors20.createMcpLogger)({ name: "@toolcairn/tools:toolcairn-init" });
4002
4778
  async function handleToolcairnInit(args, deps = {}) {
4003
4779
  try {
4004
- logger8.info({ agent: args.agent, project_root: args.project_root }, "toolcairn_init called");
4780
+ logger18.info({ agent: args.agent, project_root: args.project_root }, "toolcairn_init called");
4005
4781
  const scan = await scanProject(args.project_root, { batchResolve: deps.batchResolve });
4006
4782
  const audit = {
4007
4783
  action: "init",
@@ -4072,22 +4848,22 @@ async function handleToolcairnInit(args, deps = {}) {
4072
4848
  next_steps: "Config written. Apply the setup_steps above (CLAUDE.md rules + .mcp.json merge + .gitignore). Then proceed with normal tool calls \u2014 the server owns .toolcairn/ going forward."
4073
4849
  });
4074
4850
  } catch (e) {
4075
- logger8.error({ err: e }, "toolcairn_init failed");
4851
+ logger18.error({ err: e }, "toolcairn_init failed");
4076
4852
  return errResult("init_error", e instanceof Error ? e.message : String(e));
4077
4853
  }
4078
4854
  }
4079
4855
 
4080
4856
  // ../../packages/tools-local/dist/handlers/read-project-config.js
4081
4857
  init_esm_shims();
4082
- var import_errors11 = __toESM(require_dist2(), 1);
4083
- var logger9 = (0, import_errors11.createMcpLogger)({ name: "@toolcairn/tools:read-project-config" });
4858
+ var import_errors21 = __toESM(require_dist2(), 1);
4859
+ var logger19 = (0, import_errors21.createMcpLogger)({ name: "@toolcairn/tools:read-project-config" });
4084
4860
  var STALENESS_THRESHOLD_DAYS = 90;
4085
4861
  function daysSince(isoDate) {
4086
4862
  return (Date.now() - new Date(isoDate).getTime()) / (1e3 * 60 * 60 * 24);
4087
4863
  }
4088
4864
  async function handleReadProjectConfig(args) {
4089
4865
  try {
4090
- logger9.info({ project_root: args.project_root }, "read_project_config called");
4866
+ logger19.info({ project_root: args.project_root }, "read_project_config called");
4091
4867
  const { config: initial, corrupt_backup_path } = await readConfig(args.project_root);
4092
4868
  if (!initial) {
4093
4869
  return okResult({
@@ -4170,18 +4946,18 @@ async function handleReadProjectConfig(args) {
4170
4946
  agent_instructions: instructions_lines.join("\n")
4171
4947
  });
4172
4948
  } catch (e) {
4173
- logger9.error({ err: e }, "read_project_config failed");
4949
+ logger19.error({ err: e }, "read_project_config failed");
4174
4950
  return errResult("read_config_error", e instanceof Error ? e.message : String(e));
4175
4951
  }
4176
4952
  }
4177
4953
 
4178
4954
  // ../../packages/tools-local/dist/handlers/update-project-config.js
4179
4955
  init_esm_shims();
4180
- var import_errors12 = __toESM(require_dist2(), 1);
4181
- var logger10 = (0, import_errors12.createMcpLogger)({ name: "@toolcairn/tools:update-project-config" });
4956
+ var import_errors22 = __toESM(require_dist2(), 1);
4957
+ var logger20 = (0, import_errors22.createMcpLogger)({ name: "@toolcairn/tools:update-project-config" });
4182
4958
  async function handleUpdateProjectConfig(args) {
4183
4959
  try {
4184
- logger10.info({ project_root: args.project_root, action: args.action, tool: args.tool_name }, "update_project_config called");
4960
+ logger20.info({ project_root: args.project_root, action: args.action, tool: args.tool_name }, "update_project_config called");
4185
4961
  const data = args.data ?? {};
4186
4962
  let notFound = false;
4187
4963
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -4266,7 +5042,7 @@ async function handleUpdateProjectConfig(args) {
4266
5042
  audit_log_path: ".toolcairn/audit-log.jsonl"
4267
5043
  });
4268
5044
  } catch (e) {
4269
- logger10.error({ err: e }, "update_project_config failed");
5045
+ logger20.error({ err: e }, "update_project_config failed");
4270
5046
  return errResult("update_config_error", e instanceof Error ? e.message : String(e));
4271
5047
  }
4272
5048
  }
@@ -4289,10 +5065,10 @@ import { z as z2 } from "zod";
4289
5065
  // src/middleware/event-logger.ts
4290
5066
  init_esm_shims();
4291
5067
  var import_config = __toESM(require_dist(), 1);
4292
- var import_errors13 = __toESM(require_dist2(), 1);
5068
+ var import_errors23 = __toESM(require_dist2(), 1);
4293
5069
  import { appendFile as appendFile2, mkdir as mkdir6 } from "fs/promises";
4294
5070
  import { dirname } from "path";
4295
- var logger11 = (0, import_errors13.createMcpLogger)({ name: "@toolcairn/mcp-server:event-logger" });
5071
+ var logger21 = (0, import_errors23.createMcpLogger)({ name: "@toolcairn/mcp-server:event-logger" });
4296
5072
  function isTrackingEnabled() {
4297
5073
  return process.env.TOOLCAIRN_TRACKING_ENABLED !== "false";
4298
5074
  }
@@ -4336,7 +5112,7 @@ async function writeToFile(eventsPath, event) {
4336
5112
  await appendFile2(eventsPath, `${JSON.stringify(event)}
4337
5113
  `, "utf-8");
4338
5114
  } catch (e) {
4339
- logger11.warn({ err: e, path: eventsPath }, "Failed to write event to JSONL file");
5115
+ logger21.warn({ err: e, path: eventsPath }, "Failed to write event to JSONL file");
4340
5116
  }
4341
5117
  }
4342
5118
  async function sendToApi(event) {
@@ -4358,7 +5134,7 @@ async function sendToApi(event) {
4358
5134
  })
4359
5135
  });
4360
5136
  } catch (e) {
4361
- logger11.debug({ err: e }, "Failed to send event to API \u2014 non-fatal");
5137
+ logger21.debug({ err: e }, "Failed to send event to API \u2014 non-fatal");
4362
5138
  }
4363
5139
  }
4364
5140
  function withEventLogging(toolName, handler) {
@@ -4399,7 +5175,7 @@ function withEventLogging(toolName, handler) {
4399
5175
  }
4400
5176
 
4401
5177
  // src/server.prod.ts
4402
- var logger12 = (0, import_errors14.createMcpLogger)({ name: "@toolcairn/mcp-server:prod" });
5178
+ var logger22 = (0, import_errors24.createMcpLogger)({ name: "@toolcairn/mcp-server:prod" });
4403
5179
  var SETUP_INSTRUCTIONS = `
4404
5180
  # ToolCairn \u2014 Agent Setup Instructions
4405
5181
 
@@ -4465,9 +5241,9 @@ async function addToolsToServer(server) {
4465
5241
  apiKey: creds.client_id,
4466
5242
  accessToken: creds.access_token
4467
5243
  });
4468
- logger12.info({ user: creds.user_email }, "Registering production tools");
5244
+ logger22.info({ user: creds.user_email }, "Registering production tools");
4469
5245
  function wrap(toolName, fn) {
4470
- return withEventLogging(toolName, (0, import_errors14.withErrorHandling)(toolName, logger12, fn));
5246
+ return withEventLogging(toolName, (0, import_errors24.withErrorHandling)(toolName, logger22, fn));
4471
5247
  }
4472
5248
  server.registerTool(
4473
5249
  "classify_prompt",
@@ -4687,14 +5463,14 @@ function createTransport() {
4687
5463
 
4688
5464
  // src/index.prod.ts
4689
5465
  process.env.TOOLPILOT_MODE = "production";
4690
- var logger13 = (0, import_errors15.createMcpLogger)({ name: "@toolcairn/mcp-server" });
5466
+ var logger23 = (0, import_errors25.createMcpLogger)({ name: "@toolcairn/mcp-server" });
4691
5467
  async function main() {
4692
5468
  await ensureProjectSetup();
4693
5469
  const creds = await loadCredentials();
4694
5470
  const authenticated = creds !== null && isTokenValid(creds);
4695
5471
  let server;
4696
5472
  if (authenticated) {
4697
- logger13.info({ user: creds.user_email }, "Authenticated \u2014 starting full server");
5473
+ logger23.info({ user: creds.user_email }, "Authenticated \u2014 starting full server");
4698
5474
  server = await buildProdServer();
4699
5475
  } else {
4700
5476
  let verificationUri = "https://toolcairn.neurynae.com/signup";
@@ -4704,15 +5480,15 @@ async function main() {
4704
5480
  if (pending) {
4705
5481
  verificationUri = pending.verification_uri;
4706
5482
  userCode = pending.user_code;
4707
- logger13.info({ userCode }, "Resuming pending sign-in");
5483
+ logger23.info({ userCode }, "Resuming pending sign-in");
4708
5484
  } else {
4709
5485
  const codeData = await requestDeviceCode(import_config4.config.TOOLPILOT_API_URL);
4710
5486
  verificationUri = codeData.verification_uri;
4711
5487
  userCode = codeData.user_code;
4712
- logger13.info({ userCode }, "New sign-in started");
5488
+ logger23.info({ userCode }, "New sign-in started");
4713
5489
  }
4714
5490
  } catch (err) {
4715
- logger13.error({ err }, "Could not reach ToolCairn API \u2014 check your connection");
5491
+ logger23.error({ err }, "Could not reach ToolCairn API \u2014 check your connection");
4716
5492
  }
4717
5493
  const instructions = userCode ? `# ToolCairn \u2014 Sign In Required
4718
5494
 
@@ -4744,23 +5520,23 @@ Open the URL, sign in, and confirm the code shown. All 14 tools will appear auto
4744
5520
  })
4745
5521
  );
4746
5522
  startDeviceAuth(import_config4.config.TOOLPILOT_API_URL).then(async () => {
4747
- logger13.info("Sign-in complete \u2014 adding all tools to running server");
5523
+ logger23.info("Sign-in complete \u2014 adding all tools to running server");
4748
5524
  try {
4749
5525
  await addToolsToServer(server);
4750
- logger13.info("All ToolCairn tools now available");
5526
+ logger23.info("All ToolCairn tools now available");
4751
5527
  } catch (err) {
4752
- logger13.error({ err }, "Failed to add tools after sign-in \u2014 please reconnect");
5528
+ logger23.error({ err }, "Failed to add tools after sign-in \u2014 please reconnect");
4753
5529
  }
4754
5530
  }).catch((err) => {
4755
- logger13.error({ err }, "Sign-in failed \u2014 please try again");
5531
+ logger23.error({ err }, "Sign-in failed \u2014 please try again");
4756
5532
  });
4757
5533
  }
4758
5534
  const transport = createTransport();
4759
5535
  await server.connect(transport);
4760
- logger13.info(authenticated ? "ToolCairn MCP ready" : "ToolCairn MCP ready (awaiting sign-in)");
5536
+ logger23.info(authenticated ? "ToolCairn MCP ready" : "ToolCairn MCP ready (awaiting sign-in)");
4761
5537
  }
4762
5538
  main().catch((error) => {
4763
- (0, import_errors15.createMcpLogger)({ name: "@toolcairn/mcp-server" }).error(
5539
+ (0, import_errors25.createMcpLogger)({ name: "@toolcairn/mcp-server" }).error(
4764
5540
  { err: error },
4765
5541
  "Failed to start MCP server"
4766
5542
  );