sneakoscope 0.7.56 → 0.7.57

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
@@ -42,7 +42,7 @@ sks selftest --mock
42
42
 
43
43
  | Area | What it does |
44
44
  | --- | --- |
45
- | CLI runtime | Bare `sks` opens or reuses the default tmux Codex CLI workspace. `sks tmux open` remains the explicit form for session/workspace flags, and `sks --mad` launches the explicit full-access high-reasoning profile. |
45
+ | CLI runtime | Bare `sks` opens or reuses the default tmux Codex CLI workspace. `sks tmux open` remains the explicit form for session/workspace flags, and `sks --mad` launches a multi-pane MAD tmux cockpit with the explicit full-access high-reasoning profile. |
46
46
  | Codex App commands | Installs generated skills so `$Team`, `$From-Chat-IMG`, `$DFix`, `$QA-LOOP`, `$PPT`, `$Image-UX-Review`, `$UX-Review`, `$Goal`, `$DB`, `$Wiki`, `$Help`, and related routes are visible in prompt workflows. `sks codex-app remote-control` wraps Codex CLI 0.130.0+ headless remote control without falling back to older app-server internals. |
47
47
  | OpenClaw agents | Generates an OpenClaw skill package so OpenClaw agents can attach `sneakoscope-codex`, enable the `shell` tool, and discover/use SKS commands from the target repo root. |
48
48
  | Pipeline plans | Writes `pipeline-plan.json` for stateful routes so the runtime lane, kept stages, skipped stages, verification commands, and no-unrequested-fallback invariant are visible with `sks pipeline plan`. |
@@ -53,7 +53,7 @@ sks selftest --mock
53
53
  | PPT pipeline | Uses `$PPT` for simple, restrained, information-first HTML/PDF presentation artifacts, first asking delivery context, audience profile, STP strategy, decision context, and 3+ pain-point to solution/aha mappings before source research, design-system work, HTML/PDF export, and render QA. Independent strategy/render/file-write phases run in parallel where inputs allow and are recorded in `ppt-parallel-report.json`; editable source HTML is preserved under `source-html/`, PPT-only temporary build files are cleaned after completion, installed skills/MCPs outside the `$PPT` allowlist are ignored, generated image assets must use real `$imagegen`/`gpt-image-2` output when sealed in the contract, and `ppt-style-tokens.json` records the design SSOT plus fused source inputs. |
54
54
  | Image UX Review | Uses `$Image-UX-Review` / `$UX-Review` for UI/UX audits where source screenshots are first turned into generated annotated review images through Codex App `$imagegen`/`gpt-image-2`; those generated images are then read back into `image-ux-issue-ledger.json`, optional requested fixes are rechecked, and missing generated review images or text-only screenshot critique cannot pass `image-ux-review-gate.json`. |
55
55
  | Computer Use fast lane | Uses `$Computer-Use` / `$CU` for UI/browser/visual work that needs maximum speed: skip Team debate and upfront TriWiki loops, use Codex Computer Use directly, then refresh/validate TriWiki and run Honest Mode at final closeout. |
56
- | Goal | Provides a fast SKS bridge overlay for Codex native persisted `/goal` create, pause, resume, and clear controls; implementation continues through the selected SKS execution route. |
56
+ | Goal | Provides a fast SKS bridge overlay for Codex native persisted `/goal` create, pause, resume, and clear controls; an ambient non-disruptive Goal continuation overlay is also recorded in normal pipeline plans while implementation continues through the selected SKS execution route. |
57
57
  | TriWiki voxels | Maintains `.sneakoscope/wiki/context-pack.json` as the context SSOT with coordinate anchors, voxel metadata, `attention.use_first`, `attention.hydrate_first`, and prompt-bound mistake recall ledgers. |
58
58
  | Context7 | Requires current docs for external packages, APIs, MCPs, SDKs, and framework/runtime behavior when correctness depends on current guidance. |
59
59
  | Design SSOT | Treats `design.md` as the only design decision source of truth. `docs/Design-Sys-Prompt.md` is the builder prompt; getdesign.md, official getdesign docs, and curated DESIGN.md examples from `VoltAgent/awesome-design-md` are source inputs that must be fused into `design.md` or route-local style tokens instead of becoming parallel authorities. |
@@ -80,7 +80,7 @@ The default `sks` runtime checks npm for newer `sneakoscope` and `@openai/codex`
80
80
  - Checks npm for newer `sneakoscope` and `@openai/codex` versions before launch and asks whether to update when the terminal can answer y/n.
81
81
  - Installs the latest Codex CLI with `npm i -g @openai/codex@latest` when it is missing and you approve or pass `--yes`.
82
82
  - Requires tmux 3.x or newer before opening the session.
83
- - Creates or reuses a named detached tmux session and prints only the session, gate, attach, and blocker details needed to act.
83
+ - Creates a named detached tmux cockpit with multiple panes and prints only the session, gate, attach, and blocker details needed to act.
84
84
 
85
85
  ## Installation
86
86
 
@@ -167,7 +167,7 @@ sks tmux check
167
167
  sks tmux status --once
168
168
  ```
169
169
 
170
- Bare `sks` creates or reuses the default named tmux session for Codex CLI and attaches to it in an interactive terminal. By default it launches Codex in the SKS fast-high runtime (`--model gpt-5.5 -c model_reasoning_effort="high"`) with a static SKS 3D ASCII intro inside tmux; the animated intro is reserved for non-tmux unauthenticated Codex launches and can be disabled with `SKS_TMUX_LOGO_ANIMATION=0`. SKS always forces the model to `gpt-5.5`; `SKS_CODEX_MODEL` and `SKS_CODEX_FAST_HIGH=0` cannot downgrade or remove that model pin. You can still set `SKS_CODEX_REASONING` to change reasoning effort. Use `sks tmux open` when you need explicit `--workspace` / `--session` flags, `sks tmux check` for readiness without launching, and `sks help` for CLI help. Use `--no-attach` or `SKS_TMUX_NO_AUTO_ATTACH=1` when you only want SKS to create/reuse the session and print the manual attach command.
170
+ Bare `sks` creates or reuses the default named tmux session for Codex CLI and attaches to it in an interactive terminal. By default it launches Codex in Fast service tier with `--model gpt-5.5`, `-c service_tier="fast"`, and the selected `model_reasoning_effort` with a static SKS 3D ASCII intro inside tmux; the animated intro is reserved for non-tmux unauthenticated Codex launches and can be disabled with `SKS_TMUX_LOGO_ANIMATION=0`. SKS always forces the model to `gpt-5.5`; `SKS_CODEX_MODEL` and `SKS_CODEX_FAST_HIGH=0` cannot downgrade or remove that model pin. You can still set `SKS_CODEX_REASONING` to change reasoning effort. Use `sks tmux open` when you need explicit `--workspace` / `--session` flags, `sks tmux check` for readiness without launching, and `sks help` for CLI help. Use `--no-attach` or `SKS_TMUX_NO_AUTO_ATTACH=1` when you only want SKS to create/reuse the session and print the manual attach command.
171
171
 
172
172
  Before opening tmux, SKS checks the installed Codex CLI against npm `@openai/codex@latest`. If a newer version exists, it asks `Y/n`; answering `y` updates automatically with `npm i -g @openai/codex@latest` and then opens tmux with the updated Codex CLI.
173
173
 
@@ -211,7 +211,7 @@ sks --mad
211
211
  sks --mad --yes
212
212
  ```
213
213
 
214
- This syncs existing codex-lb/Codex CLI auth before launch, creates/uses the `sks-mad-high` Codex profile for a one-shot full-access, high-reasoning tmux session with `sandbox_mode = "danger-full-access"` and `approval_policy = "never"`, opens an active MAD-SKS permission gate for that tmux run, then launches Codex with `--sandbox danger-full-access --ask-for-approval never` and attaches to the session in an interactive terminal. If codex-lb is configured and no explicit `--workspace`/`--session` was passed, SKS opens a fresh tmux session so the repaired key is loaded by the Codex process immediately. While the gate is active, live server work, Supabase MCP database writes, direct SQL, targeted DML, schema cleanup, Supabase MCP `apply_migration`, and required Supabase CLI migration application such as `supabase migration up` or `supabase db push` are allowed. Catastrophic database wipe/all-row/project-management safeguards remain active. Repeat launches reuse the same named SKS MAD tmux session unless auth repair requires a fresh codex-lb session.
214
+ This syncs existing codex-lb/Codex CLI auth before launch, creates/uses the `sks-mad-high` Codex profile for a one-shot full-access, high-reasoning tmux cockpit with `sandbox_mode = "danger-full-access"` and `approval_policy = "never"`, opens an active MAD-SKS permission gate for that tmux run, then launches a tiled cockpit: Codex CLI, MAD permission gate status, and live guide panes. The cockpit recreates the named session on launch so stale single-pane sessions do not hide the split layout, then attaches in an interactive terminal. If codex-lb is configured and no explicit `--workspace`/`--session` was passed, SKS opens a fresh tmux session so the repaired key is loaded by the Codex process immediately. While the gate is active, live server work, Supabase MCP database writes, direct SQL, targeted DML, schema cleanup, Supabase MCP `apply_migration`, and required Supabase CLI migration application such as `supabase migration up` or `supabase db push` are allowed. Catastrophic database wipe/all-row/project-management safeguards remain active.
215
215
 
216
216
  MAD does not disable the pipeline contract: stages, executors, reviewers, and auto-review policy still must not invent unrequested fallback implementation code. If the requested path cannot be implemented, SKS should block with evidence rather than add substitute behavior.
217
217
 
@@ -239,10 +239,12 @@ sks team log latest
239
239
 
240
240
  By default, Team missions keep at least five QA/reviewer lanes active. Use explicit role counts only when you need to raise or otherwise pin the lane mix for a specific mission.
241
241
 
242
- Team mode prepares the mission, records live events, compiles runtime tasks and worker inboxes, writes schema-backed effort/work-order/dashboard artifacts, and opens a named tmux Team session with split live lanes when tmux is available. The default terminal output stays compact: mission id, agent count, role count, tmux status, watch command, and artifact directory. `sks team dashboard` renders the cockpit panes for mission overview, agent lanes, task DAG, QA/dogfood, artifacts/evidence, and performance.
242
+ Team mode prepares the mission, records live events, compiles runtime tasks and worker inboxes, writes schema-backed effort/work-order/dashboard artifacts, and opens a named tmux Team session with split live lanes by default when tmux is available. Use `--no-open-tmux` for artifact-only mission creation. The default terminal output stays compact: mission id, agent count, role count, tmux status, watch command, and artifact directory. `sks team dashboard` renders the cockpit panes for mission overview, agent lanes, task DAG, QA/dogfood, artifacts/evidence, and performance.
243
243
 
244
244
  The tmux Team launch is a live orchestration screen in one tmux window: the first pane follows `sks team watch <mission-id> --follow` as the mission overview, and neighboring split panes follow individual `sks team lane <mission-id> --agent <name> --follow` views. Pane headers show only mission, lane, phase, follow command, and cleanup command. SKS gives lanes role-specific colors, labels, and terminal titles, so scouts, planning/debate voices, executors, reviewers, and safety lanes are visually distinct while detailed evidence is mirrored into `team-transcript.jsonl`, `team-live.md`, and `team-dashboard.json`.
245
245
 
246
+ Team roster and runtime artifacts now include per-agent Fast reasoning metadata. Simple bounded Team lanes can use low reasoning, tool-heavy runtime/CLI/tmux work uses medium, and knowledge, current-docs, safety, DB, release, commit, or research-heavy lanes use high or xhigh as appropriate instead of opening every scout at high.
247
+
246
248
  Agent sessions communicate through the bounded Team transcript. Use `sks team message <mission-id|latest> --from <agent> --to <agent|all> --message "..."` to add direct or broadcast messages; lane panes show messages addressed to that agent plus the fallback global tail.
247
249
 
248
250
  When the Team route reaches `session_cleanup`, SKS marks the tmux session record complete and asks `watch --follow` / `lane --follow` panes to show a cleanup summary and stop. You can also run `sks team cleanup-tmux <mission-id|latest>` manually, or `sks team cleanup-tmux latest --close` to kill the recorded tmux session.
@@ -412,7 +414,7 @@ Use these inside Codex App or another agent prompt. They are prompt commands, no
412
414
  | --- | --- |
413
415
  | `$Team` | You want implementation, code changes, or substantial repo work. |
414
416
  | `$From-Chat-IMG` | You have a chat screenshot plus original attachments and want each visible request mapped to work. |
415
- | `$DFix` | You need a tiny design/content edit such as copy, label, color, spacing, or translation, with no Team/TriWiki/reflection recording and only a one-line DFix Honest check. |
417
+ | `$DFix` | You need Direct Fix work: tiny copy/config/docs/labels/spacing/translation/simple mechanical edits, with broad implementation still routed to Team and UI design specifics handled by the relevant UI/design route rules. |
416
418
  | `$Answer` | You want an answer only and no implementation should start. |
417
419
  | `$SKS` | You need setup, status, usage, or workflow help. |
418
420
  | `$QA-LOOP` | You want UI/API dogfooding, safe fixes, and rechecks. |
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "sneakoscope",
3
3
  "displayName": "ㅅㅋㅅ",
4
- "version": "0.7.56",
4
+ "version": "0.7.57",
5
5
  "description": "Sneakoscope Codex: database-safe Codex CLI/App harness with Team, Goal, AutoResearch, TriWiki, and Honest Mode.",
6
6
  "type": "module",
7
7
  "homepage": "https://github.com/mandarange/Sneakoscope-Codex#readme",
@@ -994,8 +994,8 @@ export async function selftestCodexLb(tmp) {
994
994
  if (!codexLbLaunch.includes('sks-codex-lb.env')) throw new Error('selftest failed: tmux launch command does not source codex-lb env file');
995
995
  if (!codexLbLaunch.includes("'--model' 'gpt-5.5'")) throw new Error('selftest failed: tmux launch command without args did not force GPT-5.5');
996
996
  if (!codexLbLaunch.includes('SKS_TMUX_LOGO_ANIMATION') || !codexLbLaunch.includes('SNEAKOSCOPE CODEX')) throw new Error('selftest failed: tmux launch command does not include the animated SKS logo intro');
997
- const madLaunchSource = await safeReadText(path.join(packageRoot(), 'src', 'cli', 'main.mjs'));
998
- if (!madLaunchSource.includes('const lb = await maybePromptCodexLbSetupForLaunch(args)') || !madLaunchSource.includes("const launchLb = lb.status === 'present'") || !madLaunchSource.includes('codexLbImmediateLaunchOpts(cleanArgs, launchLb')) throw new Error('selftest failed: MAD launch does not sync codex-lb auth and fresh-session launch options');
997
+ const madLaunchSource = await safeReadText(path.join(packageRoot(), 'src', 'cli', 'maintenance-commands.mjs'));
998
+ if (!madLaunchSource.includes('const lb = await deps.maybePromptCodexLbSetupForLaunch(args)') || !madLaunchSource.includes("const launchLb = lb.status === 'present'") || !madLaunchSource.includes('codexLbImmediateLaunchOpts(cleanArgs, launchLb')) throw new Error('selftest failed: MAD launch does not sync codex-lb auth and fresh-session launch options');
999
999
 
1000
1000
  }
1001
1001
 
package/src/cli/main.mjs CHANGED
@@ -75,11 +75,11 @@ import { GOAL_WORKFLOW_ARTIFACT } from '../core/goal-workflow.mjs';
75
75
  import { CODEX_APP_DOCS_URL, codexAppIntegrationStatus, formatCodexAppStatus } from '../core/codex-app.mjs';
76
76
  import { codexAppRemoteControlCommand } from './codex-app-command.mjs';
77
77
  import { OPENCLAW_SKILL_NAME, installOpenClawSkill } from '../core/openclaw.mjs';
78
- import { buildTmuxLaunchPlan, buildTmuxOpenArgs, codexLaunchCommand, createTmuxSession, isTmuxShellSession, runTmuxLaunchPlanSyntaxCheck, shouldAutoAttachTmux, tmuxReadiness, tmuxStatusKind, defaultTmuxSessionName, formatTmuxBanner, launchTmuxTeamView, launchTmuxUi, platformTmuxInstallHint, runTmuxStatus, sanitizeTmuxSessionName, teamLaneStyle } from '../core/tmux-ui.mjs';
78
+ import { buildTmuxLaunchPlan, buildTmuxOpenArgs, codexLaunchCommand, createTmuxSession, defaultCodexLaunchArgs, isTmuxShellSession, runTmuxLaunchPlanSyntaxCheck, shouldAutoAttachTmux, tmuxReadiness, tmuxStatusKind, defaultTmuxSessionName, formatTmuxBanner, launchMadTmuxUi, launchTmuxTeamView, launchTmuxUi, platformTmuxInstallHint, runTmuxStatus, sanitizeTmuxSessionName, teamLaneStyle } from '../core/tmux-ui.mjs';
79
79
  import { autoReviewProfileName, autoReviewStatus, autoReviewSummary, enableAutoReview, disableAutoReview, enableMadHighProfile, madHighProfileName } from '../core/auto-review.mjs';
80
80
  import { context7Command } from './context7-command.mjs';
81
81
  import { askPostinstallQuestion, checkContext7, checkRequiredSkills, codexLbStatus, configureCodexLb, ensureCodexCliTool, ensureGlobalCodexSkillsDuringInstall, ensureProjectContext7Config, ensureRelatedCliTools, ensureSksCommandDuringInstall, ensureTmuxCliTool, globalCodexSkillsRoot, maybePromptCodexLbSetupForLaunch, maybePromptCodexUpdateForLaunch, postinstall, postinstallBootstrapDecision, repairCodexLbAuth, selftestCodexLb, shouldAutoApproveInstall } from './install-helpers.mjs';
82
- import { buildTeamPlan, codeStructureCommand, dbCommand, defaultBeta, defaultVGraph, evalCommand, gcCommand, goalCommand, gxCommand, harnessCommand, hproofCommand, memoryCommand, migrateWikiContextPack, parseTeamCreateArgs, perfCommand, profileCommand, projectWikiClaims, proofFieldCommand, qaLoopCommand, quickstartCommand, researchCommand, skillDreamCommand, statsCommand, team, teamWorkflowMarkdown, validateArtifactsCommand, wikiCommand, wikiVoxelRowCount, writeWikiContextPack } from './maintenance-commands.mjs';
82
+ import { buildTeamPlan, codeStructureCommand, dbCommand, defaultBeta, defaultVGraph, evalCommand, gcCommand, goalCommand, gxCommand, harnessCommand, hproofCommand, madHighCommand as runMadHighCommand, memoryCommand, migrateWikiContextPack, parseTeamCreateArgs, perfCommand, profileCommand, projectWikiClaims, proofFieldCommand, qaLoopCommand, quickstartCommand, researchCommand, skillDreamCommand, statsCommand, team, teamWorkflowMarkdown, validateArtifactsCommand, wikiCommand, wikiVoxelRowCount, writeWikiContextPack } from './maintenance-commands.mjs';
83
83
  import { openClawCommand } from './openclaw-command.mjs';
84
84
 
85
85
  const flag = (args, name) => args.includes(name);
@@ -96,7 +96,7 @@ function installScopeFromArgs(args = [], fallback = 'global') {
96
96
  }
97
97
 
98
98
  export async function main(args) {
99
- if (isMadHighLaunch(args)) return madHighCommand(args);
99
+ if (isMadHighLaunch(args)) return runMadHighCommand(args, { maybePromptSksUpdateForLaunch, maybePromptCodexUpdateForLaunch, ensureMadLaunchDependencies, printDepsInstallAction, maybePromptCodexLbSetupForLaunch, packageVersion: PACKAGE_VERSION });
100
100
  if (isAutoReviewFlag(args[0])) return autoReviewCommand('start', args.slice(1));
101
101
  const [cmd, sub, ...rest] = args;
102
102
  const tail = sub === undefined ? [] : [sub, ...rest];
@@ -143,6 +143,16 @@ async function defaultTmuxCommand(args = []) {
143
143
  return launchTmuxUi(args, codexLbImmediateLaunchOpts(args, lb, { conciseBlockers: true }));
144
144
  }
145
145
 
146
+ function codexLbImmediateLaunchOpts(args = [], lb = {}, opts = {}) {
147
+ if (!lb?.ok || lb.status !== 'configured') return opts;
148
+ if (readOption(args, '--session', null) || readOption(args, '--workspace', null)) return opts;
149
+ const root = readOption(args, '--root', process.cwd());
150
+ const session = sanitizeTmuxSessionName(`sks-codex-lb-${Date.now().toString(36)}-${defaultTmuxSessionName(root)}`);
151
+ console.log(`codex-lb key loaded for this launch: ${lb.env_path}`);
152
+ console.log(`Using fresh tmux session: ${session}`);
153
+ return { ...opts, session };
154
+ }
155
+
146
156
  function help(args = []) {
147
157
  const topic = args[0];
148
158
  if (topic) return usage([topic]);
@@ -338,7 +348,7 @@ async function updateCheck(args = []) {
338
348
  if (result.update_available) console.log('Run: npm i -g sneakoscope');
339
349
  }
340
350
 
341
- const DOLLAR_DEFAULT_PIPELINE_TEXT = 'Default pipeline: questions -> $Answer, small design/content -> $DFix, presentation/PDF artifacts -> $PPT, image-generation UI/UX reviews -> $Image-UX-Review/$UX-Review, Computer Use UI/browser speed work -> $Computer-Use, code -> $Team. Use $From-Chat-IMG only for chat screenshot plus original attachments. Use $MAD-SKS only as an explicit scoped DB authorization modifier that can be combined with another $ route. No route may invent unrequested fallback implementation code.';
351
+ const DOLLAR_DEFAULT_PIPELINE_TEXT = 'Default pipeline: questions -> $Answer, tiny Direct Fix edits -> $DFix, presentation/PDF artifacts -> $PPT, image-generation UI/UX reviews -> $Image-UX-Review/$UX-Review, Computer Use UI/browser speed work -> $Computer-Use, code -> $Team. Use $From-Chat-IMG only for chat screenshot plus original attachments. Use $MAD-SKS only as an explicit scoped DB authorization modifier that can be combined with another $ route. No route may invent unrequested fallback implementation code.';
342
352
 
343
353
  function commands(args = []) {
344
354
  if (flag(args, '--json')) return console.log(JSON.stringify({ aliases: ['sks', 'sneakoscope'], dollar_commands: DOLLAR_COMMANDS, app_skill_aliases: DOLLAR_COMMAND_ALIASES, commands: COMMAND_CATALOG }, null, 2));
@@ -397,18 +407,20 @@ function dollarCommandNames() {
397
407
  }
398
408
 
399
409
  function dfixHelp() {
400
- console.log(`SKS DFix Mode
410
+ console.log(`SKS Direct Fix Mode
401
411
 
402
412
  Prompt command:
403
- $DFix <small design/content request>
413
+ $DFix <tiny direct fix request>
404
414
 
405
415
  Examples:
406
416
  $DFix 글자 색 파란색으로 바꿔줘
407
417
  $DFix 내용을 영어로 바꿔줘
408
418
  $DFix Change the CTA label to "Start"
419
+ $DFix Fix the README typo
420
+ $DFix Update the package version
409
421
 
410
422
  Purpose:
411
- Fast design/content fixes only. DFix bypasses the general SKS prompt pipeline and uses an ultralight, no-record task list.
423
+ Fast tiny direct edits only. Direct Fix bypasses the general SKS prompt pipeline and uses an ultralight, no-record task list.
412
424
 
413
425
  Rules:
414
426
  List the exact micro-edits, inspect only needed files, apply only those edits.
@@ -1151,130 +1163,6 @@ async function codexLbCommand(action = 'status', args = []) {
1151
1163
  process.exitCode = 1;
1152
1164
  }
1153
1165
 
1154
- function codexLbImmediateLaunchOpts(args = [], lb = {}, opts = {}) {
1155
- if (!lb?.ok || lb.status !== 'configured') return opts;
1156
- if (readOption(args, '--session', null) || readOption(args, '--workspace', null)) return opts;
1157
- const root = readOption(args, '--root', process.cwd());
1158
- const session = sanitizeTmuxSessionName(`sks-codex-lb-${Date.now().toString(36)}-${defaultTmuxSessionName(root)}`);
1159
- console.log(`codex-lb key loaded for this launch: ${lb.env_path}`);
1160
- console.log(`Using fresh tmux session: ${session}`);
1161
- return { ...opts, session };
1162
- }
1163
-
1164
- async function madHighCommand(args = []) {
1165
- const cleanArgs = args.filter((arg) => !['--mad', '--MAD', '--mad-sks', '--high', '--no-auto-install-tmux'].includes(arg));
1166
- if (flag(args, '--json')) {
1167
- const profile = await enableMadHighProfile();
1168
- return console.log(JSON.stringify(profile, null, 2));
1169
- }
1170
- const update = await maybePromptSksUpdateForLaunch(args, { label: 'MAD launch' });
1171
- if (update.status === 'updated') {
1172
- console.log(`SKS updated from ${PACKAGE_VERSION} to ${update.latest}. Rerun: sks --mad`);
1173
- return;
1174
- }
1175
- if (update.status === 'failed') {
1176
- console.error(`SKS update failed: ${update.error}`);
1177
- process.exitCode = 1;
1178
- return;
1179
- }
1180
- const codexUpdate = await maybePromptCodexUpdateForLaunch(args, { label: 'MAD launch' });
1181
- if (codexUpdate.status === 'failed' || codexUpdate.status === 'updated_not_reflected') {
1182
- console.error(`Codex CLI update failed: ${codexUpdate.error || 'updated version was not visible on PATH'}`);
1183
- process.exitCode = 1;
1184
- return;
1185
- }
1186
- const deps = await ensureMadLaunchDependencies(args);
1187
- if (!deps.ready) {
1188
- console.error('SKS MAD launch blocked by missing dependencies.');
1189
- for (const action of deps.actions) printDepsInstallAction(action);
1190
- process.exitCode = 1;
1191
- return;
1192
- }
1193
- const lb = await maybePromptCodexLbSetupForLaunch(args);
1194
- if (lb.status === 'missing_api_key') {
1195
- process.exitCode = 1;
1196
- return;
1197
- }
1198
- const profile = await enableMadHighProfile();
1199
- const madLaunch = await activateMadTmuxPermissionState(process.cwd());
1200
- console.log(`SKS MAD ready: ${madHighProfileName()} | gate ${madLaunch.mission_id}`);
1201
- console.log('Live full-access active; catastrophic DB wipe/all-row/project-management guards remain.');
1202
- const launchLb = lb.status === 'present' ? { ...lb, status: 'configured' } : lb;
1203
- const launchOpts = codexLbImmediateLaunchOpts(cleanArgs, launchLb, {
1204
- codexArgs: profile.launch_args,
1205
- autoInstallTmux: !flag(args, '--no-auto-install-tmux'),
1206
- conciseBlockers: true
1207
- });
1208
- const workspace = readOption(cleanArgs, '--workspace', readOption(cleanArgs, '--session', launchOpts.session || `sks-mad-${defaultTmuxSessionName(process.cwd())}`));
1209
- return launchTmuxUi([...cleanArgs, '--workspace', workspace], {
1210
- ...launchOpts,
1211
- codexArgs: profile.launch_args,
1212
- autoInstallTmux: !flag(args, '--no-auto-install-tmux'),
1213
- conciseBlockers: true
1214
- });
1215
- }
1216
-
1217
- async function activateMadTmuxPermissionState(cwd = process.cwd()) {
1218
- const root = await sksRoot();
1219
- if (!(await exists(path.join(root, '.sneakoscope')))) await initProject(root, {});
1220
- const { id, dir } = await createMission(root, { mode: 'mad-sks', prompt: 'sks --mad tmux live full-access session' });
1221
- const gate = {
1222
- schema_version: 1,
1223
- passed: false,
1224
- mad_sks_permission_active: true,
1225
- permissions_deactivated: false,
1226
- live_server_writes_allowed: true,
1227
- supabase_mcp_schema_cleanup_allowed: true,
1228
- direct_execute_sql_allowed: true,
1229
- normal_db_writes_allowed: true,
1230
- migration_apply_allowed: true,
1231
- catastrophic_safety_guard_active: true,
1232
- permission_profile: permissionGateSummary(),
1233
- activated_by: 'sks --mad',
1234
- cwd: path.resolve(cwd || process.cwd())
1235
- };
1236
- await writeJsonAtomic(path.join(dir, 'mad-sks-gate.json'), gate);
1237
- await writeJsonAtomic(path.join(dir, 'route-context.json'), {
1238
- route: 'MadSKS',
1239
- command: '$MAD-SKS',
1240
- mode: 'MADSKS',
1241
- task: gate.activated_by,
1242
- mad_sks_authorization: true,
1243
- tmux_launch: true,
1244
- permission_profile: gate.permission_profile
1245
- });
1246
- await appendJsonlBounded(path.join(dir, 'events.jsonl'), {
1247
- ts: nowIso(),
1248
- type: 'mad_sks.tmux_permission_opened',
1249
- route: 'MadSKS',
1250
- live_server_writes_allowed: true,
1251
- catastrophic_safety_guard_active: true
1252
- });
1253
- await setCurrent(root, {
1254
- mission_id: id,
1255
- route: 'MadSKS',
1256
- route_command: '$MAD-SKS',
1257
- mode: 'MADSKS',
1258
- phase: 'MADSKS_TMUX_PERMISSION_ACTIVE',
1259
- questions_allowed: false,
1260
- implementation_allowed: true,
1261
- mad_sks_active: true,
1262
- mad_sks_modifier: true,
1263
- mad_sks_gate_file: 'mad-sks-gate.json',
1264
- mad_sks_gate_ready: true,
1265
- live_server_writes_allowed: true,
1266
- supabase_mcp_schema_cleanup_allowed: true,
1267
- direct_execute_sql_allowed: true,
1268
- normal_db_writes_allowed: true,
1269
- migration_apply_allowed: true,
1270
- catastrophic_safety_guard_active: true,
1271
- permission_profile: gate.permission_profile,
1272
- stop_gate: 'mad-sks-gate.json',
1273
- prompt: gate.activated_by
1274
- });
1275
- return { mission_id: id, dir, gate };
1276
- }
1277
-
1278
1166
  async function maybePromptSksUpdateForLaunch(args = [], opts = {}) {
1279
1167
  if (flag(args, '--json') || flag(args, '--skip-update-check') || process.env.SKS_SKIP_UPDATE_CHECK === '1') return { status: 'skipped' };
1280
1168
  const latest = await npmPackageVersion('sneakoscope');
@@ -1712,7 +1600,7 @@ async function setup(args) {
1712
1600
  console.log(`Codex App: .codex/config.toml, .codex/hooks.json, .agents/skills, .codex/agents, .codex/SNEAKOSCOPE.md`);
1713
1601
  console.log(`Global $: ${globalSkills.status === 'installed' ? 'ok' : globalSkills.status} ${globalSkills.root || ''}`.trimEnd());
1714
1602
  console.log(`App tools: ${appRuntime.ok ? 'ok' : 'needs setup'} Codex App=${appRuntime.app.installed ? 'ok' : 'missing'} Browser Use=${appRuntime.mcp.has_browser_use ? 'ok' : 'missing'} Computer Use=${appRuntime.mcp.has_computer_use ? 'ok' : 'missing'} Image Gen=${appRuntime.features?.image_generation ? 'ok' : 'missing'}`);
1715
- console.log(`Prompt: intent-first routing, $Answer fact-check route, $DFix ultralight design/content route, $PPT HTML/PDF presentation route, Context7 gate`);
1603
+ console.log(`Prompt: intent-first routing, $Answer fact-check route, $DFix ultralight Direct Fix route, $PPT HTML/PDF presentation route, Context7 gate`);
1716
1604
  console.log(`Skills: .agents/skills`);
1717
1605
  console.log(`Next: sks context7 check; sks selftest --mock; sks commands; sks dollar-commands`);
1718
1606
  if (cliTools.codex.status === 'failed') console.log(`\nCodex CLI install failed. Run manually: npm i -g @openai/codex. ${cliTools.codex.error || ''}`.trim());
@@ -2266,7 +2154,7 @@ async function selftest() {
2266
2154
  const madProfilePath = path.join(tmp, 'mad-codex-config.toml');
2267
2155
  const madProfile = await enableMadHighProfile({ configPath: madProfilePath });
2268
2156
  const madProfileText = await safeReadText(madProfilePath);
2269
- if (madProfile.profile_name !== 'sks-mad-high' || !madProfileText.includes('sandbox_mode = "danger-full-access"') || !madProfileText.includes('approval_policy = "never"') || !madProfileText.includes('approvals_reviewer = "auto_review"') || !madProfile.launch_args.includes('--sandbox') || !madProfile.launch_args.includes('danger-full-access') || !madProfile.launch_args.includes('--ask-for-approval') || !madProfile.launch_args.includes('never') || !madProfileText.includes('model_reasoning_effort = "high"') || !madProfileText.includes('unrequested fallback implementation code')) throw new Error('selftest failed: MAD high profile is not Codex full-access high with fallback-code guard');
2157
+ if (madProfile.profile_name !== 'sks-mad-high' || !madProfileText.includes('sandbox_mode = "danger-full-access"') || !madProfileText.includes('approval_policy = "never"') || !madProfileText.includes('approvals_reviewer = "auto_review"') || !madProfileText.includes('service_tier = "fast"') || !madProfile.launch_args.includes('--sandbox') || !madProfile.launch_args.includes('danger-full-access') || !madProfile.launch_args.includes('--ask-for-approval') || !madProfile.launch_args.includes('never') || !madProfileText.includes('model_reasoning_effort = "high"') || !madProfileText.includes('unrequested fallback implementation code')) throw new Error('selftest failed: MAD high profile is not Codex full-access high with fallback-code guard');
2270
2158
  if (!isMadHighLaunch(['--mad', '--high']) || isMadHighLaunch(['db', '--mad'])) throw new Error('selftest failed: MAD high launch flag parsing is not top-level only');
2271
2159
  const workspacePlan = { session: 'sks-mad-selftest', root: tmp, codexArgs: madProfile.launch_args };
2272
2160
  const tmuxSyntax = runTmuxLaunchPlanSyntaxCheck(workspacePlan);
@@ -2274,11 +2162,11 @@ async function selftest() {
2274
2162
  const tmuxOpenArgs = buildTmuxOpenArgs(workspacePlan);
2275
2163
  if (tmuxOpenArgs.join(' ') !== 'attach-session -t sks-mad-selftest') throw new Error('selftest failed: MAD tmux attach args are not stable by session name');
2276
2164
  const defaultFastHighPlan = await buildTmuxLaunchPlan({ root: tmp, tmux: { ok: true, bin: 'tmux', version: '3.4' }, codex: { bin: 'codex', version: 'codex-cli 99.0.0' }, app: { ok: true } });
2277
- if (defaultFastHighPlan.codexArgs.join(' ') !== '--model gpt-5.5 -c model_reasoning_effort="high"') throw new Error('selftest failed: default sks tmux launch is not fast-high');
2165
+ if (defaultFastHighPlan.codexArgs.join(' ') !== '--model gpt-5.5 -c service_tier="fast" -c model_reasoning_effort="high"') throw new Error('selftest failed: default sks tmux launch is not fast-high');
2278
2166
  const forcedModelPlan = await buildTmuxLaunchPlan({ root: tmp, env: { SKS_CODEX_MODEL: 'gpt-5.4-mini', SKS_CODEX_FAST_HIGH: '0', SKS_CODEX_REASONING: 'medium' }, tmux: { ok: true, bin: 'tmux', version: '3.4' }, codex: { bin: 'codex', version: 'codex-cli 99.0.0' }, app: { ok: true } });
2279
- if (forcedModelPlan.codexArgs.includes('gpt-5.4-mini') || forcedModelPlan.codexArgs.join(' ') !== '--model gpt-5.5 -c model_reasoning_effort="medium"') throw new Error('selftest failed: sks tmux launch allowed a non-GPT-5.5 model override');
2167
+ if (forcedModelPlan.codexArgs.includes('gpt-5.4-mini') || forcedModelPlan.codexArgs.join(' ') !== '--model gpt-5.5 -c service_tier="fast" -c model_reasoning_effort="medium"') throw new Error('selftest failed: sks tmux launch allowed a non-GPT-5.5 model override');
2280
2168
  const explicitBadModelPlan = await buildTmuxLaunchPlan({ root: tmp, codexArgs: ['--profile', 'legacy-5.4', '--model', 'gpt-5.4-mini', '-c', 'model="gpt-5.4"', '-c', 'model_reasoning_effort="low"'], tmux: { ok: true, bin: 'tmux', version: '3.4' }, codex: { bin: 'codex', version: 'codex-cli 99.0.0' }, app: { ok: true } });
2281
- if (explicitBadModelPlan.codexArgs.join(' ').includes('gpt-5.4') || explicitBadModelPlan.codexArgs.join(' ') !== '--model gpt-5.5 --profile legacy-5.4 -c model_reasoning_effort="low"') throw new Error('selftest failed: explicit tmux model override was not forced back to GPT-5.5');
2169
+ if (explicitBadModelPlan.codexArgs.join(' ').includes('gpt-5.4') || explicitBadModelPlan.codexArgs.join(' ') !== '--model gpt-5.5 -c service_tier="fast" --profile legacy-5.4 -c model_reasoning_effort="low"') throw new Error('selftest failed: explicit tmux model override was not forced back to GPT-5.5');
2282
2170
  const codexExecArgs = buildCodexExecArgs({ root: tmp, prompt: 'model guard selftest', profile: 'legacy-5.4', extraArgs: ['--model=gpt-5.4-mini', '--config', 'model = "gpt-5.4"', '-c', 'model_reasoning_effort="medium"'] });
2283
2171
  if (codexExecArgs.join(' ').includes('gpt-5.4') || !codexExecArgs.includes('gpt-5.5') || codexExecArgs.includes('--model=gpt-5.4-mini')) throw new Error('selftest failed: codex exec args allowed a non-GPT-5.5 model override');
2284
2172
  await selftestCodexLb(tmp);
@@ -2545,8 +2433,27 @@ async function selftest() {
2545
2433
  if (routePrompt('[$research](/tmp/research/SKILL.md) Codex Computer Use 도구 노출 문제를 QA루프 관점에서 연구')?.id !== 'Research') throw new Error('selftest failed: markdown-linked $Research was not treated as explicit route');
2546
2434
  if (routePrompt('$WikiRefresh 갱신')) throw new Error('selftest failed: deprecated $WikiRefresh route still resolved');
2547
2435
  if (routePrompt('$MAD-SKS Supabase MCP main 작업')?.id !== 'MadSKS') throw new Error('selftest failed: $MAD-SKS route did not resolve');
2436
+ if (routePrompt('$MAD-SKS 버튼 라벨만 바꿔줘')?.id === 'DFix') throw new Error('selftest failed: $MAD-SKS tiny label fix incorrectly routed to DFix');
2548
2437
  if (routePrompt('$MAD-SKS $Team Supabase MCP main 작업')?.id !== 'Team') throw new Error('selftest failed: $MAD-SKS did not compose with $Team');
2438
+ if (routePrompt('$MAD-SKS $Team 버튼 라벨만 바꿔줘')?.id !== 'Team') throw new Error('selftest failed: $MAD-SKS $Team tiny fix did not stay on Team route');
2549
2439
  if (routePrompt('$DB Supabase 점검 $MAD-SKS')?.id !== 'DB') throw new Error('selftest failed: trailing $MAD-SKS changed primary route');
2440
+ if (routePrompt('Fix the typo in README')?.id !== 'DFix') throw new Error('selftest failed: inferred typo Direct Fix did not route to DFix');
2441
+ if (routePrompt('Update the package version to 1.2.3')?.id !== 'DFix') throw new Error('selftest failed: inferred package-version Direct Fix did not route to DFix');
2442
+ if (routePrompt('package.json version만 1.2.3으로 바꿔줘')?.id !== 'DFix') throw new Error('selftest failed: inferred package.json version Direct Fix did not route to DFix');
2443
+ if (routePrompt('How do I fix the typo in README?')?.id !== 'Answer') throw new Error('selftest failed: how-to Direct Fix question did not route to Answer');
2444
+ if (routePrompt('How do I change README title?')?.id !== 'Answer') throw new Error('selftest failed: how-to README title question did not route to Answer');
2445
+ if (routePrompt('How do I make a settings page?')?.id !== 'Answer') throw new Error('selftest failed: how-to create question did not route to Answer');
2446
+ if (routePrompt('How to create a new form component?')?.id !== 'Answer') throw new Error('selftest failed: how-to form component question did not route to Answer');
2447
+ if (routePrompt('How can I build a modal?')?.id !== 'Answer') throw new Error('selftest failed: how-can-I build question did not route to Answer');
2448
+ if (routePrompt('Make a button')?.id !== 'Team') throw new Error('selftest failed: create-style button work did not route to Team');
2449
+ if (routePrompt('Make a button that submits the form')?.id !== 'Team') throw new Error('selftest failed: form button creation did not route to Team');
2450
+ if (routePrompt('Change button to submit the form')?.id !== 'Team') throw new Error('selftest failed: form button behavior change did not route to Team');
2451
+ if (routePrompt('버튼이 폼 제출하게 바꿔줘')?.id !== 'Team') throw new Error('selftest failed: Korean form button behavior change did not route to Team');
2452
+ if (routePrompt('Can you change the button to submit the form?')?.id !== 'Team') throw new Error('selftest failed: polite form button behavior request did not route to Team');
2453
+ if (routePrompt('Change button label to Submit')?.id !== 'DFix') throw new Error('selftest failed: button label Direct Fix did not route to DFix');
2454
+ if (routePrompt('Change button text to Submit')?.id !== 'DFix') throw new Error('selftest failed: button text Direct Fix did not route to DFix');
2455
+ if (routePrompt('Can you change the button label to Save?')?.id !== 'DFix') throw new Error('selftest failed: polite button label Direct Fix did not route to DFix');
2456
+ if (routePrompt('Make README generator work')?.id !== 'Team') throw new Error('selftest failed: README generator implementation did not route to Team');
2550
2457
  const imageUxRoute = routePrompt('$Image-UX-Review localhost 화면 검수');
2551
2458
  if (imageUxRoute?.id !== 'ImageUXReview') throw new Error('selftest failed: $Image-UX-Review did not route to ImageUXReview');
2552
2459
  if (routePrompt('$UX-Review 스크린샷 gpt-image-2 콜아웃 리뷰')?.id !== 'ImageUXReview') throw new Error('selftest failed: $UX-Review did not route to ImageUXReview');
@@ -2939,6 +2846,16 @@ async function selftest() {
2939
2846
  if (await exists(path.join(hookDfixTmp, '.sneakoscope', 'state', 'light-route-stop.json'))) throw new Error('selftest failed: $DFix hook created persistent light-route state');
2940
2847
  const hookDfixState = await readJson(stateFile(hookDfixTmp), {});
2941
2848
  if (String(hookDfixState.phase || '').includes('CLARIFICATION_AWAITING_ANSWERS')) throw new Error('selftest failed: $DFix state entered clarification gate');
2849
+ const explicitDfixDirectTmp = tmpdir();
2850
+ await initProject(explicitDfixDirectTmp, {});
2851
+ const explicitDfixDirectPayload = JSON.stringify({ cwd: explicitDfixDirectTmp, prompt: '$DFix Update the docs config wording to Direct Fix' });
2852
+ const explicitDfixDirectResult = await runProcess(process.execPath, [hookBin, 'hook', 'user-prompt-submit'], { cwd: explicitDfixDirectTmp, input: explicitDfixDirectPayload, env: { SKS_DISABLE_UPDATE_CHECK: '1' }, timeoutMs: 15000, maxOutputBytes: 128 * 1024 });
2853
+ if (explicitDfixDirectResult.code !== 0) throw new Error(`selftest failed: explicit Direct Fix docs/config hook exited ${explicitDfixDirectResult.code}: ${explicitDfixDirectResult.stderr}`);
2854
+ const explicitDfixDirectJson = JSON.parse(explicitDfixDirectResult.stdout);
2855
+ const explicitDfixDirectContext = explicitDfixDirectJson.hookSpecificOutput?.additionalContext || '';
2856
+ if (!explicitDfixDirectContext.includes('DFix ultralight pipeline active')) throw new Error('selftest failed: explicit Direct Fix docs/config request did not use ultralight hook');
2857
+ if (explicitDfixDirectContext.includes('SKS skill-first pipeline active') || explicitDfixDirectContext.includes('Mission:')) throw new Error('selftest failed: explicit Direct Fix docs/config request leaked general pipeline context');
2858
+ if (await exists(path.join(explicitDfixDirectTmp, '.sneakoscope', 'state', 'light-route-stop.json'))) throw new Error('selftest failed: explicit Direct Fix docs/config hook created persistent light-route state');
2942
2859
  const inferredDfixPayload = JSON.stringify({ cwd: hookTeamTmp, prompt: '버튼 라벨 바꿔줘' });
2943
2860
  const inferredDfixResult = await runProcess(process.execPath, [hookBin, 'hook', 'user-prompt-submit'], { cwd: hookTeamTmp, input: inferredDfixPayload, env: { SKS_DISABLE_UPDATE_CHECK: '1' }, timeoutMs: 15000, maxOutputBytes: 128 * 1024 });
2944
2861
  if (inferredDfixResult.code !== 0) throw new Error(`selftest failed: inferred DFix hook exited ${inferredDfixResult.code}: ${inferredDfixResult.stderr}`);
@@ -3196,6 +3113,15 @@ async function selftest() {
3196
3113
  if (teamPlan.role_counts.executor !== 3 || teamPlan.role_counts.user !== 1 || teamPlan.role_counts.reviewer !== 5) throw new Error('selftest failed: team default role counts invalid');
3197
3114
  if (teamPlan.codex_config_required?.features?.hooks !== true || teamPlan.codex_config_required?.features?.codex_hooks === true) throw new Error('selftest failed: team plan Codex config still uses legacy hooks feature flag');
3198
3115
  if (!teamPlan.review_gate?.passed || teamPlan.review_gate.required_reviewer_lanes !== 5) throw new Error('selftest failed: team review policy gate did not pass default plan');
3116
+ if (teamPlan.codex_config_required?.service_tier !== 'fast' || teamPlan.reasoning?.service_tier !== 'fast') throw new Error('selftest failed: team plan did not require Fast service tier');
3117
+ if (!teamPlan.goal_continuation?.enabled || teamPlan.goal_continuation?.mode !== 'ambient_codex_native_goal_overlay') throw new Error('selftest failed: Team plan did not include ambient Goal continuation');
3118
+ if (!teamPlan.roster.analysis_team.every((agent) => agent.service_tier === 'fast' && agent.reasoning_effort && agent.reasoning_profile)) throw new Error('selftest failed: analysis scouts missing dynamic Fast reasoning metadata');
3119
+ const simpleTeamPlan = buildTeamPlan(teamId, '$Team 간단한 코드 수정');
3120
+ if (!simpleTeamPlan.roster.analysis_team.some((agent) => agent.reasoning_effort === 'low')) throw new Error('selftest failed: simple Team prompt did not allow low-reasoning scouts');
3121
+ const toolingTeamPlan = buildTeamPlan(teamId, '$Team tmux CLI tool-calling runtime fix');
3122
+ if (!toolingTeamPlan.roster.analysis_team.some((agent) => agent.reasoning_effort === 'medium')) throw new Error('selftest failed: tool-heavy Team prompt did not assign medium-reasoning scouts');
3123
+ const researchTeamPlan = buildTeamPlan(teamId, '$Team external library research and current docs update');
3124
+ if (!researchTeamPlan.roster.analysis_team.some((agent) => agent.reasoning_effort === 'high')) throw new Error('selftest failed: research/docs Team prompt did not assign high-reasoning scouts');
3199
3125
  const underProvisionedReviewCount = 2;
3200
3126
  const blockedReviewGate = evaluateTeamReviewPolicyGate({ roleCounts: { reviewer: underProvisionedReviewCount }, agentSessions: 3, roster: { validation_team: [{ id: 'reviewer_1', role: 'reviewer' }] } });
3201
3127
  if (blockedReviewGate.passed || !blockedReviewGate.blockers.includes('validation_team_reviewers_below_required')) throw new Error('selftest failed: team review policy gate did not block under-provisioned review');
@@ -3237,6 +3163,8 @@ async function selftest() {
3237
3163
  if (roleParsed.roleCounts.executor !== 5 || roleParsed.roleCounts.reviewer !== 6 || roleParsed.agentSessions !== 6 || roleParsed.prompt !== '작업') throw new Error('selftest failed: team role-count parsing');
3238
3164
  const openTmuxFlagParsed = parseTeamCreateArgs(['--open-tmux', '작업']);
3239
3165
  if (openTmuxFlagParsed.prompt !== '작업') throw new Error('selftest failed: team --open-tmux leaked into prompt');
3166
+ const noOpenTmuxFlagParsed = parseTeamCreateArgs(['--no-open-tmux', '작업']);
3167
+ if (noOpenTmuxFlagParsed.prompt !== '작업') throw new Error('selftest failed: team --no-open-tmux leaked into prompt');
3240
3168
  const roleTeamPlan = buildTeamPlan(teamId, '역할 팀 테스트', { roleCounts: roleParsed.roleCounts });
3241
3169
  if (roleTeamPlan.roster.debate_team.length !== 5) throw new Error('selftest failed: executor role count not reflected in debate team size');
3242
3170
  if (roleTeamPlan.roster.analysis_team.length !== 5) throw new Error('selftest failed: executor role count not reflected in analysis scout team');
@@ -3251,6 +3179,28 @@ async function selftest() {
3251
3179
  if (teamLaneStyle('analysis_scout_1').role !== 'scout' || teamLaneStyle('executor_1').role !== 'execution' || teamLaneStyle('reviewer_1').role !== 'review') throw new Error('selftest failed: Team tmux role palette did not classify lane roles');
3252
3180
  if (!String(tmuxTeam.cleanup_policy || '').includes('mark-complete') || !tmuxTeam.lanes.every((entry) => entry.style?.color && entry.title)) throw new Error('selftest failed: Team tmux view did not expose color/title metadata and cleanup policy');
3253
3181
  if (tmuxTeam.session !== `sks-team-${teamId}` || !tmuxTeam.attach_command?.includes(`sks-team-${teamId}`)) throw new Error('selftest failed: Team tmux session is not named for visibility');
3182
+ const fakeTmuxDir = path.join(tmp, 'fake-tmux');
3183
+ await ensureDir(fakeTmuxDir);
3184
+ const fakeTmuxLog = path.join(fakeTmuxDir, 'tmux.log');
3185
+ const fakeTmuxBin = path.join(fakeTmuxDir, 'tmux');
3186
+ await writeTextAtomic(fakeTmuxBin, `#!/usr/bin/env node\nconst fs = require('node:fs');\nconst log = process.env.SKS_FAKE_TMUX_LOG;\nif (log) fs.appendFileSync(log, process.argv.slice(2).join(' ') + '\\n');\nconst cmd = process.argv[2];\nif (cmd === 'has-session') process.exit(0);\nif (cmd === 'kill-session') process.exit(0);\nif (cmd === 'new-session') { console.log('%1'); process.exit(0); }\nif (cmd === 'split-window') { console.log('%2'); process.exit(0); }\nprocess.exit(0);\n`);
3187
+ await fsp.chmod(fakeTmuxBin, 0o755);
3188
+ const previousFakeTmuxLog = process.env.SKS_FAKE_TMUX_LOG;
3189
+ process.env.SKS_FAKE_TMUX_LOG = fakeTmuxLog;
3190
+ const recreatedTmux = await createTmuxSession({ root: tmp, session: 'sks-existing-selftest', tmux: { bin: fakeTmuxBin }, codex: { bin: process.execPath } }, [
3191
+ { cwd: tmp, command: 'pwd', role: 'overview' },
3192
+ { cwd: tmp, command: 'pwd', role: 'lane' }
3193
+ ], { recreate: true });
3194
+ const fakeTmuxLogText = await safeReadText(fakeTmuxLog);
3195
+ if (!recreatedTmux.ok || !fakeTmuxLogText.includes('kill-session -t sks-existing-selftest') || !fakeTmuxLogText.includes('new-session') || !fakeTmuxLogText.includes('split-window')) throw new Error('selftest failed: tmux recreate did not replace stale existing session with split panes');
3196
+ await writeTextAtomic(fakeTmuxLog, '');
3197
+ const madCockpit = await launchMadTmuxUi(['--workspace', 'sks-mad-selftest-ui', '--no-attach'], { root: tmp, tmux: { ok: true, bin: fakeTmuxBin, version: '3.4' }, codex: { bin: process.execPath }, app: { ok: true, guidance: [] }, missionId: 'M-MAD-SELFTEST' });
3198
+ const madTmuxLogText = await safeReadText(fakeTmuxLog);
3199
+ if (!madCockpit.created || madCockpit.opened?.panes?.length !== 3 || !madTmuxLogText.includes('new-session') || !madTmuxLogText.includes('split-window')) throw new Error('selftest failed: MAD tmux launch did not create a multi-pane cockpit');
3200
+ if (previousFakeTmuxLog === undefined) delete process.env.SKS_FAKE_TMUX_LOG;
3201
+ else process.env.SKS_FAKE_TMUX_LOG = previousFakeTmuxLog;
3202
+ const codexLaunchArgs = defaultCodexLaunchArgs({ SKS_CODEX_REASONING: 'low' }).join(' ');
3203
+ if (!codexLaunchArgs.includes('service_tier="fast"') || !codexLaunchArgs.includes('model_reasoning_effort="low"')) throw new Error('selftest failed: Codex tmux launch args do not force Fast service tier plus dynamic reasoning');
3254
3204
  await initTeamLive(teamId, teamDir, '역할 팀 테스트', { agentSessions: roleTeamPlan.agent_session_count, roleCounts: roleTeamPlan.role_counts, roster: roleTeamPlan.roster });
3255
3205
  const teamWatch = await renderTeamWatch(teamDir, { missionId: teamId });
3256
3206
  if (!roleTeamPlan.roster.analysis_team.every((agent) => teamWatch.includes(`- ${agent.id}:`))) throw new Error('selftest failed: Team watch overview collapsed numbered analysis scout lanes');
@@ -3258,6 +3208,9 @@ async function selftest() {
3258
3208
  if (routeReasoning(routePrompt('$From-Chat-IMG 채팅 이미지 작업'), '$From-Chat-IMG 채팅 이미지 작업').effort !== 'xhigh') throw new Error('selftest failed: From-Chat-IMG reasoning not xhigh');
3259
3209
  if (routeReasoning(routePrompt('$Computer-Use localhost UI smoke'), '$Computer-Use localhost UI smoke').effort !== 'low') throw new Error('selftest failed: Computer Use fast lane reasoning not low');
3260
3210
  if (routeReasoning(routePrompt('$DB migration'), '$DB migration').effort !== 'high') throw new Error('selftest failed: logical reasoning not high');
3211
+ if (routeReasoning(routePrompt('$Team 간단한 코드 수정'), '$Team 간단한 코드 수정').effort !== 'low') throw new Error('selftest failed: simple Team reasoning not low');
3212
+ if (routeReasoning(routePrompt('$Team tmux CLI tool-calling fix'), '$Team tmux CLI tool-calling fix').effort !== 'medium') throw new Error('selftest failed: tool-heavy Team reasoning not medium');
3213
+ if (routeReasoning(routePrompt('$Team library research current docs'), '$Team library research current docs').effort !== 'high') throw new Error('selftest failed: research/docs Team reasoning not high');
3261
3214
  const lowReasoning = routeReasoning({ id: 'LowSmoke', reasoningPolicy: 'low' }, 'small metadata read');
3262
3215
  if (lowReasoning.effort !== 'low' || lowReasoning.profile !== 'sks-task-low') throw new Error('selftest failed: low reasoning did not route to sks-task-low');
3263
3216
  const forensicEffort = selectEffort({ mission_id: 'selftest', task_id: 'TASK-IMG', route: 'from-chat-img', prompt: '$From-Chat-IMG screenshot match' });
@@ -3296,6 +3249,8 @@ async function selftest() {
3296
3249
  if (routePrompt('근데 왜 팀원 구성을 안하고 작업을 하는 경우가 이렇게 많지?')?.id !== 'Team') throw new Error('selftest failed: question-shaped Team complaint did not route to Team');
3297
3250
  if (routePrompt('$DF button label')) throw new Error('selftest failed: deprecated $DF route still resolved');
3298
3251
  if (routePrompt('implement feature')?.id !== 'Team') throw new Error('selftest failed: implementation prompt did not default to Team');
3252
+ const broadMadTeamGoalPrompt = 'sks --mad tmux multi pane scout reasoning commit push $team $goal';
3253
+ if (routePrompt(broadMadTeamGoalPrompt)?.id !== 'Team') throw new Error('selftest failed: broad MAD/Team/Goal tmux request was misrouted away from Team');
3299
3254
  if (routePrompt('$SKS implement feature')?.id !== 'Team') throw new Error('selftest failed: $SKS implementation prompt did not promote to Team');
3300
3255
  if (routePrompt('$From-Chat-IMG 채팅 기록 이미지와 첨부 이미지로 고객사 요청 수정 작업 수행해줘')?.id !== 'Team') throw new Error('selftest failed: explicit chat capture client work did not promote to Team');
3301
3256
  if (routePrompt('$Computer-Use localhost 화면 빠르게 검증해줘')?.id !== 'ComputerUse') throw new Error('selftest failed: $Computer-Use did not route to ComputerUse fast lane');