sneakoscope 0.7.42 → 0.7.44

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -42,7 +42,7 @@ sks selftest --mock
42
42
 
43
43
  | Area | What it does |
44
44
  | --- | --- |
45
- | CLI runtime | Bare `sks` opens or reuses the default tmux Codex CLI workspace. `sks tmux open` remains the explicit form for session/workspace flags, and `sks --mad` launches the high-reasoning auto-review profile. |
45
+ | CLI runtime | Bare `sks` opens or reuses the default tmux Codex CLI workspace. `sks tmux open` remains the explicit form for session/workspace flags, and `sks --mad` launches the explicit full-access high-reasoning profile. |
46
46
  | Codex App commands | Installs generated skills so `$Team`, `$From-Chat-IMG`, `$DFix`, `$QA-LOOP`, `$PPT`, `$Goal`, `$DB`, `$Wiki`, `$Help`, and related routes are visible in prompt workflows. |
47
47
  | OpenClaw agents | Generates an OpenClaw skill package so OpenClaw agents can attach `sneakoscope-codex`, enable the `shell` tool, and discover/use SKS commands from the target repo root. |
48
48
  | Pipeline plans | Writes `pipeline-plan.json` for stateful routes so the runtime lane, kept stages, skipped stages, verification commands, and no-unrequested-fallback invariant are visible with `sks pipeline plan`. |
@@ -166,7 +166,7 @@ sks tmux check
166
166
  sks tmux status --once
167
167
  ```
168
168
 
169
- Bare `sks` creates or reuses the default named tmux session for Codex CLI and attaches to it in an interactive terminal. By default it launches Codex in the SKS fast-high runtime (`--model gpt-5.5 -c model_reasoning_effort="high"`) with a short animated SKS ASCII intro. Override with `SKS_CODEX_MODEL`, `SKS_CODEX_REASONING`, disable the default model profile with `SKS_CODEX_FAST_HIGH=0`, or disable the intro animation with `SKS_TMUX_LOGO_ANIMATION=0`. Use `sks tmux open` when you need explicit `--workspace` / `--session` flags, `sks tmux check` for readiness without launching, and `sks help` for CLI help. Use `--no-attach` or `SKS_TMUX_NO_AUTO_ATTACH=1` when you only want SKS to create/reuse the session and print the manual attach command.
169
+ Bare `sks` creates or reuses the default named tmux session for Codex CLI and attaches to it in an interactive terminal. By default it launches Codex in the SKS fast-high runtime (`--model gpt-5.5 -c model_reasoning_effort="high"`) with a static SKS 3D ASCII intro inside tmux; the animated intro is reserved for non-tmux unauthenticated Codex launches and can be disabled with `SKS_TMUX_LOGO_ANIMATION=0`. Override the runtime with `SKS_CODEX_MODEL`, `SKS_CODEX_REASONING`, or disable the default model profile with `SKS_CODEX_FAST_HIGH=0`. Use `sks tmux open` when you need explicit `--workspace` / `--session` flags, `sks tmux check` for readiness without launching, and `sks help` for CLI help. Use `--no-attach` or `SKS_TMUX_NO_AUTO_ATTACH=1` when you only want SKS to create/reuse the session and print the manual attach command.
170
170
 
171
171
  Before opening tmux, SKS checks the installed Codex CLI against npm `@openai/codex@latest`. If a newer version exists, it asks `Y/n`; answering `y` updates automatically with `npm i -g @openai/codex@latest` and then opens tmux with the updated Codex CLI.
172
172
 
@@ -204,7 +204,7 @@ sks --mad
204
204
  sks --mad --yes
205
205
  ```
206
206
 
207
- This creates/uses the `sks-mad-high` Codex profile for a one-shot full-access, high-reasoning tmux session with `approval_policy = "on-request"` and `approvals_reviewer = "auto_review"`, then attaches to the session in an interactive terminal. It is scoped to that explicit command and does not change normal SKS/DB safety defaults. Repeat launches reuse the same named SKS MAD tmux session.
207
+ This creates/uses the `sks-mad-high` Codex profile for a one-shot full-access, high-reasoning tmux session with `sandbox_mode = "danger-full-access"` and `approval_policy = "never"`, then launches Codex with `--sandbox danger-full-access --ask-for-approval never` and attaches to the session in an interactive terminal. It is scoped to that explicit command and does not change normal SKS/DB safety defaults. Repeat launches reuse the same named SKS MAD tmux session.
208
208
 
209
209
  MAD does not disable the pipeline contract: stages, executors, reviewers, and auto-review policy still must not invent unrequested fallback implementation code. If the requested path cannot be implemented, SKS should block with evidence rather than add substitute behavior.
210
210
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "sneakoscope",
3
3
  "displayName": "ㅅㅋㅅ",
4
- "version": "0.7.42",
4
+ "version": "0.7.44",
5
5
  "description": "Sneakoscope Codex: database-safe Codex CLI/App harness with Team, Goal, AutoResearch, TriWiki, and Honest Mode.",
6
6
  "type": "module",
7
7
  "homepage": "https://github.com/mandarange/Sneakoscope-Codex#readme",
package/src/cli/main.mjs CHANGED
@@ -1005,11 +1005,11 @@ async function madHighCommand(args = []) {
1005
1005
  return;
1006
1006
  }
1007
1007
  const profile = await enableMadHighProfile();
1008
- console.log(`SKS MAD auto-review profile ready: ${madHighProfileName()}`);
1009
- console.log('Scope: explicit tmux launch only; full access uses Codex auto_review approvals when approval prompts are raised.');
1008
+ console.log(`SKS MAD full-access profile ready: ${madHighProfileName()}`);
1009
+ console.log('Scope: explicit tmux launch only; Codex opens with danger-full-access sandbox and approval_policy=never.');
1010
1010
  const workspace = readOption(cleanArgs, '--workspace', readOption(cleanArgs, '--session', `sks-mad-${defaultTmuxSessionName(process.cwd())}`));
1011
1011
  return launchTmuxUi([...cleanArgs, '--workspace', workspace], {
1012
- codexArgs: ['--profile', profile.profile_name],
1012
+ codexArgs: profile.launch_args,
1013
1013
  autoInstallTmux: !flag(args, '--no-auto-install-tmux'),
1014
1014
  conciseBlockers: true
1015
1015
  });
@@ -1927,6 +1927,7 @@ async function selftest() {
1927
1927
  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');
1928
1928
  const postinstallSetupTmp = tmpdir();
1929
1929
  await writeJsonAtomic(path.join(postinstallSetupTmp, 'package.json'), { name: 'postinstall-setup-smoke', version: '0.0.0' });
1930
+ await ensureDir(path.join(postinstallSetupTmp, '.git'));
1930
1931
  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 });
1931
1932
  if (postinstallSetup.code !== 0) throw new Error(`selftest failed: postinstall setup exited ${postinstallSetup.code}: ${postinstallSetup.stderr}`);
1932
1933
  if (await exists(path.join(postinstallSetupTmp, '.agents', 'skills', 'agent-team', 'SKILL.md'))) throw new Error('selftest failed: postinstall installed deprecated agent-team fallback skill');
@@ -1964,6 +1965,7 @@ async function selftest() {
1964
1965
  if (!String(postinstallNoMarker.stdout || '').includes('no project marker found; auto-running global SKS runtime bootstrap')) throw new Error('selftest failed: no-marker postinstall did not report global runtime bootstrap');
1965
1966
  if (!(await exists(path.join(postinstallNoMarkerGlobalRoot, '.sneakoscope', 'manifest.json')))) throw new Error('selftest failed: no-marker postinstall did not bootstrap global runtime root');
1966
1967
  if (await exists(path.join(postinstallNoMarkerCwd, '.sneakoscope'))) throw new Error('selftest failed: no-marker postinstall polluted install cwd');
1968
+ if (await exists(path.join(postinstallNoMarkerGlobalRoot, '.gitignore'))) throw new Error('selftest failed: global runtime bootstrap without project git wrote shared .gitignore');
1967
1969
  const bootstrapJsonTmp = tmpdir();
1968
1970
  await writeJsonAtomic(path.join(bootstrapJsonTmp, 'package.json'), { name: 'bootstrap-json-smoke', version: '0.0.0' });
1969
1971
  const bootstrapJson = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'bootstrap', '--json'], { cwd: bootstrapJsonTmp, env: { HOME: path.join(bootstrapJsonTmp, 'home'), SKS_SKIP_POSTINSTALL_GLOBAL_SKILLS: '1', SKS_SKIP_CLI_TOOLS: '1' }, timeoutMs: 30000, maxOutputBytes: 256 * 1024 });
@@ -1987,9 +1989,9 @@ async function selftest() {
1987
1989
  const madProfilePath = path.join(tmp, 'mad-codex-config.toml');
1988
1990
  const madProfile = await enableMadHighProfile({ configPath: madProfilePath });
1989
1991
  const madProfileText = await safeReadText(madProfilePath);
1990
- 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');
1992
+ if (madProfile.profile_name !== 'sks-mad-high' || !madProfileText.includes('sandbox_mode = "danger-full-access"') || !madProfileText.includes('approval_policy = "never"') || !madProfileText.includes('approvals_reviewer = "auto_review"') || !madProfile.launch_args.includes('--sandbox') || !madProfile.launch_args.includes('danger-full-access') || !madProfile.launch_args.includes('--ask-for-approval') || !madProfile.launch_args.includes('never') || !madProfileText.includes('model_reasoning_effort = "high"') || !madProfileText.includes('unrequested fallback implementation code')) throw new Error('selftest failed: MAD high profile is not Codex full-access high with fallback-code guard');
1991
1993
  if (!isMadHighLaunch(['--mad', '--high']) || isMadHighLaunch(['db', '--mad'])) throw new Error('selftest failed: MAD high launch flag parsing is not top-level only');
1992
- const workspacePlan = { session: 'sks-mad-selftest', root: tmp, codexArgs: ['--profile', 'sks-mad-high'] };
1994
+ const workspacePlan = { session: 'sks-mad-selftest', root: tmp, codexArgs: madProfile.launch_args };
1993
1995
  const tmuxSyntax = runTmuxLaunchPlanSyntaxCheck(workspacePlan);
1994
1996
  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');
1995
1997
  const tmuxOpenArgs = buildTmuxOpenArgs(workspacePlan);
@@ -2405,7 +2407,8 @@ async function selftest() {
2405
2407
  if (!hookKoreanSksContext.includes('Route: $Team')) throw new Error('selftest failed: Korean implementation prompt did not promote to Team route');
2406
2408
  if (hookKoreanSksContext.includes('SKS answer-only pipeline active')) throw new Error('selftest failed: Korean implementation prompt still used answer-only pipeline');
2407
2409
  const hookKoreanSksState = await readJson(stateFile(hookKoreanSksTmp), {});
2408
- if (hookKoreanSksState.phase !== 'TEAM_CLARIFICATION_CONTRACT_SEALED' || hookKoreanSksState.implementation_allowed !== true || !hookKoreanSksState.ambiguity_gate_passed) throw new Error('selftest failed: Korean Team auto-seal');
2410
+ if (hookKoreanSksState.phase !== 'TEAM_PARALLEL_ANALYSIS_SCOUTING' || hookKoreanSksState.implementation_allowed !== true || !hookKoreanSksState.ambiguity_gate_passed || !hookKoreanSksState.team_plan_ready) throw new Error('selftest failed: Korean Team auto-seal did not materialize Team');
2411
+ if (!(await exists(path.join(missionDir(hookKoreanSksTmp, hookKoreanSksState.mission_id), 'team-plan.json')))) throw new Error('selftest failed: Korean Team auto-seal did not write team-plan.json');
2409
2412
  const hookPaymentTeamTmp = tmpdir();
2410
2413
  await initProject(hookPaymentTeamTmp, {});
2411
2414
  const hookPaymentTeamPayload = JSON.stringify({ cwd: hookPaymentTeamTmp, prompt: '$Team 결제 재시도 정책과 로그인 세션 만료 버그 수정 executor:2 reviewer:1 user:1' });
@@ -2416,9 +2419,10 @@ async function selftest() {
2416
2419
  if (!hookPaymentTeamContext.includes('Ambiguity gate auto-sealed')) throw new Error('selftest failed: predictable payment/auth Team prompt did not auto-seal');
2417
2420
  if (hookPaymentTeamContext.includes('PAYMENT_RETRY_POLICY') || hookPaymentTeamContext.includes('AUTH_PROTOCOL_CHANGE_ALLOWED')) throw new Error('selftest failed: predictable payment/auth policy defaults were asked instead of inferred');
2418
2421
  const hookPaymentTeamState = await readJson(stateFile(hookPaymentTeamTmp), {});
2419
- if (hookPaymentTeamState.phase !== 'TEAM_CLARIFICATION_CONTRACT_SEALED' || hookPaymentTeamState.implementation_allowed !== true || !hookPaymentTeamState.ambiguity_gate_passed) throw new Error('selftest failed: predictable payment/auth Team state was not executable after auto-seal');
2422
+ if (hookPaymentTeamState.phase !== 'TEAM_PARALLEL_ANALYSIS_SCOUTING' || hookPaymentTeamState.implementation_allowed !== true || !hookPaymentTeamState.ambiguity_gate_passed || !hookPaymentTeamState.team_plan_ready) throw new Error('selftest failed: predictable payment/auth Team did not materialize after auto-seal');
2420
2423
  const hookPaymentTeamSchema = await readJson(path.join(missionDir(hookPaymentTeamTmp, hookPaymentTeamState.mission_id), 'required-answers.schema.json'));
2421
2424
  if (hookPaymentTeamSchema.slots.length !== 0 || hookPaymentTeamSchema.inferred_answers?.PAYMENT_RETRY_POLICY === undefined || hookPaymentTeamSchema.inferred_answers?.AUTH_SESSION_EXPIRED_BEHAVIOR === undefined) throw new Error('selftest failed: predictable payment/auth defaults were not recorded as inferred answers');
2425
+ if (!(await exists(path.join(missionDir(hookPaymentTeamTmp, hookPaymentTeamState.mission_id), 'team-plan.json')))) throw new Error('selftest failed: predictable payment/auth Team auto-seal did not write team-plan.json');
2422
2426
  const hookTeamTmp = tmpdir();
2423
2427
  await initProject(hookTeamTmp, {});
2424
2428
  const hookTeamPayload = JSON.stringify({ cwd: hookTeamTmp, prompt: '$Team 발표자료 만들어줘 executor:2 reviewer:1 user:1' });
@@ -2626,6 +2630,7 @@ async function selftest() {
2626
2630
  if (!codexConfigText.includes('multi_agent = true')) throw new Error('selftest failed: multi_agent not enabled');
2627
2631
  if (!hasContext7ConfigText(codexConfigText)) throw new Error('selftest failed: Context7 MCP not configured');
2628
2632
  if (!codexConfigText.includes('[profiles.sks-task-low]') || !codexConfigText.includes('[profiles.sks-task-medium]') || !codexConfigText.includes('[profiles.sks-logic-high]') || !codexConfigText.includes('[profiles.sks-fast-high]') || !codexConfigText.includes('[profiles.sks-research-xhigh]') || !codexConfigText.includes('[profiles.sks-mad-high]')) throw new Error('selftest failed: GPT-5.5 reasoning profiles not configured');
2633
+ if (!/\[profiles\.sks-mad-high\][\s\S]*?approval_policy = "never"[\s\S]*?sandbox_mode = "danger-full-access"/.test(codexConfigText)) throw new Error('selftest failed: generated sks-mad-high profile is not full access');
2629
2634
  if (!codexConfigText.includes('[agents.analysis_scout]')) throw new Error('selftest failed: analysis_scout agent not configured');
2630
2635
  if (!codexConfigText.includes('[agents.team_consensus]')) throw new Error('selftest failed: team_consensus agent not configured');
2631
2636
  const preservedConfigTmp = tmpdir();
@@ -2633,7 +2638,7 @@ async function selftest() {
2633
2638
  await writeTextAtomic(path.join(preservedConfigTmp, '.codex', 'config.toml'), '[features]\nfast_mode_ui = true\n\n[user.fast_mode]\nvisible = true\n');
2634
2639
  await initProject(preservedConfigTmp, {});
2635
2640
  const preservedConfig = await safeReadText(path.join(preservedConfigTmp, '.codex', 'config.toml'));
2636
- if (!preservedConfig.includes('fast_mode_ui = true') || !preservedConfig.includes('[user.fast_mode]') || !preservedConfig.includes('visible = true')) throw new Error('selftest failed: Codex config merge dropped user Fast mode settings');
2641
+ if (!preservedConfig.includes('fast_mode_ui = true') || !preservedConfig.includes('[user.fast_mode]') || !preservedConfig.includes('visible = true') || !preservedConfig.includes('enabled = true') || !preservedConfig.includes('default_profile = "sks-fast-high"')) throw new Error('selftest failed: Codex config merge dropped or failed to enable Fast mode settings');
2637
2642
  if (!preservedConfig.includes('codex_hooks = true') || !preservedConfig.includes('[profiles.sks-fast-high]')) throw new Error('selftest failed: Codex config merge did not add SKS managed settings');
2638
2643
  const autoReviewHome = path.join(tmp, 'auto-review-home');
2639
2644
  const autoReviewEnv = { HOME: autoReviewHome };
@@ -2988,6 +2993,10 @@ async function selftest() {
2988
2993
  if (buttonUxSlotIds.length) throw new Error(`selftest failed: clear small UI work should auto-seal, got ${buttonUxSlotIds.join(',')}`);
2989
2994
  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');
2990
2995
  if (buttonUxSchema.inferred_answers.VISUAL_REGRESSION_REQUIRED !== 'yes_if_available') throw new Error('selftest failed: visual regression default inference missing');
2996
+ const predictableAuthCliSchema = buildQuestionSchema('회전 아스키 아트는 제일 처음 인증 안됐을때만 codex cli처럼 애니메이션으로 보이게 하고 tmux에서는 정적 3d 아스키 아트로 보여줘');
2997
+ const predictableAuthCliSlotIds = predictableAuthCliSchema.slots.map((s) => s.id);
2998
+ if (predictableAuthCliSlotIds.length) throw new Error(`selftest failed: clear auth-worded CLI rendering work should auto-seal, got ${predictableAuthCliSlotIds.join(',')}`);
2999
+ if (!predictableAuthCliSchema.inferred_answers.RISK_BOUNDARY?.includes('no destructive commands or live data writes')) throw new Error('selftest failed: predictable auth-worded CLI work did not infer conservative risk boundary');
2991
3000
  const vagueSchema = buildQuestionSchema('뭔가 개선해줘');
2992
3001
  const vagueSlotIds = vagueSchema.slots.map((s) => s.id);
2993
3002
  if (!vagueSlotIds.includes('INTENT_TARGET') || vagueSlotIds.includes('GOAL_PRECISE') || vagueSlotIds.includes('ACCEPTANCE_CRITERIA')) throw new Error(`selftest failed: vague work should ask dynamic intent questions only, got ${vagueSlotIds.join(',')}`);
@@ -3034,6 +3043,7 @@ async function selftest() {
3034
3043
  if (!pptHtml.includes('<html') || pptHtml.includes('gradient')) throw new Error('selftest failed: PPT HTML artifact missing or over-designed');
3035
3044
  const pptStyleTokens = await readJson(path.join(pptMission.dir, 'ppt-style-tokens.json'));
3036
3045
  if (pptStyleTokens.design_policy?.design_ssot?.authority !== DESIGN_SYSTEM_SSOT.authority_file || !pptStyleTokens.design_policy?.source_inputs?.some((entry) => entry.url === AWESOME_DESIGN_MD_REFERENCE.url && entry.role === 'source_input_for_ssot') || !pptStyleTokens.design_policy?.anti_generic_ai_style) throw new Error('selftest failed: PPT style tokens missing fused design SSOT/source-input anti-generic policy');
3046
+ if (!pptStyleTokens.design_policy?.design_reference_selection?.primary?.id?.startsWith('awesome-design-md:') || !pptStyleTokens.design_policy?.design_reference_selection?.selected_sources?.length || !pptStyleTokens.layout?.composition || !pptStyleTokens.layout?.treatment) throw new Error('selftest failed: PPT style tokens did not select and apply a concrete awesome-design-md reference profile');
3037
3047
  if (JSON.stringify(pptStyleTokens.design_policy?.pipeline_allowlist?.required_skills || []) !== JSON.stringify(PPT_PIPELINE_SKILL_ALLOWLIST) || !pptStyleTokens.design_policy?.pipeline_allowlist?.ignore_installed_out_of_pipeline_skills || !(pptStyleTokens.design_policy?.pipeline_allowlist?.ignored_design_skills_even_if_installed || []).includes('design-artifact-expert') || !/AI-like/.test(pptStyleTokens.design_policy?.pipeline_allowlist?.anti_ai_design_goal || '')) throw new Error('selftest failed: PPT style tokens missing skill/MCP allowlist enforcement');
3038
3048
  const audienceScript = pptHtml.match(/id="ppt-audience-strategy">([^<]+)<\/script>/);
3039
3049
  if (!audienceScript) throw new Error('selftest failed: PPT HTML missing audience strategy script data');
@@ -64,7 +64,7 @@ export async function enableMadHighProfile(opts = {}) {
64
64
  let next = upsertTable(current, `profiles.${MAD_HIGH_PROFILE}`, [
65
65
  `[profiles.${MAD_HIGH_PROFILE}]`,
66
66
  'model = "gpt-5.5"',
67
- 'approval_policy = "on-request"',
67
+ 'approval_policy = "never"',
68
68
  `approvals_reviewer = "${AUTO_REVIEW_REVIEWER}"`,
69
69
  'sandbox_mode = "danger-full-access"',
70
70
  'model_reasoning_effort = "high"'
@@ -75,9 +75,9 @@ export async function enableMadHighProfile(opts = {}) {
75
75
  return {
76
76
  config_path: configPath,
77
77
  profile_name: MAD_HIGH_PROFILE,
78
- launch_args: ['--profile', MAD_HIGH_PROFILE],
78
+ launch_args: ['--profile', MAD_HIGH_PROFILE, '--sandbox', 'danger-full-access', '--ask-for-approval', 'never'],
79
79
  sandbox_mode: 'danger-full-access',
80
- approval_policy: 'on-request',
80
+ approval_policy: 'never',
81
81
  approvals_reviewer: AUTO_REVIEW_REVIEWER,
82
82
  model_reasoning_effort: 'high',
83
83
  scope: 'explicit_launch_only'
package/src/core/fsx.mjs CHANGED
@@ -5,7 +5,7 @@ import os from 'node:os';
5
5
  import crypto from 'node:crypto';
6
6
  import { spawn } from 'node:child_process';
7
7
 
8
- export const PACKAGE_VERSION = '0.7.42';
8
+ export const PACKAGE_VERSION = '0.7.44';
9
9
  export const DEFAULT_PROCESS_TAIL_BYTES = 256 * 1024;
10
10
  export const DEFAULT_PROCESS_TIMEOUT_MS = 30 * 60 * 1000;
11
11
 
package/src/core/init.mjs CHANGED
@@ -113,8 +113,9 @@ export async function initProject(root, opts = {}) {
113
113
  '.sneakoscope/state', '.sneakoscope/missions', '.sneakoscope/db', '.sneakoscope/bus', '.sneakoscope/hproof', '.sneakoscope/db', '.sneakoscope/wiki', '.sneakoscope/skills', '.sneakoscope/memory/q0_raw', '.sneakoscope/memory/q1_evidence', '.sneakoscope/memory/q2_facts', '.sneakoscope/memory/q3_tags', '.sneakoscope/memory/q4_bits', '.sneakoscope/gx/cartridges', '.sneakoscope/model/fingerprints', '.sneakoscope/genome/candidates', '.sneakoscope/trajectories/raw', '.sneakoscope/locks', '.sneakoscope/tmp', '.sneakoscope/arenas', '.sneakoscope/reports', '.codex', '.codex/agents', '.agents/skills'
114
114
  ];
115
115
  for (const d of dirs) await ensureDir(path.join(root, d));
116
+ const sharedIgnoreWanted = !localOnly && await shouldWriteSharedGitIgnore(root, installScope);
116
117
  const localExclude = localOnly ? await ensureLocalOnlyGitExclude(root) : null;
117
- const sharedIgnore = localOnly ? null : await ensureSharedGitIgnore(root);
118
+ const sharedIgnore = sharedIgnoreWanted ? await ensureSharedGitIgnore(root) : null;
118
119
  if (localExclude?.path) created.push(`${path.relative(root, localExclude.path)} local-only excludes`);
119
120
  if (sharedIgnore?.changed) created.push(`${path.relative(root, sharedIgnore.path)} SKS generated files ignore`);
120
121
 
@@ -429,6 +430,10 @@ function mergeManagedCodexConfigToml(existingContent = '') {
429
430
  let next = String(existingContent || '').trimEnd();
430
431
  next = upsertTomlTableKey(next, 'features', 'codex_hooks = true');
431
432
  next = upsertTomlTableKey(next, 'features', 'multi_agent = true');
433
+ next = upsertTomlTableKey(next, 'features', 'fast_mode_ui = true');
434
+ next = upsertTomlTableKey(next, 'user.fast_mode', 'visible = true');
435
+ next = upsertTomlTableKey(next, 'user.fast_mode', 'enabled = true');
436
+ next = upsertTomlTableKey(next, 'user.fast_mode', 'default_profile = "sks-fast-high"');
432
437
  next = upsertTomlTableKey(next, 'agents', 'max_threads = 6');
433
438
  next = upsertTomlTableKey(next, 'agents', 'max_depth = 1');
434
439
  for (const block of managedCodexConfigBlocks()) {
@@ -452,7 +457,7 @@ function managedCodexConfigBlocks() {
452
457
  { table: 'profiles.sks-research-xhigh', text: profileConfigBlock('sks-research-xhigh', 'xhigh') },
453
458
  { table: 'profiles.sks-research', text: profileConfigBlock('sks-research', 'xhigh', { approval: 'never' }) },
454
459
  { table: 'profiles.sks-team', text: profileConfigBlock('sks-team', 'high') },
455
- { table: 'profiles.sks-mad-high', text: profileConfigBlock('sks-mad-high', 'high', { sandbox: 'danger-full-access', approvalsReviewer: 'auto_review' }) },
460
+ { table: 'profiles.sks-mad-high', text: profileConfigBlock('sks-mad-high', 'high', { approval: 'never', sandbox: 'danger-full-access', approvalsReviewer: 'auto_review' }) },
456
461
  {
457
462
  table: 'auto_review',
458
463
  text: '[auto_review]\npolicy = "Deny destructive database operations, credential exfiltration, persistent security weakening, broad file deletion, writes outside the workspace, and unrequested fallback implementation code unless explicitly authorized by the user or sealed decision contract."'
@@ -613,6 +618,13 @@ async function ensureSharedGitIgnore(root) {
613
618
  return { path: ignorePath, patterns, changed: true };
614
619
  }
615
620
 
621
+ async function shouldWriteSharedGitIgnore(root, installScope) {
622
+ if (normalizeInstallScope(installScope) === 'project') return true;
623
+ if (await exists(path.join(root, '.git'))) return true;
624
+ if (await exists(path.join(root, '.gitignore'))) return true;
625
+ return false;
626
+ }
627
+
616
628
  async function ensureLocalOnlyGitExclude(root) {
617
629
  const gitDir = await resolveGitDir(root);
618
630
  if (!gitDir) return { path: null, patterns: [] };
@@ -504,10 +504,14 @@ async function prepareClarificationGate(root, route, task, required, opts = {})
504
504
  if (schema.slots.length === 0) {
505
505
  await writeJsonAtomic(path.join(dir, 'answers.json'), schema.inferred_answers || {});
506
506
  const result = await sealContract(dir, mission);
507
- const plan = await writePipelinePlan(dir, { missionId: id, route, task, required, ambiguity: { required: true, slots: 0, auto_sealed: result.ok, passed: result.ok, contract_hash: result.contract?.sealed_hash || null } });
507
+ const materialized = result.ok && route?.id === 'Team'
508
+ ? await materializeAutoSealedTeam(root, id, dir, route, task, result.contract?.sealed_hash || null)
509
+ : {};
510
+ const effectiveTask = materialized.prompt || task;
511
+ const plan = await writePipelinePlan(dir, { missionId: id, route, task: effectiveTask, required, ambiguity: { required: true, slots: 0, auto_sealed: result.ok, passed: result.ok, contract_hash: result.contract?.sealed_hash || null } });
508
512
  await appendJsonl(path.join(dir, 'events.jsonl'), { ts: nowIso(), type: 'route.clarification.auto_sealed', route: route.id, slots: 0, ok: result.ok });
509
- await setCurrent(root, routeState(id, route, result.ok ? `${route.mode}_CLARIFICATION_CONTRACT_SEALED` : `${route.mode}_CLARIFICATION_AWAITING_ANSWERS`, required, {
510
- prompt: task,
513
+ await setCurrent(root, routeState(id, route, result.ok ? (materialized.phase || `${route.mode}_CLARIFICATION_CONTRACT_SEALED`) : `${route.mode}_CLARIFICATION_AWAITING_ANSWERS`, required, {
514
+ prompt: effectiveTask,
511
515
  questions_allowed: false,
512
516
  implementation_allowed: result.ok,
513
517
  clarification_required: false,
@@ -517,8 +521,10 @@ async function prepareClarificationGate(root, route, task, required, opts = {})
517
521
  pipeline_plan_ready: validatePipelinePlan(plan).ok,
518
522
  pipeline_plan_path: PIPELINE_PLAN_ARTIFACT,
519
523
  original_stop_gate: route.stopGate,
520
- stop_gate: route.stopGate
524
+ stop_gate: route.stopGate,
525
+ ...(materialized.state || {})
521
526
  }));
527
+ const materializedLine = materialized.phase ? `\nTeam runtime artifacts were materialized immediately; state advanced to ${materialized.phase}.` : '';
522
528
  return {
523
529
  route,
524
530
  additionalContext: `${promptPipelineContext(task, route)}
@@ -528,6 +534,7 @@ Mission: ${id}
528
534
  Decision contract: .sneakoscope/missions/${id}/decision-contract.json
529
535
  Resolved answers: .sneakoscope/missions/${id}/resolved-answers.json
530
536
  Pipeline plan: .sneakoscope/missions/${id}/${PIPELINE_PLAN_ARTIFACT}
537
+ ${materializedLine}
531
538
  Next atomic action: continue the original route lifecycle with the sealed decision-contract.json.`
532
539
  };
533
540
  }
@@ -581,6 +588,91 @@ function applyMadSksAuthorizationToSchema(schema = {}) {
581
588
  return schema;
582
589
  }
583
590
 
591
+ async function materializeAutoSealedTeam(root, id, dir, route, task, contractHash = null) {
592
+ const spec = parseTeamSpecText(task);
593
+ const cleanTask = spec.prompt || task;
594
+ const fromChatImgRequired = hasFromChatImgSignal(cleanTask);
595
+ const { agentSessions, roleCounts, roster } = spec;
596
+ const plan = {
597
+ schema_version: 1,
598
+ mission_id: id,
599
+ task: cleanTask,
600
+ agent_session_count: agentSessions,
601
+ default_agent_session_count: 3,
602
+ role_counts: roleCounts,
603
+ session_policy: `Use at most ${agentSessions} subagent sessions at a time; the parent orchestrator is not counted.`,
604
+ bundle_size: roster.bundle_size,
605
+ roster,
606
+ contract_hash: contractHash,
607
+ team_model: {
608
+ phases: ['parallel_analysis_scouts', 'triwiki_stage_refresh', 'debate_team', 'runtime_task_graph', 'development_team', 'review'],
609
+ analysis_team: `Read-only parallel scouting with exactly ${roster.bundle_size} analysis_scout_N agents.`,
610
+ debate_team: `Read-only role debate with exactly ${roster.bundle_size} participants.`,
611
+ development_team: `Fresh parallel development bundle with exactly ${roster.bundle_size} executor_N developers implementing disjoint slices.`
612
+ },
613
+ context_tracking: triwikiContextTracking(),
614
+ team_runtime: teamRuntimePlanMetadata(),
615
+ phases: [
616
+ { id: 'team_roster_confirmation', goal: `Materialize the Team roster from default SKS counts or explicit user counts, write team-roster.json, and surface role counts ${formatRoleCounts(roleCounts)}.`, agents: ['parent_orchestrator'], output: 'team-roster.json' },
617
+ { id: 'parallel_analysis_scouting', goal: `Read TriWiki context, then spawn exactly ${roster.bundle_size} read-only analysis_scout_N agents in parallel. ${fromChatImgRequired ? `From-Chat-IMG active: ${CODEX_COMPUTER_USE_ONLY_POLICY}` : 'From-Chat-IMG inactive: do not assume ordinary images are chat captures.'}`, agents: roster.analysis_team.map((agent) => agent.id), max_parallel_subagents: agentSessions, write_policy: 'read-only' },
618
+ { id: 'triwiki_refresh', goal: `Refresh or pack TriWiki and run ${triwikiContextTracking().validate_command}.`, agents: ['parent_orchestrator'], output: '.sneakoscope/wiki/context-pack.json' },
619
+ { id: 'planning_debate', goal: 'Run read-only planning debate, map constraints and implementation slices, then seal one objective.', agents: roster.debate_team.map((agent) => agent.id) },
620
+ { id: 'runtime_task_graph_compile', goal: `Compile the agreed Team plan into ${TEAM_GRAPH_ARTIFACT}, ${TEAM_RUNTIME_TASKS_ARTIFACT}, ${TEAM_DECOMPOSITION_ARTIFACT}, and ${TEAM_INBOX_DIR}.`, agents: ['parent_orchestrator'], output: [TEAM_GRAPH_ARTIFACT, TEAM_RUNTIME_TASKS_ARTIFACT, TEAM_DECOMPOSITION_ARTIFACT, TEAM_INBOX_DIR] },
621
+ { id: 'parallel_implementation', goal: `Close debate agents, then spawn a fresh ${roster.bundle_size}-person executor development team with non-overlapping write ownership.`, agents: roster.development_team.map((agent) => agent.id) },
622
+ { id: 'review_integration', goal: 'Review, integrate, verify, and record evidence before final.', agents: roster.validation_team.map((agent) => agent.id) },
623
+ { id: 'session_cleanup', goal: `Close or account for Team subagent sessions and write ${TEAM_SESSION_CLEANUP_ARTIFACT}.`, agents: ['parent_orchestrator'] }
624
+ ],
625
+ live_visibility: {
626
+ markdown: 'team-live.md',
627
+ transcript: 'team-transcript.jsonl',
628
+ dashboard: 'team-dashboard.json',
629
+ tmux: 'CLI Team entrypoints open tmux live lanes for the visible Team agent budget when tmux is available.',
630
+ commands: ['sks team status latest', 'sks team log latest', 'sks team tail latest', 'sks team watch latest', 'sks team lane latest --agent <name> --follow']
631
+ },
632
+ required_artifacts: ['team-roster.json', 'team-analysis.md', ...(fromChatImgRequired ? [FROM_CHAT_IMG_COVERAGE_ARTIFACT, FROM_CHAT_IMG_CHECKLIST_ARTIFACT, FROM_CHAT_IMG_TEMP_TRIWIKI_ARTIFACT, FROM_CHAT_IMG_QA_LOOP_ARTIFACT] : []), 'team-consensus.md', ...teamRuntimeRequiredArtifacts(), 'team-review.md', 'team-gate.json', TEAM_SESSION_CLEANUP_ARTIFACT, 'reflection.md', 'reflection-gate.json', 'team-live.md', 'team-transcript.jsonl', 'team-dashboard.json', '.sneakoscope/wiki/context-pack.json', 'context7-evidence.jsonl']
633
+ };
634
+ await writeJsonAtomic(path.join(dir, 'team-plan.json'), plan);
635
+ await writeJsonAtomic(path.join(dir, 'team-roster.json'), { schema_version: 1, mission_id: id, role_counts: roleCounts, agent_sessions: agentSessions, bundle_size: roster.bundle_size, roster, confirmed: true, source: 'auto_sealed_team_spec' });
636
+ const contextTracking = triwikiContextTracking();
637
+ await writeTextAtomic(path.join(dir, 'team-workflow.md'), `# SKS Team Workflow\n\nTask: ${cleanTask}\n\nAgent session budget: ${agentSessions}\nBundle size: ${roster.bundle_size}\nRole counts: ${formatRoleCounts(roleCounts)}\nContext tracking: ${contextTracking.ssot} SSOT, ${contextTracking.default_pack}.\n\nAuto-sealed ambiguity gate: no user question was required. Continue directly with Team scouting, debate, runtime task graph, implementation, review, cleanup, reflection, and Honest Mode.\n`);
638
+ await initTeamLive(id, dir, cleanTask, { agentSessions, roleCounts, roster });
639
+ const runtime = await writeTeamRuntimeArtifacts(dir, plan, { contractHash });
640
+ await writeMemorySweepReport(root, dir, { missionId: id }).catch(() => null);
641
+ await writeSkillForgeReport(dir, { mission_id: id, route: 'team', task_signature: cleanTask }).catch(() => null);
642
+ await writeMistakeMemoryReport(dir, { mission_id: id, route: 'team', task: cleanTask }).catch(() => null);
643
+ await writeCodeStructureReport(root, dir, { missionId: id, exception: 'Team auto-seal records split-review risk; extraction happens only when the mission scope includes the touched file.' }).catch(() => null);
644
+ await writeJsonAtomic(path.join(dir, 'team-gate.json'), {
645
+ passed: false,
646
+ team_roster_confirmed: true,
647
+ analysis_artifact: false,
648
+ triwiki_refreshed: false,
649
+ triwiki_validated: false,
650
+ consensus_artifact: false,
651
+ ...runtime.gate_fields,
652
+ implementation_team_fresh: false,
653
+ review_artifact: false,
654
+ integration_evidence: false,
655
+ session_cleanup: false,
656
+ context7_evidence: false,
657
+ ...(fromChatImgRequired ? { from_chat_img_required: true, from_chat_img_request_coverage: false } : {}),
658
+ contract_hash: contractHash
659
+ });
660
+ await appendJsonl(path.join(dir, 'events.jsonl'), { ts: nowIso(), type: 'team.materialized_after_auto_sealed_ambiguity_gate', route: route.id, bundle_size: roster.bundle_size, agent_sessions: agentSessions });
661
+ return {
662
+ phase: 'TEAM_PARALLEL_ANALYSIS_SCOUTING',
663
+ prompt: cleanTask,
664
+ state: {
665
+ agent_sessions: agentSessions,
666
+ role_counts: roleCounts,
667
+ team_roster_confirmed: true,
668
+ team_plan_ready: true,
669
+ team_graph_ready: runtime.ok,
670
+ team_live_ready: true,
671
+ from_chat_img_required: fromChatImgRequired
672
+ }
673
+ };
674
+ }
675
+
584
676
  async function prepareTeam(root, route, task, required) {
585
677
  const spec = parseTeamSpecText(task);
586
678
  const cleanTask = spec.prompt || task;
package/src/core/ppt.mjs CHANGED
@@ -16,6 +16,99 @@ export const PPT_CLEANUP_REPORT_ARTIFACT = 'ppt-cleanup-report.json';
16
16
  export const PPT_PARALLEL_REPORT_ARTIFACT = 'ppt-parallel-report.json';
17
17
  export const PPT_TEMP_DIR = 'ppt-tmp';
18
18
 
19
+ const PPT_DESIGN_REFERENCE_PROFILES = Object.freeze([
20
+ {
21
+ id: 'awesome-design-md:ibm',
22
+ name: 'IBM Carbon enterprise',
23
+ source_url: 'https://raw.githubusercontent.com/VoltAgent/awesome-design-md/main/design-md/ibm/DESIGN.md',
24
+ source_summary: 'enterprise Carbon-style system: white surfaces, charcoal text, IBM Blue as the single accent, flat square tiles, thin rules, no shadow',
25
+ keywords: ['enterprise', 'b2b', 'investor', 'vc', 'strategy', 'proposal', 'board', 'finance', 'risk', 'compliance', '운영', '투자', '의사결정', '리스크', '전략'],
26
+ tokens: {
27
+ bg: '#ffffff',
28
+ text: '#161616',
29
+ muted: '#525252',
30
+ primary: '#0f62fe',
31
+ accent: '#393939',
32
+ surface: '#f4f4f4',
33
+ rule: '#e0e0e0',
34
+ display_px: 64,
35
+ body_px: 28,
36
+ caption_px: 15,
37
+ line_height: 1.36,
38
+ radius_px: 2,
39
+ treatment: 'flat_thin_rules_no_shadow',
40
+ composition: 'enterprise_evidence_grid',
41
+ mono_label: 'uppercase technical labels, sparse blue accent, source-visible rows'
42
+ },
43
+ applied_rules: [
44
+ 'use white/charcoal enterprise canvas',
45
+ 'reserve IBM Blue for one decision/action accent',
46
+ 'prefer thin rules and square evidence rows over decorative cards',
47
+ 'avoid shadows, gradients, and ornamental surfaces'
48
+ ]
49
+ },
50
+ {
51
+ id: 'awesome-design-md:vercel',
52
+ name: 'Vercel developer infrastructure',
53
+ source_url: 'https://raw.githubusercontent.com/VoltAgent/awesome-design-md/main/design-md/vercel/DESIGN.md',
54
+ source_summary: 'developer-infrastructure minimalism: white canvas, near-black type, shadow-as-border, mono technical labels, functional blue/red/pink workflow accents',
55
+ keywords: ['developer', 'devtools', 'api', 'sdk', 'cloud', 'infra', 'saas', 'technical', 'codex', 'ai', 'agent', '배포', '개발자', '기술', '자동화'],
56
+ tokens: {
57
+ bg: '#ffffff',
58
+ text: '#171717',
59
+ muted: '#4d4d4d',
60
+ primary: '#0072f5',
61
+ accent: '#ff5b4f',
62
+ surface: '#fafafa',
63
+ rule: '#ebebeb',
64
+ display_px: 66,
65
+ body_px: 28,
66
+ caption_px: 14,
67
+ line_height: 1.34,
68
+ radius_px: 8,
69
+ treatment: 'shadow_as_border_minimal_depth',
70
+ composition: 'technical_pipeline_grid',
71
+ mono_label: 'mono labels, workflow accent only when it clarifies sequence'
72
+ },
73
+ applied_rules: [
74
+ 'use near-black text on a white technical canvas',
75
+ 'show structure through shadow-as-border or one-pixel rules',
76
+ 'use mono labels for sources and technical evidence',
77
+ 'keep color functional rather than decorative'
78
+ ]
79
+ },
80
+ {
81
+ id: 'awesome-design-md:linear',
82
+ name: 'Linear precision operations',
83
+ source_url: 'https://github.com/VoltAgent/awesome-design-md',
84
+ source_summary: 'ultra-minimal precise product-management system: restrained neutral surfaces, exact spacing, one controlled purple accent',
85
+ keywords: ['roadmap', 'product', 'ops', 'workflow', 'issue', 'planning', 'productivity', '운영', '워크플로우', '프로덕트', '계획'],
86
+ tokens: {
87
+ bg: '#f7f8fb',
88
+ text: '#101114',
89
+ muted: '#5f6673',
90
+ primary: '#5e6ad2',
91
+ accent: '#26a69a',
92
+ surface: '#ffffff',
93
+ rule: '#dfe3ea',
94
+ display_px: 62,
95
+ body_px: 27,
96
+ caption_px: 14,
97
+ line_height: 1.38,
98
+ radius_px: 6,
99
+ treatment: 'precise_subtle_product_grid',
100
+ composition: 'operational_decision_matrix',
101
+ mono_label: 'compact status labels, dense but quiet operations layout'
102
+ },
103
+ applied_rules: [
104
+ 'use a quiet operational canvas with dense hierarchy',
105
+ 'keep the purple accent sparse and semantic',
106
+ 'make comparison rows easy to scan',
107
+ 'avoid marketing-style hero composition'
108
+ ]
109
+ }
110
+ ]);
111
+
19
112
  export const PPT_REQUIRED_GATE_FIELDS = Object.freeze([
20
113
  'clarification_contract_sealed',
21
114
  'audience_strategy_sealed',
@@ -282,6 +375,13 @@ export function buildPptStoryboard(contract = {}, audience = buildPptAudienceStr
282
375
 
283
376
  export function buildPptStyleTokens(contract = {}) {
284
377
  const korean = /[ㄱ-ㅎ가-힣]/.test(`${contract.prompt || ''} ${JSON.stringify(contract.answers || {})}`);
378
+ const reference = selectPptDesignReference(contract);
379
+ const refTokens = reference.applied_token_profile.color;
380
+ const fontStack = korean
381
+ ? '"Pretendard Variable", Pretendard, -apple-system, BlinkMacSystemFont, "Apple SD Gothic Neo", "Noto Sans KR", sans-serif'
382
+ : reference.primary.id.endsWith(':ibm')
383
+ ? '"IBM Plex Sans", -apple-system, BlinkMacSystemFont, "SF Pro Display", Inter, Arial, sans-serif'
384
+ : '-apple-system, BlinkMacSystemFont, "SF Pro Display", Inter, "Helvetica Neue", Arial, sans-serif';
285
385
  return {
286
386
  schema_version: 1,
287
387
  created_at: nowIso(),
@@ -294,23 +394,31 @@ export function buildPptStyleTokens(contract = {}) {
294
394
  gutter_px: 24
295
395
  },
296
396
  color: {
297
- bg: '#f7f8fa',
298
- text: '#111318',
299
- muted: '#5b6270',
300
- primary: '#0b5cff',
301
- accent: '#00a88f',
302
- surface: '#ffffff',
303
- rule: '#d7dce5'
397
+ bg: refTokens.bg,
398
+ text: refTokens.text,
399
+ muted: refTokens.muted,
400
+ primary: refTokens.primary,
401
+ accent: refTokens.accent,
402
+ surface: refTokens.surface,
403
+ rule: refTokens.rule
304
404
  },
305
405
  typography: {
306
406
  language: korean ? 'ko' : 'en',
307
- font_stack: korean
308
- ? '"Pretendard Variable", Pretendard, -apple-system, BlinkMacSystemFont, "Apple SD Gothic Neo", "Noto Sans KR", sans-serif'
309
- : '-apple-system, BlinkMacSystemFont, "SF Pro Display", Inter, "Helvetica Neue", Arial, sans-serif',
310
- display_px: 76,
311
- body_px: 30,
312
- caption_px: 16,
313
- line_height: korean ? 1.42 : 1.32
407
+ font_stack: fontStack,
408
+ display_px: refTokens.display_px,
409
+ body_px: refTokens.body_px,
410
+ caption_px: refTokens.caption_px,
411
+ line_height: korean ? Math.max(1.4, refTokens.line_height) : refTokens.line_height,
412
+ letter_spacing: 0
413
+ },
414
+ layout: {
415
+ composition: refTokens.composition,
416
+ treatment: refTokens.treatment,
417
+ radius_px: refTokens.radius_px,
418
+ rule_px: 1,
419
+ source_rail: true,
420
+ evidence_grid: true,
421
+ mono_label: refTokens.mono_label
314
422
  },
315
423
  design_policy: {
316
424
  priority: 'information_first',
@@ -328,8 +436,9 @@ export function buildPptStyleTokens(contract = {}) {
328
436
  authority: DESIGN_SYSTEM_SSOT.authority_file,
329
437
  builder_prompt: DESIGN_SYSTEM_SSOT.builder_prompt,
330
438
  route_local_artifact: PPT_STYLE_TOKENS_ARTIFACT,
331
- rule: 'PPT style tokens are a route-local projection of the design SSOT; source inputs are fused here and are not independent authorities.'
439
+ rule: 'PPT style tokens are a route-local projection of the design SSOT; source inputs are selected, fused, and applied here rather than kept as independent authorities.'
332
440
  },
441
+ design_reference_selection: reference,
333
442
  source_inputs: [
334
443
  {
335
444
  id: GETDESIGN_REFERENCE.id,
@@ -343,31 +452,112 @@ export function buildPptStyleTokens(contract = {}) {
343
452
  }
344
453
  ],
345
454
  avoid: ['over-designed decoration', 'ornamental gradients', 'nested cards', 'low-contrast gray body text', 'excessive motion or effects'],
346
- detail_strategy: ['precise spacing', 'clear hierarchy', 'thin rules', 'disciplined alignment', 'subtle accent color only when it clarifies meaning'],
347
- anti_generic_ai_style: 'prevent AI-like design: select a concrete DESIGN.md or route-local visual system before styling; do not default to generic cards, gradients, vague SaaS visuals, oversized decoration, or unsupported image-like flourishes',
455
+ detail_strategy: ['precise spacing', 'clear hierarchy', 'thin rules', 'disciplined alignment', 'visible source rails', 'subtle accent color only when it clarifies meaning'],
456
+ anti_generic_ai_style: 'prevent AI-like design: select and apply a concrete awesome-design-md reference profile before styling; do not default to generic cards, gradients, vague SaaS visuals, oversized decoration, or unsupported image-like flourishes',
348
457
  image_policy: 'use images only when they improve comprehension; prefer Codex App built-in image generation via https://developers.openai.com/codex/app/features#image-generation when generated assets are needed'
349
458
  }
350
459
  };
351
460
  }
352
461
 
462
+ export function selectPptDesignReference(contract = {}) {
463
+ const text = cleanText(`${contract.prompt || ''} ${JSON.stringify(contract.answers || {})}`).toLowerCase();
464
+ const scored = PPT_DESIGN_REFERENCE_PROFILES.map((profile) => {
465
+ const score = profile.keywords.reduce((sum, keyword) => sum + (text.includes(String(keyword).toLowerCase()) ? 1 : 0), 0);
466
+ return { profile, score };
467
+ }).sort((a, b) => b.score - a.score);
468
+ const primary = scored[0]?.score > 0 ? scored[0].profile : PPT_DESIGN_REFERENCE_PROFILES[0];
469
+ const secondary = scored.find((entry) => entry.profile.id !== primary.id && entry.score > 0)?.profile || PPT_DESIGN_REFERENCE_PROFILES.find((entry) => entry.id !== primary.id);
470
+ return {
471
+ source: AWESOME_DESIGN_MD_REFERENCE.url,
472
+ selection_method: 'keyword_match_against_sealed_ppt_contract',
473
+ primary: {
474
+ id: primary.id,
475
+ name: primary.name,
476
+ source_url: primary.source_url,
477
+ source_summary: primary.source_summary,
478
+ applied_rules: primary.applied_rules
479
+ },
480
+ secondary: secondary ? {
481
+ id: secondary.id,
482
+ name: secondary.name,
483
+ source_url: secondary.source_url,
484
+ source_summary: secondary.source_summary,
485
+ applied_rules: secondary.applied_rules.slice(0, 2)
486
+ } : null,
487
+ selected_sources: [primary, secondary].filter(Boolean).map((profile) => ({
488
+ id: profile.id,
489
+ name: profile.name,
490
+ source_url: profile.source_url,
491
+ role: profile.id === primary.id ? 'primary_style_reference' : 'secondary_guardrail_reference'
492
+ })),
493
+ applied_token_profile: {
494
+ color: primary.tokens,
495
+ composition: primary.tokens.composition,
496
+ treatment: primary.tokens.treatment
497
+ },
498
+ selection_reason: scored[0]?.score > 0
499
+ ? `matched ${scored[0].score} contract keyword(s) to ${primary.name}`
500
+ : `no strong domain match; defaulted to ${primary.name} for restrained business presentation output`
501
+ };
502
+ }
503
+
353
504
  export function buildPptHtml({ contract = {}, audience, sourceLedger, storyboard, styleTokens }) {
354
505
  const title = escapeHtml(storyboard.title);
506
+ const referenceName = escapeHtml(styleTokens.design_policy?.design_reference_selection?.primary?.name || 'selected design reference');
507
+ const audienceRaw = escapeHtml(audience?.audience_profile?.raw || 'Audience context');
508
+ const stpRaw = escapeHtml(audience?.stp?.raw || 'STP context');
509
+ const decisionRaw = escapeHtml(audience?.decision_context?.raw || storyboard.thesis || '');
510
+ const surfaceRule = styleTokens.layout?.treatment === 'shadow_as_border_minimal_depth'
511
+ ? `box-shadow: 0 0 0 1px ${styleTokens.color.rule}; border: 0;`
512
+ : `border: 1px solid ${styleTokens.color.rule}; box-shadow: none;`;
355
513
  const css = `@page { size: 16in 9in; margin: 0; }
356
514
  * { box-sizing: border-box; }
357
515
  body { margin: 0; background: ${styleTokens.color.bg}; color: ${styleTokens.color.text}; font-family: ${styleTokens.typography.font_stack}; }
358
- .page { width: 100vw; min-height: 100vh; page-break-after: always; padding: 72px 96px; display: grid; align-content: center; gap: 26px; }
359
- .kicker { color: ${styleTokens.color.primary}; font-size: 18px; font-weight: 700; letter-spacing: 0; text-transform: uppercase; }
360
- h1 { margin: 0; font-size: 72px; line-height: 1.08; letter-spacing: 0; max-width: 1120px; }
361
- p { margin: 0; color: ${styleTokens.color.muted}; font-size: 28px; line-height: ${styleTokens.typography.line_height}; max-width: 920px; }
362
- .panel { border-left: 6px solid ${styleTokens.color.primary}; padding-left: 26px; }
363
- .source { font-size: 14px; color: ${styleTokens.color.muted}; align-self: end; }`;
516
+ .page { width: 100vw; min-height: 100vh; page-break-after: always; padding: 64px 88px 54px; display: grid; grid-template-rows: auto 1fr auto; gap: 34px; }
517
+ .topline { display: grid; grid-template-columns: 1fr auto; align-items: end; border-bottom: 1px solid ${styleTokens.color.rule}; padding-bottom: 14px; }
518
+ .kicker { color: ${styleTokens.color.primary}; font-size: ${styleTokens.typography.caption_px}px; font-weight: 600; letter-spacing: 0; text-transform: uppercase; }
519
+ .reference { color: ${styleTokens.color.muted}; font-size: ${styleTokens.typography.caption_px}px; letter-spacing: 0; }
520
+ .content { display: grid; grid-template-columns: minmax(0, 6fr) minmax(320px, 4fr); gap: 58px; align-items: center; }
521
+ h1 { margin: 0; font-size: ${styleTokens.typography.display_px}px; line-height: 1.08; letter-spacing: 0; max-width: 1040px; font-weight: 600; }
522
+ p { margin: 0; color: ${styleTokens.color.muted}; font-size: ${styleTokens.typography.body_px}px; line-height: ${styleTokens.typography.line_height}; max-width: 920px; }
523
+ .claim { display: grid; gap: 26px; }
524
+ .evidence { ${surfaceRule} border-radius: ${styleTokens.layout.radius_px}px; background: ${styleTokens.color.surface}; display: grid; }
525
+ .evidence-row { padding: 22px 24px; border-bottom: 1px solid ${styleTokens.color.rule}; }
526
+ .evidence-row:last-child { border-bottom: 0; }
527
+ .label { color: ${styleTokens.color.primary}; font-size: ${styleTokens.typography.caption_px}px; font-weight: 600; letter-spacing: 0; text-transform: uppercase; margin-bottom: 8px; }
528
+ .value { color: ${styleTokens.color.text}; font-size: 20px; line-height: 1.42; }
529
+ .source { display: grid; grid-template-columns: 1fr auto; gap: 24px; color: ${styleTokens.color.muted}; font-size: ${styleTokens.typography.caption_px}px; border-top: 1px solid ${styleTokens.color.rule}; padding-top: 14px; }
530
+ .accent { width: 64px; height: 3px; background: ${styleTokens.color.accent}; }`;
364
531
  const pages = storyboard.pages.map((page) => `<section class="page">
365
- <div class="kicker">${escapeHtml(page.kind)} / ${page.number}</div>
366
- <div class="panel">
367
- <h1>${escapeHtml(page.claim)}</h1>
368
- <p>${escapeHtml(page.support)}</p>
532
+ <header class="topline">
533
+ <div class="kicker">${escapeHtml(page.kind)} / ${page.number}</div>
534
+ <div class="reference">${referenceName}</div>
535
+ </header>
536
+ <main class="content">
537
+ <div class="claim">
538
+ <div class="accent"></div>
539
+ <h1>${escapeHtml(page.claim)}</h1>
540
+ <p>${escapeHtml(page.support)}</p>
541
+ </div>
542
+ <aside class="evidence" aria-label="decision evidence">
543
+ <div class="evidence-row">
544
+ <div class="label">Audience</div>
545
+ <div class="value">${audienceRaw}</div>
546
+ </div>
547
+ <div class="evidence-row">
548
+ <div class="label">STP</div>
549
+ <div class="value">${stpRaw}</div>
550
+ </div>
551
+ <div class="evidence-row">
552
+ <div class="label">Decision</div>
553
+ <div class="value">${decisionRaw}</div>
554
+ </div>
555
+ </aside>
556
+ </main>
557
+ <div class="source">
558
+ <span>Sources: ${escapeHtml((page.source_ids || []).join(', ') || 'none')}</span>
559
+ <span>${escapeHtml(styleTokens.layout?.composition || 'presentation-grid')}</span>
369
560
  </div>
370
- <div class="source">Sources: ${escapeHtml((page.source_ids || []).join(', ') || 'none')}</div>
371
561
  </section>`).join('\n');
372
562
  return `<!doctype html>
373
563
  <html lang="${styleTokens.typography.language}">
@@ -495,6 +685,9 @@ export function buildPptRenderReport({ contract = {}, audience, sourceLedger, st
495
685
  { id: 'restrained_detail', passed: styleTokens.design_policy?.visual_style === 'simple_restrained_detailed' },
496
686
  { id: 'design_ssot_declared', passed: styleTokens.design_policy?.design_ssot?.authority === DESIGN_SYSTEM_SSOT.authority_file },
497
687
  { id: 'curated_design_md_input_fused', passed: (styleTokens.design_policy?.source_inputs || []).some((entry) => entry.url === AWESOME_DESIGN_MD_REFERENCE.url && entry.role === 'source_input_for_ssot') },
688
+ { id: 'concrete_design_reference_selected', passed: Boolean(styleTokens.design_policy?.design_reference_selection?.primary?.id && styleTokens.design_policy?.design_reference_selection?.selected_sources?.length) },
689
+ { id: 'reference_rules_applied_to_tokens', passed: Boolean(styleTokens.layout?.composition && styleTokens.layout?.treatment && styleTokens.design_policy?.design_reference_selection?.applied_token_profile) },
690
+ { id: 'html_uses_reference_layout', passed: typeof html === 'string' && html.includes('decision evidence') && html.includes(styleTokens.layout?.composition || 'presentation-grid') },
498
691
  { id: 'ppt_skill_allowlist_enforced', passed: JSON.stringify(styleTokens.design_policy?.pipeline_allowlist?.required_skills || []) === JSON.stringify([...PPT_PIPELINE_SKILL_ALLOWLIST]) },
499
692
  { id: 'out_of_pipeline_design_skills_ignored', passed: styleTokens.design_policy?.pipeline_allowlist?.ignore_installed_out_of_pipeline_skills === true && (styleTokens.design_policy?.pipeline_allowlist?.ignored_design_skills_even_if_installed || []).includes('design-artifact-expert') },
500
693
  { id: 'ppt_mcp_allowlist_scoped', passed: (styleTokens.design_policy?.pipeline_allowlist?.allowed_mcp_servers || []).every((entry) => entry.mcp === 'context7' && /external_documentation/.test(entry.condition || '')) },
@@ -131,6 +131,10 @@ function promptHasRisk(lower) {
131
131
  return /(운영|production|prod|live|배포|publish|release|결제|payment|billing|auth|인증|보안|security|db|database|supabase|postgres|sql|schema|migration|마이그레이션|삭제|delete|drop|truncate|reset|권한|permission|credential|secret)/.test(lower);
132
132
  }
133
133
 
134
+ function promptNeedsExplicitRiskBoundary(lower) {
135
+ return /(drop|truncate|wipe|reset\s+(?:db|database)|delete\s+all|all-row|전체\s*(?:삭제|초기화)|운영\s*(?:db|데이터|삭제|쓰기|변경)|production\s*(?:db|database|delete|write|mutation)|prod\s*(?:db|database|delete|write|mutation)|credential|secret|api\s*key|토큰\s*(?:노출|삭제|교체)|권한\s*(?:상승|확대)|permission\s*(?:escalation|widening))/.test(lower);
136
+ }
137
+
134
138
  function promptHasContextTarget(text, lower) {
135
139
  return promptHasTarget(text, lower)
136
140
  || /https?:\/\/\S+/.test(text)
@@ -146,8 +150,11 @@ export function buildAmbiguityAssessment(prompt, explicitAnswers = {}) {
146
150
  const acceptance = promptHasExplicitAcceptance(lower) || hasAnswer(explicitAnswers.ACCEPTANCE_CRITERIA) || hasAnswer(explicitAnswers.SUCCESS_CRITERIA_OR_ACCEPTANCE);
147
151
  const risk = promptHasRisk(lower);
148
152
  const contextTarget = promptHasContextTarget(text, lower) || hasAnswer(explicitAnswers.CODEBASE_CONTEXT_TARGET);
153
+ const actionable = target && action && !underspecified;
154
+ const hardRiskNeedsBoundary = promptNeedsExplicitRiskBoundary(lower);
149
155
  const predictableSafetyDefault = /(재시도|retry|세션\s*만료|session\s*expired|session\s*expiry|token\s*expired)/.test(lower);
150
- const hasPolicy = hasAnswer(explicitAnswers.RISK_BOUNDARY) || hasAnswer(explicitAnswers.RISK_AND_BOUNDARY) || predictableSafetyDefault || /(하지\s*마|금지|no\s+|never|묻지|보존|preserve|safe|안전|검증|approval|승인|알아서|판단|추론|infer|default|기본)/.test(lower);
156
+ const predictableImplementationBoundary = actionable && risk && !hardRiskNeedsBoundary;
157
+ const hasPolicy = hasAnswer(explicitAnswers.RISK_BOUNDARY) || hasAnswer(explicitAnswers.RISK_AND_BOUNDARY) || predictableSafetyDefault || predictableImplementationBoundary || /(하지\s*마|금지|no\s+|never|묻지|보존|preserve|safe|안전|검증|approval|승인|알아서|판단|추론|infer|default|기본)/.test(lower);
151
158
  const hasMultipleChoiceRisk = /(\bor\b|또는|아니면|선택|둘 중|여러|multiple|대안)/.test(lower) && !/(알아서|판단|infer|추론|default|기본)/.test(lower);
152
159
 
153
160
  const goalClarity = underspecified ? (target || action ? 0.45 : 0.2) : (target && action ? 0.9 : target || action ? 0.62 : 0.25);
@@ -169,7 +176,7 @@ export function buildAmbiguityAssessment(prompt, explicitAnswers = {}) {
169
176
  const unresolved = [];
170
177
  if (components.goal.clarity_score < CLARITY_FLOORS.goal) unresolved.push('intent_target_or_required_outcome');
171
178
  if (components.success.clarity_score < CLARITY_FLOORS.success && (!target || !action || risk)) unresolved.push('success_criteria_or_acceptance');
172
- if (components.constraints.clarity_score < CLARITY_FLOORS.constraints || hasMultipleChoiceRisk) unresolved.push('risk_boundary_or_choice');
179
+ if ((components.constraints.clarity_score < CLARITY_FLOORS.constraints && hardRiskNeedsBoundary) || hasMultipleChoiceRisk) unresolved.push('risk_boundary_or_choice');
173
180
  if (components.context.clarity_score < CLARITY_FLOORS.context) unresolved.push('codebase_context_target');
174
181
  const uniqueUnresolved = [...new Set(unresolved)];
175
182
  return {
@@ -679,6 +679,7 @@ export function subagentExecutionPolicyText(route, prompt = '') {
679
679
  }
680
680
  return [
681
681
  'Subagent policy: REQUIRED for code-changing or execution work in this route.',
682
+ 'The selected SKS route itself authorizes route-owned worker/reviewer subagents; the user does not need to separately ask for subagents when the default Team pipeline is active.',
682
683
  'Before editing, the parent orchestrator must visibly state the SKS route, split independent write scopes, and spawn worker/reviewer subagents whenever the tools are available.',
683
684
  'Run workers in parallel only with disjoint ownership. The parent owns integration, verification, and final evidence.',
684
685
  'If subagent tools are unavailable or the work cannot be safely split, record that as explicit subagent evidence before editing.',
@@ -6,34 +6,113 @@ import { getCodexInfo } from './codex-adapter.mjs';
6
6
  import { codexAppIntegrationStatus, formatCodexAppStatus } from './codex-app.mjs';
7
7
 
8
8
  export const SKS_TMUX_LOGO = [
9
- ' _____ __ __ _____',
10
- ' / ___/ / //_/ / ___/',
11
- ' \\__ \\ / ,< \\__ \\ ',
12
- ' ___/ / / /| | ___/ / ',
13
- '/____/ /_/ |_| /____/ ',
14
- ' SNEAKOSCOPE CODEX'
9
+ ' _______ __ __ _______',
10
+ ' / _____/| / /_/ /| / _____/|',
11
+ ' / /____| | / __ / | / /____| |',
12
+ ' \\____ \\ | / / / /| | \\____ \\ |',
13
+ ' ____/ / | |/_/ /_/ / | |____/ / | |',
14
+ '/_____/ |//_/\\__/ / |//_____/ |/',
15
+ '\\_____\\___/ \\_\\ \\_\\/___/ \\_____\\___/',
16
+ ' SNEAKOSCOPE CODEX'
15
17
  ].join('\n');
16
18
 
17
19
  const SKS_TMUX_LOGO_FRAMES = [
18
20
  [
19
- ' __ __',
20
- ' / //_/ .',
21
- ' / ,< .',
22
- ' / /| | .',
23
- ' /_/ |_|',
24
- ' S K S'
21
+ ' ||',
22
+ ' ||',
23
+ ' ||',
24
+ ' ||',
25
+ ' ||',
26
+ ' ||',
27
+ ' SKS',
28
+ ' SNEAKOSCOPE CODEX'
25
29
  ].join('\n'),
26
30
  [
27
- ' _____ __ __ _____',
28
- ' / ___/ / //_/ / ___/',
29
- ' \\__ \\ / ,< \\__ \\ ',
30
- ' / / /| | / / ',
31
- ' /_/ /_/ |_| /_/ ',
32
- ' SNEAKOSCOPE'
31
+ ' //||',
32
+ ' // || .',
33
+ ' // || .:',
34
+ ' // || .::',
35
+ ' // || .:::',
36
+ ' // ||.::::',
37
+ ' S K S',
38
+ ' SNEAKOSCOPE CODEX'
39
+ ].join('\n'),
40
+ [
41
+ ' _______ __ __ _______',
42
+ ' / _____/| / /_/ /| / _____/|',
43
+ ' / /____| | / __ / | / /____| |',
44
+ ' \\____ \\ | / / / /| | \\____ \\ |',
45
+ ' ____/ / | |/_/ /_/ / | |____/ / | |',
46
+ '/_____/ |//_/\\__/ / |//_____/ |/',
47
+ '\\_____\\___/ \\_\\ \\_\\/___/ \\_____\\___/',
48
+ ' SNEAKOSCOPE CODEX'
49
+ ].join('\n'),
50
+ [
51
+ ' _______ __ __ _______',
52
+ ' / _____/ / /_/ / / _____/|',
53
+ ' / /____ / __ / / /____ | |',
54
+ ' \\____ \\ / / / / \\____ \\| |',
55
+ ' ____/ / /_/ /_/ / ____/ / | |',
56
+ '/_____/ /_/\\__/ /_____/ |/',
57
+ ' \\_____\\ \\_\\ \\_\\ \\_____\\___/',
58
+ ' SNEAKOSCOPE CODEX'
59
+ ].join('\n'),
60
+ [
61
+ ' _______ __ __ _______',
62
+ ' / _____/| / /_/ /| / _____/|',
63
+ ' / /____| | / __ / | / /____| |',
64
+ ' \\____ \\ | / / / /| | \\____ \\ |',
65
+ ' ____/ / | |/_/ /_/ / | |____/ / | |',
66
+ '/_____/ |//_/\\__/ / |//_____/ |/',
67
+ '\\_____\\___/ \\_\\ \\_\\/___/ \\_____\\___/',
68
+ ' SNEAKOSCOPE CODEX'
69
+ ].join('\n'),
70
+ [
71
+ ' _______ __ __ _______',
72
+ ' |\\_____ \\ / /_/ / |\\_____ \\',
73
+ ' | |____\\ \\/ __ / | |____\\ \\',
74
+ ' | |\\____\\/ / / / | |\\____\\ \\',
75
+ ' | | |___/ /_/ /__ | | |___/ /',
76
+ ' \\|_|/____/\\__/__/ \\|_|/____/',
77
+ ' S K S',
78
+ ' SNEAKOSCOPE CODEX'
79
+ ].join('\n'),
80
+ [
81
+ ' ||\\\\',
82
+ ' . || \\\\',
83
+ ' ::. || \\\\',
84
+ ' ::::. || \\\\',
85
+ ' ::::::. || \\\\',
86
+ ' :::::::::|| \\\\',
87
+ ' S K S',
88
+ ' SNEAKOSCOPE CODEX'
89
+ ].join('\n'),
90
+ [
91
+ ' ||',
92
+ ' ||',
93
+ ' ||',
94
+ ' ||',
95
+ ' ||',
96
+ ' ||',
97
+ ' SKS',
98
+ ' SNEAKOSCOPE CODEX'
33
99
  ].join('\n'),
34
100
  SKS_TMUX_LOGO
35
101
  ];
36
102
 
103
+ const SKS_TMUX_LOGO_ANIMATION_STEPS = Object.freeze([
104
+ { frame: 0, color: '39', bold: false, delay: '0.045' },
105
+ { frame: 1, color: '39', bold: false, delay: '0.045' },
106
+ { frame: 2, color: '45', bold: false, delay: '0.05' },
107
+ { frame: 3, color: '51', bold: false, delay: '0.055' },
108
+ { frame: 4, color: '51', bold: true, delay: '0.07' },
109
+ { frame: 5, color: '51', bold: true, delay: '0.07' },
110
+ { frame: 6, color: '45', bold: false, delay: '0.05' },
111
+ { frame: 7, color: '39', bold: false, delay: '0.045' },
112
+ { frame: 8, color: '39', bold: false, delay: '0.045' },
113
+ { frame: 9, color: '51', bold: true, delay: '0.16' }
114
+ ]);
115
+
37
116
  export const DEFAULT_SKS_CODEX_MODEL = 'gpt-5.5';
38
117
  export const DEFAULT_SKS_CODEX_REASONING = 'high';
39
118
 
@@ -123,7 +202,7 @@ export function tmuxStatusKind(tmux = {}) {
123
202
  export function codexLaunchCommand(root, codexBin, codexArgs = []) {
124
203
  const extraArgs = Array.isArray(codexArgs) ? codexArgs : [];
125
204
  return [
126
- sksLogoIntroCommand(),
205
+ sksLogoIntroCommand(codexBin),
127
206
  `printf '\\nProject: %s\\n' ${shellEscape(root)}`,
128
207
  'printf \'Runtime: tmux session for Codex CLI\\n\'',
129
208
  'printf \'Prompt: use canonical $ commands, for example $Team or $QA-LOOP\\n\\n\'',
@@ -133,20 +212,25 @@ export function codexLaunchCommand(root, codexBin, codexArgs = []) {
133
212
  ].join('; ');
134
213
  }
135
214
 
136
- export function sksLogoIntroCommand() {
215
+ export function sksLogoIntroCommand(codexBin = 'codex') {
137
216
  const staticLogo = `clear; printf '\\033[1;38;5;51m%s\\033[0m\\n' ${shellEscape(SKS_TMUX_LOGO)}`;
217
+ const authenticatedCheck = `${shellEscape(codexBin)} login status >/dev/null 2>&1`;
138
218
  const animated = [
139
219
  'clear',
140
- `printf '\\033[38;5;39m%s\\033[0m\\n' ${shellEscape(SKS_TMUX_LOGO_FRAMES[0])}`,
141
- 'sleep 0.07',
142
- 'clear',
143
- `printf '\\033[1;38;5;45m%s\\033[0m\\n' ${shellEscape(SKS_TMUX_LOGO_FRAMES[1])}`,
144
- 'sleep 0.08',
145
- 'clear',
146
- `printf '\\033[1;38;5;51m%s\\033[0m\\n' ${shellEscape(SKS_TMUX_LOGO_FRAMES[2])}`,
147
- 'sleep 0.15'
220
+ 'trap \'printf "\\033[0m\\033[?25h"\' EXIT INT TERM',
221
+ `printf '\\033[?25l'`,
222
+ ...SKS_TMUX_LOGO_ANIMATION_STEPS.flatMap((step) => {
223
+ const style = `${step.bold ? '1;' : ''}38;5;${step.color}`;
224
+ return [
225
+ `printf '\\033[H\\033[J\\033[${style}m%s\\033[0m\\n' ${shellEscape(SKS_TMUX_LOGO_FRAMES[step.frame])}`,
226
+ `sleep ${step.delay}`
227
+ ];
228
+ }),
229
+ `printf '\\033[H\\033[J\\033[1;38;5;51m%s\\033[0m\\n' ${shellEscape(SKS_TMUX_LOGO)}`,
230
+ `printf '\\033[?25h'`,
231
+ 'trap - EXIT INT TERM'
148
232
  ].join('; ');
149
- return `if [ "\${SKS_TMUX_LOGO_ANIMATION:-1}" = "0" ]; then ${staticLogo}; else ${animated}; fi`;
233
+ return `if [ -n "\${TMUX:-}" ] || [ "\${SKS_TMUX_LOGO_ANIMATION:-1}" = "0" ] || ${authenticatedCheck}; then ${staticLogo}; else ${animated}; fi`;
150
234
  }
151
235
 
152
236
  function terminalTitleCommand(title = '') {