sneakoscope 5.12.0 → 6.0.1

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.
Files changed (141) hide show
  1. package/README.md +1 -1
  2. package/config/bench-baseline.json +3 -3
  3. package/config/perf-budgets.v1.json +11 -6
  4. package/crates/sks-core/Cargo.lock +1 -1
  5. package/crates/sks-core/Cargo.toml +1 -1
  6. package/crates/sks-core/src/main.rs +1 -1
  7. package/dist/bin/fast-inline.js +55 -0
  8. package/dist/bin/sks-dispatch.js +29 -0
  9. package/dist/bin/sks.js +2 -2
  10. package/dist/cli/codex-app-command.js +2 -2
  11. package/dist/cli/install-helpers.js +88 -73
  12. package/dist/cli/router.js +43 -26
  13. package/dist/commands/doctor.js +3 -4
  14. package/dist/config/skills-manifest.json +57 -57
  15. package/dist/core/agents/agent-effort-policy.js +9 -9
  16. package/dist/core/agents/native-cli-session-swarm.js +0 -1
  17. package/dist/core/agents/native-cli-worker.js +0 -6
  18. package/dist/core/auto-review.js +7 -7
  19. package/dist/core/codex/agent-config-file-repair.js +1 -4
  20. package/dist/core/codex/codex-config-guard.js +18 -12
  21. package/dist/core/codex/codex-config-toml.js +17 -35
  22. package/dist/core/codex/codex-project-config-policy.js +8 -4
  23. package/dist/core/codex-adapter.js +2 -2
  24. package/dist/core/codex-app/codex-agent-type-probe.js +29 -9
  25. package/dist/core/codex-app/codex-app-fast-ui-repair.js +42 -4
  26. package/dist/core/codex-app.js +5 -7
  27. package/dist/core/codex-control/codex-model-capabilities.js +2 -1
  28. package/dist/core/codex-control/codex-model-metadata.js +3 -2
  29. package/dist/core/codex-control/codex-sdk-config-policy.js +2 -1
  30. package/dist/core/codex-control/codex-task-runner.js +7 -2
  31. package/dist/core/codex-control/codex-thread-registry.js +8 -26
  32. package/dist/core/codex-model-guard.js +50 -6
  33. package/dist/core/commands/fast-mode-command.js +1 -1
  34. package/dist/core/commands/mad-sks-command.js +36 -1
  35. package/dist/core/commands/naruto-command.js +127 -37
  36. package/dist/core/commands/profile-command.js +7 -6
  37. package/dist/core/commands/run-command.js +29 -8
  38. package/dist/core/commands/ui-command.js +42 -4
  39. package/dist/core/daemon/sksd-hook-daemon-entrypoint.js +12 -0
  40. package/dist/core/daemon/sksd-hook-daemon.js +178 -0
  41. package/dist/core/daemon/sksd-hook-dispatch.js +36 -0
  42. package/dist/core/doctor/doctor-codex-startup-repair.js +2 -2
  43. package/dist/core/fsx.js +11 -2
  44. package/dist/core/hooks-runtime/hook-io.js +132 -0
  45. package/dist/core/hooks-runtime.js +5 -126
  46. package/dist/core/image-ux-review/imagegen-adapter.js +2 -1
  47. package/dist/core/init/skills.js +8 -7
  48. package/dist/core/init.js +40 -25
  49. package/dist/core/locks/file-lock.js +96 -16
  50. package/dist/core/mad-db/mad-db-coordinator.js +11 -3
  51. package/dist/core/mad-db/mad-db-executor.js +17 -1
  52. package/dist/core/mad-db/mad-db-ledger.js +7 -1
  53. package/dist/core/mad-db/mad-db-lock.js +8 -30
  54. package/dist/core/mad-db/mad-db-policy-resolver.js +2 -0
  55. package/dist/core/mad-db/mad-db-postconditions.js +19 -4
  56. package/dist/core/mad-db/mad-db-recovery.js +2 -0
  57. package/dist/core/mad-db/mad-db-runtime-profile.js +8 -0
  58. package/dist/core/mad-sks/executors/db-write-executor.js +11 -7
  59. package/dist/core/mad-sks/executors/executor-base.js +15 -2
  60. package/dist/core/mad-sks/executors/file-write-executor.js +3 -1
  61. package/dist/core/mad-sks/executors/package-install-executor.js +3 -1
  62. package/dist/core/mad-sks/executors/service-control-executor.js +3 -1
  63. package/dist/core/mad-sks/executors/shell-command-executor.js +3 -1
  64. package/dist/core/mad-sks/executors/sql-plane-executor.js +9 -1
  65. package/dist/core/managed-assets/managed-assets-manifest.js +2 -2
  66. package/dist/core/naruto/naruto-active-pool.js +2 -0
  67. package/dist/core/naruto/naruto-real-worker-child.js +1 -0
  68. package/dist/core/naruto/naruto-real-worker-runtime.js +54 -3
  69. package/dist/core/naruto/naruto-real-write-proof.js +9 -1
  70. package/dist/core/naruto/naruto-verification-dag.js +15 -2
  71. package/dist/core/naruto/naruto-write-e2e.js +1 -0
  72. package/dist/core/perf/perf-budget.js +8 -32
  73. package/dist/core/pipeline-internals/runtime-core.js +1 -1
  74. package/dist/core/pipeline-internals/runtime-gates.js +1 -1
  75. package/dist/core/proof/evidence-collector.js +2 -0
  76. package/dist/core/proof/route-finalizer.js +1 -0
  77. package/dist/core/provider/model-router.js +6 -5
  78. package/dist/core/release/release-gate-affected-selector.js +7 -3
  79. package/dist/core/routes/constants.js +5 -1
  80. package/dist/core/routes/dollar-manifest-lite.js +3 -3
  81. package/dist/core/routes.js +11 -1
  82. package/dist/core/update/update-migration-state.js +97 -24
  83. package/dist/core/version.js +1 -1
  84. package/dist/scripts/agent-role-config-repair-check.js +4 -2
  85. package/dist/scripts/build-dist.js +3 -1
  86. package/dist/scripts/codex-app-ui-preservation-check.js +19 -10
  87. package/dist/scripts/codex-lb-fast-ui-preservation-check.js +8 -9
  88. package/dist/scripts/codex-lb-gpt55-fast-profile-check.js +8 -7
  89. package/dist/scripts/codex-sdk-all-pipelines-check.js +1 -3
  90. package/dist/scripts/coding-bench-check.js +8 -2
  91. package/dist/scripts/doctor-fixes-codex-app-fast-ui-check.js +3 -3
  92. package/dist/scripts/install-update-preserves-config-check.js +7 -2
  93. package/dist/scripts/naruto-actual-worker-control-plane-check.js +1 -1
  94. package/dist/scripts/naruto-extreme-parallelism-real-check.js +3 -1
  95. package/dist/scripts/naruto-real-active-pool-runtime-check.js +6 -1
  96. package/dist/scripts/naruto-shadow-clone-swarm-check.js +5 -3
  97. package/dist/scripts/release-affected-selector-check.js +4 -4
  98. package/dist/scripts/release-gate-dag-runner-check.js +4 -6
  99. package/dist/scripts/release-provenance-check.js +4 -1
  100. package/dist/scripts/release-version-truth-check.js +8 -2
  101. package/dist/scripts/sizecheck.js +6 -3
  102. package/package.json +3 -3
  103. package/dist/scripts/agent-codex-child-overlap-check.js +0 -21
  104. package/dist/scripts/agent-model-authored-patch-envelope-check.js +0 -15
  105. package/dist/scripts/agent-native-cli-session-proof-check.js +0 -7
  106. package/dist/scripts/agent-no-subagent-scaling-check.js +0 -7
  107. package/dist/scripts/brand-neutrality-rename-map-check.js +0 -4
  108. package/dist/scripts/changelog-loop-productionization-check.js +0 -3
  109. package/dist/scripts/cli-check-tiers-check.js +0 -10
  110. package/dist/scripts/codex-0139-code-mode-web-search-check.js +0 -25
  111. package/dist/scripts/codex-agent-type-probe-check.js +0 -4
  112. package/dist/scripts/codex-native-interop-policy-check.js +0 -4
  113. package/dist/scripts/codex-sdk-core-skill-pipeline-check.js +0 -9
  114. package/dist/scripts/codex-sdk-dfix-pipeline-check.js +0 -9
  115. package/dist/scripts/core-skill-legacy-promotion-api-audit.js +0 -54
  116. package/dist/scripts/docs-brand-neutrality-check.js +0 -4
  117. package/dist/scripts/doctor-codex-0138-fix-check.js +0 -10
  118. package/dist/scripts/doctor-codex-0139-real-probes-check.js +0 -25
  119. package/dist/scripts/doctor-codex-doctor-parity-check.js +0 -17
  120. package/dist/scripts/doctor-codex-native-readiness-ux-check.js +0 -29
  121. package/dist/scripts/doctor-codex-native-repair-actions-check.js +0 -24
  122. package/dist/scripts/gate-pack-manifest-check.js +0 -9
  123. package/dist/scripts/git-worktree-batch-allocation-check.js +0 -10
  124. package/dist/scripts/github-release-body-helper.js +0 -65
  125. package/dist/scripts/local-llm-all-pipelines-check.js +0 -11
  126. package/dist/scripts/mad-db-ledger-check.js +0 -8
  127. package/dist/scripts/native-swarm-heartbeat-does-not-serialize-launch-check.js +0 -8
  128. package/dist/scripts/no-ts-nocheck-core-check.js +0 -4
  129. package/dist/scripts/orphan-gate-detection-check.js +0 -53
  130. package/dist/scripts/orphan-purge-final-check.js +0 -5
  131. package/dist/scripts/parallel-claim-enforcement-check.js +0 -30
  132. package/dist/scripts/release-batch-runner-check.js +0 -6
  133. package/dist/scripts/release-gate-planner.js +0 -55
  134. package/dist/scripts/release-runtime-truth-matrix-check.js +0 -47
  135. package/dist/scripts/research-quality-gate-check.js +0 -112
  136. package/dist/scripts/runtime-no-mjs-scripts-check.js +0 -45
  137. package/dist/scripts/scheduler-no-false-pending-block-check.js +0 -7
  138. package/dist/scripts/team-alias-to-naruto-check.js +0 -7
  139. package/dist/scripts/terminal-keyboard-enhancement-safety-check.js +0 -12
  140. package/dist/scripts/test-no-orphan-dist-imports-check.js +0 -73
  141. package/dist/scripts/update-gate-removed-check.js +0 -9
package/README.md CHANGED
@@ -25,7 +25,7 @@ sneakoscope is a proof-first Codex trust layer for bounded agent workflows, sear
25
25
 
26
26
  Sneakoscope Codex (`sks`) is a Codex CLI and Codex App harness for people who want parallel AI coding without losing proof. It gives Codex a simple front door, a dynamic worker swarm, a local dashboard, TriWiki project memory, and release gates that separate machine evidence from LLM opinion.
27
27
 
28
- Current release: SKS **5.12.0**. New in this release: operations maturity scorecards, explicit Naruto real-write proof, real CLI negative smokes for high-risk commands, doctor fast/full/fix semantics, upgrade and rollback recovery gates, diagnostics bundles, and stricter SEO metadata/strategy evidence. See [CHANGELOG.md](CHANGELOG.md).
28
+ Current release: SKS **6.0.1**. New in this release: GPT-5.6 Codex App model selection repair, Sol/Terra/Luna compatibility, removal of legacy `multi_agent_v2`/Fast-mode config stamps, renewed Codex App feature flags, safer menu bar and doctor repair paths, and release metadata prepared for `npm publish --ignore-scripts`. See [CHANGELOG.md](CHANGELOG.md).
29
29
 
30
30
  ## Install
31
31
 
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schema": "sks.coding-bench-baseline.v1",
3
3
  "baseline_version": "5.0.0",
4
- "pass_rate": 0,
5
- "min_pass_rate": 0,
6
- "notes": "Real model coding bench is skipped with no_backend unless SKS_RUN_CODING_BENCH_REAL=1 and a backend credential is present."
4
+ "pass_rate": 0.5,
5
+ "min_pass_rate": 0.5,
6
+ "notes": "Real model coding bench is skipped (status: not_run, ok: false) with no_backend unless SKS_RUN_CODING_BENCH_REAL=1 and a backend credential is present. A fully-skipped run can no longer report ok:true (20차 P0-5)."
7
7
  }
@@ -1,11 +1,16 @@
1
1
  {
2
2
  "schema": "sks.perf-budgets.v1",
3
+ "_note": "budget_p50_ms/budget_p95_ms are judged against raw wall-clock time (no node-startup-baseline deduction, 20차 P0-11). Calibrated from raw measurement on a warm dev machine (2026-07-09: version/root/commands/super-search-doctor ~40-70ms, dollar-commands ~50-130ms, doctor --json fast-mode ~40ms, plan --help/status --json/commands/mad-sks --help added after P2-2/P2-5d verification ~45-167ms) with headroom for slower/CI hosts; retighten once real CI raw numbers are collected. Gate-runtime and $Swarm-overhead budgets from the 20차 P2-6 table are NOT covered here — the former needs the P3 gate consolidation (207->~30) done first, the latter needs a real-backend E2E run (P4 E2E-1) to measure against plain `codex exec`, neither of which this repo state has yet.",
3
4
  "commands": [
4
- { "name": "version", "argv": ["node", "./dist/bin/sks.js", "--version"], "budget_p50_ms": 120, "budget_p95_ms": 140 },
5
- { "name": "commands-json", "argv": ["node", "./dist/bin/sks.js", "commands", "--json"], "budget_p50_ms": 200, "budget_p95_ms": 240 },
6
- { "name": "root-json", "argv": ["node", "./dist/bin/sks.js", "root", "--json"], "budget_p50_ms": 150, "budget_p95_ms": 180 },
7
- { "name": "dollar-commands-json", "argv": ["node", "./dist/bin/sks.js", "dollar-commands", "--json"], "budget_p50_ms": 200, "budget_p95_ms": 240 },
8
- { "name": "super-search-doctor-json", "argv": ["node", "./dist/bin/sks.js", "super-search", "doctor", "--json"], "budget_p50_ms": 180, "budget_p95_ms": 220 },
9
- { "name": "doctor-json", "argv": ["node", "./dist/bin/sks.js", "doctor", "--json"], "budget_p50_ms": 1000, "budget_p95_ms": 1500 }
5
+ { "name": "version", "argv": ["node", "./dist/bin/sks.js", "--version"], "budget_p50_ms": 100, "budget_p95_ms": 150 },
6
+ { "name": "commands-json", "argv": ["node", "./dist/bin/sks.js", "commands", "--json"], "budget_p50_ms": 150, "budget_p95_ms": 220 },
7
+ { "name": "commands", "argv": ["node", "./dist/bin/sks.js", "commands"], "budget_p50_ms": 250, "budget_p95_ms": 300 },
8
+ { "name": "root-json", "argv": ["node", "./dist/bin/sks.js", "root", "--json"], "budget_p50_ms": 150, "budget_p95_ms": 220 },
9
+ { "name": "dollar-commands-json", "argv": ["node", "./dist/bin/sks.js", "dollar-commands", "--json"], "budget_p50_ms": 200, "budget_p95_ms": 300 },
10
+ { "name": "super-search-doctor-json", "argv": ["node", "./dist/bin/sks.js", "super-search", "doctor", "--json"], "budget_p50_ms": 150, "budget_p95_ms": 220 },
11
+ { "name": "doctor-json", "argv": ["node", "./dist/bin/sks.js", "doctor", "--json"], "budget_p50_ms": 200, "budget_p95_ms": 300 },
12
+ { "name": "status-json", "argv": ["node", "./dist/bin/sks.js", "status", "--json"], "budget_p50_ms": 220, "budget_p95_ms": 300 },
13
+ { "name": "plan-help", "argv": ["node", "./dist/bin/sks.js", "plan", "--help"], "budget_p50_ms": 100, "budget_p95_ms": 150 },
14
+ { "name": "mad-sks-help", "argv": ["node", "./dist/bin/sks.js", "mad-sks", "--help"], "budget_p50_ms": 100, "budget_p95_ms": 150 }
10
15
  ]
11
16
  }
@@ -76,7 +76,7 @@ dependencies = [
76
76
 
77
77
  [[package]]
78
78
  name = "sks-core"
79
- version = "5.12.0"
79
+ version = "6.0.1"
80
80
  dependencies = [
81
81
  "serde_json",
82
82
  ]
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "sks-core"
3
- version = "5.12.0"
3
+ version = "6.0.1"
4
4
  edition = "2021"
5
5
 
6
6
  [dependencies]
@@ -4,7 +4,7 @@ use std::io::{self, Read, Seek, SeekFrom};
4
4
  fn main() {
5
5
  let mut args = std::env::args().skip(1);
6
6
  match args.next().as_deref() {
7
- Some("--version") => println!("sks-rs 5.11.0"),
7
+ Some("--version") => println!("sks-rs {}", env!("CARGO_PKG_VERSION")),
8
8
  Some("compact-info") => {
9
9
  let mut input = String::new();
10
10
  let _ = io::stdin().read_to_string(&mut input);
@@ -156,3 +156,58 @@ exports.rootJsonFastInline = rootJsonFastInline;
156
156
  exports.doctorJsonFastInline = doctorJsonFastInline;
157
157
  exports.narutoHelpJsonFastInline = narutoHelpJsonFastInline;
158
158
  exports.hookUserPromptSubmitPerfInline = hookUserPromptSubmitPerfInline;
159
+
160
+ exports.rootJsonFastInline = rootJsonFastInline;
161
+ exports.doctorJsonFastInline = doctorJsonFastInline;
162
+ exports.narutoHelpJsonFastInline = narutoHelpJsonFastInline;
163
+ exports.hookUserPromptSubmitPerfInline = hookUserPromptSubmitPerfInline;
164
+
165
+ exports.rootJsonFastInline = rootJsonFastInline;
166
+ exports.doctorJsonFastInline = doctorJsonFastInline;
167
+ exports.narutoHelpJsonFastInline = narutoHelpJsonFastInline;
168
+ exports.hookUserPromptSubmitPerfInline = hookUserPromptSubmitPerfInline;
169
+
170
+ exports.rootJsonFastInline = rootJsonFastInline;
171
+ exports.doctorJsonFastInline = doctorJsonFastInline;
172
+ exports.narutoHelpJsonFastInline = narutoHelpJsonFastInline;
173
+ exports.hookUserPromptSubmitPerfInline = hookUserPromptSubmitPerfInline;
174
+
175
+ exports.rootJsonFastInline = rootJsonFastInline;
176
+ exports.doctorJsonFastInline = doctorJsonFastInline;
177
+ exports.narutoHelpJsonFastInline = narutoHelpJsonFastInline;
178
+ exports.hookUserPromptSubmitPerfInline = hookUserPromptSubmitPerfInline;
179
+
180
+ exports.rootJsonFastInline = rootJsonFastInline;
181
+ exports.doctorJsonFastInline = doctorJsonFastInline;
182
+ exports.narutoHelpJsonFastInline = narutoHelpJsonFastInline;
183
+ exports.hookUserPromptSubmitPerfInline = hookUserPromptSubmitPerfInline;
184
+
185
+ exports.rootJsonFastInline = rootJsonFastInline;
186
+ exports.doctorJsonFastInline = doctorJsonFastInline;
187
+ exports.narutoHelpJsonFastInline = narutoHelpJsonFastInline;
188
+ exports.hookUserPromptSubmitPerfInline = hookUserPromptSubmitPerfInline;
189
+
190
+ exports.rootJsonFastInline = rootJsonFastInline;
191
+ exports.doctorJsonFastInline = doctorJsonFastInline;
192
+ exports.narutoHelpJsonFastInline = narutoHelpJsonFastInline;
193
+ exports.hookUserPromptSubmitPerfInline = hookUserPromptSubmitPerfInline;
194
+
195
+ exports.rootJsonFastInline = rootJsonFastInline;
196
+ exports.doctorJsonFastInline = doctorJsonFastInline;
197
+ exports.narutoHelpJsonFastInline = narutoHelpJsonFastInline;
198
+ exports.hookUserPromptSubmitPerfInline = hookUserPromptSubmitPerfInline;
199
+
200
+ exports.rootJsonFastInline = rootJsonFastInline;
201
+ exports.doctorJsonFastInline = doctorJsonFastInline;
202
+ exports.narutoHelpJsonFastInline = narutoHelpJsonFastInline;
203
+ exports.hookUserPromptSubmitPerfInline = hookUserPromptSubmitPerfInline;
204
+
205
+ exports.rootJsonFastInline = rootJsonFastInline;
206
+ exports.doctorJsonFastInline = doctorJsonFastInline;
207
+ exports.narutoHelpJsonFastInline = narutoHelpJsonFastInline;
208
+ exports.hookUserPromptSubmitPerfInline = hookUserPromptSubmitPerfInline;
209
+
210
+ exports.rootJsonFastInline = rootJsonFastInline;
211
+ exports.doctorJsonFastInline = doctorJsonFastInline;
212
+ exports.narutoHelpJsonFastInline = narutoHelpJsonFastInline;
213
+ exports.hookUserPromptSubmitPerfInline = hookUserPromptSubmitPerfInline;
@@ -32,6 +32,13 @@ async function runSks(args) {
32
32
  const { hookUserPromptSubmitPerfInline } = await import('./fast-inline.js');
33
33
  await hookUserPromptSubmitPerfInline();
34
34
  }
35
+ else if (args[0] === 'hook' && args[1] && process.env.SKS_HOOK_DAEMON === '1') {
36
+ // 20차 P2-1: opt-in daemon-accelerated hook path. Default hook behavior
37
+ // (.codex/hooks.json's actual command) is unchanged until this has been
38
+ // verified and deliberately turned on.
39
+ const { hookDaemonInline } = await import('../core/daemon/sksd-hook-dispatch.js');
40
+ await hookDaemonInline(args[1]);
41
+ }
35
42
  else if (args[0] === 'naruto' && (args[1] === 'help' || args.includes('--help')) && args.includes('--json')) {
36
43
  const { narutoHelpJsonFastInline } = await import('./fast-inline.js');
37
44
  narutoHelpJsonFastInline();
@@ -55,3 +62,25 @@ async function runSks(args) {
55
62
  exports.runSks = runSks;
56
63
 
57
64
  exports.runSks = runSks;
65
+
66
+ exports.runSks = runSks;
67
+
68
+ exports.runSks = runSks;
69
+
70
+ exports.runSks = runSks;
71
+
72
+ exports.runSks = runSks;
73
+
74
+ exports.runSks = runSks;
75
+
76
+ exports.runSks = runSks;
77
+
78
+ exports.runSks = runSks;
79
+
80
+ exports.runSks = runSks;
81
+
82
+ exports.runSks = runSks;
83
+
84
+ exports.runSks = runSks;
85
+
86
+ exports.runSks = runSks;
package/dist/bin/sks.js CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
- const FAST_PACKAGE_VERSION = '5.12.0';
2
+ const { PACKAGE_VERSION } = require('../core/version.js');
3
3
  const firstArg = process.argv[2];
4
4
  if (firstArg === '--version' || firstArg === '-v' || firstArg === 'version') {
5
- process.stdout.write(`sneakoscope ${FAST_PACKAGE_VERSION}\n`);
5
+ process.stdout.write(`sneakoscope ${PACKAGE_VERSION}\n`);
6
6
  }
7
7
  else {
8
8
  import('./sks-dispatch.js').then(({ runSks }) => runSks(process.argv.slice(2))).catch((err) => {
@@ -1,6 +1,6 @@
1
1
  import { spawn } from 'node:child_process';
2
2
  import { codexRemoteControlStatus, formatCodexRemoteControlStatus } from '../core/codex-app.js';
3
- import { forceGpt55CodexConfigArgs } from '../core/codex-model-guard.js';
3
+ import { forceRequiredCodexModelConfigArgs } from '../core/codex-model-guard.js';
4
4
  export async function codexAppRemoteControlCommand(args = [], opts = {}) {
5
5
  const controlArgs = argsBeforeSeparator(args);
6
6
  if (controlArgs.includes('--help') || controlArgs.includes('-h')) {
@@ -25,7 +25,7 @@ export async function codexAppRemoteControlCommand(args = [], opts = {}) {
25
25
  process.exitCode = 1;
26
26
  return;
27
27
  }
28
- const passthrough = forceGpt55CodexConfigArgs(stripSeparator(args));
28
+ const passthrough = forceRequiredCodexModelConfigArgs(stripSeparator(args));
29
29
  const spawnFn = opts.spawn || spawn;
30
30
  const code = await spawnInherited(spawnFn, status.codex_cli.bin, ['remote-control', ...passthrough], {
31
31
  cwd: process.cwd(),
@@ -23,7 +23,7 @@ import { extractTomlTable, writeCodexConfigGuarded } from '../core/codex/codex-c
23
23
  import { cleanupCodexConfigBackups, validateCodexConfigRoundTrip } from '../core/codex/codex-config-toml.js';
24
24
  import { runPostinstallGlobalDoctorAndMarkPending } from '../core/update/update-migration-state.js';
25
25
  import { repairCodexImagegen } from '../core/doctor/imagegen-repair.js';
26
- import { REQUIRED_CODEX_MODEL } from '../core/codex-model-guard.js';
26
+ import { GPT55_CODEX_MODEL, GPT56_CODEX_MODELS, REQUIRED_CODEX_MODEL } from '../core/codex-model-guard.js';
27
27
  const CODEX_LB_PROVIDER_NAME = 'openai';
28
28
  const CODEX_LB_PROVIDER_ENV_KEY = 'CODEX_LB_API_KEY';
29
29
  const CODEX_LB_CANONICAL_FAST_SERVICE_TIER = 'priority';
@@ -993,12 +993,12 @@ function codexLbFastModeConfigStatus(text = '') {
993
993
  const globalServiceTier = topLevelTomlString(text, 'service_tier');
994
994
  const profileBlock = String(text || '').match(/(^|\n)\[profiles\.sks-fast-high\]([\s\S]*?)(?=\n\[[^\]]+\]|\s*$)/)?.[2] || '';
995
995
  const profileServiceTier = profileBlock.match(/(^|\n)\s*service_tier\s*=\s*"([^"]+)"/)?.[2] || '';
996
- const configured = globalServiceTier === 'fast' || globalServiceTier === CODEX_LB_CANONICAL_FAST_SERVICE_TIER || profileServiceTier === 'fast';
996
+ const configured = globalServiceTier === 'fast' || globalServiceTier === CODEX_LB_CANONICAL_FAST_SERVICE_TIER;
997
997
  return {
998
998
  schema: 'sks.codex-lb-fast-mode-config.v1',
999
999
  configured,
1000
1000
  top_level_service_tier: globalServiceTier || null,
1001
- profile_service_tier: profileServiceTier || null,
1001
+ legacy_profile_service_tier: profileServiceTier || null,
1002
1002
  codex_request_service_tier: configured ? 'fast' : null,
1003
1003
  codex_lb_upstream_service_tier: configured ? CODEX_LB_CANONICAL_FAST_SERVICE_TIER : null,
1004
1004
  actual_service_tier_verified: false,
@@ -1030,10 +1030,7 @@ export async function repairCodexLbAuth(opts = {}) {
1030
1030
  }
1031
1031
  if (status.env_key_configured && status.base_url && (!status.provider_contract_ok || !status.selected || legacyAuthMigrated || hasTopLevelCodexModeLock(currentConfig) || (opts.forceCodexLbApiKeyAuth === true && !status.ok))) {
1032
1032
  await ensureDir(path.dirname(status.config_path));
1033
- const preservedUserFastMode = extractTomlTable(currentConfig, 'user.fast_mode');
1034
1033
  let next = upsertCodexLbConfig(currentConfig, status.base_url);
1035
- if (preservedUserFastMode)
1036
- next = upsertTomlTable(next, 'user.fast_mode', preservedUserFastMode);
1037
1034
  next = normalizeCodexFastModeUiConfig(next, {
1038
1035
  forceFastMode: opts.forceFastMode === true || opts.forceCodexLbApiKeyAuth === true
1039
1036
  });
@@ -1873,7 +1870,9 @@ export async function ensureGlobalCodexFastModeDuringInstall(opts = {}) {
1873
1870
  });
1874
1871
  if (next === ensureTrailingNewline(current))
1875
1872
  return { status: 'present', config_path: configPath };
1876
- const safeWrite = await safeWriteCodexConfigToml(configPath, current, next, 'codex-fast-mode-install');
1873
+ const safeWrite = await safeWriteCodexConfigToml(configPath, current, next, 'codex-fast-mode-install', {
1874
+ preserveFastUiKeys: opts.forceFastModeOff !== true
1875
+ });
1877
1876
  return {
1878
1877
  status: safeWrite.status === 'written' ? 'updated' : safeWrite.status,
1879
1878
  config_path: configPath,
@@ -1891,65 +1890,42 @@ export function normalizeCodexFastModeUiConfig(text = '', opts = {}) {
1891
1890
  return normalizeCodexFastModeUiConfigOnce(normalizeCodexFastModeUiConfigOnce(text, opts), opts);
1892
1891
  }
1893
1892
  function normalizeCodexFastModeUiConfigOnce(text = '', opts = {}) {
1894
- // Keep SKS-owned model/reasoning defaults out of top-level config so Codex
1895
- // Desktop can expose its native selectors, but preserve a user's deliberate
1896
- // top-level model/service/reasoning choices across install, update, and doctor.
1893
+ // 2026-07 Codex App renewal (ChatGPT desktop merge): the config schema removed
1894
+ // `default_profile`, `[profiles.<name>]` tables, `[user.fast_mode]`, and
1895
+ // `notice.fast_default_opt_out`. The only documented fast-default mechanism is
1896
+ // the plain top-level `service_tier = "fast"`. This pass strips every legacy
1897
+ // stamp SKS ever wrote and keeps user-authored top-level choices intact.
1897
1898
  let next = String(text || '');
1898
- const misplacedDefaultProfile = tomlTableString(next, 'user.fast_mode', 'default_profile');
1899
1899
  next = removeLegacyTopLevelCodexModeLocks(next);
1900
- next = removeTomlTableKey(next, 'user.fast_mode', 'default_profile');
1900
+ next = normalizeCodexAppDefaultModel(next);
1901
+ next = removeTopLevelTomlKeyIfValue(next, 'default_profile', 'sks-fast-high');
1902
+ next = removeTomlTable(next, 'user.fast_mode');
1903
+ next = removeTomlTable(next, 'profiles.sks-fast-high');
1901
1904
  next = removeTomlTableKey(next, 'notice', 'fast_default_opt_out');
1902
- next = removeTomlTableKey(next, 'features', 'codex_hooks');
1905
+ // Feature flags SKS wrote in earlier versions that are not in the current
1906
+ // [features] reference (renewal removed/renamed them). They only trigger the
1907
+ // under-development warning now — strip our own stamps.
1908
+ for (const legacyFlag of [
1909
+ 'codex_hooks', 'remote_control', 'fast_mode_ui', 'codex_git_commit', 'computer_use',
1910
+ 'browser_use', 'browser_use_external', 'image_generation', 'in_app_browser',
1911
+ 'guardian_approval', 'tool_suggest', 'plugins'
1912
+ ]) {
1913
+ next = removeTomlTableKey(next, 'features', legacyFlag);
1914
+ }
1903
1915
  if (opts.forceFastMode === true) {
1904
1916
  next = upsertTopLevelTomlString(next, 'service_tier', 'fast');
1905
1917
  }
1906
1918
  else if (opts.forceFastModeOff === true) {
1907
- next = upsertTopLevelTomlString(next, 'service_tier', 'default');
1919
+ // "off" = remove the tier and let Codex's own default apply. The old code
1920
+ // wrote service_tier = "default", which is not a documented tier value.
1921
+ next = removeTopLevelTomlKey(next, 'service_tier');
1908
1922
  }
1909
- // Codex App feature flags / fast-mode UI / suppress-warning are SET-IF-ABSENT: a fresh
1910
- // config still gets SKS's defaults, but SKS NEVER overrides (re-enables) a feature the
1911
- // user disabled in the App, and never rejects-then-hides UI by forcing an unrecognized
1912
- // flag on an older App build. This is what stops SKS from "removing/blocking" the App UI.
1923
+ // Documented, currently-valid feature flags are SET-IF-ABSENT: a fresh config
1924
+ // gets SKS's defaults, but SKS never re-enables a feature the user disabled.
1913
1925
  next = upsertTopLevelTomlBooleanIfAbsent(next, 'suppress_unstable_features_warning', true);
1914
- for (const featureLine of [
1915
- 'hooks = true', 'remote_control = true', 'multi_agent = true', 'fast_mode = true',
1916
- 'fast_mode_ui = true', 'codex_git_commit = true', 'computer_use = true', 'browser_use = true',
1917
- 'browser_use_external = true', 'image_generation = true', 'in_app_browser = true',
1918
- 'guardian_approval = true', 'tool_suggest = true', 'apps = true', 'plugins = true'
1919
- ]) {
1920
- const featureKey = featureLine.split('=')[0]?.trim();
1921
- next = opts.forceFastMode === true && ['fast_mode', 'fast_mode_ui'].includes(String(featureKey || ''))
1922
- ? upsertTomlTableKey(next, 'features', featureLine)
1923
- : upsertTomlTableKeyIfAbsent(next, 'features', featureLine);
1924
- }
1925
- if (opts.forceFastMode === true || opts.forceFastModeOff === true) {
1926
- next = upsertTomlTableKey(next, 'user.fast_mode', 'visible = true');
1927
- next = upsertTomlTableKey(next, 'user.fast_mode', 'enabled = true');
1928
- next = opts.forceFastMode === true
1929
- ? upsertTopLevelTomlString(next, 'default_profile', 'sks-fast-high')
1930
- : removeTopLevelTomlKeyIfValue(next, 'default_profile', 'sks-fast-high');
1931
- }
1932
- else {
1933
- next = upsertTomlTableKeyIfAbsent(next, 'user.fast_mode', 'visible = true');
1934
- next = upsertTomlTableKeyIfAbsent(next, 'user.fast_mode', 'enabled = true');
1935
- if (misplacedDefaultProfile === 'sks-fast-high') {
1936
- next = upsertTopLevelTomlString(next, 'default_profile', 'sks-fast-high');
1937
- }
1926
+ for (const featureLine of ['hooks = true', 'multi_agent = true', 'fast_mode = true', 'apps = true']) {
1927
+ next = upsertTomlTableKeyIfAbsent(next, 'features', featureLine);
1938
1928
  }
1939
- // Keep ONLY the sks-fast-high config-profile table for explicit fast-mode opt-in
1940
- // and CLI `--profile` use. The other SKS config profiles are
1941
- // no longer written as `[profiles.sks-*]` tables here (Codex 0.134+ deprecates them);
1942
- // they are managed as per-file `<name>.config.toml` overlays by
1943
- // migrateSksProfilesToPerFile (src/core/auto-review.ts), which also writes the
1944
- // sks-fast-high overlay for CLI `--profile` use.
1945
- next = upsertTomlTableKey(next, 'profiles.sks-fast-high', `model = "${REQUIRED_CODEX_MODEL}"`);
1946
- next = upsertTomlTableKey(next, 'profiles.sks-fast-high', 'service_tier = "fast"');
1947
- next = upsertTomlTableKey(next, 'profiles.sks-fast-high', 'approval_policy = "on-request"');
1948
- // Do not force a sandbox from the Codex App fast profile. The App/IDE
1949
- // permissions selector owns full-access vs workspace-write; this profile only
1950
- // supplies SKS's model, speed, approval, and reasoning defaults.
1951
- next = removeTomlTableKey(next, 'profiles.sks-fast-high', 'sandbox_mode');
1952
- next = upsertTomlTableKey(next, 'profiles.sks-fast-high', 'model_reasoning_effort = "high"');
1953
1929
  // Plugin auto-enable is OPT-IN only. Force-writing `[plugins."name@marketplace"] enabled =
1954
1930
  // true` for marketplace plugins the App may not have installed (different build/channel)
1955
1931
  // makes the App reference plugins it cannot load -> broken/blocked plugin UI. It also
@@ -1964,20 +1940,55 @@ function normalizeCodexFastModeUiConfigOnce(text = '', opts = {}) {
1964
1940
  }
1965
1941
  return ensureTrailingNewline(next);
1966
1942
  }
1943
+ function removeTopLevelTomlKey(text = '', key = '') {
1944
+ const lines = String(text || '').split('\n');
1945
+ const firstTable = lines.findIndex((x) => /^\s*\[.+\]\s*$/.test(x));
1946
+ const end = firstTable === -1 ? lines.length : firstTable;
1947
+ const keyPattern = new RegExp(`^\\s*${escapeRegExp(key)}\\s*=`);
1948
+ return lines.filter((line, index) => index >= end || !keyPattern.test(line)).join('\n').replace(/^\n+/, '').replace(/\n{3,}/g, '\n\n');
1949
+ }
1950
+ function removeTomlTable(text, table) {
1951
+ const lines = String(text || '').trimEnd().split('\n');
1952
+ const header = `[${table}]`;
1953
+ const start = lines.findIndex((x) => x.trim() === header);
1954
+ if (start === -1)
1955
+ return String(text || '');
1956
+ let end = lines.length;
1957
+ for (let i = start + 1; i < lines.length; i += 1) {
1958
+ const ln = lines[i];
1959
+ if (ln !== undefined && /^\s*\[.+\]\s*$/.test(ln)) {
1960
+ end = i;
1961
+ break;
1962
+ }
1963
+ }
1964
+ return lines.filter((_, index) => index < start || index >= end).join('\n').replace(/^\n+/, '').replace(/\n{3,}/g, '\n\n');
1965
+ }
1967
1966
  function removeLegacyTopLevelCodexModeLocks(text = '') {
1968
1967
  const lines = String(text || '').split('\n');
1969
1968
  const firstTable = lines.findIndex((x) => /^\s*\[.+\]\s*$/.test(x));
1970
1969
  const end = firstTable === -1 ? lines.length : firstTable;
1971
1970
  const topLevelModel = topLevelTomlString(text, 'model');
1972
- const removeSksOwnedModeLock = topLevelModel === REQUIRED_CODEX_MODEL;
1971
+ const removeSksOwnedModeLock = isLegacySksCodexModelStamp(topLevelModel);
1973
1972
  return lines.filter((line, index) => {
1974
1973
  if (index >= end)
1975
1974
  return true;
1976
1975
  if (!removeSksOwnedModeLock)
1977
1976
  return true;
1978
- return !/^\s*(?:model|model_reasoning_effort)\s*=/.test(line);
1977
+ return !/^\s*model_reasoning_effort\s*=/.test(line);
1979
1978
  }).join('\n').replace(/^\n+/, '').replace(/\n{3,}/g, '\n\n');
1980
1979
  }
1980
+ function normalizeCodexAppDefaultModel(text = '') {
1981
+ const currentModel = topLevelTomlString(text, 'model');
1982
+ if (!currentModel)
1983
+ return upsertTopLevelTomlString(text, 'model', REQUIRED_CODEX_MODEL);
1984
+ if (isLegacySksCodexModelStamp(currentModel))
1985
+ return upsertTopLevelTomlString(text, 'model', REQUIRED_CODEX_MODEL);
1986
+ return String(text || '');
1987
+ }
1988
+ function isLegacySksCodexModelStamp(value = '') {
1989
+ const model = String(value || '').trim().toLowerCase();
1990
+ return model === REQUIRED_CODEX_MODEL || model === GPT55_CODEX_MODEL || GPT56_CODEX_MODELS.includes(model);
1991
+ }
1981
1992
  function removeTopLevelTomlKeyIfValue(text = '', key = '', value = '') {
1982
1993
  const lines = String(text || '').split('\n');
1983
1994
  const firstTable = lines.findIndex((x) => /^\s*\[.+\]\s*$/.test(x));
@@ -2134,31 +2145,29 @@ async function backupCodexConfig(configPath, text, tag) {
2134
2145
  // - refuse to WRITE a result that would not parse (e.g. a regex helper mangled a multiline
2135
2146
  // string), leaving the existing config untouched,
2136
2147
  // - otherwise back up the prior config before mutating.
2137
- export async function safeWriteCodexConfigToml(configPath, current, next, tag = 'codex-lb') {
2148
+ export async function safeWriteCodexConfigToml(configPath, current, next, tag = 'codex-lb', opts = {}) {
2138
2149
  return writeCodexConfigGuarded({
2139
2150
  configPath,
2140
2151
  before: String(current || ''),
2141
2152
  cause: tag,
2142
2153
  removeTopLevelModeLocks: true,
2154
+ ...(opts.preserveFastUiKeys === undefined ? {} : { preserveFastUiKeys: opts.preserveFastUiKeys }),
2143
2155
  mutate: () => String(next || '')
2144
2156
  });
2145
2157
  }
2146
2158
  export function codexFastModeDesktopStatus(text = '') {
2147
2159
  const validation = validateCodexConfigRoundTrip(String(text || ''));
2148
- const profile = validation.parsed?.profiles?.['sks-fast-high'];
2149
- const globalOn = validation.ok
2150
- && validation.default_profile === 'sks-fast-high'
2151
- && profile?.model === REQUIRED_CODEX_MODEL
2152
- && profile?.service_tier === 'fast';
2160
+ // Post-renewal contract: the fast default is the plain top-level
2161
+ // service_tier = "fast". default_profile/[profiles.*]/[user.fast_mode] are
2162
+ // gone from the Codex config schema and are only reported as legacy_keys.
2163
+ const globalOn = validation.ok && validation.service_tier === 'fast';
2153
2164
  return {
2154
- schema: 'sks.codex-fast-mode-desktop-status.v1',
2165
+ schema: 'sks.codex-fast-mode-desktop-status.v2',
2155
2166
  ok: validation.ok,
2156
2167
  on: Boolean(globalOn),
2157
- default_profile: validation.default_profile || null,
2158
- top_level_default_profile: validation.top_level_default_profile === true,
2159
- user_fast_mode_default_profile: validation.user_fast_mode_default_profile,
2160
- profile_model: profile?.model || null,
2161
- profile_service_tier: profile?.service_tier || null,
2168
+ service_tier: validation.service_tier ?? null,
2169
+ model: validation.model ?? null,
2170
+ legacy_keys: validation.legacy_keys,
2162
2171
  validation
2163
2172
  };
2164
2173
  }
@@ -3397,8 +3406,14 @@ export async function selftestCodexLb(tmp) {
3397
3406
  });
3398
3407
  if (brokenChain.ok || brokenChain.status !== 'previous_response_not_found' || brokenChain.chain_unhealthy !== true)
3399
3408
  throw new Error('selftest: codex-lb response chain health check did not detect previous_response_not_found');
3400
- if (!codexLbConfig.includes('hooks = true') || hasDeprecatedCodexHooksFeatureFlag(codexLbConfig) || !codexLbConfig.includes('remote_control = true') || !codexLbConfig.includes('multi_agent = true') || !codexLbConfig.includes('fast_mode = true') || !codexLbConfig.includes('fast_mode_ui = true') || !codexLbConfig.includes('codex_git_commit = true') || !codexLbConfig.includes('computer_use = true') || !codexLbConfig.includes('browser_use = true') || !codexLbConfig.includes('browser_use_external = true') || !codexLbConfig.includes('guardian_approval = true') || !codexLbConfig.includes('tool_suggest = true') || !codexLbConfig.includes('apps = true') || !codexLbConfig.includes('plugins = true') || !codexLbConfig.includes('[plugins."latex@openai-bundled"]') || !codexLbConfig.includes('[plugins."documents@openai-primary-runtime"]') || !codexLbConfig.includes('[user.fast_mode]') || !codexLbConfig.includes('visible = true') || !codexLbConfig.includes('enabled = true') || !/\[profiles\.custom\][\s\S]*?model_reasoning_effort = "low"/.test(codexLbConfig) || !/\[profiles\.sks-fast-high\][\s\S]*?service_tier = "fast"/.test(codexLbConfig) || codexLbConfig.includes('fast_default_opt_out = true') || hasTopLevelCodexModeLock(codexLbConfig))
3401
- throw new Error('selftest: codex-lb setup did not preserve Codex App feature flags, default plugins, profile-scoped reasoning effort, explicit Fast profile, Codex Git commit generation, or migrate the hooks feature flag');
3409
+ // 2026-07 renewal contract: only documented [features] flags remain; the flags,
3410
+ // [user.fast_mode], [profiles.sks-fast-high], and notice.fast_default_opt_out SKS
3411
+ // used to write must be STRIPPED by setup. User-authored tables ([profiles.custom])
3412
+ // are preserved untouched even though the schema dropped profile tables.
3413
+ const legacyStamps = ['remote_control = true', 'fast_mode_ui = true', 'codex_git_commit = true', 'computer_use = true', 'browser_use = true', 'browser_use_external = true', 'guardian_approval = true', 'tool_suggest = true', 'plugins = true', '[user.fast_mode]', '[profiles.sks-fast-high]', 'fast_default_opt_out = true'];
3414
+ const survivingLegacy = legacyStamps.filter((stamp) => codexLbConfig.includes(stamp));
3415
+ if (!codexLbConfig.includes('hooks = true') || hasDeprecatedCodexHooksFeatureFlag(codexLbConfig) || !codexLbConfig.includes('multi_agent = true') || !codexLbConfig.includes('fast_mode = true') || !codexLbConfig.includes('apps = true') || survivingLegacy.length || !/\[profiles\.custom\][\s\S]*?model_reasoning_effort = "low"/.test(codexLbConfig) || hasTopLevelCodexModeLock(codexLbConfig))
3416
+ throw new Error(`selftest: codex-lb setup did not enforce the renewed feature-flag contract (documented flags present, legacy stamps stripped, user profile tables preserved)${survivingLegacy.length ? ` — surviving legacy stamps: ${survivingLegacy.join(', ')}` : ''}`);
3402
3417
  if (!hasCodexUnstableFeatureWarningSuppression(codexLbConfig))
3403
3418
  throw new Error('selftest: codex-lb setup did not suppress Codex unstable feature warning');
3404
3419
  const codexLbLaunch = `source ${path.join(tmp, '.codex', 'sks-codex-lb.env')} && codex '--model' '${REQUIRED_CODEX_MODEL}'`;
@@ -3414,7 +3429,7 @@ function hasTopLevelCodexModeLock(text = '') {
3414
3429
  const lines = String(text || '').split('\n');
3415
3430
  const firstTable = lines.findIndex((x) => /^\s*\[.+\]\s*$/.test(x));
3416
3431
  const top = (firstTable === -1 ? lines : lines.slice(0, firstTable)).join('\n');
3417
- return /(^|\n)\s*(?:model|model_reasoning_effort)\s*=/.test(top);
3432
+ return /(^|\n)\s*model_reasoning_effort\s*=/.test(top);
3418
3433
  }
3419
3434
  function hasDeprecatedCodexHooksFeatureFlag(text = '') {
3420
3435
  const lines = String(text || '').split('\n');
@@ -76,32 +76,39 @@ async function dispatchInner(argv) {
76
76
  return result;
77
77
  }
78
78
  const entry = COMMAND_MANIFEST_BY_NAME[command];
79
- const commandGate = await ensureActiveRouteCommandGate(command, rest);
80
- if (!commandGate.ok) {
81
- console.error(commandGate.message);
82
- process.exitCode = 1;
83
- return commandGate;
84
- }
85
- const { ensureCurrentMigrationBeforeCommand } = await import('../core/update/update-migration-state.js');
86
- const migrationGate = await ensureCurrentMigrationBeforeCommand({
87
- command,
88
- args: rest,
89
- skipMigrationGate: entry.skipMigrationGate === true || entry.readonly === true
90
- });
91
- if (!migrationGate.ok) {
92
- console.error('SKS project migration blocked.');
93
- console.error(`Scope: ${migrationGate.scope || 'project'}`);
94
- console.error(`Stage: ${migrationGate.failed_stage_id || migrationGate.status}`);
95
- if (migrationGate.failed_stage_id)
96
- console.error(`Failed stage: ${migrationGate.failed_stage_id}`);
97
- for (const blocker of migrationGate.blockers)
98
- console.error(`Required blocker: ${blocker}`);
99
- for (const warning of migrationGate.warnings)
100
- console.error(`Optional warning: ${warning}`);
101
- console.error(`Receipt: ${migrationGate.receipt_path}`);
102
- console.error('Remedies: run `sks doctor --fix --yes`, then retry; diagnostics that must bypass this gate are marked skipMigrationGate in the command registry.');
103
- process.exitCode = 1;
104
- return migrationGate;
79
+ const helpRequest = isHelpRequest(rest);
80
+ if (!helpRequest) {
81
+ const commandGate = await ensureActiveRouteCommandGate(command, rest);
82
+ if (!commandGate.ok) {
83
+ console.error(commandGate.message);
84
+ process.exitCode = 1;
85
+ return commandGate;
86
+ }
87
+ // 20차 P2-2: --help/-h/help must never wait on (or be blocked by) the
88
+ // migration gate's lock — a stuck/contended migration lock previously
89
+ // made `sks <cmd> --help` take the full MIGRATION_LOCK_WAIT_MS (20s) and
90
+ // then fail, for a request that only wants usage text.
91
+ const { ensureCurrentMigrationBeforeCommand } = await import('../core/update/update-migration-state.js');
92
+ const migrationGate = await ensureCurrentMigrationBeforeCommand({
93
+ command,
94
+ args: rest,
95
+ skipMigrationGate: entry.skipMigrationGate === true || entry.readonly === true
96
+ });
97
+ if (!migrationGate.ok) {
98
+ console.error('SKS project migration blocked.');
99
+ console.error(`Scope: ${migrationGate.scope || 'project'}`);
100
+ console.error(`Stage: ${migrationGate.failed_stage_id || migrationGate.status}`);
101
+ if (migrationGate.failed_stage_id)
102
+ console.error(`Failed stage: ${migrationGate.failed_stage_id}`);
103
+ for (const blocker of migrationGate.blockers)
104
+ console.error(`Required blocker: ${blocker}`);
105
+ for (const warning of migrationGate.warnings)
106
+ console.error(`Optional warning: ${warning}`);
107
+ console.error(`Receipt: ${migrationGate.receipt_path}`);
108
+ console.error('Remedies: run `sks doctor --fix --yes`, then retry; diagnostics that must bypass this gate are marked skipMigrationGate in the command registry.');
109
+ process.exitCode = 1;
110
+ return migrationGate;
111
+ }
105
112
  }
106
113
  const { COMMANDS } = await import('./command-registry.js');
107
114
  const commandEntry = COMMANDS[command];
@@ -110,6 +117,16 @@ async function dispatchInner(argv) {
110
117
  throw new Error(`Command ${command} must export run(command, args)`);
111
118
  return mod.run(rawCommand || command, rest);
112
119
  }
120
+ // --help/-h/help must skip the active-route and migration gates regardless
121
+ // of where it appears in args — a pure usage-text request should never wait
122
+ // on (or be blocked by) project state (20차 P2-2).
123
+ function isHelpRequest(args) {
124
+ // --help/-h are unambiguous flags wherever they appear; bare "help" is
125
+ // only treated as the request when it's the subcommand position (args[0])
126
+ // so an arbitrary value elsewhere (e.g. a commit message of "help") can't
127
+ // accidentally bypass the gates.
128
+ return args.includes('--help') || args.includes('-h') || String(args[0] || '').toLowerCase() === 'help';
129
+ }
113
130
  async function ensureActiveRouteCommandGate(command, args) {
114
131
  const entry = COMMAND_MANIFEST_BY_NAME[command];
115
132
  if (command === 'route' || entry.readonly === true || entry.allowedDuringActiveRoute === true && entry.mutatesRouteState !== true) {
@@ -254,10 +254,9 @@ async function runDoctor(args = [], root, doctorFix) {
254
254
  global_skills: installScope === 'global' && !flag(args, '--local-only')
255
255
  ? deepDiagnostics ? await (await import('../cli/install-helpers.js')).ensureGlobalCodexSkillsDuringInstall({ force: true }) : { status: 'skipped', reason: 'default_doctor_no_global_skill_regeneration' }
256
256
  : { status: 'skipped', reason: 'project or local-only repair' },
257
- // Re-seed the Codex App Fast-mode UI table ([user.fast_mode] visible/enabled/
258
- // default_profile) in the global ~/.codex/config.toml so existing installs whose
259
- // config predates the Fast-mode UI keys get the App speed selector back. Safe:
260
- // backs up + parse-validates before writing, no-op when already present.
257
+ // Normalize global Codex fast-mode config to the 2026-07 schema: preserve
258
+ // service_tier="fast" when requested, strip legacy [user.fast_mode] and
259
+ // [profiles.sks-fast-high] stamps, and parse-validate before writing.
261
260
  codex_app_fast_mode: flag(args, '--local-only')
262
261
  ? { status: 'skipped', reason: 'local-only repair' }
263
262
  : await (await import('../cli/install-helpers.js')).ensureGlobalCodexFastModeDuringInstall().catch((err) => ({ status: 'failed', error: err?.message || String(err) }))