sneakoscope 0.6.64 → 0.6.66

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -40,7 +40,7 @@ sks selftest --mock
40
40
 
41
41
  | Area | What it does |
42
42
  | --- | --- |
43
- | CLI runtime | `sks`, `sks cmux`, and `sks --mad --high` open Codex CLI in a cmux workspace. |
43
+ | CLI runtime | `sks`, `sks cmux`, and `sks --mad` open Codex CLI in a cmux workspace. |
44
44
  | Codex App commands | Installs generated skills so `$Team`, `$DFix`, `$QA-LOOP`, `$Ralph`, `$DB`, `$Wiki`, `$Help`, and related routes are visible in prompt workflows. |
45
45
  | Team orchestration | Runs substantial work through ambiguity handling, scouts, TriWiki refresh, debate, runtime task graphs, worker inboxes, implementation, review, cleanup, reflection, and Honest Mode. |
46
46
  | QA loop | Dogfoods UI/API behavior with safety gates, Browser/Computer evidence, safe fixes, and rechecks. |
@@ -59,7 +59,7 @@ sks selftest --mock
59
59
  - cmux for the CLI-first runtime
60
60
  - Context7 MCP for current-docs-gated routes
61
61
 
62
- On macOS, `sks --mad --high` can install cmux through Homebrew when cmux is missing. You can also install it manually:
62
+ On macOS, `sks --mad` can install cmux through Homebrew when cmux is missing. You can also install it manually:
63
63
 
64
64
  ```sh
65
65
  brew tap manaflow-ai/cmux
@@ -70,8 +70,17 @@ If the CLI is not on `PATH`, SKS also checks the app bundle path:
70
70
 
71
71
  ```sh
72
72
  /Applications/cmux.app/Contents/Resources/bin/cmux
73
+ /Applications/cmux.app/Contents/MacOS/cmux
73
74
  ```
74
75
 
76
+ `sks --mad` is stricter than the normal runtime path:
77
+
78
+ - Checks npm for a newer `sneakoscope` before launch and asks whether to update when the terminal can answer y/n.
79
+ - Installs the latest Codex CLI with `npm i -g @openai/codex@latest` when it is missing and you approve or pass `--yes`.
80
+ - Installs or upgrades the latest cmux cask through Homebrew when cmux is missing or not launchable.
81
+ - Re-probes the real cmux binary after install instead of trusting Homebrew's success text alone.
82
+ - Wakes cmux and retries the socket probe; if the socket is broken, SKS attempts a cmux app restart during that explicit launch.
83
+
75
84
  ## Installation
76
85
 
77
86
  ### Global Install
@@ -154,13 +163,22 @@ sks cmux status --once
154
163
 
155
164
  `sks` opens a cmux workspace for Codex CLI when running in an interactive terminal. `sks cmux check` is diagnostic and prints readiness without starting a workspace.
156
165
 
157
- ### MAD High cmux Workspace
166
+ ### MAD cmux Workspace
158
167
 
159
168
  ```sh
160
- sks --mad --high
169
+ sks --mad
170
+ sks --mad --yes
171
+ ```
172
+
173
+ This creates/uses the `sks-mad-high` Codex profile for a one-shot full-access, high-reasoning cmux workspace with `approval_policy = "on-request"` and `approvals_reviewer = "auto_review"`. It is scoped to that explicit command and does not change normal SKS/DB safety defaults.
174
+
175
+ Before launching, SKS checks whether a newer `sneakoscope` exists on npm. In an interactive terminal it prompts:
176
+
177
+ ```text
178
+ SKS 0.x.y -> 0.x.z update before MAD launch? [Y/n]
161
179
  ```
162
180
 
163
- This creates/uses the `sks-mad-high` Codex profile for a one-shot full-access, high-reasoning cmux workspace. It is scoped to that explicit command and does not change normal SKS/DB safety defaults.
181
+ Answer `y` to install `sneakoscope@latest`, then rerun `sks --mad`. Answer `n` to continue with the current version. Use `--yes` to approve missing dependency installs automatically.
164
182
 
165
183
  ### Team Missions
166
184
 
@@ -191,7 +209,7 @@ sks gx render homepage --format html
191
209
 
192
210
  Sneakoscope has two surfaces:
193
211
 
194
- - Terminal commands such as `sks deps check`, `sks team "task"`, and `sks --mad --high`
212
+ - Terminal commands such as `sks deps check`, `sks team "task"`, and `sks --mad`
195
213
  - Codex App prompt commands such as `$Team`, `$DFix`, `$QA-LOOP`, and `$Wiki`
196
214
 
197
215
  After installing, run:
@@ -269,7 +287,7 @@ sks
269
287
  For the high-reasoning full-access profile:
270
288
 
271
289
  ```sh
272
- sks --mad --high
290
+ sks --mad
273
291
  ```
274
292
 
275
293
  ### Use Codex App `$Team`
@@ -331,7 +349,7 @@ sks deps install cmux
331
349
  sks cmux check
332
350
  ```
333
351
 
334
- `sks --mad --high` also attempts Homebrew installation automatically on macOS when cmux is missing.
352
+ `sks --mad` also attempts Homebrew installation or upgrade automatically on macOS when cmux is missing. If Homebrew reports the cask installed but the CLI still is not reachable, SKS checks the cmux app bundle paths directly, wakes the app, and retries the socket before reporting a remaining cmux app/socket issue.
335
353
 
336
354
  ### Codex App tools are missing
337
355
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "sneakoscope",
3
3
  "displayName": "ㅅㅋㅅ",
4
- "version": "0.6.64",
4
+ "version": "0.6.66",
5
5
  "description": "Sneakoscope Codex: database-safe Codex CLI/App harness with Team, Ralph, AutoResearch, TriWiki, and Honest Mode.",
6
6
  "type": "module",
7
7
  "homepage": "https://github.com/mandarange/Sneakoscope-Codex#readme",
package/src/cli/main.mjs CHANGED
@@ -30,7 +30,7 @@ import { context7Evidence, evaluateStop, recordContext7Evidence, recordSubagentE
30
30
  import { TEAM_DECOMPOSITION_ARTIFACT, TEAM_GRAPH_ARTIFACT, TEAM_INBOX_DIR, TEAM_RUNTIME_TASKS_ARTIFACT, teamRuntimePlanMetadata, teamRuntimeRequiredArtifacts, validateTeamRuntimeArtifacts, writeTeamRuntimeArtifacts } from '../core/team-dag.mjs';
31
31
  import { appendTeamEvent, formatRoleCounts, initTeamLive, normalizeTeamSpec, parseTeamSpecArgs, parseTeamSpecText, readTeamDashboard, readTeamLive, readTeamTranscriptTail } from '../core/team-live.mjs';
32
32
  import { CODEX_APP_DOCS_URL, codexAppIntegrationStatus, formatCodexAppStatus } from '../core/codex-app.mjs';
33
- import { buildCmuxLaunchPlan, defaultCmuxWorkspaceName, ensureCmuxInstalled, formatCmuxBanner, launchCmuxTeamView, launchCmuxUi, platformCmuxInstallHint, runCmuxStatus, sanitizeCmuxWorkspaceName, cmuxAvailable } from '../core/cmux-ui.mjs';
33
+ import { CMUX_BREW_COMMAND, CMUX_BREW_UPGRADE_COMMAND, buildCmuxLaunchPlan, defaultCmuxWorkspaceName, ensureCmuxInstalled, formatCmuxBanner, launchCmuxTeamView, launchCmuxUi, platformCmuxInstallHint, runCmuxStatus, sanitizeCmuxWorkspaceName, cmuxAvailable } from '../core/cmux-ui.mjs';
34
34
  import { autoReviewProfileName, autoReviewStatus, autoReviewSummary, enableAutoReview, disableAutoReview, enableMadHighProfile, madHighProfileName } from '../core/auto-review.mjs';
35
35
 
36
36
  const flag = (args, name) => args.includes(name);
@@ -116,7 +116,7 @@ Usage:
116
116
  sks bootstrap [--install-scope global|project] [--local-only] [--json]
117
117
  sks deps check|install [cmux|codex|context7|all] [--yes] [--json]
118
118
  sks codex-app
119
- sks --mad --high
119
+ sks --mad [--high]
120
120
  sks auto-review status|enable|start [--high]
121
121
  sks --Auto-review [--high]
122
122
  sks cmux [--workspace name]
@@ -389,13 +389,13 @@ async function ensureCodexCliTool({ skip = false } = {}) {
389
389
  const before = await getCodexInfo().catch(() => ({}));
390
390
  if (before.bin) return { status: 'present', bin: before.bin, version: before.version || null };
391
391
  const npmBin = await which('npm');
392
- if (!npmBin) return { status: 'failed', error: 'npm not found on PATH; install Codex CLI manually with npm i -g @openai/codex.' };
393
- const install = await runProcess(npmBin, ['i', '-g', '@openai/codex'], {
392
+ if (!npmBin) return { status: 'failed', error: 'npm not found on PATH; install Codex CLI manually with npm i -g @openai/codex@latest.' };
393
+ const install = await runProcess(npmBin, ['i', '-g', '@openai/codex@latest'], {
394
394
  timeoutMs: 120000,
395
395
  maxOutputBytes: 128 * 1024
396
396
  }).catch((err) => ({ code: 1, stdout: '', stderr: err.message }));
397
397
  if (install.code !== 0) {
398
- return { status: 'failed', error: `${install.stderr || install.stdout || 'npm i -g @openai/codex failed'}`.trim() };
398
+ return { status: 'failed', error: `${install.stderr || install.stdout || 'npm i -g @openai/codex@latest failed'}`.trim() };
399
399
  }
400
400
  const after = await getCodexInfo().catch(() => ({}));
401
401
  return {
@@ -1045,10 +1045,30 @@ async function cmuxCommand(sub = 'start', args = []) {
1045
1045
 
1046
1046
  async function madHighCommand(args = []) {
1047
1047
  const cleanArgs = args.filter((arg) => !['--mad', '--MAD', '--mad-sks', '--high', '--no-auto-install-cmux'].includes(arg));
1048
+ if (flag(args, '--json')) {
1049
+ const profile = await enableMadHighProfile();
1050
+ return console.log(JSON.stringify(profile, null, 2));
1051
+ }
1052
+ const update = await maybePromptSksUpdateForMad(args);
1053
+ if (update.status === 'updated') {
1054
+ console.log(`SKS updated from ${PACKAGE_VERSION} to ${update.latest}. Rerun: sks --mad`);
1055
+ return;
1056
+ }
1057
+ if (update.status === 'failed') {
1058
+ console.error(`SKS update failed: ${update.error}`);
1059
+ process.exitCode = 1;
1060
+ return;
1061
+ }
1062
+ const deps = await ensureMadLaunchDependencies(args);
1063
+ if (!deps.ready) {
1064
+ console.error('SKS MAD launch blocked by missing dependencies.');
1065
+ for (const action of deps.actions) printDepsInstallAction(action);
1066
+ process.exitCode = 1;
1067
+ return;
1068
+ }
1048
1069
  const profile = await enableMadHighProfile();
1049
- if (flag(args, '--json')) return console.log(JSON.stringify(profile, null, 2));
1050
- console.log(`SKS MAD high profile ready: ${madHighProfileName()}`);
1051
- console.log('Scope: explicit cmux launch only; normal SKS/DB safety returns after this command.');
1070
+ console.log(`SKS MAD auto-review profile ready: ${madHighProfileName()}`);
1071
+ console.log('Scope: explicit cmux launch only; full access uses Codex auto_review approvals when approval prompts are raised.');
1052
1072
  const workspace = readOption(cleanArgs, '--workspace', readOption(cleanArgs, '--session', `sks-mad-${defaultCmuxWorkspaceName(process.cwd())}`));
1053
1073
  return launchCmuxUi([...cleanArgs, '--workspace', workspace], {
1054
1074
  codexArgs: ['--profile', profile.profile_name],
@@ -1057,6 +1077,44 @@ async function madHighCommand(args = []) {
1057
1077
  });
1058
1078
  }
1059
1079
 
1080
+ async function maybePromptSksUpdateForMad(args = []) {
1081
+ if (flag(args, '--json') || flag(args, '--skip-update-check') || process.env.SKS_SKIP_UPDATE_CHECK === '1') return { status: 'skipped' };
1082
+ const latest = await npmPackageVersion('sneakoscope');
1083
+ if (!latest.version || compareVersions(latest.version, PACKAGE_VERSION) <= 0) return { status: 'current', latest: latest.version || null, error: latest.error || null };
1084
+ const command = 'npm i -g sneakoscope@latest';
1085
+ if (flag(args, '--yes') || flag(args, '-y')) return installSksLatest(command, latest.version);
1086
+ if (!canAskYesNo()) {
1087
+ console.log(`SKS update available: ${PACKAGE_VERSION} -> ${latest.version}. Run: ${command}`);
1088
+ return { status: 'available', latest: latest.version, command };
1089
+ }
1090
+ const answer = (await askPostinstallQuestion(`SKS ${PACKAGE_VERSION} -> ${latest.version} update before MAD launch? [Y/n] `)).trim();
1091
+ const yes = answer === '' || /^(y|yes|예|네|응)$/i.test(answer);
1092
+ if (!yes) return { status: 'skipped_by_user', latest: latest.version, command };
1093
+ return installSksLatest(command, latest.version);
1094
+ }
1095
+
1096
+ async function installSksLatest(command, latestVersion) {
1097
+ const npm = await which('npm').catch(() => null);
1098
+ if (!npm) return { status: 'failed', latest: latestVersion, command, error: 'npm not found on PATH' };
1099
+ const install = await runProcess(npm, ['i', '-g', 'sneakoscope@latest'], { timeoutMs: 180000, maxOutputBytes: 128 * 1024 }).catch((err) => ({ code: 1, stdout: '', stderr: err.message }));
1100
+ if (install.code !== 0) return { status: 'failed', latest: latestVersion, command, error: `${install.stderr || install.stdout || command + ' failed'}`.trim() };
1101
+ return { status: 'updated', latest: latestVersion, command };
1102
+ }
1103
+
1104
+ async function ensureMadLaunchDependencies(args = []) {
1105
+ const actions = [];
1106
+ if (!flag(args, '--skip-cli-tools')) {
1107
+ const codex = await getCodexInfo().catch(() => ({}));
1108
+ if (!codex.bin) actions.push(await installCodexDependency(args, { prompt: 'Codex CLI missing. Install latest Codex CLI with npm i -g @openai/codex@latest?' }));
1109
+ }
1110
+ if (!flag(args, '--no-auto-install-cmux')) {
1111
+ const cmux = await cmuxAvailable().catch(() => ({ ok: false }));
1112
+ if (!cmux.ok && !cmux.bin) actions.push(await installCmuxDependency(args));
1113
+ }
1114
+ const status = await depsStatus(await projectRoot());
1115
+ return { ready: Boolean(status.codex_cli.ok && (status.cmux.ok || status.cmux.bin)), actions, status };
1116
+ }
1117
+
1060
1118
  async function deps(sub = 'check', args = []) {
1061
1119
  const action = sub || 'check';
1062
1120
  if (action === 'check' || action === 'status') {
@@ -1098,7 +1156,7 @@ async function depsStatus(root = null, opts = {}) {
1098
1156
  context7,
1099
1157
  browser_use: { ok: app.mcp.has_browser_use, cache: app.plugins.browser_use_cache },
1100
1158
  computer_use: { ok: app.mcp.has_computer_use, cache: app.plugins.computer_use_cache },
1101
- cmux: { ok: Boolean(cmux.ok), version: cmux.version || null, install_hint: cmux.ok ? null : platformCmuxInstallHint(), error: cmux.error || null },
1159
+ cmux: { ok: Boolean(cmux.ok), bin: cmux.bin || null, version: cmux.version || null, install_hint: cmux.ok ? null : platformCmuxInstallHint(), error: cmux.error || null },
1102
1160
  homebrew: process.platform === 'darwin' ? { ok: Boolean(brew), bin: brew, required_for_cmux_install: homebrewNeeded } : { ok: null, bin: null, required_for_cmux_install: false },
1103
1161
  next_actions: depsNextActions({ npmBin, globalBin, codex, app, context7, cmux, brew, nodeOk })
1104
1162
  };
@@ -1153,10 +1211,11 @@ async function depsInstall(args = []) {
1153
1211
  if (!status.ready) process.exitCode = 1;
1154
1212
  }
1155
1213
 
1156
- async function installCodexDependency(args = []) {
1214
+ async function installCodexDependency(args = [], opts = {}) {
1157
1215
  const before = await getCodexInfo().catch(() => ({}));
1158
1216
  if (before.bin) return { target: 'codex', status: 'present', bin: before.bin, version: before.version || null };
1159
- if (!await confirmInstall('Install Codex CLI with npm i -g @openai/codex?', args)) return { target: 'codex', status: 'needs_approval', command: 'npm i -g @openai/codex' };
1217
+ const command = 'npm i -g @openai/codex@latest';
1218
+ if (!await confirmInstall(opts.prompt || `Install Codex CLI with ${command}?`, args)) return { target: 'codex', status: 'needs_approval', command };
1160
1219
  return { target: 'codex', ...(await ensureCodexCliTool()) };
1161
1220
  }
1162
1221
 
@@ -1172,10 +1231,10 @@ async function installCmuxDependency(args = []) {
1172
1231
  if (before.ok) return { target: 'cmux', status: 'present', version: before.version || null };
1173
1232
  if (process.platform === 'darwin') {
1174
1233
  const brew = await which('brew').catch(() => null);
1175
- const command = 'brew tap manaflow-ai/cmux && brew install --cask cmux';
1234
+ const command = CMUX_BREW_COMMAND;
1176
1235
  if (!brew) return { target: 'cmux', status: 'homebrew_missing', command: `/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" && ${command}` };
1177
1236
  if (flag(args, '--dry-run')) return { target: 'cmux', status: 'dry_run', command };
1178
- if (!await confirmInstall(`Install cmux with Homebrew (${command})?`, args)) return { target: 'cmux', status: 'needs_approval', command };
1237
+ if (!await confirmInstall(`Install/update latest cmux with Homebrew (${command}; ${CMUX_BREW_UPGRADE_COMMAND} if already installed)?`, args)) return { target: 'cmux', status: 'needs_approval', command };
1179
1238
  const installed = await ensureCmuxInstalled({ autoInstall: true });
1180
1239
  return {
1181
1240
  target: 'cmux',
@@ -1191,10 +1250,14 @@ async function installCmuxDependency(args = []) {
1191
1250
 
1192
1251
  async function confirmInstall(question, args = []) {
1193
1252
  if (flag(args, '--yes') || flag(args, '-y')) return true;
1194
- if (!input.isTTY || !output.isTTY || process.env.CI === 'true') return false;
1253
+ if (!canAskYesNo()) return false;
1195
1254
  return /^(y|yes|예|네|응)$/i.test((await askPostinstallQuestion(`${question} [y/N] `)).trim());
1196
1255
  }
1197
1256
 
1257
+ function canAskYesNo() {
1258
+ return Boolean(input.isTTY && output.isTTY && process.env.CI !== 'true');
1259
+ }
1260
+
1198
1261
  function printDepsInstallAction(action) {
1199
1262
  if (!action) return;
1200
1263
  console.log(`${action.target}: ${action.status}${action.version ? ` ${action.version}` : ''}`);
@@ -1767,6 +1830,8 @@ async function npmGlobalSksBin() {
1767
1830
  }
1768
1831
 
1769
1832
  async function npmPackageVersion(name) {
1833
+ const envName = `SKS_NPM_VIEW_${String(name || '').replace(/[^A-Za-z0-9]+/g, '_').toUpperCase()}_VERSION`;
1834
+ if (process.env[envName]) return { version: process.env[envName] };
1770
1835
  const npm = await which('npm').catch(() => null);
1771
1836
  if (!npm) return { error: 'npm not found' };
1772
1837
  const result = await runProcess(npm, ['view', name, 'version'], { timeoutMs: 5000, maxOutputBytes: 4096 });
@@ -2259,7 +2324,7 @@ async function selftest() {
2259
2324
  const madProfilePath = path.join(tmp, 'mad-codex-config.toml');
2260
2325
  const madProfile = await enableMadHighProfile({ configPath: madProfilePath });
2261
2326
  const madProfileText = await safeReadText(madProfilePath);
2262
- if (madProfile.profile_name !== 'sks-mad-high' || !madProfileText.includes('sandbox_mode = "danger-full-access"') || !madProfileText.includes('approval_policy = "never"') || !madProfileText.includes('model_reasoning_effort = "high"')) throw new Error('selftest failed: MAD high profile is not full-access high/fast');
2327
+ if (madProfile.profile_name !== 'sks-mad-high' || !madProfileText.includes('sandbox_mode = "danger-full-access"') || !madProfileText.includes('approval_policy = "on-request"') || !madProfileText.includes('approvals_reviewer = "auto_review"') || !madProfileText.includes('model_reasoning_effort = "high"')) throw new Error('selftest failed: MAD high profile is not full-access auto-review high');
2263
2328
  if (!isMadHighLaunch(['--mad', '--high']) || isMadHighLaunch(['db', '--mad'])) throw new Error('selftest failed: MAD high launch flag parsing is not top-level only');
2264
2329
  const guardBlocked = await checkHarnessModification(tmp, { tool_name: 'apply_patch', command: '*** Update File: .agents/skills/team/SKILL.md\n+tamper\n' });
2265
2330
  if (guardBlocked.action !== 'block') throw new Error('selftest failed: harness guard allowed skill tampering');
@@ -2654,11 +2719,11 @@ async function selftest() {
2654
2719
  const autoReviewEnabled = await enableAutoReview({ env: autoReviewEnv, high: true });
2655
2720
  if (!autoReviewEnabled.enabled || autoReviewEnabled.profile_name !== 'sks-auto-review-high' || !autoReviewEnabled.high_profile) throw new Error('selftest failed: auto-review high profile was not enabled');
2656
2721
  const autoReviewConfig = await safeReadText(path.join(autoReviewHome, '.codex', 'config.toml'));
2657
- if (!autoReviewConfig.includes('approvals_reviewer = "guardian_subagent"') || autoReviewConfig.includes('approvals_reviewer = "auto_review"') || !autoReviewConfig.includes('[profiles.sks-auto-review-high]')) throw new Error('selftest failed: auto-review config not written');
2722
+ if (!autoReviewConfig.includes('approvals_reviewer = "auto_review"') || autoReviewConfig.includes('approvals_reviewer = "guardian_subagent"') || !autoReviewConfig.includes('[profiles.sks-auto-review-high]')) throw new Error('selftest failed: auto-review config not written');
2658
2723
  const autoReviewDisabled = await disableAutoReview({ env: autoReviewEnv });
2659
2724
  if (autoReviewDisabled.enabled || autoReviewDisabled.approvals_reviewer !== 'user') throw new Error('selftest failed: auto-review disable did not restore user reviewer');
2660
2725
  const autoReviewDisabledConfig = await safeReadText(path.join(autoReviewHome, '.codex', 'config.toml'));
2661
- if (autoReviewDisabledConfig.includes('approvals_reviewer = "auto_review"')) throw new Error('selftest failed: auto-review disable left legacy invalid reviewer values');
2726
+ if (autoReviewDisabledConfig.includes('approvals_reviewer = "guardian_subagent"')) throw new Error('selftest failed: auto-review disable left legacy reviewer values');
2662
2727
  const analysisAgentExists = await exists(path.join(tmp, '.codex', 'agents', 'analysis-scout.toml'));
2663
2728
  if (!analysisAgentExists) throw new Error('selftest failed: analysis scout agent not installed');
2664
2729
  const teamAgentExists = await exists(path.join(tmp, '.codex', 'agents', 'team-consensus.toml'));
@@ -2,8 +2,8 @@ import os from 'node:os';
2
2
  import path from 'node:path';
3
3
  import { ensureDir, exists, readText, writeTextAtomic } from './fsx.mjs';
4
4
 
5
- export const AUTO_REVIEW_REVIEWER = 'guardian_subagent';
6
- export const LEGACY_AUTO_REVIEW_REVIEWER = 'auto_review';
5
+ export const AUTO_REVIEW_REVIEWER = 'auto_review';
6
+ export const LEGACY_AUTO_REVIEW_REVIEWER = 'guardian_subagent';
7
7
  export const AUTO_REVIEW_PROFILE = 'sks-auto-review';
8
8
  export const AUTO_REVIEW_HIGH_PROFILE = 'sks-auto-review-high';
9
9
  export const MAD_HIGH_PROFILE = 'sks-mad-high';
@@ -64,10 +64,12 @@ export async function enableMadHighProfile(opts = {}) {
64
64
  let next = upsertTable(current, `profiles.${MAD_HIGH_PROFILE}`, [
65
65
  `[profiles.${MAD_HIGH_PROFILE}]`,
66
66
  'model = "gpt-5.5"',
67
- 'approval_policy = "never"',
67
+ 'approval_policy = "on-request"',
68
+ `approvals_reviewer = "${AUTO_REVIEW_REVIEWER}"`,
68
69
  'sandbox_mode = "danger-full-access"',
69
70
  'model_reasoning_effort = "high"'
70
71
  ].join('\n'));
72
+ next = upsertAutoReviewPolicy(next);
71
73
  if (!next.endsWith('\n')) next += '\n';
72
74
  await writeTextAtomic(configPath, next);
73
75
  return {
@@ -75,7 +77,8 @@ export async function enableMadHighProfile(opts = {}) {
75
77
  profile_name: MAD_HIGH_PROFILE,
76
78
  launch_args: ['--profile', MAD_HIGH_PROFILE],
77
79
  sandbox_mode: 'danger-full-access',
78
- approval_policy: 'never',
80
+ approval_policy: 'on-request',
81
+ approvals_reviewer: AUTO_REVIEW_REVIEWER,
79
82
  model_reasoning_effort: 'high',
80
83
  scope: 'explicit_launch_only'
81
84
  };
@@ -111,7 +114,7 @@ export function autoReviewSummary(status = {}) {
111
114
  lines.push('Launch high mode with: sks --Auto-review --high');
112
115
  }
113
116
  if (status.legacy_invalid) {
114
- lines.push('', 'Legacy invalid reviewer value found: run sks auto-review enable or sks auto-review disable to rewrite Codex config.');
117
+ lines.push('', 'Legacy reviewer value found: run sks auto-review enable or sks auto-review disable to rewrite Codex config.');
115
118
  }
116
119
  return lines.join('\n');
117
120
  }
@@ -1,4 +1,5 @@
1
1
  import path from 'node:path';
2
+ import fsp from 'node:fs/promises';
2
3
  import { spawnSync } from 'node:child_process';
3
4
  import { exists, packageRoot, projectRoot, runProcess, sha256, which } from './fsx.mjs';
4
5
  import { getCodexInfo } from './codex-adapter.mjs';
@@ -14,6 +15,7 @@ export const SKS_CMUX_LOGO = [
14
15
  ].join('\n');
15
16
 
16
17
  export const CMUX_BREW_COMMAND = 'brew tap manaflow-ai/cmux && brew install --cask cmux';
18
+ export const CMUX_BREW_UPGRADE_COMMAND = 'brew tap manaflow-ai/cmux && brew upgrade --cask cmux';
17
19
 
18
20
  export function sanitizeCmuxWorkspaceName(input) {
19
21
  const base = String(input || 'sks').trim().replace(/[^A-Za-z0-9_.-]+/g, '-').replace(/^-+|-+$/g, '');
@@ -34,8 +36,8 @@ export function platformCmuxInstallHint() {
34
36
  if (process.platform !== 'darwin') return 'cmux is a native macOS app; install it on macOS 14+ from https://cmux.com or https://github.com/manaflow-ai/cmux.';
35
37
  return [
36
38
  CMUX_BREW_COMMAND,
37
- 'then expose the CLI if needed:',
38
- 'sudo ln -sf "/Applications/cmux.app/Contents/Resources/bin/cmux" /usr/local/bin/cmux'
39
+ 'then run:',
40
+ 'sks cmux check'
39
41
  ].join(' ');
40
42
  }
41
43
 
@@ -44,17 +46,43 @@ export async function findCmuxBinary() {
44
46
  if (env && await exists(env)) return env;
45
47
  const onPath = await which('cmux').catch(() => null);
46
48
  if (onPath) return onPath;
47
- const appBin = '/Applications/cmux.app/Contents/Resources/bin/cmux';
48
- if (process.platform === 'darwin' && await exists(appBin)) return appBin;
49
+ for (const candidate of cmuxBinaryCandidates()) {
50
+ if (await exists(candidate)) return candidate;
51
+ }
49
52
  return null;
50
53
  }
51
54
 
55
+ export function cmuxBinaryCandidates() {
56
+ if (process.platform !== 'darwin') return [];
57
+ const envApps = String(process.env.SKS_CMUX_APP_PATHS || '')
58
+ .split(path.delimiter)
59
+ .map((entry) => entry.trim())
60
+ .filter(Boolean);
61
+ const appBundles = [
62
+ ...envApps,
63
+ '/Applications/cmux.app',
64
+ '/Applications/Cmux.app',
65
+ '/Applications/CMUX.app',
66
+ path.join(process.env.HOME || '', 'Applications', 'cmux.app'),
67
+ '/opt/homebrew/Caskroom/cmux/latest/cmux.app'
68
+ ].filter(Boolean);
69
+ const candidates = [];
70
+ for (const app of appBundles) {
71
+ candidates.push(
72
+ path.join(app, 'Contents', 'Resources', 'bin', 'cmux'),
73
+ path.join(app, 'Contents', 'MacOS', 'cmux')
74
+ );
75
+ }
76
+ candidates.push('/opt/homebrew/bin/cmux', '/usr/local/bin/cmux');
77
+ return Array.from(new Set(candidates));
78
+ }
79
+
52
80
  export async function cmuxAvailable() {
53
81
  const bin = await findCmuxBinary();
54
82
  if (!bin) return { ok: false, bin: null, version: null, error: 'cmux CLI not found' };
55
- const probe = await runProcess(bin, ['list-workspaces', '--json'], { timeoutMs: 5000, maxOutputBytes: 16 * 1024 }).catch((err) => ({ code: 1, stderr: err.message, stdout: '' }));
83
+ const probe = await runProcess(bin, ['version'], { timeoutMs: 5000, maxOutputBytes: 16 * 1024 }).catch((err) => ({ code: 1, stderr: err.message, stdout: '' }));
56
84
  const text = `${probe.stdout || ''}${probe.stderr || ''}`.trim();
57
- return { ok: probe.code === 0, bin, version: text || 'cmux CLI', error: probe.code === 0 ? null : text || 'cmux workspace probe failed' };
85
+ return { ok: probe.code === 0, bin, version: text || 'cmux CLI', error: probe.code === 0 ? null : text || 'cmux CLI probe failed' };
58
86
  }
59
87
 
60
88
  export async function ensureCmuxInstalled(opts = {}) {
@@ -70,18 +98,28 @@ export async function ensureCmuxInstalled(opts = {}) {
70
98
  if (!brew) {
71
99
  return { target: 'cmux', status: 'homebrew_missing', cmux: before, command: CMUX_BREW_COMMAND, error: 'Homebrew is required for automatic cmux install' };
72
100
  }
73
- if (!opts.quiet) console.log('cmux CLI missing; installing cmux with Homebrew...');
101
+ if (!opts.quiet) console.log('cmux CLI missing; installing/updating cmux with Homebrew...');
74
102
  const tap = await runProcess(brew, ['tap', 'manaflow-ai/cmux'], { timeoutMs: 180000, maxOutputBytes: 128 * 1024 }).catch((err) => ({ code: 1, stdout: '', stderr: err.message }));
75
103
  const install = tap.code === 0
76
- ? await runProcess(brew, ['install', '--cask', 'cmux'], { timeoutMs: 300000, maxOutputBytes: 256 * 1024 }).catch((err) => ({ code: 1, stdout: '', stderr: err.message }))
104
+ ? await installOrUpgradeCmuxCask(brew)
77
105
  : tap;
78
106
  let after = await cmuxAvailable().catch((err) => ({ ok: false, error: err.message || 'cmux probe failed after install' }));
79
107
  if (!after.ok && after.bin) after = await wakeCmuxAndReprobe(after);
80
108
  if (after.ok) return { target: 'cmux', status: 'installed', cmux: after, command: CMUX_BREW_COMMAND };
81
- const rawError = `${install.stderr || install.stdout || after.error || 'brew install --cask cmux failed'}`.trim();
109
+ const installText = `${install.stderr || ''}\n${install.stdout || ''}`.trim();
110
+ const rawError = after.error || installText || 'brew install --cask cmux completed, but no working cmux CLI was found';
82
111
  return { target: 'cmux', status: 'failed', cmux: after, command: CMUX_BREW_COMMAND, code: install.code, error: rawError };
83
112
  }
84
113
 
114
+ async function installOrUpgradeCmuxCask(brew) {
115
+ const install = await runProcess(brew, ['install', '--cask', 'cmux'], { timeoutMs: 300000, maxOutputBytes: 256 * 1024 }).catch((err) => ({ code: 1, stdout: '', stderr: err.message }));
116
+ if (install.code === 0) return install;
117
+ const text = `${install.stderr || ''}\n${install.stdout || ''}`;
118
+ if (!/already installed|to upgrade|brew upgrade/i.test(text)) return install;
119
+ const upgrade = await runProcess(brew, ['upgrade', '--cask', 'cmux'], { timeoutMs: 300000, maxOutputBytes: 256 * 1024 }).catch((err) => ({ code: 1, stdout: '', stderr: err.message }));
120
+ return upgrade.code === 0 ? upgrade : install;
121
+ }
122
+
85
123
  export function codexLaunchCommand(root, codexBin, codexArgs = []) {
86
124
  const extraArgs = Array.isArray(codexArgs) ? codexArgs : [];
87
125
  return [
@@ -139,7 +177,7 @@ export function formatCmuxBanner(status = null) {
139
177
  '',
140
178
  'CLI-first runtime:',
141
179
  ' sks open a cmux Codex CLI workspace',
142
- ' sks --mad --high open one-shot MAD-SKS high reasoning workspace',
180
+ ' sks --mad open one-shot MAD full-access auto-review workspace',
143
181
  ' sks team "task" prepare Team mission and cmux multi-line agent view',
144
182
  '',
145
183
  'Useful terminal commands:',
@@ -171,6 +209,13 @@ export async function launchCmuxUi(args = [], opts = {}) {
171
209
  process.exitCode = 1;
172
210
  return { plan };
173
211
  }
212
+ const daemon = await ensureCmuxDaemonReady(plan.cmux);
213
+ if (!daemon.ok && !args.includes('--status-only')) {
214
+ const blocked = { ...plan, ready: false, cmux: { ...plan.cmux, ok: false, error: daemon.error || 'cmux app did not become ready' } };
215
+ printCmuxLaunchBlocked(blocked, { concise: opts.conciseBlockers, cmuxRepair });
216
+ process.exitCode = 1;
217
+ return { plan: blocked };
218
+ }
174
219
  if (!args.includes('--no-open')) await openCmuxApp().catch(() => null);
175
220
  const command = codexLaunchCommand(plan.root, plan.codex.bin, plan.codexArgs);
176
221
  const created = spawnSync(plan.cmux.bin, ['new-workspace', '--cwd', plan.root, '--command', command], { encoding: 'utf8', stdio: args.includes('--quiet') ? 'pipe' : 'inherit' });
@@ -190,11 +235,16 @@ function printCmuxLaunchBlocked(plan, opts = {}) {
190
235
  console.error('SKS cmux launch blocked.');
191
236
  if (!plan.cmux.ok) {
192
237
  const repair = opts.cmuxRepair;
193
- const prefix = repair?.status ? `cmux ${repair.status}` : 'cmux missing';
238
+ const installedButUnhealthy = Boolean(plan.cmux.bin);
239
+ const prefix = repair?.status
240
+ ? `cmux ${repair.status}`
241
+ : installedButUnhealthy
242
+ ? 'cmux app/socket unhealthy'
243
+ : 'cmux missing';
194
244
  console.error(`- ${prefix}: ${repair?.error || plan.cmux.error || 'cmux CLI not found'}`);
195
- console.error(`- Install command: ${repair?.command || CMUX_BREW_COMMAND}`);
245
+ console.error(`- ${installedButUnhealthy ? 'Repair command' : 'Install command'}: ${repair?.command || (installedButUnhealthy ? 'sks deps install cmux --yes' : CMUX_BREW_COMMAND)}`);
196
246
  }
197
- if (!plan.codex.bin) console.error('- Codex CLI missing. Install: npm i -g @openai/codex, or set SKS_CODEX_BIN.');
247
+ if (!plan.codex.bin) console.error('- Codex CLI missing. Install: npm i -g @openai/codex@latest, or set SKS_CODEX_BIN.');
198
248
  return;
199
249
  }
200
250
  console.log(formatCmuxBanner(plan.app));
@@ -205,15 +255,69 @@ function printCmuxLaunchBlocked(plan, opts = {}) {
205
255
  export async function openCmuxApp() {
206
256
  if (process.platform !== 'darwin') return { ok: false, reason: 'not_macos' };
207
257
  const run = await runProcess('open', ['-a', 'cmux'], { timeoutMs: 5000, maxOutputBytes: 16 * 1024 }).catch((err) => ({ code: 1, stderr: err.message, stdout: '' }));
208
- return { ok: run.code === 0, stdout: run.stdout || '', stderr: run.stderr || '' };
258
+ if (run.code === 0) return { ok: true, stdout: run.stdout || '', stderr: run.stderr || '' };
259
+ for (const app of ['/Applications/cmux.app', '/Applications/Cmux.app']) {
260
+ if (!await exists(app)) continue;
261
+ const byPath = await runProcess('open', [app], { timeoutMs: 5000, maxOutputBytes: 16 * 1024 }).catch((err) => ({ code: 1, stderr: err.message, stdout: '' }));
262
+ if (byPath.code === 0) return { ok: true, stdout: byPath.stdout || '', stderr: byPath.stderr || '' };
263
+ }
264
+ return { ok: false, stdout: run.stdout || '', stderr: run.stderr || '' };
209
265
  }
210
266
 
211
267
  async function wakeCmuxAndReprobe(fallback = {}) {
212
- if (process.platform !== 'darwin') return fallback;
268
+ return ensureCmuxDaemonReady(fallback);
269
+ }
270
+
271
+ async function ensureCmuxDaemonReady(cmux = {}) {
272
+ if (!cmux?.bin) return { ok: false, error: cmux?.error || 'cmux CLI not found' };
273
+ const first = await cmuxSocketProbe(cmux.bin);
274
+ if (first.ok) return { ...cmux, ok: true, error: null };
275
+ if (process.platform !== 'darwin') return first;
213
276
  const opened = await openCmuxApp().catch(() => null);
214
- if (!opened?.ok) return fallback;
277
+ if (!opened?.ok) return { ok: false, error: opened?.stderr || opened?.reason || first.error || 'cmux app launch failed' };
278
+ let last = first;
279
+ for (let i = 0; i < 8; i++) {
280
+ await new Promise((resolve) => setTimeout(resolve, 750));
281
+ last = await cmuxSocketProbe(cmux.bin);
282
+ if (last.ok) return { ...cmux, ok: true, error: null };
283
+ }
284
+ if (isRecoverableCmuxSocketError(last.error) && process.env.SKS_CMUX_NO_RESTART !== '1') {
285
+ await restartCmuxApp();
286
+ for (let i = 0; i < 8; i++) {
287
+ await new Promise((resolve) => setTimeout(resolve, 750));
288
+ last = await cmuxSocketProbe(cmux.bin);
289
+ if (last.ok) return { ...cmux, ok: true, error: null };
290
+ }
291
+ }
292
+ return { ok: false, error: last.error || 'cmux socket did not become ready' };
293
+ }
294
+
295
+ async function cmuxSocketProbe(bin) {
296
+ const probe = await runProcess(bin, ['list-workspaces', '--json'], { timeoutMs: 5000, maxOutputBytes: 16 * 1024 }).catch((err) => ({ code: 1, stderr: err.message, stdout: '' }));
297
+ const text = `${probe.stdout || ''}${probe.stderr || ''}`.trim();
298
+ return { ok: probe.code === 0, error: probe.code === 0 ? null : text || 'cmux socket probe failed' };
299
+ }
300
+
301
+ function isRecoverableCmuxSocketError(error) {
302
+ return /socket|broken pipe|receive timeout|connection refused/i.test(String(error || ''));
303
+ }
304
+
305
+ async function restartCmuxApp() {
306
+ if (process.platform !== 'darwin') return { ok: false, reason: 'not_macos' };
307
+ const quit = await runProcess('osascript', ['-e', 'tell application "cmux" to quit'], { timeoutMs: 8000, maxOutputBytes: 16 * 1024 }).catch((err) => ({ code: 1, stderr: err.message, stdout: '' }));
308
+ if (quit.code !== 0) {
309
+ await runProcess('pkill', ['-TERM', '-f', '/Applications/cmux.app/Contents/MacOS/cmux'], { timeoutMs: 8000, maxOutputBytes: 16 * 1024 }).catch(() => null);
310
+ }
215
311
  await new Promise((resolve) => setTimeout(resolve, 1500));
216
- return cmuxAvailable().catch(() => fallback);
312
+ await removeStaleCmuxSocket().catch(() => null);
313
+ return openCmuxApp();
314
+ }
315
+
316
+ async function removeStaleCmuxSocket() {
317
+ const home = process.env.HOME;
318
+ if (!home) return;
319
+ const sock = path.join(home, 'Library', 'Application Support', 'cmux', 'cmux.sock');
320
+ await fsp.rm(sock, { force: true });
217
321
  }
218
322
 
219
323
  export async function launchCmuxTeamView({ root, missionId, plan = {}, promptFile = null, json = false } = {}) {
package/src/core/fsx.mjs CHANGED
@@ -5,7 +5,7 @@ import os from 'node:os';
5
5
  import crypto from 'node:crypto';
6
6
  import { spawn } from 'node:child_process';
7
7
 
8
- export const PACKAGE_VERSION = '0.6.64';
8
+ export const PACKAGE_VERSION = '0.6.66';
9
9
  export const DEFAULT_PROCESS_TAIL_BYTES = 256 * 1024;
10
10
  export const DEFAULT_PROCESS_TIMEOUT_MS = 30 * 60 * 1000;
11
11
 
package/src/core/init.mjs CHANGED
@@ -404,10 +404,14 @@ export async function initProject(root, opts = {}) {
404
404
 
405
405
  await writeTextAtomic(path.join(root, '.codex', 'config.toml'), `[features]\ncodex_hooks = true\nmulti_agent = true\n\n[agents]\nmax_threads = 6\nmax_depth = 1\n\n${context7ConfigToml()}\n[agents.analysis_scout]\ndescription = "Read-only SKS scout."\nconfig_file = "./agents/analysis-scout.toml"\nnickname_candidates = ["Scout", "Mapper"]\n\n[agents.team_consensus]\ndescription = "SKS planning/debate agent."\nconfig_file = "./agents/team-consensus.toml"\nnickname_candidates = ["Consensus", "Atlas"]\n\n[agents.implementation_worker]\ndescription = "SKS bounded implementation worker."\nconfig_file = "./agents/implementation-worker.toml"\nnickname_candidates = ["Builder", "Mason"]\n\n[agents.db_safety_reviewer]\ndescription = "Read-only DB safety reviewer."\nconfig_file = "./agents/db-safety-reviewer.toml"\nnickname_candidates = ["Sentinel", "Ledger"]\n\n[agents.qa_reviewer]\ndescription = "Read-only QA reviewer."\nconfig_file = "./agents/qa-reviewer.toml"\nnickname_candidates = ["Verifier", "Scout"]\n\n[profiles.sks-task-medium]\nmodel = "gpt-5.5"\napproval_policy = "on-request"\nsandbox_mode = "workspace-write"\nmodel_reasoning_effort = "medium"\n\n[profiles.sks-logic-high]\nmodel = "gpt-5.5"\napproval_policy = "on-request"\nsandbox_mode = "workspace-write"\nmodel_reasoning_effort = "high"\n\n[profiles.sks-research-xhigh]\nmodel = "gpt-5.5"\napproval_policy = "on-request"\nsandbox_mode = "workspace-write"\nmodel_reasoning_effort = "xhigh"\n\n[profiles.sks-ralph]\nmodel = "gpt-5.5"\napproval_policy = "never"\nsandbox_mode = "workspace-write"\nmodel_reasoning_effort = "high"\n\n[profiles.sks-research]\nmodel = "gpt-5.5"\napproval_policy = "never"\nsandbox_mode = "workspace-write"\nmodel_reasoning_effort = "xhigh"\n\n[profiles.sks-team]\nmodel = "gpt-5.5"\napproval_policy = "on-request"\nsandbox_mode = "workspace-write"\nmodel_reasoning_effort = "high"\n\n[profiles.sks-mad-high]
406
406
  model = "gpt-5.5"
407
- approval_policy = "never"
407
+ approval_policy = "on-request"
408
+ approvals_reviewer = "auto_review"
408
409
  sandbox_mode = "danger-full-access"
409
410
  model_reasoning_effort = "high"
410
411
 
412
+ [auto_review]
413
+ policy = "Deny destructive database operations, credential exfiltration, persistent security weakening, broad file deletion, and writes outside the workspace unless explicitly authorized by the user."
414
+
411
415
  [profiles.sks-default]\nmodel = "gpt-5.5"\napproval_policy = "on-request"\nsandbox_mode = "workspace-write"\nmodel_reasoning_effort = "medium"\n`);
412
416
  created.push('.codex/config.toml');
413
417
 
@@ -334,7 +334,7 @@ export const COMMAND_CATALOG = [
334
334
  { name: 'deps', usage: 'sks deps check|install [cmux|codex|context7|all] [--yes]', description: 'Check or guided-install Node/npm PATH, Codex CLI/App, Context7, Browser Use, Computer Use, cmux, and Homebrew on macOS.' },
335
335
  { name: 'codex-app', usage: 'sks codex-app [check|open]', description: 'Check Codex App install and first-party MCP/plugin readiness, then show app setup files and examples.' },
336
336
  { name: 'cmux', usage: 'sks cmux [check|status] [--workspace name]', description: 'Open the SKS cmux runtime with the ㅅㅋㅅ ASCII status pane and Codex CLI.' },
337
- { name: 'mad-high', usage: 'sks --mad --high', description: 'Open a one-shot cmux Codex CLI workspace with the SKS MAD high full-access profile.' },
337
+ { name: 'mad', usage: 'sks --mad [--high]', description: 'Open a one-shot cmux Codex CLI workspace with the SKS MAD full-access auto-review profile.' },
338
338
  { name: 'auto-review', usage: 'sks auto-review status|enable|start [--high] | sks --Auto-review --high', description: 'Enable Codex automatic approval review and launch SKS cmux with the auto-review profile.' },
339
339
  { name: 'dollar-commands', usage: 'sks dollar-commands [--json]', description: 'List Codex App $ commands such as $DFix and $Team.' },
340
340
  { name: 'dfix', usage: 'sks dfix', description: 'Explain $DFix ultralight design/content fix mode.' },