buddy-agent-harness 0.9.0 → 0.10.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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "buddy-agent-harness",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "description": "Initialize standards-based agent configuration across agent harnesses",
5
5
  "author": {
6
6
  "name": "repobuddy"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "buddy-agent-harness",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "description": "Initialize standards-based agent configuration across agent harnesses",
5
5
  "author": {
6
6
  "name": "repobuddy"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "buddy-agent-harness",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "description": "Initialize standards-based agent configuration across agent harnesses",
5
5
  "author": {
6
6
  "name": "repobuddy"
package/dist/cli.mjs CHANGED
@@ -18994,14 +18994,7 @@ const initCommand = command({
18994
18994
  });
18995
18995
  //#endregion
18996
18996
  //#region src/cli.ts
18997
- /**
18998
- * Read from the manifest rather than restated here: a version written into this call is a second
18999
- * home for a number that already has one, and it stayed at `0.1.0` for five minor releases.
19000
- *
19001
- * `../package.json` resolves from `src/cli.ts` and from the bundled `dist/cli.mjs` alike — both sit
19002
- * one directory under the package root.
19003
- */
19004
- const version = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version;
18997
+ const version = typeof __PACKAGE_VERSION__ !== "undefined" ? __PACKAGE_VERSION__ : JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version;
19005
18998
  /**
19006
18999
  * A factory rather than a module-level constant: `cli()` builds state, and state built at import
19007
19000
  * time is shared by every later call in the process.
package/dist/index.d.mts CHANGED
@@ -598,5 +598,43 @@ type InitializeResult = {
598
598
  };
599
599
  export declare function initializeHarnesses({ root, harnesses: preferred, copy, force }: InitializeOptions): InitializeResult;
600
600
  //#endregion
601
- export type { BridgeFinding, BridgeKind, BridgeProblem, BridgeReport, BridgeStatus, ConfigurationFault, ConfigurationProblem, DiagnoseMcpOptions, DiagnoseOptions, DiagnoseResult, DivergenceDirection, DivergenceReport, DoctorProblem, DoctorReport, Harness, HarnessName, HarnessScope, HarnessScopeName, InitializeOptions, InitializeResult, InstructionBridge, InstructionKind, InstructionProblem, InstructionReport, InstructionStatus, McpConfig, McpDirection, McpField, McpProblem, McpServer, McpTransport, Repair };
601
+ //#region src/mcp-inventory/mcp-inventory.d.ts
602
+ /** One configured MCP server, redacted of everything that could carry a credential. */
603
+ type McpServerEntry = {
604
+ harness: HarnessName;
605
+ scope: HarnessScopeName;
606
+ /** The absolute path of the config file this entry was read from. */
607
+ configFile: string;
608
+ /** The name the server is declared under. */
609
+ name: string;
610
+ transport: McpTransport | undefined;
611
+ /** The command's basename, never its full path. */
612
+ command: string | undefined;
613
+ /** `args`, with any credential-bearing `--flag=value` pair dropped. */
614
+ args: readonly string[];
615
+ /** The URL's origin and path, never its query string. */
616
+ url: string | undefined;
617
+ enabled: boolean;
618
+ };
619
+ type ListMcpServersOptions = {
620
+ /** The project directory to read project-scope configuration from. */
621
+ projectDir: string;
622
+ /** Overrides `os.homedir()`, for a test that must not touch the real one. */
623
+ homeDir?: string;
624
+ /** Overrides `process.env`, for a test — read only for `CODEX_HOME` and `COPILOT_HOME`. */
625
+ env?: Readonly<Record<string, string | undefined>>;
626
+ };
627
+ /**
628
+ * Every MCP server configured for a project, across every supported harness and every scope that
629
+ * harness documents — without ever surfacing a credential.
630
+ *
631
+ * Reuses `diagnoseMcp`'s own parsing (`parseTarget`), model (`McpServer`), and redaction
632
+ * (`nonSecretArgs`, and the URL/command trimming this module adds) rather than re-reading or
633
+ * re-parsing any of the supported harnesses' MCP files. A harness with no file present, or one whose
634
+ * file does not parse, contributes no entries; this function reports an inventory, not a diagnosis,
635
+ * so it has nothing to say about why a file is missing or broken.
636
+ */
637
+ export declare function listMcpServers(options: ListMcpServersOptions): McpServerEntry[];
638
+ //#endregion
639
+ export type { BridgeFinding, BridgeKind, BridgeProblem, BridgeReport, BridgeStatus, ConfigurationFault, ConfigurationProblem, DiagnoseMcpOptions, DiagnoseOptions, DiagnoseResult, DivergenceDirection, DivergenceReport, DoctorProblem, DoctorReport, Harness, HarnessName, HarnessScope, HarnessScopeName, InitializeOptions, InitializeResult, InstructionBridge, InstructionKind, InstructionProblem, InstructionReport, InstructionStatus, ListMcpServersOptions, McpConfig, McpDirection, McpField, McpProblem, McpServer, McpServerEntry, McpTransport, Repair };
602
640
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/cli.ts","../src/harness-registry/instruction-bridge.ts","../src/harness-registry/mcp-config.ts","../src/harness-registry/nonstandard-artifact.ts","../src/harness-registry/harness-registry.ts","../src/diagnose-bridges/diagnose-instructions.ts","../src/diagnose-bridges/locator.ts","../src/diagnose-bridges/doctor-guidance.ts","../src/diagnose-bridges/git-bridge-state.ts","../src/diagnose-bridges/diagnose-bridges.ts","../src/diagnose-configuration/diagnose-configuration.ts","../src/diagnose-bridges/doctor.command.ts","../src/diagnose-mcp/diagnose-mcp.ts","../src/diagnose-mcp/mcp-model.ts","../src/diagnose-mcp/mcp-baseline.ts","../src/diagnose-mcp/mcp-sources.ts","../src/initialize-harnesses/init.command.ts","../src/skill-projection/skill-projection.ts","../src/initialize-harnesses/initialize-harnesses.ts"],"mappings":";;;;;;;;;;;;;wBAyCsB,IAAI,iBAAiB;;;;;;;;;;;;;;;KC7B/B;;;;;;EAOT;;EAEA;;;EAIA;;EAEA;;EAEA;;;;;;;;;;;;;;;;;;;;KCbS;;EAEX;;;;;;EAMA;;;;;EAKA;;;;;;EAMA;;;;;;;;;;;;;;;;;KCtBW;KAgBA;;;;;EAKX;;EAEA;EACA,MAAM;;;;KC/BK;;KAUA;;;;;;;;;;;;;;;;;;;;;;;;;KA0BA;EACX;EACA;EACA,oBAAoB;EACpB,YAAY;EACZ,uBAAuB;;KAGZ;EACX,MAAM;;EAEN,SAAS;;;;;EAKT,OAAO;;EAEP,aAAa;;;;;;;;;;;;;;;;;;;;;;qBAuBD,0BAA0B;;;;KCzE3B;;;;;;KAMA;KAEA;EACX,SAAS;;EAET;EACA,MAAM;EACN,QAAQ;;;;;;;;;;wBAkFO,qBACf,cACA,oBAAoB,WACpB;EACI,cAAc;EAAqB,UAAU;;;;;;;;;;;;;;;;;;;;KC5FtC;EAAa;EAAc;;KAE3B;;EAEX;;EAEA;;EAEA;;EAEA,WAAW;;;;;;;;;;KCfA;;;;;;;;;;;KAqBA;;;;;;;;;KAUA;;;;;;;;;KAoBA;KAOA;;KAOA,gBAAgB,gBAAgB,qBAAqB,qBAAqB,aAAa;;KAGvF,uBAAuB;;;;;;;;;;;;;;;;;;KAmBvB;;EAEX;;EAEA;;KAGW;EACX,SAAS;;EAET;;;;;;;EAOA,OAAO,IAAI,SAAS,cAAc;;;;;;EAMlC,YAAY,IAAI;;qBAwKJ,wBAAwB;qBA4CxB,6BAA6B;qBAuM7B,wBAAwB;qBAOxB;WACZ;WACA;;;;;;;;;;wBAmCe,kBAAkB;;;;KC5jBtB;;KAGA;;;;;;cAwBC;mBAIA;;mBAFK;EAEjB,YAAY;;;;;;EASZ,WAAW,eAAe;;;;;;;EAa1B,UAAU;;;;;;;;;EAaV,gBAAgB;;EAMhB,UAAU,gBAAgB;;;;;EAS1B,YAAY,gBAAgB,oBAAoB;;UAYxC;;;;;UAmBA;;;;KC5GG;KACA;KAEA;EACX,SAAS;;EAET;EACA,MAAM;EACN,QAAQ;;KAGG;EACX;;;;;EAKA,SAAS;EACT;;EAEA,QAAQ;;KAGG;EACX;EACA,WAAW;;KAGA;EACX;EACA,YAAY;;EAEZ;;KAGW;EACX,SAAS;;;;;EAKT,cAAc;EACd,YAAY;EACZ,UAAU;;;;;;;wBA2EK,kBAAkB,MAAM,WAAW,WAAgB,OAAO,kBAAkB;;;;;;;;;;;;;;;;;KC1GhF;;EAEX;EACA,SAAS;EACT;;EAEA,QAAQ;;;;KCfG;EACX;EACA,SAAS;EACT,cAAc;EACd,aAAa;;;;;EAKb;IAAY;IAAc,SAAS;IAAe;;;;;;;;;;EASlD,OAAO;;;;;;wBAOQ,kBACf,aACA,QAAQ,gBACR,gBAAe,yBACb;qBAgCU,eAAe,MAAI;;;;;;;;;;;;;;;;;;KC7CpB;EACX;EACA,KAAK;;EAEL;;wBAgCe,cAAc,MAAM,KAAK,OAAO,qBAAqB;;;;;;;;;;;;;;;;KClDzD;KAEA;EACX,YAAY;EACZ;EACA;EACA,MAAM,SAAS;EACf;EACA,UAAU,SAAS;EACnB;EACA;EACA;EACA;;;cAIY;KAaD,mBAAmB;;;;KCnCnB;;;;qBCAC;;;qBCAA,aAAa,MAAI;qBAoDjB,gBAAgB,MAAI;wBAMjB,WAAW;EAAgB,WAAW,gBAAgB;;;;;;;;;KCV1D;;;KCpDA;EACX;EACA,YAAY;EACZ;EACA,QAAQ;;KAEG;EACX;EACA,WAAW;EACX,QAAQ;EACR,QAAQ;;EAER,SAAS;;EAET;IAAc,MAAM;IAAa,YAAY;;EAC7C;EACA;;wBAGe,sBACf,MACA,WAAW,WACX,MACA,SACE,oBAAoB"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/cli.ts","../src/harness-registry/instruction-bridge.ts","../src/harness-registry/mcp-config.ts","../src/harness-registry/nonstandard-artifact.ts","../src/harness-registry/harness-registry.ts","../src/diagnose-bridges/diagnose-instructions.ts","../src/diagnose-bridges/locator.ts","../src/diagnose-bridges/doctor-guidance.ts","../src/diagnose-bridges/git-bridge-state.ts","../src/diagnose-bridges/diagnose-bridges.ts","../src/diagnose-configuration/diagnose-configuration.ts","../src/diagnose-bridges/doctor.command.ts","../src/diagnose-mcp/diagnose-mcp.ts","../src/diagnose-mcp/mcp-model.ts","../src/diagnose-mcp/mcp-baseline.ts","../src/diagnose-mcp/mcp-sources.ts","../src/initialize-harnesses/init.command.ts","../src/skill-projection/skill-projection.ts","../src/initialize-harnesses/initialize-harnesses.ts","../src/mcp-inventory/mcp-inventory.ts"],"mappings":"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"}
package/dist/index.mjs CHANGED
@@ -2,7 +2,7 @@ import { createRequire } from "node:module";
2
2
  import { cpSync, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, readlinkSync, realpathSync, rmSync, statSync, symlinkSync, writeFileSync } from "node:fs";
3
3
  import { cli, command, exitCodes, z } from "clibuilder";
4
4
  import { homedir, platform } from "node:os";
5
- import { dirname, join, posix, relative, resolve, sep } from "node:path";
5
+ import { basename, dirname, join, posix, relative, resolve, sep } from "node:path";
6
6
  import { encode } from "@toon-format/toon";
7
7
  import { parse } from "smol-toml";
8
8
  import { execFileSync } from "node:child_process";
@@ -955,10 +955,10 @@ const skillInvocation = (version) => `npx -y ${commandInvocation}@^${version}`;
955
955
  */
956
956
  const launcherFor = (subcommand) => `scripts/${subcommand}.mjs`;
957
957
  /**
958
- * How a skill names its launcher: the path as the skill sees it, resolved by the agent against the
958
+ * How a skill names its script: the path as the skill sees it, resolved by the agent against the
959
959
  * directory it read the `SKILL.md` from.
960
960
  *
961
- * `node` stays in front. The launcher ships without an executable bit, and its shebang does nothing
961
+ * `node` stays in front. The script ships without an executable bit, and its shebang does nothing
962
962
  * on Windows, so naming the file alone would not run it.
963
963
  */
964
964
  const launcherInvocation = (subcommand) => `node ${launcherFor(subcommand)}`;
@@ -1323,7 +1323,7 @@ Diagnose it:
1323
1323
  ${launcherInvocation("doctor")}
1324
1324
  \`\`\`
1325
1325
 
1326
- That path is relative to this skill's own directory. The launcher runs the CLI that shipped beside it against the current working directory, so nothing is downloaded. Fall back to \`${skillInvocation(version)} doctor\` when the launcher cannot be resolved or run, which is the case when the plugin was installed from git rather than npm and its dependencies were never installed.
1326
+ That path is relative to this skill's own directory: \`scripts/doctor.mjs\` is a self-contained bundle built from this package's source and shipped inside the skill folder through the npm package, so it runs against the current working directory with nothing downloaded and no \`node_modules\` needed. Fall back to \`${skillInvocation(version)} doctor\` when \`scripts/doctor.mjs\` is missing or cannot be run — the case for a skill installed from git rather than from the npm package, which does not carry the bundle.
1327
1327
 
1328
1328
  The command is read-only. It never repairs anything, so it is safe to run at any point, including from a session-start hook.
1329
1329
 
@@ -1831,9 +1831,25 @@ function credentialFields(server) {
1831
1831
  ...server.url && urlHoldsCredential(server.url) ? ["url"] : []
1832
1832
  ];
1833
1833
  }
1834
+ /**
1835
+ * `args` with any `--flag=value` pair dropped whose flag names a credential and whose value is a
1836
+ * literal rather than a reference — the same test `credentialKey` and `holdsLiteral` apply to `env`
1837
+ * and `headers`, applied here to the one field that carries names and values in a single string
1838
+ * instead of a map.
1839
+ *
1840
+ * A positional argument, or a flag with no `=`, passes through unfiltered: `-y` and a package name
1841
+ * are not a place a credential's value is written, and there is no key in either to test.
1842
+ */
1843
+ function nonSecretArgs(args) {
1844
+ return (args ?? []).filter((arg) => {
1845
+ const at = arg.indexOf("=");
1846
+ if (at === -1) return true;
1847
+ return !(credentialKey(arg.slice(0, at)) && holdsLiteral(arg.slice(at + 1)));
1848
+ });
1849
+ }
1834
1850
  //#endregion
1835
1851
  //#region src/diagnose-mcp/diagnose-mcp.ts
1836
- function read(root, path) {
1852
+ function read$1(root, path) {
1837
1853
  const absolute = join(root, path);
1838
1854
  return existsSync(absolute) ? readFileSync(absolute, "utf8") : void 0;
1839
1855
  }
@@ -1884,7 +1900,7 @@ function diagnoseMcp({ root, git, cli }) {
1884
1900
  const targets = /* @__PURE__ */ new Map();
1885
1901
  const configs = [];
1886
1902
  for (const config of targetsOf(root)) {
1887
- const parsed = parseTarget(config, read(root, config.path));
1903
+ const parsed = parseTarget(config, read$1(root, config.path));
1888
1904
  if (parsed.kind === "unreadable") {
1889
1905
  add({ file: config.path }, "mcp-target-unreadable");
1890
1906
  continue;
@@ -1894,7 +1910,7 @@ function diagnoseMcp({ root, git, cli }) {
1894
1910
  targets.set(config.path, parsed.servers);
1895
1911
  configs.push(config);
1896
1912
  }
1897
- const golden = parseGoldenSet(read(root, goldenSetPath));
1913
+ const golden = parseGoldenSet(read$1(root, goldenSetPath));
1898
1914
  if (golden.kind === "unreadable") {
1899
1915
  add({
1900
1916
  file: goldenSetPath,
@@ -1906,7 +1922,7 @@ function diagnoseMcp({ root, git, cli }) {
1906
1922
  reportSecrets(goldenSetPath, golden.servers);
1907
1923
  const baseline = new McpBaseline({
1908
1924
  git,
1909
- record: parseProjectionRecord(read(root, projectionRecordPath))
1925
+ record: parseProjectionRecord(read$1(root, projectionRecordPath))
1910
1926
  });
1911
1927
  for (const config of configs) {
1912
1928
  const servers = targets.get(config.path);
@@ -2690,14 +2706,7 @@ function activate({ addCommand }) {
2690
2706
  }
2691
2707
  //#endregion
2692
2708
  //#region src/cli.ts
2693
- /**
2694
- * Read from the manifest rather than restated here: a version written into this call is a second
2695
- * home for a number that already has one, and it stayed at `0.1.0` for five minor releases.
2696
- *
2697
- * `../package.json` resolves from `src/cli.ts` and from the bundled `dist/cli.mjs` alike — both sit
2698
- * one directory under the package root.
2699
- */
2700
- const version = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version;
2709
+ const version = typeof __PACKAGE_VERSION__ !== "undefined" ? __PACKAGE_VERSION__ : JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version;
2701
2710
  /**
2702
2711
  * A factory rather than a module-level constant: `cli()` builds state, and state built at import
2703
2712
  * time is shared by every later call in the process.
@@ -2730,6 +2739,135 @@ async function run(argv) {
2730
2739
  }
2731
2740
  }
2732
2741
  //#endregion
2733
- export { activate, bridgeRepairs, buildDoctorReport, diagnoseBridges, diagnoseInstructions, diagnoseMcp, doctorCommand, doctorRepairs, doctorSkill, goldenSetPath, harnessCommand, harnessRegistry, initCommand, initializeHarnesses, instructionRepairs, renderDoctorSkill, run };
2742
+ //#region src/mcp-inventory/mcp-inventory.ts
2743
+ /**
2744
+ * A public, redaction-safe inventory of configured MCP servers: what `diagnoseMcp` already knows how
2745
+ * to read, reshaped for a consumer that wants a list rather than a drift diagnosis.
2746
+ *
2747
+ * This is a **different question** than `doctor` answers, not a wider version of it. `doctor`
2748
+ * compares project-scope configuration against a golden set the user authors, and stays
2749
+ * project-scope only by design — see `apps/web/src/content/docs/agent-configuration/mcp-servers.md`
2750
+ * for why reading a user's home directory is a wider blast radius than diagnosis needs. An inventory
2751
+ * has no golden set to compare against and nothing to diagnose; it answers "what MCP servers does
2752
+ * this project, and this machine, already have configured", which is a question a consumer can only
2753
+ * answer by reading both scopes. Adding user scope here changes nothing about what `doctor` reads:
2754
+ * `diagnoseMcp` still touches `harness.project.mcpConfig` alone.
2755
+ *
2756
+ * Every field that could carry a credential is reduced before it leaves this module, the same
2757
+ * discipline `mcp-secrets.ts` documents for `doctor`'s own findings: `command` is trimmed to its
2758
+ * basename, `args` drops any `--flag=value` pair `nonSecretArgs` flags, and `url` is trimmed to its
2759
+ * origin and path. `env` and `headers` are not returned at all — a name alone does not say whether
2760
+ * the value behind it is a literal or a reference, and the safe answer is not to carry the map.
2761
+ */
2762
+ /** Where a harness keeps its **user-scope** MCP configuration, as that harness documents it.
2763
+ *
2764
+ * Unlike `harnessRegistry`, this table is read only from here: it is not wired into `doctor`, and
2765
+ * adding a harness to it never changes what `doctor` reads. Codex and Copilot CLI each let their
2766
+ * whole configuration directory move via an environment variable — `CODEX_HOME` and `COPILOT_HOME`
2767
+ * — so those two resolve through `env` before falling back to the home directory; the rest do not
2768
+ * document one.
2769
+ */
2770
+ function userScopeConfig(harness, home, env) {
2771
+ switch (harness) {
2772
+ case "claude-code": return {
2773
+ file: join(home, ".claude.json"),
2774
+ config: {
2775
+ path: ".claude.json",
2776
+ key: "mcpServers",
2777
+ format: "json"
2778
+ }
2779
+ };
2780
+ case "cursor": return {
2781
+ file: join(home, ".cursor", "mcp.json"),
2782
+ config: {
2783
+ path: ".cursor/mcp.json",
2784
+ key: "mcpServers",
2785
+ format: "json"
2786
+ }
2787
+ };
2788
+ case "codex": return {
2789
+ file: join(env["CODEX_HOME"] || join(home, ".codex"), "config.toml"),
2790
+ config: {
2791
+ path: "config.toml",
2792
+ key: "mcp_servers",
2793
+ format: "toml"
2794
+ }
2795
+ };
2796
+ case "copilot-cli": return {
2797
+ file: join(env["COPILOT_HOME"] || join(home, ".copilot"), "mcp-config.json"),
2798
+ config: {
2799
+ path: "mcp-config.json",
2800
+ key: "mcpServers",
2801
+ format: "json"
2802
+ }
2803
+ };
2804
+ case "gemini-cli": return {
2805
+ file: join(home, ".gemini", "settings.json"),
2806
+ config: {
2807
+ path: ".gemini/settings.json",
2808
+ key: "mcpServers",
2809
+ format: "json",
2810
+ shared: true
2811
+ }
2812
+ };
2813
+ default: return;
2814
+ }
2815
+ }
2816
+ function read(file) {
2817
+ return existsSync(file) ? readFileSync(file, "utf8") : void 0;
2818
+ }
2819
+ /** A URL trimmed to its origin and path, dropping any query string a token could ride in on. */
2820
+ function safeUrl(value) {
2821
+ if (!value) return void 0;
2822
+ try {
2823
+ const url = new URL(value);
2824
+ return `${url.origin}${url.pathname}`;
2825
+ } catch {
2826
+ return;
2827
+ }
2828
+ }
2829
+ function toEntry(harness, scope, file, name, server) {
2830
+ return {
2831
+ harness,
2832
+ scope,
2833
+ configFile: file,
2834
+ name,
2835
+ transport: server.transport,
2836
+ command: server.command ? basename(server.command) : void 0,
2837
+ args: nonSecretArgs(server.args),
2838
+ url: safeUrl(server.url),
2839
+ enabled: server.enabled !== false
2840
+ };
2841
+ }
2842
+ function entriesFrom(harness, scope, file, config) {
2843
+ const parsed = parseTarget(config, read(file));
2844
+ if (parsed.kind !== "servers") return [];
2845
+ return [...parsed.servers].map(([name, server]) => toEntry(harness, scope, file, name, server));
2846
+ }
2847
+ /**
2848
+ * Every MCP server configured for a project, across every supported harness and every scope that
2849
+ * harness documents — without ever surfacing a credential.
2850
+ *
2851
+ * Reuses `diagnoseMcp`'s own parsing (`parseTarget`), model (`McpServer`), and redaction
2852
+ * (`nonSecretArgs`, and the URL/command trimming this module adds) rather than re-reading or
2853
+ * re-parsing any of the supported harnesses' MCP files. A harness with no file present, or one whose
2854
+ * file does not parse, contributes no entries; this function reports an inventory, not a diagnosis,
2855
+ * so it has nothing to say about why a file is missing or broken.
2856
+ */
2857
+ function listMcpServers(options) {
2858
+ const home = options.homeDir ?? homedir();
2859
+ const env = options.env ?? process.env;
2860
+ const entries = [];
2861
+ for (const harness of harnessRegistry) {
2862
+ if (harness.deprecated) continue;
2863
+ const project = harness.project.mcpConfig;
2864
+ if (project) entries.push(...entriesFrom(harness.name, "project", join(options.projectDir, project.path), project));
2865
+ const user = userScopeConfig(harness.name, home, env);
2866
+ if (user) entries.push(...entriesFrom(harness.name, "user", user.file, user.config));
2867
+ }
2868
+ return entries;
2869
+ }
2870
+ //#endregion
2871
+ export { activate, bridgeRepairs, buildDoctorReport, diagnoseBridges, diagnoseInstructions, diagnoseMcp, doctorCommand, doctorRepairs, doctorSkill, goldenSetPath, harnessCommand, harnessRegistry, initCommand, initializeHarnesses, instructionRepairs, listMcpServers, renderDoctorSkill, run };
2734
2872
 
2735
2873
  //# sourceMappingURL=index.mjs.map