@kyo-so/cli 0.7.1 → 0.9.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
@@ -7587,7 +7587,7 @@ ${lanes.join(`
7587
7587
  getCurrentDirectory,
7588
7588
  getAccessibleSortedChildDirectories,
7589
7589
  fileSystemEntryExists,
7590
- realpath,
7590
+ realpath: realpath2,
7591
7591
  setTimeout: setTimeout2,
7592
7592
  clearTimeout: clearTimeout2
7593
7593
  }) {
@@ -7763,13 +7763,13 @@ ${lanes.join(`
7763
7763
  const parentWatcher = cache.get(parentDirPath);
7764
7764
  if (!parentWatcher)
7765
7765
  return false;
7766
- const target = normalizePath(realpath(parentDir));
7766
+ const target = normalizePath(realpath2(parentDir));
7767
7767
  let hasChanges;
7768
7768
  let newChildWatches;
7769
7769
  if (filePathComparer(target, parentDir) === 0) {
7770
7770
  hasChanges = enumerateInsertsAndDeletes(fileSystemEntryExists(parentDir, 1) ? mapDefined(getAccessibleSortedChildDirectories(parentDir), (child) => {
7771
7771
  const childFullName = getNormalizedAbsolutePath(child, parentDir);
7772
- return !isIgnoredPath(childFullName, options) && filePathComparer(childFullName, normalizePath(realpath(childFullName))) === 0 ? childFullName : undefined;
7772
+ return !isIgnoredPath(childFullName, options) && filePathComparer(childFullName, normalizePath(realpath2(childFullName))) === 0 ? childFullName : undefined;
7773
7773
  }) : emptyArray, parentWatcher.childWatches, (child, childWatcher) => filePathComparer(child, childWatcher.dirName), createAndAddChildDirectoryWatcher, closeFileWatcher, addChildDirectoryWatcher);
7774
7774
  } else if (parentWatcher.targetWatcher && filePathComparer(target, parentWatcher.targetWatcher.dirName) === 0) {
7775
7775
  hasChanges = false;
@@ -7843,7 +7843,7 @@ ${lanes.join(`
7843
7843
  getCurrentDirectory,
7844
7844
  fsSupportsRecursiveFsWatch,
7845
7845
  getAccessibleSortedChildDirectories,
7846
- realpath,
7846
+ realpath: realpath2,
7847
7847
  tscWatchFile,
7848
7848
  useNonPollingWatchers,
7849
7849
  tscWatchDirectory,
@@ -7928,7 +7928,7 @@ ${lanes.join(`
7928
7928
  fileSystemEntryExists,
7929
7929
  getAccessibleSortedChildDirectories,
7930
7930
  watchDirectory: nonRecursiveWatchDirectory,
7931
- realpath,
7931
+ realpath: realpath2,
7932
7932
  setTimeout: setTimeout2,
7933
7933
  clearTimeout: clearTimeout2
7934
7934
  });
@@ -8104,7 +8104,7 @@ ${lanes.join(`
8104
8104
  fileSystemEntryExists,
8105
8105
  fsSupportsRecursiveFsWatch,
8106
8106
  getAccessibleSortedChildDirectories: (path) => getAccessibleFileSystemEntries(path).directories,
8107
- realpath,
8107
+ realpath: realpath2,
8108
8108
  tscWatchFile: process.env.TSC_WATCHFILE,
8109
8109
  useNonPollingWatchers: !!process.env.TSC_NONPOLLING_WATCHER,
8110
8110
  tscWatchDirectory: process.env.TSC_WATCHDIRECTORY,
@@ -8178,7 +8178,7 @@ ${lanes.join(`
8178
8178
  enableCPUProfiler,
8179
8179
  disableCPUProfiler,
8180
8180
  cpuProfilingEnabled: () => !!activeSession || contains(process.execArgv, "--cpu-prof") || contains(process.execArgv, "--prof"),
8181
- realpath,
8181
+ realpath: realpath2,
8182
8182
  debugMode: !!process.env.NODE_INSPECTOR_IPC || !!process.env.VSCODE_INSPECTOR_OPTIONS || some(process.execArgv, (arg) => /^--(?:inspect|debug)(?:-brk)?(?:=\d+)?$/i.test(arg)) || !!process.recordreplay,
8183
8183
  tryEnableSourceMapsForHost() {
8184
8184
  try {
@@ -8391,7 +8391,7 @@ ${lanes.join(`
8391
8391
  }
8392
8392
  }
8393
8393
  function readDirectory(path, extensions, excludes, includes, depth) {
8394
- return matchFiles(path, extensions, excludes, includes, useCaseSensitiveFileNames2, process.cwd(), depth, getAccessibleFileSystemEntries, realpath);
8394
+ return matchFiles(path, extensions, excludes, includes, useCaseSensitiveFileNames2, process.cwd(), depth, getAccessibleFileSystemEntries, realpath2);
8395
8395
  }
8396
8396
  function fileSystemEntryExists(path, entryKind) {
8397
8397
  const stat = statSync(path);
@@ -8419,7 +8419,7 @@ ${lanes.join(`
8419
8419
  function fsRealPathHandlingLongPath(path) {
8420
8420
  return path.length < 260 ? _fs.realpathSync.native(path) : _fs.realpathSync(path);
8421
8421
  }
8422
- function realpath(path) {
8422
+ function realpath2(path) {
8423
8423
  try {
8424
8424
  return fsRealpath(path);
8425
8425
  } catch {
@@ -19724,18 +19724,18 @@ ${lanes.join(`
19724
19724
  diagnostics.add(createCompilerDiagnostic(Diagnostics.Could_not_write_file_0_Colon_1, fileName, hostErrorMessage));
19725
19725
  }, sourceFiles, data);
19726
19726
  }
19727
- function ensureDirectoriesExist(directoryPath, createDirectory, directoryExists) {
19727
+ function ensureDirectoriesExist(directoryPath, createDirectory2, directoryExists) {
19728
19728
  if (directoryPath.length > getRootLength(directoryPath) && !directoryExists(directoryPath)) {
19729
19729
  const parentDirectory = getDirectoryPath(directoryPath);
19730
- ensureDirectoriesExist(parentDirectory, createDirectory, directoryExists);
19731
- createDirectory(directoryPath);
19730
+ ensureDirectoriesExist(parentDirectory, createDirectory2, directoryExists);
19731
+ createDirectory2(directoryPath);
19732
19732
  }
19733
19733
  }
19734
- function writeFileEnsuringDirectories(path, data, writeByteOrderMark, writeFile22, createDirectory, directoryExists) {
19734
+ function writeFileEnsuringDirectories(path, data, writeByteOrderMark, writeFile22, createDirectory2, directoryExists) {
19735
19735
  try {
19736
19736
  writeFile22(path, data, writeByteOrderMark);
19737
19737
  } catch {
19738
- ensureDirectoriesExist(getDirectoryPath(normalizePath(path)), createDirectory, directoryExists);
19738
+ ensureDirectoriesExist(getDirectoryPath(normalizePath(path)), createDirectory2, directoryExists);
19739
19739
  writeFile22(path, data, writeByteOrderMark);
19740
19740
  }
19741
19741
  }
@@ -21670,7 +21670,7 @@ ${lanes.join(`
21670
21670
  function getRegexFromPattern(pattern, useCaseSensitiveFileNames2) {
21671
21671
  return new RegExp(pattern, useCaseSensitiveFileNames2 ? "" : "i");
21672
21672
  }
21673
- function matchFiles(path, extensions, excludes, includes, useCaseSensitiveFileNames2, currentDirectory, depth, getFileSystemEntries, realpath) {
21673
+ function matchFiles(path, extensions, excludes, includes, useCaseSensitiveFileNames2, currentDirectory, depth, getFileSystemEntries, realpath2) {
21674
21674
  path = normalizePath(path);
21675
21675
  currentDirectory = normalizePath(currentDirectory);
21676
21676
  const patterns = getFileMatcherPatterns(path, excludes, includes, useCaseSensitiveFileNames2, currentDirectory);
@@ -21685,7 +21685,7 @@ ${lanes.join(`
21685
21685
  }
21686
21686
  return flatten(results);
21687
21687
  function visitDirectory(path2, absolutePath, depth2) {
21688
- const canonicalPath = toCanonical(realpath(absolutePath));
21688
+ const canonicalPath = toCanonical(realpath2(absolutePath));
21689
21689
  if (visited.has(canonicalPath))
21690
21690
  return;
21691
21691
  visited.set(canonicalPath, true);
@@ -41674,11 +41674,11 @@ ${lanes.join(`
41674
41674
  if (i < rootLength) {
41675
41675
  return;
41676
41676
  }
41677
- const sep = directory.lastIndexOf(directorySeparator, i - 1);
41678
- if (sep === -1) {
41677
+ const sep3 = directory.lastIndexOf(directorySeparator, i - 1);
41678
+ if (sep3 === -1) {
41679
41679
  return;
41680
41680
  }
41681
- return directory.substr(0, Math.max(sep, rootLength));
41681
+ return directory.substr(0, Math.max(sep3, rootLength));
41682
41682
  }
41683
41683
  }
41684
41684
  }
@@ -98915,14 +98915,14 @@ ${lanes.join(`
98915
98915
  }
98916
98916
  }
98917
98917
  function createImportCallExpressionAMD(arg, containsLexicalThis) {
98918
- const resolve = factory2.createUniqueName("resolve");
98918
+ const resolve3 = factory2.createUniqueName("resolve");
98919
98919
  const reject = factory2.createUniqueName("reject");
98920
98920
  const parameters = [
98921
- factory2.createParameterDeclaration(undefined, undefined, resolve),
98921
+ factory2.createParameterDeclaration(undefined, undefined, resolve3),
98922
98922
  factory2.createParameterDeclaration(undefined, undefined, reject)
98923
98923
  ];
98924
98924
  const body = factory2.createBlock([
98925
- factory2.createExpressionStatement(factory2.createCallExpression(factory2.createIdentifier("require"), undefined, [factory2.createArrayLiteralExpression([arg || factory2.createOmittedExpression()]), resolve, reject]))
98925
+ factory2.createExpressionStatement(factory2.createCallExpression(factory2.createIdentifier("require"), undefined, [factory2.createArrayLiteralExpression([arg || factory2.createOmittedExpression()]), resolve3, reject]))
98926
98926
  ]);
98927
98927
  let func;
98928
98928
  if (languageVersion >= 2) {
@@ -107027,12 +107027,12 @@ ${lanes.join(`
107027
107027
  directoryExists: host.directoryExists && directoryExists,
107028
107028
  getDirectories,
107029
107029
  readDirectory,
107030
- createDirectory: host.createDirectory && createDirectory,
107030
+ createDirectory: host.createDirectory && createDirectory2,
107031
107031
  writeFile: host.writeFile && writeFile22,
107032
107032
  addOrDeleteFileOrDirectory,
107033
107033
  addOrDeleteFile,
107034
107034
  clearCache,
107035
- realpath: host.realpath && realpath
107035
+ realpath: host.realpath && realpath2
107036
107036
  };
107037
107037
  function toPath3(fileName) {
107038
107038
  return toPath(fileName, currentDirectory, getCanonicalFileName);
@@ -107104,7 +107104,7 @@ ${lanes.join(`
107104
107104
  const path = toPath3(dirPath);
107105
107105
  return cachedReadDirectoryResult.has(ensureTrailingDirectorySeparator(path)) || host.directoryExists(dirPath);
107106
107106
  }
107107
- function createDirectory(dirPath) {
107107
+ function createDirectory2(dirPath) {
107108
107108
  const path = toPath3(dirPath);
107109
107109
  const result = getCachedFileSystemEntriesForBaseDir(path);
107110
107110
  if (result) {
@@ -107130,7 +107130,7 @@ ${lanes.join(`
107130
107130
  const rootResult = tryReadDirectory2(rootDir, rootDirPath);
107131
107131
  let rootSymLinkResult;
107132
107132
  if (rootResult !== undefined) {
107133
- return matchFiles(rootDir, extensions, excludes, includes, useCaseSensitiveFileNames2, currentDirectory, depth, getFileSystemEntries, realpath);
107133
+ return matchFiles(rootDir, extensions, excludes, includes, useCaseSensitiveFileNames2, currentDirectory, depth, getFileSystemEntries, realpath2);
107134
107134
  }
107135
107135
  return host.readDirectory(rootDir, extensions, excludes, includes, depth);
107136
107136
  function getFileSystemEntries(dir) {
@@ -107153,7 +107153,7 @@ ${lanes.join(`
107153
107153
  return result;
107154
107154
  }
107155
107155
  }
107156
- function realpath(s) {
107156
+ function realpath2(s) {
107157
107157
  return host.realpath ? host.realpath(s) : s;
107158
107158
  }
107159
107159
  function clearFirstAncestorEntry(fileOrDirectoryPath) {
@@ -107527,11 +107527,11 @@ ${lanes.join(`
107527
107527
  return text !== undefined ? createSourceFile(fileName, text, languageVersionOrOptions, setParentNodes) : undefined;
107528
107528
  };
107529
107529
  }
107530
- function createWriteFileMeasuringIO(actualWriteFile, createDirectory, directoryExists) {
107530
+ function createWriteFileMeasuringIO(actualWriteFile, createDirectory2, directoryExists) {
107531
107531
  return (fileName, data, writeByteOrderMark, onError) => {
107532
107532
  try {
107533
107533
  mark("beforeIOWrite");
107534
- writeFileEnsuringDirectories(fileName, data, writeByteOrderMark, actualWriteFile, createDirectory, directoryExists);
107534
+ writeFileEnsuringDirectories(fileName, data, writeByteOrderMark, actualWriteFile, createDirectory2, directoryExists);
107535
107535
  mark("afterIOWrite");
107536
107536
  measure("I/O Write", "beforeIOWrite", "afterIOWrite");
107537
107537
  } catch (e) {
@@ -107558,7 +107558,7 @@ ${lanes.join(`
107558
107558
  return getDirectoryPath(normalizePath(system.getExecutingFilePath()));
107559
107559
  }
107560
107560
  const newLine = getNewLineCharacter(options);
107561
- const realpath = system.realpath && ((path) => system.realpath(path));
107561
+ const realpath2 = system.realpath && ((path) => system.realpath(path));
107562
107562
  const compilerHost = {
107563
107563
  getSourceFile: createGetSourceFile((fileName) => compilerHost.readFile(fileName), setParentNodes),
107564
107564
  getDefaultLibLocation,
@@ -107574,7 +107574,7 @@ ${lanes.join(`
107574
107574
  directoryExists: (directoryName) => system.directoryExists(directoryName),
107575
107575
  getEnvironmentVariable: (name) => system.getEnvironmentVariable ? system.getEnvironmentVariable(name) : "",
107576
107576
  getDirectories: (path) => system.getDirectories(path),
107577
- realpath,
107577
+ realpath: realpath2,
107578
107578
  readDirectory: (path, extensions, include, exclude, depth) => system.readDirectory(path, extensions, include, exclude, depth),
107579
107579
  createDirectory: (d) => system.createDirectory(d),
107580
107580
  createHash: maybeBind(system, system.createHash)
@@ -108611,10 +108611,10 @@ ${lanes.join(`
108611
108611
  }
108612
108612
  function getDefaultLibFilePriority(a) {
108613
108613
  if (containsPath(defaultLibraryPath, a.fileName, false)) {
108614
- const basename = getBaseFileName(a.fileName);
108615
- if (basename === "lib.d.ts" || basename === "lib.es6.d.ts")
108614
+ const basename2 = getBaseFileName(a.fileName);
108615
+ if (basename2 === "lib.d.ts" || basename2 === "lib.es6.d.ts")
108616
108616
  return 0;
108617
- const name = removeSuffix(removePrefix(basename, "lib."), ".d.ts");
108617
+ const name = removeSuffix(removePrefix(basename2, "lib."), ".d.ts");
108618
108618
  const index = libs.indexOf(name);
108619
108619
  if (index !== -1)
108620
108620
  return index + 1;
@@ -160210,9 +160210,9 @@ ${options.prefix}` : `
160210
160210
  if (this.host.realpath) {
160211
160211
  Debug.assert(!!this.containingProjects.length);
160212
160212
  const project = this.containingProjects[0];
160213
- const realpath = this.host.realpath(this.path);
160214
- if (realpath) {
160215
- this.realpath = project.toPath(realpath);
160213
+ const realpath2 = this.host.realpath(this.path);
160214
+ if (realpath2) {
160215
+ this.realpath = project.toPath(realpath2);
160216
160216
  if (this.realpath !== this.path) {
160217
160217
  project.projectService.realpathToScriptInfos.add(this.realpath, this);
160218
160218
  }
@@ -161298,8 +161298,8 @@ ${options.prefix}` : `
161298
161298
  }
161299
161299
  };
161300
161300
  for (const file2 of files) {
161301
- const basename = getBaseFileName(file2);
161302
- if (basename === "package.json" || basename === "bower.json") {
161301
+ const basename2 = getBaseFileName(file2);
161302
+ if (basename2 === "package.json" || basename2 === "bower.json") {
161303
161303
  createProjectWatcher(file2, "FileWatcher");
161304
161304
  continue;
161305
161305
  }
@@ -163486,9 +163486,9 @@ ${options.prefix}` : `
163486
163486
  this.filenameToScriptInfo.delete(info.path);
163487
163487
  this.filenameToScriptInfoVersion.set(info.path, info.textStorage.version);
163488
163488
  this.stopWatchingScriptInfo(info);
163489
- const realpath = info.getRealpathIfDifferent();
163490
- if (realpath) {
163491
- this.realpathToScriptInfos.remove(realpath, info);
163489
+ const realpath2 = info.getRealpathIfDifferent();
163490
+ if (realpath2) {
163491
+ this.realpathToScriptInfos.remove(realpath2, info);
163492
163492
  }
163493
163493
  info.closeSourceMapFileWatcher();
163494
163494
  }
@@ -164146,9 +164146,9 @@ All files are: ${JSON.stringify(names)}`, "Err");
164146
164146
  getSymlinkedProjects(info) {
164147
164147
  let projects;
164148
164148
  if (this.realpathToScriptInfos) {
164149
- const realpath = info.getRealpathIfDifferent();
164150
- if (realpath) {
164151
- forEach(this.realpathToScriptInfos.get(realpath), combineProjects);
164149
+ const realpath2 = info.getRealpathIfDifferent();
164150
+ if (realpath2) {
164151
+ forEach(this.realpathToScriptInfos.get(realpath2), combineProjects);
164152
164152
  }
164153
164153
  forEach(this.realpathToScriptInfos.get(info.path), combineProjects);
164154
164154
  }
@@ -164186,8 +164186,8 @@ All files are: ${JSON.stringify(names)}`, "Err");
164186
164186
  const fileOrDirectoryPath = removeIgnoredPath(this.toPath(fileOrDirectory));
164187
164187
  if (!fileOrDirectoryPath)
164188
164188
  return;
164189
- const basename = getBaseFileName(fileOrDirectoryPath);
164190
- if (((_a3 = result.affectedModuleSpecifierCacheProjects) == null ? undefined : _a3.size) && (basename === "package.json" || basename === "node_modules")) {
164189
+ const basename2 = getBaseFileName(fileOrDirectoryPath);
164190
+ if (((_a3 = result.affectedModuleSpecifierCacheProjects) == null ? undefined : _a3.size) && (basename2 === "package.json" || basename2 === "node_modules")) {
164191
164191
  result.affectedModuleSpecifierCacheProjects.forEach((project) => {
164192
164192
  var _a22;
164193
164193
  (_a22 = project.getModuleSpecifierCache()) == null || _a22.clear();
@@ -169234,8 +169234,8 @@ Additional information: BADCLIENT: Bad error code, ${badCode} not found in range
169234
169234
  installPackage(options) {
169235
169235
  this.packageInstallId++;
169236
169236
  const request = { kind: "installPackage", ...options, id: this.packageInstallId };
169237
- const promise2 = new Promise((resolve, reject) => {
169238
- (this.packageInstalledPromise ?? (this.packageInstalledPromise = /* @__PURE__ */ new Map)).set(this.packageInstallId, { resolve, reject });
169237
+ const promise2 = new Promise((resolve3, reject) => {
169238
+ (this.packageInstalledPromise ?? (this.packageInstalledPromise = /* @__PURE__ */ new Map)).set(this.packageInstallId, { resolve: resolve3, reject });
169239
169239
  });
169240
169240
  this.installer.send(request);
169241
169241
  return promise2;
@@ -169495,7 +169495,7 @@ Additional information: BADCLIENT: Bad error code, ${badCode} not found in range
169495
169495
  });
169496
169496
 
169497
169497
  // src/cli/main.ts
169498
- import { spawnSync as spawnSync2 } from "node:child_process";
169498
+ import { spawnSync as spawnSync3 } from "node:child_process";
169499
169499
 
169500
169500
  // src/cli/args.ts
169501
169501
  function parseArgs(argv) {
@@ -169599,14 +169599,364 @@ function languageFromPath(path) {
169599
169599
  }
169600
169600
 
169601
169601
  // src/cli/doctor.ts
169602
- import { accessSync } from "node:fs";
169603
- import { delimiter as delimiter2, resolve as resolve4 } from "node:path";
169602
+ import { accessSync as accessSync2 } from "node:fs";
169603
+ import { delimiter as delimiter3, resolve as resolve8 } from "node:path";
169604
+
169605
+ // src/audit/stateRoot.ts
169606
+ import { createHash } from "node:crypto";
169607
+ import { lstat, mkdir as mkdir2, realpath } from "node:fs/promises";
169608
+ import { basename, dirname as dirname2, isAbsolute, join, resolve as resolve2 } from "node:path";
169609
+
169610
+ // src/context/pathPolicy.ts
169611
+ import { normalize, sep } from "node:path";
169612
+
169613
+ // src/core/errors.ts
169614
+ class KyosoRequestError extends Error {
169615
+ code;
169616
+ constructor(message, code) {
169617
+ super(message);
169618
+ this.code = code;
169619
+ this.name = "KyosoRequestError";
169620
+ }
169621
+ }
169622
+
169623
+ // src/context/pathPolicy.ts
169624
+ function normalizeRelativePath(path) {
169625
+ const normalized = normalize(path).replaceAll("\\", "/");
169626
+ if (normalized === "." || normalized.startsWith("..") || normalized.includes(`${sep}..${sep}`) || normalized.startsWith("/") || /^[A-Za-z]:\//.test(normalized)) {
169627
+ throw new KyosoRequestError(`Path escapes workspace root: ${path}`, "INVALID_PATH");
169628
+ }
169629
+ return normalized;
169630
+ }
169631
+ function isDeniedPath(path, denyPatterns) {
169632
+ return matchesPathPattern(path, denyPatterns, "deny");
169633
+ }
169634
+ function isAllowedPath(path, allowPatterns) {
169635
+ if (allowPatterns.length === 0)
169636
+ return true;
169637
+ return matchesPathPattern(path, allowPatterns, "allow");
169638
+ }
169639
+ function matchesPathPattern(path, patterns, mode) {
169640
+ const normalized = normalizeRelativePath(path);
169641
+ const segments = normalized.split("/");
169642
+ return patterns.some((pattern) => {
169643
+ const normalizedPattern = normalizeRelativePath(pattern);
169644
+ if (normalizedPattern.endsWith(".*") && !normalizedPattern.includes("/")) {
169645
+ const base = normalizedPattern.slice(0, -2);
169646
+ if (mode === "allow") {
169647
+ const firstSegment = segments[0] ?? "";
169648
+ return firstSegment === base || firstSegment.startsWith(`${base}.`);
169649
+ }
169650
+ return segments.some((segment) => segment === base || segment.startsWith(`${base}.`));
169651
+ }
169652
+ if (normalizedPattern.includes("*")) {
169653
+ const escaped = normalizedPattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replaceAll("*", ".*");
169654
+ const regex = mode === "allow" ? new RegExp(`^${escaped}($|/)`) : new RegExp(`(^|/)${escaped}($|/)`);
169655
+ return regex.test(normalized);
169656
+ }
169657
+ if (!normalizedPattern.includes("/")) {
169658
+ if (mode === "allow") {
169659
+ return normalized === normalizedPattern || normalized.startsWith(`${normalizedPattern}/`);
169660
+ }
169661
+ return segments.includes(normalizedPattern);
169662
+ }
169663
+ if (mode === "allow") {
169664
+ return normalized === normalizedPattern || normalized.startsWith(`${normalizedPattern}/`);
169665
+ }
169666
+ return normalized === normalizedPattern || normalized.startsWith(`${normalizedPattern}/`) || normalized.endsWith(`/${normalizedPattern}`) || normalized.includes(`/${normalizedPattern}/`);
169667
+ });
169668
+ }
169669
+
169670
+ // src/core/constants.ts
169671
+ var DEFAULT_AGENT_TIMEOUT_MS = 120000;
169672
+ var RAW_OUTPUT_MAX_CHARS = 16384;
169673
+ var TRACE_DIR = ".kyoso/traces";
169674
+ var KYOSO_CHILD_AGENT = "KYOSO_CHILD_AGENT";
169675
+ var KYOSO_VERSION = "0.9.0";
169676
+
169677
+ // src/utils/pathContainment.ts
169678
+ import { resolve, sep as sep2 } from "node:path";
169679
+ function isPathWithin(candidate, root) {
169680
+ const resolvedCandidate = resolve(candidate);
169681
+ const resolvedRoot = resolve(root);
169682
+ const boundary = resolvedRoot.endsWith(sep2) ? resolvedRoot : `${resolvedRoot}${sep2}`;
169683
+ return resolvedCandidate === resolvedRoot || resolvedCandidate.startsWith(boundary);
169684
+ }
169685
+
169686
+ // src/audit/stateRoot.ts
169687
+ var PRIVATE_DIRECTORY_MODE = 448;
169688
+ var UNSAFE_DIRECTORY_MODE = 18;
169689
+ var STICKY_DIRECTORY_MODE = 512;
169690
+ var AUDIT_WARNING_DIRECTORY_IGNORED = "AUDIT_DIRECTORY_IGNORED: Audit directory is invalid; using the default logical directory.";
169691
+ var AUDIT_WARNING_UNSUPPORTED_PLATFORM = "AUDIT_DISABLED_UNSUPPORTED_PLATFORM: Audit trace writing is unavailable on this platform.";
169692
+ var AUDIT_WARNING_UNSUPPORTED_CAPABILITY = "AUDIT_DISABLED_UNSUPPORTED_CAPABILITY: Audit trace writing requires unavailable filesystem capabilities.";
169693
+ var AUDIT_WARNING_UNSAFE_STATE_ROOT = "AUDIT_DISABLED_UNSAFE_STATE_ROOT: Audit state root could not be verified.";
169694
+ async function resolveAuditStateRoot(options) {
169695
+ const warnings = [];
169696
+ const logicalDirectory = validateAuditDirectory(options.directory, warnings);
169697
+ const platform = options.platform ?? process.platform;
169698
+ if (platform === "win32") {
169699
+ return { warnings: [...warnings, AUDIT_WARNING_UNSUPPORTED_PLATFORM] };
169700
+ }
169701
+ const uid = getCurrentUid(options.getuid);
169702
+ if (uid === undefined) {
169703
+ return { warnings: [...warnings, AUDIT_WARNING_UNSUPPORTED_CAPABILITY] };
169704
+ }
169705
+ try {
169706
+ const workspaceRoot = await realpath(resolve2(options.cwd));
169707
+ const candidate = resolveStateBaseCandidate(options.env ?? process.env);
169708
+ if (!candidate)
169709
+ throw new Error("missing trusted state base");
169710
+ const stateBase = await ensureTrustedStateBase({
169711
+ candidate,
169712
+ workspaceRoot,
169713
+ uid
169714
+ });
169715
+ const kyosoRoot = await ensureTrustedDirectory({
169716
+ root: stateBase,
169717
+ segments: ["kyoso"],
169718
+ uid,
169719
+ workspaceRoot
169720
+ });
169721
+ return {
169722
+ stateBase,
169723
+ kyosoRoot,
169724
+ workspaceRoot,
169725
+ workspaceHash: createHash("sha256").update(workspaceRoot).digest("hex"),
169726
+ logicalDirectory,
169727
+ uid,
169728
+ warnings
169729
+ };
169730
+ } catch {
169731
+ return { warnings: [...warnings, AUDIT_WARNING_UNSAFE_STATE_ROOT] };
169732
+ }
169733
+ }
169734
+ async function inspectAuditStateRootCapability(options) {
169735
+ const platform = options.platform ?? process.platform;
169736
+ if (platform === "win32" || getCurrentUid(options.getuid) === undefined) {
169737
+ return { available: false };
169738
+ }
169739
+ try {
169740
+ const uid = getCurrentUid(options.getuid);
169741
+ if (uid === undefined)
169742
+ return { available: false };
169743
+ const workspaceRoot = await realpath(resolve2(options.cwd));
169744
+ const candidate = resolveStateBaseCandidate(options.env ?? process.env);
169745
+ if (!candidate || isPathWithin(candidate, workspaceRoot)) {
169746
+ return { available: false };
169747
+ }
169748
+ const existing = await findExistingAncestor(candidate);
169749
+ assertSafeDirectory(existing.path, uid, await lstat(existing.path), {
169750
+ allowFilesystemRoot: true
169751
+ });
169752
+ const realExisting = await realpath(existing.path);
169753
+ if (isPathWithin(realExisting, workspaceRoot))
169754
+ return { available: false };
169755
+ await assertTrustedAncestorChain(realExisting, uid);
169756
+ if (existing.missingSegments.length === 0) {
169757
+ const kyosoRoot = join(realExisting, "kyoso");
169758
+ const kyosoStat = await optionalLstat(kyosoRoot);
169759
+ if (kyosoStat)
169760
+ assertSafeDirectory(kyosoRoot, uid, kyosoStat);
169761
+ }
169762
+ return { available: true };
169763
+ } catch {
169764
+ return { available: false };
169765
+ }
169766
+ }
169767
+ async function ensureTrustedDirectory(options) {
169768
+ const realRoot = await realpath(options.root);
169769
+ assertSafeDirectory(realRoot, options.uid, await lstat(realRoot));
169770
+ if (options.workspaceRoot && isPathWithin(realRoot, options.workspaceRoot)) {
169771
+ throw new Error("trusted directory resolves inside workspace");
169772
+ }
169773
+ await assertTrustedAncestorChain(realRoot, options.uid);
169774
+ let current = realRoot;
169775
+ for (const segment of options.segments) {
169776
+ if (!isSafePathSegment(segment))
169777
+ throw new Error("unsafe path segment");
169778
+ const next = join(current, segment);
169779
+ let entry = await optionalLstat(next);
169780
+ if (!entry) {
169781
+ await createDirectory(next);
169782
+ entry = await lstat(next);
169783
+ }
169784
+ assertSafeDirectory(next, options.uid, entry);
169785
+ const realNext = await realpath(next);
169786
+ if (!isPathWithin(realNext, realRoot)) {
169787
+ throw new Error("managed directory escaped trusted root");
169788
+ }
169789
+ if (options.workspaceRoot && isPathWithin(realNext, options.workspaceRoot)) {
169790
+ throw new Error("managed directory resolves inside workspace");
169791
+ }
169792
+ current = realNext;
169793
+ }
169794
+ return current;
169795
+ }
169796
+ function isResolvedAuditStateRoot(resolution) {
169797
+ return "kyosoRoot" in resolution;
169798
+ }
169799
+ function validateAuditDirectory(directory, warnings) {
169800
+ try {
169801
+ if (directory.trim().length === 0 || isAbsolute(directory) || directory.split(/[\\/]+/).includes("..")) {
169802
+ throw new Error("unsafe logical directory");
169803
+ }
169804
+ const normalized = normalizeRelativePath(directory);
169805
+ if (normalized === "." || normalized.split("/").includes("..")) {
169806
+ throw new Error("unsafe logical directory");
169807
+ }
169808
+ return normalized;
169809
+ } catch {
169810
+ warnings.push(AUDIT_WARNING_DIRECTORY_IGNORED);
169811
+ return TRACE_DIR;
169812
+ }
169813
+ }
169814
+ function resolveStateBaseCandidate(env) {
169815
+ const xdgStateHome = env.XDG_STATE_HOME?.trim();
169816
+ if (xdgStateHome && isAbsolute(xdgStateHome)) {
169817
+ return resolve2(xdgStateHome);
169818
+ }
169819
+ const home = env.HOME?.trim();
169820
+ if (!home || !isAbsolute(home))
169821
+ return;
169822
+ return join(resolve2(home), ".local", "state");
169823
+ }
169824
+ async function ensureTrustedStateBase(options) {
169825
+ if (isPathWithin(options.candidate, options.workspaceRoot)) {
169826
+ throw new Error("state base is inside workspace");
169827
+ }
169828
+ const existing = await findExistingAncestor(options.candidate);
169829
+ assertSafeDirectory(existing.path, options.uid, await lstat(existing.path), {
169830
+ allowFilesystemRoot: true
169831
+ });
169832
+ const realExisting = await realpath(existing.path);
169833
+ if (isPathWithin(realExisting, options.workspaceRoot)) {
169834
+ throw new Error("state base resolves inside workspace");
169835
+ }
169836
+ await assertTrustedAncestorChain(realExisting, options.uid);
169837
+ let current = realExisting;
169838
+ for (const segment of existing.missingSegments) {
169839
+ current = join(current, segment);
169840
+ await createDirectory(current);
169841
+ const entry = await lstat(current);
169842
+ assertSafeDirectory(current, options.uid, entry);
169843
+ const realCurrent = await realpath(current);
169844
+ if (!isPathWithin(realCurrent, realExisting)) {
169845
+ throw new Error("state base changed while being created");
169846
+ }
169847
+ if (isPathWithin(realCurrent, options.workspaceRoot)) {
169848
+ throw new Error("state base resolves inside workspace");
169849
+ }
169850
+ current = realCurrent;
169851
+ }
169852
+ const stateBase = await realpath(current);
169853
+ assertSafeDirectory(stateBase, options.uid, await lstat(stateBase));
169854
+ if (isPathWithin(stateBase, options.workspaceRoot)) {
169855
+ throw new Error("state base resolves inside workspace");
169856
+ }
169857
+ await assertTrustedAncestorChain(stateBase, options.uid);
169858
+ return stateBase;
169859
+ }
169860
+ async function findExistingAncestor(candidate) {
169861
+ let current = resolve2(candidate);
169862
+ const missingSegments = [];
169863
+ while (true) {
169864
+ const entry = await optionalLstat(current);
169865
+ if (entry) {
169866
+ if (entry.isSymbolicLink() || !entry.isDirectory()) {
169867
+ throw new Error("state base ancestor is unsafe");
169868
+ }
169869
+ return { path: current, missingSegments };
169870
+ }
169871
+ const parent = dirname2(current);
169872
+ if (parent === current)
169873
+ throw new Error("state base has no existing ancestor");
169874
+ missingSegments.unshift(basename(current));
169875
+ current = parent;
169876
+ }
169877
+ }
169878
+ function assertSafeDirectory(path, uid, entry, options = {}) {
169879
+ const stat = entry;
169880
+ if (!stat || stat.isSymbolicLink() || !stat.isDirectory()) {
169881
+ throw new Error("state directory is unsafe");
169882
+ }
169883
+ if (numericStatValue(stat.uid) !== uid && !(options.allowFilesystemRoot && isFilesystemRoot(path))) {
169884
+ throw new Error("state directory owner is unsafe");
169885
+ }
169886
+ const mode = numericStatValue(stat.mode);
169887
+ if ((mode & UNSAFE_DIRECTORY_MODE) !== 0) {
169888
+ throw new Error("state directory mode is unsafe");
169889
+ }
169890
+ }
169891
+ function numericStatValue(value) {
169892
+ return typeof value === "number" ? value : Number(value);
169893
+ }
169894
+ function getCurrentUid(getuid) {
169895
+ const resolveUid = getuid ?? (typeof process.getuid === "function" ? process.getuid.bind(process) : undefined);
169896
+ try {
169897
+ const uid = resolveUid?.();
169898
+ return typeof uid === "number" && Number.isInteger(uid) && uid >= 0 ? uid : undefined;
169899
+ } catch {
169900
+ return;
169901
+ }
169902
+ }
169903
+ function isFilesystemRoot(path) {
169904
+ return dirname2(path) === path;
169905
+ }
169906
+ async function assertTrustedAncestorChain(path, uid) {
169907
+ let child = path;
169908
+ while (!isFilesystemRoot(child)) {
169909
+ const parent = dirname2(child);
169910
+ const [parentStat, childStat] = await Promise.all([
169911
+ lstat(parent),
169912
+ lstat(child)
169913
+ ]);
169914
+ if (parentStat.isSymbolicLink() || !parentStat.isDirectory()) {
169915
+ throw new Error("state directory ancestor is unsafe");
169916
+ }
169917
+ const parentMode = numericStatValue(parentStat.mode);
169918
+ const parentUid = numericStatValue(parentStat.uid);
169919
+ const childUid = numericStatValue(childStat.uid);
169920
+ const parentWritableByOthers = (parentMode & UNSAFE_DIRECTORY_MODE) !== 0;
169921
+ const stickyChildEntry = (parentMode & STICKY_DIRECTORY_MODE) !== 0 && childUid === uid;
169922
+ if (parentUid !== uid && parentUid !== 0 || parentWritableByOthers && !stickyChildEntry) {
169923
+ throw new Error("state directory ancestor permissions are unsafe");
169924
+ }
169925
+ child = parent;
169926
+ }
169927
+ }
169928
+ function isSafePathSegment(segment) {
169929
+ return segment.length > 0 && segment !== "." && segment !== ".." && !segment.includes("/") && !segment.includes("\\");
169930
+ }
169931
+ async function optionalLstat(path) {
169932
+ try {
169933
+ return await lstat(path);
169934
+ } catch (error) {
169935
+ if (isMissingPathError(error))
169936
+ return;
169937
+ throw error;
169938
+ }
169939
+ }
169940
+ async function createDirectory(path) {
169941
+ try {
169942
+ await mkdir2(path, { mode: PRIVATE_DIRECTORY_MODE });
169943
+ } catch (error) {
169944
+ if (!isAlreadyExistsError(error))
169945
+ throw error;
169946
+ }
169947
+ }
169948
+ function isMissingPathError(error) {
169949
+ return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
169950
+ }
169951
+ function isAlreadyExistsError(error) {
169952
+ return typeof error === "object" && error !== null && "code" in error && error.code === "EEXIST";
169953
+ }
169604
169954
 
169605
169955
  // src/config/loadConfig.ts
169606
169956
  import { access as access2, readFile as readFile4 } from "node:fs/promises";
169607
169957
  import { homedir as homedir2 } from "node:os";
169608
169958
  import { stderr, stdin } from "node:process";
169609
- import { extname as extname3, join as join2, resolve as resolve2 } from "node:path";
169959
+ import { extname as extname3, join as join3, resolve as resolve4 } from "node:path";
169610
169960
  import { createInterface as createInterface2 } from "node:readline/promises";
169611
169961
 
169612
169962
  // src/config/defaultConfig.ts
@@ -169739,6 +170089,28 @@ var defaultConfig = {
169739
170089
 
169740
170090
  // src/config/projectScope.ts
169741
170091
  var PROJECT_GLOBAL_ONLY_MESSAGE = "Move global-only settings to the user global config:";
170092
+ var kyosoConfigOverridePaths = [
170093
+ "agents.codex.enabled",
170094
+ "agents.codex.model",
170095
+ "agents.codex.effort",
170096
+ "agents.codex.role",
170097
+ "agents.codex.timeoutMs",
170098
+ "agents.claude.enabled",
170099
+ "agents.claude.model",
170100
+ "agents.claude.effort",
170101
+ "agents.claude.role",
170102
+ "agents.claude.timeoutMs",
170103
+ "verification.enabled",
170104
+ "verification.maxFindings",
170105
+ "verification.timeoutMs",
170106
+ "judge.mode",
170107
+ "judge.provider",
170108
+ "judge.timeoutMs"
170109
+ ];
170110
+ var CONFIG_OVERRIDE_PATHS = new Set(kyosoConfigOverridePaths);
170111
+ function isAllowedConfigOverridePath(path) {
170112
+ return CONFIG_OVERRIDE_PATHS.has(path.join("."));
170113
+ }
169742
170114
  function mergeProjectTomlConfig(baseConfig, projectConfig, options) {
169743
170115
  const violations = collectProjectScopeViolations(projectConfig);
169744
170116
  if (violations.length > 0) {
@@ -169771,13 +170143,9 @@ function collectProjectScopeViolations(config) {
169771
170143
  }
169772
170144
  function isAllowedProjectPath(path) {
169773
170145
  const [top, second, third, fourth] = path;
169774
- if (top === "tools" && path.length === 2 && ["planReview", "securityReview", "diffReview"].includes(second ?? "")) {
169775
- return true;
169776
- }
169777
- if (top === "agents" && path.length === 3 && ["codex", "claude"].includes(second ?? "") && ["enabled", "model", "effort", "role", "timeoutMs"].includes(third ?? "")) {
170146
+ if (isAllowedConfigOverridePath(path))
169778
170147
  return true;
169779
- }
169780
- if (top === "verification" && path.length === 2 && ["enabled", "maxFindings", "timeoutMs"].includes(second ?? "")) {
170148
+ if (top === "tools" && path.length === 2 && ["planReview", "securityReview", "diffReview"].includes(second ?? "")) {
169781
170149
  return true;
169782
170150
  }
169783
170151
  if (top === "workspace" && path.length === 2 && ["maxContextBytes", "maxDiffBytes", "deny"].includes(second ?? "")) {
@@ -169789,9 +170157,6 @@ function isAllowedProjectPath(path) {
169789
170157
  if (top === "secrets" && path.length === 2 && ["blockOnDetectedSecret", "allowOverride"].includes(second ?? "")) {
169790
170158
  return true;
169791
170159
  }
169792
- if (top === "judge" && path.length === 2 && ["mode", "provider", "timeoutMs"].includes(second ?? "")) {
169793
- return true;
169794
- }
169795
170160
  if (top === "securityReview" && second === "cisaSecureByDesign" && path.length >= 3) {
169796
170161
  if (third === "dimensions") {
169797
170162
  return path.length === 4 && [
@@ -184345,12 +184710,6 @@ var kyosoConfigSecuritySensitivePrefixes = [
184345
184710
  // src/security/redact.ts
184346
184711
  var REDACTION = "[KYOSO_REDACTED]";
184347
184712
 
184348
- // src/core/constants.ts
184349
- var DEFAULT_AGENT_TIMEOUT_MS = 120000;
184350
- var RAW_OUTPUT_MAX_CHARS = 16384;
184351
- var KYOSO_CHILD_AGENT = "KYOSO_CHILD_AGENT";
184352
- var KYOSO_VERSION = "0.7.1";
184353
-
184354
184713
  // src/security/sanitizeText.ts
184355
184714
  var SENSITIVE_TEXT_PATTERNS = [
184356
184715
  /\bsk-(?:proj-)?[A-Za-z0-9_-]{8,}\b/g,
@@ -184818,7 +185177,7 @@ function skipVoid(str, ptr, banNewLines, banComments) {
184818
185177
  }
184819
185178
  return ptr;
184820
185179
  }
184821
- function skipUntil(str, ptr, sep, end, banNewLines = false) {
185180
+ function skipUntil(str, ptr, sep3, end, banNewLines = false) {
184822
185181
  if (!end) {
184823
185182
  ptr = indexOfNewline(str, ptr);
184824
185183
  return ptr < 0 ? str.length : ptr;
@@ -184827,7 +185186,7 @@ function skipUntil(str, ptr, sep, end, banNewLines = false) {
184827
185186
  let c = str[i];
184828
185187
  if (c === "#") {
184829
185188
  i = indexOfNewline(str, i);
184830
- } else if (c === sep) {
185189
+ } else if (c === sep3) {
184831
185190
  return i + 1;
184832
185191
  } else if (c === end || banNewLines && (c === `
184833
185192
  ` || c === "\r" && str[i + 1] === `
@@ -185325,7 +185684,7 @@ function formatTomlError(configPath, error51) {
185325
185684
  // src/config/tsConfigLoader.ts
185326
185685
  var import_typescript = __toESM(require_typescript(), 1);
185327
185686
  import { createRequire as createRequire2 } from "node:module";
185328
- import { dirname as dirname2, extname as extname2 } from "node:path";
185687
+ import { dirname as dirname3, extname as extname2 } from "node:path";
185329
185688
  import { pathToFileURL } from "node:url";
185330
185689
  import vm from "node:vm";
185331
185690
 
@@ -185359,20 +185718,20 @@ function loadTypeScriptConfig(filePath, source) {
185359
185718
  ${transpiled}
185360
185719
  })`, { filename: filePath });
185361
185720
  const runner = script.runInThisContext();
185362
- runner(module.exports, require2, module, filePath, dirname2(filePath), defineConfig);
185721
+ runner(module.exports, require2, module, filePath, dirname3(filePath), defineConfig);
185363
185722
  return module.exports;
185364
185723
  }
185365
185724
 
185366
185725
  // src/config/trustedConfig.ts
185367
- import { createHash } from "node:crypto";
185368
- import { mkdir as mkdir2, readFile as readFile3, writeFile as writeFile2 } from "node:fs/promises";
185726
+ import { createHash as createHash2 } from "node:crypto";
185727
+ import { mkdir as mkdir3, readFile as readFile3, writeFile as writeFile2 } from "node:fs/promises";
185369
185728
  import { homedir } from "node:os";
185370
- import { dirname as dirname3, join, resolve } from "node:path";
185729
+ import { dirname as dirname4, join as join2, resolve as resolve3 } from "node:path";
185371
185730
  function hashConfigSource(source) {
185372
- return createHash("sha256").update(source).digest("hex");
185731
+ return createHash2("sha256").update(source).digest("hex");
185373
185732
  }
185374
185733
  function defaultTrustedConfigStorePath(env = process.env) {
185375
- return env.KYOSO_TRUST_STORE_PATH ? resolve(env.KYOSO_TRUST_STORE_PATH) : join(env.HOME ? resolve(env.HOME) : homedir(), ".kyoso", "trusted-configs.json");
185734
+ return env.KYOSO_TRUST_STORE_PATH ? resolve3(env.KYOSO_TRUST_STORE_PATH) : join2(env.HOME ? resolve3(env.HOME) : homedir(), ".kyoso", "trusted-configs.json");
185376
185735
  }
185377
185736
  async function isTrustedConfig(storePath, configPath, configHash) {
185378
185737
  const store = await readTrustedConfigStore(storePath);
@@ -185381,7 +185740,7 @@ async function isTrustedConfig(storePath, configPath, configHash) {
185381
185740
  async function trustConfig(storePath, configPath, configHash) {
185382
185741
  const store = await readTrustedConfigStore(storePath);
185383
185742
  store[configPath] = configHash;
185384
- await mkdir2(dirname3(storePath), { recursive: true });
185743
+ await mkdir3(dirname4(storePath), { recursive: true });
185385
185744
  await writeFile2(storePath, `${JSON.stringify(store, null, 2)}
185386
185745
  `, {
185387
185746
  encoding: "utf8",
@@ -185393,7 +185752,7 @@ async function readTrustedConfigStore(storePath) {
185393
185752
  try {
185394
185753
  parsed = JSON.parse(await readFile3(storePath, "utf8"));
185395
185754
  } catch (error51) {
185396
- if (isMissingPathError(error51))
185755
+ if (isMissingPathError2(error51))
185397
185756
  return {};
185398
185757
  if (error51 instanceof SyntaxError)
185399
185758
  return {};
@@ -185411,7 +185770,7 @@ async function readTrustedConfigStore(storePath) {
185411
185770
  function isRecord2(value) {
185412
185771
  return typeof value === "object" && value !== null && !Array.isArray(value);
185413
185772
  }
185414
- function isMissingPathError(error51) {
185773
+ function isMissingPathError2(error51) {
185415
185774
  return typeof error51 === "object" && error51 !== null && "code" in error51 && error51.code === "ENOENT";
185416
185775
  }
185417
185776
 
@@ -185442,7 +185801,7 @@ async function loadConfig(options = {}) {
185442
185801
  sources.push({ path: globalConfigPath, layer: "global_toml" });
185443
185802
  }
185444
185803
  if (options.configPath) {
185445
- const explicitConfigPath = resolve2(cwd, options.configPath);
185804
+ const explicitConfigPath = resolve4(cwd, options.configPath);
185446
185805
  if (!await exists2(explicitConfigPath)) {
185447
185806
  throw new Error(`Config file not found: ${explicitConfigPath} (from --config)`);
185448
185807
  }
@@ -185459,8 +185818,8 @@ async function loadConfig(options = {}) {
185459
185818
  sources.push(loaded.source);
185460
185819
  warnings.push(...loaded.warnings);
185461
185820
  } else {
185462
- const projectTomlPath = resolve2(cwd, "kyoso.toml");
185463
- const projectTsPath = resolve2(cwd, "kyoso.config.ts");
185821
+ const projectTomlPath = resolve4(cwd, "kyoso.toml");
185822
+ const projectTsPath = resolve4(cwd, "kyoso.config.ts");
185464
185823
  const hasProjectToml = await exists2(projectTomlPath);
185465
185824
  const hasProjectTs = await exists2(projectTsPath);
185466
185825
  if (hasProjectToml) {
@@ -185496,8 +185855,8 @@ async function loadConfig(options = {}) {
185496
185855
  };
185497
185856
  }
185498
185857
  function resolveGlobalTomlConfigPath(env = process.env) {
185499
- const configHome = env.XDG_CONFIG_HOME ? resolve2(env.XDG_CONFIG_HOME) : join2(env.HOME ? resolve2(env.HOME) : homedir2(), ".config");
185500
- return join2(configHome, "kyoso", "config.toml");
185858
+ const configHome = env.XDG_CONFIG_HOME ? resolve4(env.XDG_CONFIG_HOME) : join3(env.HOME ? resolve4(env.HOME) : homedir2(), ".config");
185859
+ return join3(configHome, "kyoso", "config.toml");
185501
185860
  }
185502
185861
  function collectGlobalConfigWarnings(configPath, config2) {
185503
185862
  const unknownSettings = flattenLeaves(config2).map((leaf) => leaf.path).filter((path) => !isKnownGlobalConfigPath(path)).map((path) => ({
@@ -185932,23 +186291,1174 @@ function hasEnv(env, key) {
185932
186291
  return typeof env[key] === "string" && env[key].trim().length > 0;
185933
186292
  }
185934
186293
 
185935
- // src/cli/setup.ts
186294
+ // src/cli/codexPluginDetector.ts
185936
186295
  import { spawnSync } from "node:child_process";
185937
- import { existsSync, readFileSync } from "node:fs";
185938
- import { cp, mkdir as mkdir3, readFile as readFile5, writeFile as writeFile3 } from "node:fs/promises";
186296
+
186297
+ // src/cli/pluginRuntimeContract.ts
186298
+ var PLUGIN_RUNTIME_COMPATIBILITY_SCHEMA_VERSION = 1;
186299
+ var MINIMUM_SUPPORTED_CODEX_VERSION = "0.144.0-alpha.4";
186300
+ var PLUGIN_RUNTIME_EXPECTED_CONTRACT = {
186301
+ distribution: {
186302
+ pluginVersion: "0.1.0",
186303
+ mcpCommand: "npx",
186304
+ mcpPackagePin: "@kyo-so/cli@0.8.0"
186305
+ },
186306
+ marketplace: {
186307
+ name: "kyoso",
186308
+ listed: true,
186309
+ pluginId: "kyoso@kyoso",
186310
+ selector: "kyoso@kyoso",
186311
+ installPolicy: "AVAILABLE",
186312
+ authPolicy: "ON_USE"
186313
+ },
186314
+ transitions: {
186315
+ beforeAdd: { installed: false, enabled: false },
186316
+ afterAdd: { installed: true, enabled: true },
186317
+ disabled: { installed: true, enabled: false },
186318
+ afterRemove: { installed: false, enabled: false },
186319
+ installPluginId: "kyoso@kyoso",
186320
+ removePluginId: "kyoso@kyoso"
186321
+ },
186322
+ mcp: {
186323
+ default: {
186324
+ enabled: true,
186325
+ command: "$NODE",
186326
+ args: ["$PROBE_SERVER", "$OBSERVATION"],
186327
+ envVars: [
186328
+ "OPENAI_API_KEY",
186329
+ "CODEX_API_KEY",
186330
+ "CODEX_HOME",
186331
+ "CODEX_ACCESS_TOKEN",
186332
+ "ANTHROPIC_API_KEY",
186333
+ "CLAUDE_CODE_OAUTH_TOKEN"
186334
+ ],
186335
+ cwd: null,
186336
+ startupTimeoutSec: 20,
186337
+ toolTimeoutSec: 360
186338
+ },
186339
+ pluginOverride: {
186340
+ enabled: false,
186341
+ command: "$NODE",
186342
+ args: ["$PROBE_SERVER", "$OBSERVATION"],
186343
+ envVars: [
186344
+ "OPENAI_API_KEY",
186345
+ "CODEX_API_KEY",
186346
+ "CODEX_HOME",
186347
+ "CODEX_ACCESS_TOKEN",
186348
+ "ANTHROPIC_API_KEY",
186349
+ "CLAUDE_CODE_OAUTH_TOKEN"
186350
+ ],
186351
+ cwd: null,
186352
+ startupTimeoutSec: 20,
186353
+ toolTimeoutSec: 360
186354
+ },
186355
+ manualOverride: {
186356
+ enabled: true,
186357
+ command: "manual-kyoso",
186358
+ args: ["mcp"],
186359
+ envVars: [],
186360
+ cwd: null,
186361
+ startupTimeoutSec: null,
186362
+ toolTimeoutSec: null
186363
+ }
186364
+ },
186365
+ appServer: {
186366
+ default: {
186367
+ serverFound: true,
186368
+ toolNames: ["probe_environment"],
186369
+ authStatus: "unsupported",
186370
+ skillFound: false,
186371
+ skillEnabled: null,
186372
+ skillHasKyosoMcpDependency: false
186373
+ },
186374
+ pluginOverride: {
186375
+ serverFound: true,
186376
+ toolNames: [],
186377
+ authStatus: "unsupported",
186378
+ skillFound: false,
186379
+ skillEnabled: null,
186380
+ skillHasKyosoMcpDependency: false,
186381
+ mcpObservationWritten: false
186382
+ }
186383
+ },
186384
+ environment: {
186385
+ cwdIsWorkspace: true,
186386
+ homeInherited: true,
186387
+ codexHomeForwarded: true,
186388
+ tempDirectoryInherited: true,
186389
+ pathInherited: true,
186390
+ allowlistedEnvForwarded: {
186391
+ OPENAI_API_KEY: true,
186392
+ CODEX_API_KEY: true,
186393
+ CODEX_ACCESS_TOKEN: true,
186394
+ ANTHROPIC_API_KEY: true,
186395
+ CLAUDE_CODE_OAUTH_TOKEN: true
186396
+ },
186397
+ deniedSentinelForwarded: false
186398
+ },
186399
+ isolation: {
186400
+ distinctHomeAndCodexHome: true,
186401
+ distinctWorkspace: true
186402
+ }
186403
+ };
186404
+ var PLUGIN_LIST_JSON_SCHEMA = {
186405
+ collections: ["installed", "available"],
186406
+ entry: {
186407
+ pluginId: "kyoso@kyoso",
186408
+ requiredFields: ["pluginId", "installed", "enabled"]
186409
+ }
186410
+ };
186411
+ var pluginRuntimeContract = {
186412
+ schemaVersion: PLUGIN_RUNTIME_COMPATIBILITY_SCHEMA_VERSION,
186413
+ minimumSupportedCodexVersion: MINIMUM_SUPPORTED_CODEX_VERSION,
186414
+ pluginId: "kyoso@kyoso",
186415
+ pluginListTopLevelKeys: PLUGIN_LIST_JSON_SCHEMA.collections,
186416
+ expectedContract: PLUGIN_RUNTIME_EXPECTED_CONTRACT
186417
+ };
186418
+
186419
+ // src/cli/codexPluginDetector.ts
186420
+ var CODEX_PLUGIN_INSPECTION_TIMEOUT_MS = 5000;
186421
+ var MAX_CODEX_INSPECTION_TIMEOUT_MS = 1e4;
186422
+ var CODEX_INSPECTION_MAX_BUFFER_BYTES = 1024 * 1024;
186423
+ function inspectCodexPlugin(options) {
186424
+ const input2 = normalizeOptions(options);
186425
+ const versionResult = input2.runCodex(commandFor(input2, ["--version"]));
186426
+ if (versionResult.kind !== "completed") {
186427
+ return unsupported("version", failureReason(versionResult));
186428
+ }
186429
+ const codexVersion = parseCodexVersion(versionResult.stdout);
186430
+ if (!codexVersion)
186431
+ return unsupported("version", "invalid_output");
186432
+ if (compareSemver(codexVersion, MINIMUM_SUPPORTED_CODEX_VERSION) < 0) {
186433
+ return unsupported("version", "unsupported_version");
186434
+ }
186435
+ const pluginListResult = input2.runCodex(commandFor(input2, ["plugin", "list", "--json"]));
186436
+ if (pluginListResult.kind !== "completed") {
186437
+ return unsupported("plugin_list", failureReason(pluginListResult));
186438
+ }
186439
+ const parsed = parseJson(pluginListResult.stdout);
186440
+ if (parsed === undefined)
186441
+ return unsupported("plugin_list", "invalid_output");
186442
+ const plugin = parsePluginList(parsed);
186443
+ if (!plugin)
186444
+ return unsupported("plugin_list", "unknown_schema");
186445
+ return { status: "supported", codexVersion, plugin };
186446
+ }
186447
+ function inspectCodexMcpList(options) {
186448
+ const input2 = normalizeOptions(options);
186449
+ const result = input2.runCodex(commandFor(input2, ["mcp", "list", "--json"]));
186450
+ if (result.kind !== "completed") {
186451
+ return unsupported("mcp_list", failureReason(result));
186452
+ }
186453
+ const parsed = parseJson(result.stdout);
186454
+ if (parsed === undefined)
186455
+ return unsupported("mcp_list", "invalid_output");
186456
+ const kyoso = parseMcpList(parsed);
186457
+ if (!kyoso)
186458
+ return unsupported("mcp_list", "unknown_schema");
186459
+ return { status: "supported", kyoso };
186460
+ }
186461
+ function formatCodexInspectionFailure(failure) {
186462
+ const operation = {
186463
+ version: "Codex version",
186464
+ plugin_list: "Codex Plugin list",
186465
+ mcp_list: "Codex MCP list"
186466
+ }[failure.operation];
186467
+ const reason = {
186468
+ unavailable: "is unavailable",
186469
+ timeout: "timed out",
186470
+ command_failed: "could not be inspected",
186471
+ invalid_output: "returned unsupported output",
186472
+ unknown_schema: "returned an unsupported schema",
186473
+ unsupported_version: "is below the supported version"
186474
+ }[failure.reason];
186475
+ return `${operation} ${reason}.`;
186476
+ }
186477
+ function normalizeOptions(options) {
186478
+ return {
186479
+ cwd: options.cwd,
186480
+ env: options.env ?? process.env,
186481
+ timeoutMs: boundedTimeout(options.timeoutMs),
186482
+ runCodex: options.runCodex ?? runCodex
186483
+ };
186484
+ }
186485
+ function boundedTimeout(timeoutMs) {
186486
+ if (typeof timeoutMs !== "number" || !Number.isSafeInteger(timeoutMs) || timeoutMs <= 0) {
186487
+ return CODEX_PLUGIN_INSPECTION_TIMEOUT_MS;
186488
+ }
186489
+ return Math.min(timeoutMs, MAX_CODEX_INSPECTION_TIMEOUT_MS);
186490
+ }
186491
+ function commandFor(options, args) {
186492
+ return {
186493
+ args,
186494
+ cwd: options.cwd,
186495
+ env: options.env,
186496
+ timeoutMs: options.timeoutMs
186497
+ };
186498
+ }
186499
+ function runCodex(command) {
186500
+ const result = spawnSync("codex", command.args, {
186501
+ cwd: command.cwd,
186502
+ env: command.env,
186503
+ encoding: "utf8",
186504
+ timeout: command.timeoutMs,
186505
+ maxBuffer: CODEX_INSPECTION_MAX_BUFFER_BYTES,
186506
+ windowsHide: true
186507
+ });
186508
+ const errorCode = commandErrorCode(result.error);
186509
+ if (errorCode === "ENOENT")
186510
+ return { kind: "unavailable" };
186511
+ if (errorCode === "ETIMEDOUT")
186512
+ return { kind: "timeout" };
186513
+ if (result.error || result.status === null || result.status !== 0) {
186514
+ return { kind: "failed" };
186515
+ }
186516
+ return {
186517
+ kind: "completed",
186518
+ exitCode: result.status,
186519
+ stdout: String(result.stdout ?? "")
186520
+ };
186521
+ }
186522
+ function commandErrorCode(error51) {
186523
+ if (typeof error51 !== "object" || error51 === null || !("code" in error51) || typeof error51.code !== "string") {
186524
+ return;
186525
+ }
186526
+ return error51.code;
186527
+ }
186528
+ function failureReason(result) {
186529
+ if (result.kind === "unavailable")
186530
+ return "unavailable";
186531
+ if (result.kind === "timeout")
186532
+ return "timeout";
186533
+ return "command_failed";
186534
+ }
186535
+ function unsupported(operation, reason) {
186536
+ return { status: "unsupported", failure: { operation, reason } };
186537
+ }
186538
+ function parseJson(value) {
186539
+ try {
186540
+ return JSON.parse(value);
186541
+ } catch {
186542
+ return;
186543
+ }
186544
+ }
186545
+ function parsePluginList(value) {
186546
+ if (!isRecord5(value))
186547
+ return;
186548
+ const allowedKeys = new Set(PLUGIN_LIST_JSON_SCHEMA.collections);
186549
+ if (Object.keys(value).some((key) => !allowedKeys.has(key))) {
186550
+ return;
186551
+ }
186552
+ const installed = parsePluginEntries(value.installed);
186553
+ const available = parsePluginEntries(value.available);
186554
+ if (!installed || !available)
186555
+ return;
186556
+ const matches = [...installed, ...available].filter((entry2) => entry2.pluginId === PLUGIN_RUNTIME_EXPECTED_CONTRACT.marketplace.pluginId);
186557
+ if (matches.length > 1)
186558
+ return;
186559
+ if (matches.length === 0) {
186560
+ return pluginState(false, false);
186561
+ }
186562
+ const entry = matches[0];
186563
+ if (!entry.installed && entry.enabled)
186564
+ return;
186565
+ return pluginState(entry.installed, entry.enabled);
186566
+ }
186567
+ function parsePluginEntries(value) {
186568
+ if (value === undefined)
186569
+ return [];
186570
+ if (!Array.isArray(value))
186571
+ return;
186572
+ const entries = [];
186573
+ for (const item of value) {
186574
+ if (!isRecord5(item))
186575
+ return;
186576
+ if (typeof item.pluginId !== "string" || typeof item.installed !== "boolean" || typeof item.enabled !== "boolean") {
186577
+ return;
186578
+ }
186579
+ entries.push({
186580
+ pluginId: item.pluginId,
186581
+ installed: item.installed,
186582
+ enabled: item.enabled
186583
+ });
186584
+ }
186585
+ return entries;
186586
+ }
186587
+ function pluginState(installed, enabled) {
186588
+ return {
186589
+ pluginId: PLUGIN_RUNTIME_EXPECTED_CONTRACT.marketplace.pluginId,
186590
+ installed,
186591
+ enabled,
186592
+ state: installed ? enabled ? "enabled" : "disabled" : "not_installed"
186593
+ };
186594
+ }
186595
+ function parseMcpList(value) {
186596
+ if (!Array.isArray(value))
186597
+ return;
186598
+ const matches = [];
186599
+ for (const item of value) {
186600
+ if (!isRecord5(item) || typeof item.name !== "string")
186601
+ return "unknown";
186602
+ if (item.name !== "kyoso")
186603
+ continue;
186604
+ if (typeof item.enabled !== "boolean")
186605
+ return "unknown";
186606
+ matches.push(item.enabled);
186607
+ }
186608
+ if (matches.length === 0)
186609
+ return "missing";
186610
+ if (matches.length > 1)
186611
+ return "unknown";
186612
+ return matches[0] ? "enabled" : "disabled";
186613
+ }
186614
+ function parseCodexVersion(output2) {
186615
+ for (const line of output2.split(/\r?\n/)) {
186616
+ const match = /^codex-cli\s+(\S+)\s*$/.exec(line.trim());
186617
+ const version2 = match?.[1];
186618
+ if (version2 && parseSemver(version2))
186619
+ return version2;
186620
+ }
186621
+ return;
186622
+ }
186623
+ function compareSemver(left, right) {
186624
+ const parsedLeft = parseSemver(left);
186625
+ const parsedRight = parseSemver(right);
186626
+ if (!parsedLeft || !parsedRight)
186627
+ return 0;
186628
+ for (const key of ["major", "minor", "patch"]) {
186629
+ if (parsedLeft[key] !== parsedRight[key]) {
186630
+ return parsedLeft[key] < parsedRight[key] ? -1 : 1;
186631
+ }
186632
+ }
186633
+ return comparePrerelease(parsedLeft.prerelease, parsedRight.prerelease);
186634
+ }
186635
+ function parseSemver(value) {
186636
+ const match = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z.-]+)?$/.exec(value);
186637
+ if (!match)
186638
+ return;
186639
+ const major = Number(match[1]);
186640
+ const minor = Number(match[2]);
186641
+ const patch = Number(match[3]);
186642
+ if (!Number.isSafeInteger(major) || !Number.isSafeInteger(minor) || !Number.isSafeInteger(patch)) {
186643
+ return;
186644
+ }
186645
+ return {
186646
+ major,
186647
+ minor,
186648
+ patch,
186649
+ prerelease: match[4]?.split(".") ?? []
186650
+ };
186651
+ }
186652
+ function comparePrerelease(left, right) {
186653
+ if (left.length === 0 && right.length === 0)
186654
+ return 0;
186655
+ if (left.length === 0)
186656
+ return 1;
186657
+ if (right.length === 0)
186658
+ return -1;
186659
+ const count = Math.max(left.length, right.length);
186660
+ for (let index = 0;index < count; index += 1) {
186661
+ const leftPart = left[index];
186662
+ const rightPart = right[index];
186663
+ if (leftPart === undefined)
186664
+ return -1;
186665
+ if (rightPart === undefined)
186666
+ return 1;
186667
+ if (leftPart === rightPart)
186668
+ continue;
186669
+ const leftNumeric = /^\d+$/.test(leftPart);
186670
+ const rightNumeric = /^\d+$/.test(rightPart);
186671
+ if (leftNumeric && rightNumeric) {
186672
+ return Number(leftPart) < Number(rightPart) ? -1 : 1;
186673
+ }
186674
+ if (leftNumeric)
186675
+ return -1;
186676
+ if (rightNumeric)
186677
+ return 1;
186678
+ return leftPart < rightPart ? -1 : 1;
186679
+ }
186680
+ return 0;
186681
+ }
186682
+ function isRecord5(value) {
186683
+ return typeof value === "object" && value !== null && !Array.isArray(value);
186684
+ }
186685
+
186686
+ // src/cli/integration.ts
186687
+ import {
186688
+ accessSync,
186689
+ constants,
186690
+ existsSync,
186691
+ readFileSync,
186692
+ realpathSync
186693
+ } from "node:fs";
186694
+ import { tmpdir } from "node:os";
186695
+ import {
186696
+ delimiter,
186697
+ dirname as dirname5,
186698
+ extname as extname4,
186699
+ isAbsolute as isAbsolute2,
186700
+ join as join4,
186701
+ relative,
186702
+ resolve as resolve5
186703
+ } from "node:path";
186704
+ function detectCli(options) {
186705
+ const env = options.env ?? process.env;
186706
+ const platform = options.platform ?? process.platform;
186707
+ return {
186708
+ kyoso: detectInstalledKyoso(options.cwd, env, platform),
186709
+ npx: commandExists("npx", env, platform),
186710
+ bunx: commandExists("bunx", env, platform)
186711
+ };
186712
+ }
186713
+ function determineNonPluginIntegration(options) {
186714
+ const warnings = manualMcpWarnings(options.manualMcpStatus);
186715
+ if (options.manualMcpStatus === "unknown") {
186716
+ return { mode: "unknown", warnings };
186717
+ }
186718
+ if (options.manualMcpStatus === "enabled") {
186719
+ return {
186720
+ mode: options.hasSkill ? "manual-mcp" : "mcp-only",
186721
+ warnings
186722
+ };
186723
+ }
186724
+ const cliWarnings = cliIdentityWarnings(options.cli.kyoso);
186725
+ warnings.push(...cliWarnings);
186726
+ if (options.hasSkill) {
186727
+ if (options.cli.kyoso.kind === "installed") {
186728
+ return { mode: "cli-skill", warnings };
186729
+ }
186730
+ if (options.cli.npx || options.cli.bunx) {
186731
+ warnings.push("Package-runner fallback may require network access and can drift between versions.");
186732
+ return { mode: "skill-on-demand", warnings };
186733
+ }
186734
+ if (options.cli.kyoso.kind === "unknown") {
186735
+ return { mode: "unknown", warnings };
186736
+ }
186737
+ warnings.push("The Skill is installed, but no supported Kyoso CLI execution path was found.");
186738
+ return { mode: "missing", warnings };
186739
+ }
186740
+ if (options.cli.kyoso.kind === "installed") {
186741
+ return { mode: "cli-only", warnings };
186742
+ }
186743
+ if (options.cli.kyoso.kind === "unknown") {
186744
+ return { mode: "unknown", warnings };
186745
+ }
186746
+ return { mode: "missing", warnings };
186747
+ }
186748
+ function formatCliAvailability(cli) {
186749
+ if (cli.kind === "installed") {
186750
+ return `installed @kyo-so/cli ${cli.version} (${cli.scope})`;
186751
+ }
186752
+ if (cli.kind === "transient") {
186753
+ return "temporary runner (not treated as an installed CLI)";
186754
+ }
186755
+ if (cli.kind === "unknown") {
186756
+ return "unrecognized PATH entry (not treated as an installed CLI)";
186757
+ }
186758
+ return "missing";
186759
+ }
186760
+ function detectInstalledKyoso(cwd, env, platform) {
186761
+ const executable = resolveCommand("kyoso", env, platform);
186762
+ if (!executable)
186763
+ return { kind: "missing" };
186764
+ let realExecutable;
186765
+ try {
186766
+ realExecutable = realpathSync(executable);
186767
+ } catch {
186768
+ return { kind: "unknown" };
186769
+ }
186770
+ if (isTransientPath(realExecutable, cwd))
186771
+ return { kind: "transient" };
186772
+ const packageInfo = findKyosoPackage(realExecutable);
186773
+ if (!packageInfo)
186774
+ return { kind: "unknown" };
186775
+ return {
186776
+ kind: "installed",
186777
+ version: packageInfo.version,
186778
+ scope: isWithin(packageInfo.directory, realPathOrResolved(cwd)) ? "project" : "global"
186779
+ };
186780
+ }
186781
+ function findKyosoPackage(executable) {
186782
+ let directory = dirname5(executable);
186783
+ for (;; ) {
186784
+ const packagePath = join4(directory, "package.json");
186785
+ if (existsSync(packagePath)) {
186786
+ try {
186787
+ const parsed = JSON.parse(readFileSync(packagePath, "utf8"));
186788
+ if (isRecord6(parsed) && parsed.name === "@kyo-so/cli" && typeof parsed.version === "string" && parsed.version.trim().length > 0) {
186789
+ return { directory, version: parsed.version };
186790
+ }
186791
+ } catch {}
186792
+ }
186793
+ const parent = dirname5(directory);
186794
+ if (parent === directory)
186795
+ return;
186796
+ directory = parent;
186797
+ }
186798
+ }
186799
+ function isTransientPath(path, cwd) {
186800
+ if (path.split(/[\\/]/).includes("_npx"))
186801
+ return true;
186802
+ const temporaryRoot = realPathOrResolved(tmpdir());
186803
+ return isWithin(path, temporaryRoot) && !isWithin(path, realPathOrResolved(cwd));
186804
+ }
186805
+ function realPathOrResolved(path) {
186806
+ try {
186807
+ return realpathSync(path);
186808
+ } catch {
186809
+ return resolve5(path);
186810
+ }
186811
+ }
186812
+ function manualMcpWarnings(status) {
186813
+ if (status === "disabled")
186814
+ return ["Manual MCP registration is disabled."];
186815
+ if (status === "unknown") {
186816
+ return [
186817
+ "Manual MCP registration could not be safely classified from its configuration."
186818
+ ];
186819
+ }
186820
+ return [];
186821
+ }
186822
+ function cliIdentityWarnings(cli) {
186823
+ if (cli.kind === "transient") {
186824
+ return [
186825
+ "A temporary Kyoso runner was found on PATH and is not treated as an installed CLI."
186826
+ ];
186827
+ }
186828
+ if (cli.kind === "unknown") {
186829
+ return [
186830
+ "A PATH entry named kyoso does not identify itself as @kyo-so/cli and is not treated as an installed CLI."
186831
+ ];
186832
+ }
186833
+ return [];
186834
+ }
186835
+ function commandExists(command, env, platform) {
186836
+ return resolveCommand(command, env, platform) !== undefined;
186837
+ }
186838
+ function resolveCommand(command, env, platform) {
186839
+ for (const path of env.PATH?.split(delimiter) ?? []) {
186840
+ if (path.length === 0)
186841
+ continue;
186842
+ for (const name of commandNames(command, env, platform)) {
186843
+ const candidate = join4(path, name);
186844
+ try {
186845
+ accessSync(candidate, constants.X_OK);
186846
+ return candidate;
186847
+ } catch {}
186848
+ }
186849
+ }
186850
+ return;
186851
+ }
186852
+ function commandNames(command, env, platform) {
186853
+ if (platform !== "win32" || extname4(command).length > 0)
186854
+ return [command];
186855
+ const names = [command];
186856
+ const seen = new Set([command.toLowerCase()]);
186857
+ for (const extension of env.PATHEXT?.split(";") ?? []) {
186858
+ const normalized = extension.trim();
186859
+ if (!normalized.startsWith(".") || /[\\/]/.test(normalized))
186860
+ continue;
186861
+ const candidate = `${command}${normalized}`;
186862
+ const key = candidate.toLowerCase();
186863
+ if (seen.has(key))
186864
+ continue;
186865
+ seen.add(key);
186866
+ names.push(candidate);
186867
+ }
186868
+ return names;
186869
+ }
186870
+ function isWithin(path, parent) {
186871
+ const relativePath = relative(resolve5(parent), resolve5(path));
186872
+ return relativePath === "" || !relativePath.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`) && relativePath !== ".." && !isAbsolute2(relativePath);
186873
+ }
186874
+ function isRecord6(value) {
186875
+ return typeof value === "object" && value !== null && !Array.isArray(value);
186876
+ }
186877
+
186878
+ // src/cli/setup.ts
186879
+ import { spawnSync as spawnSync2 } from "node:child_process";
186880
+ import { existsSync as existsSync2, readFileSync as readFileSync2, realpathSync as realpathSync2 } from "node:fs";
186881
+ import { mkdir as mkdir5, readFile as readFile6, writeFile as writeFile4 } from "node:fs/promises";
185939
186882
  import { homedir as homedir3 } from "node:os";
185940
- import { delimiter, dirname as dirname4, join as join3 } from "node:path";
186883
+ import { delimiter as delimiter2, dirname as dirname7, join as join6, resolve as resolve7 } from "node:path";
185941
186884
  import { fileURLToPath } from "node:url";
186885
+
186886
+ // src/cli/skillInstall.ts
186887
+ import { createHash as createHash3, randomUUID } from "node:crypto";
186888
+ import {
186889
+ copyFile,
186890
+ link,
186891
+ lstat as lstat2,
186892
+ mkdir as mkdir4,
186893
+ mkdtemp,
186894
+ readFile as readFile5,
186895
+ readdir,
186896
+ realpath as realpath2,
186897
+ rename,
186898
+ rm,
186899
+ writeFile as writeFile3
186900
+ } from "node:fs/promises";
186901
+ import {
186902
+ basename as basename2,
186903
+ dirname as dirname6,
186904
+ isAbsolute as isAbsolute3,
186905
+ join as join5,
186906
+ relative as relative2,
186907
+ resolve as resolve6,
186908
+ sep as sep3
186909
+ } from "node:path";
186910
+
186911
+ // src/cli/knownSkillDigests.ts
186912
+ var CURRENT_SKILL_DIGEST = "sha256:98e715396ee3e1994c29ab49bc489197f242091c8ac0e06e182c0eccd5ab277e";
186913
+ var KNOWN_SKILL_DIGESTS_BY_VERSION = {
186914
+ "0.8.0": [
186915
+ {
186916
+ digest: "sha256:b16ea3f8141a01399b96dee650365d99df2b8c5fc99184d9cb22d5d72c106fd8",
186917
+ kind: "historical"
186918
+ }
186919
+ ]
186920
+ };
186921
+ function knownSkillDigest(digest) {
186922
+ if (digest === CURRENT_SKILL_DIGEST) {
186923
+ return { version: KYOSO_VERSION, kind: "current" };
186924
+ }
186925
+ for (const [version2, entries] of Object.entries(KNOWN_SKILL_DIGESTS_BY_VERSION)) {
186926
+ const entry = entries.find((candidate) => candidate.digest === digest);
186927
+ if (entry)
186928
+ return { version: version2, kind: entry.kind };
186929
+ }
186930
+ return;
186931
+ }
186932
+
186933
+ // src/cli/skillInstall.ts
186934
+ var SKILL_INSTALL_MARKER = ".kyoso-install.json";
186935
+ var SKILL_INSTALL_BACKUP = ".kyoso-review.backup";
186936
+ var SKILL_INSTALL_TRANSACTION = ".kyoso-review.install-transaction.json";
186937
+ async function ensureManagedSkill(options) {
186938
+ const sourceDigest = await hashSkillDirectory(options.sourceDir);
186939
+ if (sourceDigest !== CURRENT_SKILL_DIGEST) {
186940
+ throw new Error(`Bundled kyoso-review digest is ${sourceDigest}; update knownSkillDigests.ts before distributing it.`);
186941
+ }
186942
+ const safeDestination = await resolveSafeDestination(options.trustedRoot, options.destinationDir);
186943
+ const destinationDir = safeDestination.destinationDir;
186944
+ const recovery = await recoverInterruptedReplacement({
186945
+ destinationDir,
186946
+ trustedRoot: safeDestination.realRoot,
186947
+ write: options.write
186948
+ });
186949
+ if (recovery.status !== "none") {
186950
+ return {
186951
+ status: recovery.status,
186952
+ path: destinationDir,
186953
+ detail: recovery.detail
186954
+ };
186955
+ }
186956
+ const destinationStat = await optionalLstat2(destinationDir);
186957
+ if (!destinationStat) {
186958
+ const detail2 = copyDetail(options.sourceDir, destinationDir, "create");
186959
+ if (!options.write) {
186960
+ return { status: "dry-run", path: destinationDir, detail: detail2 };
186961
+ }
186962
+ await replaceSkillDirectory({
186963
+ sourceDir: options.sourceDir,
186964
+ destinationDir,
186965
+ trustedRoot: safeDestination.realRoot,
186966
+ destinationExists: false
186967
+ });
186968
+ return { status: "created", path: destinationDir, detail: detail2 };
186969
+ }
186970
+ if (!destinationStat.isDirectory()) {
186971
+ if (!options.force) {
186972
+ return conflictResult(destinationDir, "destination exists and is not a directory");
186973
+ }
186974
+ const detail2 = copyDetail(options.sourceDir, destinationDir, "force replace");
186975
+ if (!options.write) {
186976
+ return { status: "dry-run", path: destinationDir, detail: detail2 };
186977
+ }
186978
+ await replaceSkillDirectory({
186979
+ sourceDir: options.sourceDir,
186980
+ destinationDir,
186981
+ trustedRoot: safeDestination.realRoot,
186982
+ destinationExists: true
186983
+ });
186984
+ return { status: "updated", path: destinationDir, detail: detail2 };
186985
+ }
186986
+ if (await realpath2(options.sourceDir) === await realpath2(destinationDir)) {
186987
+ return {
186988
+ status: "skipped",
186989
+ path: destinationDir,
186990
+ detail: "canonical source is already the destination"
186991
+ };
186992
+ }
186993
+ const actualDigest = await hashSkillDirectory(destinationDir);
186994
+ const marker = await readInstallMarker(destinationDir);
186995
+ if (options.force) {
186996
+ const detail2 = [
186997
+ `force replace ${destinationDir}`,
186998
+ `from ${actualDigest}`,
186999
+ `to ${CURRENT_SKILL_DIGEST}`
187000
+ ].join(`
187001
+ `);
187002
+ if (!options.write) {
187003
+ return { status: "dry-run", path: destinationDir, detail: detail2 };
187004
+ }
187005
+ await replaceSkillDirectory({
187006
+ sourceDir: options.sourceDir,
187007
+ destinationDir,
187008
+ trustedRoot: safeDestination.realRoot,
187009
+ destinationExists: true
187010
+ });
187011
+ return { status: "updated", path: destinationDir, detail: detail2 };
187012
+ }
187013
+ if (marker.kind === "invalid") {
187014
+ return conflictResult(destinationDir, marker.reason);
187015
+ }
187016
+ if (marker.kind === "valid") {
187017
+ if (marker.value.digest !== actualDigest) {
187018
+ return conflictResult(destinationDir, `managed digest ${marker.value.digest} does not match installed digest ${actualDigest}`);
187019
+ }
187020
+ if (actualDigest === CURRENT_SKILL_DIGEST && marker.value.cliVersion === KYOSO_VERSION) {
187021
+ return {
187022
+ status: "skipped",
187023
+ path: destinationDir,
187024
+ detail: `managed skill is current (${CURRENT_SKILL_DIGEST})`
187025
+ };
187026
+ }
187027
+ if (actualDigest === CURRENT_SKILL_DIGEST) {
187028
+ const detail3 = `refresh install marker to CLI ${KYOSO_VERSION}`;
187029
+ if (!options.write) {
187030
+ return { status: "dry-run", path: destinationDir, detail: detail3 };
187031
+ }
187032
+ await writeInstallMarker(destinationDir);
187033
+ return { status: "updated", path: destinationDir, detail: detail3 };
187034
+ }
187035
+ const detail2 = [
187036
+ `update managed skill from ${actualDigest}`,
187037
+ `to ${CURRENT_SKILL_DIGEST}`
187038
+ ].join(`
187039
+ `);
187040
+ if (!options.write) {
187041
+ return { status: "dry-run", path: destinationDir, detail: detail2 };
187042
+ }
187043
+ await replaceSkillDirectory({
187044
+ sourceDir: options.sourceDir,
187045
+ destinationDir,
187046
+ trustedRoot: safeDestination.realRoot,
187047
+ destinationExists: true
187048
+ });
187049
+ return { status: "updated", path: destinationDir, detail: detail2 };
187050
+ }
187051
+ const known = knownSkillDigest(actualDigest);
187052
+ if (!known) {
187053
+ return conflictResult(destinationDir, `unmanaged skill digest ${actualDigest} is not recognized`);
187054
+ }
187055
+ if (actualDigest === CURRENT_SKILL_DIGEST) {
187056
+ const detail2 = `adopt existing ${known.version} skill and write ${SKILL_INSTALL_MARKER}`;
187057
+ if (!options.write) {
187058
+ return { status: "dry-run", path: destinationDir, detail: detail2 };
187059
+ }
187060
+ await writeInstallMarker(destinationDir);
187061
+ return { status: "updated", path: destinationDir, detail: detail2 };
187062
+ }
187063
+ const detail = [
187064
+ `adopt known ${known.version} ${known.kind} skill (${actualDigest})`,
187065
+ `update to ${CURRENT_SKILL_DIGEST}`
187066
+ ].join(`
187067
+ `);
187068
+ if (!options.write) {
187069
+ return { status: "dry-run", path: destinationDir, detail };
187070
+ }
187071
+ await replaceSkillDirectory({
187072
+ sourceDir: options.sourceDir,
187073
+ destinationDir,
187074
+ trustedRoot: safeDestination.realRoot,
187075
+ destinationExists: true
187076
+ });
187077
+ return { status: "updated", path: destinationDir, detail };
187078
+ }
187079
+ async function hashSkillDirectory(directory) {
187080
+ const rootStat = await lstat2(directory);
187081
+ if (rootStat.isSymbolicLink() || !rootStat.isDirectory()) {
187082
+ throw new Error(`Skill root must be a regular directory: ${directory}`);
187083
+ }
187084
+ const files = await listRegularFiles(directory);
187085
+ files.sort((left, right) => Buffer.compare(Buffer.from(left.relativePath), Buffer.from(right.relativePath)));
187086
+ const hash2 = createHash3("sha256");
187087
+ for (const file2 of files) {
187088
+ const bytes = await readFile5(file2.absolutePath);
187089
+ hash2.update(file2.relativePath);
187090
+ hash2.update("\x00");
187091
+ hash2.update(String(bytes.byteLength));
187092
+ hash2.update("\x00");
187093
+ hash2.update(bytes);
187094
+ }
187095
+ return `sha256:${hash2.digest("hex")}`;
187096
+ }
187097
+ async function listRegularFiles(directory, prefix = "") {
187098
+ const result = [];
187099
+ const entries = await readdir(directory);
187100
+ for (const name of entries) {
187101
+ const relativePath = prefix.length > 0 ? `${prefix}/${name}` : name;
187102
+ const absolutePath = join5(directory, name);
187103
+ const stat = await lstat2(absolutePath);
187104
+ if (stat.isSymbolicLink()) {
187105
+ throw new Error(`Skill directory contains a symlink: ${relativePath}`);
187106
+ }
187107
+ if (relativePath === SKILL_INSTALL_MARKER) {
187108
+ if (!stat.isFile()) {
187109
+ throw new Error(`${SKILL_INSTALL_MARKER} must be a regular file`);
187110
+ }
187111
+ continue;
187112
+ }
187113
+ if (stat.isDirectory()) {
187114
+ result.push(...await listRegularFiles(absolutePath, relativePath));
187115
+ continue;
187116
+ }
187117
+ if (!stat.isFile()) {
187118
+ throw new Error(`Skill directory contains a non-regular file: ${relativePath}`);
187119
+ }
187120
+ result.push({ relativePath, absolutePath });
187121
+ }
187122
+ return result;
187123
+ }
187124
+ async function resolveSafeDestination(trustedRoot, destinationDir) {
187125
+ const logicalRoot = resolve6(trustedRoot);
187126
+ const logicalDestination = resolve6(destinationDir);
187127
+ const relativeDestination = relative2(logicalRoot, logicalDestination);
187128
+ if (relativeDestination === "" || !isPathWithin(logicalDestination, logicalRoot)) {
187129
+ throw new Error(`Skill destination escapes trusted install root: ${destinationDir}`);
187130
+ }
187131
+ const realRoot = await realpath2(logicalRoot);
187132
+ const safeDestination = resolve6(realRoot, relativeDestination);
187133
+ if (!isPathWithin(safeDestination, realRoot)) {
187134
+ throw new Error(`Skill destination escapes trusted install root: ${destinationDir}`);
187135
+ }
187136
+ await assertExistingSegmentsSafe(realRoot, safeDestination);
187137
+ return { realRoot, destinationDir: safeDestination };
187138
+ }
187139
+ async function assertExistingSegmentsSafe(realRoot, destination) {
187140
+ const segments = relative2(realRoot, destination).split(sep3).filter(Boolean);
187141
+ let current = realRoot;
187142
+ for (const segment of segments) {
187143
+ current = join5(current, segment);
187144
+ const stat = await optionalLstat2(current);
187145
+ if (!stat)
187146
+ return;
187147
+ if (stat.isSymbolicLink()) {
187148
+ throw new Error(`Skill install path contains a symlink: ${current}`);
187149
+ }
187150
+ if (current !== destination && !stat.isDirectory()) {
187151
+ throw new Error(`Skill install parent is not a directory: ${current}`);
187152
+ }
187153
+ }
187154
+ }
187155
+ async function ensureSafeParent(realRoot, destinationDir) {
187156
+ const parent = dirname6(destinationDir);
187157
+ const relativeParent = relative2(realRoot, parent);
187158
+ if (relativeParent === ".." || relativeParent.startsWith(`..${sep3}`) || isAbsolute3(relativeParent)) {
187159
+ throw new Error(`Skill install parent escapes trusted root: ${parent}`);
187160
+ }
187161
+ const segments = relative2(realRoot, parent).split(sep3).filter(Boolean);
187162
+ let current = realRoot;
187163
+ for (const segment of segments) {
187164
+ current = join5(current, segment);
187165
+ let stat = await optionalLstat2(current);
187166
+ if (!stat) {
187167
+ await mkdir4(current);
187168
+ stat = await lstat2(current);
187169
+ }
187170
+ if (stat.isSymbolicLink() || !stat.isDirectory()) {
187171
+ throw new Error(`Skill install parent is unsafe: ${current}`);
187172
+ }
187173
+ }
187174
+ }
187175
+ async function replaceSkillDirectory(options) {
187176
+ await ensureSafeParent(options.trustedRoot, options.destinationDir);
187177
+ await assertExistingSegmentsSafe(options.trustedRoot, options.destinationDir);
187178
+ const parent = dirname6(options.destinationDir);
187179
+ const mutationGuard = await captureMutationGuard(parent);
187180
+ const stage = await mkdtemp(join5(parent, ".kyoso-review.stage-"));
187181
+ const backup = join5(parent, SKILL_INSTALL_BACKUP);
187182
+ const transaction = join5(parent, SKILL_INSTALL_TRANSACTION);
187183
+ let backupCreated = false;
187184
+ let transactionCreated = false;
187185
+ try {
187186
+ await copySkillFiles(options.sourceDir, stage);
187187
+ await writeInstallMarker(stage);
187188
+ const stagedDigest = await hashSkillDirectory(stage);
187189
+ if (stagedDigest !== CURRENT_SKILL_DIGEST) {
187190
+ throw new Error(`Staged kyoso-review digest mismatch: ${stagedDigest} != ${CURRENT_SKILL_DIGEST}`);
187191
+ }
187192
+ if (options.destinationExists) {
187193
+ await assertMutationGuard(mutationGuard);
187194
+ if (await optionalLstat2(backup) || await optionalLstat2(transaction)) {
187195
+ throw interruptedReplacementConflict(options.destinationDir, backup);
187196
+ }
187197
+ await writeReplacementTransaction(transaction, options.destinationDir);
187198
+ transactionCreated = true;
187199
+ await assertMutationGuard(mutationGuard);
187200
+ await rename(options.destinationDir, backup);
187201
+ backupCreated = true;
187202
+ await assertMutationGuard(mutationGuard);
187203
+ }
187204
+ try {
187205
+ await assertMutationGuard(mutationGuard);
187206
+ await rename(stage, options.destinationDir);
187207
+ await assertMutationGuard(mutationGuard);
187208
+ } catch (error51) {
187209
+ if (backupCreated && !await optionalLstat2(options.destinationDir)) {
187210
+ await assertMutationGuard(mutationGuard);
187211
+ await rename(backup, options.destinationDir);
187212
+ backupCreated = false;
187213
+ await assertMutationGuard(mutationGuard);
187214
+ await rm(transaction, { force: true });
187215
+ transactionCreated = false;
187216
+ await assertMutationGuard(mutationGuard);
187217
+ }
187218
+ throw error51;
187219
+ }
187220
+ if (backupCreated) {
187221
+ await assertMutationGuard(mutationGuard);
187222
+ await rm(backup, { recursive: true, force: true });
187223
+ backupCreated = false;
187224
+ await assertMutationGuard(mutationGuard);
187225
+ await rm(transaction, { force: true });
187226
+ transactionCreated = false;
187227
+ await assertMutationGuard(mutationGuard);
187228
+ }
187229
+ } finally {
187230
+ await assertMutationGuard(mutationGuard);
187231
+ await rm(stage, { recursive: true, force: true });
187232
+ if (backupCreated && !await optionalLstat2(options.destinationDir)) {
187233
+ await assertMutationGuard(mutationGuard);
187234
+ await rename(backup, options.destinationDir);
187235
+ backupCreated = false;
187236
+ await assertMutationGuard(mutationGuard);
187237
+ }
187238
+ if (transactionCreated && !backupCreated) {
187239
+ await rm(transaction, { force: true });
187240
+ }
187241
+ }
187242
+ }
187243
+ async function captureMutationGuard(parent) {
187244
+ const stat = await lstat2(parent);
187245
+ if (stat.isSymbolicLink() || !stat.isDirectory()) {
187246
+ throw new Error(`Skill install parent is unsafe: ${parent}`);
187247
+ }
187248
+ const realParent = await realpath2(parent);
187249
+ if (realParent !== parent) {
187250
+ throw new Error(`Skill install parent changed during setup: ${parent}`);
187251
+ }
187252
+ return {
187253
+ parent,
187254
+ realParent,
187255
+ device: stat.dev,
187256
+ inode: stat.ino
187257
+ };
187258
+ }
187259
+ async function assertMutationGuard(guard) {
187260
+ const stat = await optionalLstat2(guard.parent);
187261
+ if (!stat || stat.isSymbolicLink() || !stat.isDirectory() || stat.dev !== guard.device || stat.ino !== guard.inode || await realpath2(guard.parent) !== guard.realParent) {
187262
+ throw new Error(`Skill install parent changed during setup: ${guard.parent}. No further path mutation was attempted.`);
187263
+ }
187264
+ }
187265
+ async function recoverInterruptedReplacement(options) {
187266
+ const parent = dirname6(options.destinationDir);
187267
+ const backup = join5(parent, SKILL_INSTALL_BACKUP);
187268
+ const transaction = join5(parent, SKILL_INSTALL_TRANSACTION);
187269
+ const backupStat = await optionalLstat2(backup);
187270
+ const transactionStat = await optionalLstat2(transaction);
187271
+ if (!backupStat && !transactionStat)
187272
+ return { status: "none" };
187273
+ if (backupStat?.isSymbolicLink()) {
187274
+ throw new Error(`Interrupted Skill backup is a symlink: ${backup}`);
187275
+ }
187276
+ if (transactionStat?.isSymbolicLink() || !transactionStat?.isFile()) {
187277
+ throw new Error(`Interrupted Skill transaction marker is not a regular file: ${transaction}`);
187278
+ }
187279
+ if (!backupStat) {
187280
+ await assertExistingSegmentsSafe(options.trustedRoot, transaction);
187281
+ } else {
187282
+ await assertExistingSegmentsSafe(options.trustedRoot, backup);
187283
+ }
187284
+ const parsed = await readReplacementTransaction(transaction);
187285
+ if (parsed.destinationName !== basename2(options.destinationDir) || parsed.backupName !== SKILL_INSTALL_BACKUP) {
187286
+ throw new Error(`Interrupted Skill transaction does not match ${options.destinationDir}: ${transaction}`);
187287
+ }
187288
+ const destinationStat = await optionalLstat2(options.destinationDir);
187289
+ if (backupStat && destinationStat) {
187290
+ throw interruptedReplacementConflict(options.destinationDir, backup);
187291
+ }
187292
+ if (!backupStat && !destinationStat) {
187293
+ throw new Error(`Interrupted Skill transaction has neither destination nor backup: ${transaction}`);
187294
+ }
187295
+ if (!options.write) {
187296
+ return {
187297
+ status: "dry-run",
187298
+ detail: backupStat ? `recover interrupted skill replacement from ${SKILL_INSTALL_BACKUP}` : `clean completed Skill replacement marker ${SKILL_INSTALL_TRANSACTION}`
187299
+ };
187300
+ }
187301
+ const mutationGuard = await captureMutationGuard(parent);
187302
+ await assertMutationGuard(mutationGuard);
187303
+ if (backupStat) {
187304
+ await rename(backup, options.destinationDir);
187305
+ }
187306
+ await assertMutationGuard(mutationGuard);
187307
+ await rm(transaction, { force: true });
187308
+ await assertMutationGuard(mutationGuard);
187309
+ return {
187310
+ status: "updated",
187311
+ detail: backupStat ? `recovered interrupted skill replacement from ${SKILL_INSTALL_BACKUP}; rerun setup to apply the requested install` : `cleaned completed Skill replacement marker ${SKILL_INSTALL_TRANSACTION}; rerun setup to verify the installed Skill`
187312
+ };
187313
+ }
187314
+ function interruptedReplacementConflict(destinationDir, backup) {
187315
+ return new Error(`Interrupted Skill replacement is ambiguous because both ${destinationDir} and ${backup} exist. Inspect both directories and remove ${backup} only after preserving any needed files.`);
187316
+ }
187317
+ async function writeReplacementTransaction(transactionPath, destinationDir) {
187318
+ const transaction = {
187319
+ schemaVersion: 1,
187320
+ installer: "@kyo-so/cli",
187321
+ destinationName: basename2(destinationDir),
187322
+ backupName: SKILL_INSTALL_BACKUP
187323
+ };
187324
+ const temporaryPath = `${transactionPath}.tmp-${randomUUID()}`;
187325
+ await writeFile3(temporaryPath, `${JSON.stringify(transaction, null, 2)}
187326
+ `, {
187327
+ encoding: "utf8",
187328
+ flag: "wx"
187329
+ });
187330
+ try {
187331
+ await link(temporaryPath, transactionPath);
187332
+ } finally {
187333
+ await rm(temporaryPath, { force: true });
187334
+ }
187335
+ }
187336
+ async function readReplacementTransaction(transactionPath) {
187337
+ let parsed;
187338
+ try {
187339
+ parsed = JSON.parse(await readFile5(transactionPath, "utf8"));
187340
+ } catch (error51) {
187341
+ throw new Error(`Interrupted Skill transaction is invalid: ${transactionPath}: ${error51 instanceof Error ? error51.message : String(error51)}`);
187342
+ }
187343
+ if (!isReplacementTransaction(parsed)) {
187344
+ throw new Error(`Interrupted Skill transaction has an invalid schema: ${transactionPath}`);
187345
+ }
187346
+ return parsed;
187347
+ }
187348
+ function isReplacementTransaction(value) {
187349
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
187350
+ return false;
187351
+ }
187352
+ const candidate = value;
187353
+ return candidate.schemaVersion === 1 && candidate.installer === "@kyo-so/cli" && typeof candidate.destinationName === "string" && candidate.destinationName.length > 0 && candidate.backupName === SKILL_INSTALL_BACKUP;
187354
+ }
187355
+ async function copySkillFiles(sourceDir, destinationDir) {
187356
+ for (const file2 of await listRegularFiles(sourceDir)) {
187357
+ const destination = join5(destinationDir, ...file2.relativePath.split("/"));
187358
+ await mkdir4(dirname6(destination), { recursive: true });
187359
+ await copyFile(file2.absolutePath, destination);
187360
+ }
187361
+ }
187362
+ async function readInstallMarker(destinationDir) {
187363
+ const markerPath = join5(destinationDir, SKILL_INSTALL_MARKER);
187364
+ const markerStat = await optionalLstat2(markerPath);
187365
+ if (!markerStat)
187366
+ return { kind: "missing" };
187367
+ if (markerStat.isSymbolicLink() || !markerStat.isFile()) {
187368
+ return {
187369
+ kind: "invalid",
187370
+ reason: `${SKILL_INSTALL_MARKER} is not a regular file`
187371
+ };
187372
+ }
187373
+ try {
187374
+ const parsed = JSON.parse(await readFile5(markerPath, "utf8"));
187375
+ if (!isInstallMarker(parsed)) {
187376
+ return {
187377
+ kind: "invalid",
187378
+ reason: `${SKILL_INSTALL_MARKER} has an invalid schema`
187379
+ };
187380
+ }
187381
+ return { kind: "valid", value: parsed };
187382
+ } catch (error51) {
187383
+ return {
187384
+ kind: "invalid",
187385
+ reason: `${SKILL_INSTALL_MARKER} is invalid: ${error51 instanceof Error ? error51.message : String(error51)}`
187386
+ };
187387
+ }
187388
+ }
187389
+ async function writeInstallMarker(destinationDir) {
187390
+ const marker = {
187391
+ schemaVersion: 1,
187392
+ installer: "@kyo-so/cli",
187393
+ cliVersion: KYOSO_VERSION,
187394
+ digest: CURRENT_SKILL_DIGEST
187395
+ };
187396
+ const markerPath = join5(destinationDir, SKILL_INSTALL_MARKER);
187397
+ const temporaryPath = join5(destinationDir, `${SKILL_INSTALL_MARKER}.tmp-${randomUUID()}`);
187398
+ await writeFile3(temporaryPath, `${JSON.stringify(marker, null, 2)}
187399
+ `, {
187400
+ encoding: "utf8",
187401
+ flag: "wx"
187402
+ });
187403
+ try {
187404
+ await rename(temporaryPath, markerPath);
187405
+ } finally {
187406
+ await rm(temporaryPath, { force: true });
187407
+ }
187408
+ }
187409
+ function isInstallMarker(value) {
187410
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
187411
+ return false;
187412
+ }
187413
+ const candidate = value;
187414
+ return candidate.schemaVersion === 1 && candidate.installer === "@kyo-so/cli" && typeof candidate.cliVersion === "string" && candidate.cliVersion.length > 0 && typeof candidate.digest === "string" && /^sha256:[0-9a-f]{64}$/.test(candidate.digest);
187415
+ }
187416
+ function copyDetail(sourceDir, destinationDir, action) {
187417
+ return [
187418
+ `${action} ${sourceDir}`,
187419
+ `to ${destinationDir}`,
187420
+ `digest ${CURRENT_SKILL_DIGEST}`
187421
+ ].join(`
187422
+ `);
187423
+ }
187424
+ function conflictResult(destinationDir, reason) {
187425
+ return {
187426
+ status: "conflict",
187427
+ path: destinationDir,
187428
+ detail: `${reason}
187429
+ Existing skill was not changed. Rerun with --force to replace only this skill directory.`
187430
+ };
187431
+ }
187432
+ async function optionalLstat2(path) {
187433
+ try {
187434
+ return await lstat2(path);
187435
+ } catch (error51) {
187436
+ if (isMissingPathError3(error51))
187437
+ return;
187438
+ throw error51;
187439
+ }
187440
+ }
187441
+ function isMissingPathError3(error51) {
187442
+ return typeof error51 === "object" && error51 !== null && "code" in error51 && error51.code === "ENOENT";
187443
+ }
187444
+
187445
+ // src/cli/setup.ts
185942
187446
  async function runSetup(options) {
185943
187447
  const client = parseClient(options.client);
187448
+ validateSkillOnlyOptions(options, client);
185944
187449
  const runner = parseRunner(options.runner);
185945
187450
  const command = options.command ? parseCommandSpec(options.command) : commandForRunner(runner);
187451
+ const env = options.env ?? process.env;
187452
+ const home = resolveUserHome(env);
185946
187453
  const context = {
185947
187454
  cwd: options.cwd,
185948
- home: options.env?.HOME ?? homedir3(),
185949
- env: options.env ?? process.env,
187455
+ home,
187456
+ codexHome: dirname7(resolveCodexConfigPath(env, options.cwd)),
187457
+ env,
185950
187458
  write: options.write,
185951
187459
  scope: options.global ? "global" : "project",
187460
+ skillOnly: options.skillOnly ?? false,
187461
+ force: options.force ?? false,
185952
187462
  mcpCommand: command,
185953
187463
  sourceSkillDir: resolveBundledSkillDir()
185954
187464
  };
@@ -185991,50 +187501,103 @@ function buildClaudeMcpEntry(command) {
185991
187501
  function skillDestination(client, scope, cwd, home) {
185992
187502
  if (client === "codex") {
185993
187503
  const root2 = scope === "global" ? home : cwd;
185994
- return join3(root2, ".agents", "skills", "kyoso-review");
187504
+ return join6(root2, ".agents", "skills", "kyoso-review");
185995
187505
  }
185996
187506
  const root = scope === "global" ? home : cwd;
185997
- return join3(root, ".claude", "skills", "kyoso-review");
187507
+ return join6(root, ".claude", "skills", "kyoso-review");
187508
+ }
187509
+ function resolveUserHome(env = process.env) {
187510
+ return env.HOME ? resolve7(env.HOME) : homedir3();
187511
+ }
187512
+ function resolveCodexConfigPath(env = process.env, cwd = process.cwd()) {
187513
+ const codexHome = env.CODEX_HOME ? resolve7(cwd, env.CODEX_HOME) : join6(resolveUserHome(env), ".codex");
187514
+ return join6(codexHome, "config.toml");
187515
+ }
187516
+ function detectCodexPluginMcpOverride(options) {
187517
+ const env = options.env ?? process.env;
187518
+ const path = resolveCodexConfigPath(env, options.cwd);
187519
+ if (!existsSync2(path))
187520
+ return { status: "missing", path };
187521
+ try {
187522
+ const parsed = parse5(readTextSync(path));
187523
+ if (hasUnprobedProjectIntegrationOverride(parsed, options.cwd, resolveUserHome(env))) {
187524
+ return { status: "unknown", path };
187525
+ }
187526
+ return {
187527
+ status: nestedMcpEntryStatus(parsed, [
187528
+ "plugins",
187529
+ "kyoso@kyoso",
187530
+ "mcp_servers",
187531
+ "kyoso"
187532
+ ]),
187533
+ path
187534
+ };
187535
+ } catch {
187536
+ return { status: "unknown", path };
187537
+ }
185998
187538
  }
185999
187539
  function detectSetup(options) {
186000
- const home = options.home ?? homedir3();
187540
+ const env = options.env ?? process.env;
187541
+ const home = options.home ? resolve7(options.home) : resolveUserHome(env);
187542
+ const codexConfigPath = options.codexHome ? join6(resolve7(options.codexHome), "config.toml") : options.home ? join6(home, ".codex", "config.toml") : resolveCodexConfigPath(env, options.cwd);
187543
+ const codexMcp = detectCodexMcp(codexConfigPath, options.cwd, home);
187544
+ const claudeMcp = mergeMcpDetections([
187545
+ detectClaudeMcp(join6(options.cwd, ".mcp.json"), options.cwd, home),
187546
+ detectClaudeMcp(join6(home, ".claude.json"), options.cwd, home)
187547
+ ]);
187548
+ const codexSkillPaths = existingSkillPaths([
187549
+ join6(options.cwd, ".agents", "skills", "kyoso-review"),
187550
+ join6(home, ".agents", "skills", "kyoso-review")
187551
+ ]);
187552
+ const claudeSkillPaths = existingSkillPaths([
187553
+ join6(options.cwd, ".claude", "skills", "kyoso-review"),
187554
+ join6(home, ".claude", "skills", "kyoso-review")
187555
+ ]);
186001
187556
  return {
186002
187557
  codex: {
186003
- mcp: hasCodexMcp(join3(home, ".codex", "config.toml")),
186004
- skill: existsSync(join3(options.cwd, ".agents", "skills", "kyoso-review", "SKILL.md")) || existsSync(join3(home, ".agents", "skills", "kyoso-review", "SKILL.md"))
187558
+ mcp: codexMcp.status === "enabled",
187559
+ skill: codexSkillPaths.length > 0,
187560
+ manualMcpStatus: codexMcp.status,
187561
+ mcpPaths: codexMcp.paths,
187562
+ skillPaths: codexSkillPaths
186005
187563
  },
186006
187564
  "claude-code": {
186007
- mcp: hasClaudeMcp(join3(options.cwd, ".mcp.json")) || hasClaudeMcp(join3(home, ".claude.json")),
186008
- skill: existsSync(join3(options.cwd, ".claude", "skills", "kyoso-review", "SKILL.md")) || existsSync(join3(home, ".claude", "skills", "kyoso-review", "SKILL.md"))
187565
+ mcp: claudeMcp.status === "enabled",
187566
+ skill: claudeSkillPaths.length > 0,
187567
+ manualMcpStatus: claudeMcp.status,
187568
+ mcpPaths: claudeMcp.paths,
187569
+ skillPaths: claudeSkillPaths
186009
187570
  }
186010
187571
  };
186011
187572
  }
186012
187573
  async function setupCodex(context) {
187574
+ if (context.skillOnly) {
187575
+ return [
187576
+ await ensureSkill(context, "codex", "Codex skill"),
187577
+ ...singleAgentAdvice(context, "codex")
187578
+ ];
187579
+ }
186013
187580
  return [
186014
187581
  await ensureCodexMcp(context),
186015
- await ensureSkill({
186016
- title: "Codex skill",
186017
- sourceDir: context.sourceSkillDir,
186018
- destinationDir: skillDestination("codex", context.scope, context.cwd, context.home),
186019
- write: context.write
186020
- }),
187582
+ await ensureSkill(context, "codex", "Codex skill"),
186021
187583
  ...singleAgentAdvice(context, "codex")
186022
187584
  ];
186023
187585
  }
186024
187586
  async function setupClaudeCode(context) {
187587
+ if (context.skillOnly) {
187588
+ return [
187589
+ await ensureSkill(context, "claude-code", "Claude Code skill"),
187590
+ ...singleAgentAdvice(context, "claude-code")
187591
+ ];
187592
+ }
186025
187593
  return [
186026
187594
  await ensureClaudeMcp(context),
186027
- await ensureSkill({
186028
- title: "Claude Code skill",
186029
- sourceDir: context.sourceSkillDir,
186030
- destinationDir: skillDestination("claude-code", context.scope, context.cwd, context.home),
186031
- write: context.write
186032
- }),
187595
+ await ensureSkill(context, "claude-code", "Claude Code skill"),
186033
187596
  ...singleAgentAdvice(context, "claude-code")
186034
187597
  ];
186035
187598
  }
186036
187599
  async function ensureCodexMcp(context) {
186037
- const configPath = join3(context.home, ".codex", "config.toml");
187600
+ const configPath = join6(context.codexHome, "config.toml");
186038
187601
  const snippet = buildCodexMcpToml(context.mcpCommand);
186039
187602
  const current = await readOptionalFile(configPath);
186040
187603
  if (hasCodexMcpContent(current)) {
@@ -186042,7 +187605,7 @@ async function ensureCodexMcp(context) {
186042
187605
  title: "Codex MCP",
186043
187606
  status: "skipped",
186044
187607
  path: configPath,
186045
- detail: "existing [mcp_servers.kyoso] kept"
187608
+ detail: codexMcpStatusFromContent(current) === "disabled" ? disabledCodexMcpDetail(configPath) : "existing [mcp_servers.kyoso] kept"
186046
187609
  };
186047
187610
  }
186048
187611
  const detail = diffForAppend(configPath, snippet);
@@ -186053,8 +187616,8 @@ async function ensureCodexMcp(context) {
186053
187616
  `) ? `
186054
187617
 
186055
187618
  ` : "";
186056
- await mkdir3(dirname4(configPath), { recursive: true });
186057
- await writeFile3(configPath, `${current}${separator}${snippet}`, "utf8");
187619
+ await mkdir5(dirname7(configPath), { recursive: true });
187620
+ await writeFile4(configPath, `${current}${separator}${snippet}`, "utf8");
186058
187621
  return {
186059
187622
  title: "Codex MCP",
186060
187623
  status: current.length > 0 ? "updated" : "created",
@@ -186066,15 +187629,15 @@ async function ensureClaudeMcp(context) {
186066
187629
  if (context.scope === "global") {
186067
187630
  return ensureClaudeGlobalMcp(context);
186068
187631
  }
186069
- const configPath = join3(context.cwd, ".mcp.json");
187632
+ const configPath = join6(context.cwd, ".mcp.json");
186070
187633
  const current = await readJsonObject(configPath);
186071
187634
  const mcpServers = recordValue(current.mcpServers);
186072
- if (isRecord5(mcpServers.kyoso)) {
187635
+ if (isRecord7(mcpServers.kyoso)) {
186073
187636
  return {
186074
187637
  title: "Claude Code MCP",
186075
187638
  status: "skipped",
186076
187639
  path: configPath,
186077
- detail: "existing mcpServers.kyoso kept"
187640
+ detail: mcpEntryStatus(mcpServers.kyoso) === "disabled" ? disabledClaudeMcpDetail(configPath) : "existing mcpServers.kyoso kept"
186078
187641
  };
186079
187642
  }
186080
187643
  const next = {
@@ -186093,7 +187656,7 @@ async function ensureClaudeMcp(context) {
186093
187656
  detail
186094
187657
  };
186095
187658
  }
186096
- await writeFile3(configPath, `${JSON.stringify(next, null, 2)}
187659
+ await writeFile4(configPath, `${JSON.stringify(next, null, 2)}
186097
187660
  `, "utf8");
186098
187661
  return {
186099
187662
  title: "Claude Code MCP",
@@ -186103,13 +187666,14 @@ async function ensureClaudeMcp(context) {
186103
187666
  };
186104
187667
  }
186105
187668
  function ensureClaudeGlobalMcp(context) {
186106
- const configPath = join3(context.home, ".claude.json");
186107
- if (hasClaudeMcp(configPath)) {
187669
+ const configPath = join6(context.home, ".claude.json");
187670
+ const existingMcp = detectClaudeMcp(configPath, context.cwd, context.home);
187671
+ if (existingMcp.status !== "missing") {
186108
187672
  return {
186109
187673
  title: "Claude Code MCP",
186110
187674
  status: "skipped",
186111
187675
  path: configPath,
186112
- detail: "existing mcpServers.kyoso kept"
187676
+ detail: existingMcp.status === "disabled" ? disabledClaudeMcpDetail(configPath) : "existing mcpServers.kyoso kept"
186113
187677
  };
186114
187678
  }
186115
187679
  const json2 = JSON.stringify(buildClaudeMcpEntry(context.mcpCommand));
@@ -186123,7 +187687,7 @@ function ensureClaudeGlobalMcp(context) {
186123
187687
  detail: commandLine
186124
187688
  };
186125
187689
  }
186126
- const result = spawnSync("claude", args, { encoding: "utf8" });
187690
+ const result = spawnSync2("claude", args, { encoding: "utf8" });
186127
187691
  if (result.status !== 0) {
186128
187692
  throw new Error(result.stderr || result.stdout || "claude mcp add-json failed");
186129
187693
  }
@@ -186134,43 +187698,25 @@ function ensureClaudeGlobalMcp(context) {
186134
187698
  detail: commandLine
186135
187699
  };
186136
187700
  }
186137
- async function ensureSkill(options) {
186138
- const destinationSkill = join3(options.destinationDir, "SKILL.md");
186139
- if (existsSync(destinationSkill)) {
186140
- return {
186141
- title: options.title,
186142
- status: "skipped",
186143
- path: options.destinationDir,
186144
- detail: "existing kyoso-review skill kept"
186145
- };
186146
- }
186147
- const detail = [
186148
- `copy ${options.sourceDir}`,
186149
- `to ${options.destinationDir}`
186150
- ].join(`
186151
- `);
186152
- if (!options.write) {
186153
- return {
186154
- title: options.title,
186155
- status: "dry-run",
186156
- path: options.destinationDir,
186157
- detail
186158
- };
186159
- }
186160
- await mkdir3(dirname4(options.destinationDir), { recursive: true });
186161
- await cp(options.sourceDir, options.destinationDir, {
186162
- recursive: true,
186163
- force: false
187701
+ async function ensureSkill(context, client, title) {
187702
+ const result = await ensureManagedSkill({
187703
+ sourceDir: context.sourceSkillDir,
187704
+ destinationDir: skillDestination(client, context.scope, context.cwd, context.home),
187705
+ trustedRoot: context.scope === "global" ? context.home : context.cwd,
187706
+ write: context.write,
187707
+ force: context.force
186164
187708
  });
186165
187709
  return {
186166
- title: options.title,
186167
- status: "created",
186168
- path: options.destinationDir,
186169
- detail
187710
+ title,
187711
+ ...result
186170
187712
  };
186171
187713
  }
186172
187714
  function renderSetupOverview(context) {
186173
- const detected = detectSetup({ cwd: context.cwd, home: context.home });
187715
+ const detected = detectSetup({
187716
+ cwd: context.cwd,
187717
+ home: context.home,
187718
+ codexHome: context.codexHome
187719
+ });
186174
187720
  return [
186175
187721
  "Kyoso setup",
186176
187722
  "",
@@ -186179,8 +187725,10 @@ function renderSetupOverview(context) {
186179
187725
  ` claude-code: MCP ${statusWord(detected["claude-code"].mcp)}, skill ${statusWord(detected["claude-code"].skill)}`,
186180
187726
  "",
186181
187727
  "Commands",
186182
- " kyoso setup codex [--write] [--runner npx|bunx] [--global]",
186183
- " kyoso setup claude-code [--write] [--runner npx|bunx] [--global]",
187728
+ " kyoso setup codex [--write] [--runner npx|bunx] [--command <command>] [--global] [--force]",
187729
+ " kyoso setup claude-code [--write] [--runner npx|bunx] [--command <command>] [--global] [--force]",
187730
+ " kyoso setup codex --skill-only [--write] [--global] [--force]",
187731
+ " kyoso setup claude-code --skill-only [--write] [--global] [--force]",
186184
187732
  "",
186185
187733
  `Default MCP command: ${context.mcpCommand.command} ${context.mcpCommand.args.join(" ")}`,
186186
187734
  "Dry-run is the default. Add --write to modify files."
@@ -186206,6 +187754,19 @@ function parseClient(client) {
186206
187754
  return client;
186207
187755
  throw new Error(`Invalid setup client "${client}". Expected codex or claude-code.`);
186208
187756
  }
187757
+ function validateSkillOnlyOptions(options, client) {
187758
+ if (!options.skillOnly)
187759
+ return;
187760
+ if (!client) {
187761
+ throw new Error("--skill-only requires setup client codex or claude-code.");
187762
+ }
187763
+ if (options.runner !== undefined) {
187764
+ throw new Error("--skill-only cannot be combined with --runner.");
187765
+ }
187766
+ if (options.command !== undefined) {
187767
+ throw new Error("--skill-only cannot be combined with --command.");
187768
+ }
187769
+ }
186209
187770
  function parseRunner(runner) {
186210
187771
  if (runner === undefined || runner === "npx")
186211
187772
  return "npx";
@@ -186253,21 +187814,21 @@ function splitCommand(value) {
186253
187814
  return parts;
186254
187815
  }
186255
187816
  function resolveBundledSkillDir() {
186256
- const start = dirname4(fileURLToPath(import.meta.url));
187817
+ const start = dirname7(fileURLToPath(import.meta.url));
186257
187818
  let current = start;
186258
187819
  for (let depth = 0;depth < 5; depth += 1) {
186259
- const candidate = join3(current, ".agents", "skills", "kyoso-review");
186260
- if (existsSync(join3(candidate, "SKILL.md")))
187820
+ const candidate = join6(current, ".agents", "skills", "kyoso-review");
187821
+ if (existsSync2(join6(candidate, "SKILL.md")))
186261
187822
  return candidate;
186262
- current = dirname4(current);
187823
+ current = dirname7(current);
186263
187824
  }
186264
187825
  throw new Error("Bundled kyoso-review skill was not found in this package.");
186265
187826
  }
186266
187827
  async function readOptionalFile(path) {
186267
187828
  try {
186268
- return await readFile5(path, "utf8");
187829
+ return await readFile6(path, "utf8");
186269
187830
  } catch (error51) {
186270
- if (isMissingPathError2(error51))
187831
+ if (isMissingPathError4(error51))
186271
187832
  return "";
186272
187833
  throw error51;
186273
187834
  }
@@ -186277,41 +187838,180 @@ async function readJsonObject(path) {
186277
187838
  if (content.trim().length === 0)
186278
187839
  return {};
186279
187840
  const parsed = JSON.parse(content);
186280
- if (!isRecord5(parsed))
187841
+ if (!isRecord7(parsed))
186281
187842
  throw new Error(`${path} must contain a JSON object`);
186282
187843
  return parsed;
186283
187844
  }
186284
- function hasCodexMcp(path) {
186285
- return existsSync(path) && hasCodexMcpContent(readTextSync(path));
186286
- }
186287
187845
  function hasCodexMcpContent(content) {
186288
187846
  return /^\s*\[mcp_servers\.(?:"kyoso"|kyoso)]\s*$/m.test(content);
186289
187847
  }
186290
- function hasClaudeMcp(path) {
186291
- if (!existsSync(path))
186292
- return false;
187848
+ function codexMcpStatusFromContent(content) {
187849
+ try {
187850
+ const parsed = parse5(content);
187851
+ if (!isRecord7(parsed))
187852
+ return "unknown";
187853
+ if (!isRecord7(parsed.mcp_servers))
187854
+ return "missing";
187855
+ if (!("kyoso" in parsed.mcp_servers))
187856
+ return "missing";
187857
+ return mcpEntryStatus(parsed.mcp_servers.kyoso);
187858
+ } catch {
187859
+ return "unknown";
187860
+ }
187861
+ }
187862
+ function disabledCodexMcpDetail(configPath) {
187863
+ return [
187864
+ "existing [mcp_servers.kyoso] is disabled and was kept unchanged.",
187865
+ `To re-enable it, edit ${configPath} and change enabled = false to enabled = true.`
187866
+ ].join(" ");
187867
+ }
187868
+ function disabledClaudeMcpDetail(configPath) {
187869
+ return [
187870
+ "existing mcpServers.kyoso is disabled and was kept unchanged.",
187871
+ `To re-enable it, edit ${configPath} and change "enabled": false to "enabled": true.`
187872
+ ].join(" ");
187873
+ }
187874
+ function detectCodexMcp(path, cwd, home) {
187875
+ if (!existsSync2(path))
187876
+ return { status: "missing", paths: [] };
187877
+ try {
187878
+ const parsed = parse5(readTextSync(path));
187879
+ if (hasUnprobedProjectIntegrationOverride(parsed, cwd, home)) {
187880
+ return { status: "unknown", paths: [path] };
187881
+ }
187882
+ if (!isRecord7(parsed))
187883
+ return { status: "unknown", paths: [path] };
187884
+ if (!("mcp_servers" in parsed))
187885
+ return { status: "missing", paths: [] };
187886
+ if (!isRecord7(parsed.mcp_servers)) {
187887
+ return { status: "unknown", paths: [path] };
187888
+ }
187889
+ if (!("kyoso" in parsed.mcp_servers)) {
187890
+ return { status: "missing", paths: [] };
187891
+ }
187892
+ return { status: mcpEntryStatus(parsed.mcp_servers.kyoso), paths: [path] };
187893
+ } catch {
187894
+ return { status: "unknown", paths: [path] };
187895
+ }
187896
+ }
187897
+ function detectClaudeMcp(path, cwd, home) {
187898
+ if (!existsSync2(path))
187899
+ return { status: "missing", paths: [] };
186293
187900
  try {
186294
187901
  const parsed = JSON.parse(readTextSync(path));
186295
- return jsonHasKyosoMcp(parsed);
187902
+ const statuses = jsonMcpStatuses(parsed, cwd, home);
187903
+ if (statuses.length === 0)
187904
+ return { status: "missing", paths: [] };
187905
+ return { status: mergeMcpStatuses(statuses), paths: [path] };
186296
187906
  } catch {
186297
- return false;
187907
+ return { status: "unknown", paths: [path] };
186298
187908
  }
186299
187909
  }
186300
- function jsonHasKyosoMcp(value) {
186301
- if (!isRecord5(value))
187910
+ function jsonMcpStatuses(value, cwd, home) {
187911
+ if (!isRecord7(value))
187912
+ return ["unknown"];
187913
+ const statuses = directMcpStatuses(value);
187914
+ if (!("projects" in value))
187915
+ return statuses;
187916
+ if (!isRecord7(value.projects))
187917
+ return [...statuses, "unknown"];
187918
+ const currentProject = normalizeProjectPath(cwd, home);
187919
+ for (const [projectPath, projectConfig] of Object.entries(value.projects)) {
187920
+ if (normalizeProjectPath(projectPath, home) !== currentProject)
187921
+ continue;
187922
+ if (!isRecord7(projectConfig)) {
187923
+ statuses.push("unknown");
187924
+ continue;
187925
+ }
187926
+ statuses.push(...directMcpStatuses(projectConfig));
187927
+ }
187928
+ return statuses;
187929
+ }
187930
+ function directMcpStatuses(value) {
187931
+ const statuses = [];
187932
+ if ("mcpServers" in value) {
187933
+ if (!isRecord7(value.mcpServers)) {
187934
+ statuses.push("unknown");
187935
+ } else if ("kyoso" in value.mcpServers) {
187936
+ statuses.push(mcpEntryStatus(value.mcpServers.kyoso));
187937
+ }
187938
+ }
187939
+ return statuses;
187940
+ }
187941
+ function nestedMcpEntryStatus(value, path) {
187942
+ let current = value;
187943
+ for (const key of path) {
187944
+ if (!isRecord7(current))
187945
+ return "unknown";
187946
+ if (!(key in current))
187947
+ return "missing";
187948
+ current = current[key];
187949
+ }
187950
+ return mcpEntryStatus(current);
187951
+ }
187952
+ function hasUnprobedProjectIntegrationOverride(value, cwd, home) {
187953
+ if (!isRecord7(value) || !isRecord7(value.projects))
186302
187954
  return false;
186303
- if (isRecord5(value.mcpServers) && isRecord5(value.mcpServers.kyoso)) {
186304
- return true;
187955
+ const currentProject = normalizeProjectPath(cwd, home);
187956
+ for (const [projectPath, projectConfig] of Object.entries(value.projects)) {
187957
+ if (normalizeProjectPath(projectPath, home) !== currentProject || !isRecord7(projectConfig)) {
187958
+ continue;
187959
+ }
187960
+ if ("mcp_servers" in projectConfig || "plugins" in projectConfig) {
187961
+ return true;
187962
+ }
186305
187963
  }
186306
- return Object.values(value).some((child) => jsonHasKyosoMcp(child));
187964
+ return false;
187965
+ }
187966
+ function normalizeProjectPath(path, home) {
187967
+ if (path.trim().length === 0)
187968
+ return;
187969
+ const expanded = path === "~" ? home : path.startsWith("~/") || path.startsWith("~\\") ? join6(home, path.slice(2)) : path;
187970
+ const resolved = resolve7(expanded);
187971
+ let normalized;
187972
+ try {
187973
+ normalized = realpathSync2(resolved);
187974
+ } catch {
187975
+ normalized = resolved;
187976
+ }
187977
+ return process.platform === "win32" ? normalized.toLowerCase() : normalized;
187978
+ }
187979
+ function mcpEntryStatus(value) {
187980
+ if (!isRecord7(value))
187981
+ return "unknown";
187982
+ if (!("enabled" in value))
187983
+ return "enabled";
187984
+ if (value.enabled === true)
187985
+ return "enabled";
187986
+ if (value.enabled === false)
187987
+ return "disabled";
187988
+ return "unknown";
187989
+ }
187990
+ function mergeMcpDetections(detections) {
187991
+ return {
187992
+ status: mergeMcpStatuses(detections.map((detection) => detection.status)),
187993
+ paths: detections.flatMap((detection) => detection.paths)
187994
+ };
187995
+ }
187996
+ function mergeMcpStatuses(statuses) {
187997
+ if (statuses.includes("enabled"))
187998
+ return "enabled";
187999
+ if (statuses.includes("unknown"))
188000
+ return "unknown";
188001
+ if (statuses.includes("disabled"))
188002
+ return "disabled";
188003
+ return "missing";
188004
+ }
188005
+ function existingSkillPaths(paths) {
188006
+ return [...new Set(paths)].filter((path) => existsSync2(join6(path, "SKILL.md")));
186307
188007
  }
186308
188008
  function readTextSync(path) {
186309
- return readFileSync(path, "utf8");
188009
+ return readFileSync2(path, "utf8");
186310
188010
  }
186311
188011
  function recordValue(value) {
186312
188012
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
186313
188013
  }
186314
- function isRecord5(value) {
188014
+ function isRecord7(value) {
186315
188015
  return typeof value === "object" && value !== null && !Array.isArray(value);
186316
188016
  }
186317
188017
  function diffForAppend(path, snippet) {
@@ -186342,7 +188042,7 @@ function statusWord(value) {
186342
188042
  return value ? "ok" : "missing";
186343
188043
  }
186344
188044
  function singleAgentAdvice(context, client) {
186345
- if (client === "claude-code" && !commandExists("codex", context.env)) {
188045
+ if (client === "claude-code" && !commandExists2("codex", context.env)) {
186346
188046
  return [
186347
188047
  {
186348
188048
  title: "Single-agent config",
@@ -186358,7 +188058,7 @@ function singleAgentAdvice(context, client) {
186358
188058
  }
186359
188059
  ];
186360
188060
  }
186361
- if (client === "codex" && !commandExists("claude", context.env)) {
188061
+ if (client === "codex" && !commandExists2("claude", context.env)) {
186362
188062
  return [
186363
188063
  {
186364
188064
  title: "Single-agent config",
@@ -186386,11 +188086,11 @@ function shellQuote(value) {
186386
188086
  return value;
186387
188087
  return `'${value.replaceAll("'", "'\\''")}'`;
186388
188088
  }
186389
- function commandExists(command, env) {
186390
- const paths = env.PATH?.split(delimiter) ?? [];
186391
- return paths.some((path) => existsSync(join3(path, command)));
188089
+ function commandExists2(command, env) {
188090
+ const paths = env.PATH?.split(delimiter2) ?? [];
188091
+ return paths.some((path) => existsSync2(join6(path, command)));
186392
188092
  }
186393
- function isMissingPathError2(error51) {
188093
+ function isMissingPathError4(error51) {
186394
188094
  return typeof error51 === "object" && error51 !== null && "code" in error51 && error51.code === "ENOENT";
186395
188095
  }
186396
188096
 
@@ -186399,11 +188099,11 @@ async function runDoctor(options) {
186399
188099
  const env = options.env ?? process.env;
186400
188100
  const loaded = await loadConfig(options);
186401
188101
  const globalConfigPath = resolveGlobalTomlConfigPath(env);
186402
- const projectTomlPath = resolve4(options.cwd, "kyoso.toml");
186403
- const projectTsPath = resolve4(options.cwd, "kyoso.config.ts");
188102
+ const projectTomlPath = resolve8(options.cwd, "kyoso.toml");
188103
+ const projectTsPath = resolve8(options.cwd, "kyoso.config.ts");
186404
188104
  const lines = ["Kyoso doctor", "", "Runtime"];
186405
- lines.push(` Bun: ${commandExists2("bun", env) ? "ok" : "warning not found"}`);
186406
- lines.push(` Node/npm: ${commandExists2("npm", env) ? "ok" : "warning npm not found"}`);
188105
+ lines.push(` Bun: ${commandExists3("bun", env) ? "ok" : "warning not found"}`);
188106
+ lines.push(` Node/npm: ${commandExists3("npm", env) ? "ok" : "warning npm not found"}`);
186407
188107
  lines.push("", "Config");
186408
188108
  lines.push(` global config.toml: ${formatLayer(loaded, "global_toml", globalConfigPath)}`);
186409
188109
  lines.push(` kyoso.toml: ${formatLayer(loaded, "project_toml", projectTomlPath)}`);
@@ -186413,36 +188113,37 @@ async function runDoctor(options) {
186413
188113
  lines.push(` config hash: ${loaded.configHash}`);
186414
188114
  for (const warning of loaded.warnings)
186415
188115
  lines.push(` warning: ${warning}`);
186416
- const setup = detectSetup({ cwd: options.cwd, home: env.HOME });
188116
+ const setup = detectSetup({ cwd: options.cwd, env });
188117
+ const cli = detectCli({ cwd: options.cwd, env });
188118
+ const codexIntegration = determineCodexIntegration({
188119
+ cwd: options.cwd,
188120
+ env,
188121
+ setup: setup.codex,
188122
+ cli,
188123
+ pluginInspector: options.pluginInspector ?? inspectCodexPlugin,
188124
+ mcpListInspector: options.mcpListInspector ?? inspectCodexMcpList
188125
+ });
188126
+ const claudeIntegration = determineClientIntegration("claude-code", setup["claude-code"], cli);
186417
188127
  lines.push("", "MCP", " stdio server: ok");
186418
- lines.push(` Codex registration: ${setup.codex.mcp ? "ok" : "missing"}`);
186419
- lines.push(` Claude Code registration: ${setup["claude-code"].mcp ? "ok" : "missing"}`);
186420
- if (!setup.codex.mcp) {
186421
- lines.push(" next: run `npx @kyo-so/cli setup codex --write`");
186422
- }
186423
- if (!setup["claude-code"].mcp) {
186424
- lines.push(" next: run `npx @kyo-so/cli setup claude-code --write`");
186425
- }
188128
+ lines.push(` Codex registration: ${formatManualMcpStatus(setup.codex.manualMcpStatus)}`);
188129
+ lines.push(` Claude Code registration: ${formatManualMcpStatus(setup["claude-code"].manualMcpStatus)}`);
186426
188130
  lines.push("", "Skills");
186427
188131
  lines.push(` Codex kyoso-review: ${setup.codex.skill ? "ok" : "missing"}`);
186428
188132
  lines.push(` Claude Code kyoso-review: ${setup["claude-code"].skill ? "ok" : "missing"}`);
186429
- if (!setup.codex.skill) {
186430
- lines.push(" next: run `npx @kyo-so/cli setup codex --write`");
186431
- }
186432
- if (!setup["claude-code"].skill) {
186433
- lines.push(" next: run `npx @kyo-so/cli setup claude-code --write`");
186434
- }
188133
+ lines.push("", "Integration");
188134
+ appendIntegration(lines, codexIntegration);
188135
+ appendIntegration(lines, claudeIntegration);
186435
188136
  lines.push("", "ACP agents");
186436
188137
  const agentCommandExists = {
186437
- codex: commandExists2(loaded.config.agents.codex.command, env),
186438
- claude: commandExists2(loaded.config.agents.claude.command, env)
188138
+ codex: commandExists3(loaded.config.agents.codex.command, env),
188139
+ claude: commandExists3(loaded.config.agents.claude.command, env)
186439
188140
  };
186440
188141
  for (const agent of ["codex", "claude"]) {
186441
188142
  const config2 = loaded.config.agents[agent];
186442
188143
  const exists3 = agentCommandExists[agent];
186443
188144
  lines.push(` ${agent === "codex" ? "Codex" : "Claude"}: ${exists3 ? "ok" : "warning command not found"}`);
186444
188145
  lines.push(` command: ${[config2.command, ...config2.args].join(" ")}`);
186445
- if (!exists3 && config2.command === "npx" && commandExists2("bunx", env)) {
188146
+ if (!exists3 && config2.command === "npx" && commandExists3("bunx", env)) {
186446
188147
  lines.push(' hint: set agents.<name>.command = "bunx" in config.toml');
186447
188148
  }
186448
188149
  if (agent === "claude") {
@@ -186477,10 +188178,209 @@ async function runDoctor(options) {
186477
188178
  lines.push(` network default: ${loaded.config.network.defaultMode}`);
186478
188179
  lines.push("", "Audit");
186479
188180
  lines.push(` directory: ${loaded.config.audit.directory}`);
188181
+ const auditStateRoot = await inspectAuditStateRootCapability({
188182
+ cwd: options.cwd,
188183
+ env
188184
+ });
188185
+ lines.push(` state root: ${auditStateRoot.available ? "available" : "unavailable"}`);
186480
188186
  lines.push(` raw agent output: ${loaded.config.audit.includeRawAgentOutput ? "enabled" : "disabled"}`);
186481
188187
  return lines.join(`
186482
188188
  `);
186483
188189
  }
188190
+ function determineCodexIntegration(options) {
188191
+ const fallback = determineClientIntegration("codex", options.setup, options.cli);
188192
+ const plugin = options.pluginInspector({
188193
+ cwd: options.cwd,
188194
+ env: options.env
188195
+ });
188196
+ if (plugin.status === "unsupported") {
188197
+ return withPluginDetails(withIntegrationWarnings(fallback, [
188198
+ `Plugin detection unsupported: ${formatCodexInspectionFailure(plugin.failure)}`
188199
+ ], undefined, "not applicable"), "unsupported", "not applicable");
188200
+ }
188201
+ if (plugin.plugin.state === "not_installed") {
188202
+ return withPluginDetails(fallback, "not installed", "not applicable");
188203
+ }
188204
+ if (plugin.plugin.state === "disabled") {
188205
+ return withPluginDetails(withIntegrationWarnings(fallback, ["Plugin disabled."], undefined, undefined), "installed, disabled", "disabled with Plugin");
188206
+ }
188207
+ const pluginWarnings = pluginSkillWarnings(options.setup);
188208
+ const override = detectCodexPluginMcpOverride({
188209
+ cwd: options.cwd,
188210
+ env: options.env
188211
+ });
188212
+ if (options.setup.manualMcpStatus === "unknown") {
188213
+ return withPluginDetails(withIntegrationWarnings(fallback, [
188214
+ ...pluginWarnings,
188215
+ "Plugin MCP origin is unknown because the manual MCP configuration could not be classified."
188216
+ ], "unknown", "unknown"), "installed, enabled", "unknown");
188217
+ }
188218
+ if (options.setup.manualMcpStatus === "enabled" && override.status !== "disabled") {
188219
+ return withPluginDetails(withIntegrationWarnings({
188220
+ ...fallback,
188221
+ mode: "manual-mcp",
188222
+ advice: integrationAdvice("manual-mcp", "codex")
188223
+ }, [
188224
+ ...pluginWarnings,
188225
+ "Plugin and manual Codex MCP registrations coexist; neither registration was changed.",
188226
+ "Plugin MCP origin is unknown while a manual MCP registration is enabled."
188227
+ ], undefined, "unknown"), "installed, enabled", "unknown");
188228
+ }
188229
+ if (override.status === "unknown") {
188230
+ return withPluginDetails(withIntegrationWarnings(fallback, [
188231
+ ...pluginWarnings,
188232
+ "Plugin MCP override could not be safely classified from the Codex configuration."
188233
+ ], "unknown", "unknown"), "installed, enabled", "unknown");
188234
+ }
188235
+ if (override.status === "disabled" && options.setup.manualMcpStatus === "enabled") {
188236
+ return withPluginDetails(withIntegrationWarnings({
188237
+ ...fallback,
188238
+ mode: "manual-mcp",
188239
+ advice: integrationAdvice("manual-mcp", "codex")
188240
+ }, [
188241
+ ...pluginWarnings,
188242
+ "Plugin and manual Codex MCP registrations coexist; neither registration was changed.",
188243
+ "Plugin MCP is disabled by configuration override while the manual MCP remains enabled."
188244
+ ], undefined, "disabled by configuration override"), "installed, enabled", "disabled by configuration override");
188245
+ }
188246
+ if (override.status === "disabled" && options.setup.manualMcpStatus === "disabled") {
188247
+ return withPluginDetails(withIntegrationWarnings({
188248
+ ...fallback,
188249
+ mode: "plugin-skill",
188250
+ advice: integrationAdvice("plugin-skill", "codex")
188251
+ }, pluginWarnings, undefined, "disabled by configuration override"), "installed, enabled", "disabled by configuration override");
188252
+ }
188253
+ if (options.setup.manualMcpStatus === "disabled") {
188254
+ return withPluginDetails(withIntegrationWarnings({
188255
+ ...fallback,
188256
+ mode: "plugin-mcp",
188257
+ advice: integrationAdvice("plugin-mcp", "codex")
188258
+ }, pluginWarnings, undefined, "enabled (manual registration is disabled)"), "installed, enabled", "enabled (manual registration is disabled)");
188259
+ }
188260
+ const effectiveMcp = options.mcpListInspector({
188261
+ cwd: options.cwd,
188262
+ env: options.env
188263
+ });
188264
+ if (effectiveMcp.status === "unsupported") {
188265
+ return withPluginDetails(withIntegrationWarnings(fallback, [
188266
+ ...pluginWarnings,
188267
+ `Plugin MCP effective-state check unsupported: ${formatCodexInspectionFailure(effectiveMcp.failure)}`
188268
+ ], "unknown", "unknown"), "installed, enabled", "unknown");
188269
+ }
188270
+ const expectedEffectiveState = override.status === "disabled" ? "disabled" : "enabled";
188271
+ if (effectiveMcp.kyoso === "enabled" && expectedEffectiveState === "enabled") {
188272
+ return withPluginDetails(withIntegrationWarnings({
188273
+ ...fallback,
188274
+ mode: "plugin-mcp",
188275
+ advice: integrationAdvice("plugin-mcp", "codex")
188276
+ }, pluginWarnings, undefined, "enabled"), "installed, enabled", "enabled");
188277
+ }
188278
+ if (effectiveMcp.kyoso === "disabled" && expectedEffectiveState === "disabled") {
188279
+ return withPluginDetails(withIntegrationWarnings({
188280
+ ...fallback,
188281
+ mode: "plugin-skill",
188282
+ advice: integrationAdvice("plugin-skill", "codex")
188283
+ }, pluginWarnings, undefined, "disabled"), "installed, enabled", "disabled");
188284
+ }
188285
+ if (effectiveMcp.kyoso === "enabled" || effectiveMcp.kyoso === "disabled") {
188286
+ return withPluginDetails(withIntegrationWarnings(fallback, [
188287
+ ...pluginWarnings,
188288
+ "Plugin MCP effective state does not match the recorded configuration precedence; Plugin MCP mode was not inferred."
188289
+ ], "unknown", "unknown"), "installed, enabled", "unknown");
188290
+ }
188291
+ return withPluginDetails(withIntegrationWarnings(fallback, [
188292
+ ...pluginWarnings,
188293
+ "Plugin MCP effective state is unknown; Plugin MCP mode was not inferred."
188294
+ ], "unknown", "unknown"), "installed, enabled", "unknown");
188295
+ }
188296
+ function determineClientIntegration(client, setup, cli) {
188297
+ const integration = determineNonPluginIntegration({
188298
+ manualMcpStatus: setup.manualMcpStatus,
188299
+ hasSkill: setup.skill,
188300
+ cli
188301
+ });
188302
+ return {
188303
+ client: client === "codex" ? "Codex" : "Claude Code",
188304
+ commandClient: client,
188305
+ mode: integration.mode,
188306
+ setup,
188307
+ cli,
188308
+ warnings: integration.warnings,
188309
+ advice: integrationAdvice(integration.mode, client)
188310
+ };
188311
+ }
188312
+ function withPluginDetails(integration, plugin, pluginMcp) {
188313
+ return { ...integration, plugin, pluginMcp };
188314
+ }
188315
+ function withIntegrationWarnings(integration, warnings, mode, pluginMcp) {
188316
+ const nextMode = mode ?? integration.mode;
188317
+ return {
188318
+ ...integration,
188319
+ mode: nextMode,
188320
+ warnings: [...integration.warnings, ...warnings],
188321
+ advice: integrationAdvice(nextMode, integration.commandClient),
188322
+ ...pluginMcp ? { pluginMcp } : {}
188323
+ };
188324
+ }
188325
+ function pluginSkillWarnings(setup) {
188326
+ if (!setup.skill)
188327
+ return [];
188328
+ return [
188329
+ `Plugin Skill and manual Skill copy coexist; priority is not inferred. Manual Skill path(s): ${setup.skillPaths.join(", ")}`
188330
+ ];
188331
+ }
188332
+ function appendIntegration(lines, integration) {
188333
+ lines.push(` ${integration.client} integration: ${integration.mode}`);
188334
+ lines.push(` manual MCP: ${formatManualMcpStatus(integration.setup.manualMcpStatus)}`);
188335
+ if (integration.setup.mcpPaths.length > 0) {
188336
+ lines.push(` manual MCP path(s): ${integration.setup.mcpPaths.join(", ")}`);
188337
+ }
188338
+ lines.push(` Skill: ${integration.setup.skill ? "ok" : "missing"}`);
188339
+ if (integration.setup.skillPaths.length > 0) {
188340
+ lines.push(` manual Skill path(s): ${integration.setup.skillPaths.join(", ")}`);
188341
+ }
188342
+ lines.push(` CLI: ${formatCliAvailability(integration.cli.kyoso)}`);
188343
+ lines.push(` npx: ${integration.cli.npx ? "available" : "missing"}`);
188344
+ lines.push(` bunx: ${integration.cli.bunx ? "available" : "missing"}`);
188345
+ if (integration.plugin)
188346
+ lines.push(` Plugin: ${integration.plugin}`);
188347
+ if (integration.pluginMcp) {
188348
+ lines.push(` Plugin MCP: ${integration.pluginMcp}`);
188349
+ }
188350
+ for (const warning of integration.warnings) {
188351
+ lines.push(` warning: ${warning}`);
188352
+ }
188353
+ lines.push(` ${integration.advice}`);
188354
+ }
188355
+ function formatManualMcpStatus(status) {
188356
+ if (status === "enabled")
188357
+ return "ok";
188358
+ return status;
188359
+ }
188360
+ function integrationAdvice(mode, client) {
188361
+ if (mode === "plugin-mcp" || mode === "manual-mcp") {
188362
+ return "status: ready";
188363
+ }
188364
+ if (mode === "plugin-skill") {
188365
+ return "status: bundled Plugin MCP is disabled; re-enable it or remove the Plugin and use CLI plus Skill-only.";
188366
+ }
188367
+ if (mode === "cli-skill") {
188368
+ return "status: ready; MCP is optional for CLI plus Skill mode.";
188369
+ }
188370
+ if (mode === "skill-on-demand") {
188371
+ return "status: runnable on demand; package-runner fallback may need network access.";
188372
+ }
188373
+ if (mode === "mcp-only") {
188374
+ return `next: run \`npx @kyo-so/cli setup ${client} --write --skill-only\``;
188375
+ }
188376
+ if (mode === "cli-only") {
188377
+ return `next: run \`kyoso setup ${client} --write --skill-only\``;
188378
+ }
188379
+ if (mode === "missing") {
188380
+ return "next: choose a Marketplace Plugin, CLI plus Skill, or traditional MCP setup.";
188381
+ }
188382
+ return "status: inspect the warnings before changing integration state.";
188383
+ }
186484
188384
  function formatLayer(loaded, layer, defaultPath) {
186485
188385
  const source = loaded.sources.find((candidate) => candidate.layer === layer);
186486
188386
  return source ? `found ${source.path}` : `not found ${defaultPath}`;
@@ -186514,11 +188414,11 @@ function formatClaudeDualAuthWarning(preferApiKey) {
186514
188414
  function hasEnv2(env, key) {
186515
188415
  return typeof env[key] === "string" && env[key].trim().length > 0;
186516
188416
  }
186517
- function commandExists2(command, env) {
186518
- const paths = env.PATH?.split(delimiter2) ?? [];
188417
+ function commandExists3(command, env) {
188418
+ const paths = env.PATH?.split(delimiter3) ?? [];
186519
188419
  return paths.some((path) => {
186520
188420
  try {
186521
- accessSync(`${path}/${command}`);
188421
+ accessSync2(`${path}/${command}`);
186522
188422
  return true;
186523
188423
  } catch {
186524
188424
  return false;
@@ -186527,13 +188427,13 @@ function commandExists2(command, env) {
186527
188427
  }
186528
188428
 
186529
188429
  // src/cli/init.ts
186530
- import { access as access3, readFile as readFile6, writeFile as writeFile4 } from "node:fs/promises";
186531
- import { join as join4 } from "node:path";
188430
+ import { access as access3, readFile as readFile7, writeFile as writeFile5 } from "node:fs/promises";
188431
+ import { join as join7 } from "node:path";
186532
188432
  async function runInit(options) {
186533
- const configPath = join4(options.cwd, "kyoso.toml");
186534
- const tsConfigPath = join4(options.cwd, "kyoso.config.ts");
186535
- const skillPath = join4(options.cwd, ".agents/skills/kyoso-review/SKILL.md");
186536
- const gitignorePath = join4(options.cwd, ".gitignore");
188433
+ const configPath = join7(options.cwd, "kyoso.toml");
188434
+ const tsConfigPath = join7(options.cwd, "kyoso.config.ts");
188435
+ const skillPath = join7(options.cwd, ".agents/skills/kyoso-review/SKILL.md");
188436
+ const gitignorePath = join7(options.cwd, ".gitignore");
186537
188437
  const configResult = await writeFileWithOverwritePrompt(configPath, CONFIG_TEMPLATE, options.force);
186538
188438
  const skillResult = await writeFileWithOverwritePrompt(skillPath, SKILL_TEMPLATE, options.force);
186539
188439
  const gitignoreResult = await ensureGitignoreEntry(gitignorePath, ".kyoso/");
@@ -186551,11 +188451,11 @@ async function runInit(options) {
186551
188451
  async function ensureGitignoreEntry(path, entry) {
186552
188452
  let content = "";
186553
188453
  try {
186554
- content = await readFile6(path, "utf8");
188454
+ content = await readFile7(path, "utf8");
186555
188455
  } catch (error51) {
186556
- if (!isMissingPathError3(error51))
188456
+ if (!isMissingPathError5(error51))
186557
188457
  throw error51;
186558
- await writeFile4(path, `${entry}
188458
+ await writeFile5(path, `${entry}
186559
188459
  `, "utf8");
186560
188460
  return "created";
186561
188461
  }
@@ -186565,11 +188465,11 @@ async function ensureGitignoreEntry(path, entry) {
186565
188465
  const separator = content.length > 0 && !content.endsWith(`
186566
188466
  `) ? `
186567
188467
  ` : "";
186568
- await writeFile4(path, `${content}${separator}${entry}
188468
+ await writeFile5(path, `${content}${separator}${entry}
186569
188469
  `, "utf8");
186570
188470
  return "updated";
186571
188471
  }
186572
- function isMissingPathError3(error51) {
188472
+ function isMissingPathError5(error51) {
186573
188473
  return typeof error51 === "object" && error51 !== null && "code" in error51 && error51.code === "ENOENT";
186574
188474
  }
186575
188475
  async function exists3(path) {
@@ -187052,11 +188952,11 @@ var require_codegen = /* @__PURE__ */ __commonJSMin((exports) => {
187052
188952
  const rhs = this.rhs === undefined ? "" : ` = ${this.rhs}`;
187053
188953
  return `${varKind} ${this.name}${rhs};` + _n;
187054
188954
  }
187055
- optimizeNames(names, constants) {
188955
+ optimizeNames(names, constants2) {
187056
188956
  if (!names[this.name.str])
187057
188957
  return;
187058
188958
  if (this.rhs)
187059
- this.rhs = optimizeExpr(this.rhs, names, constants);
188959
+ this.rhs = optimizeExpr(this.rhs, names, constants2);
187060
188960
  return this;
187061
188961
  }
187062
188962
  get names() {
@@ -187073,10 +188973,10 @@ var require_codegen = /* @__PURE__ */ __commonJSMin((exports) => {
187073
188973
  render({ _n }) {
187074
188974
  return `${this.lhs} = ${this.rhs};` + _n;
187075
188975
  }
187076
- optimizeNames(names, constants) {
188976
+ optimizeNames(names, constants2) {
187077
188977
  if (this.lhs instanceof code_1.Name && !names[this.lhs.str] && !this.sideEffects)
187078
188978
  return;
187079
- this.rhs = optimizeExpr(this.rhs, names, constants);
188979
+ this.rhs = optimizeExpr(this.rhs, names, constants2);
187080
188980
  return this;
187081
188981
  }
187082
188982
  get names() {
@@ -187135,8 +189035,8 @@ var require_codegen = /* @__PURE__ */ __commonJSMin((exports) => {
187135
189035
  optimizeNodes() {
187136
189036
  return `${this.code}` ? this : undefined;
187137
189037
  }
187138
- optimizeNames(names, constants) {
187139
- this.code = optimizeExpr(this.code, names, constants);
189038
+ optimizeNames(names, constants2) {
189039
+ this.code = optimizeExpr(this.code, names, constants2);
187140
189040
  return this;
187141
189041
  }
187142
189042
  get names() {
@@ -187165,12 +189065,12 @@ var require_codegen = /* @__PURE__ */ __commonJSMin((exports) => {
187165
189065
  }
187166
189066
  return nodes.length > 0 ? this : undefined;
187167
189067
  }
187168
- optimizeNames(names, constants) {
189068
+ optimizeNames(names, constants2) {
187169
189069
  const { nodes } = this;
187170
189070
  let i = nodes.length;
187171
189071
  while (i--) {
187172
189072
  const n = nodes[i];
187173
- if (n.optimizeNames(names, constants))
189073
+ if (n.optimizeNames(names, constants2))
187174
189074
  continue;
187175
189075
  subtractNames(names, n.names);
187176
189076
  nodes.splice(i, 1);
@@ -187223,12 +189123,12 @@ var require_codegen = /* @__PURE__ */ __commonJSMin((exports) => {
187223
189123
  return;
187224
189124
  return this;
187225
189125
  }
187226
- optimizeNames(names, constants) {
189126
+ optimizeNames(names, constants2) {
187227
189127
  var _a3;
187228
- this.else = (_a3 = this.else) === null || _a3 === undefined ? undefined : _a3.optimizeNames(names, constants);
187229
- if (!(super.optimizeNames(names, constants) || this.else))
189128
+ this.else = (_a3 = this.else) === null || _a3 === undefined ? undefined : _a3.optimizeNames(names, constants2);
189129
+ if (!(super.optimizeNames(names, constants2) || this.else))
187230
189130
  return;
187231
- this.condition = optimizeExpr(this.condition, names, constants);
189131
+ this.condition = optimizeExpr(this.condition, names, constants2);
187232
189132
  return this;
187233
189133
  }
187234
189134
  get names() {
@@ -187251,10 +189151,10 @@ var require_codegen = /* @__PURE__ */ __commonJSMin((exports) => {
187251
189151
  render(opts) {
187252
189152
  return `for(${this.iteration})` + super.render(opts);
187253
189153
  }
187254
- optimizeNames(names, constants) {
187255
- if (!super.optimizeNames(names, constants))
189154
+ optimizeNames(names, constants2) {
189155
+ if (!super.optimizeNames(names, constants2))
187256
189156
  return;
187257
- this.iteration = optimizeExpr(this.iteration, names, constants);
189157
+ this.iteration = optimizeExpr(this.iteration, names, constants2);
187258
189158
  return this;
187259
189159
  }
187260
189160
  get names() {
@@ -187289,10 +189189,10 @@ var require_codegen = /* @__PURE__ */ __commonJSMin((exports) => {
187289
189189
  render(opts) {
187290
189190
  return `for(${this.varKind} ${this.name} ${this.loop} ${this.iterable})` + super.render(opts);
187291
189191
  }
187292
- optimizeNames(names, constants) {
187293
- if (!super.optimizeNames(names, constants))
189192
+ optimizeNames(names, constants2) {
189193
+ if (!super.optimizeNames(names, constants2))
187294
189194
  return;
187295
- this.iterable = optimizeExpr(this.iterable, names, constants);
189195
+ this.iterable = optimizeExpr(this.iterable, names, constants2);
187296
189196
  return this;
187297
189197
  }
187298
189198
  get names() {
@@ -187333,11 +189233,11 @@ var require_codegen = /* @__PURE__ */ __commonJSMin((exports) => {
187333
189233
  (_b = this.finally) === null || _b === undefined || _b.optimizeNodes();
187334
189234
  return this;
187335
189235
  }
187336
- optimizeNames(names, constants) {
189236
+ optimizeNames(names, constants2) {
187337
189237
  var _a3, _b;
187338
- super.optimizeNames(names, constants);
187339
- (_a3 = this.catch) === null || _a3 === undefined || _a3.optimizeNames(names, constants);
187340
- (_b = this.finally) === null || _b === undefined || _b.optimizeNames(names, constants);
189238
+ super.optimizeNames(names, constants2);
189239
+ (_a3 = this.catch) === null || _a3 === undefined || _a3.optimizeNames(names, constants2);
189240
+ (_b = this.finally) === null || _b === undefined || _b.optimizeNames(names, constants2);
187341
189241
  return this;
187342
189242
  }
187343
189243
  get names() {
@@ -187606,7 +189506,7 @@ var require_codegen = /* @__PURE__ */ __commonJSMin((exports) => {
187606
189506
  function addExprNames(names, from) {
187607
189507
  return from instanceof code_1._CodeOrName ? addNames(names, from.names) : names;
187608
189508
  }
187609
- function optimizeExpr(expr, names, constants) {
189509
+ function optimizeExpr(expr, names, constants2) {
187610
189510
  if (expr instanceof code_1.Name)
187611
189511
  return replaceName(expr);
187612
189512
  if (!canOptimize(expr))
@@ -187621,14 +189521,14 @@ var require_codegen = /* @__PURE__ */ __commonJSMin((exports) => {
187621
189521
  return items;
187622
189522
  }, []));
187623
189523
  function replaceName(n) {
187624
- const c = constants[n.str];
189524
+ const c = constants2[n.str];
187625
189525
  if (c === undefined || names[n.str] !== 1)
187626
189526
  return n;
187627
189527
  delete names[n.str];
187628
189528
  return c;
187629
189529
  }
187630
189530
  function canOptimize(e) {
187631
- return e instanceof code_1._Code && e._items.some((c) => c instanceof code_1.Name && names[c.str] === 1 && constants[c.str] !== undefined);
189531
+ return e instanceof code_1._Code && e._items.some((c) => c instanceof code_1.Name && names[c.str] === 1 && constants2[c.str] !== undefined);
187632
189532
  }
187633
189533
  }
187634
189534
  function subtractNames(names, from) {
@@ -188765,8 +190665,8 @@ var require_resolve = /* @__PURE__ */ __commonJSMin((exports) => {
188765
190665
  }
188766
190666
  return count;
188767
190667
  }
188768
- function getFullPath(resolver, id = "", normalize) {
188769
- if (normalize !== false)
190668
+ function getFullPath(resolver, id = "", normalize2) {
190669
+ if (normalize2 !== false)
188770
190670
  id = normalizeId(id);
188771
190671
  return _getFullPath(resolver, resolver.parse(id));
188772
190672
  }
@@ -189481,7 +191381,7 @@ var require_compile = /* @__PURE__ */ __commonJSMin((exports) => {
189481
191381
  const schOrFunc = root.refs[ref];
189482
191382
  if (schOrFunc)
189483
191383
  return schOrFunc;
189484
- let _sch = resolve5.call(this, root, ref);
191384
+ let _sch = resolve9.call(this, root, ref);
189485
191385
  if (_sch === undefined) {
189486
191386
  const schema = (_a3 = root.localRefs) === null || _a3 === undefined ? undefined : _a3[ref];
189487
191387
  const { schemaId } = this.opts;
@@ -189512,7 +191412,7 @@ var require_compile = /* @__PURE__ */ __commonJSMin((exports) => {
189512
191412
  function sameSchemaEnv(s1, s2) {
189513
191413
  return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
189514
191414
  }
189515
- function resolve5(root, ref) {
191415
+ function resolve9(root, ref) {
189516
191416
  let sch;
189517
191417
  while (typeof (sch = this.refs[ref]) == "string")
189518
191418
  ref = sch;
@@ -190011,60 +191911,60 @@ var require_schemes = /* @__PURE__ */ __commonJSMin((exports, module) => {
190011
191911
  var require_fast_uri = /* @__PURE__ */ __commonJSMin((exports, module) => {
190012
191912
  const { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizeComponentEncoding, isIPv4, nonSimpleDomain } = require_utils();
190013
191913
  const { SCHEMES, getSchemeHandler } = require_schemes();
190014
- function normalize(uri, options) {
191914
+ function normalize2(uri, options) {
190015
191915
  if (typeof uri === "string")
190016
191916
  uri = serialize(parse6(uri, options), options);
190017
191917
  else if (typeof uri === "object")
190018
191918
  uri = parse6(serialize(uri, options), options);
190019
191919
  return uri;
190020
191920
  }
190021
- function resolve5(baseURI, relativeURI, options) {
191921
+ function resolve9(baseURI, relativeURI, options) {
190022
191922
  const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
190023
191923
  const resolved = resolveComponent(parse6(baseURI, schemelessOptions), parse6(relativeURI, schemelessOptions), schemelessOptions, true);
190024
191924
  schemelessOptions.skipEscape = true;
190025
191925
  return serialize(resolved, schemelessOptions);
190026
191926
  }
190027
- function resolveComponent(base, relative, options, skipNormalization) {
191927
+ function resolveComponent(base, relative3, options, skipNormalization) {
190028
191928
  const target = {};
190029
191929
  if (!skipNormalization) {
190030
191930
  base = parse6(serialize(base, options), options);
190031
- relative = parse6(serialize(relative, options), options);
191931
+ relative3 = parse6(serialize(relative3, options), options);
190032
191932
  }
190033
191933
  options = options || {};
190034
- if (!options.tolerant && relative.scheme) {
190035
- target.scheme = relative.scheme;
190036
- target.userinfo = relative.userinfo;
190037
- target.host = relative.host;
190038
- target.port = relative.port;
190039
- target.path = removeDotSegments(relative.path || "");
190040
- target.query = relative.query;
191934
+ if (!options.tolerant && relative3.scheme) {
191935
+ target.scheme = relative3.scheme;
191936
+ target.userinfo = relative3.userinfo;
191937
+ target.host = relative3.host;
191938
+ target.port = relative3.port;
191939
+ target.path = removeDotSegments(relative3.path || "");
191940
+ target.query = relative3.query;
190041
191941
  } else {
190042
- if (relative.userinfo !== undefined || relative.host !== undefined || relative.port !== undefined) {
190043
- target.userinfo = relative.userinfo;
190044
- target.host = relative.host;
190045
- target.port = relative.port;
190046
- target.path = removeDotSegments(relative.path || "");
190047
- target.query = relative.query;
191942
+ if (relative3.userinfo !== undefined || relative3.host !== undefined || relative3.port !== undefined) {
191943
+ target.userinfo = relative3.userinfo;
191944
+ target.host = relative3.host;
191945
+ target.port = relative3.port;
191946
+ target.path = removeDotSegments(relative3.path || "");
191947
+ target.query = relative3.query;
190048
191948
  } else {
190049
- if (!relative.path) {
191949
+ if (!relative3.path) {
190050
191950
  target.path = base.path;
190051
- if (relative.query !== undefined)
190052
- target.query = relative.query;
191951
+ if (relative3.query !== undefined)
191952
+ target.query = relative3.query;
190053
191953
  else
190054
191954
  target.query = base.query;
190055
191955
  } else {
190056
- if (relative.path[0] === "/")
190057
- target.path = removeDotSegments(relative.path);
191956
+ if (relative3.path[0] === "/")
191957
+ target.path = removeDotSegments(relative3.path);
190058
191958
  else {
190059
191959
  if ((base.userinfo !== undefined || base.host !== undefined || base.port !== undefined) && !base.path)
190060
- target.path = "/" + relative.path;
191960
+ target.path = "/" + relative3.path;
190061
191961
  else if (!base.path)
190062
- target.path = relative.path;
191962
+ target.path = relative3.path;
190063
191963
  else
190064
- target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) + relative.path;
191964
+ target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) + relative3.path;
190065
191965
  target.path = removeDotSegments(target.path);
190066
191966
  }
190067
- target.query = relative.query;
191967
+ target.query = relative3.query;
190068
191968
  }
190069
191969
  target.userinfo = base.userinfo;
190070
191970
  target.host = base.host;
@@ -190072,7 +191972,7 @@ var require_fast_uri = /* @__PURE__ */ __commonJSMin((exports, module) => {
190072
191972
  }
190073
191973
  target.scheme = base.scheme;
190074
191974
  }
190075
- target.fragment = relative.fragment;
191975
+ target.fragment = relative3.fragment;
190076
191976
  return target;
190077
191977
  }
190078
191978
  function equal(uriA, uriB, options) {
@@ -190228,8 +192128,8 @@ var require_fast_uri = /* @__PURE__ */ __commonJSMin((exports, module) => {
190228
192128
  }
190229
192129
  const fastUri = {
190230
192130
  SCHEMES,
190231
- normalize,
190232
- resolve: resolve5,
192131
+ normalize: normalize2,
192132
+ resolve: resolve9,
190233
192133
  resolveComponent,
190234
192134
  equal,
190235
192135
  serialize,
@@ -197371,14 +199271,14 @@ function inputRequiredRoundsExceededMessage(method, maxRounds) {
197371
199271
  return `Multi-round-trip request '${method}' still required input after ${maxRounds} rounds (inputRequired.maxRounds)`;
197372
199272
  }
197373
199273
  function sleep(ms, signal) {
197374
- return new Promise((resolve5, reject) => {
199274
+ return new Promise((resolve9, reject) => {
197375
199275
  if (signal?.aborted) {
197376
199276
  reject(signal.reason instanceof SdkError ? signal.reason : new SdkError(SdkErrorCode.RequestTimeout, String(signal.reason)));
197377
199277
  return;
197378
199278
  }
197379
199279
  const timer = setTimeout(() => {
197380
199280
  signal?.removeEventListener("abort", onAbort);
197381
- resolve5();
199281
+ resolve9();
197382
199282
  }, ms);
197383
199283
  const onAbort = () => {
197384
199284
  clearTimeout(timer);
@@ -198140,7 +200040,7 @@ var Protocol = class {
198140
200040
  const flowStartedAt = Date.now();
198141
200041
  let onAbort;
198142
200042
  let cleanupMessageId;
198143
- return new Promise((resolve5, reject) => {
200043
+ return new Promise((resolve9, reject) => {
198144
200044
  const earlyReject = (error51) => {
198145
200045
  reject(error51);
198146
200046
  };
@@ -198216,7 +200116,7 @@ var Protocol = class {
198216
200116
  return reject(decoded.error);
198217
200117
  if (decoded.kind === "input_required") {
198218
200118
  if (options?.allowInputRequired === true)
198219
- return resolve5(manualInputRequiredValue(decoded));
200119
+ return resolve9(manualInputRequiredValue(decoded));
198220
200120
  const flow = {
198221
200121
  codec: codec2,
198222
200122
  request,
@@ -198228,12 +200128,12 @@ var Protocol = class {
198228
200128
  params
198229
200129
  }, resultSchema, legOptions)
198230
200130
  };
198231
- return resolve5(this._resolveNonCompleteResult(decoded, flow));
200131
+ return resolve9(this._resolveNonCompleteResult(decoded, flow));
198232
200132
  }
198233
200133
  const result = decoded.result;
198234
200134
  validateStandardSchema(resultSchema, result).then((parseResult) => {
198235
200135
  if (parseResult.success)
198236
- resolve5(parseResult.data);
200136
+ resolve9(parseResult.data);
198237
200137
  else
198238
200138
  reject(new SdkError(SdkErrorCode.InvalidResult, `Invalid result for ${request.method}: ${parseResult.error}`));
198239
200139
  }, reject);
@@ -199777,7 +201677,7 @@ var StdioServerTransport = class {
199777
201677
  send(message) {
199778
201678
  if (this._closed)
199779
201679
  return Promise.reject(/* @__PURE__ */ new Error("StdioServerTransport is closed"));
199780
- return new Promise((resolve5, reject) => {
201680
+ return new Promise((resolve9, reject) => {
199781
201681
  const json2 = serializeMessage(message);
199782
201682
  let settled = false;
199783
201683
  const onError = (error51) => {
@@ -199794,7 +201694,7 @@ var StdioServerTransport = class {
199794
201694
  settled = true;
199795
201695
  this._stdout.off("error", onError);
199796
201696
  this._stdout.off("drain", onDrain);
199797
- resolve5();
201697
+ resolve9();
199798
201698
  };
199799
201699
  this._stdout.once("error", onError);
199800
201700
  if (this._stdout.write(json2)) {
@@ -199802,7 +201702,7 @@ var StdioServerTransport = class {
199802
201702
  return;
199803
201703
  settled = true;
199804
201704
  this._stdout.off("error", onError);
199805
- resolve5();
201705
+ resolve9();
199806
201706
  } else if (!settled)
199807
201707
  this._stdout.once("drain", onDrain);
199808
201708
  });
@@ -199810,12 +201710,96 @@ var StdioServerTransport = class {
199810
201710
  };
199811
201711
 
199812
201712
  // src/core/runReview.ts
199813
- import { resolve as resolve6 } from "node:path";
201713
+ import { resolve as resolve10 } from "node:path";
201714
+
201715
+ // src/config/configOverrides.ts
201716
+ var NUMBER_VALUE = /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?$/i;
201717
+ function applyConfigOverrides(config2, assignments) {
201718
+ if (assignments.length === 0)
201719
+ return config2;
201720
+ const overrides = assignments.map(parseConfigOverride);
201721
+ const baseConfig = config2;
201722
+ const overridden = structuredClone(config2);
201723
+ for (const override of overrides) {
201724
+ writePath2(overridden, override.path, parseConfigOverrideValue(override.value, readPath2(baseConfig, override.path)));
201725
+ }
201726
+ const parsed = kyosoConfigSchema.safeParse(overridden);
201727
+ if (parsed.success)
201728
+ return parsed.data;
201729
+ const issue2 = parsed.error.issues[0];
201730
+ const issuePath = issue2?.path.map(String).join(".") ?? "config";
201731
+ const assignment = findAssignmentForPath(overrides, issuePath) ?? assignments.at(-1) ?? "";
201732
+ throw new Error(`Invalid --set value ${JSON.stringify(assignment)}: ${issuePath}: ${issue2?.message ?? "config validation failed"}.`);
201733
+ }
201734
+ function findAssignmentForPath(overrides, path) {
201735
+ for (let index = overrides.length - 1;index >= 0; index -= 1) {
201736
+ const override = overrides[index];
201737
+ if (override?.path.join(".") === path)
201738
+ return override.assignment;
201739
+ }
201740
+ return;
201741
+ }
201742
+ function parseConfigOverride(assignment) {
201743
+ const separator = assignment.indexOf("=");
201744
+ if (separator <= 0) {
201745
+ throw new Error(`Invalid --set value ${JSON.stringify(assignment)}. Expected key=value.`);
201746
+ }
201747
+ const key = assignment.slice(0, separator);
201748
+ const path = key.split(".");
201749
+ if (!isAllowedConfigOverridePath(path)) {
201750
+ throw new Error(`Unknown --set key ${JSON.stringify(key)}.`);
201751
+ }
201752
+ return {
201753
+ assignment,
201754
+ path,
201755
+ value: assignment.slice(separator + 1)
201756
+ };
201757
+ }
201758
+ function parseConfigOverrideValue(value, currentValue) {
201759
+ if (typeof currentValue === "boolean") {
201760
+ if (value === "true")
201761
+ return true;
201762
+ if (value === "false")
201763
+ return false;
201764
+ return value;
201765
+ }
201766
+ if (typeof currentValue === "number" && NUMBER_VALUE.test(value)) {
201767
+ const parsed = Number(value);
201768
+ if (Number.isFinite(parsed))
201769
+ return parsed;
201770
+ }
201771
+ return value;
201772
+ }
201773
+ function readPath2(target, path) {
201774
+ let current = target;
201775
+ for (const key of path) {
201776
+ if (!isRecord8(current))
201777
+ return;
201778
+ current = current[key];
201779
+ }
201780
+ return current;
201781
+ }
201782
+ function writePath2(target, path, value) {
201783
+ let current = target;
201784
+ for (const key of path.slice(0, -1)) {
201785
+ const child = current[key];
201786
+ if (!isRecord8(child)) {
201787
+ throw new Error(`Cannot apply --set key ${JSON.stringify(path.join("."))}.`);
201788
+ }
201789
+ current = child;
201790
+ }
201791
+ const leaf = path.at(-1);
201792
+ if (leaf)
201793
+ current[leaf] = value;
201794
+ }
201795
+ function isRecord8(value) {
201796
+ return typeof value === "object" && value !== null && !Array.isArray(value);
201797
+ }
199814
201798
 
199815
201799
  // src/acp/AcpAgentProcess.ts
199816
201800
  import { spawn } from "node:child_process";
199817
- import { readFile as readFile7, realpath } from "node:fs/promises";
199818
- import { isAbsolute, relative, resolve as resolve5 } from "node:path";
201801
+ import { readFile as readFile8, realpath as realpath3 } from "node:fs/promises";
201802
+ import { isAbsolute as isAbsolute4, relative as relative3, resolve as resolve9 } from "node:path";
199819
201803
  import { Readable, Writable } from "node:stream";
199820
201804
 
199821
201805
  // node_modules/@agentclientprotocol/sdk/dist/schema/index.js
@@ -199887,6 +201871,48 @@ function requiredDefaultOnError(schema, fallback) {
199887
201871
  return exports_external.NEVER;
199888
201872
  });
199889
201873
  }
201874
+ function stringTag(value, key) {
201875
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
201876
+ return;
201877
+ }
201878
+ const tag = value[key];
201879
+ return typeof tag === "string" ? tag : undefined;
201880
+ }
201881
+ function excludeKnownTags(schema, key, knownTags) {
201882
+ return schema.superRefine((value, context) => {
201883
+ const tag = stringTag(value, key);
201884
+ if (tag !== undefined && knownTags.includes(tag)) {
201885
+ context.addIssue({
201886
+ code: "custom",
201887
+ path: [key],
201888
+ message: `${key} ${JSON.stringify(tag)} is reserved by a known variant, ` + `but the value does not match that variant's schema`
201889
+ });
201890
+ }
201891
+ });
201892
+ }
201893
+ function preserveCustomPayload(schema, key, knownTags) {
201894
+ return exports_external.unknown().transform((value, context) => {
201895
+ const result = schema.safeParse(value);
201896
+ if (!result.success) {
201897
+ for (const issue2 of result.error.issues) {
201898
+ context.addIssue({ ...issue2, input: value });
201899
+ }
201900
+ return exports_external.NEVER;
201901
+ }
201902
+ const output2 = result.data;
201903
+ const tag = stringTag(value, key);
201904
+ if (tag !== undefined && !knownTags.includes(tag)) {
201905
+ const raw = value;
201906
+ for (const [property, rawValue] of Object.entries(raw)) {
201907
+ if (property === "__proto__")
201908
+ continue;
201909
+ if (!Object.hasOwn(output2, property))
201910
+ output2[property] = rawValue;
201911
+ }
201912
+ }
201913
+ return output2;
201914
+ });
201915
+ }
199890
201916
  function vecSkipError(itemSchema) {
199891
201917
  return exports_external.array(itemSchema.catch(skippedItem)).transform((items) => items.filter((item) => item !== skippedItem));
199892
201918
  }
@@ -200310,15 +202336,15 @@ var zTitledMultiSelectItems = object({
200310
202336
  return;
200311
202337
  })
200312
202338
  });
200313
- var zMultiSelectItems = union([
202339
+ var zMultiSelectItems = preserveCustomPayload(union([
200314
202340
  zStringMultiSelectItems.and(object({
200315
202341
  type: literal("string")
200316
202342
  })),
200317
- object({
202343
+ excludeKnownTags(object({
200318
202344
  type: string2()
200319
- }),
202345
+ }), "type", ["string"]),
200320
202346
  zTitledMultiSelectItems
200321
- ]);
202347
+ ]), "type", ["string"]);
200322
202348
  var zMultiSelectPropertySchema = object({
200323
202349
  title: defaultOnError(string2().nullish(), () => {
200324
202350
  return;
@@ -200336,7 +202362,7 @@ var zMultiSelectPropertySchema = object({
200336
202362
  return;
200337
202363
  })
200338
202364
  });
200339
- var zElicitationPropertySchema = union([
202365
+ var zElicitationPropertySchema = preserveCustomPayload(union([
200340
202366
  zStringPropertySchema.and(object({
200341
202367
  type: literal("string")
200342
202368
  })),
@@ -200352,10 +202378,10 @@ var zElicitationPropertySchema = union([
200352
202378
  zMultiSelectPropertySchema.and(object({
200353
202379
  type: literal("array")
200354
202380
  })),
200355
- object({
202381
+ excludeKnownTags(object({
200356
202382
  type: string2()
200357
- })
200358
- ]);
202383
+ }), "type", ["array", "boolean", "integer", "number", "string"])
202384
+ ]), "type", ["array", "boolean", "integer", "number", "string"]);
200359
202385
  var zElicitationSchema = object({
200360
202386
  type: defaultOnError(zElicitationSchemaType.optional().default("object"), () => "object"),
200361
202387
  title: defaultOnError(string2().nullish(), () => {
@@ -200378,22 +202404,22 @@ var zElicitationUrlMode = intersection(union([zElicitationSessionScope, zElicita
200378
202404
  elicitationId: zElicitationId,
200379
202405
  url: url()
200380
202406
  }));
200381
- var zCreateElicitationRequest = intersection(union([
202407
+ var zCreateElicitationRequest = preserveCustomPayload(intersection(union([
200382
202408
  zElicitationFormMode.and(object({
200383
202409
  mode: literal("form")
200384
202410
  })),
200385
202411
  zElicitationUrlMode.and(object({
200386
202412
  mode: literal("url")
200387
202413
  })),
200388
- intersection(union([zElicitationSessionScope, zElicitationRequestScope]), object({
202414
+ excludeKnownTags(intersection(union([zElicitationSessionScope, zElicitationRequestScope]), object({
200389
202415
  mode: string2()
200390
- }))
202416
+ })), "mode", ["form", "url"])
200391
202417
  ]), object({
200392
202418
  message: string2(),
200393
202419
  _meta: defaultOnError(record(string2(), unknown()).nullish(), () => {
200394
202420
  return;
200395
202421
  })
200396
- }));
202422
+ })), "mode", ["form", "url"]);
200397
202423
  var zMcpServerAcpId = string2();
200398
202424
  var zConnectMcpRequest = object({
200399
202425
  serverId: zMcpServerAcpId,
@@ -201993,7 +204019,7 @@ var zElicitationContentValue = union([
201993
204019
  var zElicitationAcceptAction = object({
201994
204020
  content: record(string2(), zElicitationContentValue).nullish()
201995
204021
  });
201996
- var zCreateElicitationResponse = intersection(union([
204022
+ var zCreateElicitationResponse = preserveCustomPayload(intersection(union([
201997
204023
  zElicitationAcceptAction.and(object({
201998
204024
  action: literal("accept")
201999
204025
  })),
@@ -202003,14 +204029,14 @@ var zCreateElicitationResponse = intersection(union([
202003
204029
  object({
202004
204030
  action: literal("cancel")
202005
204031
  }),
202006
- object({
204032
+ excludeKnownTags(object({
202007
204033
  action: string2()
202008
- })
204034
+ }), "action", ["accept", "cancel", "decline"])
202009
204035
  ]), object({
202010
204036
  _meta: defaultOnError(record(string2(), unknown()).nullish(), () => {
202011
204037
  return;
202012
204038
  })
202013
- }));
204039
+ })), "action", ["accept", "cancel", "decline"]);
202014
204040
  var zConnectMcpResponse = object({
202015
204041
  connectionId: zMcpConnectionId,
202016
204042
  _meta: defaultOnError(record(string2(), unknown()).nullish(), () => {
@@ -202146,16 +204172,34 @@ var zCancelRequestNotification = object({
202146
204172
  return;
202147
204173
  })
202148
204174
  });
204175
+
204176
+ // node_modules/@agentclientprotocol/sdk/dist/schema/guards.gen.js
204177
+ var zGuardCreateElicitationRequestForm = zElicitationFormMode.and(object({ mode: literal("form") })).and(object({ message: string2() }));
204178
+ var zGuardCreateElicitationRequestUrl = zElicitationUrlMode.and(object({ mode: literal("url") })).and(object({ message: string2() }));
204179
+ var zGuardCreateElicitationRequestCustom = union([zElicitationSessionScope, zElicitationRequestScope]).and(object({ message: string2() }));
204180
+ var zGuardElicitationPropertySchemaString = zStringPropertySchema.and(object({ type: literal("string") }));
204181
+ var zGuardElicitationPropertySchemaNumber = zNumberPropertySchema.and(object({ type: literal("number") }));
204182
+ var zGuardElicitationPropertySchemaInteger = zIntegerPropertySchema.and(object({ type: literal("integer") }));
204183
+ var zGuardElicitationPropertySchemaBoolean = zBooleanPropertySchema.and(object({ type: literal("boolean") }));
204184
+ var zGuardElicitationPropertySchemaArray = zMultiSelectPropertySchema.and(object({ type: literal("array") }));
204185
+ var zGuardMultiSelectItemsString = zStringMultiSelectItems.and(object({ type: literal("string") }));
204186
+ var zGuardCreateElicitationResponseAccept = zElicitationAcceptAction.and(object({ action: literal("accept") }));
204187
+ var zGuardCreateElicitationResponseDecline = object({
204188
+ action: literal("decline")
204189
+ });
204190
+ var zGuardCreateElicitationResponseCancel = object({
204191
+ action: literal("cancel")
204192
+ });
202149
204193
  // node_modules/@agentclientprotocol/sdk/dist/jsonrpc.js
202150
204194
  var CANCEL_REQUEST_METHOD = "$/cancel_request";
202151
- function isRecord6(value) {
204195
+ function isRecord9(value) {
202152
204196
  return typeof value === "object" && value !== null;
202153
204197
  }
202154
204198
  function isJsonRpcId(value) {
202155
204199
  return value === null || typeof value === "string" || typeof value === "number" && Number.isFinite(value);
202156
204200
  }
202157
204201
  function cancelRequestId(params) {
202158
- if (!isRecord6(params) || !isJsonRpcId(params["requestId"])) {
204202
+ if (!isRecord9(params) || !isJsonRpcId(params["requestId"])) {
202159
204203
  return;
202160
204204
  }
202161
204205
  return params["requestId"];
@@ -202379,12 +204423,12 @@ class Connection {
202379
204423
  }
202380
204424
  const id = this.nextRequestId++;
202381
204425
  let cancel = () => {};
202382
- const responsePromise = new Promise((resolve5, reject) => {
204426
+ const responsePromise = new Promise((resolve9, reject) => {
202383
204427
  const pendingResponse = {
202384
204428
  resolve: (response) => {
202385
204429
  try {
202386
204430
  const value = mapResponse ? mapResponse(response) : response;
202387
- resolve5(value);
204431
+ resolve9(value);
202388
204432
  } catch (error51) {
202389
204433
  reject(error51);
202390
204434
  }
@@ -202449,8 +204493,8 @@ class Connection {
202449
204493
  initialize(stream, handlers) {
202450
204494
  this.stream = stream;
202451
204495
  this.staticHandlers = handlers;
202452
- this.closedPromise = new Promise((resolve5) => {
202453
- this.abortController.signal.addEventListener("abort", () => resolve5());
204496
+ this.closedPromise = new Promise((resolve9) => {
204497
+ this.abortController.signal.addEventListener("abort", () => resolve9());
202454
204498
  });
202455
204499
  this.receive();
202456
204500
  }
@@ -202502,7 +204546,7 @@ class Connection {
202502
204546
  if (this.abortController.signal.aborted) {
202503
204547
  return;
202504
204548
  }
202505
- if (!isRecord6(message)) {
204549
+ if (!isRecord9(message)) {
202506
204550
  console.error("Invalid message", { message });
202507
204551
  return;
202508
204552
  }
@@ -202595,7 +204639,7 @@ class Connection {
202595
204639
  pendingResponse.cleanup?.();
202596
204640
  if ("result" in response) {
202597
204641
  pendingResponse.resolve(response.result);
202598
- } else if ("error" in response && isRecord6(response.error)) {
204642
+ } else if ("error" in response && isRecord9(response.error)) {
202599
204643
  const { code, message, data } = response.error;
202600
204644
  pendingResponse.reject(new RequestError(code, message, data));
202601
204645
  } else {
@@ -202805,7 +204849,7 @@ function ndJsonStream(output2, input2) {
202805
204849
  if (trimmedLine) {
202806
204850
  try {
202807
204851
  const message = JSON.parse(trimmedLine);
202808
- if (isRecord6(message)) {
204852
+ if (isRecord9(message)) {
202809
204853
  controller.enqueue(message);
202810
204854
  } else {
202811
204855
  console.warn("Skipping JSON line that is not an object:", trimmedLine);
@@ -203163,8 +205207,8 @@ class AsyncQueue {
203163
205207
  if (this.failed) {
203164
205208
  return Promise.reject(this.failure);
203165
205209
  }
203166
- return new Promise((resolve5, reject) => {
203167
- this.waiters.push({ resolve: resolve5, reject });
205210
+ return new Promise((resolve9, reject) => {
205211
+ this.waiters.push({ resolve: resolve9, reject });
203168
205212
  });
203169
205213
  }
203170
205214
  }
@@ -203866,7 +205910,7 @@ function isSeverity(value) {
203866
205910
  return typeof value === "string" && severities.includes(value);
203867
205911
  }
203868
205912
  function normalizeCisaSecureByDesign(value) {
203869
- if (!isRecord7(value))
205913
+ if (!isRecord10(value))
203870
205914
  return;
203871
205915
  const normalized = {};
203872
205916
  const customerSecurityOutcomes = normalizeGateStatus(value.customerSecurityOutcomes);
@@ -203907,7 +205951,7 @@ function normalizeFindingFiles(value) {
203907
205951
  if (!Array.isArray(value))
203908
205952
  return;
203909
205953
  const files = value.flatMap((item) => {
203910
- if (!isRecord7(item) || typeof item.path !== "string" || item.path.trim().length === 0) {
205954
+ if (!isRecord10(item) || typeof item.path !== "string" || item.path.trim().length === 0) {
203911
205955
  return [];
203912
205956
  }
203913
205957
  const file2 = {
@@ -203926,7 +205970,7 @@ function normalizeFindingFiles(value) {
203926
205970
  function normalizeLineNumber(value) {
203927
205971
  return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : undefined;
203928
205972
  }
203929
- function isRecord7(value) {
205973
+ function isRecord10(value) {
203930
205974
  return typeof value === "object" && value !== null && !Array.isArray(value);
203931
205975
  }
203932
205976
 
@@ -204129,14 +206173,14 @@ function resolveEffortConfigOption(agent, effort) {
204129
206173
  return;
204130
206174
  }
204131
206175
  async function readWorkspaceFile(workspaceDir, requestedPath, line, limit) {
204132
- const workspaceRoot = await realpath(workspaceDir);
206176
+ const workspaceRoot = await realpath3(workspaceDir);
204133
206177
  const candidates = resolveReadablePaths(workspaceRoot, requestedPath);
204134
206178
  let content;
204135
206179
  for (const absolute of candidates) {
204136
206180
  const readablePath = await resolveReadableFile(workspaceRoot, absolute);
204137
206181
  if (!readablePath)
204138
206182
  continue;
204139
- content = await readFile7(readablePath, "utf8").catch(() => {
206183
+ content = await readFile8(readablePath, "utf8").catch(() => {
204140
206184
  return;
204141
206185
  });
204142
206186
  if (content !== undefined)
@@ -204152,8 +206196,8 @@ async function readWorkspaceFile(workspaceDir, requestedPath, line, limit) {
204152
206196
  `);
204153
206197
  }
204154
206198
  async function resolveReadableFile(workspaceRoot, absolute) {
204155
- const realPath = await realpath(absolute).catch((error51) => {
204156
- if (isMissingPathError4(error51))
206199
+ const realPath = await realpath3(absolute).catch((error51) => {
206200
+ if (isMissingPathError6(error51))
204157
206201
  return;
204158
206202
  throw error51;
204159
206203
  });
@@ -204163,18 +206207,18 @@ async function resolveReadableFile(workspaceRoot, absolute) {
204163
206207
  return realPath;
204164
206208
  }
204165
206209
  function resolveReadablePaths(workspaceRoot, requestedPath) {
204166
- const primary = resolve5(workspaceRoot, requestedPath);
206210
+ const primary = resolve9(workspaceRoot, requestedPath);
204167
206211
  assertWithinWorkspace(workspaceRoot, primary);
204168
- const relativePath = relative(workspaceRoot, primary).replaceAll("\\", "/");
206212
+ const relativePath = relative3(workspaceRoot, primary).replaceAll("\\", "/");
204169
206213
  if (relativePath.startsWith("context/") || relativePath.startsWith("repo/")) {
204170
206214
  return [primary];
204171
206215
  }
204172
- const repoPath = resolve5(workspaceRoot, "repo", relativePath);
206216
+ const repoPath = resolve9(workspaceRoot, "repo", relativePath);
204173
206217
  assertWithinWorkspace(workspaceRoot, repoPath);
204174
- return isAbsolute(requestedPath) ? [primary, repoPath] : [repoPath, primary];
206218
+ return isAbsolute4(requestedPath) ? [primary, repoPath] : [repoPath, primary];
204175
206219
  }
204176
206220
  function assertWithinWorkspace(workspaceRoot, absolute) {
204177
- const relativePath = relative(workspaceRoot, absolute);
206221
+ const relativePath = relative3(workspaceRoot, absolute);
204178
206222
  if (relativePath.startsWith("..") || relativePath === "" || relativePath.startsWith("/")) {
204179
206223
  throw RequestError.invalidRequest({
204180
206224
  policy: "Kyoso only allows reads from the temporary snapshot."
@@ -204191,7 +206235,7 @@ function terminateChild(child) {
204191
206235
  }, 2000);
204192
206236
  killTimer.unref();
204193
206237
  }
204194
- function isMissingPathError4(error51) {
206238
+ function isMissingPathError6(error51) {
204195
206239
  return typeof error51 === "object" && error51 !== null && "code" in error51 && error51.code === "ENOENT";
204196
206240
  }
204197
206241
  function buildAgentFailure(rawDetail, fallbackMessage) {
@@ -204887,68 +206931,78 @@ function normalizeTitle(value) {
204887
206931
  return value.toLowerCase().replace(/[^\p{L}\p{N}]+/gu, " ").trim();
204888
206932
  }
204889
206933
 
204890
- // src/audit/trace.ts
204891
- import { mkdir as mkdir4, appendFile } from "node:fs/promises";
204892
- import { dirname as dirname5, isAbsolute as isAbsolute2, join as join5 } from "node:path";
204893
-
204894
- // src/context/pathPolicy.ts
204895
- import { normalize, sep } from "node:path";
204896
-
204897
- // src/core/errors.ts
204898
- class KyosoRequestError extends Error {
204899
- code;
204900
- constructor(message, code) {
204901
- super(message);
204902
- this.code = code;
204903
- this.name = "KyosoRequestError";
206934
+ // src/audit/safeTraceFile.ts
206935
+ import { constants as constants2 } from "node:fs";
206936
+ import { lstat as lstat3, open, realpath as realpath4, stat } from "node:fs/promises";
206937
+ import { join as join8 } from "node:path";
206938
+ var AUDIT_WARNING_UNSUPPORTED_OPEN_CAPABILITY = "AUDIT_DISABLED_UNSUPPORTED_CAPABILITY: Audit trace writing requires unavailable filesystem capabilities.";
206939
+ async function openVerifiedTraceFile(options) {
206940
+ const flags = secureOpenFlags(options.openConstants);
206941
+ if (flags === undefined)
206942
+ throw new Error("secure open capability unavailable");
206943
+ if (!isSafeTraceId(options.traceId))
206944
+ throw new Error("unsafe trace id");
206945
+ const traceDirectory = await ensureTrustedDirectory({
206946
+ root: options.kyosoRoot,
206947
+ segments: [
206948
+ "workspaces",
206949
+ options.workspaceHash,
206950
+ ...options.logicalDirectory.split("/"),
206951
+ options.date
206952
+ ],
206953
+ uid: options.uid,
206954
+ workspaceRoot: options.workspaceRoot
206955
+ });
206956
+ const tracePath = join8(traceDirectory, `${options.traceId}.jsonl`);
206957
+ if (await optionalLstat3(tracePath)) {
206958
+ throw new Error("trace path already exists");
204904
206959
  }
204905
- }
204906
-
204907
- // src/context/pathPolicy.ts
204908
- function normalizeRelativePath(path) {
204909
- const normalized = normalize(path).replaceAll("\\", "/");
204910
- if (normalized === "." || normalized.startsWith("..") || normalized.includes(`${sep}..${sep}`) || normalized.startsWith("/") || /^[A-Za-z]:\//.test(normalized)) {
204911
- throw new KyosoRequestError(`Path escapes workspace root: ${path}`, "INVALID_PATH");
206960
+ await options.beforeOpen?.(tracePath);
206961
+ const handle = await open(tracePath, flags, 384);
206962
+ try {
206963
+ const [handleStat, pathStat, realTracePath] = await Promise.all([
206964
+ handle.stat({ bigint: true }),
206965
+ stat(tracePath, { bigint: true }),
206966
+ realpath4(tracePath)
206967
+ ]);
206968
+ if (!handleStat.isFile() || !pathStat.isFile() || handleStat.dev !== pathStat.dev || handleStat.ino !== pathStat.ino || !isPathWithin(realTracePath, options.kyosoRoot)) {
206969
+ throw new Error("trace file identity could not be verified");
206970
+ }
206971
+ return { handle, tracePath };
206972
+ } catch (error51) {
206973
+ try {
206974
+ await handle.close();
206975
+ } catch {}
206976
+ throw error51;
204912
206977
  }
204913
- return normalized;
204914
206978
  }
204915
- function isDeniedPath(path, denyPatterns) {
204916
- return matchesPathPattern(path, denyPatterns, "deny");
206979
+ function secureOpenFlags(provided) {
206980
+ const openConstants = provided ?? constants2;
206981
+ const required2 = [
206982
+ openConstants.O_CREAT,
206983
+ openConstants.O_EXCL,
206984
+ openConstants.O_APPEND,
206985
+ openConstants.O_WRONLY,
206986
+ openConstants.O_NOFOLLOW,
206987
+ openConstants.O_NONBLOCK
206988
+ ];
206989
+ if (required2.some((flag) => typeof flag !== "number" || flag <= 0)) {
206990
+ return;
206991
+ }
206992
+ return required2.reduce((combined, flag) => combined | flag, 0);
204917
206993
  }
204918
- function isAllowedPath(path, allowPatterns) {
204919
- if (allowPatterns.length === 0)
204920
- return true;
204921
- return matchesPathPattern(path, allowPatterns, "allow");
206994
+ function isSafeTraceId(traceId) {
206995
+ return traceId.length > 0 && traceId !== "." && traceId !== ".." && !traceId.includes("/") && !traceId.includes("\\");
204922
206996
  }
204923
- function matchesPathPattern(path, patterns, mode) {
204924
- const normalized = normalizeRelativePath(path);
204925
- const segments = normalized.split("/");
204926
- return patterns.some((pattern) => {
204927
- const normalizedPattern = normalizeRelativePath(pattern);
204928
- if (normalizedPattern.endsWith(".*") && !normalizedPattern.includes("/")) {
204929
- const base = normalizedPattern.slice(0, -2);
204930
- if (mode === "allow") {
204931
- const firstSegment = segments[0] ?? "";
204932
- return firstSegment === base || firstSegment.startsWith(`${base}.`);
204933
- }
204934
- return segments.some((segment) => segment === base || segment.startsWith(`${base}.`));
204935
- }
204936
- if (normalizedPattern.includes("*")) {
204937
- const escaped = normalizedPattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replaceAll("*", ".*");
204938
- const regex = mode === "allow" ? new RegExp(`^${escaped}($|/)`) : new RegExp(`(^|/)${escaped}($|/)`);
204939
- return regex.test(normalized);
204940
- }
204941
- if (!normalizedPattern.includes("/")) {
204942
- if (mode === "allow") {
204943
- return normalized === normalizedPattern || normalized.startsWith(`${normalizedPattern}/`);
204944
- }
204945
- return segments.includes(normalizedPattern);
204946
- }
204947
- if (mode === "allow") {
204948
- return normalized === normalizedPattern || normalized.startsWith(`${normalizedPattern}/`);
206997
+ async function optionalLstat3(path) {
206998
+ try {
206999
+ return await lstat3(path);
207000
+ } catch (error51) {
207001
+ if (typeof error51 === "object" && error51 !== null && "code" in error51 && error51.code === "ENOENT") {
207002
+ return;
204949
207003
  }
204950
- return normalized === normalizedPattern || normalized.startsWith(`${normalizedPattern}/`) || normalized.endsWith(`/${normalizedPattern}`) || normalized.includes(`/${normalizedPattern}/`);
204951
- });
207004
+ throw error51;
207005
+ }
204952
207006
  }
204953
207007
 
204954
207008
  // src/audit/sanitize.ts
@@ -204971,46 +207025,149 @@ function sanitizeForAudit(value, options = {}) {
204971
207025
  }
204972
207026
 
204973
207027
  // src/audit/trace.ts
207028
+ var AUDIT_WARNING_WRITE_FAILED = "AUDIT_WRITE_FAILED: Audit trace writing failed; no further audit events will be written.";
207029
+ var AUDIT_WARNING_FINALIZE_FAILED = "AUDIT_FINALIZE_FAILED: Audit trace close failed.";
207030
+ var AUDIT_WARNING_WRITE_AFTER_FINALIZE = "AUDIT_WRITE_AFTER_FINALIZE: Audit trace is already finalized.";
204974
207031
  function createTraceWriter(options) {
204975
207032
  const warnings = [];
204976
- if (!options.enabled) {
204977
- return {
204978
- warnings,
204979
- async write() {
204980
- return;
204981
- }
204982
- };
204983
- }
207033
+ const warningSet = new Set;
204984
207034
  const date5 = new Date().toISOString().slice(0, 10);
204985
- const directory = validateAuditDirectory(options.directory, warnings);
204986
- const tracePath = join5(options.cwd, directory, date5, `${options.traceId}.jsonl`);
207035
+ let tracePath;
207036
+ let handle;
207037
+ let disabled = !options.enabled;
207038
+ let finalizing = false;
207039
+ let finalized = false;
207040
+ let queue = Promise.resolve();
207041
+ let finalizePromise;
207042
+ const addWarning = (warning) => {
207043
+ if (warningSet.has(warning))
207044
+ return;
207045
+ warningSet.add(warning);
207046
+ warnings.push(warning);
207047
+ };
207048
+ const closeHandle = async () => {
207049
+ const current = handle;
207050
+ handle = undefined;
207051
+ if (!current)
207052
+ return;
207053
+ try {
207054
+ await (options.closeHandle ?? defaultCloseHandle)(current);
207055
+ } catch {
207056
+ addWarning(AUDIT_WARNING_FINALIZE_FAILED);
207057
+ }
207058
+ };
207059
+ const disableAfterWriteFailure = async () => {
207060
+ disabled = true;
207061
+ addWarning(AUDIT_WARNING_WRITE_FAILED);
207062
+ await closeHandle();
207063
+ };
207064
+ const openIfNeeded = async () => {
207065
+ if (handle || disabled)
207066
+ return;
207067
+ if (secureOpenFlags(options.openConstants) === undefined) {
207068
+ disabled = true;
207069
+ addWarning(AUDIT_WARNING_UNSUPPORTED_OPEN_CAPABILITY);
207070
+ return;
207071
+ }
207072
+ const stateRoot = await resolveAuditStateRoot({
207073
+ cwd: options.cwd,
207074
+ directory: options.directory,
207075
+ env: options.env,
207076
+ platform: options.platform,
207077
+ getuid: options.getuid
207078
+ });
207079
+ for (const warning of stateRoot.warnings)
207080
+ addWarning(warning);
207081
+ if (!isResolvedAuditStateRoot(stateRoot)) {
207082
+ disabled = true;
207083
+ return;
207084
+ }
207085
+ try {
207086
+ const opened = await openVerifiedTraceFile({
207087
+ kyosoRoot: stateRoot.kyosoRoot,
207088
+ workspaceHash: stateRoot.workspaceHash,
207089
+ logicalDirectory: stateRoot.logicalDirectory,
207090
+ date: date5,
207091
+ traceId: options.traceId,
207092
+ uid: stateRoot.uid,
207093
+ workspaceRoot: stateRoot.workspaceRoot,
207094
+ openConstants: options.openConstants,
207095
+ beforeOpen: options.beforeOpen
207096
+ });
207097
+ handle = opened.handle;
207098
+ tracePath = opened.tracePath;
207099
+ } catch {
207100
+ disabled = true;
207101
+ addWarning(AUDIT_WARNING_WRITE_FAILED);
207102
+ }
207103
+ };
207104
+ const writeOne = async (event) => {
207105
+ if (disabled)
207106
+ return;
207107
+ try {
207108
+ await openIfNeeded();
207109
+ if (disabled || !handle)
207110
+ return;
207111
+ const line = Buffer.from(`${JSON.stringify(sanitizeForAudit(event, {
207112
+ includeRawAgentOutput: options.includeRawAgentOutput
207113
+ }))}
207114
+ `, "utf8");
207115
+ await writeFully(handle, line, options.writeChunk);
207116
+ } catch {
207117
+ await disableAfterWriteFailure();
207118
+ }
207119
+ };
204987
207120
  return {
204988
- tracePath,
207121
+ get tracePath() {
207122
+ return tracePath;
207123
+ },
204989
207124
  warnings,
204990
- async write(event) {
204991
- try {
204992
- await mkdir4(dirname5(tracePath), { recursive: true });
204993
- await appendFile(tracePath, `${JSON.stringify(sanitizeForAudit(event, {
204994
- includeRawAgentOutput: options.includeRawAgentOutput
204995
- }))}
204996
- `, "utf8");
204997
- } catch (error51) {
204998
- warnings.push(`Audit write failed: ${error51 instanceof Error ? error51.message : String(error51)}`);
204999
- }
207125
+ write(event) {
207126
+ if (finalizing || finalized) {
207127
+ addWarning(AUDIT_WARNING_WRITE_AFTER_FINALIZE);
207128
+ return Promise.resolve();
207129
+ }
207130
+ const task = queue.then(() => writeOne(event));
207131
+ queue = task.catch(async () => {
207132
+ await disableAfterWriteFailure();
207133
+ });
207134
+ return task.catch(() => {
207135
+ return;
207136
+ });
207137
+ },
207138
+ finalize() {
207139
+ if (finalizePromise)
207140
+ return finalizePromise;
207141
+ finalizing = true;
207142
+ finalizePromise = queue.then(async () => {
207143
+ await closeHandle();
207144
+ finalized = true;
207145
+ }).catch(() => {
207146
+ disabled = true;
207147
+ addWarning(AUDIT_WARNING_FINALIZE_FAILED);
207148
+ finalized = true;
207149
+ });
207150
+ return finalizePromise;
205000
207151
  }
205001
207152
  };
205002
207153
  }
205003
- function validateAuditDirectory(directory, warnings) {
205004
- try {
205005
- if (isAbsolute2(directory) || directory.split(/[\\/]+/).includes("..")) {
205006
- throw new Error("unsafe path");
207154
+ async function writeFully(handle, buffer, writeChunk) {
207155
+ let offset = 0;
207156
+ while (offset < buffer.byteLength) {
207157
+ const bytesWritten = await (writeChunk ?? defaultWriteChunk)(handle, buffer, offset);
207158
+ if (!Number.isInteger(bytesWritten) || bytesWritten <= 0) {
207159
+ throw new Error("partial audit write could not advance");
205007
207160
  }
205008
- return normalizeRelativePath(directory);
205009
- } catch {
205010
- warnings.push(`Unsafe audit directory ignored: ${directory}`);
205011
- return ".kyoso/traces";
207161
+ offset += bytesWritten;
205012
207162
  }
205013
207163
  }
207164
+ async function defaultWriteChunk(handle, buffer, offset) {
207165
+ const { bytesWritten } = await handle.write(buffer, offset, buffer.byteLength - offset, null);
207166
+ return bytesWritten;
207167
+ }
207168
+ async function defaultCloseHandle(handle) {
207169
+ await handle.close();
207170
+ }
205014
207171
 
205015
207172
  // src/context/truncate.ts
205016
207173
  function truncateUtf8(input2, maxBytes) {
@@ -205427,40 +207584,40 @@ function decide(input2) {
205427
207584
  }
205428
207585
 
205429
207586
  // src/workspace/createSnapshot.ts
205430
- import { chmod, mkdir as mkdir5, mkdtemp, writeFile as writeFile5 } from "node:fs/promises";
205431
- import { dirname as dirname6, join as join6 } from "node:path";
205432
- import { tmpdir } from "node:os";
207587
+ import { chmod, mkdir as mkdir6, mkdtemp as mkdtemp2, writeFile as writeFile6 } from "node:fs/promises";
207588
+ import { dirname as dirname8, join as join9 } from "node:path";
207589
+ import { tmpdir as tmpdir2 } from "node:os";
205433
207590
  async function createSnapshot(traceId, tool, request, options = {}) {
205434
- const root = await mkdtemp(join6(tmpdir(), `kyoso-${traceId}-`));
205435
- const repoDir = join6(root, "repo");
205436
- const contextDir = join6(root, "context");
205437
- await mkdir5(repoDir, { recursive: true });
205438
- await mkdir5(contextDir, { recursive: true });
207591
+ const root = await mkdtemp2(join9(tmpdir2(), `kyoso-${traceId}-`));
207592
+ const repoDir = join9(root, "repo");
207593
+ const contextDir = join9(root, "context");
207594
+ await mkdir6(repoDir, { recursive: true });
207595
+ await mkdir6(contextDir, { recursive: true });
205439
207596
  let fileCount = 0;
205440
207597
  for (const file2 of request.selectedFiles ?? []) {
205441
- const relative2 = normalizeRelativePath(file2.path);
205442
- if (isDeniedPath(relative2, options.denyPatterns ?? []))
207598
+ const relative4 = normalizeRelativePath(file2.path);
207599
+ if (isDeniedPath(relative4, options.denyPatterns ?? []))
205443
207600
  continue;
205444
- if (!isAllowedPath(relative2, options.allowPatterns ?? []))
207601
+ if (!isAllowedPath(relative4, options.allowPatterns ?? []))
205445
207602
  continue;
205446
- const dest = join6(repoDir, relative2);
205447
- await mkdir5(dirname6(dest), { recursive: true });
205448
- await writeFile5(dest, file2.content, "utf8");
207603
+ const dest = join9(repoDir, relative4);
207604
+ await mkdir6(dirname8(dest), { recursive: true });
207605
+ await writeFile6(dest, file2.content, "utf8");
205449
207606
  await chmod(dest, 292).catch(() => {
205450
207607
  return;
205451
207608
  });
205452
207609
  fileCount += 1;
205453
207610
  }
205454
- await writeFile5(join6(contextDir, "request.json"), JSON.stringify(stripContents(request), null, 2), "utf8");
205455
- await writeFile5(join6(contextDir, "selected_files_manifest.json"), JSON.stringify(buildSelectedFilesManifest(request), null, 2), "utf8");
205456
- await writeFile5(join6(contextDir, "instructions.codex.md"), buildAgentPrompt(tool, request, "codex", options.agentRoles?.codex ?? "implementation_reviewer"), "utf8");
205457
- await writeFile5(join6(contextDir, "instructions.claude.md"), buildAgentPrompt(tool, request, "claude", options.agentRoles?.claude ?? "architecture_security_reviewer"), "utf8");
207611
+ await writeFile6(join9(contextDir, "request.json"), JSON.stringify(stripContents(request), null, 2), "utf8");
207612
+ await writeFile6(join9(contextDir, "selected_files_manifest.json"), JSON.stringify(buildSelectedFilesManifest(request), null, 2), "utf8");
207613
+ await writeFile6(join9(contextDir, "instructions.codex.md"), buildAgentPrompt(tool, request, "codex", options.agentRoles?.codex ?? "implementation_reviewer"), "utf8");
207614
+ await writeFile6(join9(contextDir, "instructions.claude.md"), buildAgentPrompt(tool, request, "claude", options.agentRoles?.claude ?? "architecture_security_reviewer"), "utf8");
205458
207615
  if (request.repoSummary)
205459
- await writeFile5(join6(contextDir, "repo_summary.md"), request.repoSummary, "utf8");
207616
+ await writeFile6(join9(contextDir, "repo_summary.md"), request.repoSummary, "utf8");
205460
207617
  if (request.currentPlan)
205461
- await writeFile5(join6(contextDir, "current_plan.md"), request.currentPlan, "utf8");
207618
+ await writeFile6(join9(contextDir, "current_plan.md"), request.currentPlan, "utf8");
205462
207619
  if (request.diff?.unifiedDiff)
205463
- await writeFile5(join6(contextDir, "diff.patch"), request.diff.unifiedDiff, "utf8");
207620
+ await writeFile6(join9(contextDir, "diff.patch"), request.diff.unifiedDiff, "utf8");
205464
207621
  return { root, repoDir, contextDir, fileCount };
205465
207622
  }
205466
207623
  function stripContents(request) {
@@ -205482,17 +207639,17 @@ function buildSelectedFilesManifest(request) {
205482
207639
  }
205483
207640
 
205484
207641
  // src/workspace/cleanup.ts
205485
- import { rm } from "node:fs/promises";
207642
+ import { rm as rm2 } from "node:fs/promises";
205486
207643
  async function cleanupSnapshot(path) {
205487
207644
  if (process.env.KYOSO_KEEP_TEMP === "1")
205488
207645
  return;
205489
- await rm(path, { recursive: true, force: true });
207646
+ await rm2(path, { recursive: true, force: true });
205490
207647
  }
205491
207648
 
205492
207649
  // src/utils/ids.ts
205493
- import { randomUUID } from "node:crypto";
207650
+ import { randomUUID as randomUUID2 } from "node:crypto";
205494
207651
  function newTraceId() {
205495
- return `tr_${randomUUID()}`;
207652
+ return `tr_${randomUUID2()}`;
205496
207653
  }
205497
207654
 
205498
207655
  // src/core/verification.ts
@@ -205550,7 +207707,7 @@ function parseVerificationVerdicts(rawText) {
205550
207707
  if (!Array.isArray(parsed.verdicts))
205551
207708
  return;
205552
207709
  return parsed.verdicts.flatMap((item) => {
205553
- if (!isRecord8(item))
207710
+ if (!isRecord11(item))
205554
207711
  return [];
205555
207712
  if (typeof item.findingId !== "string")
205556
207713
  return [];
@@ -205628,7 +207785,7 @@ function verificationNote(reasoning) {
205628
207785
  function isVerdict(value) {
205629
207786
  return value === "confirmed" || value === "refuted" || value === "uncertain";
205630
207787
  }
205631
- function isRecord8(value) {
207788
+ function isRecord11(value) {
205632
207789
  return typeof value === "object" && value !== null && !Array.isArray(value);
205633
207790
  }
205634
207791
 
@@ -205637,47 +207794,54 @@ async function runReview(tool, request, options = {}) {
205637
207794
  const cwd = options.cwd ?? process.cwd();
205638
207795
  const traceId = newTraceId();
205639
207796
  const startedAt = new Date().toISOString();
207797
+ const auditEnv = { ...process.env, ...options.env };
207798
+ const traceWriterFactory = options.traceWriterFactory ?? createTraceWriter;
205640
207799
  let snapshot;
205641
207800
  try {
205642
207801
  assertNotChildAgent(options.env ?? process.env);
205643
207802
  } catch (error51) {
205644
207803
  if (error51 instanceof KyosoRequestError) {
205645
207804
  const config2 = kyosoConfigSchema.parse(defaultConfig);
205646
- const trace2 = createTraceWriter({
207805
+ const trace2 = traceWriterFactory({
205647
207806
  enabled: config2.audit.enabled,
205648
207807
  directory: config2.audit.directory,
205649
207808
  traceId,
205650
- cwd
205651
- });
205652
- await trace2.write({
205653
- type: "request_received",
205654
- traceId,
205655
- tool,
205656
- timestamp: new Date().toISOString()
205657
- });
205658
- return await buildPolicyBlockResult({
205659
- tool,
205660
- trace: trace2,
205661
- traceId,
205662
- startedAt,
205663
- networkMode: config2.network.defaultMode,
205664
- warning: error51.message,
205665
- finding: {
205666
- id: "KYOSO-1",
205667
- severity: "critical",
205668
- category: "other",
205669
- title: "Recursive Kyoso invocation blocked",
205670
- evidence: error51.message,
205671
- recommendation: "Do not expose Kyoso MCP tools to Kyoso child agents.",
205672
- sourceAgents: ["kyoso_policy"],
205673
- confidence: "high"
205674
- },
205675
- redactionsApplied: 0
207809
+ cwd,
207810
+ env: auditEnv
205676
207811
  });
207812
+ try {
207813
+ await trace2.write({
207814
+ type: "request_received",
207815
+ traceId,
207816
+ tool,
207817
+ timestamp: new Date().toISOString()
207818
+ });
207819
+ return await buildPolicyBlockResult({
207820
+ tool,
207821
+ trace: trace2,
207822
+ traceId,
207823
+ startedAt,
207824
+ networkMode: config2.network.defaultMode,
207825
+ warning: error51.message,
207826
+ finding: {
207827
+ id: "KYOSO-1",
207828
+ severity: "critical",
207829
+ category: "other",
207830
+ title: "Recursive Kyoso invocation blocked",
207831
+ evidence: error51.message,
207832
+ recommendation: "Do not expose Kyoso MCP tools to Kyoso child agents.",
207833
+ sourceAgents: ["kyoso_policy"],
207834
+ confidence: "high"
207835
+ },
207836
+ redactionsApplied: 0
207837
+ });
207838
+ } finally {
207839
+ await trace2.finalize();
207840
+ }
205677
207841
  }
205678
207842
  throw error51;
205679
207843
  }
205680
- const loaded = options.config !== undefined ? {
207844
+ const baseLoaded = options.config !== undefined ? {
205681
207845
  config: options.config,
205682
207846
  configHash: options.configHash,
205683
207847
  configTrustStatus: "trusted",
@@ -205694,12 +207858,17 @@ async function runReview(tool, request, options = {}) {
205694
207858
  env: options.env,
205695
207859
  trustPrompt: options.trustPrompt
205696
207860
  });
205697
- const trace = createTraceWriter({
207861
+ const loaded = options.configOverrides && options.configOverrides.length > 0 ? {
207862
+ ...baseLoaded,
207863
+ config: applyConfigOverrides(baseLoaded.config, options.configOverrides)
207864
+ } : baseLoaded;
207865
+ const trace = traceWriterFactory({
205698
207866
  enabled: loaded.config.audit.enabled,
205699
207867
  directory: loaded.config.audit.directory,
205700
207868
  traceId,
205701
207869
  cwd,
205702
- includeRawAgentOutput: loaded.config.audit.includeRawAgentOutput
207870
+ includeRawAgentOutput: loaded.config.audit.includeRawAgentOutput,
207871
+ env: auditEnv
205703
207872
  });
205704
207873
  const warnings = [...loaded.warnings, ...trace.warnings];
205705
207874
  try {
@@ -205781,11 +207950,11 @@ async function runReview(tool, request, options = {}) {
205781
207950
  manager,
205782
207951
  trace
205783
207952
  });
205784
- warnings.push(...agentResults.flatMap((result2) => (result2.warnings ?? []).map((warning) => `Agent ${result2.agent} ${warning}`)));
207953
+ warnings.push(...agentResults.flatMap((result) => (result.warnings ?? []).map((warning) => `Agent ${result.agent} ${warning}`)));
205785
207954
  const normalizedAgentResults = agentResults.map(normalizeAgentRunResult);
205786
- const agentsUsed = normalizedAgentResults.map((result2) => result2.agent);
207955
+ const agentsUsed = normalizedAgentResults.map((result) => result.agent);
205787
207956
  const reviewMode = agentsUsed.length === 1 ? "single_agent" : "multi_agent";
205788
- const completed = normalizedAgentResults.filter((result2) => result2.status === "completed");
207957
+ const completed = normalizedAgentResults.filter((result) => result.status === "completed");
205789
207958
  const degraded = completed.length !== agentResults.length;
205790
207959
  let aggregate = aggregateAgentResults(normalizedAgentResults, {
205791
207960
  reviewMode
@@ -205812,7 +207981,7 @@ async function runReview(tool, request, options = {}) {
205812
207981
  severity: "critical",
205813
207982
  category: "other",
205814
207983
  title: "All backend agents failed",
205815
- evidence: normalizedAgentResults.map((result2) => `${result2.agent}: ${result2.error?.code ?? result2.status}`).join("; "),
207984
+ evidence: normalizedAgentResults.map((result) => `${result.agent}: ${result.error?.code ?? result.status}`).join("; "),
205816
207985
  recommendation: "Run kyoso doctor and retry after agent authentication or adapter issues are fixed.",
205817
207986
  sourceAgents: ["kyoso_policy"],
205818
207987
  confidence: "high"
@@ -205862,7 +208031,7 @@ async function runReview(tool, request, options = {}) {
205862
208031
  residualRisks: tool === "security_review" && aggregate.residualRisks.length === 0 ? [
205863
208032
  "No residual risks were reported by completed agents; verify security assumptions before release."
205864
208033
  ] : aggregate.residualRisks,
205865
- agentOpinions: normalizedAgentResults.map((result2) => agentOpinionSummary(result2, request.options?.includeAgentRawOutputs === true)),
208034
+ agentOpinions: normalizedAgentResults.map((result) => agentOpinionSummary(result, request.options?.includeAgentRawOutputs === true)),
205866
208035
  audit: {
205867
208036
  traceId,
205868
208037
  startedAt,
@@ -205894,15 +208063,10 @@ async function runReview(tool, request, options = {}) {
205894
208063
  judgeComment: judgeComments.get(disagreement.topic) ?? disagreement.judgeComment
205895
208064
  }));
205896
208065
  const crossModelAnalysis = buildCrossModelAnalysis(judge, reviewMode);
205897
- const result = {
208066
+ const resultAfterJudge = {
205898
208067
  ...resultWithoutMarkdown,
205899
208068
  disagreements,
205900
- ...crossModelAnalysis ? { crossModelAnalysis } : {},
205901
- summaryMarkdown: renderMarkdownResult(tool, {
205902
- ...resultWithoutMarkdown,
205903
- disagreements,
205904
- ...crossModelAnalysis ? { crossModelAnalysis } : {}
205905
- }, { summaryText: judge.output.summaryText })
208069
+ ...crossModelAnalysis ? { crossModelAnalysis } : {}
205906
208070
  };
205907
208071
  const judgeEvent = {
205908
208072
  type: "judge_completed",
@@ -205914,7 +208078,7 @@ async function runReview(tool, request, options = {}) {
205914
208078
  if (judge.error)
205915
208079
  judgeEvent.error = judge.error;
205916
208080
  await trace.write(judgeEvent);
205917
- result.audit.completedAt = new Date().toISOString();
208081
+ resultAfterJudge.audit.completedAt = new Date().toISOString();
205918
208082
  await trace.write({
205919
208083
  type: "decision_completed",
205920
208084
  traceId,
@@ -205926,8 +208090,14 @@ async function runReview(tool, request, options = {}) {
205926
208090
  traceId,
205927
208091
  timestamp: new Date().toISOString()
205928
208092
  });
205929
- return result;
208093
+ return await finalizeReviewResult({
208094
+ tool,
208095
+ trace,
208096
+ result: resultAfterJudge,
208097
+ summaryText: judge.output.summaryText
208098
+ });
205930
208099
  } finally {
208100
+ await trace.finalize();
205931
208101
  if (snapshot)
205932
208102
  await cleanupSnapshot(snapshot.root);
205933
208103
  }
@@ -206176,10 +208346,6 @@ async function buildSecretBlockResult(input2) {
206176
208346
  warnings: input2.warnings
206177
208347
  }
206178
208348
  };
206179
- const result = {
206180
- ...resultWithoutMarkdown,
206181
- summaryMarkdown: renderMarkdownResult(input2.tool, resultWithoutMarkdown)
206182
- };
206183
208349
  await input2.trace.write({
206184
208350
  type: "decision_completed",
206185
208351
  traceId: input2.traceId,
@@ -206191,7 +208357,11 @@ async function buildSecretBlockResult(input2) {
206191
208357
  traceId: input2.traceId,
206192
208358
  timestamp: new Date().toISOString()
206193
208359
  });
206194
- return result;
208360
+ return await finalizeReviewResult({
208361
+ tool: input2.tool,
208362
+ trace: input2.trace,
208363
+ result: resultWithoutMarkdown
208364
+ });
206195
208365
  }
206196
208366
  function buildSecretFinding(secretScan, options) {
206197
208367
  return {
@@ -206241,10 +208411,6 @@ async function buildPolicyBlockResult(input2) {
206241
208411
  warnings: [input2.warning]
206242
208412
  }
206243
208413
  };
206244
- const result = {
206245
- ...resultWithoutMarkdown,
206246
- summaryMarkdown: renderMarkdownResult(input2.tool, resultWithoutMarkdown)
206247
- };
206248
208414
  await input2.trace.write({
206249
208415
  type: "decision_completed",
206250
208416
  traceId: input2.traceId,
@@ -206256,7 +208422,30 @@ async function buildPolicyBlockResult(input2) {
206256
208422
  traceId: input2.traceId,
206257
208423
  timestamp: new Date().toISOString()
206258
208424
  });
206259
- return result;
208425
+ return await finalizeReviewResult({
208426
+ tool: input2.tool,
208427
+ trace: input2.trace,
208428
+ result: resultWithoutMarkdown
208429
+ });
208430
+ }
208431
+ async function finalizeReviewResult(input2) {
208432
+ await input2.trace.finalize();
208433
+ const result = {
208434
+ ...input2.result,
208435
+ audit: {
208436
+ ...input2.result.audit,
208437
+ warnings: Array.from(new Set([
208438
+ ...input2.result.audit.warnings ?? [],
208439
+ ...input2.trace.warnings
208440
+ ]))
208441
+ }
208442
+ };
208443
+ return {
208444
+ ...result,
208445
+ summaryMarkdown: renderMarkdownResult(input2.tool, result, {
208446
+ summaryText: input2.summaryText
208447
+ })
208448
+ };
206260
208449
  }
206261
208450
  function mergeDenyPatterns(configDeny, requestDeny) {
206262
208451
  return Array.from(new Set([...configDeny, ...requestDeny ?? []]));
@@ -206264,7 +208453,7 @@ function mergeDenyPatterns(configDeny, requestDeny) {
206264
208453
  function assertTrustedWorkspaceRoot(requestRoot, configRoot, cwd) {
206265
208454
  if (!requestRoot)
206266
208455
  return;
206267
- if (resolve6(cwd, requestRoot) !== resolve6(cwd, configRoot)) {
208456
+ if (resolve10(cwd, requestRoot) !== resolve10(cwd, configRoot)) {
206268
208457
  throw new KyosoRequestError("workspace.root is not trusted by config", "UNTRUSTED_WORKSPACE_ROOT");
206269
208458
  }
206270
208459
  }
@@ -206382,7 +208571,9 @@ async function main() {
206382
208571
  write: booleanFlag(parsed.flags, "write"),
206383
208572
  global: booleanFlag(parsed.flags, "global"),
206384
208573
  runner: stringFlag(parsed.flags, "runner"),
206385
- command: stringFlag(parsed.flags, "command")
208574
+ command: stringFlag(parsed.flags, "command"),
208575
+ skillOnly: booleanFlag(parsed.flags, "skill-only"),
208576
+ force: booleanFlag(parsed.flags, "force")
206386
208577
  }));
206387
208578
  return;
206388
208579
  }
@@ -206395,6 +208586,7 @@ async function main() {
206395
208586
  ignoreConfig,
206396
208587
  trustConfig: trustConfig2,
206397
208588
  allowUnknownConfig,
208589
+ configOverrides: configOverrideFlags(parsed.flags),
206398
208590
  promptForTrust: canPromptForConfigTrust()
206399
208591
  });
206400
208592
  console.log(booleanFlag(parsed.flags, "json") ? JSON.stringify(result, null, 2) : result.summaryMarkdown);
@@ -206438,7 +208630,7 @@ async function buildDiff(tool, flags) {
206438
208630
  return;
206439
208631
  const base = stringFlag(flags, "base") ?? "main";
206440
208632
  const head = stringFlag(flags, "head") ?? "HEAD";
206441
- const result = spawnSync2("git", ["diff", base, head], { encoding: "utf8" });
208633
+ const result = spawnSync3("git", ["diff", base, head], { encoding: "utf8" });
206442
208634
  if (result.status !== 0) {
206443
208635
  throw new Error(result.stderr || `git diff failed for ${base}..${head}`);
206444
208636
  }
@@ -206471,6 +208663,12 @@ function parseNetworkFlag(value) {
206471
208663
  return value;
206472
208664
  throw new Error(`Invalid --network value "${value}". Expected model_only or unrestricted.`);
206473
208665
  }
208666
+ function configOverrideFlags(flags) {
208667
+ if (flags.set === true) {
208668
+ throw new Error("Missing value for --set. Expected key=value.");
208669
+ }
208670
+ return stringArrayFlag(flags, "set");
208671
+ }
206474
208672
  function canPromptForConfigTrust() {
206475
208673
  return process.stdin.isTTY === true && process.stderr.isTTY === true;
206476
208674
  }
@@ -206478,10 +208676,11 @@ var HELP = `Kyoso
206478
208676
 
206479
208677
  Usage:
206480
208678
  kyoso mcp [--config kyoso.toml|kyoso.config.ts] [--ignore-config] [--trust-config] [--allow-unknown-config] [--network model_only|unrestricted]
206481
- kyoso setup [codex|claude-code] [--write] [--runner npx|bunx] [--command <command>] [--global]
206482
- kyoso plan --goal <text> [--plan <path-or-text>] [--file <path>] [--json] [--trust-config] [--allow-unknown-config]
206483
- kyoso security --goal <text> [--diff <path>] [--file <path>] [--allow-secret-redaction] [--trust-config] [--allow-unknown-config]
206484
- kyoso diff --base main --head HEAD [--json] [--trust-config] [--allow-unknown-config]
208679
+ kyoso setup [codex|claude-code] [--write] [--runner npx|bunx] [--command <command>] [--global] [--force]
208680
+ kyoso setup codex|claude-code --skill-only [--write] [--global] [--force]
208681
+ kyoso plan --goal <text> [--plan <path-or-text>] [--file <path>] [--set <key>=<value>]... [--json] [--trust-config] [--allow-unknown-config]
208682
+ kyoso security --goal <text> [--diff <path>] [--file <path>] [--set <key>=<value>]... [--json] [--allow-secret-redaction] [--trust-config] [--allow-unknown-config]
208683
+ kyoso diff --base main --head HEAD [--set <key>=<value>]... [--json] [--trust-config] [--allow-unknown-config]
206485
208684
  kyoso doctor [--trust-config] [--allow-unknown-config]
206486
208685
  kyoso init [--force]
206487
208686
  `;