@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/index.js CHANGED
@@ -169498,7 +169498,7 @@ function defineConfig(config) {
169498
169498
  return config;
169499
169499
  }
169500
169500
  // src/core/runReview.ts
169501
- import { resolve as resolve4 } from "node:path";
169501
+ import { resolve as resolve6 } from "node:path";
169502
169502
 
169503
169503
  // node_modules/zod/v4/classic/external.js
169504
169504
  var exports_external = {};
@@ -184076,6 +184076,28 @@ import { createInterface } from "node:readline/promises";
184076
184076
 
184077
184077
  // src/config/projectScope.ts
184078
184078
  var PROJECT_GLOBAL_ONLY_MESSAGE = "Move global-only settings to the user global config:";
184079
+ var kyosoConfigOverridePaths = [
184080
+ "agents.codex.enabled",
184081
+ "agents.codex.model",
184082
+ "agents.codex.effort",
184083
+ "agents.codex.role",
184084
+ "agents.codex.timeoutMs",
184085
+ "agents.claude.enabled",
184086
+ "agents.claude.model",
184087
+ "agents.claude.effort",
184088
+ "agents.claude.role",
184089
+ "agents.claude.timeoutMs",
184090
+ "verification.enabled",
184091
+ "verification.maxFindings",
184092
+ "verification.timeoutMs",
184093
+ "judge.mode",
184094
+ "judge.provider",
184095
+ "judge.timeoutMs"
184096
+ ];
184097
+ var CONFIG_OVERRIDE_PATHS = new Set(kyosoConfigOverridePaths);
184098
+ function isAllowedConfigOverridePath(path) {
184099
+ return CONFIG_OVERRIDE_PATHS.has(path.join("."));
184100
+ }
184079
184101
  function mergeProjectTomlConfig(baseConfig, projectConfig, options) {
184080
184102
  const violations = collectProjectScopeViolations(projectConfig);
184081
184103
  if (violations.length > 0) {
@@ -184108,13 +184130,9 @@ function collectProjectScopeViolations(config2) {
184108
184130
  }
184109
184131
  function isAllowedProjectPath(path) {
184110
184132
  const [top, second, third, fourth] = path;
184111
- if (top === "tools" && path.length === 2 && ["planReview", "securityReview", "diffReview"].includes(second ?? "")) {
184133
+ if (isAllowedConfigOverridePath(path))
184112
184134
  return true;
184113
- }
184114
- if (top === "agents" && path.length === 3 && ["codex", "claude"].includes(second ?? "") && ["enabled", "model", "effort", "role", "timeoutMs"].includes(third ?? "")) {
184115
- return true;
184116
- }
184117
- if (top === "verification" && path.length === 2 && ["enabled", "maxFindings", "timeoutMs"].includes(second ?? "")) {
184135
+ if (top === "tools" && path.length === 2 && ["planReview", "securityReview", "diffReview"].includes(second ?? "")) {
184118
184136
  return true;
184119
184137
  }
184120
184138
  if (top === "workspace" && path.length === 2 && ["maxContextBytes", "maxDiffBytes", "deny"].includes(second ?? "")) {
@@ -184126,9 +184144,6 @@ function isAllowedProjectPath(path) {
184126
184144
  if (top === "secrets" && path.length === 2 && ["blockOnDetectedSecret", "allowOverride"].includes(second ?? "")) {
184127
184145
  return true;
184128
184146
  }
184129
- if (top === "judge" && path.length === 2 && ["mode", "provider", "timeoutMs"].includes(second ?? "")) {
184130
- return true;
184131
- }
184132
184147
  if (top === "securityReview" && second === "cisaSecureByDesign" && path.length >= 3) {
184133
184148
  if (third === "dimensions") {
184134
184149
  return path.length === 4 && [
@@ -184246,6 +184261,7 @@ var REDACTION = "[KYOSO_REDACTED]";
184246
184261
  // src/core/constants.ts
184247
184262
  var DEFAULT_AGENT_TIMEOUT_MS = 120000;
184248
184263
  var RAW_OUTPUT_MAX_CHARS = 16384;
184264
+ var TRACE_DIR = ".kyoso/traces";
184249
184265
  var KYOSO_CHILD_AGENT = "KYOSO_CHILD_AGENT";
184250
184266
 
184251
184267
  // src/security/sanitizeText.ts
@@ -185548,6 +185564,90 @@ async function exists(path) {
185548
185564
  }
185549
185565
  }
185550
185566
 
185567
+ // src/config/configOverrides.ts
185568
+ var NUMBER_VALUE = /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?$/i;
185569
+ function applyConfigOverrides(config2, assignments) {
185570
+ if (assignments.length === 0)
185571
+ return config2;
185572
+ const overrides = assignments.map(parseConfigOverride);
185573
+ const baseConfig = config2;
185574
+ const overridden = structuredClone(config2);
185575
+ for (const override of overrides) {
185576
+ writePath2(overridden, override.path, parseConfigOverrideValue(override.value, readPath2(baseConfig, override.path)));
185577
+ }
185578
+ const parsed = kyosoConfigSchema.safeParse(overridden);
185579
+ if (parsed.success)
185580
+ return parsed.data;
185581
+ const issue2 = parsed.error.issues[0];
185582
+ const issuePath = issue2?.path.map(String).join(".") ?? "config";
185583
+ const assignment = findAssignmentForPath(overrides, issuePath) ?? assignments.at(-1) ?? "";
185584
+ throw new Error(`Invalid --set value ${JSON.stringify(assignment)}: ${issuePath}: ${issue2?.message ?? "config validation failed"}.`);
185585
+ }
185586
+ function findAssignmentForPath(overrides, path) {
185587
+ for (let index = overrides.length - 1;index >= 0; index -= 1) {
185588
+ const override = overrides[index];
185589
+ if (override?.path.join(".") === path)
185590
+ return override.assignment;
185591
+ }
185592
+ return;
185593
+ }
185594
+ function parseConfigOverride(assignment) {
185595
+ const separator = assignment.indexOf("=");
185596
+ if (separator <= 0) {
185597
+ throw new Error(`Invalid --set value ${JSON.stringify(assignment)}. Expected key=value.`);
185598
+ }
185599
+ const key = assignment.slice(0, separator);
185600
+ const path = key.split(".");
185601
+ if (!isAllowedConfigOverridePath(path)) {
185602
+ throw new Error(`Unknown --set key ${JSON.stringify(key)}.`);
185603
+ }
185604
+ return {
185605
+ assignment,
185606
+ path,
185607
+ value: assignment.slice(separator + 1)
185608
+ };
185609
+ }
185610
+ function parseConfigOverrideValue(value, currentValue) {
185611
+ if (typeof currentValue === "boolean") {
185612
+ if (value === "true")
185613
+ return true;
185614
+ if (value === "false")
185615
+ return false;
185616
+ return value;
185617
+ }
185618
+ if (typeof currentValue === "number" && NUMBER_VALUE.test(value)) {
185619
+ const parsed = Number(value);
185620
+ if (Number.isFinite(parsed))
185621
+ return parsed;
185622
+ }
185623
+ return value;
185624
+ }
185625
+ function readPath2(target, path) {
185626
+ let current = target;
185627
+ for (const key of path) {
185628
+ if (!isRecord4(current))
185629
+ return;
185630
+ current = current[key];
185631
+ }
185632
+ return current;
185633
+ }
185634
+ function writePath2(target, path, value) {
185635
+ let current = target;
185636
+ for (const key of path.slice(0, -1)) {
185637
+ const child = current[key];
185638
+ if (!isRecord4(child)) {
185639
+ throw new Error(`Cannot apply --set key ${JSON.stringify(path.join("."))}.`);
185640
+ }
185641
+ current = child;
185642
+ }
185643
+ const leaf = path.at(-1);
185644
+ if (leaf)
185645
+ current[leaf] = value;
185646
+ }
185647
+ function isRecord4(value) {
185648
+ return typeof value === "object" && value !== null && !Array.isArray(value);
185649
+ }
185650
+
185551
185651
  // src/acp/AcpAgentProcess.ts
185552
185652
  import { spawn } from "node:child_process";
185553
185653
  import { readFile as readFile4, realpath } from "node:fs/promises";
@@ -185622,6 +185722,48 @@ function requiredDefaultOnError(schema, fallback) {
185622
185722
  return exports_external.NEVER;
185623
185723
  });
185624
185724
  }
185725
+ function stringTag(value, key) {
185726
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
185727
+ return;
185728
+ }
185729
+ const tag = value[key];
185730
+ return typeof tag === "string" ? tag : undefined;
185731
+ }
185732
+ function excludeKnownTags(schema, key, knownTags) {
185733
+ return schema.superRefine((value, context) => {
185734
+ const tag = stringTag(value, key);
185735
+ if (tag !== undefined && knownTags.includes(tag)) {
185736
+ context.addIssue({
185737
+ code: "custom",
185738
+ path: [key],
185739
+ message: `${key} ${JSON.stringify(tag)} is reserved by a known variant, ` + `but the value does not match that variant's schema`
185740
+ });
185741
+ }
185742
+ });
185743
+ }
185744
+ function preserveCustomPayload(schema, key, knownTags) {
185745
+ return exports_external.unknown().transform((value, context) => {
185746
+ const result = schema.safeParse(value);
185747
+ if (!result.success) {
185748
+ for (const issue2 of result.error.issues) {
185749
+ context.addIssue({ ...issue2, input: value });
185750
+ }
185751
+ return exports_external.NEVER;
185752
+ }
185753
+ const output = result.data;
185754
+ const tag = stringTag(value, key);
185755
+ if (tag !== undefined && !knownTags.includes(tag)) {
185756
+ const raw = value;
185757
+ for (const [property, rawValue] of Object.entries(raw)) {
185758
+ if (property === "__proto__")
185759
+ continue;
185760
+ if (!Object.hasOwn(output, property))
185761
+ output[property] = rawValue;
185762
+ }
185763
+ }
185764
+ return output;
185765
+ });
185766
+ }
185625
185767
  function vecSkipError(itemSchema) {
185626
185768
  return exports_external.array(itemSchema.catch(skippedItem)).transform((items) => items.filter((item) => item !== skippedItem));
185627
185769
  }
@@ -186045,15 +186187,15 @@ var zTitledMultiSelectItems = object({
186045
186187
  return;
186046
186188
  })
186047
186189
  });
186048
- var zMultiSelectItems = union([
186190
+ var zMultiSelectItems = preserveCustomPayload(union([
186049
186191
  zStringMultiSelectItems.and(object({
186050
186192
  type: literal("string")
186051
186193
  })),
186052
- object({
186194
+ excludeKnownTags(object({
186053
186195
  type: string2()
186054
- }),
186196
+ }), "type", ["string"]),
186055
186197
  zTitledMultiSelectItems
186056
- ]);
186198
+ ]), "type", ["string"]);
186057
186199
  var zMultiSelectPropertySchema = object({
186058
186200
  title: defaultOnError(string2().nullish(), () => {
186059
186201
  return;
@@ -186071,7 +186213,7 @@ var zMultiSelectPropertySchema = object({
186071
186213
  return;
186072
186214
  })
186073
186215
  });
186074
- var zElicitationPropertySchema = union([
186216
+ var zElicitationPropertySchema = preserveCustomPayload(union([
186075
186217
  zStringPropertySchema.and(object({
186076
186218
  type: literal("string")
186077
186219
  })),
@@ -186087,10 +186229,10 @@ var zElicitationPropertySchema = union([
186087
186229
  zMultiSelectPropertySchema.and(object({
186088
186230
  type: literal("array")
186089
186231
  })),
186090
- object({
186232
+ excludeKnownTags(object({
186091
186233
  type: string2()
186092
- })
186093
- ]);
186234
+ }), "type", ["array", "boolean", "integer", "number", "string"])
186235
+ ]), "type", ["array", "boolean", "integer", "number", "string"]);
186094
186236
  var zElicitationSchema = object({
186095
186237
  type: defaultOnError(zElicitationSchemaType.optional().default("object"), () => "object"),
186096
186238
  title: defaultOnError(string2().nullish(), () => {
@@ -186113,22 +186255,22 @@ var zElicitationUrlMode = intersection(union([zElicitationSessionScope, zElicita
186113
186255
  elicitationId: zElicitationId,
186114
186256
  url: url()
186115
186257
  }));
186116
- var zCreateElicitationRequest = intersection(union([
186258
+ var zCreateElicitationRequest = preserveCustomPayload(intersection(union([
186117
186259
  zElicitationFormMode.and(object({
186118
186260
  mode: literal("form")
186119
186261
  })),
186120
186262
  zElicitationUrlMode.and(object({
186121
186263
  mode: literal("url")
186122
186264
  })),
186123
- intersection(union([zElicitationSessionScope, zElicitationRequestScope]), object({
186265
+ excludeKnownTags(intersection(union([zElicitationSessionScope, zElicitationRequestScope]), object({
186124
186266
  mode: string2()
186125
- }))
186267
+ })), "mode", ["form", "url"])
186126
186268
  ]), object({
186127
186269
  message: string2(),
186128
186270
  _meta: defaultOnError(record(string2(), unknown()).nullish(), () => {
186129
186271
  return;
186130
186272
  })
186131
- }));
186273
+ })), "mode", ["form", "url"]);
186132
186274
  var zMcpServerAcpId = string2();
186133
186275
  var zConnectMcpRequest = object({
186134
186276
  serverId: zMcpServerAcpId,
@@ -187728,7 +187870,7 @@ var zElicitationContentValue = union([
187728
187870
  var zElicitationAcceptAction = object({
187729
187871
  content: record(string2(), zElicitationContentValue).nullish()
187730
187872
  });
187731
- var zCreateElicitationResponse = intersection(union([
187873
+ var zCreateElicitationResponse = preserveCustomPayload(intersection(union([
187732
187874
  zElicitationAcceptAction.and(object({
187733
187875
  action: literal("accept")
187734
187876
  })),
@@ -187738,14 +187880,14 @@ var zCreateElicitationResponse = intersection(union([
187738
187880
  object({
187739
187881
  action: literal("cancel")
187740
187882
  }),
187741
- object({
187883
+ excludeKnownTags(object({
187742
187884
  action: string2()
187743
- })
187885
+ }), "action", ["accept", "cancel", "decline"])
187744
187886
  ]), object({
187745
187887
  _meta: defaultOnError(record(string2(), unknown()).nullish(), () => {
187746
187888
  return;
187747
187889
  })
187748
- }));
187890
+ })), "action", ["accept", "cancel", "decline"]);
187749
187891
  var zConnectMcpResponse = object({
187750
187892
  connectionId: zMcpConnectionId,
187751
187893
  _meta: defaultOnError(record(string2(), unknown()).nullish(), () => {
@@ -187881,16 +188023,34 @@ var zCancelRequestNotification = object({
187881
188023
  return;
187882
188024
  })
187883
188025
  });
188026
+
188027
+ // node_modules/@agentclientprotocol/sdk/dist/schema/guards.gen.js
188028
+ var zGuardCreateElicitationRequestForm = zElicitationFormMode.and(object({ mode: literal("form") })).and(object({ message: string2() }));
188029
+ var zGuardCreateElicitationRequestUrl = zElicitationUrlMode.and(object({ mode: literal("url") })).and(object({ message: string2() }));
188030
+ var zGuardCreateElicitationRequestCustom = union([zElicitationSessionScope, zElicitationRequestScope]).and(object({ message: string2() }));
188031
+ var zGuardElicitationPropertySchemaString = zStringPropertySchema.and(object({ type: literal("string") }));
188032
+ var zGuardElicitationPropertySchemaNumber = zNumberPropertySchema.and(object({ type: literal("number") }));
188033
+ var zGuardElicitationPropertySchemaInteger = zIntegerPropertySchema.and(object({ type: literal("integer") }));
188034
+ var zGuardElicitationPropertySchemaBoolean = zBooleanPropertySchema.and(object({ type: literal("boolean") }));
188035
+ var zGuardElicitationPropertySchemaArray = zMultiSelectPropertySchema.and(object({ type: literal("array") }));
188036
+ var zGuardMultiSelectItemsString = zStringMultiSelectItems.and(object({ type: literal("string") }));
188037
+ var zGuardCreateElicitationResponseAccept = zElicitationAcceptAction.and(object({ action: literal("accept") }));
188038
+ var zGuardCreateElicitationResponseDecline = object({
188039
+ action: literal("decline")
188040
+ });
188041
+ var zGuardCreateElicitationResponseCancel = object({
188042
+ action: literal("cancel")
188043
+ });
187884
188044
  // node_modules/@agentclientprotocol/sdk/dist/jsonrpc.js
187885
188045
  var CANCEL_REQUEST_METHOD = "$/cancel_request";
187886
- function isRecord4(value) {
188046
+ function isRecord5(value) {
187887
188047
  return typeof value === "object" && value !== null;
187888
188048
  }
187889
188049
  function isJsonRpcId(value) {
187890
188050
  return value === null || typeof value === "string" || typeof value === "number" && Number.isFinite(value);
187891
188051
  }
187892
188052
  function cancelRequestId(params) {
187893
- if (!isRecord4(params) || !isJsonRpcId(params["requestId"])) {
188053
+ if (!isRecord5(params) || !isJsonRpcId(params["requestId"])) {
187894
188054
  return;
187895
188055
  }
187896
188056
  return params["requestId"];
@@ -188237,7 +188397,7 @@ class Connection {
188237
188397
  if (this.abortController.signal.aborted) {
188238
188398
  return;
188239
188399
  }
188240
- if (!isRecord4(message)) {
188400
+ if (!isRecord5(message)) {
188241
188401
  console.error("Invalid message", { message });
188242
188402
  return;
188243
188403
  }
@@ -188330,7 +188490,7 @@ class Connection {
188330
188490
  pendingResponse.cleanup?.();
188331
188491
  if ("result" in response) {
188332
188492
  pendingResponse.resolve(response.result);
188333
- } else if ("error" in response && isRecord4(response.error)) {
188493
+ } else if ("error" in response && isRecord5(response.error)) {
188334
188494
  const { code, message, data } = response.error;
188335
188495
  pendingResponse.reject(new RequestError(code, message, data));
188336
188496
  } else {
@@ -188540,7 +188700,7 @@ function ndJsonStream(output, input) {
188540
188700
  if (trimmedLine) {
188541
188701
  try {
188542
188702
  const message = JSON.parse(trimmedLine);
188543
- if (isRecord4(message)) {
188703
+ if (isRecord5(message)) {
188544
188704
  controller.enqueue(message);
188545
188705
  } else {
188546
188706
  console.warn("Skipping JSON line that is not an object:", trimmedLine);
@@ -189601,7 +189761,7 @@ function isSeverity(value) {
189601
189761
  return typeof value === "string" && severities.includes(value);
189602
189762
  }
189603
189763
  function normalizeCisaSecureByDesign(value) {
189604
- if (!isRecord5(value))
189764
+ if (!isRecord6(value))
189605
189765
  return;
189606
189766
  const normalized = {};
189607
189767
  const customerSecurityOutcomes = normalizeGateStatus(value.customerSecurityOutcomes);
@@ -189642,7 +189802,7 @@ function normalizeFindingFiles(value) {
189642
189802
  if (!Array.isArray(value))
189643
189803
  return;
189644
189804
  const files = value.flatMap((item) => {
189645
- if (!isRecord5(item) || typeof item.path !== "string" || item.path.trim().length === 0) {
189805
+ if (!isRecord6(item) || typeof item.path !== "string" || item.path.trim().length === 0) {
189646
189806
  return [];
189647
189807
  }
189648
189808
  const file2 = {
@@ -189661,7 +189821,7 @@ function normalizeFindingFiles(value) {
189661
189821
  function normalizeLineNumber(value) {
189662
189822
  return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : undefined;
189663
189823
  }
189664
- function isRecord5(value) {
189824
+ function isRecord6(value) {
189665
189825
  return typeof value === "object" && value !== null && !Array.isArray(value);
189666
189826
  }
189667
189827
 
@@ -190622,9 +190782,10 @@ function normalizeTitle(value) {
190622
190782
  return value.toLowerCase().replace(/[^\p{L}\p{N}]+/gu, " ").trim();
190623
190783
  }
190624
190784
 
190625
- // src/audit/trace.ts
190626
- import { mkdir as mkdir2, appendFile } from "node:fs/promises";
190627
- import { dirname as dirname3, isAbsolute as isAbsolute2, join as join3 } from "node:path";
190785
+ // src/audit/stateRoot.ts
190786
+ import { createHash as createHash2 } from "node:crypto";
190787
+ import { lstat, mkdir as mkdir2, realpath as realpath2 } from "node:fs/promises";
190788
+ import { basename, dirname as dirname3, isAbsolute as isAbsolute2, join as join3, resolve as resolve5 } from "node:path";
190628
190789
 
190629
190790
  // src/context/pathPolicy.ts
190630
190791
  import { normalize, sep } from "node:path";
@@ -190686,6 +190847,325 @@ function matchesPathPattern(path, patterns, mode) {
190686
190847
  });
190687
190848
  }
190688
190849
 
190850
+ // src/utils/pathContainment.ts
190851
+ import { resolve as resolve4, sep as sep2 } from "node:path";
190852
+ function isPathWithin(candidate, root) {
190853
+ const resolvedCandidate = resolve4(candidate);
190854
+ const resolvedRoot = resolve4(root);
190855
+ const boundary = resolvedRoot.endsWith(sep2) ? resolvedRoot : `${resolvedRoot}${sep2}`;
190856
+ return resolvedCandidate === resolvedRoot || resolvedCandidate.startsWith(boundary);
190857
+ }
190858
+
190859
+ // src/audit/stateRoot.ts
190860
+ var PRIVATE_DIRECTORY_MODE = 448;
190861
+ var UNSAFE_DIRECTORY_MODE = 18;
190862
+ var STICKY_DIRECTORY_MODE = 512;
190863
+ var AUDIT_WARNING_DIRECTORY_IGNORED = "AUDIT_DIRECTORY_IGNORED: Audit directory is invalid; using the default logical directory.";
190864
+ var AUDIT_WARNING_UNSUPPORTED_PLATFORM = "AUDIT_DISABLED_UNSUPPORTED_PLATFORM: Audit trace writing is unavailable on this platform.";
190865
+ var AUDIT_WARNING_UNSUPPORTED_CAPABILITY = "AUDIT_DISABLED_UNSUPPORTED_CAPABILITY: Audit trace writing requires unavailable filesystem capabilities.";
190866
+ var AUDIT_WARNING_UNSAFE_STATE_ROOT = "AUDIT_DISABLED_UNSAFE_STATE_ROOT: Audit state root could not be verified.";
190867
+ async function resolveAuditStateRoot(options) {
190868
+ const warnings = [];
190869
+ const logicalDirectory = validateAuditDirectory(options.directory, warnings);
190870
+ const platform = options.platform ?? process.platform;
190871
+ if (platform === "win32") {
190872
+ return { warnings: [...warnings, AUDIT_WARNING_UNSUPPORTED_PLATFORM] };
190873
+ }
190874
+ const uid = getCurrentUid(options.getuid);
190875
+ if (uid === undefined) {
190876
+ return { warnings: [...warnings, AUDIT_WARNING_UNSUPPORTED_CAPABILITY] };
190877
+ }
190878
+ try {
190879
+ const workspaceRoot = await realpath2(resolve5(options.cwd));
190880
+ const candidate = resolveStateBaseCandidate(options.env ?? process.env);
190881
+ if (!candidate)
190882
+ throw new Error("missing trusted state base");
190883
+ const stateBase = await ensureTrustedStateBase({
190884
+ candidate,
190885
+ workspaceRoot,
190886
+ uid
190887
+ });
190888
+ const kyosoRoot = await ensureTrustedDirectory({
190889
+ root: stateBase,
190890
+ segments: ["kyoso"],
190891
+ uid,
190892
+ workspaceRoot
190893
+ });
190894
+ return {
190895
+ stateBase,
190896
+ kyosoRoot,
190897
+ workspaceRoot,
190898
+ workspaceHash: createHash2("sha256").update(workspaceRoot).digest("hex"),
190899
+ logicalDirectory,
190900
+ uid,
190901
+ warnings
190902
+ };
190903
+ } catch {
190904
+ return { warnings: [...warnings, AUDIT_WARNING_UNSAFE_STATE_ROOT] };
190905
+ }
190906
+ }
190907
+ async function ensureTrustedDirectory(options) {
190908
+ const realRoot = await realpath2(options.root);
190909
+ assertSafeDirectory(realRoot, options.uid, await lstat(realRoot));
190910
+ if (options.workspaceRoot && isPathWithin(realRoot, options.workspaceRoot)) {
190911
+ throw new Error("trusted directory resolves inside workspace");
190912
+ }
190913
+ await assertTrustedAncestorChain(realRoot, options.uid);
190914
+ let current = realRoot;
190915
+ for (const segment of options.segments) {
190916
+ if (!isSafePathSegment(segment))
190917
+ throw new Error("unsafe path segment");
190918
+ const next = join3(current, segment);
190919
+ let entry = await optionalLstat(next);
190920
+ if (!entry) {
190921
+ await createDirectory(next);
190922
+ entry = await lstat(next);
190923
+ }
190924
+ assertSafeDirectory(next, options.uid, entry);
190925
+ const realNext = await realpath2(next);
190926
+ if (!isPathWithin(realNext, realRoot)) {
190927
+ throw new Error("managed directory escaped trusted root");
190928
+ }
190929
+ if (options.workspaceRoot && isPathWithin(realNext, options.workspaceRoot)) {
190930
+ throw new Error("managed directory resolves inside workspace");
190931
+ }
190932
+ current = realNext;
190933
+ }
190934
+ return current;
190935
+ }
190936
+ function isResolvedAuditStateRoot(resolution) {
190937
+ return "kyosoRoot" in resolution;
190938
+ }
190939
+ function validateAuditDirectory(directory, warnings) {
190940
+ try {
190941
+ if (directory.trim().length === 0 || isAbsolute2(directory) || directory.split(/[\\/]+/).includes("..")) {
190942
+ throw new Error("unsafe logical directory");
190943
+ }
190944
+ const normalized = normalizeRelativePath(directory);
190945
+ if (normalized === "." || normalized.split("/").includes("..")) {
190946
+ throw new Error("unsafe logical directory");
190947
+ }
190948
+ return normalized;
190949
+ } catch {
190950
+ warnings.push(AUDIT_WARNING_DIRECTORY_IGNORED);
190951
+ return TRACE_DIR;
190952
+ }
190953
+ }
190954
+ function resolveStateBaseCandidate(env) {
190955
+ const xdgStateHome = env.XDG_STATE_HOME?.trim();
190956
+ if (xdgStateHome && isAbsolute2(xdgStateHome)) {
190957
+ return resolve5(xdgStateHome);
190958
+ }
190959
+ const home = env.HOME?.trim();
190960
+ if (!home || !isAbsolute2(home))
190961
+ return;
190962
+ return join3(resolve5(home), ".local", "state");
190963
+ }
190964
+ async function ensureTrustedStateBase(options) {
190965
+ if (isPathWithin(options.candidate, options.workspaceRoot)) {
190966
+ throw new Error("state base is inside workspace");
190967
+ }
190968
+ const existing = await findExistingAncestor(options.candidate);
190969
+ assertSafeDirectory(existing.path, options.uid, await lstat(existing.path), {
190970
+ allowFilesystemRoot: true
190971
+ });
190972
+ const realExisting = await realpath2(existing.path);
190973
+ if (isPathWithin(realExisting, options.workspaceRoot)) {
190974
+ throw new Error("state base resolves inside workspace");
190975
+ }
190976
+ await assertTrustedAncestorChain(realExisting, options.uid);
190977
+ let current = realExisting;
190978
+ for (const segment of existing.missingSegments) {
190979
+ current = join3(current, segment);
190980
+ await createDirectory(current);
190981
+ const entry = await lstat(current);
190982
+ assertSafeDirectory(current, options.uid, entry);
190983
+ const realCurrent = await realpath2(current);
190984
+ if (!isPathWithin(realCurrent, realExisting)) {
190985
+ throw new Error("state base changed while being created");
190986
+ }
190987
+ if (isPathWithin(realCurrent, options.workspaceRoot)) {
190988
+ throw new Error("state base resolves inside workspace");
190989
+ }
190990
+ current = realCurrent;
190991
+ }
190992
+ const stateBase = await realpath2(current);
190993
+ assertSafeDirectory(stateBase, options.uid, await lstat(stateBase));
190994
+ if (isPathWithin(stateBase, options.workspaceRoot)) {
190995
+ throw new Error("state base resolves inside workspace");
190996
+ }
190997
+ await assertTrustedAncestorChain(stateBase, options.uid);
190998
+ return stateBase;
190999
+ }
191000
+ async function findExistingAncestor(candidate) {
191001
+ let current = resolve5(candidate);
191002
+ const missingSegments = [];
191003
+ while (true) {
191004
+ const entry = await optionalLstat(current);
191005
+ if (entry) {
191006
+ if (entry.isSymbolicLink() || !entry.isDirectory()) {
191007
+ throw new Error("state base ancestor is unsafe");
191008
+ }
191009
+ return { path: current, missingSegments };
191010
+ }
191011
+ const parent = dirname3(current);
191012
+ if (parent === current)
191013
+ throw new Error("state base has no existing ancestor");
191014
+ missingSegments.unshift(basename(current));
191015
+ current = parent;
191016
+ }
191017
+ }
191018
+ function assertSafeDirectory(path, uid, entry, options = {}) {
191019
+ const stat = entry;
191020
+ if (!stat || stat.isSymbolicLink() || !stat.isDirectory()) {
191021
+ throw new Error("state directory is unsafe");
191022
+ }
191023
+ if (numericStatValue(stat.uid) !== uid && !(options.allowFilesystemRoot && isFilesystemRoot(path))) {
191024
+ throw new Error("state directory owner is unsafe");
191025
+ }
191026
+ const mode = numericStatValue(stat.mode);
191027
+ if ((mode & UNSAFE_DIRECTORY_MODE) !== 0) {
191028
+ throw new Error("state directory mode is unsafe");
191029
+ }
191030
+ }
191031
+ function numericStatValue(value) {
191032
+ return typeof value === "number" ? value : Number(value);
191033
+ }
191034
+ function getCurrentUid(getuid) {
191035
+ const resolveUid = getuid ?? (typeof process.getuid === "function" ? process.getuid.bind(process) : undefined);
191036
+ try {
191037
+ const uid = resolveUid?.();
191038
+ return typeof uid === "number" && Number.isInteger(uid) && uid >= 0 ? uid : undefined;
191039
+ } catch {
191040
+ return;
191041
+ }
191042
+ }
191043
+ function isFilesystemRoot(path) {
191044
+ return dirname3(path) === path;
191045
+ }
191046
+ async function assertTrustedAncestorChain(path, uid) {
191047
+ let child = path;
191048
+ while (!isFilesystemRoot(child)) {
191049
+ const parent = dirname3(child);
191050
+ const [parentStat, childStat] = await Promise.all([
191051
+ lstat(parent),
191052
+ lstat(child)
191053
+ ]);
191054
+ if (parentStat.isSymbolicLink() || !parentStat.isDirectory()) {
191055
+ throw new Error("state directory ancestor is unsafe");
191056
+ }
191057
+ const parentMode = numericStatValue(parentStat.mode);
191058
+ const parentUid = numericStatValue(parentStat.uid);
191059
+ const childUid = numericStatValue(childStat.uid);
191060
+ const parentWritableByOthers = (parentMode & UNSAFE_DIRECTORY_MODE) !== 0;
191061
+ const stickyChildEntry = (parentMode & STICKY_DIRECTORY_MODE) !== 0 && childUid === uid;
191062
+ if (parentUid !== uid && parentUid !== 0 || parentWritableByOthers && !stickyChildEntry) {
191063
+ throw new Error("state directory ancestor permissions are unsafe");
191064
+ }
191065
+ child = parent;
191066
+ }
191067
+ }
191068
+ function isSafePathSegment(segment) {
191069
+ return segment.length > 0 && segment !== "." && segment !== ".." && !segment.includes("/") && !segment.includes("\\");
191070
+ }
191071
+ async function optionalLstat(path) {
191072
+ try {
191073
+ return await lstat(path);
191074
+ } catch (error51) {
191075
+ if (isMissingPathError3(error51))
191076
+ return;
191077
+ throw error51;
191078
+ }
191079
+ }
191080
+ async function createDirectory(path) {
191081
+ try {
191082
+ await mkdir2(path, { mode: PRIVATE_DIRECTORY_MODE });
191083
+ } catch (error51) {
191084
+ if (!isAlreadyExistsError(error51))
191085
+ throw error51;
191086
+ }
191087
+ }
191088
+ function isMissingPathError3(error51) {
191089
+ return typeof error51 === "object" && error51 !== null && "code" in error51 && error51.code === "ENOENT";
191090
+ }
191091
+ function isAlreadyExistsError(error51) {
191092
+ return typeof error51 === "object" && error51 !== null && "code" in error51 && error51.code === "EEXIST";
191093
+ }
191094
+
191095
+ // src/audit/safeTraceFile.ts
191096
+ import { constants } from "node:fs";
191097
+ import { lstat as lstat2, open, realpath as realpath3, stat } from "node:fs/promises";
191098
+ import { join as join4 } from "node:path";
191099
+ var AUDIT_WARNING_UNSUPPORTED_OPEN_CAPABILITY = "AUDIT_DISABLED_UNSUPPORTED_CAPABILITY: Audit trace writing requires unavailable filesystem capabilities.";
191100
+ async function openVerifiedTraceFile(options) {
191101
+ const flags = secureOpenFlags(options.openConstants);
191102
+ if (flags === undefined)
191103
+ throw new Error("secure open capability unavailable");
191104
+ if (!isSafeTraceId(options.traceId))
191105
+ throw new Error("unsafe trace id");
191106
+ const traceDirectory = await ensureTrustedDirectory({
191107
+ root: options.kyosoRoot,
191108
+ segments: [
191109
+ "workspaces",
191110
+ options.workspaceHash,
191111
+ ...options.logicalDirectory.split("/"),
191112
+ options.date
191113
+ ],
191114
+ uid: options.uid,
191115
+ workspaceRoot: options.workspaceRoot
191116
+ });
191117
+ const tracePath = join4(traceDirectory, `${options.traceId}.jsonl`);
191118
+ if (await optionalLstat2(tracePath)) {
191119
+ throw new Error("trace path already exists");
191120
+ }
191121
+ await options.beforeOpen?.(tracePath);
191122
+ const handle = await open(tracePath, flags, 384);
191123
+ try {
191124
+ const [handleStat, pathStat, realTracePath] = await Promise.all([
191125
+ handle.stat({ bigint: true }),
191126
+ stat(tracePath, { bigint: true }),
191127
+ realpath3(tracePath)
191128
+ ]);
191129
+ if (!handleStat.isFile() || !pathStat.isFile() || handleStat.dev !== pathStat.dev || handleStat.ino !== pathStat.ino || !isPathWithin(realTracePath, options.kyosoRoot)) {
191130
+ throw new Error("trace file identity could not be verified");
191131
+ }
191132
+ return { handle, tracePath };
191133
+ } catch (error51) {
191134
+ try {
191135
+ await handle.close();
191136
+ } catch {}
191137
+ throw error51;
191138
+ }
191139
+ }
191140
+ function secureOpenFlags(provided) {
191141
+ const openConstants = provided ?? constants;
191142
+ const required2 = [
191143
+ openConstants.O_CREAT,
191144
+ openConstants.O_EXCL,
191145
+ openConstants.O_APPEND,
191146
+ openConstants.O_WRONLY,
191147
+ openConstants.O_NOFOLLOW,
191148
+ openConstants.O_NONBLOCK
191149
+ ];
191150
+ if (required2.some((flag) => typeof flag !== "number" || flag <= 0)) {
191151
+ return;
191152
+ }
191153
+ return required2.reduce((combined, flag) => combined | flag, 0);
191154
+ }
191155
+ function isSafeTraceId(traceId) {
191156
+ return traceId.length > 0 && traceId !== "." && traceId !== ".." && !traceId.includes("/") && !traceId.includes("\\");
191157
+ }
191158
+ async function optionalLstat2(path) {
191159
+ try {
191160
+ return await lstat2(path);
191161
+ } catch (error51) {
191162
+ if (typeof error51 === "object" && error51 !== null && "code" in error51 && error51.code === "ENOENT") {
191163
+ return;
191164
+ }
191165
+ throw error51;
191166
+ }
191167
+ }
191168
+
190689
191169
  // src/audit/sanitize.ts
190690
191170
  function sanitizeForAudit(value, options = {}) {
190691
191171
  if (typeof value === "string")
@@ -190706,46 +191186,149 @@ function sanitizeForAudit(value, options = {}) {
190706
191186
  }
190707
191187
 
190708
191188
  // src/audit/trace.ts
191189
+ var AUDIT_WARNING_WRITE_FAILED = "AUDIT_WRITE_FAILED: Audit trace writing failed; no further audit events will be written.";
191190
+ var AUDIT_WARNING_FINALIZE_FAILED = "AUDIT_FINALIZE_FAILED: Audit trace close failed.";
191191
+ var AUDIT_WARNING_WRITE_AFTER_FINALIZE = "AUDIT_WRITE_AFTER_FINALIZE: Audit trace is already finalized.";
190709
191192
  function createTraceWriter(options) {
190710
191193
  const warnings = [];
190711
- if (!options.enabled) {
190712
- return {
190713
- warnings,
190714
- async write() {
190715
- return;
190716
- }
190717
- };
190718
- }
191194
+ const warningSet = new Set;
190719
191195
  const date5 = new Date().toISOString().slice(0, 10);
190720
- const directory = validateAuditDirectory(options.directory, warnings);
190721
- const tracePath = join3(options.cwd, directory, date5, `${options.traceId}.jsonl`);
191196
+ let tracePath;
191197
+ let handle;
191198
+ let disabled = !options.enabled;
191199
+ let finalizing = false;
191200
+ let finalized = false;
191201
+ let queue = Promise.resolve();
191202
+ let finalizePromise;
191203
+ const addWarning = (warning) => {
191204
+ if (warningSet.has(warning))
191205
+ return;
191206
+ warningSet.add(warning);
191207
+ warnings.push(warning);
191208
+ };
191209
+ const closeHandle = async () => {
191210
+ const current = handle;
191211
+ handle = undefined;
191212
+ if (!current)
191213
+ return;
191214
+ try {
191215
+ await (options.closeHandle ?? defaultCloseHandle)(current);
191216
+ } catch {
191217
+ addWarning(AUDIT_WARNING_FINALIZE_FAILED);
191218
+ }
191219
+ };
191220
+ const disableAfterWriteFailure = async () => {
191221
+ disabled = true;
191222
+ addWarning(AUDIT_WARNING_WRITE_FAILED);
191223
+ await closeHandle();
191224
+ };
191225
+ const openIfNeeded = async () => {
191226
+ if (handle || disabled)
191227
+ return;
191228
+ if (secureOpenFlags(options.openConstants) === undefined) {
191229
+ disabled = true;
191230
+ addWarning(AUDIT_WARNING_UNSUPPORTED_OPEN_CAPABILITY);
191231
+ return;
191232
+ }
191233
+ const stateRoot = await resolveAuditStateRoot({
191234
+ cwd: options.cwd,
191235
+ directory: options.directory,
191236
+ env: options.env,
191237
+ platform: options.platform,
191238
+ getuid: options.getuid
191239
+ });
191240
+ for (const warning of stateRoot.warnings)
191241
+ addWarning(warning);
191242
+ if (!isResolvedAuditStateRoot(stateRoot)) {
191243
+ disabled = true;
191244
+ return;
191245
+ }
191246
+ try {
191247
+ const opened = await openVerifiedTraceFile({
191248
+ kyosoRoot: stateRoot.kyosoRoot,
191249
+ workspaceHash: stateRoot.workspaceHash,
191250
+ logicalDirectory: stateRoot.logicalDirectory,
191251
+ date: date5,
191252
+ traceId: options.traceId,
191253
+ uid: stateRoot.uid,
191254
+ workspaceRoot: stateRoot.workspaceRoot,
191255
+ openConstants: options.openConstants,
191256
+ beforeOpen: options.beforeOpen
191257
+ });
191258
+ handle = opened.handle;
191259
+ tracePath = opened.tracePath;
191260
+ } catch {
191261
+ disabled = true;
191262
+ addWarning(AUDIT_WARNING_WRITE_FAILED);
191263
+ }
191264
+ };
191265
+ const writeOne = async (event) => {
191266
+ if (disabled)
191267
+ return;
191268
+ try {
191269
+ await openIfNeeded();
191270
+ if (disabled || !handle)
191271
+ return;
191272
+ const line = Buffer.from(`${JSON.stringify(sanitizeForAudit(event, {
191273
+ includeRawAgentOutput: options.includeRawAgentOutput
191274
+ }))}
191275
+ `, "utf8");
191276
+ await writeFully(handle, line, options.writeChunk);
191277
+ } catch {
191278
+ await disableAfterWriteFailure();
191279
+ }
191280
+ };
190722
191281
  return {
190723
- tracePath,
191282
+ get tracePath() {
191283
+ return tracePath;
191284
+ },
190724
191285
  warnings,
190725
- async write(event) {
190726
- try {
190727
- await mkdir2(dirname3(tracePath), { recursive: true });
190728
- await appendFile(tracePath, `${JSON.stringify(sanitizeForAudit(event, {
190729
- includeRawAgentOutput: options.includeRawAgentOutput
190730
- }))}
190731
- `, "utf8");
190732
- } catch (error51) {
190733
- warnings.push(`Audit write failed: ${error51 instanceof Error ? error51.message : String(error51)}`);
190734
- }
191286
+ write(event) {
191287
+ if (finalizing || finalized) {
191288
+ addWarning(AUDIT_WARNING_WRITE_AFTER_FINALIZE);
191289
+ return Promise.resolve();
191290
+ }
191291
+ const task = queue.then(() => writeOne(event));
191292
+ queue = task.catch(async () => {
191293
+ await disableAfterWriteFailure();
191294
+ });
191295
+ return task.catch(() => {
191296
+ return;
191297
+ });
191298
+ },
191299
+ finalize() {
191300
+ if (finalizePromise)
191301
+ return finalizePromise;
191302
+ finalizing = true;
191303
+ finalizePromise = queue.then(async () => {
191304
+ await closeHandle();
191305
+ finalized = true;
191306
+ }).catch(() => {
191307
+ disabled = true;
191308
+ addWarning(AUDIT_WARNING_FINALIZE_FAILED);
191309
+ finalized = true;
191310
+ });
191311
+ return finalizePromise;
190735
191312
  }
190736
191313
  };
190737
191314
  }
190738
- function validateAuditDirectory(directory, warnings) {
190739
- try {
190740
- if (isAbsolute2(directory) || directory.split(/[\\/]+/).includes("..")) {
190741
- throw new Error("unsafe path");
191315
+ async function writeFully(handle, buffer, writeChunk) {
191316
+ let offset = 0;
191317
+ while (offset < buffer.byteLength) {
191318
+ const bytesWritten = await (writeChunk ?? defaultWriteChunk)(handle, buffer, offset);
191319
+ if (!Number.isInteger(bytesWritten) || bytesWritten <= 0) {
191320
+ throw new Error("partial audit write could not advance");
190742
191321
  }
190743
- return normalizeRelativePath(directory);
190744
- } catch {
190745
- warnings.push(`Unsafe audit directory ignored: ${directory}`);
190746
- return ".kyoso/traces";
191322
+ offset += bytesWritten;
190747
191323
  }
190748
191324
  }
191325
+ async function defaultWriteChunk(handle, buffer, offset) {
191326
+ const { bytesWritten } = await handle.write(buffer, offset, buffer.byteLength - offset, null);
191327
+ return bytesWritten;
191328
+ }
191329
+ async function defaultCloseHandle(handle) {
191330
+ await handle.close();
191331
+ }
190749
191332
 
190750
191333
  // src/context/truncate.ts
190751
191334
  function truncateUtf8(input, maxBytes) {
@@ -191019,7 +191602,7 @@ function parseJudgeOutput(text, fallbackSummaryText) {
191019
191602
  const parsed = JSON.parse(json2);
191020
191603
  const summaryText = typeof parsed.summaryText === "string" && parsed.summaryText.trim().length > 0 ? sanitizeText(parsed.summaryText) : fallbackSummaryText;
191021
191604
  const disagreementComments = Array.isArray(parsed.disagreementComments) ? parsed.disagreementComments.flatMap((item) => {
191022
- if (!isRecord6(item) || typeof item.topic !== "string" || typeof item.judgeComment !== "string") {
191605
+ if (!isRecord7(item) || typeof item.topic !== "string" || typeof item.judgeComment !== "string") {
191023
191606
  return [];
191024
191607
  }
191025
191608
  return [
@@ -191035,7 +191618,7 @@ function parseJudgeOutput(text, fallbackSummaryText) {
191035
191618
  return { summaryText, disagreementComments, analysis };
191036
191619
  }
191037
191620
  function parseAnalysis(value) {
191038
- if (!isRecord6(value))
191621
+ if (!isRecord7(value))
191039
191622
  return;
191040
191623
  if (!Array.isArray(value.blindSpots) || !Array.isArray(value.contradictions) || !Array.isArray(value.partialCoverage)) {
191041
191624
  return;
@@ -191043,7 +191626,7 @@ function parseAnalysis(value) {
191043
191626
  return {
191044
191627
  blindSpots: value.blindSpots.slice(0, ANALYSIS_MAX_ITEMS).flatMap((item) => typeof item === "string" ? [sanitizeAnalysisText(item)] : []),
191045
191628
  contradictions: value.contradictions.slice(0, ANALYSIS_MAX_ITEMS).flatMap((item) => {
191046
- if (!isRecord6(item) || typeof item.topic !== "string" || typeof item.detail !== "string") {
191629
+ if (!isRecord7(item) || typeof item.topic !== "string" || typeof item.detail !== "string") {
191047
191630
  return [];
191048
191631
  }
191049
191632
  return [
@@ -191054,7 +191637,7 @@ function parseAnalysis(value) {
191054
191637
  ];
191055
191638
  }),
191056
191639
  partialCoverage: value.partialCoverage.slice(0, ANALYSIS_MAX_ITEMS).flatMap((item) => {
191057
- if (!isRecord6(item) || typeof item.note !== "string")
191640
+ if (!isRecord7(item) || typeof item.note !== "string")
191058
191641
  return [];
191059
191642
  const findingId = typeof item.findingId === "string" ? sanitizeAnalysisText(item.findingId) : undefined;
191060
191643
  return [
@@ -191101,7 +191684,7 @@ function extractFirstJsonObject2(text) {
191101
191684
  }
191102
191685
  return;
191103
191686
  }
191104
- function isRecord6(value) {
191687
+ function isRecord7(value) {
191105
191688
  return typeof value === "object" && value !== null && !Array.isArray(value);
191106
191689
  }
191107
191690
 
@@ -191437,12 +192020,12 @@ function decide(input) {
191437
192020
 
191438
192021
  // src/workspace/createSnapshot.ts
191439
192022
  import { chmod, mkdir as mkdir3, mkdtemp, writeFile as writeFile2 } from "node:fs/promises";
191440
- import { dirname as dirname4, join as join4 } from "node:path";
192023
+ import { dirname as dirname4, join as join5 } from "node:path";
191441
192024
  import { tmpdir } from "node:os";
191442
192025
  async function createSnapshot(traceId, tool, request, options = {}) {
191443
- const root = await mkdtemp(join4(tmpdir(), `kyoso-${traceId}-`));
191444
- const repoDir = join4(root, "repo");
191445
- const contextDir = join4(root, "context");
192026
+ const root = await mkdtemp(join5(tmpdir(), `kyoso-${traceId}-`));
192027
+ const repoDir = join5(root, "repo");
192028
+ const contextDir = join5(root, "context");
191446
192029
  await mkdir3(repoDir, { recursive: true });
191447
192030
  await mkdir3(contextDir, { recursive: true });
191448
192031
  let fileCount = 0;
@@ -191452,7 +192035,7 @@ async function createSnapshot(traceId, tool, request, options = {}) {
191452
192035
  continue;
191453
192036
  if (!isAllowedPath(relative2, options.allowPatterns ?? []))
191454
192037
  continue;
191455
- const dest = join4(repoDir, relative2);
192038
+ const dest = join5(repoDir, relative2);
191456
192039
  await mkdir3(dirname4(dest), { recursive: true });
191457
192040
  await writeFile2(dest, file2.content, "utf8");
191458
192041
  await chmod(dest, 292).catch(() => {
@@ -191460,16 +192043,16 @@ async function createSnapshot(traceId, tool, request, options = {}) {
191460
192043
  });
191461
192044
  fileCount += 1;
191462
192045
  }
191463
- await writeFile2(join4(contextDir, "request.json"), JSON.stringify(stripContents(request), null, 2), "utf8");
191464
- await writeFile2(join4(contextDir, "selected_files_manifest.json"), JSON.stringify(buildSelectedFilesManifest(request), null, 2), "utf8");
191465
- await writeFile2(join4(contextDir, "instructions.codex.md"), buildAgentPrompt(tool, request, "codex", options.agentRoles?.codex ?? "implementation_reviewer"), "utf8");
191466
- await writeFile2(join4(contextDir, "instructions.claude.md"), buildAgentPrompt(tool, request, "claude", options.agentRoles?.claude ?? "architecture_security_reviewer"), "utf8");
192046
+ await writeFile2(join5(contextDir, "request.json"), JSON.stringify(stripContents(request), null, 2), "utf8");
192047
+ await writeFile2(join5(contextDir, "selected_files_manifest.json"), JSON.stringify(buildSelectedFilesManifest(request), null, 2), "utf8");
192048
+ await writeFile2(join5(contextDir, "instructions.codex.md"), buildAgentPrompt(tool, request, "codex", options.agentRoles?.codex ?? "implementation_reviewer"), "utf8");
192049
+ await writeFile2(join5(contextDir, "instructions.claude.md"), buildAgentPrompt(tool, request, "claude", options.agentRoles?.claude ?? "architecture_security_reviewer"), "utf8");
191467
192050
  if (request.repoSummary)
191468
- await writeFile2(join4(contextDir, "repo_summary.md"), request.repoSummary, "utf8");
192051
+ await writeFile2(join5(contextDir, "repo_summary.md"), request.repoSummary, "utf8");
191469
192052
  if (request.currentPlan)
191470
- await writeFile2(join4(contextDir, "current_plan.md"), request.currentPlan, "utf8");
192053
+ await writeFile2(join5(contextDir, "current_plan.md"), request.currentPlan, "utf8");
191471
192054
  if (request.diff?.unifiedDiff)
191472
- await writeFile2(join4(contextDir, "diff.patch"), request.diff.unifiedDiff, "utf8");
192055
+ await writeFile2(join5(contextDir, "diff.patch"), request.diff.unifiedDiff, "utf8");
191473
192056
  return { root, repoDir, contextDir, fileCount };
191474
192057
  }
191475
192058
  function stripContents(request) {
@@ -191559,7 +192142,7 @@ function parseVerificationVerdicts(rawText) {
191559
192142
  if (!Array.isArray(parsed.verdicts))
191560
192143
  return;
191561
192144
  return parsed.verdicts.flatMap((item) => {
191562
- if (!isRecord7(item))
192145
+ if (!isRecord8(item))
191563
192146
  return [];
191564
192147
  if (typeof item.findingId !== "string")
191565
192148
  return [];
@@ -191637,7 +192220,7 @@ function verificationNote(reasoning) {
191637
192220
  function isVerdict(value) {
191638
192221
  return value === "confirmed" || value === "refuted" || value === "uncertain";
191639
192222
  }
191640
- function isRecord7(value) {
192223
+ function isRecord8(value) {
191641
192224
  return typeof value === "object" && value !== null && !Array.isArray(value);
191642
192225
  }
191643
192226
 
@@ -191646,47 +192229,54 @@ async function runReview(tool, request, options = {}) {
191646
192229
  const cwd = options.cwd ?? process.cwd();
191647
192230
  const traceId = newTraceId();
191648
192231
  const startedAt = new Date().toISOString();
192232
+ const auditEnv = { ...process.env, ...options.env };
192233
+ const traceWriterFactory = options.traceWriterFactory ?? createTraceWriter;
191649
192234
  let snapshot;
191650
192235
  try {
191651
192236
  assertNotChildAgent(options.env ?? process.env);
191652
192237
  } catch (error51) {
191653
192238
  if (error51 instanceof KyosoRequestError) {
191654
192239
  const config2 = kyosoConfigSchema.parse(defaultConfig);
191655
- const trace2 = createTraceWriter({
192240
+ const trace2 = traceWriterFactory({
191656
192241
  enabled: config2.audit.enabled,
191657
192242
  directory: config2.audit.directory,
191658
192243
  traceId,
191659
- cwd
191660
- });
191661
- await trace2.write({
191662
- type: "request_received",
191663
- traceId,
191664
- tool,
191665
- timestamp: new Date().toISOString()
191666
- });
191667
- return await buildPolicyBlockResult({
191668
- tool,
191669
- trace: trace2,
191670
- traceId,
191671
- startedAt,
191672
- networkMode: config2.network.defaultMode,
191673
- warning: error51.message,
191674
- finding: {
191675
- id: "KYOSO-1",
191676
- severity: "critical",
191677
- category: "other",
191678
- title: "Recursive Kyoso invocation blocked",
191679
- evidence: error51.message,
191680
- recommendation: "Do not expose Kyoso MCP tools to Kyoso child agents.",
191681
- sourceAgents: ["kyoso_policy"],
191682
- confidence: "high"
191683
- },
191684
- redactionsApplied: 0
192244
+ cwd,
192245
+ env: auditEnv
191685
192246
  });
192247
+ try {
192248
+ await trace2.write({
192249
+ type: "request_received",
192250
+ traceId,
192251
+ tool,
192252
+ timestamp: new Date().toISOString()
192253
+ });
192254
+ return await buildPolicyBlockResult({
192255
+ tool,
192256
+ trace: trace2,
192257
+ traceId,
192258
+ startedAt,
192259
+ networkMode: config2.network.defaultMode,
192260
+ warning: error51.message,
192261
+ finding: {
192262
+ id: "KYOSO-1",
192263
+ severity: "critical",
192264
+ category: "other",
192265
+ title: "Recursive Kyoso invocation blocked",
192266
+ evidence: error51.message,
192267
+ recommendation: "Do not expose Kyoso MCP tools to Kyoso child agents.",
192268
+ sourceAgents: ["kyoso_policy"],
192269
+ confidence: "high"
192270
+ },
192271
+ redactionsApplied: 0
192272
+ });
192273
+ } finally {
192274
+ await trace2.finalize();
192275
+ }
191686
192276
  }
191687
192277
  throw error51;
191688
192278
  }
191689
- const loaded = options.config !== undefined ? {
192279
+ const baseLoaded = options.config !== undefined ? {
191690
192280
  config: options.config,
191691
192281
  configHash: options.configHash,
191692
192282
  configTrustStatus: "trusted",
@@ -191703,12 +192293,17 @@ async function runReview(tool, request, options = {}) {
191703
192293
  env: options.env,
191704
192294
  trustPrompt: options.trustPrompt
191705
192295
  });
191706
- const trace = createTraceWriter({
192296
+ const loaded = options.configOverrides && options.configOverrides.length > 0 ? {
192297
+ ...baseLoaded,
192298
+ config: applyConfigOverrides(baseLoaded.config, options.configOverrides)
192299
+ } : baseLoaded;
192300
+ const trace = traceWriterFactory({
191707
192301
  enabled: loaded.config.audit.enabled,
191708
192302
  directory: loaded.config.audit.directory,
191709
192303
  traceId,
191710
192304
  cwd,
191711
- includeRawAgentOutput: loaded.config.audit.includeRawAgentOutput
192305
+ includeRawAgentOutput: loaded.config.audit.includeRawAgentOutput,
192306
+ env: auditEnv
191712
192307
  });
191713
192308
  const warnings = [...loaded.warnings, ...trace.warnings];
191714
192309
  try {
@@ -191790,11 +192385,11 @@ async function runReview(tool, request, options = {}) {
191790
192385
  manager,
191791
192386
  trace
191792
192387
  });
191793
- warnings.push(...agentResults.flatMap((result2) => (result2.warnings ?? []).map((warning) => `Agent ${result2.agent} ${warning}`)));
192388
+ warnings.push(...agentResults.flatMap((result) => (result.warnings ?? []).map((warning) => `Agent ${result.agent} ${warning}`)));
191794
192389
  const normalizedAgentResults = agentResults.map(normalizeAgentRunResult);
191795
- const agentsUsed = normalizedAgentResults.map((result2) => result2.agent);
192390
+ const agentsUsed = normalizedAgentResults.map((result) => result.agent);
191796
192391
  const reviewMode = agentsUsed.length === 1 ? "single_agent" : "multi_agent";
191797
- const completed = normalizedAgentResults.filter((result2) => result2.status === "completed");
192392
+ const completed = normalizedAgentResults.filter((result) => result.status === "completed");
191798
192393
  const degraded = completed.length !== agentResults.length;
191799
192394
  let aggregate = aggregateAgentResults(normalizedAgentResults, {
191800
192395
  reviewMode
@@ -191821,7 +192416,7 @@ async function runReview(tool, request, options = {}) {
191821
192416
  severity: "critical",
191822
192417
  category: "other",
191823
192418
  title: "All backend agents failed",
191824
- evidence: normalizedAgentResults.map((result2) => `${result2.agent}: ${result2.error?.code ?? result2.status}`).join("; "),
192419
+ evidence: normalizedAgentResults.map((result) => `${result.agent}: ${result.error?.code ?? result.status}`).join("; "),
191825
192420
  recommendation: "Run kyoso doctor and retry after agent authentication or adapter issues are fixed.",
191826
192421
  sourceAgents: ["kyoso_policy"],
191827
192422
  confidence: "high"
@@ -191871,7 +192466,7 @@ async function runReview(tool, request, options = {}) {
191871
192466
  residualRisks: tool === "security_review" && aggregate.residualRisks.length === 0 ? [
191872
192467
  "No residual risks were reported by completed agents; verify security assumptions before release."
191873
192468
  ] : aggregate.residualRisks,
191874
- agentOpinions: normalizedAgentResults.map((result2) => agentOpinionSummary(result2, request.options?.includeAgentRawOutputs === true)),
192469
+ agentOpinions: normalizedAgentResults.map((result) => agentOpinionSummary(result, request.options?.includeAgentRawOutputs === true)),
191875
192470
  audit: {
191876
192471
  traceId,
191877
192472
  startedAt,
@@ -191903,15 +192498,10 @@ async function runReview(tool, request, options = {}) {
191903
192498
  judgeComment: judgeComments.get(disagreement.topic) ?? disagreement.judgeComment
191904
192499
  }));
191905
192500
  const crossModelAnalysis = buildCrossModelAnalysis(judge, reviewMode);
191906
- const result = {
192501
+ const resultAfterJudge = {
191907
192502
  ...resultWithoutMarkdown,
191908
192503
  disagreements,
191909
- ...crossModelAnalysis ? { crossModelAnalysis } : {},
191910
- summaryMarkdown: renderMarkdownResult(tool, {
191911
- ...resultWithoutMarkdown,
191912
- disagreements,
191913
- ...crossModelAnalysis ? { crossModelAnalysis } : {}
191914
- }, { summaryText: judge.output.summaryText })
192504
+ ...crossModelAnalysis ? { crossModelAnalysis } : {}
191915
192505
  };
191916
192506
  const judgeEvent = {
191917
192507
  type: "judge_completed",
@@ -191923,7 +192513,7 @@ async function runReview(tool, request, options = {}) {
191923
192513
  if (judge.error)
191924
192514
  judgeEvent.error = judge.error;
191925
192515
  await trace.write(judgeEvent);
191926
- result.audit.completedAt = new Date().toISOString();
192516
+ resultAfterJudge.audit.completedAt = new Date().toISOString();
191927
192517
  await trace.write({
191928
192518
  type: "decision_completed",
191929
192519
  traceId,
@@ -191935,8 +192525,14 @@ async function runReview(tool, request, options = {}) {
191935
192525
  traceId,
191936
192526
  timestamp: new Date().toISOString()
191937
192527
  });
191938
- return result;
192528
+ return await finalizeReviewResult({
192529
+ tool,
192530
+ trace,
192531
+ result: resultAfterJudge,
192532
+ summaryText: judge.output.summaryText
192533
+ });
191939
192534
  } finally {
192535
+ await trace.finalize();
191940
192536
  if (snapshot)
191941
192537
  await cleanupSnapshot(snapshot.root);
191942
192538
  }
@@ -192185,10 +192781,6 @@ async function buildSecretBlockResult(input) {
192185
192781
  warnings: input.warnings
192186
192782
  }
192187
192783
  };
192188
- const result = {
192189
- ...resultWithoutMarkdown,
192190
- summaryMarkdown: renderMarkdownResult(input.tool, resultWithoutMarkdown)
192191
- };
192192
192784
  await input.trace.write({
192193
192785
  type: "decision_completed",
192194
192786
  traceId: input.traceId,
@@ -192200,7 +192792,11 @@ async function buildSecretBlockResult(input) {
192200
192792
  traceId: input.traceId,
192201
192793
  timestamp: new Date().toISOString()
192202
192794
  });
192203
- return result;
192795
+ return await finalizeReviewResult({
192796
+ tool: input.tool,
192797
+ trace: input.trace,
192798
+ result: resultWithoutMarkdown
192799
+ });
192204
192800
  }
192205
192801
  function buildSecretFinding(secretScan, options) {
192206
192802
  return {
@@ -192250,10 +192846,6 @@ async function buildPolicyBlockResult(input) {
192250
192846
  warnings: [input.warning]
192251
192847
  }
192252
192848
  };
192253
- const result = {
192254
- ...resultWithoutMarkdown,
192255
- summaryMarkdown: renderMarkdownResult(input.tool, resultWithoutMarkdown)
192256
- };
192257
192849
  await input.trace.write({
192258
192850
  type: "decision_completed",
192259
192851
  traceId: input.traceId,
@@ -192265,7 +192857,30 @@ async function buildPolicyBlockResult(input) {
192265
192857
  traceId: input.traceId,
192266
192858
  timestamp: new Date().toISOString()
192267
192859
  });
192268
- return result;
192860
+ return await finalizeReviewResult({
192861
+ tool: input.tool,
192862
+ trace: input.trace,
192863
+ result: resultWithoutMarkdown
192864
+ });
192865
+ }
192866
+ async function finalizeReviewResult(input) {
192867
+ await input.trace.finalize();
192868
+ const result = {
192869
+ ...input.result,
192870
+ audit: {
192871
+ ...input.result.audit,
192872
+ warnings: Array.from(new Set([
192873
+ ...input.result.audit.warnings ?? [],
192874
+ ...input.trace.warnings
192875
+ ]))
192876
+ }
192877
+ };
192878
+ return {
192879
+ ...result,
192880
+ summaryMarkdown: renderMarkdownResult(input.tool, result, {
192881
+ summaryText: input.summaryText
192882
+ })
192883
+ };
192269
192884
  }
192270
192885
  function mergeDenyPatterns(configDeny, requestDeny) {
192271
192886
  return Array.from(new Set([...configDeny, ...requestDeny ?? []]));
@@ -192273,7 +192888,7 @@ function mergeDenyPatterns(configDeny, requestDeny) {
192273
192888
  function assertTrustedWorkspaceRoot(requestRoot, configRoot, cwd) {
192274
192889
  if (!requestRoot)
192275
192890
  return;
192276
- if (resolve4(cwd, requestRoot) !== resolve4(cwd, configRoot)) {
192891
+ if (resolve6(cwd, requestRoot) !== resolve6(cwd, configRoot)) {
192277
192892
  throw new KyosoRequestError("workspace.root is not trusted by config", "UNTRUSTED_WORKSPACE_ROOT");
192278
192893
  }
192279
192894
  }