@kyo-so/cli 0.7.1 → 0.8.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.
@@ -29,10 +29,19 @@ Do not use this skill for every coding task. It is intended for deliberate revie
29
29
  - selected files
30
30
  - unified diff if available
31
31
  - constraints
32
- 4. Call the appropriate Kyoso MCP tool:
33
- - `plan_review`
34
- - `security_review`
35
- - `diff_review`
32
+ 4. Run the review through the first available path:
33
+ - Prefer the corresponding Kyoso MCP tool when it is available:
34
+ - `plan_review`
35
+ - `security_review`
36
+ - `diff_review`
37
+ - If the MCP tools are unavailable, use the CLI fallback with JSON output:
38
+ - `plan_review` -> `npx -y @kyo-so/cli plan --goal <text> [--plan <path-or-text>] [--file <path>] --json`
39
+ - `security_review` -> `npx -y @kyo-so/cli security --goal <text> [--diff <path>] [--file <path>] --json`
40
+ - `diff_review` -> `npx -y @kyo-so/cli diff --base <ref> --head <ref> --json`
41
+ - `bunx @kyo-so/cli` may be used instead of `npx -y @kyo-so/cli`.
42
+ - The CLI also accepts `--repo-summary`, repeatable `--constraint`, and repeatable `--file` flags. For a large review, adjust an agent timeout with `--set agents.<agent>.timeoutMs=<ms>`.
43
+ - Run the CLI without a config trust flag first. Inspect `audit.warnings` in the JSON result; if it contains `untrusted config was not executed`, or the command fails with an untrusted-config message, ask the user whether to rerun with `--trust-config` to use it or `--ignore-config` to skip it. Never add `--trust-config` without confirmation.
44
+ - Keep `--json` enabled and interpret the returned `decision` exactly like the MCP result.
36
45
  5. Treat `decision: block` as a stop signal. Present the result to the user before implementing.
37
46
  6. Treat `decision: approve_with_changes` as requiring changes to the plan or implementation.
38
47
  7. Do not claim Kyoso modified files. Kyoso only reviews.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.8.0] - 2026-07-10
11
+
12
+ ### Added
13
+
14
+ - Repeatable `--set <config-key>=<value>` option on the `plan`, `security`,
15
+ and `diff` commands for overriding config values such as
16
+ `agents.<agent>.model`, `agents.<agent>.effort`, and `timeoutMs` from the
17
+ command line. Overrides are restricted to the shared project-scope
18
+ allowlist, applied after config files (including with `--ignore-config`),
19
+ and schema-validated.
20
+ - CLI fallback in the bundled `kyoso-review` skill: when the Kyoso MCP
21
+ server is not registered, the skill falls back to
22
+ `npx`/`bunx @kyo-so/cli plan|security|diff --json`. The fallback runs
23
+ without config trust flags first and requires user confirmation before
24
+ `--trust-config` or `--ignore-config`. Documented in all README languages.
25
+
10
26
  ## [0.7.1] - 2026-07-09
11
27
 
12
28
  ### Changed
package/README.ja.md CHANGED
@@ -136,7 +136,7 @@ Nix dev shell は Node.js 24 と nixpkgs が提供する Bun version を固定
136
136
  ```bash
137
137
  kyoso plan --goal "Review this OAuth callback plan" --plan plan.md
138
138
  kyoso security --goal "Review this auth diff" --diff changes.patch
139
- kyoso diff --base main --head HEAD
139
+ kyoso diff --base main --head HEAD --set agents.claude.effort=high
140
140
  kyoso doctor
141
141
  kyoso init
142
142
  kyoso setup codex
@@ -203,6 +203,8 @@ MCP stdout は protocol messages 専用です。logs は stderr または local
203
203
 
204
204
  同梱の `kyoso-review` skill は意図的に狭い用途にしています。Kyoso、multi-agent review、plan review、security review、CISA Secure by Design review、diff review を明示的に依頼したときだけ trigger されるべきです。
205
205
 
206
+ skill は、利用可能な場合は Kyoso MCP tools を優先します。MCP server が未登録の場合は、記載された CLI fallback に従い、`npx -y @kyo-so/cli` または `bunx @kyo-so/cli` から JSON output を取得します。
207
+
206
208
  `npx @kyo-so/cli setup codex --write` と `bunx @kyo-so/cli setup codex --write` は、既定で `.agents/skills/kyoso-review/` にコピーします。`--global` を追加すると `~/.agents/skills/kyoso-review/` にコピーします。
207
209
 
208
210
  `npx @kyo-so/cli setup claude-code --write` と `bunx @kyo-so/cli setup claude-code --write` は、既定で `.claude/skills/kyoso-review/` にコピーします。`--global` を追加すると `~/.claude/skills/kyoso-review/` にコピーします。
@@ -281,7 +283,15 @@ Kyoso は次の順に config を load します。
281
283
  - built-in defaults
282
284
  - user global TOML: `$XDG_CONFIG_HOME/kyoso/config.toml`、または `~/.config/kyoso/config.toml`
283
285
  - project TOML: `<cwd>/kyoso.toml`
284
- - `--network` などの CLI flags
286
+ - `--network` などの CLI flags と `--set agents.claude.effort=high` などの反復可能な overrides
287
+
288
+ `plan`、`security`、`diff` は、反復可能な `--set <key>=<value>` overrides を受け付けます。CLI で指定した値は config files より優先され、`--ignore-config` との併用も可能です。
289
+
290
+ - Agent keys: `agents.<codex|claude>.<enabled|model|effort|role|timeoutMs>`
291
+ - Verification keys: `verification.<enabled|maxFindings|timeoutMs>`
292
+ - Judge keys: `judge.<mode|provider|timeoutMs>`
293
+
294
+ 未知の key は拒否されます。boolean / numeric config keys は schema の型へ変換し、string keys は文字列のまま保持した後、config 全体を再検証します。
285
295
 
286
296
  Project `kyoso.toml` は declarative で、trust approval は不要です。tools toggles、agent `enabled` / `model` / `effort` / `role` / `timeoutMs`、workspace byte limits と additive `workspace.deny`、verification settings、advisory judge settings、tightening-only security/network settings を設定できます。
287
297
 
package/README.md CHANGED
@@ -137,7 +137,7 @@ Known distribution risk: `@modelcontextprotocol/server` has no stable release ye
137
137
  ```bash
138
138
  kyoso plan --goal "Review this OAuth callback plan" --plan plan.md
139
139
  kyoso security --goal "Review this auth diff" --diff changes.patch
140
- kyoso diff --base main --head HEAD
140
+ kyoso diff --base main --head HEAD --set agents.claude.effort=high
141
141
  kyoso doctor
142
142
  kyoso init
143
143
  kyoso setup codex
@@ -204,6 +204,8 @@ MCP stdout is reserved for protocol messages. Logs go to stderr or local audit t
204
204
 
205
205
  The bundled `kyoso-review` skill is intentionally narrow. It should trigger only when you explicitly ask for Kyoso, multi-agent review, plan review, security review, CISA Secure by Design review, or diff review.
206
206
 
207
+ The skill prefers the Kyoso MCP tools when available. If the MCP server is not registered, it follows its documented CLI fallback and requests JSON output from `npx -y @kyo-so/cli` or `bunx @kyo-so/cli`.
208
+
207
209
  `npx @kyo-so/cli setup codex --write` and `bunx @kyo-so/cli setup codex --write` copy it to `.agents/skills/kyoso-review/` by default. Add `--global` to copy it to `~/.agents/skills/kyoso-review/`.
208
210
 
209
211
  `npx @kyo-so/cli setup claude-code --write` and `bunx @kyo-so/cli setup claude-code --write` copy it to `.claude/skills/kyoso-review/` by default. Add `--global` to copy it to `~/.claude/skills/kyoso-review/`.
@@ -282,7 +284,15 @@ Kyoso loads config in this order:
282
284
  - built-in defaults
283
285
  - user global TOML: `$XDG_CONFIG_HOME/kyoso/config.toml`, or `~/.config/kyoso/config.toml`
284
286
  - project TOML: `<cwd>/kyoso.toml`
285
- - CLI flags such as `--network`
287
+ - CLI flags such as `--network` and repeatable overrides such as `--set agents.claude.effort=high`
288
+
289
+ `plan`, `security`, and `diff` accept repeatable `--set <key>=<value>` overrides. Values set on the CLI take precedence over config files, including when `--ignore-config` is used.
290
+
291
+ - Agent keys: `agents.<codex|claude>.<enabled|model|effort|role|timeoutMs>`
292
+ - Verification keys: `verification.<enabled|maxFindings|timeoutMs>`
293
+ - Judge keys: `judge.<mode|provider|timeoutMs>`
294
+
295
+ Unknown keys are rejected. Boolean and numeric config keys are converted to their schema types; string keys remain strings. The complete config is then validated.
286
296
 
287
297
  Project `kyoso.toml` is declarative and does not require trust approval. It can set safe project-scoped keys such as tool toggles, agent `enabled` / `model` / `effort` / `role` / `timeoutMs`, workspace byte limits and additive `workspace.deny`, verification settings, advisory judge settings, and tightening-only security/network settings.
288
298
 
package/README.zh-CN.md CHANGED
@@ -136,7 +136,7 @@ Nix dev shell 会 pin Node.js 24 和 nixpkgs 提供的 Bun version。确认 `.en
136
136
  ```bash
137
137
  kyoso plan --goal "Review this OAuth callback plan" --plan plan.md
138
138
  kyoso security --goal "Review this auth diff" --diff changes.patch
139
- kyoso diff --base main --head HEAD
139
+ kyoso diff --base main --head HEAD --set agents.claude.effort=high
140
140
  kyoso doctor
141
141
  kyoso init
142
142
  kyoso setup codex
@@ -203,6 +203,8 @@ MCP stdout 专用于 protocol messages。Logs 会写到 stderr 或 local audit t
203
203
 
204
204
  内置的 `kyoso-review` skill 有意保持范围很窄。只有当你明确请求 Kyoso、multi-agent review、plan review、security review、CISA Secure by Design review 或 diff review 时,才应触发它。
205
205
 
206
+ 当 Kyoso MCP tools 可用时,skill 会优先使用它们。如果尚未注册 MCP server,skill 会按照文档中的 CLI fallback,通过 `npx -y @kyo-so/cli` 或 `bunx @kyo-so/cli` 获取 JSON output。
207
+
206
208
  `npx @kyo-so/cli setup codex --write` 和 `bunx @kyo-so/cli setup codex --write` 默认将其复制到 `.agents/skills/kyoso-review/`。添加 `--global` 会复制到 `~/.agents/skills/kyoso-review/`。
207
209
 
208
210
  `npx @kyo-so/cli setup claude-code --write` 和 `bunx @kyo-so/cli setup claude-code --write` 默认将其复制到 `.claude/skills/kyoso-review/`。添加 `--global` 会复制到 `~/.claude/skills/kyoso-review/`。
@@ -281,7 +283,15 @@ Kyoso 按以下顺序 load config:
281
283
  - built-in defaults
282
284
  - user global TOML: `$XDG_CONFIG_HOME/kyoso/config.toml`,或 `~/.config/kyoso/config.toml`
283
285
  - project TOML: `<cwd>/kyoso.toml`
284
- - `--network` 等 CLI flags
286
+ - `--network` 等 CLI flags,以及 `--set agents.claude.effort=high` 等可重复的 overrides
287
+
288
+ `plan`、`security` 和 `diff` 接受可重复的 `--set <key>=<value>` overrides。CLI 指定的值优先于 config files,也可以与 `--ignore-config` 一起使用。
289
+
290
+ - Agent keys: `agents.<codex|claude>.<enabled|model|effort|role|timeoutMs>`
291
+ - Verification keys: `verification.<enabled|maxFindings|timeoutMs>`
292
+ - Judge keys: `judge.<mode|provider|timeoutMs>`
293
+
294
+ 未知 key 会被拒绝。Boolean / numeric config keys 会转换为 schema 类型,string keys 保持字符串,然后重新验证完整 config。
285
295
 
286
296
  Project `kyoso.toml` 是 declarative config,不需要 trust approval。它可以设置 tools toggles、agent `enabled` / `model` / `effort` / `role` / `timeoutMs`、workspace byte limits 和 additive `workspace.deny`、verification settings、advisory judge settings,以及 tightening-only security/network settings。
287
297
 
package/dist/bin/kyoso.js CHANGED
@@ -169739,6 +169739,28 @@ var defaultConfig = {
169739
169739
 
169740
169740
  // src/config/projectScope.ts
169741
169741
  var PROJECT_GLOBAL_ONLY_MESSAGE = "Move global-only settings to the user global config:";
169742
+ var kyosoConfigOverridePaths = [
169743
+ "agents.codex.enabled",
169744
+ "agents.codex.model",
169745
+ "agents.codex.effort",
169746
+ "agents.codex.role",
169747
+ "agents.codex.timeoutMs",
169748
+ "agents.claude.enabled",
169749
+ "agents.claude.model",
169750
+ "agents.claude.effort",
169751
+ "agents.claude.role",
169752
+ "agents.claude.timeoutMs",
169753
+ "verification.enabled",
169754
+ "verification.maxFindings",
169755
+ "verification.timeoutMs",
169756
+ "judge.mode",
169757
+ "judge.provider",
169758
+ "judge.timeoutMs"
169759
+ ];
169760
+ var CONFIG_OVERRIDE_PATHS = new Set(kyosoConfigOverridePaths);
169761
+ function isAllowedConfigOverridePath(path) {
169762
+ return CONFIG_OVERRIDE_PATHS.has(path.join("."));
169763
+ }
169742
169764
  function mergeProjectTomlConfig(baseConfig, projectConfig, options) {
169743
169765
  const violations = collectProjectScopeViolations(projectConfig);
169744
169766
  if (violations.length > 0) {
@@ -169771,13 +169793,9 @@ function collectProjectScopeViolations(config) {
169771
169793
  }
169772
169794
  function isAllowedProjectPath(path) {
169773
169795
  const [top, second, third, fourth] = path;
169774
- if (top === "tools" && path.length === 2 && ["planReview", "securityReview", "diffReview"].includes(second ?? "")) {
169796
+ if (isAllowedConfigOverridePath(path))
169775
169797
  return true;
169776
- }
169777
- if (top === "agents" && path.length === 3 && ["codex", "claude"].includes(second ?? "") && ["enabled", "model", "effort", "role", "timeoutMs"].includes(third ?? "")) {
169778
- return true;
169779
- }
169780
- if (top === "verification" && path.length === 2 && ["enabled", "maxFindings", "timeoutMs"].includes(second ?? "")) {
169798
+ if (top === "tools" && path.length === 2 && ["planReview", "securityReview", "diffReview"].includes(second ?? "")) {
169781
169799
  return true;
169782
169800
  }
169783
169801
  if (top === "workspace" && path.length === 2 && ["maxContextBytes", "maxDiffBytes", "deny"].includes(second ?? "")) {
@@ -169789,9 +169807,6 @@ function isAllowedProjectPath(path) {
169789
169807
  if (top === "secrets" && path.length === 2 && ["blockOnDetectedSecret", "allowOverride"].includes(second ?? "")) {
169790
169808
  return true;
169791
169809
  }
169792
- if (top === "judge" && path.length === 2 && ["mode", "provider", "timeoutMs"].includes(second ?? "")) {
169793
- return true;
169794
- }
169795
169810
  if (top === "securityReview" && second === "cisaSecureByDesign" && path.length >= 3) {
169796
169811
  if (third === "dimensions") {
169797
169812
  return path.length === 4 && [
@@ -184349,7 +184364,7 @@ var REDACTION = "[KYOSO_REDACTED]";
184349
184364
  var DEFAULT_AGENT_TIMEOUT_MS = 120000;
184350
184365
  var RAW_OUTPUT_MAX_CHARS = 16384;
184351
184366
  var KYOSO_CHILD_AGENT = "KYOSO_CHILD_AGENT";
184352
- var KYOSO_VERSION = "0.7.1";
184367
+ var KYOSO_VERSION = "0.8.0";
184353
184368
 
184354
184369
  // src/security/sanitizeText.ts
184355
184370
  var SENSITIVE_TEXT_PATTERNS = [
@@ -199812,6 +199827,90 @@ var StdioServerTransport = class {
199812
199827
  // src/core/runReview.ts
199813
199828
  import { resolve as resolve6 } from "node:path";
199814
199829
 
199830
+ // src/config/configOverrides.ts
199831
+ var NUMBER_VALUE = /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?$/i;
199832
+ function applyConfigOverrides(config2, assignments) {
199833
+ if (assignments.length === 0)
199834
+ return config2;
199835
+ const overrides = assignments.map(parseConfigOverride);
199836
+ const baseConfig = config2;
199837
+ const overridden = structuredClone(config2);
199838
+ for (const override of overrides) {
199839
+ writePath2(overridden, override.path, parseConfigOverrideValue(override.value, readPath2(baseConfig, override.path)));
199840
+ }
199841
+ const parsed = kyosoConfigSchema.safeParse(overridden);
199842
+ if (parsed.success)
199843
+ return parsed.data;
199844
+ const issue2 = parsed.error.issues[0];
199845
+ const issuePath = issue2?.path.map(String).join(".") ?? "config";
199846
+ const assignment = findAssignmentForPath(overrides, issuePath) ?? assignments.at(-1) ?? "";
199847
+ throw new Error(`Invalid --set value ${JSON.stringify(assignment)}: ${issuePath}: ${issue2?.message ?? "config validation failed"}.`);
199848
+ }
199849
+ function findAssignmentForPath(overrides, path) {
199850
+ for (let index = overrides.length - 1;index >= 0; index -= 1) {
199851
+ const override = overrides[index];
199852
+ if (override?.path.join(".") === path)
199853
+ return override.assignment;
199854
+ }
199855
+ return;
199856
+ }
199857
+ function parseConfigOverride(assignment) {
199858
+ const separator = assignment.indexOf("=");
199859
+ if (separator <= 0) {
199860
+ throw new Error(`Invalid --set value ${JSON.stringify(assignment)}. Expected key=value.`);
199861
+ }
199862
+ const key = assignment.slice(0, separator);
199863
+ const path = key.split(".");
199864
+ if (!isAllowedConfigOverridePath(path)) {
199865
+ throw new Error(`Unknown --set key ${JSON.stringify(key)}.`);
199866
+ }
199867
+ return {
199868
+ assignment,
199869
+ path,
199870
+ value: assignment.slice(separator + 1)
199871
+ };
199872
+ }
199873
+ function parseConfigOverrideValue(value, currentValue) {
199874
+ if (typeof currentValue === "boolean") {
199875
+ if (value === "true")
199876
+ return true;
199877
+ if (value === "false")
199878
+ return false;
199879
+ return value;
199880
+ }
199881
+ if (typeof currentValue === "number" && NUMBER_VALUE.test(value)) {
199882
+ const parsed = Number(value);
199883
+ if (Number.isFinite(parsed))
199884
+ return parsed;
199885
+ }
199886
+ return value;
199887
+ }
199888
+ function readPath2(target, path) {
199889
+ let current = target;
199890
+ for (const key of path) {
199891
+ if (!isRecord6(current))
199892
+ return;
199893
+ current = current[key];
199894
+ }
199895
+ return current;
199896
+ }
199897
+ function writePath2(target, path, value) {
199898
+ let current = target;
199899
+ for (const key of path.slice(0, -1)) {
199900
+ const child = current[key];
199901
+ if (!isRecord6(child)) {
199902
+ throw new Error(`Cannot apply --set key ${JSON.stringify(path.join("."))}.`);
199903
+ }
199904
+ current = child;
199905
+ }
199906
+ const leaf = path.at(-1);
199907
+ if (leaf)
199908
+ current[leaf] = value;
199909
+ }
199910
+ function isRecord6(value) {
199911
+ return typeof value === "object" && value !== null && !Array.isArray(value);
199912
+ }
199913
+
199815
199914
  // src/acp/AcpAgentProcess.ts
199816
199915
  import { spawn } from "node:child_process";
199817
199916
  import { readFile as readFile7, realpath } from "node:fs/promises";
@@ -202148,14 +202247,14 @@ var zCancelRequestNotification = object({
202148
202247
  });
202149
202248
  // node_modules/@agentclientprotocol/sdk/dist/jsonrpc.js
202150
202249
  var CANCEL_REQUEST_METHOD = "$/cancel_request";
202151
- function isRecord6(value) {
202250
+ function isRecord7(value) {
202152
202251
  return typeof value === "object" && value !== null;
202153
202252
  }
202154
202253
  function isJsonRpcId(value) {
202155
202254
  return value === null || typeof value === "string" || typeof value === "number" && Number.isFinite(value);
202156
202255
  }
202157
202256
  function cancelRequestId(params) {
202158
- if (!isRecord6(params) || !isJsonRpcId(params["requestId"])) {
202257
+ if (!isRecord7(params) || !isJsonRpcId(params["requestId"])) {
202159
202258
  return;
202160
202259
  }
202161
202260
  return params["requestId"];
@@ -202502,7 +202601,7 @@ class Connection {
202502
202601
  if (this.abortController.signal.aborted) {
202503
202602
  return;
202504
202603
  }
202505
- if (!isRecord6(message)) {
202604
+ if (!isRecord7(message)) {
202506
202605
  console.error("Invalid message", { message });
202507
202606
  return;
202508
202607
  }
@@ -202595,7 +202694,7 @@ class Connection {
202595
202694
  pendingResponse.cleanup?.();
202596
202695
  if ("result" in response) {
202597
202696
  pendingResponse.resolve(response.result);
202598
- } else if ("error" in response && isRecord6(response.error)) {
202697
+ } else if ("error" in response && isRecord7(response.error)) {
202599
202698
  const { code, message, data } = response.error;
202600
202699
  pendingResponse.reject(new RequestError(code, message, data));
202601
202700
  } else {
@@ -202805,7 +202904,7 @@ function ndJsonStream(output2, input2) {
202805
202904
  if (trimmedLine) {
202806
202905
  try {
202807
202906
  const message = JSON.parse(trimmedLine);
202808
- if (isRecord6(message)) {
202907
+ if (isRecord7(message)) {
202809
202908
  controller.enqueue(message);
202810
202909
  } else {
202811
202910
  console.warn("Skipping JSON line that is not an object:", trimmedLine);
@@ -203866,7 +203965,7 @@ function isSeverity(value) {
203866
203965
  return typeof value === "string" && severities.includes(value);
203867
203966
  }
203868
203967
  function normalizeCisaSecureByDesign(value) {
203869
- if (!isRecord7(value))
203968
+ if (!isRecord8(value))
203870
203969
  return;
203871
203970
  const normalized = {};
203872
203971
  const customerSecurityOutcomes = normalizeGateStatus(value.customerSecurityOutcomes);
@@ -203907,7 +204006,7 @@ function normalizeFindingFiles(value) {
203907
204006
  if (!Array.isArray(value))
203908
204007
  return;
203909
204008
  const files = value.flatMap((item) => {
203910
- if (!isRecord7(item) || typeof item.path !== "string" || item.path.trim().length === 0) {
204009
+ if (!isRecord8(item) || typeof item.path !== "string" || item.path.trim().length === 0) {
203911
204010
  return [];
203912
204011
  }
203913
204012
  const file2 = {
@@ -203926,7 +204025,7 @@ function normalizeFindingFiles(value) {
203926
204025
  function normalizeLineNumber(value) {
203927
204026
  return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : undefined;
203928
204027
  }
203929
- function isRecord7(value) {
204028
+ function isRecord8(value) {
203930
204029
  return typeof value === "object" && value !== null && !Array.isArray(value);
203931
204030
  }
203932
204031
 
@@ -205550,7 +205649,7 @@ function parseVerificationVerdicts(rawText) {
205550
205649
  if (!Array.isArray(parsed.verdicts))
205551
205650
  return;
205552
205651
  return parsed.verdicts.flatMap((item) => {
205553
- if (!isRecord8(item))
205652
+ if (!isRecord9(item))
205554
205653
  return [];
205555
205654
  if (typeof item.findingId !== "string")
205556
205655
  return [];
@@ -205628,7 +205727,7 @@ function verificationNote(reasoning) {
205628
205727
  function isVerdict(value) {
205629
205728
  return value === "confirmed" || value === "refuted" || value === "uncertain";
205630
205729
  }
205631
- function isRecord8(value) {
205730
+ function isRecord9(value) {
205632
205731
  return typeof value === "object" && value !== null && !Array.isArray(value);
205633
205732
  }
205634
205733
 
@@ -205677,7 +205776,7 @@ async function runReview(tool, request, options = {}) {
205677
205776
  }
205678
205777
  throw error51;
205679
205778
  }
205680
- const loaded = options.config !== undefined ? {
205779
+ const baseLoaded = options.config !== undefined ? {
205681
205780
  config: options.config,
205682
205781
  configHash: options.configHash,
205683
205782
  configTrustStatus: "trusted",
@@ -205694,6 +205793,10 @@ async function runReview(tool, request, options = {}) {
205694
205793
  env: options.env,
205695
205794
  trustPrompt: options.trustPrompt
205696
205795
  });
205796
+ const loaded = options.configOverrides && options.configOverrides.length > 0 ? {
205797
+ ...baseLoaded,
205798
+ config: applyConfigOverrides(baseLoaded.config, options.configOverrides)
205799
+ } : baseLoaded;
205697
205800
  const trace = createTraceWriter({
205698
205801
  enabled: loaded.config.audit.enabled,
205699
205802
  directory: loaded.config.audit.directory,
@@ -206395,6 +206498,7 @@ async function main() {
206395
206498
  ignoreConfig,
206396
206499
  trustConfig: trustConfig2,
206397
206500
  allowUnknownConfig,
206501
+ configOverrides: configOverrideFlags(parsed.flags),
206398
206502
  promptForTrust: canPromptForConfigTrust()
206399
206503
  });
206400
206504
  console.log(booleanFlag(parsed.flags, "json") ? JSON.stringify(result, null, 2) : result.summaryMarkdown);
@@ -206471,6 +206575,12 @@ function parseNetworkFlag(value) {
206471
206575
  return value;
206472
206576
  throw new Error(`Invalid --network value "${value}". Expected model_only or unrestricted.`);
206473
206577
  }
206578
+ function configOverrideFlags(flags) {
206579
+ if (flags.set === true) {
206580
+ throw new Error("Missing value for --set. Expected key=value.");
206581
+ }
206582
+ return stringArrayFlag(flags, "set");
206583
+ }
206474
206584
  function canPromptForConfigTrust() {
206475
206585
  return process.stdin.isTTY === true && process.stderr.isTTY === true;
206476
206586
  }
@@ -206479,9 +206589,9 @@ var HELP = `Kyoso
206479
206589
  Usage:
206480
206590
  kyoso mcp [--config kyoso.toml|kyoso.config.ts] [--ignore-config] [--trust-config] [--allow-unknown-config] [--network model_only|unrestricted]
206481
206591
  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]
206592
+ kyoso plan --goal <text> [--plan <path-or-text>] [--file <path>] [--set <key>=<value>]... [--json] [--trust-config] [--allow-unknown-config]
206593
+ kyoso security --goal <text> [--diff <path>] [--file <path>] [--set <key>=<value>]... [--json] [--allow-secret-redaction] [--trust-config] [--allow-unknown-config]
206594
+ kyoso diff --base main --head HEAD [--set <key>=<value>]... [--json] [--trust-config] [--allow-unknown-config]
206485
206595
  kyoso doctor [--trust-config] [--allow-unknown-config]
206486
206596
  kyoso init [--force]
206487
206597
  `;
@@ -0,0 +1,2 @@
1
+ import { type KyosoConfig } from "./schema.js";
2
+ export declare function applyConfigOverrides(config: KyosoConfig, assignments: string[]): KyosoConfig;
@@ -6,6 +6,8 @@ type Violation = {
6
6
  path: string;
7
7
  reason?: string;
8
8
  };
9
+ export declare const kyosoConfigOverridePaths: readonly ["agents.codex.enabled", "agents.codex.model", "agents.codex.effort", "agents.codex.role", "agents.codex.timeoutMs", "agents.claude.enabled", "agents.claude.model", "agents.claude.effort", "agents.claude.role", "agents.claude.timeoutMs", "verification.enabled", "verification.maxFindings", "verification.timeoutMs", "judge.mode", "judge.provider", "judge.timeoutMs"];
10
+ export declare function isAllowedConfigOverridePath(path: string[]): boolean;
9
11
  export declare function mergeProjectTomlConfig(baseConfig: unknown, projectConfig: unknown, options: ProjectScopeOptions): unknown;
10
12
  export declare function collectProjectScopeViolations(config: unknown): Violation[];
11
13
  export declare function flattenLeaves(value: unknown, path?: string[]): Array<{
@@ -4,4 +4,4 @@ export declare const DEFAULT_MAX_DIFF_BYTES = 300000;
4
4
  export declare const RAW_OUTPUT_MAX_CHARS = 16384;
5
5
  export declare const TRACE_DIR = ".kyoso/traces";
6
6
  export declare const KYOSO_CHILD_AGENT = "KYOSO_CHILD_AGENT";
7
- export declare const KYOSO_VERSION = "0.7.1";
7
+ export declare const KYOSO_VERSION = "0.8.0";
@@ -4,6 +4,7 @@ import type { AcpAgentManager } from "../acp/AcpAgentManager.js";
4
4
  import type { KyosoResult, KyosoReviewRequest, NetworkMode, ReviewTool } from "./types.js";
5
5
  export type RunReviewOptions = LoadConfigOptions & {
6
6
  config?: KyosoConfig;
7
+ configOverrides?: string[];
7
8
  configHash?: string;
8
9
  agentManager?: AcpAgentManager;
9
10
  env?: NodeJS.ProcessEnv;
package/dist/index.js CHANGED
@@ -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 && [
@@ -185548,6 +185563,90 @@ async function exists(path) {
185548
185563
  }
185549
185564
  }
185550
185565
 
185566
+ // src/config/configOverrides.ts
185567
+ var NUMBER_VALUE = /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?$/i;
185568
+ function applyConfigOverrides(config2, assignments) {
185569
+ if (assignments.length === 0)
185570
+ return config2;
185571
+ const overrides = assignments.map(parseConfigOverride);
185572
+ const baseConfig = config2;
185573
+ const overridden = structuredClone(config2);
185574
+ for (const override of overrides) {
185575
+ writePath2(overridden, override.path, parseConfigOverrideValue(override.value, readPath2(baseConfig, override.path)));
185576
+ }
185577
+ const parsed = kyosoConfigSchema.safeParse(overridden);
185578
+ if (parsed.success)
185579
+ return parsed.data;
185580
+ const issue2 = parsed.error.issues[0];
185581
+ const issuePath = issue2?.path.map(String).join(".") ?? "config";
185582
+ const assignment = findAssignmentForPath(overrides, issuePath) ?? assignments.at(-1) ?? "";
185583
+ throw new Error(`Invalid --set value ${JSON.stringify(assignment)}: ${issuePath}: ${issue2?.message ?? "config validation failed"}.`);
185584
+ }
185585
+ function findAssignmentForPath(overrides, path) {
185586
+ for (let index = overrides.length - 1;index >= 0; index -= 1) {
185587
+ const override = overrides[index];
185588
+ if (override?.path.join(".") === path)
185589
+ return override.assignment;
185590
+ }
185591
+ return;
185592
+ }
185593
+ function parseConfigOverride(assignment) {
185594
+ const separator = assignment.indexOf("=");
185595
+ if (separator <= 0) {
185596
+ throw new Error(`Invalid --set value ${JSON.stringify(assignment)}. Expected key=value.`);
185597
+ }
185598
+ const key = assignment.slice(0, separator);
185599
+ const path = key.split(".");
185600
+ if (!isAllowedConfigOverridePath(path)) {
185601
+ throw new Error(`Unknown --set key ${JSON.stringify(key)}.`);
185602
+ }
185603
+ return {
185604
+ assignment,
185605
+ path,
185606
+ value: assignment.slice(separator + 1)
185607
+ };
185608
+ }
185609
+ function parseConfigOverrideValue(value, currentValue) {
185610
+ if (typeof currentValue === "boolean") {
185611
+ if (value === "true")
185612
+ return true;
185613
+ if (value === "false")
185614
+ return false;
185615
+ return value;
185616
+ }
185617
+ if (typeof currentValue === "number" && NUMBER_VALUE.test(value)) {
185618
+ const parsed = Number(value);
185619
+ if (Number.isFinite(parsed))
185620
+ return parsed;
185621
+ }
185622
+ return value;
185623
+ }
185624
+ function readPath2(target, path) {
185625
+ let current = target;
185626
+ for (const key of path) {
185627
+ if (!isRecord4(current))
185628
+ return;
185629
+ current = current[key];
185630
+ }
185631
+ return current;
185632
+ }
185633
+ function writePath2(target, path, value) {
185634
+ let current = target;
185635
+ for (const key of path.slice(0, -1)) {
185636
+ const child = current[key];
185637
+ if (!isRecord4(child)) {
185638
+ throw new Error(`Cannot apply --set key ${JSON.stringify(path.join("."))}.`);
185639
+ }
185640
+ current = child;
185641
+ }
185642
+ const leaf = path.at(-1);
185643
+ if (leaf)
185644
+ current[leaf] = value;
185645
+ }
185646
+ function isRecord4(value) {
185647
+ return typeof value === "object" && value !== null && !Array.isArray(value);
185648
+ }
185649
+
185551
185650
  // src/acp/AcpAgentProcess.ts
185552
185651
  import { spawn } from "node:child_process";
185553
185652
  import { readFile as readFile4, realpath } from "node:fs/promises";
@@ -187883,14 +187982,14 @@ var zCancelRequestNotification = object({
187883
187982
  });
187884
187983
  // node_modules/@agentclientprotocol/sdk/dist/jsonrpc.js
187885
187984
  var CANCEL_REQUEST_METHOD = "$/cancel_request";
187886
- function isRecord4(value) {
187985
+ function isRecord5(value) {
187887
187986
  return typeof value === "object" && value !== null;
187888
187987
  }
187889
187988
  function isJsonRpcId(value) {
187890
187989
  return value === null || typeof value === "string" || typeof value === "number" && Number.isFinite(value);
187891
187990
  }
187892
187991
  function cancelRequestId(params) {
187893
- if (!isRecord4(params) || !isJsonRpcId(params["requestId"])) {
187992
+ if (!isRecord5(params) || !isJsonRpcId(params["requestId"])) {
187894
187993
  return;
187895
187994
  }
187896
187995
  return params["requestId"];
@@ -188237,7 +188336,7 @@ class Connection {
188237
188336
  if (this.abortController.signal.aborted) {
188238
188337
  return;
188239
188338
  }
188240
- if (!isRecord4(message)) {
188339
+ if (!isRecord5(message)) {
188241
188340
  console.error("Invalid message", { message });
188242
188341
  return;
188243
188342
  }
@@ -188330,7 +188429,7 @@ class Connection {
188330
188429
  pendingResponse.cleanup?.();
188331
188430
  if ("result" in response) {
188332
188431
  pendingResponse.resolve(response.result);
188333
- } else if ("error" in response && isRecord4(response.error)) {
188432
+ } else if ("error" in response && isRecord5(response.error)) {
188334
188433
  const { code, message, data } = response.error;
188335
188434
  pendingResponse.reject(new RequestError(code, message, data));
188336
188435
  } else {
@@ -188540,7 +188639,7 @@ function ndJsonStream(output, input) {
188540
188639
  if (trimmedLine) {
188541
188640
  try {
188542
188641
  const message = JSON.parse(trimmedLine);
188543
- if (isRecord4(message)) {
188642
+ if (isRecord5(message)) {
188544
188643
  controller.enqueue(message);
188545
188644
  } else {
188546
188645
  console.warn("Skipping JSON line that is not an object:", trimmedLine);
@@ -189601,7 +189700,7 @@ function isSeverity(value) {
189601
189700
  return typeof value === "string" && severities.includes(value);
189602
189701
  }
189603
189702
  function normalizeCisaSecureByDesign(value) {
189604
- if (!isRecord5(value))
189703
+ if (!isRecord6(value))
189605
189704
  return;
189606
189705
  const normalized = {};
189607
189706
  const customerSecurityOutcomes = normalizeGateStatus(value.customerSecurityOutcomes);
@@ -189642,7 +189741,7 @@ function normalizeFindingFiles(value) {
189642
189741
  if (!Array.isArray(value))
189643
189742
  return;
189644
189743
  const files = value.flatMap((item) => {
189645
- if (!isRecord5(item) || typeof item.path !== "string" || item.path.trim().length === 0) {
189744
+ if (!isRecord6(item) || typeof item.path !== "string" || item.path.trim().length === 0) {
189646
189745
  return [];
189647
189746
  }
189648
189747
  const file2 = {
@@ -189661,7 +189760,7 @@ function normalizeFindingFiles(value) {
189661
189760
  function normalizeLineNumber(value) {
189662
189761
  return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : undefined;
189663
189762
  }
189664
- function isRecord5(value) {
189763
+ function isRecord6(value) {
189665
189764
  return typeof value === "object" && value !== null && !Array.isArray(value);
189666
189765
  }
189667
189766
 
@@ -191019,7 +191118,7 @@ function parseJudgeOutput(text, fallbackSummaryText) {
191019
191118
  const parsed = JSON.parse(json2);
191020
191119
  const summaryText = typeof parsed.summaryText === "string" && parsed.summaryText.trim().length > 0 ? sanitizeText(parsed.summaryText) : fallbackSummaryText;
191021
191120
  const disagreementComments = Array.isArray(parsed.disagreementComments) ? parsed.disagreementComments.flatMap((item) => {
191022
- if (!isRecord6(item) || typeof item.topic !== "string" || typeof item.judgeComment !== "string") {
191121
+ if (!isRecord7(item) || typeof item.topic !== "string" || typeof item.judgeComment !== "string") {
191023
191122
  return [];
191024
191123
  }
191025
191124
  return [
@@ -191035,7 +191134,7 @@ function parseJudgeOutput(text, fallbackSummaryText) {
191035
191134
  return { summaryText, disagreementComments, analysis };
191036
191135
  }
191037
191136
  function parseAnalysis(value) {
191038
- if (!isRecord6(value))
191137
+ if (!isRecord7(value))
191039
191138
  return;
191040
191139
  if (!Array.isArray(value.blindSpots) || !Array.isArray(value.contradictions) || !Array.isArray(value.partialCoverage)) {
191041
191140
  return;
@@ -191043,7 +191142,7 @@ function parseAnalysis(value) {
191043
191142
  return {
191044
191143
  blindSpots: value.blindSpots.slice(0, ANALYSIS_MAX_ITEMS).flatMap((item) => typeof item === "string" ? [sanitizeAnalysisText(item)] : []),
191045
191144
  contradictions: value.contradictions.slice(0, ANALYSIS_MAX_ITEMS).flatMap((item) => {
191046
- if (!isRecord6(item) || typeof item.topic !== "string" || typeof item.detail !== "string") {
191145
+ if (!isRecord7(item) || typeof item.topic !== "string" || typeof item.detail !== "string") {
191047
191146
  return [];
191048
191147
  }
191049
191148
  return [
@@ -191054,7 +191153,7 @@ function parseAnalysis(value) {
191054
191153
  ];
191055
191154
  }),
191056
191155
  partialCoverage: value.partialCoverage.slice(0, ANALYSIS_MAX_ITEMS).flatMap((item) => {
191057
- if (!isRecord6(item) || typeof item.note !== "string")
191156
+ if (!isRecord7(item) || typeof item.note !== "string")
191058
191157
  return [];
191059
191158
  const findingId = typeof item.findingId === "string" ? sanitizeAnalysisText(item.findingId) : undefined;
191060
191159
  return [
@@ -191101,7 +191200,7 @@ function extractFirstJsonObject2(text) {
191101
191200
  }
191102
191201
  return;
191103
191202
  }
191104
- function isRecord6(value) {
191203
+ function isRecord7(value) {
191105
191204
  return typeof value === "object" && value !== null && !Array.isArray(value);
191106
191205
  }
191107
191206
 
@@ -191559,7 +191658,7 @@ function parseVerificationVerdicts(rawText) {
191559
191658
  if (!Array.isArray(parsed.verdicts))
191560
191659
  return;
191561
191660
  return parsed.verdicts.flatMap((item) => {
191562
- if (!isRecord7(item))
191661
+ if (!isRecord8(item))
191563
191662
  return [];
191564
191663
  if (typeof item.findingId !== "string")
191565
191664
  return [];
@@ -191637,7 +191736,7 @@ function verificationNote(reasoning) {
191637
191736
  function isVerdict(value) {
191638
191737
  return value === "confirmed" || value === "refuted" || value === "uncertain";
191639
191738
  }
191640
- function isRecord7(value) {
191739
+ function isRecord8(value) {
191641
191740
  return typeof value === "object" && value !== null && !Array.isArray(value);
191642
191741
  }
191643
191742
 
@@ -191686,7 +191785,7 @@ async function runReview(tool, request, options = {}) {
191686
191785
  }
191687
191786
  throw error51;
191688
191787
  }
191689
- const loaded = options.config !== undefined ? {
191788
+ const baseLoaded = options.config !== undefined ? {
191690
191789
  config: options.config,
191691
191790
  configHash: options.configHash,
191692
191791
  configTrustStatus: "trusted",
@@ -191703,6 +191802,10 @@ async function runReview(tool, request, options = {}) {
191703
191802
  env: options.env,
191704
191803
  trustPrompt: options.trustPrompt
191705
191804
  });
191805
+ const loaded = options.configOverrides && options.configOverrides.length > 0 ? {
191806
+ ...baseLoaded,
191807
+ config: applyConfigOverrides(baseLoaded.config, options.configOverrides)
191808
+ } : baseLoaded;
191706
191809
  const trace = createTraceWriter({
191707
191810
  enabled: loaded.config.audit.enabled,
191708
191811
  directory: loaded.config.audit.directory,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kyo-so/cli",
3
- "version": "0.7.1",
3
+ "version": "0.8.0",
4
4
  "description": "Kyo-so: MCP-native, ACP-powered multi-agent review gates for AI coding workflows.",
5
5
  "license": "AGPL-3.0-or-later",
6
6
  "repository": {