@kyo-so/cli 0.13.1 → 0.14.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/bin/kyoso.js CHANGED
@@ -183951,7 +183951,7 @@ var JUDGE_MAX_OUTPUT_TOKENS = 4096;
183951
183951
  var RAW_OUTPUT_MAX_CHARS = 16384;
183952
183952
  var TRACE_DIR = ".kyoso/traces";
183953
183953
  var KYOSO_CHILD_AGENT = "KYOSO_CHILD_AGENT";
183954
- var KYOSO_VERSION = "0.13.1";
183954
+ var KYOSO_VERSION = "0.14.0";
183955
183955
 
183956
183956
  // src/utils/pathContainment.ts
183957
183957
  import { resolve, sep as sep2 } from "node:path";
@@ -187092,13 +187092,14 @@ function isPlainObject2(value) {
187092
187092
  import { spawnSync } from "node:child_process";
187093
187093
 
187094
187094
  // src/cli/pluginRuntimeContract.ts
187095
- var PLUGIN_RUNTIME_COMPATIBILITY_SCHEMA_VERSION = 1;
187095
+ var PLUGIN_RUNTIME_COMPATIBILITY_SCHEMA_VERSION = 2;
187096
187096
  var MINIMUM_SUPPORTED_CODEX_VERSION = "0.144.0-alpha.4";
187097
187097
  var PLUGIN_RUNTIME_EXPECTED_CONTRACT = {
187098
187098
  distribution: {
187099
- pluginVersion: "0.7.0",
187099
+ pluginVersion: "0.7.2",
187100
187100
  mcpCommand: "npx",
187101
- mcpPackagePin: "@kyo-so/cli@0.13.0"
187101
+ mcpPackagePin: "@kyo-so/cli@0.13.1",
187102
+ mcpExecutable: "kyoso"
187102
187103
  },
187103
187104
  marketplace: {
187104
187105
  name: "kyoso",
@@ -187501,21 +187502,66 @@ import {
187501
187502
  relative,
187502
187503
  resolve as resolve5
187503
187504
  } from "node:path";
187505
+
187506
+ // src/cli/packageRunner.ts
187507
+ var KYOSO_PACKAGE_NAME = "@kyo-so/cli";
187508
+ var KYOSO_EXECUTABLE_NAME = "kyoso";
187509
+ var COMPLETE_SEMVER = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
187510
+ function buildKyosoPackageCommand(options) {
187511
+ const packageSpec = options.version === undefined ? KYOSO_PACKAGE_NAME : `${KYOSO_PACKAGE_NAME}@${assertCompleteSemVer(options.version)}`;
187512
+ const cliArgs = [...options.cliArgs];
187513
+ if (options.runner === "npx") {
187514
+ return {
187515
+ command: "npx",
187516
+ args: [
187517
+ "-y",
187518
+ `--package=${packageSpec}`,
187519
+ KYOSO_EXECUTABLE_NAME,
187520
+ ...cliArgs
187521
+ ]
187522
+ };
187523
+ }
187524
+ return {
187525
+ command: "bunx",
187526
+ args: ["--package", packageSpec, KYOSO_EXECUTABLE_NAME, ...cliArgs]
187527
+ };
187528
+ }
187529
+ function formatKyosoPackageCommand(options) {
187530
+ const command = buildKyosoPackageCommand(options);
187531
+ return [command.command, ...command.args].join(" ");
187532
+ }
187533
+ function isCompleteSemVer(value) {
187534
+ const match = COMPLETE_SEMVER.exec(value);
187535
+ const prerelease = match?.[4];
187536
+ return match !== null && (prerelease === undefined || prerelease.split(".").every((identifier) => {
187537
+ return !/^\d+$/.test(identifier) || identifier === "0" || !identifier.startsWith("0");
187538
+ }));
187539
+ }
187540
+ function assertCompleteSemVer(value) {
187541
+ if (isCompleteSemVer(value))
187542
+ return value;
187543
+ throw new Error(`Kyoso package version must be a complete SemVer; received ${JSON.stringify(value)}.`);
187544
+ }
187545
+
187546
+ // src/cli/integration.ts
187504
187547
  function detectCli(options) {
187505
187548
  const env = options.env ?? process.env;
187506
187549
  const platform = options.platform ?? process.platform;
187507
187550
  return {
187508
187551
  kyoso: detectInstalledKyoso(options.cwd, env, platform),
187509
- npx: commandExists("npx", env, platform),
187510
- bunx: commandExists("bunx", env, platform)
187552
+ npx: commandExists("npx", env, platform) ? "available" : "missing",
187553
+ bunx: commandExists("bunx", env, platform) ? "present-unverified" : "missing"
187511
187554
  };
187512
187555
  }
187513
187556
  function determineNonPluginIntegration(options) {
187514
- const warnings = manualMcpWarnings(options.manualMcpStatus);
187557
+ const warnings = manualMcpWarnings(options.manualMcpStatus, options.manualMcpRegistrations, options.cli);
187515
187558
  if (options.manualMcpStatus === "unknown") {
187516
187559
  return { mode: "unknown", warnings };
187517
187560
  }
187518
187561
  if (options.manualMcpStatus === "enabled") {
187562
+ if (!hasCurrentManualMcp(options.manualMcpRegistrations, options.cli)) {
187563
+ return { mode: "unknown", warnings };
187564
+ }
187519
187565
  return {
187520
187566
  mode: options.hasSkill ? "manual-mcp" : "mcp-only",
187521
187567
  warnings
@@ -187527,10 +187573,13 @@ function determineNonPluginIntegration(options) {
187527
187573
  if (options.cli.kyoso.kind === "installed") {
187528
187574
  return { mode: "cli-skill", warnings };
187529
187575
  }
187530
- if (options.cli.npx || options.cli.bunx) {
187576
+ if (options.cli.npx === "available") {
187531
187577
  warnings.push("Package-runner fallback may require network access and can drift between versions.");
187532
187578
  return { mode: "skill-on-demand", warnings };
187533
187579
  }
187580
+ if (options.cli.bunx === "present-unverified") {
187581
+ warnings.push("bunx is present but unverified; install Kyoso on PATH or npx first, then run the client-specific setup with --write --runner bunx to verify Bun 1.3.14 or newer.");
187582
+ }
187534
187583
  if (options.cli.kyoso.kind === "unknown") {
187535
187584
  return { mode: "unknown", warnings };
187536
187585
  }
@@ -187545,6 +187594,13 @@ function determineNonPluginIntegration(options) {
187545
187594
  }
187546
187595
  return { mode: "missing", warnings };
187547
187596
  }
187597
+ function formatRunnerAvailability(runner) {
187598
+ if (runner === "available")
187599
+ return "available";
187600
+ if (runner === "present-unverified")
187601
+ return "present-unverified";
187602
+ return "missing";
187603
+ }
187548
187604
  function formatCliAvailability(cli) {
187549
187605
  if (cli.kind === "installed") {
187550
187606
  return `installed @kyo-so/cli ${cli.version} (${cli.scope})`;
@@ -187609,7 +187665,9 @@ function realPathOrResolved(path) {
187609
187665
  return resolve5(path);
187610
187666
  }
187611
187667
  }
187612
- function manualMcpWarnings(status) {
187668
+ function manualMcpWarnings(status, registrations, cli) {
187669
+ if (status === "missing")
187670
+ return [];
187613
187671
  if (status === "disabled")
187614
187672
  return ["Manual MCP registration is disabled."];
187615
187673
  if (status === "unknown") {
@@ -187617,8 +187675,89 @@ function manualMcpWarnings(status) {
187617
187675
  "Manual MCP registration could not be safely classified from its configuration."
187618
187676
  ];
187619
187677
  }
187678
+ if (registrations.length === 0) {
187679
+ return [
187680
+ "Manual MCP registration is enabled but no exact registration could be verified."
187681
+ ];
187682
+ }
187683
+ if (registrations.length !== 1) {
187684
+ return [
187685
+ "Multiple manual MCP registrations were found; their effective precedence is not inferred."
187686
+ ];
187687
+ }
187688
+ const registration = registrations[0];
187689
+ const invocation = registration?.invocation;
187690
+ if (invocation?.kind === "legacy") {
187691
+ return [legacyManualMcpRepairWarning(registration, cli)];
187692
+ }
187693
+ if (invocation?.kind === "custom") {
187694
+ return [
187695
+ "Manual MCP registration is custom/unverified and was not treated as a ready Kyoso registration."
187696
+ ];
187697
+ }
187698
+ if (invocation?.kind === "unknown") {
187699
+ return [
187700
+ "Manual MCP invocation could not be safely classified and was not treated as a ready Kyoso registration."
187701
+ ];
187702
+ }
187703
+ if (invocation?.runner === "npx" && cli.npx !== "available") {
187704
+ return [
187705
+ "Manual MCP registration uses npx, but npx is not available on PATH."
187706
+ ];
187707
+ }
187708
+ if (invocation?.runner === "bunx") {
187709
+ if (cli.bunx === "missing") {
187710
+ return [
187711
+ "Manual MCP registration uses bunx, but bunx is not available on PATH."
187712
+ ];
187713
+ }
187714
+ const verificationCommand = registration ? manualMcpSetupCommand(registration, cli, [
187715
+ "--write",
187716
+ "--runner",
187717
+ "bunx"
187718
+ ]) : undefined;
187719
+ return [
187720
+ `Manual MCP registration uses bunx, but normal doctor does not verify the required Bun capability.${verificationCommand ? ` Run \`${verificationCommand}\` before treating it as ready.` : " Run setup with --write --runner bunx from an installed Kyoso CLI before treating it as ready."}`
187721
+ ];
187722
+ }
187620
187723
  return [];
187621
187724
  }
187725
+ function legacyManualMcpRepairWarning(registration, cli) {
187726
+ if (!registration) {
187727
+ return "Manual MCP registration uses legacy package-runner arguments and requires repair.";
187728
+ }
187729
+ const client = registration.scope === "codex-global" ? "codex" : registration.scope === "claude-project" ? "claude-code" : undefined;
187730
+ if (!client) {
187731
+ return `Manual MCP registration at ${registration.path} uses legacy package-runner arguments. Its ${registration.scope} scope is not automatically migrated; update it manually.`;
187732
+ }
187733
+ const runner = cli.npx === "available" ? "npx" : cli.kyoso.kind === "installed" && cli.bunx === "present-unverified" ? "bunx" : undefined;
187734
+ const command = runner ? manualMcpSetupCommand(registration, cli, [
187735
+ "--write",
187736
+ "--runner",
187737
+ runner,
187738
+ "--force"
187739
+ ]) : undefined;
187740
+ if (!command) {
187741
+ return `Manual MCP registration at ${registration.path} uses legacy package-runner arguments, but no executable Kyoso repair path is available. Update it manually.`;
187742
+ }
187743
+ return `Manual MCP registration uses legacy package-runner arguments. Run \`${command}\` to migrate this exact registration.`;
187744
+ }
187745
+ function manualMcpSetupCommand(registration, cli, setupArgs) {
187746
+ const client = registration.scope === "codex-global" ? "codex" : registration.scope === "claude-project" ? "claude-code" : undefined;
187747
+ if (!client)
187748
+ return;
187749
+ const cliArgs = ["setup", client, ...setupArgs];
187750
+ if (cli.kyoso.kind === "installed") {
187751
+ return `kyoso ${cliArgs.join(" ")}`;
187752
+ }
187753
+ if (cli.npx !== "available")
187754
+ return;
187755
+ return formatKyosoPackageCommand({ runner: "npx", cliArgs });
187756
+ }
187757
+ function hasCurrentManualMcp(registrations, cli) {
187758
+ const registration = registrations[0];
187759
+ return registrations.length === 1 && registration?.invocation.kind === "current" && registration.invocation.runner === "npx" && cli.npx === "available";
187760
+ }
187622
187761
  function cliIdentityWarnings(cli) {
187623
187762
  if (cli.kind === "transient") {
187624
187763
  return [
@@ -187677,12 +187816,206 @@ function isRecord8(value) {
187677
187816
 
187678
187817
  // src/cli/setup.ts
187679
187818
  import { spawnSync as spawnSync2 } from "node:child_process";
187680
- import { existsSync as existsSync2, readFileSync as readFileSync2, realpathSync as realpathSync2 } from "node:fs";
187681
- import { mkdir as mkdir5, readFile as readFile6, writeFile as writeFile4 } from "node:fs/promises";
187682
- import { homedir as homedir3 } from "node:os";
187683
- import { delimiter as delimiter2, dirname as dirname8, join as join6, resolve as resolve7 } from "node:path";
187819
+ import { randomUUID as randomUUID2 } from "node:crypto";
187820
+ import {
187821
+ existsSync as existsSync2,
187822
+ readFileSync as readFileSync2,
187823
+ realpathSync as realpathSync2,
187824
+ mkdtempSync,
187825
+ rmSync
187826
+ } from "node:fs";
187827
+ import {
187828
+ chmod,
187829
+ lstat as lstat3,
187830
+ mkdir as mkdir5,
187831
+ open,
187832
+ readFile as readFile6,
187833
+ rename as rename2,
187834
+ rm as rm2,
187835
+ writeFile as writeFile4
187836
+ } from "node:fs/promises";
187837
+ import { homedir as homedir3, tmpdir as tmpdir2 } from "node:os";
187838
+ import { basename as basename3, delimiter as delimiter2, dirname as dirname8, join as join6, resolve as resolve7 } from "node:path";
187684
187839
  import { fileURLToPath } from "node:url";
187685
187840
 
187841
+ // src/cli/manualMcpInvocation.ts
187842
+ var GENERATED_MCP_ENV_VALUE_NAMES = new Set([
187843
+ "OPENAI_API_KEY",
187844
+ "ANTHROPIC_API_KEY",
187845
+ "CLAUDE_CODE_OAUTH_TOKEN",
187846
+ "OPENROUTER_API_KEY"
187847
+ ]);
187848
+ var GENERATED_MCP_ENV_VAR_NAMES = new Set([
187849
+ "OPENAI_API_KEY",
187850
+ "CODEX_API_KEY",
187851
+ "CODEX_HOME",
187852
+ "CODEX_ACCESS_TOKEN",
187853
+ "OPENROUTER_API_KEY",
187854
+ "ANTHROPIC_API_KEY",
187855
+ "CLAUDE_CODE_OAUTH_TOKEN"
187856
+ ]);
187857
+ function inspectManualMcpInvocation(value) {
187858
+ if (!isRecord9(value)) {
187859
+ return { kind: "unknown", reason: "MCP entry is not an object." };
187860
+ }
187861
+ if (typeof value.command !== "string" || value.command.length === 0) {
187862
+ return {
187863
+ kind: "unknown",
187864
+ reason: "MCP command is missing or is not a string."
187865
+ };
187866
+ }
187867
+ if (!Array.isArray(value.args) || !value.args.every(isString)) {
187868
+ return {
187869
+ kind: "unknown",
187870
+ reason: "MCP args are missing or are not an array of strings."
187871
+ };
187872
+ }
187873
+ const environment = inspectMcpEnvironment(value);
187874
+ if (value.command === "npx") {
187875
+ return applyEnvironmentSafety(inspectNpx(value.args), environment);
187876
+ }
187877
+ if (value.command === "bunx") {
187878
+ return applyEnvironmentSafety(inspectBunx(value.args), environment);
187879
+ }
187880
+ return {
187881
+ kind: "custom",
187882
+ reason: `MCP command ${JSON.stringify(value.command)} is not a Kyoso package runner.`
187883
+ };
187884
+ }
187885
+ function inspectMcpEnvironment(value) {
187886
+ if ("env" in value && !isGeneratedMcpEnvironment(value.env)) {
187887
+ return {
187888
+ kind: "custom",
187889
+ reason: "MCP environment is not limited to generated credential placeholders."
187890
+ };
187891
+ }
187892
+ if ("env_vars" in value && !isGeneratedMcpEnvironmentVariables(value.env_vars)) {
187893
+ return {
187894
+ kind: "custom",
187895
+ reason: "MCP environment variable forwarding is not limited to generated credential names."
187896
+ };
187897
+ }
187898
+ return;
187899
+ }
187900
+ function applyEnvironmentSafety(invocation, environment) {
187901
+ return environment ?? invocation;
187902
+ }
187903
+ function isGeneratedMcpEnvironment(value) {
187904
+ if (!isRecord9(value))
187905
+ return false;
187906
+ return Object.entries(value).every(([name, placeholder]) => GENERATED_MCP_ENV_VALUE_NAMES.has(name) && placeholder === `\${${name}}`);
187907
+ }
187908
+ function isGeneratedMcpEnvironmentVariables(value) {
187909
+ if (!Array.isArray(value) || !value.every(isString))
187910
+ return false;
187911
+ return new Set(value).size === value.length && value.every((name) => GENERATED_MCP_ENV_VAR_NAMES.has(name));
187912
+ }
187913
+ function inspectNpx(args) {
187914
+ const current = parseNpxCurrent(args);
187915
+ if (current) {
187916
+ return {
187917
+ kind: "current",
187918
+ runner: "npx",
187919
+ packageSpec: current.packageSpec,
187920
+ reason: "npx explicitly selects the Kyoso executable from its package."
187921
+ };
187922
+ }
187923
+ const legacy = parseNpxLegacy(args);
187924
+ if (legacy)
187925
+ return legacyInspection("npx", legacy, args);
187926
+ return {
187927
+ kind: "custom",
187928
+ runner: "npx",
187929
+ reason: "npx arguments do not exactly match a supported Kyoso invocation."
187930
+ };
187931
+ }
187932
+ function inspectBunx(args) {
187933
+ const current = parseBunxCurrent(args);
187934
+ if (current) {
187935
+ return {
187936
+ kind: "current",
187937
+ runner: "bunx",
187938
+ packageSpec: current.packageSpec,
187939
+ reason: "bunx explicitly selects the Kyoso executable from its package."
187940
+ };
187941
+ }
187942
+ const legacy = parseBunxLegacy(args);
187943
+ if (legacy)
187944
+ return legacyInspection("bunx", legacy, args);
187945
+ return {
187946
+ kind: "custom",
187947
+ runner: "bunx",
187948
+ reason: "bunx arguments do not exactly match a supported Kyoso invocation."
187949
+ };
187950
+ }
187951
+ function legacyInspection(runner, packageSpec, legacyArgs) {
187952
+ const version2 = versionFromKnownPackageSpec(packageSpec);
187953
+ if (version2 === undefined && packageSpec !== KYOSO_PACKAGE_NAME) {
187954
+ return {
187955
+ kind: "custom",
187956
+ runner,
187957
+ packageSpec,
187958
+ reason: "Kyoso package spec is a tag, range, or malformed pin and is preserved."
187959
+ };
187960
+ }
187961
+ return {
187962
+ kind: "legacy",
187963
+ runner,
187964
+ packageSpec,
187965
+ legacyArgs: [...legacyArgs],
187966
+ replacement: buildKyosoPackageCommand({
187967
+ runner,
187968
+ ...version2 === undefined ? {} : { version: version2 },
187969
+ cliArgs: ["mcp"]
187970
+ }),
187971
+ reason: "Kyoso package runner relies on executable inference with a multi-bin package."
187972
+ };
187973
+ }
187974
+ function parseNpxCurrent(args) {
187975
+ if (args.length !== 4 || args[0] !== "-y" || args[2] !== KYOSO_EXECUTABLE_NAME || args[3] !== "mcp") {
187976
+ return;
187977
+ }
187978
+ const packageSpec = args[1]?.startsWith("--package=") ? args[1].slice("--package=".length) : undefined;
187979
+ return packageSpec && isKnownPackageSpec(packageSpec) ? { packageSpec } : undefined;
187980
+ }
187981
+ function parseBunxCurrent(args) {
187982
+ if (args.length !== 4 || args[0] !== "--package" || args[2] !== KYOSO_EXECUTABLE_NAME || args[3] !== "mcp") {
187983
+ return;
187984
+ }
187985
+ const packageSpec = args[1];
187986
+ return packageSpec && isKnownPackageSpec(packageSpec) ? { packageSpec } : undefined;
187987
+ }
187988
+ function parseNpxLegacy(args) {
187989
+ const packageIndex = args[0] === "-y" ? 1 : 0;
187990
+ if (args.length !== packageIndex + 2 || args[packageIndex + 1] !== "mcp") {
187991
+ return;
187992
+ }
187993
+ const packageSpec = args[packageIndex];
187994
+ return packageSpec && packageSpec.startsWith(KYOSO_PACKAGE_NAME) ? packageSpec : undefined;
187995
+ }
187996
+ function parseBunxLegacy(args) {
187997
+ if (args.length !== 2 || args[1] !== "mcp")
187998
+ return;
187999
+ const packageSpec = args[0];
188000
+ return packageSpec && packageSpec.startsWith(KYOSO_PACKAGE_NAME) ? packageSpec : undefined;
188001
+ }
188002
+ function isKnownPackageSpec(packageSpec) {
188003
+ return packageSpec === KYOSO_PACKAGE_NAME || versionFromKnownPackageSpec(packageSpec) !== undefined;
188004
+ }
188005
+ function versionFromKnownPackageSpec(packageSpec) {
188006
+ const prefix = `${KYOSO_PACKAGE_NAME}@`;
188007
+ if (!packageSpec.startsWith(prefix))
188008
+ return;
188009
+ const version2 = packageSpec.slice(prefix.length);
188010
+ return isCompleteSemVer(version2) ? version2 : undefined;
188011
+ }
188012
+ function isRecord9(value) {
188013
+ return typeof value === "object" && value !== null && !Array.isArray(value);
188014
+ }
188015
+ function isString(value) {
188016
+ return typeof value === "string";
188017
+ }
188018
+
187686
188019
  // src/cli/skillInstall.ts
187687
188020
  import { createHash as createHash3, randomUUID } from "node:crypto";
187688
188021
  import {
@@ -187709,8 +188042,14 @@ import {
187709
188042
  } from "node:path";
187710
188043
 
187711
188044
  // src/cli/knownSkillDigests.ts
187712
- var CURRENT_SKILL_DIGEST = "sha256:8654e68ea61f2acea29027056802bf627ad737f084c9a86ab052946943538409";
188045
+ var CURRENT_SKILL_DIGEST = "sha256:d28acaadf490df9e58e12f195804f181a683d0d33344275d7989efbee26a4504";
187713
188046
  var KNOWN_SKILL_DIGESTS_BY_VERSION = {
188047
+ "0.13.1": [
188048
+ {
188049
+ digest: "sha256:8654e68ea61f2acea29027056802bf627ad737f084c9a86ab052946943538409",
188050
+ kind: "historical"
188051
+ }
188052
+ ],
187714
188053
  "0.11.0": [
187715
188054
  {
187716
188055
  digest: "sha256:110dd872a3d1c8a71474a0eadb226f51a6addd86f9d4f3ed17b73678b3179a4e",
@@ -188270,7 +188609,15 @@ async function runSetup(options) {
188270
188609
  skillOnly: options.skillOnly ?? false,
188271
188610
  force: options.force ?? false,
188272
188611
  withOpenRouter: options.withOpenRouter ?? false,
188612
+ customCommand: options.command !== undefined,
188613
+ runnerExplicit: options.runner !== undefined,
188273
188614
  mcpCommand: command,
188615
+ bunxVersionProbe: options.bunxVersionProbe ?? probeBunxVersion,
188616
+ beforeManualMcpWrite: options.beforeManualMcpWrite,
188617
+ beforeManualMcpCommit: options.beforeManualMcpCommit,
188618
+ afterManualMcpValidation: options.afterManualMcpValidation,
188619
+ beforeManualMcpRename: options.beforeManualMcpRename,
188620
+ manualMcpRename: options.manualMcpRename ?? rename2,
188274
188621
  sourceSkillDir: resolveBundledSkillDir()
188275
188622
  };
188276
188623
  if (!client)
@@ -188281,10 +188628,7 @@ async function runSetup(options) {
188281
188628
  return renderResults(context, await setupClaudeCode(context));
188282
188629
  }
188283
188630
  function commandForRunner(runner) {
188284
- if (runner === "bunx") {
188285
- return { command: "bunx", args: ["@kyo-so/cli", "mcp"] };
188286
- }
188287
- return { command: "npx", args: ["-y", "@kyo-so/cli", "mcp"] };
188631
+ return buildKyosoPackageCommand({ runner, cliArgs: ["mcp"] });
188288
188632
  }
188289
188633
  function buildCodexMcpToml(command, withOpenRouter = false) {
188290
188634
  const envVars = [
@@ -188380,16 +188724,18 @@ function detectSetup(options) {
188380
188724
  ]);
188381
188725
  return {
188382
188726
  codex: {
188383
- mcp: codexMcp.status === "enabled",
188727
+ mcp: isCurrentManualMcp(codexMcp),
188384
188728
  skill: codexSkillPaths.length > 0,
188385
188729
  manualMcpStatus: codexMcp.status,
188730
+ manualMcpRegistrations: codexMcp.registrations,
188386
188731
  mcpPaths: codexMcp.paths,
188387
188732
  skillPaths: codexSkillPaths
188388
188733
  },
188389
188734
  "claude-code": {
188390
- mcp: claudeMcp.status === "enabled",
188735
+ mcp: isCurrentManualMcp(claudeMcp),
188391
188736
  skill: claudeSkillPaths.length > 0,
188392
188737
  manualMcpStatus: claudeMcp.status,
188738
+ manualMcpRegistrations: claudeMcp.registrations,
188393
188739
  mcpPaths: claudeMcp.paths,
188394
188740
  skillPaths: claudeSkillPaths
188395
188741
  }
@@ -188425,14 +188771,19 @@ async function ensureCodexMcp(context) {
188425
188771
  const configPath = join6(context.codexHome, "config.toml");
188426
188772
  const snippet = buildCodexMcpToml(context.mcpCommand, context.withOpenRouter);
188427
188773
  const current = await readOptionalFile(configPath);
188428
- if (hasCodexMcpContent(current)) {
188774
+ const existing = inspectCodexMcpContent(current, configPath, context.cwd, context.home);
188775
+ if (existing) {
188776
+ return ensureExistingCodexMcp(context, current, existing);
188777
+ }
188778
+ const appendSafety = inspectCodexAppendSafety(current, context.cwd, context.home);
188779
+ if (!appendSafety.ok) {
188429
188780
  return {
188430
188781
  kind: "mcp",
188431
- registration: "preserved",
188782
+ registration: "blocked",
188432
188783
  title: "Codex MCP",
188433
- status: "skipped",
188784
+ status: "conflict",
188434
188785
  path: configPath,
188435
- detail: codexMcpStatusFromContent(current) === "disabled" ? disabledCodexMcpDetail(configPath) : "existing [mcp_servers.kyoso] kept"
188786
+ detail: appendSafety.detail
188436
188787
  };
188437
188788
  }
188438
188789
  const detail = diffForAppend(configPath, snippet);
@@ -188443,9 +188794,18 @@ async function ensureCodexMcp(context) {
188443
188794
  title: "Codex MCP",
188444
188795
  status: "dry-run",
188445
188796
  path: configPath,
188446
- detail
188797
+ detail: `${detail}${bunxVerificationPendingDetail(context, context.mcpCommand)}`
188447
188798
  };
188448
188799
  }
188800
+ const unsupportedBunx = unsupportedBunxResult(context, "Codex MCP", configPath, context.mcpCommand);
188801
+ if (unsupportedBunx)
188802
+ return unsupportedBunx;
188803
+ if (context.bunxProbe?.status === "verified") {
188804
+ const latest = await readOptionalFile(configPath);
188805
+ if (latest !== current) {
188806
+ return migrationConflictResult("Codex MCP", configPath, "Codex MCP config changed while bunx verification; it was not overwritten.");
188807
+ }
188808
+ }
188449
188809
  const separator = current.length > 0 && !current.endsWith(`
188450
188810
  `) ? `
188451
188811
 
@@ -188466,18 +188826,36 @@ async function ensureClaudeMcp(context) {
188466
188826
  return ensureClaudeGlobalMcp(context);
188467
188827
  }
188468
188828
  const configPath = join6(context.cwd, ".mcp.json");
188469
- const current = await readJsonObject(configPath);
188470
- const mcpServers = recordValue(current.mcpServers);
188471
- if (isRecord9(mcpServers.kyoso)) {
188829
+ const userConfig = detectClaudeMcp(join6(context.home, ".claude.json"), context.cwd, context.home);
188830
+ const applicableUserRegistrations = userConfig.registrations.filter((registration) => registration.status !== "disabled");
188831
+ if (applicableUserRegistrations.length > 0) {
188832
+ return claudeProjectMcpScopeConflictResult(configPath, {
188833
+ ...userConfig,
188834
+ registrations: applicableUserRegistrations,
188835
+ paths: [
188836
+ ...new Set(applicableUserRegistrations.map((registration) => registration.path))
188837
+ ]
188838
+ });
188839
+ }
188840
+ const content = await readOptionalFile(configPath);
188841
+ let current;
188842
+ try {
188843
+ current = parseJsonObject(configPath, content);
188844
+ } catch (error51) {
188472
188845
  return {
188473
188846
  kind: "mcp",
188474
- registration: "preserved",
188847
+ registration: "blocked",
188475
188848
  title: "Claude Code MCP",
188476
- status: "skipped",
188849
+ status: "conflict",
188477
188850
  path: configPath,
188478
- detail: mcpEntryStatus(mcpServers.kyoso) === "disabled" ? disabledClaudeMcpDetail(configPath) : "existing mcpServers.kyoso kept"
188851
+ detail: `Claude Code MCP config could not be parsed and was left unchanged: ${error51 instanceof Error ? error51.message : String(error51)}`
188479
188852
  };
188480
188853
  }
188854
+ const mcpServers = recordValue(current.mcpServers);
188855
+ const existing = inspectClaudeProjectMcp(current, configPath);
188856
+ if (existing) {
188857
+ return ensureExistingClaudeProjectMcp(context, content, existing);
188858
+ }
188481
188859
  const next = {
188482
188860
  ...current,
188483
188861
  mcpServers: {
@@ -188493,9 +188871,19 @@ async function ensureClaudeMcp(context) {
188493
188871
  title: "Claude Code MCP",
188494
188872
  status: "dry-run",
188495
188873
  path: configPath,
188496
- detail
188874
+ detail: `${detail}${bunxVerificationPendingDetail(context, context.mcpCommand)}`
188497
188875
  };
188498
188876
  }
188877
+ const unsupportedBunx = unsupportedBunxResult(context, "Claude Code MCP", configPath, context.mcpCommand);
188878
+ if (unsupportedBunx)
188879
+ return unsupportedBunx;
188880
+ if (context.bunxProbe?.status === "verified") {
188881
+ const latest = await readOptionalFile(configPath);
188882
+ if (latest !== content) {
188883
+ return migrationConflictResult("Claude Code MCP", configPath, "Claude Code MCP config changed while bunx verification; it was not overwritten.");
188884
+ }
188885
+ }
188886
+ await mkdir5(dirname8(configPath), { recursive: true });
188499
188887
  await writeFile4(configPath, `${JSON.stringify(next, null, 2)}
188500
188888
  `, "utf8");
188501
188889
  return {
@@ -188507,6 +188895,166 @@ async function ensureClaudeMcp(context) {
188507
188895
  detail
188508
188896
  };
188509
188897
  }
188898
+ async function ensureExistingCodexMcp(context, current, existing) {
188899
+ const currentBunxVerification = verifyCurrentBunxRegistration(context, "Codex MCP", existing);
188900
+ if (currentBunxVerification)
188901
+ return currentBunxVerification;
188902
+ if (existing.invocation.kind !== "legacy") {
188903
+ return preservedMcpResult("Codex MCP", existing, codexPreservedDetail(existing));
188904
+ }
188905
+ const replacement = migrationReplacementForContext(context, existing.invocation);
188906
+ if (!replacement) {
188907
+ return preservedMcpResult("Codex MCP", existing, codexPreservedDetail(existing));
188908
+ }
188909
+ const detail = legacyMigrationDetail(existing.path, existing.invocation, replacement);
188910
+ if (!context.write) {
188911
+ return {
188912
+ kind: "mcp",
188913
+ registration: "preserved",
188914
+ title: "Codex MCP",
188915
+ status: "dry-run",
188916
+ path: existing.path,
188917
+ detail: `${detail}${bunxVerificationPendingDetail(context, replacement)}`
188918
+ };
188919
+ }
188920
+ if (!context.force || context.customCommand) {
188921
+ return preservedMcpResult("Codex MCP", existing, `${detail}
188922
+ Legacy registration was kept. Re-run with --write --force to migrate this exact invocation.`);
188923
+ }
188924
+ if (requiresExplicitBunxRunnerForMigration(context, replacement)) {
188925
+ return preservedMcpResult("Codex MCP", existing, `${detail}
188926
+ Legacy Bun registration was kept. Re-run with --write --runner bunx --force to verify and migrate this exact invocation, or with --runner npx --force to migrate it using npx.`);
188927
+ }
188928
+ const unsupportedBunx = unsupportedBunxResult(context, "Codex MCP", existing.path, replacement, { migration: true });
188929
+ if (unsupportedBunx)
188930
+ return unsupportedBunx;
188931
+ const safety = await inspectMigrationFile(existing.path);
188932
+ if (!safety.ok) {
188933
+ return preservedMcpResult("Codex MCP", existing, `${detail}
188934
+ ${safety.detail}`);
188935
+ }
188936
+ if (context.beforeManualMcpWrite) {
188937
+ await context.beforeManualMcpWrite(existing.path);
188938
+ }
188939
+ const latest = await readOptionalFile(existing.path);
188940
+ if (latest !== current) {
188941
+ return migrationConflictResult("Codex MCP", existing.path, "Codex MCP config changed after inspection; it was not overwritten.");
188942
+ }
188943
+ const next = patchCodexLegacyInvocation(latest, replacement);
188944
+ if (!next) {
188945
+ return preservedMcpResult("Codex MCP", existing, `${detail}
188946
+ The TOML shape is not a safe single-line legacy target; migrate it manually.`);
188947
+ }
188948
+ const verified = inspectCodexMcpContent(next, existing.path, context.cwd, context.home);
188949
+ if (!isCurrentMcpCommand(verified?.invocation, replacement)) {
188950
+ return preservedMcpResult("Codex MCP", existing, `${detail}
188951
+ The proposed TOML patch could not be verified; it was not written.`);
188952
+ }
188953
+ try {
188954
+ await writeFileAtomically(existing.path, next, safety.safety, latest, {
188955
+ beforeCommit: context.beforeManualMcpCommit,
188956
+ afterExpectedContentsCheck: context.afterManualMcpValidation,
188957
+ beforeRename: context.beforeManualMcpRename,
188958
+ replace: context.manualMcpRename
188959
+ });
188960
+ } catch (error51) {
188961
+ if (error51 instanceof MigrationCommittedError) {
188962
+ return migrationConflictResult("Codex MCP", existing.path, "Codex MCP migration may have been committed but could not be verified; inspect the current config before retrying.");
188963
+ }
188964
+ if (error51 instanceof MigrationConflictError) {
188965
+ return migrationConflictResult("Codex MCP", existing.path, "Codex MCP config changed before migration could be committed; it was not overwritten.");
188966
+ }
188967
+ throw error51;
188968
+ }
188969
+ return {
188970
+ kind: "mcp",
188971
+ registration: "migrated",
188972
+ title: "Codex MCP",
188973
+ status: "updated",
188974
+ path: existing.path,
188975
+ detail: "migrated exact legacy [mcp_servers.kyoso] invocation to explicit package/executable args"
188976
+ };
188977
+ }
188978
+ async function ensureExistingClaudeProjectMcp(context, content, existing) {
188979
+ const currentBunxVerification = verifyCurrentBunxRegistration(context, "Claude Code MCP", existing);
188980
+ if (currentBunxVerification)
188981
+ return currentBunxVerification;
188982
+ if (existing.invocation.kind !== "legacy") {
188983
+ return preservedMcpResult("Claude Code MCP", existing, claudePreservedDetail(existing));
188984
+ }
188985
+ const replacement = migrationReplacementForContext(context, existing.invocation);
188986
+ if (!replacement) {
188987
+ return preservedMcpResult("Claude Code MCP", existing, claudePreservedDetail(existing));
188988
+ }
188989
+ const detail = legacyMigrationDetail(existing.path, existing.invocation, replacement);
188990
+ if (!context.write) {
188991
+ return {
188992
+ kind: "mcp",
188993
+ registration: "preserved",
188994
+ title: "Claude Code MCP",
188995
+ status: "dry-run",
188996
+ path: existing.path,
188997
+ detail: `${detail}${bunxVerificationPendingDetail(context, replacement)}`
188998
+ };
188999
+ }
189000
+ if (!context.force || context.customCommand) {
189001
+ return preservedMcpResult("Claude Code MCP", existing, `${detail}
189002
+ Legacy registration was kept. Re-run with --write --force to migrate this exact invocation.`);
189003
+ }
189004
+ if (requiresExplicitBunxRunnerForMigration(context, replacement)) {
189005
+ return preservedMcpResult("Claude Code MCP", existing, `${detail}
189006
+ Legacy Bun registration was kept. Re-run with --write --runner bunx --force to verify and migrate this exact invocation, or with --runner npx --force to migrate it using npx.`);
189007
+ }
189008
+ const unsupportedBunx = unsupportedBunxResult(context, "Claude Code MCP", existing.path, replacement, { migration: true });
189009
+ if (unsupportedBunx)
189010
+ return unsupportedBunx;
189011
+ const safety = await inspectMigrationFile(existing.path);
189012
+ if (!safety.ok) {
189013
+ return preservedMcpResult("Claude Code MCP", existing, `${detail}
189014
+ ${safety.detail}`);
189015
+ }
189016
+ if (context.beforeManualMcpWrite) {
189017
+ await context.beforeManualMcpWrite(existing.path);
189018
+ }
189019
+ const latest = await readOptionalFile(existing.path);
189020
+ if (latest !== content) {
189021
+ return migrationConflictResult("Claude Code MCP", existing.path, "Claude Code MCP config changed after inspection; it was not overwritten.");
189022
+ }
189023
+ const next = patchClaudeProjectMcpInvocation(latest, replacement);
189024
+ if (!next) {
189025
+ return preservedMcpResult("Claude Code MCP", existing, `${detail}
189026
+ The JSON shape is not a safe exact legacy target; migrate it manually.`);
189027
+ }
189028
+ const verified = inspectClaudeProjectMcp(parseJsonObject(existing.path, next), existing.path);
189029
+ if (!isCurrentMcpCommand(verified?.invocation, replacement)) {
189030
+ return preservedMcpResult("Claude Code MCP", existing, `${detail}
189031
+ The proposed JSON patch could not be verified; it was not written.`);
189032
+ }
189033
+ try {
189034
+ await writeFileAtomically(existing.path, next, safety.safety, latest, {
189035
+ beforeCommit: context.beforeManualMcpCommit,
189036
+ afterExpectedContentsCheck: context.afterManualMcpValidation,
189037
+ beforeRename: context.beforeManualMcpRename,
189038
+ replace: context.manualMcpRename
189039
+ });
189040
+ } catch (error51) {
189041
+ if (error51 instanceof MigrationCommittedError) {
189042
+ return migrationConflictResult("Claude Code MCP", existing.path, "Claude Code MCP migration may have been committed but could not be verified; inspect the current config before retrying.");
189043
+ }
189044
+ if (error51 instanceof MigrationConflictError) {
189045
+ return migrationConflictResult("Claude Code MCP", existing.path, "Claude Code MCP config changed before migration could be committed; it was not overwritten.");
189046
+ }
189047
+ throw error51;
189048
+ }
189049
+ return {
189050
+ kind: "mcp",
189051
+ registration: "migrated",
189052
+ title: "Claude Code MCP",
189053
+ status: "updated",
189054
+ path: existing.path,
189055
+ detail: "migrated exact legacy mcpServers.kyoso invocation to explicit package/executable args"
189056
+ };
189057
+ }
188510
189058
  function ensureClaudeGlobalMcp(context) {
188511
189059
  const configPath = join6(context.home, ".claude.json");
188512
189060
  const existingMcp = detectClaudeMcp(configPath, context.cwd, context.home);
@@ -188517,7 +189065,7 @@ function ensureClaudeGlobalMcp(context) {
188517
189065
  title: "Claude Code MCP",
188518
189066
  status: "skipped",
188519
189067
  path: configPath,
188520
- detail: existingMcp.status === "disabled" ? disabledClaudeMcpDetail(configPath) : "existing mcpServers.kyoso kept"
189068
+ detail: existingMcp.status === "disabled" ? disabledClaudeMcpDetail(configPath) : claudeGlobalMcpPreservedDetail(existingMcp)
188521
189069
  };
188522
189070
  }
188523
189071
  const json2 = JSON.stringify(buildClaudeMcpEntry(context.mcpCommand, context.withOpenRouter));
@@ -188530,9 +189078,12 @@ function ensureClaudeGlobalMcp(context) {
188530
189078
  title: "Claude Code MCP",
188531
189079
  status: "dry-run",
188532
189080
  path: configPath,
188533
- detail: commandLine
189081
+ detail: `${commandLine}${bunxVerificationPendingDetail(context, context.mcpCommand)}`
188534
189082
  };
188535
189083
  }
189084
+ const unsupportedBunx = unsupportedBunxResult(context, "Claude Code MCP", configPath, context.mcpCommand);
189085
+ if (unsupportedBunx)
189086
+ return unsupportedBunx;
188536
189087
  const result = spawnSync2("claude", args, { encoding: "utf8" });
188537
189088
  if (result.status !== 0) {
188538
189089
  throw new Error(result.stderr || result.stdout || "claude mcp add-json failed");
@@ -188546,6 +189097,27 @@ function ensureClaudeGlobalMcp(context) {
188546
189097
  detail: commandLine
188547
189098
  };
188548
189099
  }
189100
+ function claudeGlobalMcpPreservedDetail(detection) {
189101
+ if (detection.registrations.length !== 1) {
189102
+ return "Multiple existing mcpServers.kyoso registrations were kept; their effective precedence is not inferred.";
189103
+ }
189104
+ const registration = detection.registrations[0];
189105
+ if (registration?.invocation.kind !== "legacy") {
189106
+ return "existing mcpServers.kyoso kept";
189107
+ }
189108
+ const scope = registration.scope === "claude-global" ? "user" : "project-scoped user-config";
189109
+ return `existing ${scope} mcpServers.kyoso uses legacy package-runner arguments and was kept. Automatic migration supports only a project .mcp.json; update ${registration.path} manually.`;
189110
+ }
189111
+ function claudeProjectMcpScopeConflictResult(projectPath, userConfig) {
189112
+ return {
189113
+ kind: "mcp",
189114
+ registration: "preserved",
189115
+ title: "Claude Code MCP",
189116
+ status: "skipped",
189117
+ path: projectPath,
189118
+ detail: `Claude user-config MCP registration${userConfig.registrations.length === 1 ? "" : "s"} at ${userConfig.paths.join(", ")} ${userConfig.registrations.length === 1 ? "was" : "were"} kept. Project setup does not infer effective precedence; update the intended scope manually.`
189119
+ };
189120
+ }
188549
189121
  async function ensureSkill(context, client, title) {
188550
189122
  const result = await ensureManagedSkill({
188551
189123
  sourceDir: context.sourceSkillDir,
@@ -188570,8 +189142,8 @@ function renderSetupOverview(context) {
188570
189142
  "Kyoso setup",
188571
189143
  "",
188572
189144
  "Clients",
188573
- ` codex: MCP ${statusWord(detected.codex.mcp)}, skill ${statusWord(detected.codex.skill)}`,
188574
- ` claude-code: MCP ${statusWord(detected["claude-code"].mcp)}, skill ${statusWord(detected["claude-code"].skill)}`,
189145
+ ` codex: MCP ${setupOverviewMcpStatus(detected.codex, context)}, skill ${statusWord(detected.codex.skill)}`,
189146
+ ` claude-code: MCP ${setupOverviewMcpStatus(detected["claude-code"], context)}, skill ${statusWord(detected["claude-code"].skill)}`,
188575
189147
  "",
188576
189148
  "Commands",
188577
189149
  " kyoso setup codex [--write] [--with-openrouter] [--runner npx|bunx] [--command <command>] [--global] [--force]",
@@ -188584,151 +189156,852 @@ function renderSetupOverview(context) {
188584
189156
  ].join(`
188585
189157
  `);
188586
189158
  }
188587
- function renderResults(context, results) {
188588
- const mcpSteps = results.filter((result) => result.kind === "mcp");
188589
- const includesGeneratedMcp = mcpSteps.some((result) => result.registration === "generated");
188590
- const includesGeneratedCodexMcp = mcpSteps.some((result) => result.registration === "generated" && result.title === "Codex MCP");
188591
- const includesPreservedMcp = mcpSteps.some((result) => result.registration === "preserved");
188592
- return [
188593
- "Kyoso setup",
188594
- "",
188595
- ...results.flatMap((result) => [
188596
- `${result.title}: ${result.status}${result.path ? ` (${result.path})` : ""}`,
188597
- ...result.detail ? indent(result.detail).split(`
188598
- `) : []
188599
- ]),
188600
- ...mcpSteps.length > 0 ? [
188601
- "",
188602
- "Credential scope",
188603
- ...includesGeneratedMcp ? [
188604
- context.withOpenRouter ? " Newly generated and dry-run MCP registrations include OPENROUTER_API_KEY because --with-openrouter was set." : " Newly generated and dry-run MCP registrations omit OPENROUTER_API_KEY. Use --with-openrouter before writing a new registration to include it."
188605
- ] : [],
188606
- ...includesGeneratedCodexMcp ? [
188607
- " Newly generated and dry-run Codex MCP registrations intentionally forward CODEX_ACCESS_TOKEN for default Codex authentication; OpenRouter mode withholds it from the Codex child."
188608
- ] : [],
188609
- ...includesPreservedMcp ? [
188610
- " Existing MCP registrations were preserved unchanged; --with-openrouter does not edit them."
188611
- ] : [],
188612
- " Use --with-openrouter only when OpenRouter is intentionally selected."
188613
- ] : []
188614
- ].join(`
188615
- `);
189159
+ function setupOverviewMcpStatus(detection, context) {
189160
+ if (detection.manualMcpStatus === "missing")
189161
+ return "missing";
189162
+ if (detection.manualMcpRegistrations.length !== 1)
189163
+ return "unverified";
189164
+ const registration = detection.manualMcpRegistrations[0];
189165
+ if (!registration)
189166
+ return "unverified";
189167
+ if (registration.status === "disabled")
189168
+ return "disabled";
189169
+ if (registration.status !== "enabled")
189170
+ return "unverified";
189171
+ const invocation = registration.invocation;
189172
+ if (invocation.kind === "legacy")
189173
+ return "repair required (legacy)";
189174
+ if (invocation.kind === "custom")
189175
+ return "custom/unverified";
189176
+ if (invocation.kind !== "current")
189177
+ return "unverified";
189178
+ if (invocation.runner === "npx") {
189179
+ return commandExists2("npx", context.env) ? "ok" : "npx missing";
189180
+ }
189181
+ if (invocation.runner === "bunx") {
189182
+ return commandExists2("bunx", context.env) ? "bunx unverified" : "bunx missing";
189183
+ }
189184
+ return "unverified";
189185
+ }
189186
+ function renderResults(context, results) {
189187
+ const mcpSteps = results.filter((result) => result.kind === "mcp");
189188
+ const includesGeneratedMcp = mcpSteps.some((result) => result.registration === "generated");
189189
+ const includesGeneratedCodexMcp = mcpSteps.some((result) => result.registration === "generated" && result.title === "Codex MCP");
189190
+ const includesPreservedMcp = mcpSteps.some((result) => result.registration === "preserved");
189191
+ return [
189192
+ "Kyoso setup",
189193
+ "",
189194
+ ...results.flatMap((result) => [
189195
+ `${result.title}: ${result.status}${result.path ? ` (${result.path})` : ""}`,
189196
+ ...result.detail ? indent(result.detail).split(`
189197
+ `) : []
189198
+ ]),
189199
+ ...mcpSteps.length > 0 ? [
189200
+ "",
189201
+ "Credential scope",
189202
+ ...includesGeneratedMcp ? [
189203
+ context.withOpenRouter ? " Newly generated and dry-run MCP registrations include OPENROUTER_API_KEY because --with-openrouter was set." : " Newly generated and dry-run MCP registrations omit OPENROUTER_API_KEY. Use --with-openrouter before writing a new registration to include it."
189204
+ ] : [],
189205
+ ...includesGeneratedCodexMcp ? [
189206
+ " Newly generated and dry-run Codex MCP registrations intentionally forward CODEX_ACCESS_TOKEN for default Codex authentication; OpenRouter mode withholds it from the Codex child."
189207
+ ] : [],
189208
+ ...includesPreservedMcp ? [
189209
+ " Existing MCP registrations were preserved unchanged; --with-openrouter does not edit them."
189210
+ ] : [],
189211
+ " Use --with-openrouter only when OpenRouter is intentionally selected."
189212
+ ] : []
189213
+ ].join(`
189214
+ `);
189215
+ }
189216
+ function parseClient(client) {
189217
+ if (client === undefined)
189218
+ return;
189219
+ if (client === "codex" || client === "claude-code")
189220
+ return client;
189221
+ throw new Error(`Invalid setup client "${client}". Expected codex or claude-code.`);
189222
+ }
189223
+ function validateSkillOnlyOptions(options, client) {
189224
+ if (!options.skillOnly)
189225
+ return;
189226
+ if (!client) {
189227
+ throw new Error("--skill-only requires setup client codex or claude-code.");
189228
+ }
189229
+ if (options.runner !== undefined) {
189230
+ throw new Error("--skill-only cannot be combined with --runner.");
189231
+ }
189232
+ if (options.command !== undefined) {
189233
+ throw new Error("--skill-only cannot be combined with --command.");
189234
+ }
189235
+ if (options.withOpenRouter) {
189236
+ throw new Error("--skill-only cannot be combined with --with-openrouter.");
189237
+ }
189238
+ }
189239
+ function parseRunner(runner) {
189240
+ if (runner === undefined || runner === "npx")
189241
+ return "npx";
189242
+ if (runner === "bunx")
189243
+ return "bunx";
189244
+ throw new Error(`Invalid --runner value "${runner}". Expected npx or bunx.`);
189245
+ }
189246
+ function parseCommandSpec(value) {
189247
+ const parts = splitCommand(value);
189248
+ const [command, ...args] = parts;
189249
+ if (!command)
189250
+ throw new Error("--command must not be empty");
189251
+ return { command, args };
189252
+ }
189253
+ function splitCommand(value) {
189254
+ const parts = [];
189255
+ let current = "";
189256
+ let quote;
189257
+ for (const char of value.trim()) {
189258
+ if (quote) {
189259
+ if (char === quote) {
189260
+ quote = undefined;
189261
+ } else {
189262
+ current += char;
189263
+ }
189264
+ continue;
189265
+ }
189266
+ if (char === '"' || char === "'") {
189267
+ quote = char;
189268
+ continue;
189269
+ }
189270
+ if (/\s/.test(char)) {
189271
+ if (current.length > 0) {
189272
+ parts.push(current);
189273
+ current = "";
189274
+ }
189275
+ continue;
189276
+ }
189277
+ current += char;
189278
+ }
189279
+ if (quote)
189280
+ throw new Error("--command has an unterminated quote");
189281
+ if (current.length > 0)
189282
+ parts.push(current);
189283
+ return parts;
189284
+ }
189285
+ function resolveBundledSkillDir() {
189286
+ const start = dirname8(fileURLToPath(import.meta.url));
189287
+ let current = start;
189288
+ for (let depth = 0;depth < 5; depth += 1) {
189289
+ const candidate = join6(current, ".agents", "skills", "kyoso-review");
189290
+ if (existsSync2(join6(candidate, "SKILL.md")))
189291
+ return candidate;
189292
+ current = dirname8(current);
189293
+ }
189294
+ throw new Error("Bundled kyoso-review skill was not found in this package.");
189295
+ }
189296
+ async function readOptionalFile(path) {
189297
+ try {
189298
+ return await readFile6(path, "utf8");
189299
+ } catch (error51) {
189300
+ if (isMissingPathError4(error51))
189301
+ return "";
189302
+ throw error51;
189303
+ }
189304
+ }
189305
+ function parseJsonObject(path, content) {
189306
+ if (content.trim().length === 0)
189307
+ return {};
189308
+ const parsed = JSON.parse(content);
189309
+ if (!isRecord10(parsed))
189310
+ throw new Error(`${path} must contain a JSON object`);
189311
+ return parsed;
189312
+ }
189313
+ function inspectCodexAppendSafety(content, cwd, home) {
189314
+ if (content.trim().length === 0)
189315
+ return { ok: true };
189316
+ try {
189317
+ const parsed = parse5(content);
189318
+ if (!isRecord10(parsed)) {
189319
+ return {
189320
+ ok: false,
189321
+ detail: "Codex config is not a TOML object and was left unchanged."
189322
+ };
189323
+ }
189324
+ if (hasUnprobedProjectIntegrationOverride(parsed, cwd, home)) {
189325
+ return {
189326
+ ok: false,
189327
+ detail: "Codex has a project-scoped MCP or Plugin override; the global config was left unchanged."
189328
+ };
189329
+ }
189330
+ if ("mcp_servers" in parsed && !isRecord10(parsed.mcp_servers)) {
189331
+ return {
189332
+ ok: false,
189333
+ detail: "Codex mcp_servers is malformed and was left unchanged."
189334
+ };
189335
+ }
189336
+ if (isRecord10(parsed.mcp_servers) && "kyoso" in parsed.mcp_servers) {
189337
+ return {
189338
+ ok: false,
189339
+ detail: "Codex already defines mcp_servers.kyoso in a form setup cannot safely extend; migrate it manually."
189340
+ };
189341
+ }
189342
+ if (hasTomlMcpServersAssignment(content)) {
189343
+ return {
189344
+ ok: false,
189345
+ detail: "Codex defines mcp_servers with an inline assignment setup cannot safely extend; add the registration manually."
189346
+ };
189347
+ }
189348
+ return { ok: true };
189349
+ } catch {
189350
+ return {
189351
+ ok: false,
189352
+ detail: "Codex config could not be parsed and was left unchanged."
189353
+ };
189354
+ }
189355
+ }
189356
+ function hasTomlMcpServersAssignment(content) {
189357
+ return /^[ \t]*(?:mcp_servers|"mcp_servers")[ \t]*=/m.test(content);
189358
+ }
189359
+ function inspectCodexMcpContent(content, path, cwd, home) {
189360
+ if (!hasCodexMcpContent(content))
189361
+ return;
189362
+ try {
189363
+ const parsed = parse5(content);
189364
+ if (hasUnprobedProjectIntegrationOverride(parsed, cwd, home)) {
189365
+ return manualMcpRegistration({
189366
+ path,
189367
+ scope: "codex-global",
189368
+ status: "unknown",
189369
+ value: undefined
189370
+ });
189371
+ }
189372
+ if (!isRecord10(parsed) || !isRecord10(parsed.mcp_servers)) {
189373
+ return manualMcpRegistration({
189374
+ path,
189375
+ scope: "codex-global",
189376
+ status: "unknown",
189377
+ value: undefined
189378
+ });
189379
+ }
189380
+ if (!("kyoso" in parsed.mcp_servers))
189381
+ return;
189382
+ return manualMcpRegistration({
189383
+ path,
189384
+ scope: "codex-global",
189385
+ status: mcpEntryStatus(parsed.mcp_servers.kyoso),
189386
+ value: parsed.mcp_servers.kyoso
189387
+ });
189388
+ } catch {
189389
+ return manualMcpRegistration({
189390
+ path,
189391
+ scope: "codex-global",
189392
+ status: "unknown",
189393
+ value: undefined
189394
+ });
189395
+ }
189396
+ }
189397
+ function inspectClaudeProjectMcp(current, path) {
189398
+ if (!("mcpServers" in current))
189399
+ return;
189400
+ if (!isRecord10(current.mcpServers)) {
189401
+ return manualMcpRegistration({
189402
+ path,
189403
+ scope: "claude-project",
189404
+ status: "unknown",
189405
+ value: undefined
189406
+ });
189407
+ }
189408
+ if (!("kyoso" in current.mcpServers))
189409
+ return;
189410
+ return manualMcpRegistration({
189411
+ path,
189412
+ scope: "claude-project",
189413
+ status: mcpEntryStatus(current.mcpServers.kyoso),
189414
+ value: current.mcpServers.kyoso
189415
+ });
189416
+ }
189417
+ function manualMcpRegistration(options) {
189418
+ const { value, ...registration } = options;
189419
+ const invocation = inspectManualMcpInvocation(value);
189420
+ return {
189421
+ ...registration,
189422
+ invocation,
189423
+ autoMigrationEligible: (options.scope === "codex-global" || options.scope === "claude-project") && invocation.kind === "legacy"
189424
+ };
189425
+ }
189426
+ function preservedMcpResult(title, registration, detail) {
189427
+ return {
189428
+ kind: "mcp",
189429
+ registration: "preserved",
189430
+ title,
189431
+ status: "skipped",
189432
+ path: registration.path,
189433
+ detail
189434
+ };
189435
+ }
189436
+ function codexPreservedDetail(registration) {
189437
+ if (registration.status === "disabled") {
189438
+ return disabledCodexMcpDetail(registration.path);
189439
+ }
189440
+ if (registration.invocation.kind === "current") {
189441
+ return "existing [mcp_servers.kyoso] uses the current explicit package/executable invocation and was kept.";
189442
+ }
189443
+ if (registration.invocation.kind === "legacy") {
189444
+ return "existing [mcp_servers.kyoso] uses a legacy invocation and was kept unchanged.";
189445
+ }
189446
+ return `existing [mcp_servers.kyoso] is ${formatInvocationKind(registration.invocation.kind)} and was kept unchanged. ${registration.invocation.reason}`;
189447
+ }
189448
+ function claudePreservedDetail(registration) {
189449
+ if (registration.status === "disabled") {
189450
+ return disabledClaudeMcpDetail(registration.path);
189451
+ }
189452
+ if (registration.invocation.kind === "current") {
189453
+ return "existing mcpServers.kyoso uses the current explicit package/executable invocation and was kept.";
189454
+ }
189455
+ if (registration.invocation.kind === "legacy") {
189456
+ return "existing mcpServers.kyoso uses a legacy invocation and was kept unchanged.";
189457
+ }
189458
+ return `existing mcpServers.kyoso is ${formatInvocationKind(registration.invocation.kind)} and was kept unchanged. ${registration.invocation.reason}`;
189459
+ }
189460
+ function formatInvocationKind(kind) {
189461
+ if (kind === "custom")
189462
+ return "custom/unverified";
189463
+ return kind;
189464
+ }
189465
+ function legacyMigrationDetail(path, invocation, replacement) {
189466
+ const legacyArgs = invocation.legacyArgs;
189467
+ if (!invocation.runner || !legacyArgs) {
189468
+ return "legacy package-runner invocation detected; only --write --force may migrate it. The migration preview is unavailable because its exact command arguments could not be reconstructed.";
189469
+ }
189470
+ return [
189471
+ "legacy package-runner invocation detected; only --write --force may migrate it.",
189472
+ `--- ${formatMigrationPreviewValue(path)}`,
189473
+ `+++ ${formatMigrationPreviewValue(path)}`,
189474
+ "@@ Kyoso MCP invocation",
189475
+ `- command = ${formatMigrationPreviewValue(invocation.runner)}`,
189476
+ `- args = ${formatMigrationPreviewArgs(legacyArgs)}`,
189477
+ `+ command = ${formatMigrationPreviewValue(replacement.command)}`,
189478
+ `+ args = ${formatMigrationPreviewArgs(replacement.args)}`,
189479
+ "Only the Kyoso command and arguments are shown; other configuration values remain unchanged."
189480
+ ].join(`
189481
+ `);
189482
+ }
189483
+ function patchCodexLegacyInvocation(content, replacement) {
189484
+ const tableMatches = [...content.matchAll(/^\s*\[mcp_servers\.kyoso]\s*$/gm)];
189485
+ if (tableMatches.length !== 1)
189486
+ return;
189487
+ const table = tableMatches[0];
189488
+ if (table?.index === undefined)
189489
+ return;
189490
+ const bodyStart = table.index + table[0].length;
189491
+ const remaining = content.slice(bodyStart);
189492
+ const nextTableOffset = remaining.search(/^\s*\[/m);
189493
+ const bodyEnd = nextTableOffset === -1 ? content.length : bodyStart + nextTableOffset;
189494
+ const body = content.slice(bodyStart, bodyEnd);
189495
+ const commandMatches = [
189496
+ ...body.matchAll(/^([ \t]*command[ \t]*=[ \t]*)"[^"\r\n]*"([ \t]*(?:#.*)?)(\r?\n|$)/gm)
189497
+ ];
189498
+ const argsMatches = [
189499
+ ...body.matchAll(/^([ \t]*args[ \t]*=[ \t]*)([^\r\n]*)(\r?\n|$)/gm)
189500
+ ];
189501
+ if (commandMatches.length !== 1 || argsMatches.length !== 1)
189502
+ return;
189503
+ const argsMatch = argsMatches[0];
189504
+ const argsLine = argsMatch?.[0];
189505
+ const argsPrefix = argsMatch?.[1];
189506
+ const argsValue = argsMatch?.[2];
189507
+ const argsNewline = argsMatch?.[3];
189508
+ if (argsLine === undefined || argsPrefix === undefined || argsValue === undefined || argsNewline === undefined) {
189509
+ return;
189510
+ }
189511
+ const patchedArgsValue = patchTomlInlineArrayValue(argsValue, replacement.args);
189512
+ if (patchedArgsValue === undefined)
189513
+ return;
189514
+ const commandPatched = body.replace(/^([ \t]*command[ \t]*=[ \t]*)"[^"\r\n]*"([ \t]*(?:#.*)?)(\r?\n|$)/m, (_line, prefix, suffix, newline) => `${prefix}${JSON.stringify(replacement.command)}${suffix}${newline}`);
189515
+ const nextBody = commandPatched.replace(argsLine, `${argsPrefix}${patchedArgsValue}${argsNewline}`);
189516
+ return `${content.slice(0, bodyStart)}${nextBody}${content.slice(bodyEnd)}`;
189517
+ }
189518
+ function patchTomlInlineArrayValue(value, replacement) {
189519
+ const closingIndex = tomlInlineArrayClosingIndex(value);
189520
+ if (closingIndex === undefined)
189521
+ return;
189522
+ const suffix = value.slice(closingIndex + 1);
189523
+ if (!/^[ \t]*(?:#.*)?$/.test(suffix))
189524
+ return;
189525
+ return `${JSON.stringify(replacement)}${suffix}`;
189526
+ }
189527
+ function tomlInlineArrayClosingIndex(value) {
189528
+ if (!value.startsWith("["))
189529
+ return;
189530
+ let depth = 0;
189531
+ let quote;
189532
+ for (let index = 0;index < value.length; index += 1) {
189533
+ const character = value[index];
189534
+ if (character === undefined)
189535
+ return;
189536
+ if (quote) {
189537
+ if (quote === '"' && character === "\\") {
189538
+ index += 1;
189539
+ continue;
189540
+ }
189541
+ if (character === quote) {
189542
+ if (quote === "'" && value[index + 1] === "'") {
189543
+ index += 1;
189544
+ continue;
189545
+ }
189546
+ quote = undefined;
189547
+ }
189548
+ continue;
189549
+ }
189550
+ if (character === '"' || character === "'") {
189551
+ quote = character;
189552
+ continue;
189553
+ }
189554
+ if (character === "[") {
189555
+ depth += 1;
189556
+ continue;
189557
+ }
189558
+ if (character === "]") {
189559
+ depth -= 1;
189560
+ if (depth === 0)
189561
+ return index;
189562
+ if (depth < 0)
189563
+ return;
189564
+ }
189565
+ }
189566
+ return;
189567
+ }
189568
+ function isCurrentMcpCommand(invocation, replacement) {
189569
+ if (invocation?.kind !== "current" || invocation.runner !== replacement.command) {
189570
+ return false;
189571
+ }
189572
+ const packageSpec = replacement.command === "npx" ? replacement.args[1]?.slice("--package=".length) : replacement.args[1];
189573
+ return invocation.packageSpec === packageSpec;
189574
+ }
189575
+ function migrationReplacementForContext(context, invocation) {
189576
+ const replacement = invocation.replacement;
189577
+ if (!replacement || context.customCommand || !context.runnerExplicit || !isKyosoPackageRunner(context.mcpCommand.command) || context.mcpCommand.command === replacement.command) {
189578
+ return replacement;
189579
+ }
189580
+ const packageSpec = invocation.packageSpec;
189581
+ if (packageSpec === undefined || packageSpec !== KYOSO_PACKAGE_NAME && !packageSpec.startsWith(`${KYOSO_PACKAGE_NAME}@`)) {
189582
+ return replacement;
189583
+ }
189584
+ const version2 = packageSpec === KYOSO_PACKAGE_NAME ? undefined : packageSpec.slice(`${KYOSO_PACKAGE_NAME}@`.length);
189585
+ return buildKyosoPackageCommand({
189586
+ runner: context.mcpCommand.command,
189587
+ ...version2 === undefined ? {} : { version: version2 },
189588
+ cliArgs: ["mcp"]
189589
+ });
189590
+ }
189591
+ function isKyosoPackageRunner(value) {
189592
+ return value === "npx" || value === "bunx";
189593
+ }
189594
+ function patchClaudeProjectMcpInvocation(content, replacement) {
189595
+ try {
189596
+ JSON.parse(content);
189597
+ const root = scanJsonValue(content, skipJsonWhitespace(content, 0));
189598
+ if (skipJsonWhitespace(content, root.end) !== content.length)
189599
+ return;
189600
+ const mcpServers = singleJsonObjectProperty(root, "mcpServers");
189601
+ const kyoso = mcpServers && singleJsonObjectProperty(mcpServers, "kyoso");
189602
+ const command = kyoso && singleJsonProperty(kyoso, "command");
189603
+ const args = kyoso && singleJsonProperty(kyoso, "args");
189604
+ if (!command || !args)
189605
+ return;
189606
+ return replaceJsonValueSpans(content, [
189607
+ { ...command.value, replacement: JSON.stringify(replacement.command) },
189608
+ { ...args.value, replacement: JSON.stringify(replacement.args) }
189609
+ ]);
189610
+ } catch {
189611
+ return;
189612
+ }
189613
+ }
189614
+ function scanJsonValue(content, start) {
189615
+ const index = skipJsonWhitespace(content, start);
189616
+ const character = content[index];
189617
+ if (character === "{")
189618
+ return scanJsonObject(content, index);
189619
+ if (character === "[")
189620
+ return scanJsonArray(content, index);
189621
+ if (character === '"') {
189622
+ const end2 = scanJsonStringEnd(content, index);
189623
+ return { kind: "scalar", start: index, end: end2 };
189624
+ }
189625
+ let end = index;
189626
+ while (end < content.length && !/[\s,\]}]/.test(content[end] ?? "")) {
189627
+ end += 1;
189628
+ }
189629
+ if (end === index)
189630
+ throw new Error("expected JSON value");
189631
+ return { kind: "scalar", start: index, end };
189632
+ }
189633
+ function scanJsonObject(content, start) {
189634
+ let index = skipJsonWhitespace(content, start + 1);
189635
+ const entries = [];
189636
+ if (content[index] === "}") {
189637
+ return { kind: "object", start, end: index + 1, entries };
189638
+ }
189639
+ for (;; ) {
189640
+ if (content[index] !== '"')
189641
+ throw new Error("expected JSON object key");
189642
+ const keyEnd = scanJsonStringEnd(content, index);
189643
+ const key = JSON.parse(content.slice(index, keyEnd));
189644
+ if (typeof key !== "string")
189645
+ throw new Error("invalid JSON object key");
189646
+ index = skipJsonWhitespace(content, keyEnd);
189647
+ if (content[index] !== ":")
189648
+ throw new Error("expected JSON object separator");
189649
+ const value = scanJsonValue(content, index + 1);
189650
+ entries.push({ key, value });
189651
+ index = skipJsonWhitespace(content, value.end);
189652
+ if (content[index] === "}") {
189653
+ return { kind: "object", start, end: index + 1, entries };
189654
+ }
189655
+ if (content[index] !== ",")
189656
+ throw new Error("expected JSON object delimiter");
189657
+ index = skipJsonWhitespace(content, index + 1);
189658
+ }
189659
+ }
189660
+ function scanJsonArray(content, start) {
189661
+ let index = skipJsonWhitespace(content, start + 1);
189662
+ if (content[index] === "]")
189663
+ return { kind: "array", start, end: index + 1 };
189664
+ for (;; ) {
189665
+ const value = scanJsonValue(content, index);
189666
+ index = skipJsonWhitespace(content, value.end);
189667
+ if (content[index] === "]")
189668
+ return { kind: "array", start, end: index + 1 };
189669
+ if (content[index] !== ",")
189670
+ throw new Error("expected JSON array delimiter");
189671
+ index = skipJsonWhitespace(content, index + 1);
189672
+ }
189673
+ }
189674
+ function scanJsonStringEnd(content, start) {
189675
+ let escaped = false;
189676
+ for (let index = start + 1;index < content.length; index += 1) {
189677
+ const character = content[index];
189678
+ if (escaped) {
189679
+ escaped = false;
189680
+ continue;
189681
+ }
189682
+ if (character === "\\") {
189683
+ escaped = true;
189684
+ continue;
189685
+ }
189686
+ if (character === '"')
189687
+ return index + 1;
189688
+ }
189689
+ throw new Error("unterminated JSON string");
189690
+ }
189691
+ function skipJsonWhitespace(content, start) {
189692
+ let index = start;
189693
+ while (index < content.length && /\s/.test(content[index] ?? ""))
189694
+ index += 1;
189695
+ return index;
189696
+ }
189697
+ function singleJsonObjectProperty(object2, key) {
189698
+ const property = singleJsonProperty(object2, key);
189699
+ return property?.value.kind === "object" ? property.value : undefined;
189700
+ }
189701
+ function singleJsonProperty(object2, key) {
189702
+ if (object2.kind !== "object")
189703
+ return;
189704
+ const matches = object2.entries?.filter((entry) => entry.key === key) ?? [];
189705
+ return matches.length === 1 ? matches[0] : undefined;
189706
+ }
189707
+ function replaceJsonValueSpans(content, replacements) {
189708
+ const sorted = [...replacements].sort((left, right) => right.start - left.start);
189709
+ if (sorted[0]?.start === sorted[1]?.start)
189710
+ return;
189711
+ let next = content;
189712
+ for (const replacement of sorted) {
189713
+ next = `${next.slice(0, replacement.start)}${replacement.replacement}${next.slice(replacement.end)}`;
189714
+ }
189715
+ return next;
189716
+ }
189717
+ async function inspectMigrationFile(path, options = {}) {
189718
+ try {
189719
+ const before = await lstat3(path);
189720
+ if (!isSafeMigrationFile(before, options.expectedSafety, options.expectedNlink)) {
189721
+ return {
189722
+ ok: false,
189723
+ detail: "The existing config is not an unlinked regular file and was left for manual migration."
189724
+ };
189725
+ }
189726
+ if (options.expectedContents !== undefined) {
189727
+ const contents = await readFile6(path, "utf8");
189728
+ const after = await lstat3(path);
189729
+ if (!isSafeMigrationFile(after, options.expectedSafety, options.expectedNlink) || !sameMigrationFileIdentity(before, after) || contents !== options.expectedContents) {
189730
+ return {
189731
+ ok: false,
189732
+ detail: "The existing config changed after final migration validation and was left unchanged."
189733
+ };
189734
+ }
189735
+ return { ok: true, safety: migrationFileSafety(after) };
189736
+ }
189737
+ return { ok: true, safety: migrationFileSafety(before) };
189738
+ } catch (error51) {
189739
+ return {
189740
+ ok: false,
189741
+ detail: `The existing config could not be safely inspected: ${error51 instanceof Error ? error51.message : String(error51)}`
189742
+ };
189743
+ }
189744
+ }
189745
+ function isSafeMigrationFile(metadata, expectedSafety, expectedNlink = 1) {
189746
+ if (metadata.isSymbolicLink() || !metadata.isFile() || metadata.nlink !== expectedNlink) {
189747
+ return false;
189748
+ }
189749
+ return expectedSafety === undefined || sameMigrationFileIdentity(metadata, expectedSafety) && (metadata.mode & 511) === expectedSafety.mode;
189750
+ }
189751
+ function sameMigrationFileIdentity(metadata, expected) {
189752
+ return metadata.dev === expected.dev && metadata.ino === expected.ino;
189753
+ }
189754
+ function migrationFileSafety(metadata) {
189755
+ return {
189756
+ mode: metadata.mode & 511,
189757
+ dev: metadata.dev,
189758
+ ino: metadata.ino
189759
+ };
189760
+ }
189761
+
189762
+ class MigrationConflictError extends Error {
189763
+ }
189764
+
189765
+ class MigrationCommittedError extends Error {
189766
+ }
189767
+ function migrationConflictResult(title, path, detail) {
189768
+ return {
189769
+ kind: "mcp",
189770
+ registration: "preserved",
189771
+ title,
189772
+ status: "conflict",
189773
+ path,
189774
+ detail
189775
+ };
189776
+ }
189777
+ async function writeFileAtomically(path, contents, expectedSafety, expectedContents, options) {
189778
+ const directory = dirname8(path);
189779
+ const temporaryPath = join6(directory, `.${basename3(path)}.kyoso-${process.pid}-${randomUUID2()}.tmp`);
189780
+ let handle;
189781
+ let replacementCommitted = false;
189782
+ try {
189783
+ handle = await open(temporaryPath, "wx", expectedSafety.mode);
189784
+ await handle.writeFile(contents, "utf8");
189785
+ await chmod(temporaryPath, expectedSafety.mode);
189786
+ await handle.sync();
189787
+ await handle.close();
189788
+ handle = undefined;
189789
+ await options.beforeCommit?.(path);
189790
+ await options.afterExpectedContentsCheck?.(path);
189791
+ await options.beforeRename?.(path);
189792
+ const beforeRename = await inspectMigrationFile(path, {
189793
+ expectedContents,
189794
+ expectedSafety
189795
+ });
189796
+ if (!beforeRename.ok) {
189797
+ throw new MigrationConflictError(beforeRename.detail);
189798
+ }
189799
+ try {
189800
+ await (options.replace ?? rename2)(temporaryPath, path);
189801
+ replacementCommitted = true;
189802
+ } catch (error51) {
189803
+ const installed2 = await inspectMigrationFile(path, {
189804
+ expectedContents: contents
189805
+ });
189806
+ if (installed2.ok && installed2.safety.mode === expectedSafety.mode) {
189807
+ throw new MigrationCommittedError("manual MCP migration may have been committed before replacement reported an error");
189808
+ }
189809
+ throw error51;
189810
+ }
189811
+ await syncDirectory(directory);
189812
+ const installed = await inspectMigrationFile(path, {
189813
+ expectedContents: contents
189814
+ });
189815
+ if (!installed.ok || installed.safety.mode !== expectedSafety.mode) {
189816
+ throw new MigrationCommittedError("the replacement config could not be verified after installation");
189817
+ }
189818
+ } catch (error51) {
189819
+ if (error51 instanceof MigrationConflictError || error51 instanceof MigrationCommittedError) {
189820
+ throw error51;
189821
+ }
189822
+ if (replacementCommitted) {
189823
+ throw new MigrationCommittedError(`manual MCP migration may have been committed but could not be verified: ${error51 instanceof Error ? error51.message : String(error51)}`);
189824
+ }
189825
+ throw new MigrationConflictError(`manual MCP migration failed: ${error51 instanceof Error ? error51.message : String(error51)}`);
189826
+ } finally {
189827
+ await handle?.close().catch(() => {
189828
+ return;
189829
+ });
189830
+ await rm2(temporaryPath, { force: true }).catch(() => {
189831
+ return;
189832
+ });
189833
+ }
189834
+ }
189835
+ async function syncDirectory(path) {
189836
+ let handle;
189837
+ try {
189838
+ handle = await open(path, "r");
189839
+ await handle.sync();
189840
+ } catch (error51) {
189841
+ if (isUnsupportedDirectorySyncError(error51))
189842
+ return;
189843
+ throw error51;
189844
+ } finally {
189845
+ await handle?.close().catch(() => {
189846
+ return;
189847
+ });
189848
+ }
188616
189849
  }
188617
- function parseClient(client) {
188618
- if (client === undefined)
189850
+ function isUnsupportedDirectorySyncError(error51) {
189851
+ const code = errorCode(error51);
189852
+ return code === "EINVAL" || code === "ENOTSUP" || code === "EOPNOTSUPP" || process.platform === "win32" && code === "EPERM";
189853
+ }
189854
+ function errorCode(error51) {
189855
+ if (typeof error51 !== "object" || error51 === null || !("code" in error51)) {
188619
189856
  return;
188620
- if (client === "codex" || client === "claude-code")
188621
- return client;
188622
- throw new Error(`Invalid setup client "${client}". Expected codex or claude-code.`);
189857
+ }
189858
+ return typeof error51.code === "string" ? error51.code : undefined;
188623
189859
  }
188624
- function validateSkillOnlyOptions(options, client) {
188625
- if (!options.skillOnly)
189860
+ function unsupportedBunxResult(context, title, path, command, options = {}) {
189861
+ if (!context.write || options.migration && !context.force || context.customCommand || command.command !== "bunx") {
188626
189862
  return;
188627
- if (!client) {
188628
- throw new Error("--skill-only requires setup client codex or claude-code.");
188629
189863
  }
188630
- if (options.runner !== undefined) {
188631
- throw new Error("--skill-only cannot be combined with --runner.");
189864
+ const probe = ensureBunxProbe(context);
189865
+ if (probe.status === "verified")
189866
+ return;
189867
+ const fallback = formatBunxFallbackCommand(context, title, options);
189868
+ return {
189869
+ kind: "mcp",
189870
+ registration: "blocked",
189871
+ title,
189872
+ status: "skipped",
189873
+ path,
189874
+ detail: `bunx was not verified for explicit package selection (${probe.detail}). No MCP config was written. Use ${fallback}, or install Bun 1.3.14 or newer and retry --runner bunx.`
189875
+ };
189876
+ }
189877
+ function verifyCurrentBunxRegistration(context, title, existing) {
189878
+ if (!context.write || !context.runnerExplicit || context.customCommand || context.mcpCommand.command !== "bunx" || existing.invocation.kind !== "current" || existing.invocation.runner !== "bunx") {
189879
+ return;
188632
189880
  }
188633
- if (options.command !== undefined) {
188634
- throw new Error("--skill-only cannot be combined with --command.");
189881
+ const probe = ensureBunxProbe(context);
189882
+ if (probe.status !== "verified") {
189883
+ return {
189884
+ kind: "mcp",
189885
+ registration: "blocked",
189886
+ title,
189887
+ status: "skipped",
189888
+ path: existing.path,
189889
+ detail: `existing current bunx registration was kept unchanged. bunx could not be verified for explicit package selection (${probe.detail}). Install Bun 1.3.14 or newer before treating this registration as ready.`
189890
+ };
188635
189891
  }
188636
- if (options.withOpenRouter) {
188637
- throw new Error("--skill-only cannot be combined with --with-openrouter.");
189892
+ const detail = title === "Codex MCP" ? codexPreservedDetail(existing) : claudePreservedDetail(existing);
189893
+ return preservedMcpResult(title, existing, `${detail} bunx ${probe.version} was verified for explicit package selection; no MCP config bytes changed.`);
189894
+ }
189895
+ function ensureBunxProbe(context) {
189896
+ return context.bunxProbe ?? (context.bunxProbe = context.bunxVersionProbe({
189897
+ cwd: context.cwd,
189898
+ env: context.env
189899
+ }));
189900
+ }
189901
+ function requiresExplicitBunxRunnerForMigration(context, command) {
189902
+ return command.command === "bunx" && !context.runnerExplicit;
189903
+ }
189904
+ function formatBunxFallbackCommand(context, title, options) {
189905
+ const cliArgs = [
189906
+ "setup",
189907
+ title === "Codex MCP" ? "codex" : "claude-code",
189908
+ "--write",
189909
+ "--runner",
189910
+ "npx"
189911
+ ];
189912
+ if (context.scope === "global")
189913
+ cliArgs.push("--global");
189914
+ if (context.withOpenRouter)
189915
+ cliArgs.push("--with-openrouter");
189916
+ if (options.migration)
189917
+ cliArgs.push("--force");
189918
+ return formatKyosoPackageCommand({ runner: "npx", cliArgs });
189919
+ }
189920
+ function bunxVerificationPendingDetail(context, command) {
189921
+ if (context.customCommand || command.command !== "bunx")
189922
+ return "";
189923
+ if (!context.runnerExplicit) {
189924
+ return `
189925
+ Legacy Bun registration will stay unchanged unless you rerun with --write --runner bunx --force to verify and migrate it, or with --runner npx --force to migrate it using npx.`;
188638
189926
  }
189927
+ return `
189928
+ Bun 1.3.14 or newer will be verified before any MCP config is written.`;
188639
189929
  }
188640
- function parseRunner(runner) {
188641
- if (runner === undefined || runner === "npx")
188642
- return "npx";
188643
- if (runner === "bunx")
188644
- return "bunx";
188645
- throw new Error(`Invalid --runner value "${runner}". Expected npx or bunx.`);
189930
+ function formatMigrationPreviewValue(value) {
189931
+ return JSON.stringify(sanitizeTextForDisplay(value));
188646
189932
  }
188647
- function parseCommandSpec(value) {
188648
- const parts = splitCommand(value);
188649
- const [command, ...args] = parts;
188650
- if (!command)
188651
- throw new Error("--command must not be empty");
188652
- return { command, args };
189933
+ function formatMigrationPreviewArgs(args) {
189934
+ return `[${args.map(formatMigrationPreviewValue).join(", ")}]`;
188653
189935
  }
188654
- function splitCommand(value) {
188655
- const parts = [];
188656
- let current = "";
188657
- let quote;
188658
- for (const char of value.trim()) {
188659
- if (quote) {
188660
- if (char === quote) {
188661
- quote = undefined;
188662
- } else {
188663
- current += char;
188664
- }
188665
- continue;
189936
+ function probeBunxVersion(options) {
189937
+ const probeDirectory = mkdtempSync(join6(tmpdir2(), "kyoso-bunx-probe-"));
189938
+ try {
189939
+ const result = spawnSync2("bunx", ["--version"], {
189940
+ cwd: probeDirectory,
189941
+ env: sanitizedBunxProbeEnv(options.env, probeDirectory),
189942
+ encoding: "utf8",
189943
+ timeout: 2000,
189944
+ shell: false
189945
+ });
189946
+ const errorCode2 = result.error && "code" in result.error ? result.error.code : undefined;
189947
+ if (errorCode2 === "ENOENT") {
189948
+ return { status: "missing", detail: "bunx was not found on PATH" };
188666
189949
  }
188667
- if (char === '"' || char === "'") {
188668
- quote = char;
188669
- continue;
189950
+ if (errorCode2 === "ETIMEDOUT" || result.signal) {
189951
+ return { status: "timeout", detail: "bunx --version timed out" };
188670
189952
  }
188671
- if (/\s/.test(char)) {
188672
- if (current.length > 0) {
188673
- parts.push(current);
188674
- current = "";
188675
- }
188676
- continue;
189953
+ if (result.status !== 0) {
189954
+ return {
189955
+ status: "failed",
189956
+ detail: `bunx --version exited ${result.status ?? "without a status"}`
189957
+ };
188677
189958
  }
188678
- current += char;
189959
+ const version2 = result.stdout.trim();
189960
+ if (!/^\d+\.\d+\.\d+$/.test(version2)) {
189961
+ return {
189962
+ status: "invalid",
189963
+ detail: "bunx --version did not return a stable SemVer"
189964
+ };
189965
+ }
189966
+ if (!isCompleteSemVer(version2) || !isMinimumBunVersion(version2, "1.3.14")) {
189967
+ return {
189968
+ status: "unsupported",
189969
+ detail: `bunx ${version2} is older than the verified minimum 1.3.14`
189970
+ };
189971
+ }
189972
+ return { status: "verified", version: version2 };
189973
+ } finally {
189974
+ rmSync(probeDirectory, { force: true, recursive: true });
188679
189975
  }
188680
- if (quote)
188681
- throw new Error("--command has an unterminated quote");
188682
- if (current.length > 0)
188683
- parts.push(current);
188684
- return parts;
188685
189976
  }
188686
- function resolveBundledSkillDir() {
188687
- const start = dirname8(fileURLToPath(import.meta.url));
188688
- let current = start;
188689
- for (let depth = 0;depth < 5; depth += 1) {
188690
- const candidate = join6(current, ".agents", "skills", "kyoso-review");
188691
- if (existsSync2(join6(candidate, "SKILL.md")))
188692
- return candidate;
188693
- current = dirname8(current);
189977
+ function sanitizedBunxProbeEnv(env, temporaryDirectory) {
189978
+ const result = {
189979
+ PATH: env.PATH ?? "",
189980
+ HOME: env.HOME ?? "",
189981
+ TMPDIR: temporaryDirectory
189982
+ };
189983
+ for (const key of ["SystemRoot", "ComSpec", "PATHEXT", "WINDIR"]) {
189984
+ if (env[key])
189985
+ result[key] = env[key];
188694
189986
  }
188695
- throw new Error("Bundled kyoso-review skill was not found in this package.");
189987
+ return result;
188696
189988
  }
188697
- async function readOptionalFile(path) {
188698
- try {
188699
- return await readFile6(path, "utf8");
188700
- } catch (error51) {
188701
- if (isMissingPathError4(error51))
188702
- return "";
188703
- throw error51;
189989
+ function isMinimumBunVersion(version2, minimum) {
189990
+ const actualParts = version2.split(".").map(Number);
189991
+ const minimumParts = minimum.split(".").map(Number);
189992
+ for (let index = 0;index < minimumParts.length; index += 1) {
189993
+ const actual = actualParts[index] ?? 0;
189994
+ const required2 = minimumParts[index] ?? 0;
189995
+ if (actual > required2)
189996
+ return true;
189997
+ if (actual < required2)
189998
+ return false;
188704
189999
  }
188705
- }
188706
- async function readJsonObject(path) {
188707
- const content = await readOptionalFile(path);
188708
- if (content.trim().length === 0)
188709
- return {};
188710
- const parsed = JSON.parse(content);
188711
- if (!isRecord9(parsed))
188712
- throw new Error(`${path} must contain a JSON object`);
188713
- return parsed;
190000
+ return true;
188714
190001
  }
188715
190002
  function hasCodexMcpContent(content) {
188716
190003
  return /^\s*\[mcp_servers\.(?:"kyoso"|kyoso)]\s*$/m.test(content);
188717
190004
  }
188718
- function codexMcpStatusFromContent(content) {
188719
- try {
188720
- const parsed = parse5(content);
188721
- if (!isRecord9(parsed))
188722
- return "unknown";
188723
- if (!isRecord9(parsed.mcp_servers))
188724
- return "missing";
188725
- if (!("kyoso" in parsed.mcp_servers))
188726
- return "missing";
188727
- return mcpEntryStatus(parsed.mcp_servers.kyoso);
188728
- } catch {
188729
- return "unknown";
188730
- }
188731
- }
188732
190005
  function disabledCodexMcpDetail(configPath) {
188733
190006
  return [
188734
190007
  "existing [mcp_servers.kyoso] is disabled and was kept unchanged.",
@@ -188743,75 +190016,146 @@ function disabledClaudeMcpDetail(configPath) {
188743
190016
  }
188744
190017
  function detectCodexMcp(path, cwd, home) {
188745
190018
  if (!existsSync2(path))
188746
- return { status: "missing", paths: [] };
190019
+ return missingMcpDetection();
188747
190020
  try {
188748
190021
  const parsed = parse5(readTextSync(path));
188749
190022
  if (hasUnprobedProjectIntegrationOverride(parsed, cwd, home)) {
188750
- return { status: "unknown", paths: [path] };
190023
+ return singleMcpDetection(manualMcpRegistration({
190024
+ path,
190025
+ scope: "codex-global",
190026
+ status: "unknown",
190027
+ value: undefined
190028
+ }));
190029
+ }
190030
+ if (!isRecord10(parsed)) {
190031
+ return singleMcpDetection(manualMcpRegistration({
190032
+ path,
190033
+ scope: "codex-global",
190034
+ status: "unknown",
190035
+ value: undefined
190036
+ }));
188751
190037
  }
188752
- if (!isRecord9(parsed))
188753
- return { status: "unknown", paths: [path] };
188754
190038
  if (!("mcp_servers" in parsed))
188755
- return { status: "missing", paths: [] };
188756
- if (!isRecord9(parsed.mcp_servers)) {
188757
- return { status: "unknown", paths: [path] };
190039
+ return missingMcpDetection();
190040
+ if (!isRecord10(parsed.mcp_servers)) {
190041
+ return singleMcpDetection(manualMcpRegistration({
190042
+ path,
190043
+ scope: "codex-global",
190044
+ status: "unknown",
190045
+ value: undefined
190046
+ }));
188758
190047
  }
188759
190048
  if (!("kyoso" in parsed.mcp_servers)) {
188760
- return { status: "missing", paths: [] };
190049
+ return missingMcpDetection();
188761
190050
  }
188762
- return { status: mcpEntryStatus(parsed.mcp_servers.kyoso), paths: [path] };
190051
+ return singleMcpDetection(manualMcpRegistration({
190052
+ path,
190053
+ scope: "codex-global",
190054
+ status: mcpEntryStatus(parsed.mcp_servers.kyoso),
190055
+ value: parsed.mcp_servers.kyoso
190056
+ }));
188763
190057
  } catch {
188764
- return { status: "unknown", paths: [path] };
190058
+ return singleMcpDetection(manualMcpRegistration({
190059
+ path,
190060
+ scope: "codex-global",
190061
+ status: "unknown",
190062
+ value: undefined
190063
+ }));
188765
190064
  }
188766
190065
  }
188767
190066
  function detectClaudeMcp(path, cwd, home) {
188768
190067
  if (!existsSync2(path))
188769
- return { status: "missing", paths: [] };
190068
+ return missingMcpDetection();
188770
190069
  try {
188771
190070
  const parsed = JSON.parse(readTextSync(path));
188772
- const statuses = jsonMcpStatuses(parsed, cwd, home);
188773
- if (statuses.length === 0)
188774
- return { status: "missing", paths: [] };
188775
- return { status: mergeMcpStatuses(statuses), paths: [path] };
190071
+ const registrations = jsonMcpRegistrations(parsed, path, cwd, home);
190072
+ return registrations.length === 0 ? missingMcpDetection() : mergeMcpDetections([
190073
+ {
190074
+ status: mergeMcpStatuses(registrations.map((entry) => entry.status)),
190075
+ paths: [path],
190076
+ registrations
190077
+ }
190078
+ ]);
188776
190079
  } catch {
188777
- return { status: "unknown", paths: [path] };
190080
+ return singleMcpDetection(manualMcpRegistration({
190081
+ path,
190082
+ scope: path.endsWith(".mcp.json") ? "claude-project" : "claude-global",
190083
+ status: "unknown",
190084
+ value: undefined
190085
+ }));
188778
190086
  }
188779
190087
  }
188780
- function jsonMcpStatuses(value, cwd, home) {
188781
- if (!isRecord9(value))
188782
- return ["unknown"];
188783
- const statuses = directMcpStatuses(value);
190088
+ function jsonMcpRegistrations(value, path, cwd, home) {
190089
+ const directScope = path.endsWith(".mcp.json") ? "claude-project" : "claude-global";
190090
+ if (!isRecord10(value)) {
190091
+ return [
190092
+ manualMcpRegistration({
190093
+ path,
190094
+ scope: directScope,
190095
+ status: "unknown",
190096
+ value: undefined
190097
+ })
190098
+ ];
190099
+ }
190100
+ const registrations = directMcpRegistrations(value, path, directScope);
188784
190101
  if (!("projects" in value))
188785
- return statuses;
188786
- if (!isRecord9(value.projects))
188787
- return [...statuses, "unknown"];
190102
+ return registrations;
190103
+ if (!isRecord10(value.projects)) {
190104
+ return [
190105
+ ...registrations,
190106
+ manualMcpRegistration({
190107
+ path,
190108
+ scope: "claude-global-project",
190109
+ status: "unknown",
190110
+ value: undefined
190111
+ })
190112
+ ];
190113
+ }
188788
190114
  const currentProject = normalizeProjectPath(cwd, home);
188789
190115
  for (const [projectPath, projectConfig] of Object.entries(value.projects)) {
188790
190116
  if (normalizeProjectPath(projectPath, home) !== currentProject)
188791
190117
  continue;
188792
- if (!isRecord9(projectConfig)) {
188793
- statuses.push("unknown");
190118
+ if (!isRecord10(projectConfig)) {
190119
+ registrations.push(manualMcpRegistration({
190120
+ path,
190121
+ scope: "claude-global-project",
190122
+ status: "unknown",
190123
+ value: undefined
190124
+ }));
188794
190125
  continue;
188795
190126
  }
188796
- statuses.push(...directMcpStatuses(projectConfig));
190127
+ registrations.push(...directMcpRegistrations(projectConfig, path, "claude-global-project"));
188797
190128
  }
188798
- return statuses;
190129
+ return registrations;
188799
190130
  }
188800
- function directMcpStatuses(value) {
188801
- const statuses = [];
188802
- if ("mcpServers" in value) {
188803
- if (!isRecord9(value.mcpServers)) {
188804
- statuses.push("unknown");
188805
- } else if ("kyoso" in value.mcpServers) {
188806
- statuses.push(mcpEntryStatus(value.mcpServers.kyoso));
188807
- }
190131
+ function directMcpRegistrations(value, path, scope) {
190132
+ if (!("mcpServers" in value))
190133
+ return [];
190134
+ if (!isRecord10(value.mcpServers)) {
190135
+ return [
190136
+ manualMcpRegistration({
190137
+ path,
190138
+ scope,
190139
+ status: "unknown",
190140
+ value: undefined
190141
+ })
190142
+ ];
188808
190143
  }
188809
- return statuses;
190144
+ if (!("kyoso" in value.mcpServers))
190145
+ return [];
190146
+ return [
190147
+ manualMcpRegistration({
190148
+ path,
190149
+ scope,
190150
+ status: mcpEntryStatus(value.mcpServers.kyoso),
190151
+ value: value.mcpServers.kyoso
190152
+ })
190153
+ ];
188810
190154
  }
188811
190155
  function nestedMcpEntryStatus(value, path) {
188812
190156
  let current = value;
188813
190157
  for (const key of path) {
188814
- if (!isRecord9(current))
190158
+ if (!isRecord10(current))
188815
190159
  return "unknown";
188816
190160
  if (!(key in current))
188817
190161
  return "missing";
@@ -188820,11 +190164,11 @@ function nestedMcpEntryStatus(value, path) {
188820
190164
  return mcpEntryStatus(current);
188821
190165
  }
188822
190166
  function hasUnprobedProjectIntegrationOverride(value, cwd, home) {
188823
- if (!isRecord9(value) || !isRecord9(value.projects))
190167
+ if (!isRecord10(value) || !isRecord10(value.projects))
188824
190168
  return false;
188825
190169
  const currentProject = normalizeProjectPath(cwd, home);
188826
190170
  for (const [projectPath, projectConfig] of Object.entries(value.projects)) {
188827
- if (normalizeProjectPath(projectPath, home) !== currentProject || !isRecord9(projectConfig)) {
190171
+ if (normalizeProjectPath(projectPath, home) !== currentProject || !isRecord10(projectConfig)) {
188828
190172
  continue;
188829
190173
  }
188830
190174
  if ("mcp_servers" in projectConfig || "plugins" in projectConfig) {
@@ -188847,7 +190191,7 @@ function normalizeProjectPath(path, home) {
188847
190191
  return process.platform === "win32" ? normalized.toLowerCase() : normalized;
188848
190192
  }
188849
190193
  function mcpEntryStatus(value) {
188850
- if (!isRecord9(value))
190194
+ if (!isRecord10(value))
188851
190195
  return "unknown";
188852
190196
  if (!("enabled" in value))
188853
190197
  return "enabled";
@@ -188858,11 +190202,28 @@ function mcpEntryStatus(value) {
188858
190202
  return "unknown";
188859
190203
  }
188860
190204
  function mergeMcpDetections(detections) {
190205
+ const registrations = detections.flatMap((detection) => detection.registrations);
190206
+ if (registrations.length === 0)
190207
+ return missingMcpDetection();
190208
+ return {
190209
+ status: registrations.length === 1 ? registrations[0]?.status ?? "unknown" : "unknown",
190210
+ paths: [...new Set(registrations.map((registration) => registration.path))],
190211
+ registrations
190212
+ };
190213
+ }
190214
+ function missingMcpDetection() {
190215
+ return { status: "missing", paths: [], registrations: [] };
190216
+ }
190217
+ function singleMcpDetection(registration) {
188861
190218
  return {
188862
- status: mergeMcpStatuses(detections.map((detection) => detection.status)),
188863
- paths: detections.flatMap((detection) => detection.paths)
190219
+ status: registration.status,
190220
+ paths: [registration.path],
190221
+ registrations: [registration]
188864
190222
  };
188865
190223
  }
190224
+ function isCurrentManualMcp(detection) {
190225
+ return detection.status === "enabled" && detection.registrations.length === 1 && detection.registrations[0]?.invocation.kind === "current";
190226
+ }
188866
190227
  function mergeMcpStatuses(statuses) {
188867
190228
  if (statuses.includes("enabled"))
188868
190229
  return "enabled";
@@ -188881,7 +190242,7 @@ function readTextSync(path) {
188881
190242
  function recordValue(value) {
188882
190243
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
188883
190244
  }
188884
- function isRecord9(value) {
190245
+ function isRecord10(value) {
188885
190246
  return typeof value === "object" && value !== null && !Array.isArray(value);
188886
190247
  }
188887
190248
  function diffForAppend(path, snippet) {
@@ -189010,8 +190371,8 @@ async function runDoctor(options) {
189010
190371
  });
189011
190372
  const claudeIntegration = determineClientIntegration("claude-code", setup["claude-code"], cli);
189012
190373
  lines.push("", "MCP", " stdio server: ok");
189013
- lines.push(` Codex registration: ${formatManualMcpStatus(setup.codex.manualMcpStatus)}`);
189014
- lines.push(` Claude Code registration: ${formatManualMcpStatus(setup["claude-code"].manualMcpStatus)}`);
190374
+ lines.push(` Codex registration: ${formatManualMcpStatus(setup.codex, cli)}`);
190375
+ lines.push(` Claude Code registration: ${formatManualMcpStatus(setup["claude-code"], cli)}`);
189015
190376
  lines.push("", "Skills");
189016
190377
  lines.push(` Codex kyoso-review: ${setup.codex.skill ? "ok" : "missing"}`);
189017
190378
  lines.push(` Claude Code kyoso-review: ${setup["claude-code"].skill ? "ok" : "missing"}`);
@@ -189199,6 +190560,7 @@ function isCodexProjectProviderAllowlistIssue(_issue, path) {
189199
190560
  }
189200
190561
  function determineCodexIntegration(options) {
189201
190562
  const fallback = determineClientIntegration("codex", options.setup, options.cli);
190563
+ const hasReadyManualMcp = fallback.mode === "manual-mcp" || fallback.mode === "mcp-only";
189202
190564
  const plugin = options.pluginInspector({
189203
190565
  cwd: options.cwd,
189204
190566
  env: options.env
@@ -189225,7 +190587,13 @@ function determineCodexIntegration(options) {
189225
190587
  "Plugin MCP origin is unknown because the manual MCP configuration could not be classified."
189226
190588
  ], "unknown", "unknown"), "installed, enabled", "unknown");
189227
190589
  }
189228
- if (options.setup.manualMcpStatus === "enabled" && override.status !== "disabled") {
190590
+ if (options.setup.manualMcpStatus === "enabled" && !hasReadyManualMcp) {
190591
+ return withPluginDetails(withIntegrationWarnings(fallback, [
190592
+ ...pluginWarnings,
190593
+ "Plugin MCP origin is unknown because an enabled manual MCP registration is legacy, custom, unverified, or its runner is unavailable."
190594
+ ], "unknown", "unknown"), "installed, enabled", "unknown");
190595
+ }
190596
+ if (hasReadyManualMcp && override.status !== "disabled") {
189229
190597
  return withPluginDetails(withIntegrationWarnings({
189230
190598
  ...fallback,
189231
190599
  mode: "manual-mcp",
@@ -189242,7 +190610,7 @@ function determineCodexIntegration(options) {
189242
190610
  "Plugin MCP override could not be safely classified from the Codex configuration."
189243
190611
  ], "unknown", "unknown"), "installed, enabled", "unknown");
189244
190612
  }
189245
- if (override.status === "disabled" && options.setup.manualMcpStatus === "enabled") {
190613
+ if (override.status === "disabled" && hasReadyManualMcp) {
189246
190614
  return withPluginDetails(withIntegrationWarnings({
189247
190615
  ...fallback,
189248
190616
  mode: "manual-mcp",
@@ -189306,6 +190674,7 @@ function determineCodexIntegration(options) {
189306
190674
  function determineClientIntegration(client, setup, cli) {
189307
190675
  const integration = determineNonPluginIntegration({
189308
190676
  manualMcpStatus: setup.manualMcpStatus,
190677
+ manualMcpRegistrations: setup.manualMcpRegistrations,
189309
190678
  hasSkill: setup.skill,
189310
190679
  cli
189311
190680
  });
@@ -189341,7 +190710,7 @@ function pluginSkillWarnings(setup) {
189341
190710
  }
189342
190711
  function appendIntegration(lines, integration) {
189343
190712
  lines.push(` ${integration.client} integration: ${integration.mode}`);
189344
- lines.push(` manual MCP: ${formatManualMcpStatus(integration.setup.manualMcpStatus)}`);
190713
+ lines.push(` manual MCP: ${formatManualMcpStatus(integration.setup, integration.cli)}`);
189345
190714
  if (integration.setup.mcpPaths.length > 0) {
189346
190715
  lines.push(` manual MCP path(s): ${integration.setup.mcpPaths.join(", ")}`);
189347
190716
  }
@@ -189350,8 +190719,8 @@ function appendIntegration(lines, integration) {
189350
190719
  lines.push(` manual Skill path(s): ${integration.setup.skillPaths.join(", ")}`);
189351
190720
  }
189352
190721
  lines.push(` CLI: ${formatCliAvailability(integration.cli.kyoso)}`);
189353
- lines.push(` npx: ${integration.cli.npx ? "available" : "missing"}`);
189354
- lines.push(` bunx: ${integration.cli.bunx ? "available" : "missing"}`);
190722
+ lines.push(` npx: ${formatRunnerAvailability(integration.cli.npx)}`);
190723
+ lines.push(` bunx: ${formatRunnerAvailability(integration.cli.bunx)}`);
189355
190724
  if (integration.plugin)
189356
190725
  lines.push(` Plugin: ${integration.plugin}`);
189357
190726
  if (integration.pluginMcp) {
@@ -189362,10 +190731,26 @@ function appendIntegration(lines, integration) {
189362
190731
  }
189363
190732
  lines.push(` ${integration.advice}`);
189364
190733
  }
189365
- function formatManualMcpStatus(status) {
189366
- if (status === "enabled")
189367
- return "ok";
189368
- return status;
190734
+ function formatManualMcpStatus(setup, cli) {
190735
+ if (setup.manualMcpStatus !== "enabled")
190736
+ return setup.manualMcpStatus;
190737
+ if (setup.manualMcpRegistrations.length !== 1)
190738
+ return "unknown";
190739
+ const invocation = setup.manualMcpRegistrations[0]?.invocation;
190740
+ if (invocation?.kind === "current") {
190741
+ if (invocation.runner === "npx") {
190742
+ return cli.npx === "available" ? "ok" : "npx missing";
190743
+ }
190744
+ if (invocation.runner === "bunx") {
190745
+ return cli.bunx === "missing" ? "bunx missing" : "bunx unverified";
190746
+ }
190747
+ return "unknown";
190748
+ }
190749
+ if (invocation?.kind === "legacy")
190750
+ return "repair required (legacy)";
190751
+ if (invocation?.kind === "custom")
190752
+ return "custom/unverified";
190753
+ return "unknown";
189369
190754
  }
189370
190755
  function integrationAdvice(mode, client) {
189371
190756
  if (mode === "plugin-mcp" || mode === "manual-mcp") {
@@ -189381,7 +190766,10 @@ function integrationAdvice(mode, client) {
189381
190766
  return "status: runnable on demand; package-runner fallback may need network access.";
189382
190767
  }
189383
190768
  if (mode === "mcp-only") {
189384
- return `next: run \`npx @kyo-so/cli setup ${client} --write --skill-only\``;
190769
+ return `next: run \`${formatKyosoPackageCommand({
190770
+ runner: "npx",
190771
+ cliArgs: ["setup", client, "--write", "--skill-only"]
190772
+ })}\``;
189385
190773
  }
189386
190774
  if (mode === "cli-only") {
189387
190775
  return `next: run \`kyoso setup ${client} --write --skill-only\``;
@@ -189516,8 +190904,8 @@ Do not use this skill for every coding task. It is intended for deliberate revie
189516
190904
  `;
189517
190905
 
189518
190906
  // src/cli/openRouterAcpSmoke.ts
189519
- import { mkdir as mkdir6, mkdtemp as mkdtemp2, rm as rm2 } from "node:fs/promises";
189520
- import { tmpdir as tmpdir2 } from "node:os";
190907
+ import { mkdir as mkdir6, mkdtemp as mkdtemp2, rm as rm3 } from "node:fs/promises";
190908
+ import { tmpdir as tmpdir3 } from "node:os";
189521
190909
  import { join as join8 } from "node:path";
189522
190910
 
189523
190911
  // src/acp/AcpAgentProcess.ts
@@ -191915,14 +193303,14 @@ var zGuardCreateElicitationResponseCancel = object({
191915
193303
  });
191916
193304
  // node_modules/@agentclientprotocol/sdk/dist/jsonrpc.js
191917
193305
  var CANCEL_REQUEST_METHOD = "$/cancel_request";
191918
- function isRecord10(value) {
193306
+ function isRecord11(value) {
191919
193307
  return typeof value === "object" && value !== null;
191920
193308
  }
191921
193309
  function isJsonRpcId(value) {
191922
193310
  return value === null || typeof value === "string" || typeof value === "number" && Number.isFinite(value);
191923
193311
  }
191924
193312
  function cancelRequestId(params) {
191925
- if (!isRecord10(params) || !isJsonRpcId(params["requestId"])) {
193313
+ if (!isRecord11(params) || !isJsonRpcId(params["requestId"])) {
191926
193314
  return;
191927
193315
  }
191928
193316
  return params["requestId"];
@@ -192269,7 +193657,7 @@ class Connection {
192269
193657
  if (this.abortController.signal.aborted) {
192270
193658
  return;
192271
193659
  }
192272
- if (!isRecord10(message)) {
193660
+ if (!isRecord11(message)) {
192273
193661
  console.error("Invalid message", { message });
192274
193662
  return;
192275
193663
  }
@@ -192362,7 +193750,7 @@ class Connection {
192362
193750
  pendingResponse.cleanup?.();
192363
193751
  if ("result" in response) {
192364
193752
  pendingResponse.resolve(response.result);
192365
- } else if ("error" in response && isRecord10(response.error)) {
193753
+ } else if ("error" in response && isRecord11(response.error)) {
192366
193754
  const { code, message, data } = response.error;
192367
193755
  pendingResponse.reject(new RequestError(code, message, data));
192368
193756
  } else {
@@ -192572,7 +193960,7 @@ function ndJsonStream(output2, input2) {
192572
193960
  if (trimmedLine) {
192573
193961
  try {
192574
193962
  const message = JSON.parse(trimmedLine);
192575
- if (isRecord10(message)) {
193963
+ if (isRecord11(message)) {
192576
193964
  controller.enqueue(message);
192577
193965
  } else {
192578
193966
  console.warn("Skipping JSON line that is not an object:", trimmedLine);
@@ -193981,7 +195369,7 @@ function isSeverity(value) {
193981
195369
  return typeof value === "string" && severities.includes(value);
193982
195370
  }
193983
195371
  function normalizeCisaSecureByDesign(value) {
193984
- if (!isRecord11(value))
195372
+ if (!isRecord12(value))
193985
195373
  return;
193986
195374
  const normalized = {};
193987
195375
  const customerSecurityOutcomes = normalizeGateStatus(value.customerSecurityOutcomes);
@@ -194022,7 +195410,7 @@ function isEvidenceQuality(value) {
194022
195410
  return typeof value === "string" && evidenceQualities.includes(value);
194023
195411
  }
194024
195412
  function isStrictAgentOpinion(value) {
194025
- if (!isRecord11(value) || !hasOnlyKeys(value, STRICT_ROOT_KEYS))
195413
+ if (!isRecord12(value) || !hasOnlyKeys(value, STRICT_ROOT_KEYS))
194026
195414
  return false;
194027
195415
  if (typeof value.summary !== "string")
194028
195416
  return false;
@@ -194032,7 +195420,7 @@ function isStrictAgentOpinion(value) {
194032
195420
  return value.cisaSecureByDesign === undefined || isStrictCisaSecureByDesign(value.cisaSecureByDesign);
194033
195421
  }
194034
195422
  function isStrictFinding(value) {
194035
- if (!isRecord11(value) || !hasOnlyKeys(value, STRICT_FINDING_KEYS)) {
195423
+ if (!isRecord12(value) || !hasOnlyKeys(value, STRICT_FINDING_KEYS)) {
194036
195424
  return false;
194037
195425
  }
194038
195426
  if (!isSeverity(value.severity) || !isCategory(value.category) || !isNonEmptyString(value.title) || !isNonEmptyString(value.evidence) || !isNonEmptyString(value.recommendation) || !isConfidence(value.confidence)) {
@@ -194044,11 +195432,11 @@ function isStrictFinding(value) {
194044
195432
  return true;
194045
195433
  }
194046
195434
  function isStrictFindingFiles(value) {
194047
- return Array.isArray(value) && value.every((item) => isRecord11(item) && hasOnlyKeys(item, STRICT_FILE_KEYS) && isNonEmptyString(item.path) && isOptionalLineNumber(item.lineStart) && isOptionalLineNumber(item.lineEnd));
195435
+ return Array.isArray(value) && value.every((item) => isRecord12(item) && hasOnlyKeys(item, STRICT_FILE_KEYS) && isNonEmptyString(item.path) && isOptionalLineNumber(item.lineStart) && isOptionalLineNumber(item.lineEnd));
194048
195436
  }
194049
195437
  function isStrictEvidenceRefs(value) {
194050
195438
  return Array.isArray(value) && value.length <= MAX_EVIDENCE_REFS2 && value.every((item) => {
194051
- if (!isRecord11(item) || !hasOnlyKeys(item, STRICT_EVIDENCE_REF_KEYS)) {
195439
+ if (!isRecord12(item) || !hasOnlyKeys(item, STRICT_EVIDENCE_REF_KEYS)) {
194052
195440
  return false;
194053
195441
  }
194054
195442
  if (item.kind !== "file" && item.kind !== "diff_hunk" && item.kind !== "plan_clause") {
@@ -194064,7 +195452,7 @@ function isStrictEvidenceRefs(value) {
194064
195452
  });
194065
195453
  }
194066
195454
  function isStrictCisaSecureByDesign(value) {
194067
- if (!isRecord11(value) || !hasOnlyKeys(value, STRICT_CISA_KEYS))
195455
+ if (!isRecord12(value) || !hasOnlyKeys(value, STRICT_CISA_KEYS))
194068
195456
  return false;
194069
195457
  for (const key of [
194070
195458
  "customerSecurityOutcomes",
@@ -194103,7 +195491,7 @@ function normalizeFindingFiles(value) {
194103
195491
  if (!Array.isArray(value))
194104
195492
  return;
194105
195493
  const files = value.flatMap((item) => {
194106
- if (!isRecord11(item) || typeof item.path !== "string" || item.path.trim().length === 0) {
195494
+ if (!isRecord12(item) || typeof item.path !== "string" || item.path.trim().length === 0) {
194107
195495
  return [];
194108
195496
  }
194109
195497
  const file2 = {
@@ -194123,7 +195511,7 @@ function normalizeEvidenceRefs2(value) {
194123
195511
  if (!Array.isArray(value))
194124
195512
  return;
194125
195513
  const references = value.slice(0, MAX_EVIDENCE_REFS2).flatMap((item) => {
194126
- if (!isRecord11(item) || item.kind !== "file" && item.kind !== "diff_hunk" && item.kind !== "plan_clause") {
195514
+ if (!isRecord12(item) || item.kind !== "file" && item.kind !== "diff_hunk" && item.kind !== "plan_clause") {
194127
195515
  return [];
194128
195516
  }
194129
195517
  const reference = { kind: item.kind };
@@ -194146,7 +195534,7 @@ function normalizeEvidenceRefs2(value) {
194146
195534
  function normalizeLineNumber(value) {
194147
195535
  return typeof value === "number" && Number.isSafeInteger(value) && value > 0 && value <= MAX_EVIDENCE_LINE2 ? value : undefined;
194148
195536
  }
194149
- function isRecord11(value) {
195537
+ function isRecord12(value) {
194150
195538
  return typeof value === "object" && value !== null && !Array.isArray(value);
194151
195539
  }
194152
195540
 
@@ -194554,7 +195942,7 @@ function normalizeUsage3(usage) {
194554
195942
  return normalizeModelTokenUsage(usage);
194555
195943
  }
194556
195944
  function withReportedExecutionIdentity(identity, metadata) {
194557
- const record2 = isRecord12(metadata) ? metadata : {};
195945
+ const record2 = isRecord13(metadata) ? metadata : {};
194558
195946
  return createModelExecutionIdentity({
194559
195947
  providerRoute: identity.providerRoute,
194560
195948
  requestedModel: identity.requestedModel,
@@ -194562,7 +195950,7 @@ function withReportedExecutionIdentity(identity, metadata) {
194562
195950
  reportedModel: record2.model
194563
195951
  });
194564
195952
  }
194565
- function isRecord12(value) {
195953
+ function isRecord13(value) {
194566
195954
  return value !== null && typeof value === "object" && !Array.isArray(value);
194567
195955
  }
194568
195956
  function resolveEffortConfigOption(agent, effort) {
@@ -194764,7 +196152,7 @@ async function runOpenRouterCodexAcpSmoke(options = {}) {
194764
196152
  const env = options.env ?? process.env;
194765
196153
  const { model } = validateOpenRouterCodexAcpSmoke(env);
194766
196154
  const config2 = createOpenRouterCodexAcpSmokeConfig(model);
194767
- const smokeRoot = await mkdtemp2(join8(tmpdir2(), "kyoso-openrouter-acp-smoke-"));
196155
+ const smokeRoot = await mkdtemp2(join8(tmpdir3(), "kyoso-openrouter-acp-smoke-"));
194768
196156
  const workspaceDir = join8(smokeRoot, "workspace");
194769
196157
  const homeDir = join8(smokeRoot, "home");
194770
196158
  const codexHomeDir = join8(smokeRoot, "codex-home");
@@ -194808,7 +196196,7 @@ async function runOpenRouterCodexAcpSmoke(options = {}) {
194808
196196
  }
194809
196197
  return "OpenRouter Codex ACP smoke passed.";
194810
196198
  } finally {
194811
- await rm2(smokeRoot, { recursive: true, force: true });
196199
+ await rm3(smokeRoot, { recursive: true, force: true });
194812
196200
  }
194813
196201
  }
194814
196202
  function assertUsableEnvironmentValue(env, key) {
@@ -208825,7 +210213,7 @@ function clearInheritedOpenRouterModelForProviderReset(baseConfig, overridden, o
208825
210213
  return;
208826
210214
  }
208827
210215
  const codex = readPath2(overridden, ["agents", "codex"]);
208828
- if (isRecord13(codex))
210216
+ if (isRecord14(codex))
208829
210217
  delete codex.model;
208830
210218
  }
208831
210219
  function findAssignmentForPath(overrides, path) {
@@ -208870,7 +210258,7 @@ function parseConfigOverrideValue(value, currentValue) {
208870
210258
  function readPath2(target, path) {
208871
210259
  let current = target;
208872
210260
  for (const key of path) {
208873
- if (!isRecord13(current))
210261
+ if (!isRecord14(current))
208874
210262
  return;
208875
210263
  current = current[key];
208876
210264
  }
@@ -208880,7 +210268,7 @@ function writePath2(target, path, value) {
208880
210268
  let current = target;
208881
210269
  for (const key of path.slice(0, -1)) {
208882
210270
  const child = current[key];
208883
- if (!isRecord13(child)) {
210271
+ if (!isRecord14(child)) {
208884
210272
  throw new Error(`Cannot apply --set key ${JSON.stringify(path.join("."))}.`);
208885
210273
  }
208886
210274
  current = child;
@@ -208889,7 +210277,7 @@ function writePath2(target, path, value) {
208889
210277
  if (leaf)
208890
210278
  current[leaf] = value;
208891
210279
  }
208892
- function isRecord13(value) {
210280
+ function isRecord14(value) {
208893
210281
  return typeof value === "object" && value !== null && !Array.isArray(value);
208894
210282
  }
208895
210283
 
@@ -209594,7 +210982,7 @@ function normalizeTitle(value) {
209594
210982
 
209595
210983
  // src/audit/safeTraceFile.ts
209596
210984
  import { constants as constants2 } from "node:fs";
209597
- import { lstat as lstat3, open, realpath as realpath5, stat } from "node:fs/promises";
210985
+ import { lstat as lstat4, open as open2, realpath as realpath5, stat } from "node:fs/promises";
209598
210986
  import { join as join9 } from "node:path";
209599
210987
  var AUDIT_WARNING_UNSUPPORTED_OPEN_CAPABILITY = "AUDIT_DISABLED_UNSUPPORTED_CAPABILITY: Audit trace writing requires unavailable filesystem capabilities.";
209600
210988
  async function openVerifiedTraceFile(options) {
@@ -209619,7 +211007,7 @@ async function openVerifiedTraceFile(options) {
209619
211007
  throw new Error("trace path already exists");
209620
211008
  }
209621
211009
  await options.beforeOpen?.(tracePath);
209622
- const handle = await open(tracePath, flags, 384);
211010
+ const handle = await open2(tracePath, flags, 384);
209623
211011
  try {
209624
211012
  const [handleStat, pathStat, realTracePath] = await Promise.all([
209625
211013
  handle.stat({ bigint: true }),
@@ -209657,7 +211045,7 @@ function isSafeTraceId(traceId) {
209657
211045
  }
209658
211046
  async function optionalLstat3(path) {
209659
211047
  try {
209660
- return await lstat3(path);
211048
+ return await lstat4(path);
209661
211049
  } catch (error51) {
209662
211050
  if (typeof error51 === "object" && error51 !== null && "code" in error51 && error51.code === "ENOENT") {
209663
211051
  return;
@@ -209971,7 +211359,7 @@ function validateReviewContract(request) {
209971
211359
  const contract = request.reviewContract;
209972
211360
  if (contract === undefined)
209973
211361
  return;
209974
- if (!isRecord14(contract)) {
211362
+ if (!isRecord15(contract)) {
209975
211363
  throw new KyosoRequestError("reviewContract must be an object", "VALIDATION_ERROR");
209976
211364
  }
209977
211365
  const allowedKeys = new Set(["focus", "nonGoals", "acceptedRisks"]);
@@ -209988,7 +211376,7 @@ function validateReviewContract(request) {
209988
211376
  throw new KyosoRequestError("reviewContract.nonGoals must contain at most 20 non-empty strings of 500 characters or fewer", "VALIDATION_ERROR");
209989
211377
  }
209990
211378
  const acceptedRisks = contract.acceptedRisks;
209991
- if (acceptedRisks !== undefined && (!Array.isArray(acceptedRisks) || acceptedRisks.length > 20 || acceptedRisks.some((risk) => !isRecord14(risk) || typeof risk.findingFingerprint !== "string" || !/^sha256:[0-9a-f]{64}$/.test(risk.findingFingerprint) || typeof risk.rationale !== "string" || risk.rationale.trim().length === 0 || risk.rationale.length > 500))) {
211379
+ if (acceptedRisks !== undefined && (!Array.isArray(acceptedRisks) || acceptedRisks.length > 20 || acceptedRisks.some((risk) => !isRecord15(risk) || typeof risk.findingFingerprint !== "string" || !/^sha256:[0-9a-f]{64}$/.test(risk.findingFingerprint) || typeof risk.rationale !== "string" || risk.rationale.trim().length === 0 || risk.rationale.length > 500))) {
209992
211380
  throw new KyosoRequestError("reviewContract.acceptedRisks must contain valid finding fingerprints and bounded rationales", "VALIDATION_ERROR");
209993
211381
  }
209994
211382
  }
@@ -210000,13 +211388,13 @@ function validateSelectedFiles(request) {
210000
211388
  throw new KyosoRequestError("selectedFiles must be an array", "VALIDATION_ERROR");
210001
211389
  }
210002
211390
  for (const file2 of selectedFiles) {
210003
- if (!isRecord14(file2) || typeof file2.path !== "string" || file2.path.trim().length === 0 || typeof file2.content !== "string" || file2.language !== undefined && typeof file2.language !== "string" || file2.truncated !== undefined && typeof file2.truncated !== "boolean") {
211391
+ if (!isRecord15(file2) || typeof file2.path !== "string" || file2.path.trim().length === 0 || typeof file2.content !== "string" || file2.language !== undefined && typeof file2.language !== "string" || file2.truncated !== undefined && typeof file2.truncated !== "boolean") {
210004
211392
  throw new KyosoRequestError("selectedFiles entries require string path/content and valid optional metadata", "VALIDATION_ERROR");
210005
211393
  }
210006
211394
  normalizeRelativePath(file2.path);
210007
211395
  }
210008
211396
  }
210009
- function isRecord14(value) {
211397
+ function isRecord15(value) {
210010
211398
  return typeof value === "object" && value !== null && !Array.isArray(value);
210011
211399
  }
210012
211400
 
@@ -210431,11 +211819,11 @@ function decide(input2) {
210431
211819
  }
210432
211820
 
210433
211821
  // src/workspace/createSnapshot.ts
210434
- import { chmod, mkdir as mkdir7, mkdtemp as mkdtemp3, writeFile as writeFile6 } from "node:fs/promises";
211822
+ import { chmod as chmod2, mkdir as mkdir7, mkdtemp as mkdtemp3, writeFile as writeFile6 } from "node:fs/promises";
210435
211823
  import { dirname as dirname9, join as join10 } from "node:path";
210436
- import { tmpdir as tmpdir3 } from "node:os";
211824
+ import { tmpdir as tmpdir4 } from "node:os";
210437
211825
  async function createSnapshot(traceId, tool, request, options = {}) {
210438
- const root = await mkdtemp3(join10(tmpdir3(), `kyoso-${traceId}-`));
211826
+ const root = await mkdtemp3(join10(tmpdir4(), `kyoso-${traceId}-`));
210439
211827
  const repoDir = join10(root, "repo");
210440
211828
  const contextDir = join10(root, "context");
210441
211829
  await mkdir7(repoDir, { recursive: true });
@@ -210450,7 +211838,7 @@ async function createSnapshot(traceId, tool, request, options = {}) {
210450
211838
  const dest = join10(repoDir, relative4);
210451
211839
  await mkdir7(dirname9(dest), { recursive: true });
210452
211840
  await writeFile6(dest, file2.content, "utf8");
210453
- await chmod(dest, 292).catch(() => {
211841
+ await chmod2(dest, 292).catch(() => {
210454
211842
  return;
210455
211843
  });
210456
211844
  fileCount += 1;
@@ -210486,17 +211874,17 @@ function buildSelectedFilesManifest(request) {
210486
211874
  }
210487
211875
 
210488
211876
  // src/workspace/cleanup.ts
210489
- import { rm as rm3 } from "node:fs/promises";
211877
+ import { rm as rm4 } from "node:fs/promises";
210490
211878
  async function cleanupSnapshot(path) {
210491
211879
  if (process.env.KYOSO_KEEP_TEMP === "1")
210492
211880
  return;
210493
- await rm3(path, { recursive: true, force: true });
211881
+ await rm4(path, { recursive: true, force: true });
210494
211882
  }
210495
211883
 
210496
211884
  // src/utils/ids.ts
210497
- import { randomUUID as randomUUID2 } from "node:crypto";
211885
+ import { randomUUID as randomUUID3 } from "node:crypto";
210498
211886
  function newTraceId() {
210499
- return `tr_${randomUUID2()}`;
211887
+ return `tr_${randomUUID3()}`;
210500
211888
  }
210501
211889
 
210502
211890
  // src/core/requestFingerprint.ts
@@ -210537,11 +211925,11 @@ function canonicalJson(value) {
210537
211925
  function canonicalize(value) {
210538
211926
  if (Array.isArray(value))
210539
211927
  return value.map(canonicalize);
210540
- if (!isRecord15(value))
211928
+ if (!isRecord16(value))
210541
211929
  return value;
210542
211930
  return Object.fromEntries(Object.entries(value).filter(([, child]) => child !== undefined).sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0).map(([key, child]) => [key, canonicalize(child)]));
210543
211931
  }
210544
- function isRecord15(value) {
211932
+ function isRecord16(value) {
210545
211933
  return typeof value === "object" && value !== null && !Array.isArray(value);
210546
211934
  }
210547
211935
 
@@ -210555,7 +211943,7 @@ var REVIEW_BUDGET_KEYS = new Set([
210555
211943
  ]);
210556
211944
  var MODEL_CALL_KINDS = ["primary", "verifier", "judge"];
210557
211945
  function resolveReviewBudget(ceiling, requested) {
210558
- if (requested !== undefined && !isRecord16(requested)) {
211946
+ if (requested !== undefined && !isRecord17(requested)) {
210559
211947
  throw new KyosoRequestError("options.reviewBudget must be an object.", "REVIEW_BUDGET_INVALID");
210560
211948
  }
210561
211949
  for (const [key, value] of Object.entries(requested ?? {})) {
@@ -210899,7 +212287,7 @@ function emptyReviewModelCallPlan() {
210899
212287
  ceilingEffects: []
210900
212288
  };
210901
212289
  }
210902
- function isRecord16(value) {
212290
+ function isRecord17(value) {
210903
212291
  return typeof value === "object" && value !== null && !Array.isArray(value);
210904
212292
  }
210905
212293
 
@@ -210961,7 +212349,7 @@ function parseVerificationVerdicts(rawText) {
210961
212349
  if (!Array.isArray(parsed.verdicts))
210962
212350
  return;
210963
212351
  return parsed.verdicts.flatMap((item) => {
210964
- if (!isRecord17(item))
212352
+ if (!isRecord18(item))
210965
212353
  return [];
210966
212354
  if (typeof item.findingId !== "string")
210967
212355
  return [];
@@ -211039,7 +212427,7 @@ function verificationNote(reasoning) {
211039
212427
  function isVerdict(value) {
211040
212428
  return value === "confirmed" || value === "refuted" || value === "uncertain";
211041
212429
  }
211042
- function isRecord17(value) {
212430
+ function isRecord18(value) {
211043
212431
  return typeof value === "object" && value !== null && !Array.isArray(value);
211044
212432
  }
211045
212433