sneakoscope 0.7.6 → 0.7.13

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/src/cli/main.mjs CHANGED
@@ -23,9 +23,22 @@ import { rustInfo } from '../core/rust-accelerator.mjs';
23
23
  import { renderCartridge, validateCartridge, driftCartridge, snapshotCartridge } from '../core/gx-renderer.mjs';
24
24
  import { defaultEvaluationScenario, runEvaluationBenchmark } from '../core/evaluation.mjs';
25
25
  import { buildResearchPrompt, evaluateResearchGate, writeMockResearchResult, writeResearchPlan } from '../core/research.mjs';
26
+ import {
27
+ PPT_AUDIENCE_STRATEGY_ARTIFACT,
28
+ PPT_CLEANUP_REPORT_ARTIFACT,
29
+ PPT_GATE_ARTIFACT,
30
+ PPT_HTML_ARTIFACT,
31
+ PPT_PARALLEL_REPORT_ARTIFACT,
32
+ PPT_PDF_ARTIFACT,
33
+ PPT_RENDER_REPORT_ARTIFACT,
34
+ PPT_SOURCE_HTML_DIR,
35
+ PPT_TEMP_DIR,
36
+ writePptBuildArtifacts,
37
+ writePptRouteArtifacts
38
+ } from '../core/ppt.mjs';
26
39
  import { contextCapsule } from '../core/triwiki-attention.mjs';
27
40
  import { rgbaKey, rgbaToWikiCoord, validateWikiCoordinateIndex } from '../core/wiki-coordinate.mjs';
28
- import { ALLOWED_REASONING_EFFORTS, CODEX_COMPUTER_USE_EVIDENCE_SOURCE, CODEX_COMPUTER_USE_ONLY_POLICY, COMMAND_CATALOG, DOLLAR_COMMAND_ALIASES, DOLLAR_COMMANDS, DOLLAR_SKILL_NAMES, FROM_CHAT_IMG_CHECKLIST_ARTIFACT, FROM_CHAT_IMG_COVERAGE_ARTIFACT, FROM_CHAT_IMG_QA_LOOP_ARTIFACT, FROM_CHAT_IMG_SOURCE_INVENTORY_ARTIFACT, FROM_CHAT_IMG_TEMP_TRIWIKI_ARTIFACT, FROM_CHAT_IMG_TEMP_TRIWIKI_SESSIONS, FROM_CHAT_IMG_VISUAL_MAP_ARTIFACT, FROM_CHAT_IMG_WORK_ORDER_ARTIFACT, RECOMMENDED_SKILLS, ROUTES, USAGE_TOPICS, context7ConfigToml, hasContext7ConfigText, hasFromChatImgSignal, looksLikeAnswerOnlyRequest, noUnrequestedFallbackCodePolicyText, reflectionRequiredForRoute, reasoningInstruction, routePrompt, routeReasoning, routeRequiresSubagents, speedLanePolicyText, stackCurrentDocsPolicy, triwikiContextTracking } from '../core/routes.mjs';
41
+ import { ALLOWED_REASONING_EFFORTS, CODEX_APP_IMAGE_GENERATION_DOC_URL, CODEX_COMPUTER_USE_EVIDENCE_SOURCE, CODEX_COMPUTER_USE_ONLY_POLICY, COMMAND_CATALOG, DOLLAR_COMMAND_ALIASES, DOLLAR_COMMANDS, DOLLAR_SKILL_NAMES, FROM_CHAT_IMG_CHECKLIST_ARTIFACT, FROM_CHAT_IMG_COVERAGE_ARTIFACT, FROM_CHAT_IMG_QA_LOOP_ARTIFACT, FROM_CHAT_IMG_SOURCE_INVENTORY_ARTIFACT, FROM_CHAT_IMG_TEMP_TRIWIKI_ARTIFACT, FROM_CHAT_IMG_TEMP_TRIWIKI_SESSIONS, FROM_CHAT_IMG_VISUAL_MAP_ARTIFACT, FROM_CHAT_IMG_WORK_ORDER_ARTIFACT, GETDESIGN_REFERENCE, RECOMMENDED_SKILLS, ROUTES, USAGE_TOPICS, context7ConfigToml, hasContext7ConfigText, hasFromChatImgSignal, looksLikeAnswerOnlyRequest, noUnrequestedFallbackCodePolicyText, reflectionRequiredForRoute, reasoningInstruction, routePrompt, routeReasoning, routeRequiresSubagents, speedLanePolicyText, stackCurrentDocsPolicy, triwikiContextTracking } from '../core/routes.mjs';
29
42
  import { PIPELINE_PLAN_ARTIFACT, buildPipelinePlan, context7Evidence, evaluateStop, recordContext7Evidence, recordSubagentEvidence, validatePipelinePlan, writePipelinePlan } from '../core/pipeline.mjs';
30
43
  import { TEAM_DECOMPOSITION_ARTIFACT, TEAM_GRAPH_ARTIFACT, TEAM_INBOX_DIR, TEAM_RUNTIME_TASKS_ARTIFACT, validateTeamRuntimeArtifacts, writeTeamRuntimeArtifacts } from '../core/team-dag.mjs';
31
44
  import { appendTeamEvent, initTeamLive, parseTeamSpecText, readTeamDashboard, readTeamLive, readTeamTranscriptTail, renderTeamAgentLane } from '../core/team-live.mjs';
@@ -43,8 +56,10 @@ import { buildPromptContext } from '../core/prompt-context-builder.mjs';
43
56
  import { renderTeamDashboardState, writeTeamDashboardState } from '../core/team-dashboard-renderer.mjs';
44
57
  import { GOAL_WORKFLOW_ARTIFACT } from '../core/goal-workflow.mjs';
45
58
  import { CODEX_APP_DOCS_URL, codexAppIntegrationStatus, formatCodexAppStatus } from '../core/codex-app.mjs';
46
- import { buildWarpLaunchConfigYaml, buildWarpLaunchPlan, buildWarpOpenArgs, isWarpShellSession, runWarpLaunchConfigSyntaxCheck, warpOpenLaunchDecision, warpReadiness, warpStatusKind, defaultWarpWorkspaceName, formatWarpBanner, launchWarpTeamView, launchWarpUi, platformWarpInstallHint, runWarpStatus, sanitizeWarpWorkspaceName, teamLaneStyle, writeWarpLaunchConfig } from '../core/warp-ui.mjs';
59
+ import { buildTmuxLaunchPlan, buildTmuxOpenArgs, createTmuxSession, isTmuxShellSession, runTmuxLaunchPlanSyntaxCheck, tmuxReadiness, tmuxStatusKind, defaultTmuxSessionName, formatTmuxBanner, launchTmuxTeamView, launchTmuxUi, platformTmuxInstallHint, runTmuxStatus, sanitizeTmuxSessionName, teamLaneStyle } from '../core/tmux-ui.mjs';
47
60
  import { autoReviewProfileName, autoReviewStatus, autoReviewSummary, enableAutoReview, disableAutoReview, enableMadHighProfile, madHighProfileName } from '../core/auto-review.mjs';
61
+ import { context7Command } from './context7-command.mjs';
62
+ import { askPostinstallQuestion, checkContext7, checkRequiredSkills, ensureCodexCliTool, ensureGlobalCodexSkillsDuringInstall, ensureProjectContext7Config, ensureRelatedCliTools, ensureSksCommandDuringInstall, globalCodexSkillsRoot, postinstall, postinstallBootstrapDecision } from './install-helpers.mjs';
48
63
  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';
49
64
 
50
65
  const flag = (args, name) => args.includes(name);
@@ -68,9 +83,9 @@ export async function main(args) {
68
83
  if (!cmd) return help();
69
84
  if (cmd === '--help' || cmd === '-h') return help();
70
85
  if (cmd === '--version' || cmd === '-v' || cmd === 'version') return version();
71
- if (cmd === 'postinstall') return postinstall();
86
+ if (cmd === 'postinstall') return postinstall({ bootstrap });
72
87
  if (cmd === 'wizard' || cmd === 'ui') return wizard(tail);
73
- if (cmd === 'warp') return !sub || String(sub).startsWith('--') ? warpCommand('check', tail) : warpCommand(sub, rest);
88
+ if (cmd === 'tmux') return !sub || String(sub).startsWith('--') ? tmuxCommand('check', tail) : tmuxCommand(sub, rest);
74
89
  if (cmd === 'auto-review' || cmd === 'autoreview') return autoReviewCommand(sub, rest);
75
90
  if (cmd === 'update-check') return updateCheck(tail);
76
91
  if (cmd === 'help') return help(tail);
@@ -84,7 +99,8 @@ export async function main(args) {
84
99
  if (cmd === 'dollar-commands' || cmd === 'dollars' || cmd === '$') return dollarCommands(tail);
85
100
  if (String(cmd).toLowerCase() === 'dfix') return dfixHelp();
86
101
  if (cmd === 'qa-loop') return qaLoopCommand(sub, rest);
87
- if (cmd === 'context7') return context7(sub, rest);
102
+ if (cmd === 'ppt') return pptCommand(sub, rest);
103
+ if (cmd === 'context7') return context7Command(sub, rest);
88
104
  if (cmd === 'pipeline') return pipeline(sub, rest);
89
105
  if (cmd === 'guard') return guard(sub, rest);
90
106
  if (cmd === 'conflicts') return conflicts(sub, rest);
@@ -135,19 +151,21 @@ Usage:
135
151
  sks root [--json]
136
152
  sks quickstart
137
153
  sks bootstrap [--install-scope global|project] [--local-only] [--json]
138
- sks deps check|install [warp|codex|context7|all] [--yes] [--json]
154
+ sks deps check|install [tmux|codex|context7|all] [--yes] [--json]
139
155
  sks codex-app
140
156
  sks --mad [--high]
141
157
  sks auto-review status|enable|start [--high]
142
158
  sks --Auto-review [--high]
143
- sks warp open [--workspace name]
144
- sks warp status [--once]
159
+ sks tmux open [--workspace name]
160
+ sks tmux status [--once]
145
161
  sks dollar-commands [--json]
146
162
  sks dfix
147
163
  sks qa-loop prepare "target"
148
164
  sks qa-loop answer <mission-id|latest> <answers.json>
149
165
  sks qa-loop run <mission-id|latest> [--mock] [--max-cycles N]
150
166
  sks qa-loop status <mission-id|latest>
167
+ sks ppt build <mission-id|latest> [--json]
168
+ sks ppt status <mission-id|latest> [--json]
151
169
  sks context7 check|setup|tools|resolve|docs|evidence ...
152
170
  sks pipeline status|resume|plan [--json] [--proof-field]
153
171
  sks pipeline answer <mission-id|latest> <answers.json>
@@ -168,7 +186,7 @@ Usage:
168
186
  sks team log|tail|watch|lane|status|dashboard [mission-id|latest]
169
187
  sks team event [mission-id|latest] --agent <name> --phase <phase> --message "..."
170
188
  sks team message [mission-id|latest] --from <agent> --to <agent|all> --message "..."
171
- sks team cleanup-warp [mission-id|latest]
189
+ sks team cleanup-tmux [mission-id|latest]
172
190
  sks research prepare "topic" [--depth frontier]
173
191
  sks research run <mission-id|latest> [--mock] [--max-cycles N]
174
192
  sks research status <mission-id|latest>
@@ -228,230 +246,6 @@ function isMadHighLaunch(args = []) {
228
246
  return /^--(?:mad|MAD|mad-sks)$/i.test(String(args[0] || ''));
229
247
  }
230
248
 
231
- async function postinstall() {
232
- const installRoot = path.resolve(process.env.INIT_CWD || process.cwd());
233
- const conflictScan = await scanHarnessConflicts(installRoot);
234
- if (conflictScan.hard_block) {
235
- await postinstallHarnessConflictNotice(conflictScan);
236
- return;
237
- }
238
- console.log('\nSKS installed.');
239
- const shim = await ensureSksCommandDuringInstall();
240
- if (shim.status === 'present') console.log(`SKS command: available (${shim.command}).`);
241
- else if (shim.status === 'created') console.log(`SKS command: shim created at ${shim.command}.`);
242
- else if (shim.status === 'created_not_on_path') console.log(`SKS command: shim created at ${shim.command}. Add ${path.dirname(shim.command)} to PATH, or run npx -y -p sneakoscope sks.`);
243
- else if (shim.status === 'skipped') console.log(`SKS command: skipped (${shim.reason}).`);
244
- else console.log(`SKS command: shim unavailable. Use npx -y -p sneakoscope sks. ${shim.error || ''}`.trim());
245
- const context7Install = await ensureGlobalContext7DuringInstall();
246
- if (context7Install.status === 'present') console.log('Context7 MCP: already configured for Codex.');
247
- else if (context7Install.status === 'installed') console.log('Context7 MCP: configured for Codex.');
248
- else if (context7Install.status === 'codex_missing') console.log('Context7 MCP: Codex CLI missing. Install @openai/codex or set SKS_CODEX_BIN, then run `sks context7 setup --scope global` or `sks setup` in a project.');
249
- else if (context7Install.status === 'skipped') console.log(`Context7 MCP: skipped (${context7Install.reason}).`);
250
- else if (context7Install.status === 'failed') console.log(`Context7 MCP: auto setup failed. Run \`sks context7 setup --scope global\` or \`sks setup\`. ${context7Install.error || ''}`.trim());
251
- const globalSkills = await ensureGlobalCodexSkillsDuringInstall();
252
- if (globalSkills.status === 'installed') console.log(`Codex App global $ skills: installed in ${globalSkills.root} (${globalSkills.installed_count} skills).`);
253
- else if (globalSkills.status === 'partial') console.log(`Codex App global $ skills: partial in ${globalSkills.root}; missing ${globalSkills.missing_skills.join(', ')}. Run \`sks doctor --fix\`.`);
254
- else if (globalSkills.status === 'skipped') console.log(`Codex App global $ skills: skipped (${globalSkills.reason}).`);
255
- else if (globalSkills.status === 'failed') console.log(`Codex App global $ skills: auto setup failed. Run \`sks doctor --fix\`. ${globalSkills.error || ''}`.trim());
256
- const bootstrapDecision = await postinstallBootstrapDecision(installRoot);
257
- if (bootstrapDecision.run) {
258
- console.log(`SKS bootstrap: ${bootstrapDecision.reason}.`);
259
- await runPostinstallBootstrap(installRoot);
260
- return;
261
- }
262
- console.log('\nNext:');
263
- console.log(' sks bootstrap');
264
- console.log(`\nSKS bootstrap was not run automatically: ${bootstrapDecision.reason}.`);
265
- console.log('This initializes the current project, installs SKS Codex App skills, verifies Codex App/Context7 readiness, and checks warp/runtime dependencies.');
266
- console.log('Dependency repair: sks deps check; sks deps install warp');
267
- console.log('Open runtime after readiness is green: sks\n');
268
- }
269
-
270
- async function postinstallHarnessConflictNotice(conflictScan) {
271
- console.log('\nSneakoscope Codex package installed, but SKS setup is blocked.');
272
- console.log(formatHarnessConflictReport(conflictScan, { includePrompt: false }));
273
- console.log('\nWhat this means: npm can finish installing the package, but `sks setup` and `sks doctor --fix` will refuse to activate SKS until the conflicting harness is removed with human approval.');
274
- console.log('No files were removed by postinstall.');
275
- console.log('Cleanup requires a human-approved Codex App session. Recommended model: GPT-5.5, reasoning: high.');
276
- if (shouldAskPostinstallQuestion()) {
277
- const answer = await askPostinstallQuestion('Show the cleanup prompt now? [y/N] ');
278
- if (/^(y|yes|예|네|응)$/i.test(answer.trim())) {
279
- console.log('\nCleanup prompt:\n');
280
- console.log(llmHarnessCleanupPrompt(conflictScan));
281
- } else {
282
- console.log('Cleanup prompt skipped. You can print it later with: sks conflicts prompt');
283
- }
284
- } else {
285
- console.log('Print the cleanup prompt later with: sks conflicts prompt');
286
- }
287
- console.log('After approved cleanup, rerun: sks setup && sks doctor --fix && sks selftest --mock\n');
288
- }
289
-
290
- function shouldAskPostinstallQuestion() {
291
- if (process.env.SKS_POSTINSTALL_PROMPT === '1') return true;
292
- return Boolean(input.isTTY && output.isTTY && process.env.CI !== 'true' && process.env.SKS_POSTINSTALL_NO_PROMPT !== '1');
293
- }
294
-
295
- async function postinstallBootstrapDecision(root) {
296
- if (process.env.SKS_POSTINSTALL_NO_BOOTSTRAP === '1') return { run: false, reason: 'SKS_POSTINSTALL_NO_BOOTSTRAP=1' };
297
- if (process.env.SKS_POSTINSTALL_BOOTSTRAP === '0') return { run: false, reason: 'SKS_POSTINSTALL_BOOTSTRAP=0' };
298
- const candidate = await isProjectSetupCandidate(path.resolve(root || process.cwd()));
299
- if (!candidate && process.env.SKS_POSTINSTALL_BOOTSTRAP !== '1') return { run: false, reason: 'no project marker found in install cwd' };
300
- if (process.env.SKS_POSTINSTALL_BOOTSTRAP === '1') return { run: true, reason: 'forced by SKS_POSTINSTALL_BOOTSTRAP=1' };
301
- return { run: true, reason: 'auto-running sks setup --bootstrap --install-scope global --force' };
302
- }
303
-
304
- async function runPostinstallBootstrap(root) {
305
- const previousCwd = process.cwd();
306
- process.chdir(path.resolve(root || previousCwd));
307
- try {
308
- await bootstrap(['--from-postinstall', '--install-scope', 'global', '--force']);
309
- } finally {
310
- process.chdir(previousCwd);
311
- }
312
- }
313
-
314
- async function askPostinstallQuestion(question) {
315
- const rl = readline.createInterface({ input, output });
316
- try {
317
- return await rl.question(question);
318
- } finally {
319
- rl.close();
320
- }
321
- }
322
-
323
- async function ensureSksCommandDuringInstall(opts = {}) {
324
- if (process.env.SKS_SKIP_POSTINSTALL_SHIM === '1' && !opts.force) return { status: 'skipped', reason: 'SKS_SKIP_POSTINSTALL_SHIM=1' };
325
- const pathEnv = opts.pathEnv ?? process.env.PATH ?? '';
326
- const existing = await findCommandOnPath('sks', pathEnv);
327
- if (isStableSksBin(existing)) return { status: 'present', command: existing };
328
- const nodeBin = opts.nodeBin || process.execPath;
329
- const target = opts.target || path.join(packageRoot(), 'bin', 'sks.mjs');
330
- const dirs = candidateShimDirs(pathEnv, opts.home || process.env.HOME);
331
- const script = process.platform === 'win32'
332
- ? `@echo off\r\n"${nodeBin}" "${target}" %*\r\n`
333
- : `#!/bin/sh\nexec "${nodeBin}" "${target}" "$@"\n`;
334
- const suffix = process.platform === 'win32' ? '.cmd' : '';
335
- let createdFallback = null;
336
- let lastError = '';
337
- for (const entry of dirs) {
338
- const dest = path.join(entry.dir, `sks${suffix}`);
339
- try {
340
- await ensureDir(entry.dir);
341
- await writeTextAtomic(dest, script);
342
- if (process.platform !== 'win32') await fsp.chmod(dest, 0o755).catch(() => {});
343
- if (entry.onPath) return { status: 'created', command: dest };
344
- createdFallback ||= dest;
345
- } catch (err) {
346
- lastError = err.message;
347
- }
348
- }
349
- if (createdFallback) return { status: 'created_not_on_path', command: createdFallback };
350
- return { status: 'failed', error: lastError };
351
- }
352
-
353
- function candidateShimDirs(pathEnv, home) {
354
- const seen = new Set();
355
- const out = [];
356
- for (const raw of String(pathEnv || '').split(path.delimiter).filter(Boolean)) {
357
- const dir = path.resolve(raw);
358
- if (seen.has(dir) || isTransientNpmBinPath(dir)) continue;
359
- seen.add(dir);
360
- out.push({ dir, onPath: true });
361
- }
362
- for (const raw of [home && path.join(home, '.local', 'bin'), home && path.join(home, 'bin')].filter(Boolean)) {
363
- const dir = path.resolve(raw);
364
- if (seen.has(dir)) continue;
365
- seen.add(dir);
366
- out.push({ dir, onPath: false });
367
- }
368
- return out;
369
- }
370
-
371
- async function findCommandOnPath(name, pathEnv) {
372
- const suffixes = process.platform === 'win32' ? ['.cmd', '.exe', ''] : [''];
373
- for (const dir of String(pathEnv || '').split(path.delimiter).filter(Boolean)) {
374
- for (const suffix of suffixes) {
375
- const candidate = path.join(dir, `${name}${suffix}`);
376
- if (await exists(candidate)) return candidate;
377
- }
378
- }
379
- return null;
380
- }
381
-
382
- async function ensureGlobalContext7DuringInstall() {
383
- if (process.env.SKS_SKIP_POSTINSTALL_CONTEXT7 === '1') return { status: 'skipped', reason: 'SKS_SKIP_POSTINSTALL_CONTEXT7=1' };
384
- const codex = await getCodexInfo().catch(() => ({}));
385
- if (!codex.bin) return { status: 'codex_missing' };
386
- const list = await runProcess(codex.bin, ['mcp', 'list'], { timeoutMs: 8000, maxOutputBytes: 32 * 1024 }).catch((err) => ({ code: 1, stderr: err.message, stdout: '' }));
387
- if (list.code === 0 && /context7/i.test(`${list.stdout}\n${list.stderr}`)) return { status: 'present' };
388
- const add = await runProcess(codex.bin, ['mcp', 'add', 'context7', '--', 'npx', '-y', '@upstash/context7-mcp@latest'], { timeoutMs: 30000, maxOutputBytes: 64 * 1024 }).catch((err) => ({ code: 1, stderr: err.message, stdout: '' }));
389
- if (add.code === 0) return { status: 'installed' };
390
- return { status: 'failed', error: `${add.stderr || add.stdout || 'codex mcp add failed'}`.trim() };
391
- }
392
-
393
- async function ensureGlobalCodexSkillsDuringInstall(opts = {}) {
394
- if (process.env.SKS_SKIP_POSTINSTALL_GLOBAL_SKILLS === '1' && !opts.force) return { status: 'skipped', reason: 'SKS_SKIP_POSTINSTALL_GLOBAL_SKILLS=1' };
395
- const home = opts.home || process.env.HOME || os.homedir();
396
- if (!home) return { status: 'skipped', reason: 'home directory unavailable' };
397
- const root = globalCodexSkillsRoot(home);
398
- try {
399
- const install = await installSkills(home);
400
- const skills = await checkRequiredSkills(home, root);
401
- return { status: skills.ok ? 'installed' : 'partial', root, installed_count: install.installed_skills.length, removed_aliases: install.removed_agent_skill_aliases, missing_skills: skills.missing };
402
- } catch (err) {
403
- return { status: 'failed', root, error: err.message };
404
- }
405
- }
406
-
407
- async function ensureRelatedCliTools(args = []) {
408
- const skip = flag(args, '--skip-cli-tools') || process.env.SKS_SKIP_CLI_TOOLS === '1';
409
- const codex = await ensureCodexCliTool({ skip });
410
- const warp = await warpReadiness().catch((err) => ({ ok: false, version: null, error: err.message }));
411
- return {
412
- codex,
413
- warp: {
414
- ok: Boolean(warp.ok),
415
- app: warp.app || null,
416
- cli: warp.cli || null,
417
- version: warp.version || null,
418
- launch_config_dir: warp.launch_config_dir || null,
419
- uri_scheme: warp.uri_scheme || null,
420
- install_hint: warp.ok ? null : platformWarpInstallHint(),
421
- error: warp.error || null
422
- }
423
- };
424
- }
425
-
426
- async function ensureCodexCliTool({ skip = false } = {}) {
427
- if (skip) return { status: 'skipped', reason: 'SKS_SKIP_CLI_TOOLS=1 or --skip-cli-tools' };
428
- const before = await getCodexInfo().catch(() => ({}));
429
- if (before.bin) return { status: 'present', bin: before.bin, version: before.version || null };
430
- const npmBin = await which('npm');
431
- if (!npmBin) return { status: 'failed', error: 'npm not found on PATH; install Codex CLI manually with npm i -g @openai/codex@latest.' };
432
- const install = await runProcess(npmBin, ['i', '-g', '@openai/codex@latest'], {
433
- timeoutMs: 120000,
434
- maxOutputBytes: 128 * 1024
435
- }).catch((err) => ({ code: 1, stdout: '', stderr: err.message }));
436
- if (install.code !== 0) {
437
- return { status: 'failed', error: `${install.stderr || install.stdout || 'npm i -g @openai/codex@latest failed'}`.trim() };
438
- }
439
- const after = await getCodexInfo().catch(() => ({}));
440
- return {
441
- status: after.bin ? 'installed' : 'installed_not_on_path',
442
- bin: after.bin || null,
443
- version: after.version || null,
444
- hint: after.bin ? null : 'npm completed, but codex is not on PATH. Restart the shell or set SKS_CODEX_BIN.'
445
- };
446
- }
447
-
448
- async function isProjectSetupCandidate(root) {
449
- for (const marker of ['package.json', '.git', '.codex', '.agents', 'AGENTS.md']) {
450
- if (await exists(path.join(root, marker))) return true;
451
- }
452
- return false;
453
- }
454
-
455
249
  async function wizard(args = []) {
456
250
  if (!shouldShowWizard() && !flag(args, '--force')) return help();
457
251
  const rl = readline.createInterface({ input, output });
@@ -532,7 +326,7 @@ async function updateCheck(args = []) {
532
326
  if (result.update_available) console.log('Run: npm i -g sneakoscope');
533
327
  }
534
328
 
535
- const DOLLAR_DEFAULT_PIPELINE_TEXT = 'Default pipeline: questions -> $Answer, small design/content -> $DFix, 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.';
329
+ const DOLLAR_DEFAULT_PIPELINE_TEXT = 'Default pipeline: questions -> $Answer, small design/content -> $DFix, presentation/PDF artifacts -> $PPT, 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.';
536
330
 
537
331
  function commands(args = []) {
538
332
  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));
@@ -612,129 +406,59 @@ Rules:
612
406
  `);
613
407
  }
614
408
 
615
- async function context7(sub = 'check', args = []) {
616
- const action = sub || 'check';
617
- const setupScope = action === 'setup' ? readOption(args, '--scope', flag(args, '--global') ? 'global' : 'project') : null;
618
- const root = action === 'setup' && setupScope === 'project' ? await projectRoot() : await sksRoot();
619
- if (action === 'check') {
620
- const result = await checkContext7(root);
621
- if (flag(args, '--json')) return console.log(JSON.stringify(result, null, 2));
622
- console.log('SKS Context7 MCP\n');
623
- console.log(`Project config: ${result.project.ok ? 'ok' : 'missing'} ${result.project.path}`);
624
- console.log(`Global config: ${result.global.ok ? 'ok' : 'missing'} ${result.global.path}`);
625
- console.log(`Codex mcp list: ${result.codex_mcp_list.ok ? 'ok' : result.codex_mcp_list.checked ? 'missing' : 'not checked'}`);
626
- console.log(`Ready: ${result.ok ? 'yes' : 'no'}`);
627
- if (!result.ok) console.log('\nRun: sks context7 setup --scope project');
628
- return;
629
- }
630
- if (action === 'tools') {
631
- const result = await context7Tools({ timeoutMs: readNumberOption(args, '--timeout-ms', 30000) });
632
- if (flag(args, '--json')) return console.log(JSON.stringify(result, null, 2));
633
- console.log('SKS Context7 Local MCP Tools\n');
634
- console.log(`Server: ${result.server.info?.name || 'context7'} ${result.server.info?.version || ''}`.trim());
635
- console.log(`Command: ${result.server.command} ${result.server.args.join(' ')}`);
636
- console.log(`Tools: ${result.tool_names.join(', ') || 'none'}`);
637
- if (!result.tool_names.includes('resolve-library-id') || !result.tool_names.some((name) => name === 'query-docs' || name === 'get-library-docs')) {
638
- process.exitCode = 1;
639
- console.log('\nContext7 local MCP is missing the required resolve/docs tools.');
640
- }
641
- return;
642
- }
643
- if (action === 'resolve') {
644
- const positional = positionalArgs(args);
645
- const libraryName = positional.join(' ').trim();
646
- if (!libraryName) throw new Error('Usage: sks context7 resolve <library-name> [--query "..."] [--json]');
647
- const result = await context7Resolve(libraryName, {
648
- query: readOption(args, '--query', libraryName),
649
- timeoutMs: readNumberOption(args, '--timeout-ms', 30000)
650
- });
651
- if (flag(args, '--json')) return console.log(JSON.stringify(result, null, 2));
652
- console.log('SKS Context7 Resolve\n');
653
- console.log(`Library: ${libraryName}`);
654
- console.log(`ID: ${result.library_id || 'not resolved'}`);
655
- console.log(`Server: ${result.server.info?.name || 'context7'} ${result.server.info?.version || ''}`.trim());
656
- const text = context7Text(result.result).split(/\n/).slice(0, 24).join('\n').trim();
657
- if (text) console.log(`\n${text}`);
658
- if (!result.ok || !result.library_id) process.exitCode = 1;
659
- return;
660
- }
661
- if (action === 'docs') {
662
- const positional = positionalArgs(args);
663
- const libraryNameOrId = positional.join(' ').trim();
664
- if (!libraryNameOrId) throw new Error('Usage: sks context7 docs <library-name|/org/project> [--query "..."] [--topic "..."] [--tokens N] [--json]');
665
- const result = await context7Docs(libraryNameOrId, {
666
- query: readOption(args, '--query', readOption(args, '--topic', libraryNameOrId)),
667
- topic: readOption(args, '--topic', libraryNameOrId),
668
- tokens: readNumberOption(args, '--tokens', 2000),
669
- timeoutMs: readNumberOption(args, '--timeout-ms', 30000)
670
- });
671
- if (flag(args, '--json')) return console.log(JSON.stringify(result, null, 2));
672
- printContext7DocsResult(result, { title: 'SKS Context7 Docs' });
673
- if (!result.ok) process.exitCode = 1;
674
- return;
675
- }
676
- if (action === 'evidence') {
677
- const positional = positionalArgs(args);
678
- const missionArg = positional.shift();
679
- const libraryNameOrId = positional.join(' ').trim();
680
- if (!missionArg || !libraryNameOrId) throw new Error('Usage: sks context7 evidence <mission-id|latest> <library-name|/org/project> [--query "..."] [--topic "..."] [--tokens N] [--json]');
681
- const missionId = await resolveMissionId(root, missionArg);
682
- if (!missionId) throw new Error('No mission found for Context7 evidence.');
683
- const result = await context7Docs(libraryNameOrId, {
684
- query: readOption(args, '--query', readOption(args, '--topic', libraryNameOrId)),
685
- topic: readOption(args, '--topic', libraryNameOrId),
686
- tokens: readNumberOption(args, '--tokens', 2000),
687
- timeoutMs: readNumberOption(args, '--timeout-ms', 30000)
688
- });
689
- const state = { ...(await readJson(stateFile(root), {})), mission_id: missionId };
690
- await recordContext7Evidence(root, state, { tool_name: 'resolve-library-id', library: libraryNameOrId, library_id: result.library_id, source: result.resolve ? 'sks context7 evidence' : 'sks context7 evidence explicit-library-id' });
691
- if (result.docs_tool) {
692
- await recordContext7Evidence(root, state, { tool_name: result.docs_tool, library_id: result.library_id, source: 'sks context7 evidence' });
693
- }
694
- const evidence = await context7Evidence(root, state);
695
- const out = { ...result, mission_id: missionId, evidence };
696
- if (flag(args, '--json')) return console.log(JSON.stringify(out, null, 2));
697
- printContext7DocsResult(result, { title: 'SKS Context7 Evidence' });
698
- console.log(`\nMission: ${missionId}`);
699
- console.log(`Evidence: ${evidence.ok ? 'ok' : 'missing'} resolve=${evidence.resolve ? 'yes' : 'no'} docs=${evidence.docs ? 'yes' : 'no'} events=${evidence.count}`);
700
- if (!result.ok || !evidence.ok) process.exitCode = 1;
409
+ async function pptCommand(sub = 'status', args = []) {
410
+ const root = await sksRoot();
411
+ const action = sub || 'status';
412
+ const missionArg = args.find((arg) => !String(arg).startsWith('--')) || 'latest';
413
+ const id = await resolveMissionId(root, missionArg);
414
+ if (!id) throw new Error('Usage: sks ppt build|status <mission-id|latest> [--json]');
415
+ const { dir } = await loadMission(root, id);
416
+ if (action === 'build') {
417
+ const contract = await readJson(path.join(dir, 'decision-contract.json'), null);
418
+ if (!contract) throw new Error(`PPT build requires a sealed decision-contract.json for ${id}`);
419
+ const result = await writePptBuildArtifacts(dir, contract);
420
+ await appendJsonlBounded(path.join(dir, 'events.jsonl'), { ts: nowIso(), type: 'ppt.build.completed', ok: result.ok, files: result.files });
421
+ if (flag(args, '--json')) return console.log(JSON.stringify({ ok: result.ok, mission_id: id, files: result.files, gate: result.gate, report: result.report }, null, 2));
422
+ console.log('SKS PPT build\n');
423
+ console.log(`Mission: ${id}`);
424
+ console.log(`HTML: ${path.relative(root, result.files.html)}`);
425
+ console.log(`PDF: ${path.relative(root, result.files.pdf)}`);
426
+ console.log(`Report: ${path.relative(root, result.files.render_report)}`);
427
+ console.log(`Cleanup: ${path.relative(root, result.files.cleanup_report)}`);
428
+ console.log(`Parallel:${' '.repeat(1)}${path.relative(root, result.files.parallel_report)}`);
429
+ console.log(`Gate: ${result.ok ? 'passed' : 'blocked'} (${path.relative(root, result.files.gate)})`);
701
430
  return;
702
431
  }
703
- if (action === 'setup') {
704
- const scope = setupScope;
705
- const transport = readOption(args, '--transport', flag(args, '--remote') ? 'remote' : 'local');
706
- if (!['project', 'global'].includes(scope)) throw new Error('Invalid Context7 scope. Use project or global.');
707
- if (!['local', 'remote'].includes(transport)) throw new Error('Invalid Context7 transport. Use local or remote.');
708
- if (scope === 'project') {
709
- const changed = await ensureProjectContext7Config(root, transport);
710
- const result = await checkContext7(root);
711
- if (flag(args, '--json')) return console.log(JSON.stringify({ changed, ...result }, null, 2));
712
- console.log(`Context7 project MCP ${changed ? 'configured' : 'already configured'} in .codex/config.toml`);
713
- console.log(`Ready: ${result.ok ? 'yes' : 'no'}`);
714
- return;
715
- }
716
- const codex = await getCodexInfo();
717
- if (!codex.bin) throw new Error('Codex CLI missing. Install separately: npm i -g @openai/codex, or set SKS_CODEX_BIN.');
718
- const cmdArgs = transport === 'remote'
719
- ? ['mcp', 'add', 'context7', '--url', 'https://mcp.context7.com/mcp']
720
- : ['mcp', 'add', 'context7', '--', 'npx', '-y', '@upstash/context7-mcp@latest'];
721
- const result = await runProcess(codex.bin, cmdArgs, { timeoutMs: 30000, maxOutputBytes: 64 * 1024 });
722
- if (flag(args, '--json')) return console.log(JSON.stringify({ command: `${codex.bin} ${cmdArgs.join(' ')}`, result }, null, 2));
723
- if (result.code !== 0) throw new Error(result.stderr || result.stdout || 'codex mcp add failed');
724
- console.log('Context7 global MCP configured.');
432
+ if (action === 'status') {
433
+ const gate = await readJson(path.join(dir, PPT_GATE_ARTIFACT), null);
434
+ const report = await readJson(path.join(dir, PPT_RENDER_REPORT_ARTIFACT), null);
435
+ const status = {
436
+ ok: Boolean(gate?.passed),
437
+ mission_id: id,
438
+ gate,
439
+ report,
440
+ files: {
441
+ html: path.join(dir, PPT_HTML_ARTIFACT),
442
+ source_html: path.join(dir, PPT_HTML_ARTIFACT),
443
+ pdf: path.join(dir, PPT_PDF_ARTIFACT),
444
+ render_report: path.join(dir, PPT_RENDER_REPORT_ARTIFACT),
445
+ cleanup_report: path.join(dir, PPT_CLEANUP_REPORT_ARTIFACT),
446
+ parallel_report: path.join(dir, PPT_PARALLEL_REPORT_ARTIFACT),
447
+ gate: path.join(dir, PPT_GATE_ARTIFACT)
448
+ }
449
+ };
450
+ if (flag(args, '--json')) return console.log(JSON.stringify(status, null, 2));
451
+ console.log('SKS PPT status\n');
452
+ console.log(`Mission: ${id}`);
453
+ console.log(`Gate: ${status.ok ? 'passed' : 'not passed'}`);
454
+ console.log(`HTML: ${path.relative(root, status.files.html)}`);
455
+ console.log(`PDF: ${path.relative(root, status.files.pdf)}`);
456
+ console.log(`Report: ${path.relative(root, status.files.render_report)}`);
457
+ console.log(`Cleanup: ${path.relative(root, status.files.cleanup_report)}`);
458
+ console.log(`Parallel:${' '.repeat(1)}${path.relative(root, status.files.parallel_report)}`);
725
459
  return;
726
460
  }
727
- throw new Error(`Unknown context7 command: ${action}`);
728
- }
729
-
730
- function printContext7DocsResult(result, opts = {}) {
731
- console.log(`${opts.title || 'SKS Context7 Docs'}\n`);
732
- console.log(`Library ID: ${result.library_id || 'not resolved'}`);
733
- console.log(`Docs tool: ${result.docs_tool || 'missing'}`);
734
- console.log(`Server: ${result.server?.info?.name || 'context7'} ${result.server?.info?.version || ''}`.trim());
735
- const text = context7Text(result.docs).split(/\n/).slice(0, 48).join('\n').trim();
736
- if (text) console.log(`\n${text}`);
737
- if (result.error) console.log(`\nError: ${result.error}`);
461
+ throw new Error(`Unknown ppt command: ${action}`);
738
462
  }
739
463
 
740
464
  async function pipeline(sub = 'status', args = []) {
@@ -937,6 +661,25 @@ async function materializeAfterPipelineAnswer(root, id, dir, mission, route, rou
937
661
  }
938
662
  };
939
663
  }
664
+ if (route?.id === 'PPT') {
665
+ await writePptRouteArtifacts(dir, contract);
666
+ await appendJsonlBounded(path.join(dir, 'events.jsonl'), {
667
+ ts: nowIso(),
668
+ type: 'ppt.materialized_after_ambiguity_gate',
669
+ route: route.id,
670
+ audience_strategy_artifact: PPT_AUDIENCE_STRATEGY_ARTIFACT,
671
+ gate: PPT_GATE_ARTIFACT
672
+ });
673
+ return {
674
+ phase: 'PPT_AUDIENCE_STRATEGY_READY',
675
+ prompt: routeContext.task || mission.prompt || '',
676
+ state: {
677
+ ppt_audience_strategy_ready: true,
678
+ ppt_gate_ready: true,
679
+ ...madSksState
680
+ }
681
+ };
682
+ }
940
683
  if (route?.id !== 'Team') return Object.keys(madSksState).length ? { state: madSksState } : {};
941
684
  const spec = parseTeamSpecText(routeContext.task || mission.prompt || '');
942
685
  const prompt = spec.prompt || routeContext.task || mission.prompt || '';
@@ -1120,43 +863,6 @@ async function reasoningCommand(args = []) {
1120
863
  console.log('Lifecycle: temporary; return to default/user-selected profile after the route gate passes');
1121
864
  }
1122
865
 
1123
- async function checkContext7(root) {
1124
- const projectPath = path.join(root, '.codex', 'config.toml');
1125
- const globalPath = path.join(process.env.HOME || '', '.codex', 'config.toml');
1126
- const projectText = await safeReadText(projectPath);
1127
- const globalText = await safeReadText(globalPath);
1128
- const codex = await getCodexInfo().catch(() => ({}));
1129
- let list = { checked: false, ok: false, stdout: '', stderr: '' };
1130
- if (codex.bin) {
1131
- const out = await runProcess(codex.bin, ['mcp', 'list'], { timeoutMs: 8000, maxOutputBytes: 32 * 1024 }).catch((err) => ({ code: 1, stderr: err.message, stdout: '' }));
1132
- list = { checked: true, ok: out.code === 0 && /context7/i.test(`${out.stdout}\n${out.stderr}`), stdout: out.stdout || '', stderr: out.stderr || '' };
1133
- }
1134
- const result = {
1135
- project: { path: projectPath, ok: hasContext7ConfigText(projectText) },
1136
- global: { path: globalPath, ok: hasContext7ConfigText(globalText) },
1137
- codex_mcp_list: list
1138
- };
1139
- result.ok = result.project.ok || result.codex_mcp_list.ok || (result.global.ok && !list.checked);
1140
- return result;
1141
- }
1142
-
1143
- async function ensureProjectContext7Config(root, transport = 'local') {
1144
- const configPath = path.join(root, '.codex', 'config.toml');
1145
- await ensureDir(path.dirname(configPath));
1146
- const current = await safeReadText(configPath);
1147
- const block = context7ConfigToml(transport).trim();
1148
- const existingBlock = /(^|\n)\[mcp_servers\.context7\]\n[\s\S]*?(?=\n\[[^\]]+\]|\s*$)/;
1149
- if (existingBlock.test(current)) {
1150
- const next = current.replace(existingBlock, `$1${block}\n`);
1151
- if (next === current) return false;
1152
- await writeTextAtomic(configPath, next.endsWith('\n') ? next : `${next}\n`);
1153
- return true;
1154
- }
1155
- if (hasContext7ConfigText(current)) return false;
1156
- await writeTextAtomic(configPath, `${current.trimEnd()}${current.trim() ? '\n\n' : ''}${block}\n`);
1157
- return true;
1158
- }
1159
-
1160
866
  function readOption(args, name, fallback) {
1161
867
  const i = args.indexOf(name);
1162
868
  return i >= 0 && args[i + 1] ? args[i + 1] : fallback;
@@ -1169,23 +875,23 @@ function readNumberOption(args, name, fallback) {
1169
875
  return Number.isFinite(value) && value > 0 ? value : fallback;
1170
876
  }
1171
877
 
1172
- async function warpCommand(sub = 'start', args = []) {
878
+ async function tmuxCommand(sub = 'start', args = []) {
1173
879
  const action = sub || 'start';
1174
880
  if (action === 'status' || action === 'banner') {
1175
881
  if (flag(args, '--json')) {
1176
882
  const status = await codexAppIntegrationStatus();
1177
883
  return console.log(JSON.stringify(status, null, 2));
1178
884
  }
1179
- await runWarpStatus(action === 'banner' ? ['--once', ...args] : args);
885
+ await runTmuxStatus(action === 'banner' ? ['--once', ...args] : args);
1180
886
  return;
1181
887
  }
1182
888
  if (action === 'check') {
1183
889
  const root = await sksRoot();
1184
- const plan = await buildWarpLaunchPlan({ root, session: readOption(args, '--session', null) });
890
+ const plan = await buildTmuxLaunchPlan({ root, session: readOption(args, '--session', null) });
1185
891
  if (flag(args, '--json')) return console.log(JSON.stringify(plan, null, 2));
1186
- console.log(formatWarpBanner(plan.app));
892
+ console.log(formatTmuxBanner(plan.app));
1187
893
  console.log('');
1188
- console.log(`warp: ${plan.warp.ok ? 'ok' : 'missing'} ${plan.warp.version || ''}`.trim());
894
+ console.log(`tmux: ${plan.tmux.ok ? 'ok' : 'missing'} ${plan.tmux.version || ''}`.trim());
1189
895
  console.log(`Workspace: ${plan.workspace}`);
1190
896
  console.log(`Project: ${plan.root}`);
1191
897
  console.log(`Ready: ${plan.ready ? 'yes' : 'no'}`);
@@ -1197,16 +903,16 @@ async function warpCommand(sub = 'start', args = []) {
1197
903
  return;
1198
904
  }
1199
905
  if (['start', 'attach', 'connect', 'open'].includes(action)) {
1200
- const result = await launchWarpUi(args);
906
+ const result = await launchTmuxUi(args);
1201
907
  if (flag(args, '--json')) console.log(JSON.stringify(result, null, 2));
1202
908
  return;
1203
909
  }
1204
- console.error('Usage: sks warp open|start|check|status|banner [--workspace name]');
910
+ console.error('Usage: sks tmux open|start|check|status|banner [--workspace name]');
1205
911
  process.exitCode = 1;
1206
912
  }
1207
913
 
1208
914
  async function madHighCommand(args = []) {
1209
- const cleanArgs = args.filter((arg) => !['--mad', '--MAD', '--mad-sks', '--high', '--no-auto-install-warp'].includes(arg));
915
+ const cleanArgs = args.filter((arg) => !['--mad', '--MAD', '--mad-sks', '--high', '--no-auto-install-tmux'].includes(arg));
1210
916
  if (flag(args, '--json')) {
1211
917
  const profile = await enableMadHighProfile();
1212
918
  return console.log(JSON.stringify(profile, null, 2));
@@ -1230,11 +936,11 @@ async function madHighCommand(args = []) {
1230
936
  }
1231
937
  const profile = await enableMadHighProfile();
1232
938
  console.log(`SKS MAD auto-review profile ready: ${madHighProfileName()}`);
1233
- console.log('Scope: explicit warp launch only; full access uses Codex auto_review approvals when approval prompts are raised.');
1234
- const workspace = readOption(cleanArgs, '--workspace', readOption(cleanArgs, '--session', `sks-mad-${defaultWarpWorkspaceName(process.cwd())}`));
1235
- return launchWarpUi([...cleanArgs, '--workspace', workspace], {
939
+ console.log('Scope: explicit tmux launch only; full access uses Codex auto_review approvals when approval prompts are raised.');
940
+ const workspace = readOption(cleanArgs, '--workspace', readOption(cleanArgs, '--session', `sks-mad-${defaultTmuxSessionName(process.cwd())}`));
941
+ return launchTmuxUi([...cleanArgs, '--workspace', workspace], {
1236
942
  codexArgs: ['--profile', profile.profile_name],
1237
- autoInstallWarp: !flag(args, '--no-auto-install-warp'),
943
+ autoInstallTmux: !flag(args, '--no-auto-install-tmux'),
1238
944
  conciseBlockers: true
1239
945
  });
1240
946
  }
@@ -1270,12 +976,12 @@ async function ensureMadLaunchDependencies(args = []) {
1270
976
  const codex = await getCodexInfo().catch(() => ({}));
1271
977
  if (!codex.bin) actions.push(await installCodexDependency(args, { prompt: 'Codex CLI missing. Install latest Codex CLI with npm i -g @openai/codex@latest?' }));
1272
978
  }
1273
- if (!flag(args, '--no-auto-install-warp')) {
1274
- const warp = await warpReadiness().catch(() => ({ ok: false }));
1275
- if (!warp.ok) actions.push(await installWarpDependency(args));
979
+ if (!flag(args, '--no-auto-install-tmux')) {
980
+ const tmux = await tmuxReadiness().catch(() => ({ ok: false }));
981
+ if (!tmux.ok) actions.push(await installTmuxDependency(args));
1276
982
  }
1277
983
  const status = await depsStatus(await sksRoot());
1278
- return { ready: Boolean(status.codex_cli.ok && status.warp.ok), actions, status };
984
+ return { ready: Boolean(status.codex_cli.ok && status.tmux.ok), actions, status };
1279
985
  }
1280
986
 
1281
987
  async function deps(sub = 'check', args = []) {
@@ -1289,7 +995,7 @@ async function deps(sub = 'check', args = []) {
1289
995
  return;
1290
996
  }
1291
997
  if (action === 'install') return depsInstall(args);
1292
- console.error('Usage: sks deps check|install [warp|codex|context7|all] [--yes] [--json]');
998
+ console.error('Usage: sks deps check|install [tmux|codex|context7|all] [--yes] [--json]');
1293
999
  process.exitCode = 1;
1294
1000
  }
1295
1001
 
@@ -1299,7 +1005,7 @@ async function depsStatus(root = null, opts = {}) {
1299
1005
  const codex = opts.codex || await getCodexInfo().catch(() => ({}));
1300
1006
  const app = opts.codexApp || await codexAppIntegrationStatus({ codex });
1301
1007
  const context7 = opts.context7 || await checkContext7(root);
1302
- const warp = opts.warp || await warpReadiness().catch((err) => ({ ok: false, version: null, error: err.message }));
1008
+ const tmux = opts.tmux || await tmuxReadiness().catch((err) => ({ ok: false, version: null, error: err.message }));
1303
1009
  const brew = process.platform === 'darwin' ? await which('brew').catch(() => null) : null;
1304
1010
  const globalBin = await discoverGlobalSksCommand();
1305
1011
  const npmPrefix = npmBin ? await runProcess(npmBin, ['prefix', '-g'], { timeoutMs: 8000, maxOutputBytes: 4096 }).catch(() => null) : null;
@@ -1307,10 +1013,10 @@ async function depsStatus(root = null, opts = {}) {
1307
1013
  const npmPrefixDir = npmPrefix?.code === 0 ? npmPrefix.stdout.trim().split(/\r?\n/).pop() : null;
1308
1014
  const npmBinDir = npmPrefixDir ? (process.platform === 'win32' ? npmPrefixDir : path.join(npmPrefixDir, 'bin')) : null;
1309
1015
  const nodeOk = Number(process.versions.node.split('.')[0]) >= 20;
1310
- const homebrewNeeded = process.platform === 'darwin' && !warp.ok;
1016
+ const homebrewNeeded = process.platform === 'darwin' && !tmux.ok;
1311
1017
  return {
1312
1018
  root,
1313
- ready: Boolean(nodeOk && npmBin && globalBin && codex.bin && context7.ok && warp.ok),
1019
+ ready: Boolean(nodeOk && npmBin && globalBin && codex.bin && context7.ok && tmux.ok),
1314
1020
  node: { ok: nodeOk, version: process.version },
1315
1021
  npm: { ok: Boolean(npmBin), bin: npmBin, global_bin_dir: npmBinDir, global_bin_on_path: npmBinDir ? pathText.split(path.delimiter).includes(npmBinDir) : null },
1316
1022
  sneakoscope: { ok: Boolean(globalBin), bin: globalBin },
@@ -1319,13 +1025,13 @@ async function depsStatus(root = null, opts = {}) {
1319
1025
  context7,
1320
1026
  browser_use: { ok: app.mcp.has_browser_use, cache: app.plugins.browser_use_cache },
1321
1027
  computer_use: { ok: app.mcp.has_computer_use, cache: app.plugins.computer_use_cache },
1322
- warp: { ok: Boolean(warp.ok), app: warp.app || null, cli: warp.cli || null, version: warp.version || null, launch_config_dir: warp.launch_config_dir || null, uri_scheme: warp.uri_scheme || null, install_hint: warp.ok ? null : platformWarpInstallHint(), error: warp.error || null },
1323
- homebrew: process.platform === 'darwin' ? { ok: Boolean(brew), bin: brew, required_for_warp_install: homebrewNeeded } : { ok: null, bin: null, required_for_warp_install: false },
1324
- next_actions: depsNextActions({ npmBin, globalBin, codex, app, context7, warp, brew, nodeOk })
1028
+ tmux: { ok: Boolean(tmux.ok), bin: tmux.bin || null, version: tmux.version || null, min_version: tmux.min_version || '3.0', current_session: Boolean(tmux.current_session), install_hint: tmux.ok ? null : platformTmuxInstallHint(), error: tmux.error || null },
1029
+ homebrew: process.platform === 'darwin' ? { ok: Boolean(brew), bin: brew, required_for_tmux_install: homebrewNeeded } : { ok: null, bin: null, required_for_tmux_install: false },
1030
+ next_actions: depsNextActions({ npmBin, globalBin, codex, app, context7, tmux, brew, nodeOk })
1325
1031
  };
1326
1032
  }
1327
1033
 
1328
- function depsNextActions({ npmBin, globalBin, codex, app, context7, warp, brew, nodeOk }) {
1034
+ function depsNextActions({ npmBin, globalBin, codex, app, context7, tmux, brew, nodeOk }) {
1329
1035
  const out = [];
1330
1036
  if (!nodeOk) out.push('Install Node.js 20.11+.');
1331
1037
  if (!npmBin) out.push('Install npm or use a Node.js distribution that includes npm.');
@@ -1333,7 +1039,7 @@ function depsNextActions({ npmBin, globalBin, codex, app, context7, warp, brew,
1333
1039
  if (!codex.bin) out.push('Run: sks deps install codex');
1334
1040
  if (!context7.ok) out.push('Run: sks deps install context7');
1335
1041
  if (!app.ok) out.push('Run: sks codex-app check');
1336
- if (!warp.ok) out.push(process.platform === 'darwin' && !brew ? 'Install Warp from https://www.warp.dev/download, or install Homebrew then run: sks deps install warp' : 'Run: sks deps install warp');
1042
+ if (!tmux.ok) out.push(process.platform === 'darwin' && !brew ? 'Install tmux from https://www.tmux.dev/download, or install Homebrew then run: sks deps install tmux' : 'Run: sks deps install tmux');
1337
1043
  return out;
1338
1044
  }
1339
1045
 
@@ -1348,7 +1054,7 @@ function printDepsStatus(status) {
1348
1054
  console.log(`Context7: ${status.context7.ok ? 'ok' : 'missing'}`);
1349
1055
  console.log(`Browser Use: ${status.browser_use.ok ? 'ok' : 'missing'}`);
1350
1056
  console.log(`Computer Use:${status.computer_use.ok ? ' ok' : ' missing'}`);
1351
- console.log(`warp: ${warpStatusKind(status.warp)} ${status.warp.version || status.warp.error || ''}`.trimEnd());
1057
+ console.log(`tmux: ${tmuxStatusKind(status.tmux)} ${status.tmux.version || status.tmux.error || ''}`.trimEnd());
1352
1058
  if (process.platform === 'darwin') console.log(`Homebrew: ${status.homebrew.ok ? 'ok' : 'missing'} ${status.homebrew.bin || ''}`.trimEnd());
1353
1059
  console.log(`Ready: ${status.ready ? 'true' : 'false'}`);
1354
1060
  if (status.next_actions.length) {
@@ -1360,11 +1066,11 @@ function printDepsStatus(status) {
1360
1066
  async function depsInstall(args = []) {
1361
1067
  const root = await sksRoot();
1362
1068
  const target = positionalArgs(args)[0] || 'all';
1363
- const wants = target === 'all' ? ['codex', 'context7', 'warp'] : [target];
1069
+ const wants = target === 'all' ? ['codex', 'context7', 'tmux'] : [target];
1364
1070
  const actions = [];
1365
1071
  if (wants.includes('codex')) actions.push(await installCodexDependency(args));
1366
1072
  if (wants.includes('context7')) actions.push(await installContext7Dependency(root));
1367
- if (wants.includes('warp')) actions.push(await installWarpDependency(args));
1073
+ if (wants.includes('tmux')) actions.push(await installTmuxDependency(args));
1368
1074
  const status = await depsStatus(root);
1369
1075
  const result = { target, actions, status };
1370
1076
  if (flag(args, '--json')) return console.log(JSON.stringify(result, null, 2));
@@ -1389,12 +1095,12 @@ async function installContext7Dependency(root) {
1389
1095
  return { target: 'context7', status: changed ? 'project_configured' : 'already_configured', command: 'sks context7 check' };
1390
1096
  }
1391
1097
 
1392
- async function installWarpDependency(args = []) {
1393
- const before = await warpReadiness().catch(() => ({ ok: false }));
1394
- if (before.ok) return { target: 'warp', status: 'present', version: before.version || null, app: before.app || null, cli: before.cli || null };
1395
- const command = process.platform === 'darwin' ? 'brew install --cask warp' : platformWarpInstallHint();
1396
- if (flag(args, '--dry-run')) return { target: 'warp', status: 'dry_run', command };
1397
- return { target: 'warp', status: 'manual_required', command, error: before.error || 'Warp app not found' };
1098
+ async function installTmuxDependency(args = []) {
1099
+ const before = await tmuxReadiness().catch(() => ({ ok: false }));
1100
+ if (before.ok) return { target: 'tmux', status: 'present', version: before.version || null, app: before.app || null, cli: before.cli || null };
1101
+ const command = process.platform === 'darwin' ? 'brew install tmux' : platformTmuxInstallHint();
1102
+ if (flag(args, '--dry-run')) return { target: 'tmux', status: 'dry_run', command };
1103
+ return { target: 'tmux', status: 'manual_required', command, error: before.error || 'tmux not found' };
1398
1104
  }
1399
1105
 
1400
1106
  async function confirmInstall(question, args = []) {
@@ -1444,8 +1150,8 @@ async function autoReviewCommand(sub = 'status', args = []) {
1444
1150
  if (flag(args, '--json')) return console.log(JSON.stringify(status, null, 2));
1445
1151
  console.log(`SKS Auto-Review enabled: ${profile}`);
1446
1152
  const sessionArg = readOption(cleanArgs, '--session', null);
1447
- const session = sessionArg || sanitizeWarpWorkspaceName(`${profile}-${defaultWarpWorkspaceName(process.cwd())}`);
1448
- return launchWarpUi([...cleanArgs, '--session', session], { codexArgs: ['--profile', profile] });
1153
+ const session = sessionArg || sanitizeTmuxSessionName(`${profile}-${defaultTmuxSessionName(process.cwd())}`);
1154
+ return launchTmuxUi([...cleanArgs, '--session', session], { codexArgs: ['--profile', profile] });
1449
1155
  }
1450
1156
  console.error('Usage: sks auto-review status|enable|disable|start [--high] [--json]');
1451
1157
  console.error('Alias: sks --Auto-review [--high]');
@@ -1480,7 +1186,7 @@ async function codexAppHelp(args = []) {
1480
1186
  'ㅅㅋㅅ Codex App', '',
1481
1187
  formatCodexAppStatus(status), '',
1482
1188
  `Skills: project=${skills.project.ok ? 'ok' : `missing ${skills.project.missing.length}`} global=${skills.global.ok ? 'ok' : `missing ${skills.global.missing.length}`}`, '',
1483
- 'Setup:', ' sks bootstrap', ' sks deps check', ' sks codex-app check', ' sks warp check', '',
1189
+ 'Setup:', ' sks bootstrap', ' sks deps check', ' sks codex-app check', ' sks tmux check', '',
1484
1190
  'Generated files:', ' .codex/config.toml', ' .codex/hooks.json', ' .agents/skills/', ' .codex/agents/', ' .codex/SNEAKOSCOPE.md', ' AGENTS.md', '',
1485
1191
  'Git ignore:', ' default setup writes .gitignore entries for .sneakoscope/, .codex/, .agents/, AGENTS.md', ' --local-only writes those patterns to .git/info/exclude instead', '',
1486
1192
  'Prompt routes:', formatDollarCommandsCompact(' ')
@@ -1513,19 +1219,20 @@ Examples:
1513
1219
  function usage(args = []) {
1514
1220
  const topic = String(args[0] || 'overview').toLowerCase();
1515
1221
  const blocks = {
1516
- overview: ['ㅅㅋㅅ Usage', '', 'Discover:', ' sks commands', ' sks quickstart', ' sks root', ' sks bootstrap', ' sks deps check', ' sks codex-app check', ' sks warp check', ' sks dollar-commands', '', `Topics: ${USAGE_TOPICS}`],
1222
+ overview: ['ㅅㅋㅅ Usage', '', 'Discover:', ' sks commands', ' sks quickstart', ' sks root', ' sks bootstrap', ' sks deps check', ' sks codex-app check', ' sks tmux check', ' sks dollar-commands', '', `Topics: ${USAGE_TOPICS}`],
1517
1223
  install: ['Install', '', ' npm i -g sneakoscope', ' sks root', ' sks', '', 'Project bootstrap:', ' sks bootstrap', '', 'Fallback:', ' npx -y -p sneakoscope sks root', '', 'Project:', ' npm i -D sneakoscope', ' npx sks setup --install-scope project'],
1518
- bootstrap: ['Bootstrap', '', ' sks bootstrap', ' sks setup --bootstrap', '', 'Creates project SKS files, Codex App skills/hooks/config, state/guard files, then checks Codex App, Context7, and warp.'],
1224
+ bootstrap: ['Bootstrap', '', ' sks bootstrap', ' sks setup --bootstrap', '', 'Creates project SKS files, Codex App skills/hooks/config, state/guard files, then checks Codex App, Context7, and tmux.'],
1519
1225
  root: ['Root', '', ' sks root [--json]', '', 'Inside a project, SKS uses that project root. Outside any project marker, runtime commands use the per-user global SKS root instead of writing .sneakoscope into the current random folder.'],
1520
- deps: ['Dependencies', '', ' sks deps check [--json]', ' sks deps install [warp|codex|context7|all] [--yes]', '', 'warp on macOS uses Homebrew only after approval.'],
1521
- warp: ['warp', '', ' sks warp open', ' sks warp check', ' sks warp status --once', ' sks deps install warp', '', 'Warp launch is explicit. Running bare `sks` prints help and never opens Warp by itself.'],
1522
- team: ['Team', '', ' sks team "task" executor:5 reviewer:2 user:1', ' sks team watch latest', ' sks team lane latest --agent analysis_scout_1 --follow', ' sks team message latest --from analysis_scout_1 --to executor_1 --message "handoff note"', ' sks team cleanup-warp latest', '', '$Team runs questions -> contract -> scouts -> TriWiki attention -> debate -> runtime graph/inbox -> fresh executors -> review -> cleanup -> reflection -> Honest.'],
1226
+ deps: ['Dependencies', '', ' sks deps check [--json]', ' sks deps install [tmux|codex|context7|all] [--yes]', '', 'tmux on macOS uses Homebrew only after approval.'],
1227
+ tmux: ['tmux', '', ' sks tmux open', ' sks tmux check', ' sks tmux status --once', ' sks deps install tmux', '', 'tmux launch is explicit. Running bare `sks` prints help and never opens tmux by itself.'],
1228
+ team: ['Team', '', ' sks team "task" executor:5 reviewer:2 user:1', ' sks team watch latest', ' sks team lane latest --agent analysis_scout_1 --follow', ' sks team message latest --from analysis_scout_1 --to executor_1 --message "handoff note"', ' sks team cleanup-tmux latest', '', '$Team runs questions -> contract -> scouts -> TriWiki attention -> debate -> runtime graph/inbox -> fresh executors -> review -> cleanup -> reflection -> Honest.'],
1523
1229
  'qa-loop': ['QA-LOOP', '', ' sks qa-loop prepare "QA this app"', ' sks qa-loop answer <MISSION_ID> answers.json', ' sks qa-loop run <MISSION_ID> --max-cycles 8', '', 'Report: YYYY-MM-DD-v<version>-qa-report.md'],
1230
+ ppt: ['PPT', '', ' $PPT 투자자용 피치덱을 HTML 기반 PDF로 만들어줘', ' $PPT 우리 SaaS 소개자료 만들어줘', ' sks ppt build latest --json', ' sks ppt status latest --json', '', '$PPT asks delivery context, audience profile, STP strategy, decision context, and 3+ pain-point/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. The visual system must stay simple, restrained, and information-first; editable source HTML is kept under source-html/, PPT-only temporary build files are cleaned, and generated image assets prefer Codex App built-in image generation through imagegen.'],
1524
1231
  goal: ['Goal', '', ' sks goal create "task"', ' sks goal status latest', ' sks goal pause latest', ' sks goal resume latest', ' sks goal clear latest'],
1525
1232
  'codex-app': ['Codex App', '', ' sks bootstrap', ' sks codex-app check', ' sks dollar-commands', ' cat .codex/SNEAKOSCOPE.md'],
1526
1233
  dollar: ['Dollar Commands', '', formatDollarCommandsCompact(' '), '', 'Terminal: sks dollar-commands [--json]'],
1527
1234
  wiki: ['TriWiki', '', ' sks wiki pack', ' sks wiki refresh [--prune]', ' sks wiki sweep latest --json', ' sks wiki validate .sneakoscope/wiki/context-pack.json', ' sks wiki prune --dry-run --json', '', 'Packs include attention.use_first and attention.hydrate_first for compact recall plus source hydration. Sweep records intentional forgetting and promotion candidates.'],
1528
- harness: ['Harness Growth', '', ' sks harness fixture --json', ' sks harness review --json', '', 'Runs deterministic fixtures for deliberate forgetting, skill cards, harness experiments, tool error taxonomy, permission profiles, MultiAgentV2, and Warp cockpit views.'],
1235
+ harness: ['Harness Growth', '', ' sks harness fixture --json', ' sks harness review --json', '', 'Runs deterministic fixtures for deliberate forgetting, skill cards, harness experiments, tool error taxonomy, permission profiles, MultiAgentV2, and tmux cockpit views.'],
1529
1236
  'skill-dream': ['Skill Dreaming', '', ' sks skill-dream status', ' sks skill-dream run --json', ' sks skill-dream record --route team --skills team,prompt-pipeline', '', 'Records cheap JSON usage counters in .sneakoscope/skills/dream-state.json and periodically writes recommendation-only keep/merge/prune/improve reports. It never deletes or merges skills automatically.'],
1530
1237
  'code-structure': ['Code Structure', '', ' sks code-structure scan', ' sks code-structure scan --json', '', 'Flags handwritten source files above 1000/2000/3000-line thresholds and records split-review exceptions.'],
1531
1238
  gx: ['GX', '', ' sks gx init architecture-atlas', ' sks gx render architecture-atlas --format all', ' sks gx validate architecture-atlas']
@@ -1550,13 +1257,13 @@ async function bootstrap(args = []) {
1550
1257
  const cliTools = await ensureRelatedCliTools(args);
1551
1258
  const context7Status = await checkContext7(root);
1552
1259
  const appRuntime = await codexAppIntegrationStatus({ codex: await getCodexInfo().catch(() => ({})) });
1553
- const deps = await depsStatus(root, { context7: context7Status, codexApp: appRuntime, warp: cliTools.warp });
1260
+ const deps = await depsStatus(root, { context7: context7Status, codexApp: appRuntime, tmux: cliTools.tmux });
1554
1261
  const install = await installStatus(root, installScope, { globalCommand });
1555
1262
  const versioningInfo = await versioningStatus(root);
1556
1263
  const skills = await checkRequiredSkills(root);
1557
1264
  const guard = await harnessGuardStatus(root);
1558
1265
  const files = await codexAppFilesStatus(root, skills, versioningInfo);
1559
- const ready = Boolean(!conflicts.hard_block && install.ok && files.ok && skills.ok && guard.ok && context7Status.ok && appRuntime.ok && deps.warp.ok);
1266
+ const ready = Boolean(!conflicts.hard_block && install.ok && files.ok && skills.ok && guard.ok && context7Status.ok && appRuntime.ok && deps.tmux.ok);
1560
1267
  const result = {
1561
1268
  root,
1562
1269
  ready,
@@ -1567,7 +1274,7 @@ async function bootstrap(args = []) {
1567
1274
  codex_app: appRuntime,
1568
1275
  global_skills: globalSkills,
1569
1276
  context7: context7Status,
1570
- warp: deps.warp,
1277
+ tmux: deps.tmux,
1571
1278
  harness_guard: guard,
1572
1279
  deps,
1573
1280
  next: ready ? ['sks', '$Team implement ...', '$QA-LOOP run ...'] : deps.next_actions
@@ -1580,7 +1287,7 @@ async function bootstrap(args = []) {
1580
1287
  console.log(`Hooks: ${files.hooks.ok ? 'ok' : 'missing'}`);
1581
1288
  console.log(`Harness guard: ${guard.ok ? 'ok' : 'blocked'}`);
1582
1289
  console.log(`Context7: ${context7Status.ok ? 'ok' : 'missing'}`);
1583
- console.log(`warp: ${deps.warp.ok ? 'ok' : 'missing'}${deps.warp.version ? ` ${deps.warp.version}` : ''}`);
1290
+ console.log(`tmux: ${deps.tmux.ok ? 'ok' : 'missing'}${deps.tmux.version ? ` ${deps.tmux.version}` : ''}`);
1584
1291
  console.log(`ready: ${ready ? 'true' : 'false'}`);
1585
1292
  if (!ready) {
1586
1293
  console.log('\nNext:');
@@ -1659,7 +1366,7 @@ async function setup(args) {
1659
1366
  console.log('ㅅㅋㅅ Setup\n');
1660
1367
  console.log(`Project: ${root}`);
1661
1368
  console.log(`Install: ${install.ok ? 'ok' : 'missing'} ${install.scope} (${install.command_prefix})`);
1662
- console.log(`CLI tools: Codex ${formatCodexCliToolStatus(cliTools.codex)}; warp ${warpStatusKind(cliTools.warp)} ${cliTools.warp.version || cliTools.warp.error || ''}`.trimEnd());
1369
+ console.log(`CLI tools: Codex ${formatCodexCliToolStatus(cliTools.codex)}; tmux ${tmuxStatusKind(cliTools.tmux)} ${cliTools.tmux.version || cliTools.tmux.error || ''}`.trimEnd());
1663
1370
  console.log(`Hooks: ${path.relative(root, hooksPath)}`);
1664
1371
  console.log(`Version: ${versioningInfo.enabled ? (versioningInfo.hook_installed ? 'auto-bump enabled' : 'auto-bump hook missing') : 'not enabled'}${versioningInfo.package_version ? ` (${versioningInfo.package_version})` : ''}`);
1665
1372
  if (localOnly) console.log('Git: local-only (.git/info/exclude; user AGENTS preserved, SKS managed block refreshed)');
@@ -1667,12 +1374,12 @@ async function setup(args) {
1667
1374
  console.log(`Codex App: .codex/config.toml, .codex/hooks.json, .agents/skills, .codex/agents, .codex/SNEAKOSCOPE.md`);
1668
1375
  console.log(`Global $: ${globalSkills.status === 'installed' ? 'ok' : globalSkills.status} ${globalSkills.root || ''}`.trimEnd());
1669
1376
  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'}`);
1670
- console.log(`Prompt: intent-first routing, $Answer fact-check route, $DFix ultralight design/content route, Context7 gate`);
1377
+ console.log(`Prompt: intent-first routing, $Answer fact-check route, $DFix ultralight design/content route, $PPT HTML/PDF presentation route, Context7 gate`);
1671
1378
  console.log(`Skills: .agents/skills`);
1672
1379
  console.log(`Next: sks context7 check; sks selftest --mock; sks commands; sks dollar-commands`);
1673
1380
  if (cliTools.codex.status === 'failed') console.log(`\nCodex CLI install failed. Run manually: npm i -g @openai/codex. ${cliTools.codex.error || ''}`.trim());
1674
1381
  if (cliTools.codex.status === 'installed_not_on_path') console.log(`\nCodex CLI installed but not on PATH. ${cliTools.codex.hint}`);
1675
- if (!cliTools.warp.ok) console.log(`\nwarp ${warpStatusKind(cliTools.warp)}. Install: ${cliTools.warp.install_hint}`);
1382
+ if (!cliTools.tmux.ok) console.log(`\ntmux ${tmuxStatusKind(cliTools.tmux)}. Install: ${cliTools.tmux.install_hint}`);
1676
1383
  if (!install.ok && install.scope === 'global') console.log('\nGlobal command missing. Run: npm i -g sneakoscope');
1677
1384
  if (!install.ok && install.scope === 'project') console.log('\nProject package missing. Run: npm i -D sneakoscope');
1678
1385
  if (!appRuntime.ok) console.log('\nCodex App and first-party Codex Computer Use are required for SKS QA/visual evidence; Browser Use is not a UI verification substitute. Run: sks codex-app check');
@@ -1743,7 +1450,7 @@ async function doctor(args) {
1743
1450
  const dbScan = await scanDbSafety(root).catch((err) => ({ ok: false, findings: [{ id: 'db_safety_scan_failed', severity: 'high', reason: err.message }] }));
1744
1451
  const context7Status = await checkContext7(root);
1745
1452
  const appRuntime = await codexAppIntegrationStatus({ codex });
1746
- const warpStatus = await warpReadiness().catch((err) => ({ ok: false, version: null, error: err.message }));
1453
+ const tmuxStatus = await tmuxReadiness().catch((err) => ({ ok: false, version: null, error: err.message }));
1747
1454
  const skillStatus = await checkRequiredSkills(root);
1748
1455
  const globalSkillStatus = await checkRequiredSkills(null, globalCodexSkillsRoot());
1749
1456
  const guardStatus = await harnessGuardStatus(root);
@@ -1764,7 +1471,7 @@ async function doctor(args) {
1764
1471
  sneakoscope: { ok: await exists(path.join(root, '.sneakoscope')) },
1765
1472
  context7: context7Status,
1766
1473
  codex_app_runtime: appRuntime,
1767
- runtime: { warp: { ok: Boolean(warpStatus.ok), app: warpStatus.app || null, cli: warpStatus.cli || null, version: warpStatus.version || null, launch_config_dir: warpStatus.launch_config_dir || null, uri_scheme: warpStatus.uri_scheme || null, install_hint: warpStatus.ok ? null : platformWarpInstallHint(), error: warpStatus.error || null } },
1474
+ runtime: { tmux: { ok: Boolean(tmuxStatus.ok), bin: tmuxStatus.bin || null, version: tmuxStatus.version || null, min_version: tmuxStatus.min_version || '3.0', current_session: Boolean(tmuxStatus.current_session), install_hint: tmuxStatus.ok ? null : platformTmuxInstallHint(), error: tmuxStatus.error || null } },
1768
1475
  harness_guard: guardStatus,
1769
1476
  versioning: versioningInfo,
1770
1477
  db_guard: { ok: dbPolicyExists && dbScan.ok, policy: dbPolicyExists ? await loadDbSafetyPolicy(root) : null, scan: dbScan },
@@ -1776,7 +1483,7 @@ async function doctor(args) {
1776
1483
  },
1777
1484
  package: { bytes: pkgBytes, human: formatBytes(pkgBytes) }, storage
1778
1485
  };
1779
- result.ready = !result.harness_conflicts.hard_block && nodeOk && Boolean(codex.bin) && install.ok && result.sneakoscope.ok && result.context7.ok && appRuntime.ok && result.runtime.warp.ok && result.harness_guard.ok && result.versioning.ok && result.db_guard.ok && result.codex_app.ok && result.skills.ok && result.global_skills.ok;
1486
+ result.ready = !result.harness_conflicts.hard_block && nodeOk && Boolean(codex.bin) && install.ok && result.sneakoscope.ok && result.context7.ok && appRuntime.ok && result.runtime.tmux.ok && result.harness_guard.ok && result.versioning.ok && result.db_guard.ok && result.codex_app.ok && result.skills.ok && result.global_skills.ok;
1780
1487
  if (result.harness_conflicts.hard_block) process.exitCode = 1;
1781
1488
  if (flag(args, '--json')) return console.log(JSON.stringify(result, null, 2));
1782
1489
  console.log('ㅅㅋㅅ Doctor\n');
@@ -1792,7 +1499,7 @@ async function doctor(args) {
1792
1499
  console.log(`State: ${result.sneakoscope.ok ? 'ok' : 'missing .sneakoscope'}`);
1793
1500
  console.log(`Context7: ${result.context7.ok ? 'ok' : 'missing MCP config'} project=${result.context7.project.ok ? 'ok' : 'missing'} global=${result.context7.global.ok ? 'ok' : 'missing'}`);
1794
1501
  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'}`);
1795
- console.log(`warp: ${warpStatusKind(result.runtime.warp)} ${result.runtime.warp.version || result.runtime.warp.error || ''}`.trimEnd());
1502
+ console.log(`tmux: ${tmuxStatusKind(result.runtime.tmux)} ${result.runtime.tmux.version || result.runtime.tmux.error || ''}`.trimEnd());
1796
1503
  console.log(`Guard: ${result.harness_guard.ok ? 'ok' : 'blocked'}${result.harness_guard.source_exception ? ' source-exception' : ''}`);
1797
1504
  console.log(`Version: ${result.versioning.ok ? 'ok' : 'missing'}${result.versioning.enabled ? ` ${result.versioning.package_version || ''}` : ` ${result.versioning.reason || 'disabled'}`}`);
1798
1505
  console.log(`DB Guard: ${result.db_guard.ok ? 'ok' : 'blocked'} ${dbScan.findings?.length || 0} finding(s)`);
@@ -1809,13 +1516,13 @@ async function doctor(args) {
1809
1516
  if (result.harness_conflicts.hard_block) console.log(`\n${formatHarnessConflictReport(conflictScan)}`);
1810
1517
  if (!result.context7.ok) console.log('Context7 MCP missing. Run: sks context7 setup --scope project');
1811
1518
  if (!appRuntime.ok) console.log('Codex App or first-party MCP/plugin tools missing. Run: sks codex-app check');
1812
- if (!result.runtime.warp.ok) console.log('Warp missing. Run: sks deps install warp');
1519
+ if (!result.runtime.tmux.ok) console.log('tmux missing. Run: sks deps install tmux');
1813
1520
  if (!result.harness_guard.ok) console.log('Harness guard failed. Run: sks setup from a real terminal, then sks guard check.');
1814
1521
  if (!result.versioning.ok) console.log('Versioning hook missing. Run: sks versioning hook, or sks doctor --fix.');
1815
1522
  if (!result.skills.ok) console.log(`Missing skills: ${result.skills.missing.join(', ')}. Run: sks setup`);
1816
1523
  if (!result.global_skills.ok) console.log(`Missing global $ skills: ${result.global_skills.missing.join(', ')}. Run: npm i -g sneakoscope, or sks setup from a non-local-only run.`);
1817
1524
  const blocked = [];
1818
- if (!result.runtime.warp.ok) blocked.push(['Warp is missing', 'sks deps install warp']);
1525
+ if (!result.runtime.tmux.ok) blocked.push(['tmux is missing', 'sks deps install tmux']);
1819
1526
  if (!appRuntime.ok) blocked.push(['Codex App or first-party MCP/plugin tools need setup', 'sks codex-app check']);
1820
1527
  if (blocked.length) {
1821
1528
  console.log('\nBlocked:');
@@ -1826,18 +1533,6 @@ async function doctor(args) {
1826
1533
  if (!result.ready && !flag(args, '--fix')) console.log('Run: sks doctor --fix');
1827
1534
  }
1828
1535
 
1829
- async function checkRequiredSkills(root, skillRoot = path.join(root, '.agents', 'skills')) {
1830
- const expected = Array.from(new Set([
1831
- ...DOLLAR_SKILL_NAMES,
1832
- ...RECOMMENDED_SKILLS
1833
- ])).sort();
1834
- const missing = [];
1835
- for (const name of expected) {
1836
- if (!(await exists(path.join(skillRoot, name, 'SKILL.md')))) missing.push(name);
1837
- }
1838
- return { ok: missing.length === 0, root: skillRoot, expected, missing };
1839
- }
1840
-
1841
1536
  async function codexAppSkillReadiness(root = null) {
1842
1537
  root ||= await sksRoot();
1843
1538
  const project = await checkRequiredSkills(root);
@@ -1845,10 +1540,6 @@ async function codexAppSkillReadiness(root = null) {
1845
1540
  return { ok: project.ok || global.ok, project, global };
1846
1541
  }
1847
1542
 
1848
- function globalCodexSkillsRoot(home = process.env.HOME || os.homedir()) {
1849
- return path.join(home, '.agents', 'skills');
1850
- }
1851
-
1852
1543
  async function init(args) {
1853
1544
  const root = await projectRoot();
1854
1545
  const conflicts = await scanHarnessConflicts(root);
@@ -2100,15 +1791,15 @@ async function selftest() {
2100
1791
  await ensureDir(path.join(conflictTmp, '.omx'));
2101
1792
  const conflictScan = await scanHarnessConflicts(conflictTmp, { home: path.join(conflictTmp, 'home') });
2102
1793
  if (!conflictScan.hard_block || !formatHarnessConflictReport(conflictScan).includes('GPT-5.5')) throw new Error('selftest failed: OMX conflict did not block with cleanup prompt');
2103
- const postinstallConflict = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'postinstall'], { cwd: conflictTmp, env: { INIT_CWD: conflictTmp, HOME: path.join(conflictTmp, 'home'), SKS_SKIP_POSTINSTALL_SHIM: '1', SKS_SKIP_POSTINSTALL_CONTEXT7: '1' }, timeoutMs: 15000, maxOutputBytes: 128 * 1024 });
1794
+ const postinstallConflict = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'postinstall'], { cwd: conflictTmp, env: { INIT_CWD: conflictTmp, HOME: path.join(conflictTmp, 'home'), SKS_SKIP_POSTINSTALL_SHIM: '1', SKS_SKIP_POSTINSTALL_CONTEXT7: '1', SKS_SKIP_POSTINSTALL_GETDESIGN: '1' }, timeoutMs: 15000, maxOutputBytes: 128 * 1024 });
2104
1795
  if (postinstallConflict.code !== 0) throw new Error('selftest failed: postinstall conflict notice should not make npm install fail');
2105
1796
  const postinstallConflictOutput = String(`${postinstallConflict.stdout}\n${postinstallConflict.stderr}`);
2106
1797
  if (!postinstallConflictOutput.includes('SKS setup is blocked') || postinstallConflictOutput.includes('Cleanup prompt:')) throw new Error('selftest failed: postinstall conflict notice did not stay informational');
2107
- const postinstallConflictPrompt = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'postinstall'], { cwd: conflictTmp, input: 'y\n', env: { INIT_CWD: conflictTmp, HOME: path.join(conflictTmp, 'home'), SKS_SKIP_POSTINSTALL_SHIM: '1', SKS_SKIP_POSTINSTALL_CONTEXT7: '1', SKS_POSTINSTALL_PROMPT: '1' }, timeoutMs: 15000, maxOutputBytes: 128 * 1024 });
1798
+ const postinstallConflictPrompt = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'postinstall'], { cwd: conflictTmp, input: 'y\n', env: { INIT_CWD: conflictTmp, HOME: path.join(conflictTmp, 'home'), SKS_SKIP_POSTINSTALL_SHIM: '1', SKS_SKIP_POSTINSTALL_CONTEXT7: '1', SKS_SKIP_POSTINSTALL_GETDESIGN: '1', SKS_POSTINSTALL_PROMPT: '1' }, timeoutMs: 15000, maxOutputBytes: 128 * 1024 });
2108
1799
  if (postinstallConflictPrompt.code !== 0 || !String(postinstallConflictPrompt.stdout || '').includes('Goal: completely remove the conflicting Codex harnesses')) throw new Error('selftest failed: interactive postinstall prompt did not print cleanup prompt');
2109
1800
  const postinstallSetupTmp = tmpdir();
2110
1801
  await writeJsonAtomic(path.join(postinstallSetupTmp, 'package.json'), { name: 'postinstall-setup-smoke', version: '0.0.0' });
2111
- const postinstallSetup = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'postinstall'], { cwd: postinstallSetupTmp, env: { INIT_CWD: postinstallSetupTmp, HOME: path.join(postinstallSetupTmp, 'home'), SKS_SKIP_POSTINSTALL_SHIM: '1', SKS_SKIP_POSTINSTALL_CONTEXT7: '1', SKS_SKIP_CLI_TOOLS: '1' }, timeoutMs: 30000, maxOutputBytes: 256 * 1024 });
1802
+ const postinstallSetup = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'postinstall'], { cwd: postinstallSetupTmp, env: { INIT_CWD: postinstallSetupTmp, HOME: path.join(postinstallSetupTmp, 'home'), SKS_SKIP_POSTINSTALL_SHIM: '1', SKS_SKIP_POSTINSTALL_CONTEXT7: '1', SKS_SKIP_POSTINSTALL_GETDESIGN: '1', SKS_SKIP_CLI_TOOLS: '1' }, timeoutMs: 30000, maxOutputBytes: 256 * 1024 });
2112
1803
  if (postinstallSetup.code !== 0) throw new Error(`selftest failed: postinstall setup exited ${postinstallSetup.code}: ${postinstallSetup.stderr}`);
2113
1804
  if (await exists(path.join(postinstallSetupTmp, '.agents', 'skills', 'agent-team', 'SKILL.md'))) throw new Error('selftest failed: postinstall installed deprecated agent-team fallback skill');
2114
1805
  if (!String(postinstallSetup.stdout || '').includes('SKS bootstrap: auto-running sks setup --bootstrap --install-scope global --force') || !String(postinstallSetup.stdout || '').includes('SKS Ready')) throw new Error('selftest failed: postinstall did not auto-run global forced bootstrap');
@@ -2116,6 +1807,7 @@ async function selftest() {
2116
1807
  if (!String(postinstallSetup.stdout || '').includes('Codex App global $ skills: installed')) throw new Error('selftest failed: postinstall did not report automatic global Codex App skills');
2117
1808
  const postinstallSetupManifest = await readJson(path.join(postinstallSetupTmp, '.sneakoscope', 'manifest.json'));
2118
1809
  if (postinstallSetupManifest.installation?.scope !== 'global') throw new Error('selftest failed: postinstall automatic bootstrap did not use global install scope');
1810
+ if (!postinstallSetupManifest.recommended_design_references?.some((entry) => entry.id === 'getdesign' && entry.codex_skill_install === GETDESIGN_REFERENCE.codex_skill_install)) throw new Error('selftest failed: postinstall manifest missing getdesign reference');
2119
1811
  for (const rel of ['.agents/skills/team/SKILL.md', '.codex/config.toml', '.codex/hooks.json', '.sneakoscope/harness-guard.json', '.codex/SNEAKOSCOPE.md', 'AGENTS.md', '.gitignore']) {
2120
1812
  if (!(await exists(path.join(postinstallSetupTmp, rel)))) throw new Error(`selftest failed: automatic postinstall bootstrap did not create ${rel}`);
2121
1813
  }
@@ -2125,6 +1817,7 @@ async function selftest() {
2125
1817
  const skillName = command.slice(1).toLowerCase();
2126
1818
  if (!(await exists(path.join(postinstallSetupTmp, 'home', '.agents', 'skills', skillName, 'SKILL.md')))) throw new Error(`selftest failed: postinstall global ${command} skill not installed`);
2127
1819
  }
1820
+ if (!(await exists(path.join(postinstallSetupTmp, 'home', '.agents', 'skills', 'getdesign-reference', 'SKILL.md')))) throw new Error('selftest failed: postinstall global getdesign-reference skill not installed');
2128
1821
  const oldNoBootstrap = process.env.SKS_POSTINSTALL_NO_BOOTSTRAP;
2129
1822
  process.env.SKS_POSTINSTALL_NO_BOOTSTRAP = '1';
2130
1823
  const noBootstrapDecision = await postinstallBootstrapDecision(postinstallSetupTmp);
@@ -2138,7 +1831,7 @@ async function selftest() {
2138
1831
  if (!bootstrapResult.project_setup?.ok || typeof bootstrapResult.ready !== 'boolean') throw new Error('selftest failed: bootstrap json did not report project setup and ready boolean');
2139
1832
  const depsCheck = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'deps', 'check', '--json'], { cwd: bootstrapJsonTmp, env: { HOME: path.join(bootstrapJsonTmp, 'home') }, timeoutMs: 20000, maxOutputBytes: 256 * 1024 });
2140
1833
  const depsResult = JSON.parse(depsCheck.stdout);
2141
- if (!depsResult.node?.ok || !('warp' in depsResult) || !('homebrew' in depsResult)) throw new Error('selftest failed: deps check json missing expected fields');
1834
+ if (!depsResult.node?.ok || !('tmux' in depsResult) || !('homebrew' in depsResult)) throw new Error('selftest failed: deps check json missing expected fields');
2142
1835
  const globalCwd = tmpdir();
2143
1836
  const globalRuntimeRoot = path.join(tmpdir(), 'sks-global-root');
2144
1837
  const globalRootProbe = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'root', '--json'], { cwd: globalCwd, env: { SKS_GLOBAL_ROOT: globalRuntimeRoot }, timeoutMs: 15000, maxOutputBytes: 64 * 1024 });
@@ -2156,32 +1849,13 @@ async function selftest() {
2156
1849
  const madProfileText = await safeReadText(madProfilePath);
2157
1850
  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"') || !madProfileText.includes('unrequested fallback implementation code')) throw new Error('selftest failed: MAD high profile is not full-access auto-review high with fallback-code guard');
2158
1851
  if (!isMadHighLaunch(['--mad', '--high']) || isMadHighLaunch(['db', '--mad'])) throw new Error('selftest failed: MAD high launch flag parsing is not top-level only');
2159
- const workspacePlan = { workspace: 'sks-mad-selftest', root: tmp, codexArgs: ['--profile', 'sks-mad-high'] };
2160
- const warpLaunchYaml = buildWarpLaunchConfigYaml({ ...workspacePlan, command: 'codex', title: 'sks-mad-selftest' }, [{ cwd: tmp, command: 'codex --profile sks-mad-high', focused: true }]);
2161
- const warpSyntax = runWarpLaunchConfigSyntaxCheck(warpLaunchYaml);
2162
- if (!warpSyntax.ok || !warpLaunchYaml.includes('name: "sks-mad-selftest"') || !warpLaunchYaml.includes('commands:')) throw new Error('selftest failed: MAD Warp launch configuration was not generated with name and command');
2163
- const warpOpenArgs = buildWarpOpenArgs(workspacePlan);
2164
- if (!warpOpenArgs.includes('open') || !warpOpenArgs.some((arg) => String(arg).includes('warp://launch/sks-mad-selftest.yaml'))) throw new Error('selftest failed: MAD Warp launch URI is not stable by workspace name');
2165
- if (!isWarpShellSession({ TERM_PROGRAM: 'WarpTerminal' })) throw new Error('selftest failed: Warp shell session env was not detected');
2166
- const warpNestedDecision = warpOpenLaunchDecision({ env: { TERM_PROGRAM: 'WarpTerminal' } });
2167
- if (warpNestedDecision.open || !warpNestedDecision.current_session) throw new Error('selftest failed: nested Warp launch was not redirected to current session');
2168
- const oldWarpConfigDir = process.env.SKS_WARP_LAUNCH_CONFIG_DIR;
2169
- process.env.SKS_WARP_LAUNCH_CONFIG_DIR = path.join(tmp, 'warp-launch-configs');
2170
- const writtenWarpConfig = await writeWarpLaunchConfig({ ...workspacePlan, command: 'codex', title: 'sks-mad-selftest' }, [{ cwd: tmp, command: 'codex --profile sks-mad-high', focused: true }]);
2171
- if (!(await exists(writtenWarpConfig.config_path)) || !writtenWarpConfig.record.launch_uri.includes('warp://launch/')) throw new Error('selftest failed: Warp launch configuration was not persisted for URI launch');
2172
- const currentSessionLaunch = await launchWarpUi(['--workspace', 'sks-current-session-selftest'], {
2173
- root: tmp,
2174
- codex: { bin: 'printf', version: 'mock' },
2175
- app: { ok: true, guidance: [] },
2176
- warp: { ok: true, version: 'Warp.app' },
2177
- env: { TERM_PROGRAM: 'WarpTerminal' },
2178
- dryRunCurrentSession: true,
2179
- quiet: true
2180
- });
2181
- if (!currentSessionLaunch.opened?.current_session || currentSessionLaunch.opened?.skipped) throw new Error('selftest failed: Warp shell launch did not stay in the current session');
2182
- if (oldWarpConfigDir === undefined) delete process.env.SKS_WARP_LAUNCH_CONFIG_DIR;
2183
- else process.env.SKS_WARP_LAUNCH_CONFIG_DIR = oldWarpConfigDir;
2184
- if (warpStatusKind({ ok: false, bin: null }) !== 'missing') throw new Error('selftest failed: missing warp was not labeled missing');
1852
+ const workspacePlan = { session: 'sks-mad-selftest', root: tmp, codexArgs: ['--profile', 'sks-mad-high'] };
1853
+ const tmuxSyntax = runTmuxLaunchPlanSyntaxCheck(workspacePlan);
1854
+ if (!tmuxSyntax.ok || !tmuxSyntax.command.includes('tmux attach-session -t sks-mad-selftest')) throw new Error('selftest failed: MAD tmux attach plan is not stable by session name');
1855
+ const tmuxOpenArgs = buildTmuxOpenArgs(workspacePlan);
1856
+ if (tmuxOpenArgs.join(' ') !== 'attach-session -t sks-mad-selftest') throw new Error('selftest failed: MAD tmux attach args are not stable by session name');
1857
+ if (!isTmuxShellSession({ TMUX: '/tmp/tmux-501/default,1,0' })) throw new Error('selftest failed: tmux shell session env was not detected');
1858
+ if (tmuxStatusKind({ ok: false, bin: null }) !== 'missing') throw new Error('selftest failed: missing tmux was not labeled missing');
2185
1859
  const guardBlocked = await checkHarnessModification(tmp, { tool_name: 'apply_patch', command: '*** Update File: .agents/skills/team/SKILL.md\n+tamper\n' });
2186
1860
  if (guardBlocked.action !== 'block') throw new Error('selftest failed: harness guard allowed skill tampering');
2187
1861
  const setupBlocked = await checkHarnessModification(tmp, { command: 'sks setup --force' });
@@ -2325,16 +1999,19 @@ async function selftest() {
2325
1999
  const promptPipelineText = await safeReadText(path.join(tmp, '.agents', 'skills', 'prompt-pipeline', 'SKILL.md'));
2326
2000
  if (!promptPipelineText.includes('TriWiki context-tracking SSOT')) throw new Error('selftest failed: prompt pipeline missing TriWiki context-tracking SSOT');
2327
2001
  if (!promptPipelineText.includes('before every route stage') || !promptPipelineText.includes('sks wiki refresh')) throw new Error('selftest failed: prompt pipeline missing per-stage TriWiki policy');
2328
- if (!promptPipelineText.includes('design.md') || !promptPipelineText.includes('imagegen')) throw new Error('selftest failed: prompt pipeline missing design/image asset routing');
2002
+ if (!promptPipelineText.includes('design.md') || !promptPipelineText.includes('imagegen') || !promptPipelineText.includes('getdesign-reference')) throw new Error('selftest failed: prompt pipeline missing design/image/getdesign routing');
2003
+ if (!promptPipelineText.includes(CODEX_APP_IMAGE_GENERATION_DOC_URL)) throw new Error('selftest failed: prompt pipeline missing Codex App image generation policy');
2329
2004
  if (!promptPipelineText.includes('From-Chat-IMG') || !promptPipelineText.includes('Do not assume ordinary image prompts are chat captures')) throw new Error('selftest failed: prompt pipeline missing explicit From-Chat-IMG gating');
2330
2005
  const fromChatImgSkillText = await safeReadText(path.join(tmp, '.agents', 'skills', 'from-chat-img', 'SKILL.md'));
2331
2006
  if (!fromChatImgSkillText.includes('normal Team pipeline') || !fromChatImgSkillText.includes('Codex Computer Use visual inspection') || !fromChatImgSkillText.includes(CODEX_COMPUTER_USE_ONLY_POLICY) || !fromChatImgSkillText.includes(FROM_CHAT_IMG_CHECKLIST_ARTIFACT) || !fromChatImgSkillText.includes(FROM_CHAT_IMG_TEMP_TRIWIKI_ARTIFACT) || !fromChatImgSkillText.includes(FROM_CHAT_IMG_QA_LOOP_ARTIFACT)) throw new Error('selftest failed: from-chat-img skill missing Team/Computer Use-only inspection checklist guidance');
2332
2007
  if (fromChatImgSkillText.includes('Computer Use/browser visual inspection')) throw new Error('selftest failed: from-chat-img skill still allows browser visual inspection wording');
2333
2008
  const fromChatImgSkillMeta = await safeReadText(path.join(tmp, '.agents', 'skills', 'from-chat-img', 'agents', 'openai.yaml'));
2334
2009
  if (!fromChatImgSkillMeta.includes('model_reasoning_effort: xhigh')) throw new Error('selftest failed: from-chat-img skill metadata is not xhigh');
2335
- for (const supportSkill of ['reasoning-router', 'pipeline-runner', 'context7-docs', 'seo-geo-optimizer', 'reflection', 'design-system-builder', 'design-ui-editor', 'imagegen']) {
2010
+ for (const supportSkill of ['reasoning-router', 'pipeline-runner', 'context7-docs', 'seo-geo-optimizer', 'reflection', 'design-system-builder', 'design-ui-editor', 'getdesign-reference', 'imagegen']) {
2336
2011
  if (!(await exists(path.join(tmp, '.agents', 'skills', supportSkill, 'SKILL.md')))) throw new Error(`selftest failed: ${supportSkill} skill not installed`);
2337
2012
  }
2013
+ const imagegenSkillText = await safeReadText(path.join(tmp, '.agents', 'skills', 'imagegen', 'SKILL.md'));
2014
+ if (!imagegenSkillText.includes(CODEX_APP_IMAGE_GENERATION_DOC_URL) || !imagegenSkillText.includes('$imagegen') || !imagegenSkillText.includes('gpt-image-2') || !imagegenSkillText.includes('OPENAI_API_KEY')) throw new Error('selftest failed: imagegen skill missing official Codex App image generation priority');
2338
2015
  if (!(await exists(path.join(tmp, '.agents', 'skills', 'reasoning-router', 'agents', 'openai.yaml')))) throw new Error('selftest failed: skill metadata missing');
2339
2016
  const hookGuardPayload = JSON.stringify({ cwd: tmp, tool_name: 'apply_patch', command: '*** Update File: .agents/skills/team/SKILL.md\n+tamper\n' });
2340
2017
  const hookGuardResult = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'hook', 'pre-tool'], { cwd: tmp, input: hookGuardPayload, env: { SKS_DISABLE_UPDATE_CHECK: '1' }, timeoutMs: 15000, maxOutputBytes: 64 * 1024 });
@@ -2402,7 +2079,7 @@ async function selftest() {
2402
2079
  if (!(await exists(path.join(missionDir(hookGoalTmp, hookState.mission_id), GOAL_WORKFLOW_ARTIFACT)))) throw new Error('selftest failed: $Goal hook did not write goal workflow artifact');
2403
2080
  const hookGoalDelegationTmp = tmpdir();
2404
2081
  await initProject(hookGoalDelegationTmp, {});
2405
- const hookGoalDelegationPayload = JSON.stringify({ cwd: hookGoalDelegationTmp, prompt: '$Goal 설치 화면 문제 근본적으로 구현 수정해줘' });
2082
+ const hookGoalDelegationPayload = JSON.stringify({ cwd: hookGoalDelegationTmp, prompt: '$Goal 결제 재시도 정책 근본적으로 구현 수정해줘' });
2406
2083
  const hookGoalDelegationResult = await runProcess(process.execPath, [hookBin, 'hook', 'user-prompt-submit'], { cwd: hookGoalDelegationTmp, input: hookGoalDelegationPayload, env: { SKS_DISABLE_UPDATE_CHECK: '1' }, timeoutMs: 15000, maxOutputBytes: 256 * 1024 });
2407
2084
  if (hookGoalDelegationResult.code !== 0) throw new Error(`selftest failed: $Goal implementation delegation hook exited ${hookGoalDelegationResult.code}: ${hookGoalDelegationResult.stderr}`);
2408
2085
  const hookGoalDelegationJson = JSON.parse(hookGoalDelegationResult.stdout);
@@ -2414,7 +2091,7 @@ async function selftest() {
2414
2091
  if (hookGoalDelegationState.mode !== 'TEAM' || hookGoalDelegationState.phase !== 'TEAM_CLARIFICATION_AWAITING_ANSWERS' || hookGoalDelegationState.implementation_allowed !== false) throw new Error('selftest failed: $Goal implementation delegation did not leave Team gate current');
2415
2092
  if (!(await exists(path.join(missionDir(hookGoalDelegationTmp, hookGoalDelegationBridgeMatch[1]), GOAL_WORKFLOW_ARTIFACT)))) throw new Error('selftest failed: $Goal implementation delegation did not write bridge workflow artifact');
2416
2093
  const activeGoalMissionId = hookState.mission_id;
2417
- const hookGoalOverlayPayload = JSON.stringify({ cwd: hookGoalTmp, prompt: '설치 화면 문제 근본적으로 구현 수정해줘' });
2094
+ const hookGoalOverlayPayload = JSON.stringify({ cwd: hookGoalTmp, prompt: '결제 재시도 정책 근본적으로 구현 수정해줘' });
2418
2095
  const hookGoalOverlayResult = await runProcess(process.execPath, [hookBin, 'hook', 'user-prompt-submit'], { cwd: hookGoalTmp, input: hookGoalOverlayPayload, env: { SKS_DISABLE_UPDATE_CHECK: '1' }, timeoutMs: 15000, maxOutputBytes: 256 * 1024 });
2419
2096
  if (hookGoalOverlayResult.code !== 0) throw new Error(`selftest failed: active Goal overlay hook exited ${hookGoalOverlayResult.code}: ${hookGoalOverlayResult.stderr}`);
2420
2097
  const hookGoalOverlayJson = JSON.parse(hookGoalOverlayResult.stdout);
@@ -2514,14 +2191,14 @@ async function selftest() {
2514
2191
  if (hookKoreanSksState.phase !== 'TEAM_CLARIFICATION_CONTRACT_SEALED' || hookKoreanSksState.implementation_allowed !== true || !hookKoreanSksState.ambiguity_gate_passed) throw new Error('selftest failed: Korean Team auto-seal');
2515
2192
  const hookTeamTmp = tmpdir();
2516
2193
  await initProject(hookTeamTmp, {});
2517
- const hookTeamPayload = JSON.stringify({ cwd: hookTeamTmp, prompt: '$Team 버튼 UX 수정 executor:2 reviewer:1 user:1' });
2194
+ const hookTeamPayload = JSON.stringify({ cwd: hookTeamTmp, prompt: '$Team 결제 재시도 정책 수정 executor:2 reviewer:1 user:1' });
2518
2195
  const hookTeamResult = await runProcess(process.execPath, [hookBin, 'hook', 'user-prompt-submit'], { cwd: hookTeamTmp, input: hookTeamPayload, env: { SKS_DISABLE_UPDATE_CHECK: '1' }, timeoutMs: 15000, maxOutputBytes: 256 * 1024 });
2519
2196
  if (hookTeamResult.code !== 0) throw new Error(`selftest failed: $Team hook exited ${hookTeamResult.code}: ${hookTeamResult.stderr}`);
2520
2197
  const hookTeamJson = JSON.parse(hookTeamResult.stdout);
2521
2198
  if (!hookTeamJson.hookSpecificOutput?.additionalContext?.includes('MANDATORY ambiguity-removal gate activated')) throw new Error('selftest failed: $Team hook did not force ambiguity gate before Team execution');
2522
2199
  if (!hookTeamJson.hookSpecificOutput?.additionalContext?.includes('VISIBLE RESPONSE CONTRACT') || !String(hookTeamJson.systemMessage || '').includes('clarification questions')) throw new Error('selftest failed: $Team ambiguity gate did not force visible question response');
2523
2200
  if (hookTeamJson.hookSpecificOutput?.additionalContext?.includes('GOAL_PRECISE: 이번 작업의 최종 목표')) throw new Error('selftest failed: static Team goal');
2524
- if (!hookTeamJson.hookSpecificOutput?.additionalContext?.includes('UI_STATE_BEHAVIOR')) throw new Error('selftest failed: missing Team UI question');
2201
+ if (!hookTeamJson.hookSpecificOutput?.additionalContext?.includes('PAYMENT_RETRY_POLICY')) throw new Error('selftest failed: missing Team payment question');
2525
2202
  if (!hookTeamJson.hookSpecificOutput?.additionalContext?.includes('Codex plan-tool interaction')) throw new Error('selftest failed: $Team ambiguity gate did not inject plan-tool guidance');
2526
2203
  const hookTeamState = await readJson(stateFile(hookTeamTmp), {});
2527
2204
  if (hookTeamState.phase !== 'TEAM_CLARIFICATION_AWAITING_ANSWERS' || hookTeamState.implementation_allowed !== false) throw new Error('selftest failed: $Team hook did not lock execution behind ambiguity gate');
@@ -2533,13 +2210,13 @@ async function selftest() {
2533
2210
  const hookTeamPendingState = await readJson(stateFile(hookTeamTmp), {});
2534
2211
  const hookTeamPendingContext = hookTeamPendingJson.hookSpecificOutput?.additionalContext || '';
2535
2212
  if (hookTeamPendingState.mission_id !== hookTeamState.mission_id) throw new Error('selftest failed: pending clarification allowed a new route mission to replace the visible question sheet');
2536
- if (!hookTeamPendingContext.includes('Required questions still pending') || !hookTeamPendingContext.includes('VISIBLE RESPONSE CONTRACT') || !hookTeamPendingContext.includes('UI_STATE_BEHAVIOR')) throw new Error('selftest failed: pending clarification did not re-expose the question sheet');
2213
+ if (!hookTeamPendingContext.includes('Required questions still pending') || !hookTeamPendingContext.includes('VISIBLE RESPONSE CONTRACT') || !hookTeamPendingContext.includes('PAYMENT_RETRY_POLICY')) throw new Error('selftest failed: pending clarification did not re-expose the question sheet');
2537
2214
  if (hookTeamPendingContext.includes('MANDATORY ambiguity-removal gate activated')) throw new Error('selftest failed: pending clarification prepared a new ambiguity gate instead of reusing the active one');
2538
2215
  const hookTeamStopResult = await runProcess(process.execPath, [hookBin, 'hook', 'stop'], { cwd: hookTeamTmp, input: JSON.stringify({ cwd: hookTeamTmp, last_assistant_message: 'I need three decisions before implementation, but I will not paste the Required questions block.' }), env: { SKS_DISABLE_UPDATE_CHECK: '1' }, timeoutMs: 15000, maxOutputBytes: 128 * 1024 });
2539
2216
  if (hookTeamStopResult.code !== 0) throw new Error(`selftest failed: Team stop hook exited ${hookTeamStopResult.code}: ${hookTeamStopResult.stderr}`);
2540
2217
  const hookTeamStopJson = JSON.parse(hookTeamStopResult.stdout);
2541
2218
  if (hookTeamStopJson.decision !== 'block' || !String(hookTeamStopJson.reason || '').includes('mandatory ambiguity-removal')) throw new Error('selftest failed: Stop hook did not block missing Team ambiguity answers');
2542
- if (!String(hookTeamStopJson.reason || '').includes('Required questions') || !String(hookTeamStopJson.reason || '').includes('UI_STATE_BEHAVIOR')) throw new Error('selftest failed: missing Team stop UI question');
2219
+ if (!String(hookTeamStopJson.reason || '').includes('Required questions') || !String(hookTeamStopJson.reason || '').includes('PAYMENT_RETRY_POLICY')) throw new Error('selftest failed: missing Team stop payment question');
2543
2220
  if (String(hookTeamStopJson.reason || '').includes('GOAL_PRECISE: 이번 작업의 최종 목표')) throw new Error('selftest failed: static Team stop goal');
2544
2221
  if (!String(hookTeamStopJson.reason || '').includes('sks pipeline answer')) throw new Error('selftest failed: Stop hook did not provide pipeline answer command');
2545
2222
  if (!String(hookTeamStopJson.reason || '').includes('Codex plan-tool interaction')) throw new Error('selftest failed: Stop hook did not reprint plan-tool guidance');
@@ -2972,19 +2649,19 @@ async function selftest() {
2972
2649
  if (maxTextParsed.agentSessions !== 6 || maxTextParsed.roleCounts.executor !== 6) throw new Error('selftest failed: team max-agent text parsing');
2973
2650
  const roleParsed = parseTeamCreateArgs(['executor:5', 'reviewer:2', 'user:1', '작업']);
2974
2651
  if (roleParsed.roleCounts.executor !== 5 || roleParsed.roleCounts.reviewer !== 2 || roleParsed.agentSessions !== 5 || roleParsed.prompt !== '작업') throw new Error('selftest failed: team role-count parsing');
2975
- const openWarpFlagParsed = parseTeamCreateArgs(['--open-warp', '작업']);
2976
- if (openWarpFlagParsed.prompt !== '작업') throw new Error('selftest failed: team --open-warp leaked into prompt');
2652
+ const openTmuxFlagParsed = parseTeamCreateArgs(['--open-tmux', '작업']);
2653
+ if (openTmuxFlagParsed.prompt !== '작업') throw new Error('selftest failed: team --open-tmux leaked into prompt');
2977
2654
  const roleTeamPlan = buildTeamPlan(teamId, '역할 팀 테스트', { roleCounts: roleParsed.roleCounts });
2978
2655
  if (roleTeamPlan.roster.debate_team.length !== 5) throw new Error('selftest failed: executor role count not reflected in debate team size');
2979
2656
  if (roleTeamPlan.roster.analysis_team.length !== 5) throw new Error('selftest failed: executor role count not reflected in analysis scout team');
2980
2657
  if (roleTeamPlan.roster.development_team.filter((agent) => agent.role === 'executor').length !== 5) throw new Error('selftest failed: executor role count not reflected in development team');
2981
2658
  if (!roleTeamPlan.roster.debate_team.some((agent) => /inconvenience/.test(agent.persona))) throw new Error('selftest failed: user friction persona missing from debate team');
2982
- const warpTeam = await launchWarpTeamView({ root: tmp, missionId: teamId, plan: roleTeamPlan, json: true });
2983
- if (!warpTeam.agents?.length || !warpTeam.agents.some((entry) => entry.agent === 'analysis_scout_1') || !warpTeam.agents.every((entry) => String(entry.command || '').includes('team lane') && String(entry.command || '').includes('--agent'))) throw new Error('selftest failed: Team warp view did not expose agent live lanes');
2984
- if (!warpTeam.overview?.command?.includes('team watch') || !warpTeam.lanes?.some((entry) => entry.role === 'overview') || !warpTeam.lanes?.some((entry) => entry.agent === 'analysis_scout_1')) throw new Error('selftest failed: Team warp view did not expose orchestration overview plus agent lanes');
2985
- if (teamLaneStyle('analysis_scout_1').role !== 'scout' || teamLaneStyle('executor_1').role !== 'execution' || teamLaneStyle('reviewer_1').role !== 'review') throw new Error('selftest failed: Team warp role palette did not classify lane roles');
2986
- if (!String(warpTeam.cleanup_policy || '').includes('mark-complete') || !warpTeam.lanes.every((entry) => entry.style?.color && entry.title)) throw new Error('selftest failed: Team warp view did not expose color/title metadata and cleanup policy');
2987
- if (!warpTeam.launch_uri?.includes(encodeURIComponent(`sks-team-${teamId}.yaml`))) throw new Error('selftest failed: Team warp launch URI is not named for visibility');
2659
+ const tmuxTeam = await launchTmuxTeamView({ root: tmp, missionId: teamId, plan: roleTeamPlan, json: true });
2660
+ if (!tmuxTeam.agents?.length || !tmuxTeam.agents.some((entry) => entry.agent === 'analysis_scout_1') || !tmuxTeam.agents.every((entry) => String(entry.command || '').includes('team lane') && String(entry.command || '').includes('--agent'))) throw new Error('selftest failed: Team tmux view did not expose agent live lanes');
2661
+ if (!tmuxTeam.overview?.command?.includes('team watch') || !tmuxTeam.lanes?.some((entry) => entry.role === 'overview') || !tmuxTeam.lanes?.some((entry) => entry.agent === 'analysis_scout_1')) throw new Error('selftest failed: Team tmux view did not expose orchestration overview plus agent lanes');
2662
+ 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');
2663
+ 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');
2664
+ 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');
2988
2665
  if (routeReasoning(routePrompt('$Research frontier idea'), '$Research frontier idea').effort !== 'xhigh') throw new Error('selftest failed: research reasoning not xhigh');
2989
2666
  if (routeReasoning(routePrompt('$From-Chat-IMG 채팅 이미지 작업'), '$From-Chat-IMG 채팅 이미지 작업').effort !== 'xhigh') throw new Error('selftest failed: From-Chat-IMG reasoning not xhigh');
2990
2667
  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');
@@ -3068,6 +2745,63 @@ async function selftest() {
3068
2745
  const teamLaneCli = await runProcess(process.execPath, [hookBin, 'team', 'lane', teamId, '--agent', 'analysis_scout_1', '--lines', '4'], { cwd: tmp, env: { SKS_DISABLE_UPDATE_CHECK: '1' }, timeoutMs: 15000, maxOutputBytes: 64 * 1024 });
3069
2746
  if (teamLaneCli.code !== 0 || !String(teamLaneCli.stdout || '').includes('SKS Team Agent Lane') || !String(teamLaneCli.stdout || '').includes('analysis_scout_1')) throw new Error('selftest failed: sks team lane CLI did not render an agent lane');
3070
2747
  await writeTextAtomic(path.join(teamDir, 'team-analysis.md'), '- claim: analysis scout mapped route registry | source: src/core/routes.mjs | risk: high | confidence: supported\n');
2748
+ const buttonUxSchema = buildQuestionSchema('$Team 버튼 UX 수정');
2749
+ const buttonUxSlotIds = buttonUxSchema.slots.map((s) => s.id);
2750
+ if (buttonUxSlotIds.includes('UI_STATE_BEHAVIOR') || buttonUxSlotIds.includes('VISUAL_REGRESSION_REQUIRED')) throw new Error('selftest failed: predictable UI defaults should be inferred, not asked');
2751
+ if (buttonUxSchema.inferred_answers.UI_STATE_BEHAVIOR !== 'infer_from_task_context_and_existing_design_system; preserve existing loading/error/empty/retry behavior unless explicitly requested; add only standard states required by the touched surface') throw new Error('selftest failed: UI state default inference missing');
2752
+ if (buttonUxSchema.inferred_answers.VISUAL_REGRESSION_REQUIRED !== 'yes_if_available') throw new Error('selftest failed: visual regression default inference missing');
2753
+ const pptRoute = routePrompt('$PPT 투자자용 피치덱 만들어줘');
2754
+ if (pptRoute?.id !== 'PPT') throw new Error('selftest failed: $PPT did not route to presentation pipeline');
2755
+ const pptSchema = buildQuestionSchema('$PPT 투자자용 피치덱 만들어줘');
2756
+ const pptSlotIds = pptSchema.slots.map((s) => s.id);
2757
+ for (const id of ['PRESENTATION_DELIVERY_CONTEXT', 'PRESENTATION_AUDIENCE_PROFILE', 'PRESENTATION_STP_STRATEGY', 'PRESENTATION_PAINPOINT_SOLUTION_MAP', 'PRESENTATION_DECISION_CONTEXT']) {
2758
+ if (!pptSlotIds.includes(id)) throw new Error(`selftest failed: PPT schema missing ${id}`);
2759
+ }
2760
+ const pptSkillText = await safeReadText(path.join(tmp, '.agents', 'skills', 'ppt', 'SKILL.md'));
2761
+ if (!pptSkillText.includes('STP') || !pptSkillText.includes('target audience profile') || !pptSkillText.includes('decision context') || !pptSkillText.includes('3+ pain-point to solution mappings')) throw new Error('selftest failed: generated PPT skill missing STP/audience/pain-point guidance');
2762
+ if (!pptSkillText.includes('simple, restrained, and information-first') || !pptSkillText.includes('over-designed decoration') || !pptSkillText.includes(CODEX_APP_IMAGE_GENERATION_DOC_URL)) throw new Error('selftest failed: generated PPT skill missing restrained design/imagegen guidance');
2763
+ if (!pptSkillText.includes('source-html/') || !pptSkillText.includes('temporary build files') || !pptSkillText.includes('ppt-parallel-report.json')) throw new Error('selftest failed: generated PPT skill missing source preservation/temp cleanup/parallel guidance');
2764
+ if (routeRequiresSubagents(pptRoute, '$PPT 투자자용 피치덱 만들어줘')) throw new Error('selftest failed: PPT route should not require subagents by default');
2765
+ if (!reflectionRequiredForRoute(pptRoute)) throw new Error('selftest failed: PPT route should require reflection');
2766
+ const pptMission = await createMission(tmp, { mode: 'ppt', prompt: '$PPT 투자자용 피치덱 만들어줘' });
2767
+ await writeQuestions(pptMission.dir, pptSchema);
2768
+ const pptAnswers = {
2769
+ PRESENTATION_DELIVERY_CONTEXT: '대형 화면 16:9 발표, 한국어, 10분',
2770
+ PRESENTATION_AUDIENCE_PROFILE: '투자자, 평균 40대, VC/전략투자 직무, SaaS 이해도 높음, 의사결정권 있음',
2771
+ PRESENTATION_STP_STRATEGY: 'Segmentation: 초기 B2B SaaS 투자자; Targeting: 운영 효율 SaaS에 관심 있는 VC; Positioning: 작은 도입으로 반복 운영비를 줄이는 팀',
2772
+ PRESENTATION_PAINPOINT_SOLUTION_MAP: ['반복 리서치 비용 -> 자동화된 근거 수집 -> 비용 절감 아하', '검토 자료 품질 편차 -> 표준화된 스토리보드 -> 신뢰 아하', '도입 리스크 -> 작은 파일럿 -> 낮은 리스크 아하'],
2773
+ PRESENTATION_DECISION_CONTEXT: '파일럿 투자 승인이 목표이며, 시장 차별성과 실행 리스크가 주요 반대논리'
2774
+ };
2775
+ await writeJsonAtomic(path.join(pptMission.dir, 'answers.json'), pptAnswers);
2776
+ const pptSeal = await sealContract(pptMission.dir, pptMission.mission);
2777
+ if (!pptSeal.ok) throw new Error('selftest failed: PPT answers rejected');
2778
+ await materializeAfterPipelineAnswer(tmp, pptMission.id, pptMission.dir, pptMission.mission, pptRoute, { route: 'PPT', command: '$PPT', mode: 'PPT', task: pptMission.mission.prompt, context7_required: false }, pptSeal.contract);
2779
+ const pptAudienceStrategy = await readJson(path.join(pptMission.dir, PPT_AUDIENCE_STRATEGY_ARTIFACT));
2780
+ if (!pptAudienceStrategy?.source_answers?.PRESENTATION_STP_STRATEGY || pptAudienceStrategy.painpoint_solution_map.length !== 3) throw new Error('selftest failed: PPT audience strategy was not materialized from sealed answers');
2781
+ const pptGate = await readJson(path.join(pptMission.dir, PPT_GATE_ARTIFACT));
2782
+ if (pptGate.passed !== false || pptGate.audience_strategy_sealed !== true || pptGate.painpoint_count !== 3) throw new Error('selftest failed: PPT gate did not initialize with sealed audience strategy');
2783
+ const pptBuildResult = await runProcess(process.execPath, [hookBin, 'ppt', 'build', pptMission.id, '--json'], { cwd: tmp, env: { SKS_DISABLE_UPDATE_CHECK: '1' }, timeoutMs: 15000, maxOutputBytes: 128 * 1024 });
2784
+ if (pptBuildResult.code !== 0) throw new Error(`selftest failed: sks ppt build failed: ${pptBuildResult.stderr || pptBuildResult.stdout}`);
2785
+ const pptBuild = JSON.parse(pptBuildResult.stdout);
2786
+ if (!pptBuild.ok || !pptBuild.gate?.passed || !pptBuild.gate?.parallel_build_recorded || !pptBuild.gate?.html_artifact_created || !pptBuild.gate?.source_html_preserved || !pptBuild.gate?.pdf_exported_or_explicitly_deferred || !pptBuild.gate?.render_qa_recorded || !pptBuild.gate?.temp_cleanup_recorded) throw new Error('selftest failed: PPT build did not pass artifact gate');
2787
+ if (!PPT_HTML_ARTIFACT.startsWith(`${PPT_SOURCE_HTML_DIR}/`)) throw new Error('selftest failed: PPT HTML source must be stored in source-html folder');
2788
+ const pptHtml = await safeReadText(path.join(pptMission.dir, PPT_HTML_ARTIFACT));
2789
+ if (!pptHtml.includes('<html') || pptHtml.includes('gradient')) throw new Error('selftest failed: PPT HTML artifact missing or over-designed');
2790
+ const audienceScript = pptHtml.match(/id="ppt-audience-strategy">([^<]+)<\/script>/);
2791
+ if (!audienceScript) throw new Error('selftest failed: PPT HTML missing audience strategy script data');
2792
+ JSON.parse(audienceScript[1]);
2793
+ const pptPdfBytes = await fsp.readFile(path.join(pptMission.dir, PPT_PDF_ARTIFACT));
2794
+ if (pptPdfBytes.subarray(0, 5).toString('utf8') !== '%PDF-') throw new Error('selftest failed: PPT PDF artifact does not have a PDF header');
2795
+ const pptRenderReport = await readJson(path.join(pptMission.dir, PPT_RENDER_REPORT_ARTIFACT));
2796
+ if (!pptRenderReport.passed || !pptRenderReport.design_policy_checks.every((check) => check.passed)) throw new Error('selftest failed: PPT render report did not pass design policy checks');
2797
+ const pptParallelReport = await readJson(path.join(pptMission.dir, PPT_PARALLEL_REPORT_ARTIFACT));
2798
+ if (!pptParallelReport.passed || pptParallelReport.parallel_group_count < 2 || !pptParallelReport.parallel_groups.some((group) => group.id === 'render_targets' && group.executed_in_parallel)) throw new Error('selftest failed: PPT parallel report did not record parallel build groups');
2799
+ const pptCleanupReport = await readJson(path.join(pptMission.dir, PPT_CLEANUP_REPORT_ARTIFACT));
2800
+ if (!pptCleanupReport.source_html_preserved || !pptCleanupReport.temp_cleanup_completed || pptCleanupReport.source_html_path !== PPT_HTML_ARTIFACT) throw new Error('selftest failed: PPT cleanup report did not preserve source HTML');
2801
+ if (await exists(path.join(pptMission.dir, PPT_TEMP_DIR))) throw new Error('selftest failed: PPT temp directory was not cleaned');
2802
+ if (await exists(path.join(pptMission.dir, 'artifact.html'))) throw new Error('selftest failed: legacy root PPT HTML should not remain after source-html preservation');
2803
+ const pptStatusResult = await runProcess(process.execPath, [hookBin, 'ppt', 'status', pptMission.id, '--json'], { cwd: tmp, env: { SKS_DISABLE_UPDATE_CHECK: '1' }, timeoutMs: 15000, maxOutputBytes: 128 * 1024 });
2804
+ if (pptStatusResult.code !== 0 || !JSON.parse(pptStatusResult.stdout).ok) throw new Error('selftest failed: sks ppt status did not report the built gate');
3071
2805
  const installUxSchema = buildQuestionSchema('SKS first install/bootstrap UX and Context7 MCP setup improvement');
3072
2806
  const installUxSlotIds = installUxSchema.slots.map((s) => s.id);
3073
2807
  if (installUxSchema.domain_hints.includes('uiux') || installUxSlotIds.includes('VISUAL_REGRESSION_REQUIRED')) throw new Error('selftest failed: CLI UX install prompt should not ask visual UI questions');
@@ -3091,6 +2825,8 @@ async function selftest() {
3091
2825
  if (classifyCommand('supabase db reset').level !== 'destructive') throw new Error('selftest failed: supabase db reset not detected');
3092
2826
  const dbDecision = await checkDbOperation(tmp, { mission_id: id }, { tool_name: 'mcp__supabase__execute_sql', sql: 'drop table users;' }, { duringNoQuestion: true });
3093
2827
  if (dbDecision.action !== 'block') throw new Error('selftest failed: destructive MCP SQL allowed');
2828
+ const computerUseDecision = await checkDbOperation(tmp, { mission_id: id }, { tool_name: 'mcp__computer_use__open_app', bundle_id: 'com.microsoft.edgemac', action: 'open_app' }, { duringNoQuestion: true });
2829
+ if (computerUseDecision.action !== 'allow') throw new Error('selftest failed: Computer Use MCP was blocked by DB safety gate');
3094
2830
  const madMission = await createMission(tmp, { mode: 'mad-sks', prompt: '$MAD-SKS selftest scoped DB override' });
3095
2831
  await writeJsonAtomic(path.join(madMission.dir, 'team-gate.json'), { schema_version: 1, passed: false, team_roster_confirmed: true });
3096
2832
  const madState = { mission_id: madMission.id, mode: 'TEAM', route_command: '$Team', stop_gate: 'team-gate.json', mad_sks_active: true, mad_sks_modifier: true, mad_sks_gate_file: 'team-gate.json' };
@@ -3113,7 +2849,7 @@ async function selftest() {
3113
2849
  if (!evalReport.candidate.wiki?.valid) throw new Error('selftest failed: wiki coordinate index invalid in eval');
3114
2850
  if (evalReport.candidate.wiki?.voxel_schema !== 'sks.wiki-voxel.v1' || evalReport.candidate.wiki?.voxel_rows < 1) throw new Error('selftest failed: eval did not include voxel overlay metrics');
3115
2851
  const harnessReport = harnessGrowthReport({});
3116
- if (!harnessReport.forgetting.fixture.passed || !harnessReport.warp.views.includes('Harness Experiments View') || !harnessReport.reliability.tool_error_taxonomy.includes('Unknown')) throw new Error('selftest failed: harness growth fixture incomplete');
2852
+ if (!harnessReport.forgetting.fixture.passed || !harnessReport.tmux.views.includes('Harness Experiments View') || !harnessReport.reliability.tool_error_taxonomy.includes('Unknown')) throw new Error('selftest failed: harness growth fixture incomplete');
3117
2853
  const proofField = await proofFieldFixture();
3118
2854
  if (!proofField.validation.ok || !validateProofFieldReport(proofField.report).ok) throw new Error('selftest failed: proof field report invalid');
3119
2855
  if (!proofField.checks.route_cone_selected || !proofField.checks.cli_cone_selected || !proofField.checks.catastrophic_guard_present || !proofField.checks.negative_release_work_recorded || !proofField.checks.outcome_rubric_present || !proofField.checks.adversarial_lenses_present || !proofField.checks.simplicity_score_usable || !proofField.checks.execution_fast_lane_selected) throw new Error('selftest failed: proof field fixture checks incomplete');