sneakoscope 4.8.5 → 4.8.7

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 (197) hide show
  1. package/README.md +17 -17
  2. package/crates/sks-core/Cargo.lock +1 -1
  3. package/crates/sks-core/Cargo.toml +1 -1
  4. package/crates/sks-core/src/main.rs +1 -1
  5. package/dist/bin/sks.js +1 -1
  6. package/dist/cli/command-registry.js +63 -40
  7. package/dist/cli/install-helpers.js +115 -47
  8. package/dist/cli/router.js +58 -2
  9. package/dist/commands/codex-lb.js +4 -4
  10. package/dist/commands/doctor.js +85 -3
  11. package/dist/commands/zellij-monitor-pane.js +25 -0
  12. package/dist/commands/zellij-slot-pane.js +39 -2
  13. package/dist/commands/zellij-viewport-pane.js +56 -0
  14. package/dist/commands/zellij.js +55 -2
  15. package/dist/config/skills-manifest.json +465 -0
  16. package/dist/core/agents/agent-central-ledger.js +10 -5
  17. package/dist/core/agents/agent-lifecycle.js +86 -35
  18. package/dist/core/agents/agent-orchestrator.js +41 -11
  19. package/dist/core/agents/agent-patch-queue-store.js +14 -3
  20. package/dist/core/agents/agent-patch-queue.js +4 -0
  21. package/dist/core/agents/agent-runner-codex-exec.js +24 -0
  22. package/dist/core/agents/agent-scheduler.js +2 -3
  23. package/dist/core/agents/fast-mode-policy.js +3 -4
  24. package/dist/core/agents/native-cli-session-proof.js +1 -1
  25. package/dist/core/agents/native-cli-session-swarm.js +30 -13
  26. package/dist/core/agents/native-cli-worker.js +73 -112
  27. package/dist/core/agents/worker-pane-communication-contract.js +2 -3
  28. package/dist/core/auto-review.js +25 -4
  29. package/dist/core/codex/agent-config-file-repair.js +15 -2
  30. package/dist/core/codex/codex-cli-syntax-builder.js +2 -0
  31. package/dist/core/codex/codex-config-eperm-repair.js +8 -2
  32. package/dist/core/codex/codex-config-guard.js +289 -0
  33. package/dist/core/codex/codex-config-toml.js +122 -0
  34. package/dist/core/codex/codex-project-config-policy.js +59 -7
  35. package/dist/core/codex-app/codex-init-deep.js +10 -3
  36. package/dist/core/codex-app/sks-menubar.js +767 -122
  37. package/dist/core/codex-control/codex-fake-sdk-adapter.js +6 -4
  38. package/dist/core/codex-control/codex-reliability-shield.js +36 -17
  39. package/dist/core/codex-control/codex-sdk-adapter.js +2 -2
  40. package/dist/core/codex-control/codex-sdk-config-policy.js +4 -0
  41. package/dist/core/codex-control/codex-task-runner.js +48 -18
  42. package/dist/core/codex-control/python-codex-sdk-adapter.js +4 -1
  43. package/dist/core/codex-lb/codex-lb-setup.js +2 -5
  44. package/dist/core/codex-native/core-skill-manifest.js +2 -1
  45. package/dist/core/codex-native/skill-registry-ledger.js +24 -5
  46. package/dist/core/commands/command-utils.js +22 -2
  47. package/dist/core/commands/fast-mode-command.js +36 -11
  48. package/dist/core/commands/gates-command.js +58 -0
  49. package/dist/core/commands/mad-sks-command.js +18 -5
  50. package/dist/core/commands/menubar-command.js +125 -0
  51. package/dist/core/commands/naruto-command.js +1 -14
  52. package/dist/core/commands/pipeline-command.js +22 -3
  53. package/dist/core/commands/release-command.js +5 -5
  54. package/dist/core/commands/route-command.js +62 -0
  55. package/dist/core/commands/status-command.js +22 -3
  56. package/dist/core/commands/stop-gate-command.js +1 -1
  57. package/dist/core/commands/team-legacy-observe-command.js +221 -3
  58. package/dist/core/commands/uninstall-command.js +298 -0
  59. package/dist/core/db-safety.js +5 -1
  60. package/dist/core/doctor/codex-startup-config-repair.js +14 -3
  61. package/dist/core/doctor/confirm-repair.js +27 -0
  62. package/dist/core/doctor/context7-mcp-repair.js +35 -7
  63. package/dist/core/doctor/doctor-codex-startup-repair.js +10 -3
  64. package/dist/core/doctor/doctor-context7-repair.js +17 -4
  65. package/dist/core/doctor/doctor-dirty-planner.js +27 -9
  66. package/dist/core/doctor/doctor-native-capability-repair.js +12 -2
  67. package/dist/core/doctor/doctor-readiness-matrix.js +27 -2
  68. package/dist/core/doctor/doctor-repair-postcheck.js +4 -0
  69. package/dist/core/doctor/doctor-transaction.js +13 -5
  70. package/dist/core/doctor/supabase-mcp-repair.js +23 -5
  71. package/dist/core/feature-fixtures.js +10 -3
  72. package/dist/core/feature-registry.js +4 -1
  73. package/dist/core/fsx.js +58 -2
  74. package/dist/core/hooks-runtime/skill-reconcile-preflight.js +43 -0
  75. package/dist/core/hooks-runtime/team-digest.js +86 -0
  76. package/dist/core/hooks-runtime.js +89 -134
  77. package/dist/core/init/skills.js +647 -0
  78. package/dist/core/init.js +13 -384
  79. package/dist/core/lean-engineering-policy.js +2 -1
  80. package/dist/core/mad-db/mad-db-coordinator.js +3 -3
  81. package/dist/core/mission.js +142 -46
  82. package/dist/core/naruto/naruto-active-pool.js +94 -22
  83. package/dist/core/naruto/naruto-backpressure.js +5 -1
  84. package/dist/core/naruto/naruto-real-worker-child.js +67 -11
  85. package/dist/core/naruto/naruto-real-worker-runtime.js +9 -7
  86. package/dist/core/naruto/resource-pressure-monitor.js +33 -15
  87. package/dist/core/permission-gates.js +45 -19
  88. package/dist/core/pipeline-internals/runtime-core.js +69 -230
  89. package/dist/core/pipeline-internals/runtime-gates.js +136 -81
  90. package/dist/core/ppt/artifacts.js +19 -0
  91. package/dist/core/ppt/html.js +96 -0
  92. package/dist/core/ppt/style-tokens.js +248 -0
  93. package/dist/core/ppt.js +6 -352
  94. package/dist/core/qa-loop/{qa-contract-v2.js → qa-contract.js} +1 -1
  95. package/dist/core/qa-loop/{qa-gate-v2.js → qa-gate.js} +1 -1
  96. package/dist/core/qa-loop/qa-runtime-artifacts.js +2 -2
  97. package/dist/core/qa-loop.js +11 -4
  98. package/dist/core/questions.js +1 -1
  99. package/dist/core/recallpulse/policy.js +226 -0
  100. package/dist/core/recallpulse.js +3 -226
  101. package/dist/core/release/gate-manifest.js +2 -2
  102. package/dist/core/release/release-gate-affected-selector.js +3 -10
  103. package/dist/core/release/release-gate-batch-runner.js +4 -2
  104. package/dist/core/release/release-gate-cache-v2.js +27 -14
  105. package/dist/core/release/release-gate-dag.js +12 -3
  106. package/dist/core/release/release-gate-hermetic-env.js +10 -0
  107. package/dist/core/research/mock-result.js +406 -0
  108. package/dist/core/research/prompt.js +15 -0
  109. package/dist/core/research.js +2 -402
  110. package/dist/core/retention.js +89 -4
  111. package/dist/core/routes/constants.js +48 -0
  112. package/dist/core/routes/design-policy.js +37 -0
  113. package/dist/core/routes/diagnostic-allowlist.js +55 -0
  114. package/dist/core/routes/evidence.js +77 -0
  115. package/dist/core/routes/ppt-policy.js +26 -0
  116. package/dist/core/routes.js +31 -205
  117. package/dist/core/safety/mutation-ledger.js +2 -0
  118. package/dist/core/safety/ssot-guard.js +2 -2
  119. package/dist/core/skill-forge.js +9 -4
  120. package/dist/core/stop-gate/stop-gate-check.js +17 -17
  121. package/dist/core/stop-gate/stop-gate-resolver.js +19 -6
  122. package/dist/core/stop-gate/stop-gate-writer.js +16 -1
  123. package/dist/core/team-dag.js +12 -465
  124. package/dist/core/team-dashboard-renderer.js +13 -94
  125. package/dist/core/team-live.js +68 -804
  126. package/dist/core/update/update-migration-state.js +20 -30
  127. package/dist/core/update-check.js +28 -2
  128. package/dist/core/version.js +1 -1
  129. package/dist/core/zellij/zellij-layout-builder.js +44 -16
  130. package/dist/core/zellij/zellij-monitor-renderer.js +53 -0
  131. package/dist/core/zellij/zellij-self-heal.js +18 -26
  132. package/dist/core/zellij/zellij-slot-pane-renderer.js +166 -150
  133. package/dist/core/zellij/zellij-slot-telemetry.js +30 -12
  134. package/dist/core/zellij/zellij-theme.js +67 -0
  135. package/dist/core/zellij/zellij-ui-mode.js +16 -0
  136. package/dist/core/zellij/zellij-viewport-binder.js +59 -0
  137. package/dist/core/zellij/zellij-worker-pane-manager.js +53 -5
  138. package/dist/scripts/agent-patch-swarm-gate-lib.js +2 -2
  139. package/dist/scripts/build-dist.js +8 -0
  140. package/dist/scripts/check-pipeline-budget.js +1 -7
  141. package/dist/scripts/check-pipeline-runtime.js +1 -8
  142. package/dist/scripts/codex-control-all-pipelines-check.js +5 -14
  143. package/dist/scripts/codex-lb-fast-ui-preservation-check.js +106 -0
  144. package/dist/scripts/codex-lb-gpt55-fast-profile-check.js +105 -0
  145. package/dist/scripts/codex-lb-missing-env-regression.js +2 -3
  146. package/dist/scripts/codex-project-config-policy-merge-regression.js +3 -3
  147. package/dist/scripts/codex-sdk-all-pipelines-check.js +5 -9
  148. package/dist/scripts/concurrent-session-collision-check.js +67 -0
  149. package/dist/scripts/core-skill-integrity-blackbox.js +3 -4
  150. package/dist/scripts/docs-truthfulness-check.js +1 -1
  151. package/dist/scripts/fast-codex-service-tier-proof-check.js +2 -2
  152. package/dist/scripts/gate-policy-audit-check.js +200 -0
  153. package/dist/scripts/hook-latency-budget-check.js +45 -0
  154. package/dist/scripts/lib/codex-sdk-gate-lib.js +2 -2
  155. package/dist/scripts/lib/real-codex-parallel-gate.js +2 -2
  156. package/dist/scripts/loop-directive-check-lib.js +1 -1
  157. package/dist/scripts/mad-db-real-supabase-e2e.js +2 -2
  158. package/dist/scripts/mad-sks-zellij-default-pane-worker-check.js +3 -2
  159. package/dist/scripts/mad-sks-zellij-launch-check.js +1 -1
  160. package/dist/scripts/naruto-zellij-dynamic-right-column-check.js +7 -7
  161. package/dist/scripts/official-docs-compat-report.js +2 -2
  162. package/dist/scripts/parallel-claim-enforcement-check.js +14 -5
  163. package/dist/scripts/product-design-plugin-routing-check.js +10 -5
  164. package/dist/scripts/project-skill-dedupe-check.js +23 -5
  165. package/dist/scripts/qa-loop-surface-router-check.js +2 -2
  166. package/dist/scripts/release-affected-selector-check.js +2 -2
  167. package/dist/scripts/release-cache-glob-hashing-check.js +1 -0
  168. package/dist/scripts/release-check-dynamic-execute.js +32 -35
  169. package/dist/scripts/release-check-dynamic.js +19 -28
  170. package/dist/scripts/release-check-stamp.js +2 -2
  171. package/dist/scripts/release-dag-full-coverage-check.js +57 -394
  172. package/dist/scripts/release-dynamic-performance-check.js +18 -19
  173. package/dist/scripts/release-dynamic-presets-check.js +2 -2
  174. package/dist/scripts/release-gate-dag-runner-check.js +10 -2
  175. package/dist/scripts/release-gate-dag-runner.js +11 -1
  176. package/dist/scripts/release-gate-existence-audit.js +59 -91
  177. package/dist/scripts/release-gate-planner.js +43 -23
  178. package/dist/scripts/release-metadata-1-19-check.js +100 -12
  179. package/dist/scripts/release-readiness-report.js +38 -7
  180. package/dist/scripts/release-stability-report-check.js +12 -8
  181. package/dist/scripts/search-visibility-gate-lib.js +2 -2
  182. package/dist/scripts/skills-manifest-continuity-check.js +46 -0
  183. package/dist/scripts/sks-menubar-install-check.js +87 -21
  184. package/dist/scripts/sks-uninstall-regression-check.js +54 -0
  185. package/dist/scripts/uninstall-inventory-check.js +3 -0
  186. package/dist/scripts/worker-pane-communication-contract-check.js +1 -2
  187. package/dist/scripts/zellij-initial-main-only-blackbox.js +4 -4
  188. package/dist/scripts/zellij-layout-valid-check.js +18 -11
  189. package/dist/scripts/zellij-right-column-geometry-proof.js +2 -4
  190. package/dist/scripts/zellij-slot-pane-renderer-check.js +12 -11
  191. package/dist/scripts/zellij-slot-pane-stale-detection-check.js +3 -4
  192. package/dist/scripts/zellij-slot-pane-telemetry-renderer-check.js +3 -2
  193. package/dist/scripts/zellij-slot-telemetry-renderer-check.js +4 -2
  194. package/dist/scripts/zellij-spawn-on-demand-layout-check.js +8 -2
  195. package/dist/scripts/zellij-viewport-binder-check.js +9 -0
  196. package/package.json +16 -965
  197. package/dist/core/pipeline-runtime.js +0 -2
@@ -8,16 +8,19 @@ import { createRequestedScopeContract } from '../core/safety/requested-scope-con
8
8
  import { guardedPackageInstall, guardContextForRoute } from '../core/safety/mutation-guard.js';
9
9
  import { EMPTY_CODEX_INFO, getCodexInfo } from '../core/codex-adapter.js';
10
10
  import { formatHarnessConflictReport, llmHarnessCleanupPrompt, scanHarnessConflicts } from '../core/harness-conflicts.js';
11
- import { initProject, installSkills } from '../core/init.js';
11
+ import { initProject, installGlobalSkills } from '../core/init.js';
12
12
  import { context7ConfigToml, DOLLAR_SKILL_NAMES, GETDESIGN_REFERENCE, hasContext7ConfigText, RECOMMENDED_SKILLS } from '../core/routes.js';
13
13
  import { checkZellijCapability } from '../core/zellij/zellij-capability.js';
14
14
  import { reconcileCodexAppUpgradeProcesses } from '../core/codex-app.js';
15
15
  import { restartCodexApp } from '../core/codex-app/codex-app-restart.js';
16
+ import { cleanupMacLaunchSecretEnvironment } from '../core/codex-app/sks-menubar.js';
16
17
  import { recordCodexLbHealthEvent } from '../core/codex-lb-circuit.js';
17
18
  import { loadCodexLbEnv, writeCodexLbKeychain, codexLbMetadataPath } from '../core/codex-lb/codex-lb-env.js';
18
19
  import { GLM_CODEX_CONFIG_PROFILE_ID, GLM_CODEX_CONFIG_PROVIDER_ID, GLM_CODEX_CONFIG_REASONING_PROFILES } from '../core/providers/glm/glm-52-profile.js';
19
20
  import { GLM_52_OPENROUTER_MODEL } from '../core/providers/glm/glm-52-settings.js';
20
21
  import { buildCodexLbSetupPlan, codexLbPersistenceSummary, installCodexLbShellProfileSnippet, selectedCodexLbPersistenceModes } from '../core/codex-lb/codex-lb-setup.js';
22
+ import { extractTomlTable, writeCodexConfigGuarded } from '../core/codex/codex-config-guard.js';
23
+ import { cleanupCodexConfigBackups, validateCodexConfigRoundTrip } from '../core/codex/codex-config-toml.js';
21
24
  import { runPostinstallGlobalDoctorAndMarkPending } from '../core/update/update-migration-state.js';
22
25
  const CODEX_LB_PROVIDER_NAME = 'openai';
23
26
  const CODEX_LB_PROVIDER_ENV_KEY = 'CODEX_LB_API_KEY';
@@ -397,7 +400,7 @@ export async function configureCodexLb(opts = {}) {
397
400
  const useDefaultProvider = opts.useDefaultProvider !== false;
398
401
  const writeEnvFile = opts.writeEnvFile !== false;
399
402
  const storeKeychain = opts.storeKeychain === true || opts.keychain === true;
400
- const syncLaunchctl = opts.syncLaunchctl !== false && opts.syncLaunchEnv !== false;
403
+ const syncLaunchctl = opts.syncLaunchctl === true || opts.syncLaunchEnv === true;
401
404
  const shellProfile = opts.shellProfile || 'skip';
402
405
  const setupAnswers = {
403
406
  host_or_base_url: rawHost,
@@ -464,7 +467,7 @@ export async function configureCodexLb(opts = {}) {
464
467
  appliedActions.push({ type: 'store_keychain', target: 'macOS Keychain service sks-codex-lb', ok: keychain.ok === true, status: keychain.status });
465
468
  const codexEnvironment = await syncCodexLbProviderEnvironment({ env_path: envPath, base_url: baseUrl }, { ...opts, home, apiKey, baseUrl, syncLaunchEnv: syncLaunchctl });
466
469
  if (syncLaunchctl)
467
- appliedActions.push({ type: 'sync_launchctl', target: 'macOS launchctl user environment', ok: codexEnvironment.ok === true, status: codexEnvironment.status });
470
+ appliedActions.push({ type: 'sync_launchctl', target: 'macOS launchctl user environment (base URL only; API-key env removed)', ok: codexEnvironment.ok === true, status: codexEnvironment.status });
468
471
  const shellProfileResult = await installCodexLbShellProfileSnippet({ home, envPath, shellProfile }).catch((err) => ({ ok: false, status: 'failed', files: [], error: err.message }));
469
472
  if (shellProfile !== 'skip')
470
473
  appliedActions.push({ type: 'install_shell_profile_snippet', target: shellProfileResult.files?.join(', ') || shellProfile, ok: shellProfileResult.ok === true, status: shellProfileResult.status });
@@ -979,6 +982,10 @@ function topLevelTomlString(text = '', key) {
979
982
  const topLevel = String(text || '').split(/\n\s*\[/)[0] || '';
980
983
  return topLevel.match(new RegExp(`(^|\\n)\\s*${escapeRegExp(key)}\\s*=\\s*"([^"]+)"\\s*(?:#.*)?(?=\\n|$)`))?.[2] || '';
981
984
  }
985
+ function tomlTableString(text = '', table, key) {
986
+ const block = String(text || '').match(new RegExp(`(^|\\n)\\[${escapeRegExp(table)}\\]([\\s\\S]*?)(?=\\n\\[[^\\]]+\\]|\\s*$)`))?.[2] || '';
987
+ return block.match(new RegExp(`(^|\\n)\\s*${escapeRegExp(key)}\\s*=\\s*"([^"]+)"\\s*(?:#.*)?(?=\\n|$)`))?.[2] || '';
988
+ }
982
989
  export async function repairCodexLbAuth(opts = {}) {
983
990
  let status = await codexLbStatus(opts);
984
991
  let configRepaired = false;
@@ -995,7 +1002,11 @@ export async function repairCodexLbAuth(opts = {}) {
995
1002
  }
996
1003
  if (status.env_key_configured && status.base_url && (!status.provider_contract_ok || !status.selected || legacyAuthMigrated || hasTopLevelCodexModeLock(currentConfig) || (opts.forceCodexLbApiKeyAuth === true && !status.ok))) {
997
1004
  await ensureDir(path.dirname(status.config_path));
998
- const next = normalizeCodexFastModeUiConfig(upsertCodexLbConfig(currentConfig, status.base_url), {
1005
+ const preservedUserFastMode = extractTomlTable(currentConfig, 'user.fast_mode');
1006
+ let next = upsertCodexLbConfig(currentConfig, status.base_url);
1007
+ if (preservedUserFastMode)
1008
+ next = upsertTomlTable(next, 'user.fast_mode', preservedUserFastMode);
1009
+ next = normalizeCodexFastModeUiConfig(next, {
999
1010
  forceFastMode: opts.forceFastMode === true || opts.forceCodexLbApiKeyAuth === true
1000
1011
  });
1001
1012
  const safeWrite = await safeWriteCodexConfigToml(status.config_path, currentConfig, next, 'codex-lb-repair');
@@ -1337,8 +1348,8 @@ export async function unselectCodexLbProvider(opts = {}) {
1337
1348
  return { status: 'not_selected', config_path: configPath };
1338
1349
  try {
1339
1350
  const next = ensureTrailingNewline(removeTopLevelTomlString(current, 'model_provider'));
1340
- await writeTextAtomic(configPath, next);
1341
- return { status: 'unselected', config_path: configPath };
1351
+ const safeWrite = await safeWriteCodexConfigToml(configPath, current, next, 'codex-lb-unselect');
1352
+ return { status: safeWrite.ok ? 'unselected' : safeWrite.status, config_path: configPath, backup_path: safeWrite.backup_path };
1342
1353
  }
1343
1354
  catch (err) {
1344
1355
  return { status: 'failed', reason: 'write_failed', config_path: configPath, error: err.message };
@@ -1552,11 +1563,11 @@ async function syncCodexLbProviderEnvironment(status = {}, opts = {}) {
1552
1563
  process.env.CODEX_LB_API_KEY = apiKey;
1553
1564
  if (baseUrl)
1554
1565
  process.env.CODEX_LB_BASE_URL = baseUrl;
1555
- const launchEnv = await syncCodexLbMacLaunchEnvironment({ CODEX_LB_API_KEY: apiKey, ...(baseUrl ? { CODEX_LB_BASE_URL: baseUrl } : {}) }, opts);
1566
+ const launchEnv = await syncCodexLbMacLaunchEnvironment(baseUrl ? { CODEX_LB_BASE_URL: baseUrl } : {}, opts);
1556
1567
  const ok = launchEnv.ok || launchEnv.skipped || launchEnv.status === 'not_macos';
1557
1568
  return {
1558
1569
  ok,
1559
- status: launchEnv.status === 'synced' ? 'synced' : ok ? 'process_env' : launchEnv.status,
1570
+ status: launchEnv.status === 'synced' ? 'launch_base_url_synced_secret_env_removed' : ok ? 'process_env' : launchEnv.status,
1560
1571
  env_path: envPath,
1561
1572
  base_url: baseUrl || null,
1562
1573
  launch_environment: launchEnv,
@@ -1571,7 +1582,15 @@ async function syncCodexLbMacLaunchEnvironment(values = {}, opts = {}) {
1571
1582
  const launchctl = opts.launchctlBin || await which('launchctl').catch(() => null) || await exists('/bin/launchctl').then((ok) => ok ? '/bin/launchctl' : null).catch(() => null);
1572
1583
  if (!launchctl)
1573
1584
  return { ok: false, status: 'launchctl_missing', error: 'launchctl not found on PATH' };
1574
- const variables = Object.entries(values).filter(([, value]) => value);
1585
+ const secretCleanup = await cleanupMacLaunchSecretEnvironment({ force: opts.forceLaunchEnv === true }).catch((err) => ({
1586
+ ok: false,
1587
+ status: 'partial',
1588
+ variables: ['CODEX_LB_API_KEY', 'OPENROUTER_API_KEY'],
1589
+ cleaned: [],
1590
+ failed: [{ key: 'CODEX_LB_API_KEY', error: err?.message || String(err) }, { key: 'OPENROUTER_API_KEY', error: err?.message || String(err) }],
1591
+ next_actions: ['Run launchctl unsetenv for CODEX_LB_API_KEY and OPENROUTER_API_KEY']
1592
+ }));
1593
+ const variables = Object.entries(values).filter(([key, value]) => value && !['CODEX_LB_API_KEY', 'OPENROUTER_API_KEY'].includes(String(key)));
1575
1594
  const results = [];
1576
1595
  for (const [key, value] of variables) {
1577
1596
  const result = await runProcess(launchctl, ['setenv', key, String(value)], { timeoutMs: 5000, maxOutputBytes: 8192 });
@@ -1583,8 +1602,14 @@ async function syncCodexLbMacLaunchEnvironment(values = {}, opts = {}) {
1583
1602
  }
1584
1603
  const failed = results.filter((result) => !result.ok);
1585
1604
  if (failed.length)
1586
- return { ok: false, status: 'launch_env_failed', variables: results.map((result) => result.key), failed, error: failed.map((result) => `${result.key}: ${result.error}`).join('; ') };
1587
- return { ok: true, status: 'synced', variables: results.map((result) => result.key) };
1605
+ return { ok: false, status: 'launch_env_failed', variables: results.map((result) => result.key), failed, secret_env_cleanup: secretCleanup, error: failed.map((result) => `${result.key}: ${result.error}`).join('; ') };
1606
+ return {
1607
+ ok: secretCleanup.ok !== false,
1608
+ status: variables.length ? 'synced' : 'secret_env_removed',
1609
+ variables: results.map((result) => result.key),
1610
+ skipped_secret_variables: ['CODEX_LB_API_KEY', 'OPENROUTER_API_KEY'],
1611
+ secret_env_cleanup: secretCleanup
1612
+ };
1588
1613
  }
1589
1614
  async function inspectCodexLbMacLaunchEnvironment(baseUrl = '', opts = {}) {
1590
1615
  if (process.platform !== 'darwin' && !opts.forceLaunchEnv)
@@ -1598,20 +1623,33 @@ async function inspectCodexLbMacLaunchEnvironment(baseUrl = '', opts = {}) {
1598
1623
  };
1599
1624
  const currentBaseUrl = await readVar('CODEX_LB_BASE_URL');
1600
1625
  const currentApiKey = await readVar('CODEX_LB_API_KEY');
1626
+ const currentOpenRouterKey = await readVar('OPENROUTER_API_KEY');
1601
1627
  const baseMatches = !baseUrl || currentBaseUrl === String(baseUrl || '').trim();
1602
1628
  const basePresent = Boolean(currentBaseUrl);
1603
1629
  const keyPresent = Boolean(currentApiKey);
1630
+ const openRouterKeyPresent = Boolean(currentOpenRouterKey);
1604
1631
  return {
1605
1632
  checked: true,
1606
1633
  available: true,
1607
- status: basePresent && keyPresent && baseMatches ? 'synced' : basePresent || keyPresent ? 'partial' : 'missing',
1634
+ status: keyPresent || openRouterKeyPresent
1635
+ ? 'secret_env_present'
1636
+ : basePresent && baseMatches
1637
+ ? 'base_url_only'
1638
+ : basePresent
1639
+ ? 'partial'
1640
+ : 'missing',
1608
1641
  variables: [
1609
1642
  ...(keyPresent ? ['CODEX_LB_API_KEY'] : []),
1643
+ ...(openRouterKeyPresent ? ['OPENROUTER_API_KEY'] : []),
1610
1644
  ...(basePresent ? ['CODEX_LB_BASE_URL'] : [])
1611
1645
  ],
1612
1646
  base_url_present: basePresent,
1613
1647
  base_url_matches: baseMatches,
1614
- api_key_present: keyPresent
1648
+ api_key_present: keyPresent,
1649
+ openrouter_api_key_present: openRouterKeyPresent,
1650
+ next_actions: keyPresent || openRouterKeyPresent
1651
+ ? ['Run: sks doctor --fix', 'Rotate CODEX_LB_API_KEY and OPENROUTER_API_KEY if they were exposed in launchd.']
1652
+ : []
1615
1653
  };
1616
1654
  }
1617
1655
  async function maybeSyncCodexLbSharedLogin(apiKey, opts = {}) {
@@ -1731,7 +1769,9 @@ function detectCodexLbSetupDrift(state = {}) {
1731
1769
  if (!state.storeKeychain && state.keychain?.status && state.keychain.status !== 'skipped')
1732
1770
  drift.push('keychain_touched_despite_no_keychain');
1733
1771
  if (!state.syncLaunchctl && state.codexEnvironment?.launch_environment?.status === 'synced')
1734
- drift.push('launchctl_synced_despite_no_launchctl');
1772
+ drift.push('launchctl_base_url_synced_despite_no_launchctl');
1773
+ if (state.codexEnvironment?.launch_environment?.secret_env_cleanup?.status === 'partial')
1774
+ drift.push('launchctl_secret_env_cleanup_incomplete');
1735
1775
  if (state.shellProfile === 'skip' && state.shellProfileResult?.status === 'installed')
1736
1776
  drift.push('shell_profile_written_despite_skip');
1737
1777
  if (state.shellProfile === 'skip' && state.beforeState && state.afterState && state.beforeState.profileHash !== state.afterState.profileHash)
@@ -1773,7 +1813,7 @@ function appliedCodexLbPersistenceModes(state = {}) {
1773
1813
  if (state.storeKeychain && state.keychain?.ok === true)
1774
1814
  modes.push('durable_keychain');
1775
1815
  if (state.syncLaunchctl && state.codexEnvironment?.launch_environment?.status === 'synced')
1776
- modes.push('durable_launchctl');
1816
+ modes.push('process_only_ephemeral');
1777
1817
  if (state.shellProfile !== 'skip' && state.shellProfileResult?.status === 'installed')
1778
1818
  modes.push('shell_profile');
1779
1819
  if (!modes.length && state.apiKeySource === 'process.env')
@@ -1805,14 +1845,13 @@ export async function ensureGlobalCodexFastModeDuringInstall(opts = {}) {
1805
1845
  });
1806
1846
  if (next === ensureTrailingNewline(current))
1807
1847
  return { status: 'present', config_path: configPath };
1808
- // Safety gate 2: never WRITE a config that would not parse.
1809
- const nextSmoke = codexConfigParseSmoke(next);
1810
- if (!nextSmoke.ok)
1811
- return { status: 'skipped_unsafe_rewrite', config_path: configPath, parse_smoke: nextSmoke };
1812
- // Safety gate 3: back up the user's good config before mutating it.
1813
- const backupPath = current.trim() ? await backupCodexConfig(configPath, current, 'pre-update') : null;
1814
- await writeTextAtomic(configPath, next);
1815
- return { status: 'updated', config_path: configPath, backup_path: backupPath };
1848
+ const safeWrite = await safeWriteCodexConfigToml(configPath, current, next, 'codex-fast-mode-install');
1849
+ return {
1850
+ status: safeWrite.status === 'written' ? 'updated' : safeWrite.status,
1851
+ config_path: configPath,
1852
+ backup_path: safeWrite.backup_path,
1853
+ parse_smoke: safeWrite.ok ? undefined : safeWrite
1854
+ };
1816
1855
  }
1817
1856
  catch (err) {
1818
1857
  return { status: 'failed', config_path: configPath, error: err.message };
@@ -1827,7 +1866,9 @@ function normalizeCodexFastModeUiConfigOnce(text = '', opts = {}) {
1827
1866
  // Keep model and reasoning selection out of top-level config so Codex Desktop can
1828
1867
  // expose its native model/speed selectors. SKS-owned defaults live in profiles below.
1829
1868
  let next = String(text || '');
1869
+ const misplacedDefaultProfile = tomlTableString(next, 'user.fast_mode', 'default_profile');
1830
1870
  next = removeLegacyTopLevelCodexModeLocks(next);
1871
+ next = removeTomlTableKey(next, 'user.fast_mode', 'default_profile');
1831
1872
  next = removeTomlTableKey(next, 'notice', 'fast_default_opt_out');
1832
1873
  next = removeTomlTableKey(next, 'features', 'codex_hooks');
1833
1874
  if (opts.forceFastMode === true) {
@@ -1846,19 +1887,25 @@ function normalizeCodexFastModeUiConfigOnce(text = '', opts = {}) {
1846
1887
  'fast_mode_ui = true', 'codex_git_commit = true', 'computer_use = true', 'browser_use = true',
1847
1888
  'browser_use_external = true', 'image_generation = true', 'in_app_browser = true',
1848
1889
  'guardian_approval = true', 'tool_suggest = true', 'apps = true', 'plugins = true'
1849
- ])
1850
- next = upsertTomlTableKeyIfAbsent(next, 'features', featureLine);
1890
+ ]) {
1891
+ const featureKey = featureLine.split('=')[0]?.trim();
1892
+ next = opts.forceFastMode === true && ['fast_mode', 'fast_mode_ui'].includes(String(featureKey || ''))
1893
+ ? upsertTomlTableKey(next, 'features', featureLine)
1894
+ : upsertTomlTableKeyIfAbsent(next, 'features', featureLine);
1895
+ }
1851
1896
  if (opts.forceFastMode === true || opts.forceFastModeOff === true) {
1852
1897
  next = upsertTomlTableKey(next, 'user.fast_mode', 'visible = true');
1853
1898
  next = upsertTomlTableKey(next, 'user.fast_mode', 'enabled = true');
1854
1899
  next = opts.forceFastMode === true
1855
- ? upsertTomlTableKey(next, 'user.fast_mode', 'default_profile = "sks-fast-high"')
1856
- : removeTomlTableKey(next, 'user.fast_mode', 'default_profile');
1900
+ ? upsertTopLevelTomlString(next, 'default_profile', 'sks-fast-high')
1901
+ : removeTopLevelTomlKeyIfValue(next, 'default_profile', 'sks-fast-high');
1857
1902
  }
1858
1903
  else {
1859
1904
  next = upsertTomlTableKeyIfAbsent(next, 'user.fast_mode', 'visible = true');
1860
1905
  next = upsertTomlTableKeyIfAbsent(next, 'user.fast_mode', 'enabled = true');
1861
- next = removeTomlTableKey(next, 'user.fast_mode', 'default_profile');
1906
+ if (misplacedDefaultProfile === 'sks-fast-high') {
1907
+ next = upsertTopLevelTomlString(next, 'default_profile', 'sks-fast-high');
1908
+ }
1862
1909
  }
1863
1910
  // Keep ONLY the sks-fast-high config-profile table for explicit fast-mode opt-in
1864
1911
  // and CLI `--profile` use. The other SKS config profiles are
@@ -1955,6 +2002,8 @@ function upsertTomlTableKey(text, table, line) {
1955
2002
  return lines.join('\n').replace(/\n{3,}/g, '\n\n');
1956
2003
  }
1957
2004
  }
2005
+ if (hasTomlTableKey(lines.join('\n'), table, key))
2006
+ return lines.join('\n').replace(/\n{3,}/g, '\n\n');
1958
2007
  lines.splice(end, 0, line);
1959
2008
  return lines.join('\n').replace(/\n{3,}/g, '\n\n');
1960
2009
  }
@@ -2039,6 +2088,7 @@ async function backupCodexConfig(configPath, text, tag) {
2039
2088
  const stamp = `${PACKAGE_VERSION}-${Date.now().toString(36)}`;
2040
2089
  const backupPath = `${configPath}.sks-${tag}-${stamp}.bak`;
2041
2090
  await writeTextAtomic(backupPath, text);
2091
+ await cleanupCodexConfigBackups(configPath, { keepPerTag: 3, maxAgeMs: 30 * 24 * 60 * 60 * 1000 }).catch(() => undefined);
2042
2092
  return backupPath;
2043
2093
  }
2044
2094
  catch {
@@ -2052,20 +2102,32 @@ async function backupCodexConfig(configPath, text, tag) {
2052
2102
  // string), leaving the existing config untouched,
2053
2103
  // - otherwise back up the prior config before mutating.
2054
2104
  export async function safeWriteCodexConfigToml(configPath, current, next, tag = 'codex-lb') {
2055
- const cur = String(current || '');
2056
- if (cur.trim() && !codexConfigParseSmoke(cur).ok) {
2057
- const backupPath = await backupCodexConfig(configPath, cur, `${tag}-unparseable`);
2058
- return { ok: false, status: 'unparseable_config_preserved', config_path: configPath, backup_path: backupPath };
2059
- }
2060
- if (!codexConfigParseSmoke(next).ok) {
2061
- return { ok: false, status: 'skipped_unsafe_rewrite', config_path: configPath, backup_path: null };
2062
- }
2063
- if (next === ensureTrailingNewline(cur)) {
2064
- return { ok: true, status: 'present', config_path: configPath, backup_path: null, changed: false };
2065
- }
2066
- const backupPath = cur.trim() ? await backupCodexConfig(configPath, cur, tag) : null;
2067
- await writeTextAtomic(configPath, next);
2068
- return { ok: true, status: 'written', config_path: configPath, backup_path: backupPath, changed: true };
2105
+ return writeCodexConfigGuarded({
2106
+ configPath,
2107
+ before: String(current || ''),
2108
+ cause: tag,
2109
+ removeTopLevelModeLocks: true,
2110
+ mutate: () => String(next || '')
2111
+ });
2112
+ }
2113
+ export function codexFastModeDesktopStatus(text = '') {
2114
+ const validation = validateCodexConfigRoundTrip(String(text || ''));
2115
+ const profile = validation.parsed?.profiles?.['sks-fast-high'];
2116
+ const globalOn = validation.ok
2117
+ && validation.default_profile === 'sks-fast-high'
2118
+ && profile?.model === 'gpt-5.5'
2119
+ && profile?.service_tier === 'fast';
2120
+ return {
2121
+ schema: 'sks.codex-fast-mode-desktop-status.v1',
2122
+ ok: validation.ok,
2123
+ on: Boolean(globalOn),
2124
+ default_profile: validation.default_profile || null,
2125
+ top_level_default_profile: validation.top_level_default_profile === true,
2126
+ user_fast_mode_default_profile: validation.user_fast_mode_default_profile,
2127
+ profile_model: profile?.model || null,
2128
+ profile_service_tier: profile?.service_tier || null,
2129
+ validation
2130
+ };
2069
2131
  }
2070
2132
  function upsertTopLevelTomlBoolean(text, key, value) {
2071
2133
  const line = `${key} = ${value ? 'true' : 'false'}`;
@@ -2374,14 +2436,14 @@ export async function ensureGlobalCodexSkillsDuringInstall(opts = {}) {
2374
2436
  return { status: 'skipped', reason: 'home directory unavailable' };
2375
2437
  const root = globalCodexSkillsRoot(home);
2376
2438
  try {
2377
- const install = await installSkills(home);
2439
+ const install = await installGlobalSkills(home);
2378
2440
  const skills = await checkRequiredSkills(home, root);
2379
2441
  return {
2380
2442
  status: skills.ok ? 'installed' : 'partial',
2381
2443
  root,
2382
- installed_count: install.installed_skills.length,
2383
- removed_aliases: install.removed_agent_skill_aliases,
2384
- removed_stale_generated_skills: install.removed_stale_generated_skills,
2444
+ installed_count: install.installed.length,
2445
+ removed_aliases: [],
2446
+ removed_stale_generated_skills: install.removed,
2385
2447
  missing_skills: skills.missing
2386
2448
  };
2387
2449
  }
@@ -2685,7 +2747,13 @@ export async function ensureProjectContext7Config(root, transport = 'local') {
2685
2747
  }
2686
2748
  if (hasContext7ConfigText(current))
2687
2749
  return false;
2688
- await writeTextAtomic(configPath, `${current.trimEnd()}${current.trim() ? '\n\n' : ''}${block}\n`);
2750
+ await writeCodexConfigGuarded({
2751
+ root,
2752
+ configPath,
2753
+ before: current,
2754
+ cause: 'context7-project-config',
2755
+ mutate: () => `${current.trimEnd()}${current.trim() ? '\n\n' : ''}${block}\n`
2756
+ });
2689
2757
  return true;
2690
2758
  }
2691
2759
  export async function checkRequiredSkills(root, skillRoot = root ? path.join(root, '.agents', 'skills') : globalCodexSkillsRoot()) {
@@ -1,6 +1,8 @@
1
1
  import { COMMAND_ALIASES, COMMANDS, } from './command-registry.js';
2
2
  import { detectGlobalMode, glmWithoutMadResult } from './global-mode-router.js';
3
3
  import { ensureCurrentMigrationBeforeCommand } from '../core/update/update-migration-state.js';
4
+ import { projectRoot, readJson } from '../core/fsx.js';
5
+ import { stateFile } from '../core/mission.js';
4
6
  export function isCommandName(value) {
5
7
  return Object.prototype.hasOwnProperty.call(COMMANDS, value);
6
8
  }
@@ -49,15 +51,29 @@ export async function dispatch(args) {
49
51
  return result;
50
52
  }
51
53
  const entry = COMMANDS[command];
52
- const migrationGate = await ensureCurrentMigrationBeforeCommand({ command, args: rest });
54
+ const commandGate = await ensureActiveRouteCommandGate(command, rest);
55
+ if (!commandGate.ok) {
56
+ console.error(commandGate.message);
57
+ process.exitCode = 1;
58
+ return commandGate;
59
+ }
60
+ const migrationGate = await ensureCurrentMigrationBeforeCommand({
61
+ command,
62
+ args: rest,
63
+ skipMigrationGate: entry.skipMigrationGate === true || entry.readonly === true
64
+ });
53
65
  if (!migrationGate.ok) {
54
66
  console.error('SKS project migration blocked.');
67
+ console.error(`Scope: ${migrationGate.scope || 'project'}`);
68
+ console.error(`Stage: ${migrationGate.failed_stage_id || migrationGate.status}`);
69
+ if (migrationGate.failed_stage_id)
70
+ console.error(`Failed stage: ${migrationGate.failed_stage_id}`);
55
71
  for (const blocker of migrationGate.blockers)
56
72
  console.error(`Required blocker: ${blocker}`);
57
73
  for (const warning of migrationGate.warnings)
58
74
  console.error(`Optional warning: ${warning}`);
59
75
  console.error(`Receipt: ${migrationGate.receipt_path}`);
60
- console.error('Run: sks doctor --fix --yes');
76
+ console.error('Remedies: run `sks doctor --fix --yes`, then retry; diagnostics that must bypass this gate are marked skipMigrationGate in the command registry.');
61
77
  process.exitCode = 1;
62
78
  return migrationGate;
63
79
  }
@@ -66,4 +82,44 @@ export async function dispatch(args) {
66
82
  throw new Error(`Command ${command} must export run(command, args)`);
67
83
  return mod.run(rawCommand || command, rest);
68
84
  }
85
+ async function ensureActiveRouteCommandGate(command, args) {
86
+ const entry = COMMANDS[command];
87
+ if (command === 'route' || entry.readonly === true || entry.allowedDuringActiveRoute === true && entry.mutatesRouteState !== true) {
88
+ return { ok: true, status: 'allowed' };
89
+ }
90
+ if (entry.mutatesRouteState !== true)
91
+ return { ok: true, status: 'allowed' };
92
+ if (safeReadOnlySubcommand(args))
93
+ return { ok: true, status: 'allowed_status_subcommand' };
94
+ const root = await projectRoot(process.cwd()).catch(() => process.cwd());
95
+ const state = await readJson(stateFile(root), {}).catch(() => ({}));
96
+ if (!activeRouteStateBlocksCommand(state))
97
+ return { ok: true, status: 'allowed' };
98
+ return {
99
+ schema: 'sks.command-gate-active-route.v1',
100
+ ok: false,
101
+ status: 'blocked',
102
+ command,
103
+ active_mission_id: state.mission_id || null,
104
+ active_route: state.route || state.route_command || state.mode || null,
105
+ active_phase: state.phase || null,
106
+ message: `SKS command gate blocked '${command}' because active route mission ${state.mission_id} is not closed. Run: sks route close --mission ${state.mission_id}`
107
+ };
108
+ }
109
+ function safeReadOnlySubcommand(args) {
110
+ const sub = String(args.find((arg) => !String(arg).startsWith('-')) || '').toLowerCase();
111
+ if (!['status', 'show', 'list', 'observe', 'watch', 'doctor', 'help'].includes(sub))
112
+ return false;
113
+ return !args.some((arg) => ['--fix', '--yes', '-y', '--write', '--apply', '--execute', '--force', '--real'].includes(String(arg)));
114
+ }
115
+ function activeRouteStateBlocksCommand(state = {}) {
116
+ if (!state?.mission_id || state.route_closed === true)
117
+ return false;
118
+ const mode = String(state.mode || '').toUpperCase();
119
+ if (!mode || ['WIKI', 'STATUS', 'HELP'].includes(mode))
120
+ return false;
121
+ if (/(?:DONE|COMPLETE|CLOSED|BLOCKED|FAILED)$/i.test(String(state.phase || '')))
122
+ return false;
123
+ return Boolean(state.route || state.route_command || ['NARUTO', 'AGENT', 'QALOOP', 'RESEARCH', 'LOOP', 'MADSKS', 'MADDB', 'GOAL'].includes(mode));
124
+ }
69
125
  //# sourceMappingURL=router.js.map
@@ -443,8 +443,8 @@ function shapeCodexLbStatus(status = {}) {
443
443
  modes.push('durable_env_file');
444
444
  if (status.env_loader?.api_key?.source === 'keychain' || status.env_loader?.keychain?.status === 'present')
445
445
  modes.push('durable_keychain');
446
- if (status.launch_environment?.status === 'synced')
447
- modes.push('durable_launchctl');
446
+ if (status.launch_environment?.status === 'secret_env_present')
447
+ modes.push('process_only_ephemeral');
448
448
  if (!modes.length && status.env_loader?.api_key?.source === 'process.env')
449
449
  modes.push('process_only_ephemeral');
450
450
  const mode = modes[0] || 'none';
@@ -493,7 +493,7 @@ async function codexLbSetupOptions(args = []) {
493
493
  writeEnvFile = true;
494
494
  if (flag(args, '--no-keychain'))
495
495
  keychain = false;
496
- let syncLaunchctl = flag(args, '--no-launchctl') ? false : true;
496
+ let syncLaunchctl = false;
497
497
  if (flag(args, '--launchctl'))
498
498
  syncLaunchctl = true;
499
499
  const shellProfile = normalizeShellProfile(readOption(args, '--shell-profile', 'skip'));
@@ -508,7 +508,7 @@ async function codexLbSetupOptions(args = []) {
508
508
  writeEnvFile = parseYesNo(await ask('4. Write shell env loader to ~/.codex/sks-codex-lb.env? [Y/n] '), true);
509
509
  const storeKeychain = (await ask('5. Store the key in macOS Keychain when available? [Y/n] ')).trim();
510
510
  keychain = !/^(n|no|아니|아니요|ㄴ)$/i.test(storeKeychain || 'y');
511
- syncLaunchctl = parseYesNo(await ask('6. Sync macOS launchctl environment when available? [Y/n] '), true);
511
+ syncLaunchctl = parseYesNo(await ask('6. Sync non-secret macOS launchctl base URL only? API keys are never stored in launchd. [y/N] '), false);
512
512
  const profile = (await ask('7. Install shell profile snippet? [zsh/bash/fish/all/skip] ')).trim();
513
513
  const interactiveShellProfile = normalizeShellProfile(profile || 'skip');
514
514
  const runHealth = (await ask('8. Run health check now? [Y/n] ')).trim();
@@ -1,3 +1,5 @@
1
+ import os from 'node:os';
2
+ import path from 'node:path';
1
3
  import { projectRoot, exists, formatBytes } from '../core/fsx.js';
2
4
  import { flag } from '../cli/args.js';
3
5
  import { printJson } from '../cli/output.js';
@@ -40,6 +42,8 @@ import { doctorRepairPostcheck } from '../core/doctor/doctor-repair-postcheck.js
40
42
  import { withSecretPreservationGuard } from '../core/config/config-migration-journal.js';
41
43
  import { writeProjectUpdateMigrationReceipt } from '../core/update/update-migration-state.js';
42
44
  import { installSksMenuBar } from '../core/codex-app/sks-menubar.js';
45
+ import { sweepSksTempDirs } from '../core/retention.js';
46
+ import { reconcileSkills } from '../core/init/skills.js';
43
47
  export async function run(_command, args = []) {
44
48
  const root = await projectRoot();
45
49
  const doctorFix = flag(args, '--fix');
@@ -49,9 +53,15 @@ export async function run(_command, args = []) {
49
53
  }
50
54
  async function runDoctor(args = [], root, doctorFix) {
51
55
  const startedAtMs = Date.now();
56
+ const sksTempSweep = doctorFix ? await sweepSksTempDirs(root, { maxAgeHours: 24 }).catch((err) => ({
57
+ ok: false,
58
+ error: err?.message || String(err),
59
+ actions: []
60
+ })) : { ok: true, skipped: true, reason: 'doctor_without_fix', actions: [] };
52
61
  const doctorProfile = doctorProfileFromArgs(args, doctorFix);
53
62
  const machineOnly = flag(args, '--machine-only');
54
63
  const reportFile = readOption(args, '--report-file', null);
64
+ const argWarnings = doctorArgWarnings(args);
55
65
  const deepDiagnostics = doctorProfile === 'full' || doctorProfile === 'capabilities';
56
66
  const codexBin = readOption(args, '--codex-bin', process.env.SKS_DOCTOR_CODEX_BIN || '');
57
67
  const actualCodexProbeRequested = flag(args, '--actual-codex') || flag(args, '--require-actual-codex') || Boolean(codexBin);
@@ -99,6 +109,20 @@ async function runDoctor(args = [], root, doctorFix) {
99
109
  : deepDiagnostics ? await ensureGlobalCodexFastModeDuringInstall().catch((err) => ({ status: 'failed', error: err?.message || String(err) })) : { status: 'skipped', reason: 'default_doctor_no_global_fast_mode_regeneration' }
100
110
  };
101
111
  }
112
+ const skillsReconcile = doctorFix
113
+ ? {
114
+ global: await reconcileSkills({
115
+ targetDir: path.join(os.homedir(), '.agents', 'skills'),
116
+ scope: 'global',
117
+ fix: true
118
+ }).catch((err) => ({ ok: false, error: err?.message || String(err) })),
119
+ project: await reconcileSkills({
120
+ targetDir: path.join(root, '.agents', 'skills'),
121
+ scope: 'project',
122
+ fix: true
123
+ }).catch((err) => ({ ok: false, error: err?.message || String(err) }))
124
+ }
125
+ : { skipped: true, reason: 'doctor_without_fix' };
102
126
  const commandAliasCleanup = await runDoctorCommandAliasCleanup({
103
127
  root,
104
128
  fix: doctorFix
@@ -289,10 +313,18 @@ async function runDoctor(args = [], root, doctorFix) {
289
313
  executable_path: null,
290
314
  launch_agent_path: null,
291
315
  action_script_path: null,
316
+ build_stamp_path: null,
292
317
  report_path: `${root}/.sneakoscope/reports/sks-menubar.json`,
293
318
  menu_items: [],
294
319
  actions: [],
295
320
  launch: { requested: doctorFix, method: 'none', ok: false, error: err?.message || String(err) },
321
+ tcc_automation_status: 'unknown',
322
+ next_actions: [
323
+ 'Run: sks menubar status',
324
+ 'Run: sks menubar install',
325
+ 'Run: sks menubar restart',
326
+ 'Rotate CODEX_LB_API_KEY and OPENROUTER_API_KEY if they were previously exposed in launchd.'
327
+ ],
296
328
  blockers: [err?.message || String(err)],
297
329
  warnings: []
298
330
  }));
@@ -687,8 +719,9 @@ async function runDoctor(args = [], root, doctorFix) {
687
719
  const result = {
688
720
  schema: 'sks.doctor-status.v2',
689
721
  elapsed_ms: Date.now() - startedAtMs,
690
- ok: ready.ready && (!sksUpdate || sksUpdate.ok !== false) && commandAliasCleanup.ok !== false && codexStartupRepair.ok !== false && (!doctorFixPostcheck || doctorFixPostcheck.ok !== false),
722
+ ok: ready.ready && (!sksUpdate || sksUpdate.ok !== false) && commandAliasCleanup.ok !== false && codexStartupRepair.ok !== false,
691
723
  root,
724
+ arg_warnings: argWarnings,
692
725
  node: { ok: Number(process.versions.node.split('.')[0]) >= 20, version: process.version },
693
726
  codex,
694
727
  codex_config: codexConfig,
@@ -712,11 +745,24 @@ async function runDoctor(args = [], root, doctorFix) {
712
745
  supabase_mcp_repair: supabaseMcpRepair,
713
746
  doctor_fix_transaction: doctorFixTransaction,
714
747
  doctor_fix_postcheck: doctorFixPostcheck,
748
+ postcheck: doctorFixPostcheck ? {
749
+ ok: doctorFixPostcheck.ok === true,
750
+ pending_manual: doctorFixPostcheck.pending_manual || [],
751
+ required_blockers: doctorFixPostcheck.required_blockers || [],
752
+ optional_warnings: doctorFixPostcheck.optional_warnings || []
753
+ } : null,
715
754
  local_model: localModel,
716
755
  agent_role_config: agentRoleConfigRepair,
717
756
  zellij_readiness: zellijReadiness,
718
757
  codex_permission_profiles: permissionProfiles,
719
758
  command_aliases: commandAliasCleanup,
759
+ sks_temp_sweep: {
760
+ ok: sksTempSweep.ok !== false,
761
+ skipped: sksTempSweep.skipped === true,
762
+ action_count: Array.isArray(sksTempSweep.actions) ? sksTempSweep.actions.length : 0,
763
+ reason: sksTempSweep.reason || null,
764
+ error: sksTempSweep.error || null
765
+ },
720
766
  imagegen: {
721
767
  ok: imagegen.auth_readiness?.available_paths?.length > 0,
722
768
  auth_readiness: imagegen.auth_readiness || null,
@@ -735,7 +781,8 @@ async function runDoctor(args = [], root, doctorFix) {
735
781
  ready,
736
782
  sneakoscope: { ok: await exists(`${root}/.sneakoscope`) },
737
783
  package: { bytes: pkgBytes, human: formatBytes(pkgBytes) },
738
- repair: { sks_update: sksUpdate, setup: setupRepair, codex_config: configRepair, migration_journal: migrationJournal, global_sks_installs: globalSksInstallCleanup, agent_role_config: agentRoleConfigRepair, zellij: zellijRepair, context7: context7Repair, codex_startup: codexStartupRepair, startup_config: startupConfigRepair, context7_mcp: context7McpRepair, supabase_mcp: supabaseMcpRepair, sks_menubar: sksMenuBar, doctor_transaction: doctorFixTransaction, doctor_dirty_plan: doctorDirtyPlan, doctor_postcheck: doctorFixPostcheck, codex_native: codexNativeRepair, doctor_native_capability: doctorNativeCapabilityRepair, command_aliases: commandAliasCleanup }
784
+ skills: skillsReconcile,
785
+ repair: { sks_update: sksUpdate, setup: setupRepair, codex_config: configRepair, migration_journal: migrationJournal, global_sks_installs: globalSksInstallCleanup, agent_role_config: agentRoleConfigRepair, zellij: zellijRepair, context7: context7Repair, codex_startup: codexStartupRepair, startup_config: startupConfigRepair, context7_mcp: context7McpRepair, supabase_mcp: supabaseMcpRepair, sks_menubar: sksMenuBar, doctor_transaction: doctorFixTransaction, doctor_dirty_plan: doctorDirtyPlan, doctor_postcheck: doctorFixPostcheck, codex_native: codexNativeRepair, doctor_native_capability: doctorNativeCapabilityRepair, command_aliases: commandAliasCleanup, skills: skillsReconcile, sks_temp_sweep: sksTempSweep }
739
786
  };
740
787
  if (reportFile)
741
788
  await writeJsonReportFile(reportFile, result);
@@ -751,6 +798,8 @@ async function runDoctor(args = [], root, doctorFix) {
751
798
  return;
752
799
  }
753
800
  console.log('SKS Doctor');
801
+ for (const warning of argWarnings)
802
+ console.log(`Argument warning: ${warning}`);
754
803
  console.log(`Root: ${root}`);
755
804
  console.log(`Node: ${result.node.ok ? 'ok' : 'fail'} ${result.node.version}`);
756
805
  console.log(`Codex: ${codex.bin ? 'ok' : 'missing'} ${codex.version || ''}`);
@@ -1017,7 +1066,7 @@ function fallbackCodexNativeFeatureMatrix(codex, blockers = [], warnings = []) {
1017
1066
  warnings
1018
1067
  };
1019
1068
  }
1020
- function doctorProfileFromArgs(args = [], doctorFix = false) {
1069
+ export function doctorProfileFromArgs(args = [], doctorFix = false) {
1021
1070
  const explicit = readOption(args, '--profile', null);
1022
1071
  if (explicit === 'migration' || explicit === 'full' || explicit === 'capabilities' || explicit === 'fast' || explicit === 'fix')
1023
1072
  return explicit;
@@ -1027,6 +1076,39 @@ function doctorProfileFromArgs(args = [], doctorFix = false) {
1027
1076
  return 'capabilities';
1028
1077
  return doctorFix ? 'fix' : 'fast';
1029
1078
  }
1079
+ export function doctorArgWarnings(args = []) {
1080
+ const warnings = [];
1081
+ const explicit = readOption(args, '--profile', null);
1082
+ if (explicit && !['migration', 'full', 'capabilities', 'fast', 'fix'].includes(String(explicit))) {
1083
+ warnings.push(`unknown_profile:${explicit}; supported profiles: migration, full, capabilities, fast, fix`);
1084
+ }
1085
+ for (const flag of unknownDoctorFlags(args))
1086
+ warnings.push(`unknown_flag:${flag}`);
1087
+ return warnings;
1088
+ }
1089
+ function unknownDoctorFlags(args = []) {
1090
+ const knownBoolean = new Set([
1091
+ '--fix', '--yes', '-y', '--machine-only', '--actual-codex', '--require-actual-codex',
1092
+ '--full', '--capabilities', '--repair-codex-app-ui', '--repair-zellij', '--install-homebrew',
1093
+ '--repair-native-capabilities', '--repair-codex-native', '--local-only', '--project', '--global',
1094
+ '--dry-run', '--json'
1095
+ ]);
1096
+ const knownValue = new Set(['--profile', '--report-file', '--codex-bin', '--install-scope']);
1097
+ const unknown = [];
1098
+ for (let index = 0; index < args.length; index += 1) {
1099
+ const arg = String(args[index] || '');
1100
+ if (!arg.startsWith('-'))
1101
+ continue;
1102
+ if (knownValue.has(arg)) {
1103
+ index += 1;
1104
+ continue;
1105
+ }
1106
+ if (knownBoolean.has(arg))
1107
+ continue;
1108
+ unknown.push(arg);
1109
+ }
1110
+ return unknown;
1111
+ }
1030
1112
  function doctorPhaseIdsForProfile(profile) {
1031
1113
  const required = [
1032
1114
  'codex_startup_repair',