sneakoscope 0.7.58 → 0.7.59

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 a multi-pane MAD tmux cockpit with the explicit full-access high-reasoning profile. |
45
+ | CLI runtime | Bare `sks` opens or reuses the default tmux Codex CLI workspace. `sks tmux open` remains the explicit form for session/workspace flags, and `sks --mad` launches a single-pane MAD tmux session with the explicit full-access high-reasoning profile. Split panes are reserved for active Team scout/worker lanes. |
46
46
  | Codex App commands | Installs generated skills so `$Team`, `$From-Chat-IMG`, `$DFix`, `$QA-LOOP`, `$PPT`, `$Image-UX-Review`, `$UX-Review`, `$Goal`, `$DB`, `$Wiki`, `$Help`, and related routes are visible in prompt workflows. `sks codex-app remote-control` wraps Codex CLI 0.130.0+ headless remote control without falling back to older app-server internals. |
47
47
  | OpenClaw agents | Generates an OpenClaw skill package so OpenClaw agents can attach `sneakoscope-codex`, enable the `shell` tool, and discover/use SKS commands from the target repo root. |
48
48
  | Pipeline plans | Writes `pipeline-plan.json` for stateful routes so the runtime lane, kept stages, skipped stages, verification commands, and no-unrequested-fallback invariant are visible with `sks pipeline plan`. |
@@ -80,7 +80,7 @@ The default `sks` runtime checks npm for newer `sneakoscope` and `@openai/codex`
80
80
  - Checks npm for newer `sneakoscope` and `@openai/codex` versions before launch and asks whether to update when the terminal can answer y/n.
81
81
  - Installs the latest Codex CLI with `npm i -g @openai/codex@latest` when it is missing and you approve or pass `--yes`.
82
82
  - Requires tmux 3.x or newer before opening the session.
83
- - Creates a named detached tmux cockpit with multiple panes and prints only the session, gate, attach, and blocker details needed to act.
83
+ - Creates a named detached single-pane tmux session and prints only the session, gate, attach, and blocker details needed to act.
84
84
 
85
85
  ## Installation
86
86
 
@@ -211,7 +211,7 @@ sks --mad
211
211
  sks --mad --yes
212
212
  ```
213
213
 
214
- This syncs existing codex-lb/Codex CLI auth before launch, creates/uses the `sks-mad-high` Codex profile for a one-shot full-access, high-reasoning tmux cockpit with `sandbox_mode = "danger-full-access"` and `approval_policy = "never"`, opens an active MAD-SKS permission gate for that tmux run, then launches a tiled cockpit: Codex CLI, MAD permission gate status, and live guide panes. The cockpit recreates the named session on launch so stale single-pane sessions do not hide the split layout, then attaches in an interactive terminal. If codex-lb is configured and no explicit `--workspace`/`--session` was passed, SKS opens a fresh tmux session so the repaired key is loaded by the Codex process immediately. While the gate is active, live server work, Supabase MCP database writes, direct SQL, targeted DML, schema cleanup, Supabase MCP `apply_migration`, and required Supabase CLI migration application such as `supabase migration up` or `supabase db push` are allowed. Catastrophic database wipe/all-row/project-management safeguards remain active.
214
+ This syncs existing codex-lb/Codex CLI auth before launch, creates/uses the `sks-mad-high` Codex profile for a one-shot full-access, high-reasoning tmux session with `sandbox_mode = "danger-full-access"` and `approval_policy = "never"`, opens an active MAD-SKS permission gate for that tmux run, then launches a single Codex CLI pane. The session recreates the named session on launch so stale split-pane MAD sessions collapse back to the single-pane default, then attaches in an interactive terminal. If codex-lb is configured and no explicit `--workspace`/`--session` was passed, SKS opens a fresh tmux session so the repaired key is loaded by the Codex process immediately. While the gate is active, live server work, Supabase MCP database writes, direct SQL, targeted DML, schema cleanup, Supabase MCP `apply_migration`, and required Supabase CLI migration application such as `supabase migration up` or `supabase db push` are allowed. Catastrophic database wipe/all-row/project-management safeguards remain active.
215
215
 
216
216
  MAD does not disable the pipeline contract: stages, executors, reviewers, and auto-review policy still must not invent unrequested fallback implementation code. If the requested path cannot be implemented, SKS should block with evidence rather than add substitute behavior.
217
217
 
@@ -287,7 +287,7 @@ The design borrows two useful ideas from external planning systems without copyi
287
287
 
288
288
  `sks goal` and `$Goal` only prepare/control the native `/goal` persistence bridge. They do not replace Team, QA, DB, or other implementation routes; use the selected execution route for the actual work and verification. Context7 is only needed for Goal when external API/library documentation becomes relevant.
289
289
 
290
- Use `$Computer-Use` or `$CU` inside Codex App when the task specifically needs Codex Computer Use speed for UI/browser/visual work. This lane intentionally skips Team debate, QA-LOOP clarification, subagents, and upfront TriWiki refresh. It still requires Codex Computer Use as the evidence source, and it defers TriWiki refresh/validate plus Honest Mode to the final closeout.
290
+ Use `$Computer-Use` or `$CU` inside Codex App when the task specifically needs Codex Computer Use speed for UI/browser/visual work. This lane intentionally skips Team debate, QA-LOOP clarification, subagents, and upfront TriWiki refresh. It still requires Codex Computer Use as the evidence source, and it defers TriWiki refresh/validate plus Honest Mode to the final closeout. SKS does not install a generated skill named `computer-use`, because that name is reserved for the first-party Codex Computer Use plugin; use `$CU` or `$computer-use-fast` from the SKS picker for the SKS route, and use `@Computer` for the OpenAI plugin.
291
291
 
292
292
  ### Create A Presentation
293
293
 
@@ -555,6 +555,8 @@ codex mcp list
555
555
 
556
556
  Codex App workflows need the app installed. QA and UI/browser visual-evidence workflows require first-party Codex Computer Use; Browser Use may support non-UI browser context, but it is not valid UI/browser verification evidence. Generated raster assets and image-review evidence require real Codex App `$imagegen`/`gpt-image-2` output, or the route must stay blocked/unverified.
557
557
 
558
+ SKS setup removes old SKS-generated `computer-use` skills from `.agents/skills` so they cannot shadow the first-party Computer Use plugin. If a running Codex App thread was opened before setup or upgrade, start a fresh thread and invoke `@Computer` or Browser again so the host reloads plugin tools.
559
+
558
560
  ### Setup is blocked by another harness
559
561
 
560
562
  ```sh
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "sneakoscope",
3
3
  "displayName": "ㅅㅋㅅ",
4
- "version": "0.7.58",
4
+ "version": "0.7.59",
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",
@@ -36,7 +36,11 @@ export async function postinstall({ bootstrap }) {
36
36
  else if (fastModeRepair.status === 'skipped') console.log(`Codex App Fast mode: skipped (${fastModeRepair.reason}).`);
37
37
  else if (fastModeRepair.status === 'failed') console.log(`Codex App Fast mode: auto repair failed. Run \`sks setup\`. ${fastModeRepair.error || ''}`.trim());
38
38
  const globalSkills = await ensureGlobalCodexSkillsDuringInstall();
39
- if (globalSkills.status === 'installed') console.log(`Codex App global $ skills: installed in ${globalSkills.root} (${globalSkills.installed_count} skills).`);
39
+ if (globalSkills.status === 'installed') {
40
+ const removed = globalSkills.removed_stale_generated_skills || [];
41
+ const cleanup = removed.length ? ` Removed stale generated skill shadow(s): ${removed.join(', ')}.` : '';
42
+ console.log(`Codex App global $ skills: installed in ${globalSkills.root} (${globalSkills.installed_count} skills).${cleanup}`);
43
+ }
40
44
  else if (globalSkills.status === 'partial') console.log(`Codex App global $ skills: partial in ${globalSkills.root}; missing ${globalSkills.missing_skills.join(', ')}. Run \`sks doctor --fix\`.`);
41
45
  else if (globalSkills.status === 'skipped') console.log(`Codex App global $ skills: skipped (${globalSkills.reason}).`);
42
46
  else if (globalSkills.status === 'failed') console.log(`Codex App global $ skills: auto setup failed. Run \`sks doctor --fix\`. ${globalSkills.error || ''}`.trim());
package/src/cli/main.mjs CHANGED
@@ -283,7 +283,7 @@ async function wizard(args = []) {
283
283
  const update = await askChoice(rl, 'Update SKS before setup?', ['yes', 'no'], 'yes');
284
284
  if (update === 'yes') {
285
285
  console.log('\nRun this update command, then rerun `sks`:');
286
- console.log(' npm i -g sneakoscope\n');
286
+ console.log(' npm i -g sneakoscope@latest\n');
287
287
  return;
288
288
  }
289
289
  console.log('Skipping update for this setup run.\n');
@@ -345,7 +345,7 @@ async function updateCheck(args = []) {
345
345
  console.log(`Latest: ${result.latest || 'unknown'}`);
346
346
  console.log(`Update: ${result.update_available ? 'available' : 'not needed'}`);
347
347
  if (result.error) console.log(`Error: ${result.error}`);
348
- if (result.update_available) console.log('Run: npm i -g sneakoscope');
348
+ if (result.update_available) console.log('Run: npm i -g sneakoscope@latest');
349
349
  }
350
350
 
351
351
  const DOLLAR_DEFAULT_PIPELINE_TEXT = 'Default pipeline: direct answers -> $Answer, tiny Direct Fix edits -> $DFix, presentation/PDF artifacts -> $PPT, image-generation UI/UX reviews -> $Image-UX-Review/$UX-Review, Computer Use UI/browser speed work -> $Computer-Use, code -> $Team. Execution routes infer their contract from prompt, TriWiki/current-code defaults, and conservative policy instead of surfacing prequestion sheets. 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.';
@@ -1720,7 +1720,11 @@ async function doctor(args) {
1720
1720
  console.log(`Install: ${install.ok ? 'ok' : 'missing'} ${install.scope} (${install.command_prefix})`);
1721
1721
  console.log(`Conflicts: ${result.harness_conflicts.hard_block ? 'blocked' : 'ok'} ${result.harness_conflicts.conflicts.length} finding(s)`);
1722
1722
  if (repairApplied) console.log('Repair: regenerated SKS managed files from the installed package template');
1723
- if (globalSkillsRepair) console.log(`Global $ repair: ${globalSkillsRepair.status} ${globalSkillsRepair.root || ''}`.trimEnd());
1723
+ if (globalSkillsRepair) {
1724
+ const removed = globalSkillsRepair.removed_stale_generated_skills || [];
1725
+ const cleanup = removed.length ? ` removed stale generated skill shadow(s): ${removed.join(', ')}` : '';
1726
+ console.log(`Global $ repair: ${globalSkillsRepair.status} ${globalSkillsRepair.root || ''}${cleanup}`.trimEnd());
1727
+ }
1724
1728
  if (flag(args, '--fix') && result.harness_conflicts.hard_block) console.log('Repair: skipped because another Codex harness needs human-approved removal first');
1725
1729
  console.log(`Rust acc.: ${rust.available ? rust.version : 'optional-missing'}`);
1726
1730
  console.log(`State: ${result.sneakoscope.ok ? 'ok' : 'missing .sneakoscope'}`);
@@ -2024,12 +2028,14 @@ async function selftest() {
2024
2028
  await writeTextAtomic(path.join(repairTmp, '.agents', 'skills', 'agent-team', 'SKILL.md'), '---\nname: agent-team\ndescription: Fallback Codex App picker alias for $Team.\n---\n');
2025
2029
  await ensureDir(path.join(repairTmp, '.agents', 'skills', 'stale-sks-generated'));
2026
2030
  await writeTextAtomic(path.join(repairTmp, '.agents', 'skills', 'stale-sks-generated', 'SKILL.md'), '---\nname: stale-sks-generated\ndescription: Old SKS generated skill that should disappear on update.\n---\n');
2031
+ await ensureDir(path.join(repairTmp, '.agents', 'skills', 'computer-use'));
2032
+ await writeTextAtomic(path.join(repairTmp, '.agents', 'skills', 'computer-use', 'SKILL.md'), '---\nname: computer-use\ndescription: Maximum-speed $Computer-Use/$CU lane for Codex Computer Use UI/browser/visual tasks.\n---\n');
2027
2033
  await writeJsonAtomic(path.join(repairTmp, '.agents', 'skills', '.sks-generated.json'), {
2028
2034
  schema_version: 1,
2029
2035
  generated_by: 'sneakoscope',
2030
2036
  version: '0.0.1',
2031
- skills: ['team', 'stale-sks-generated'],
2032
- files: ['.agents/skills/team/SKILL.md', '.agents/skills/stale-sks-generated/SKILL.md']
2037
+ skills: ['team', 'stale-sks-generated', 'computer-use'],
2038
+ files: ['.agents/skills/team/SKILL.md', '.agents/skills/stale-sks-generated/SKILL.md', '.agents/skills/computer-use/SKILL.md']
2033
2039
  });
2034
2040
  const staleCodexAgentRel = '.codex/agents/stale-generated.toml';
2035
2041
  await writeTextAtomic(path.join(repairTmp, staleCodexAgentRel), 'name = "stale_generated"\n');
@@ -2050,6 +2056,8 @@ async function selftest() {
2050
2056
  await writeJsonAtomic(path.join(repairTmp, '.sneakoscope', 'policy.json'), { broken: true });
2051
2057
  const existingAgentsMd = await safeReadText(path.join(repairTmp, 'AGENTS.md'));
2052
2058
  await writeTextAtomic(path.join(repairTmp, 'AGENTS.md'), existingAgentsMd.replace(/<!-- BEGIN Sneakoscope Codex GX MANAGED BLOCK -->[\s\S]*?<!-- END Sneakoscope Codex GX MANAGED BLOCK -->\n?/, '<!-- BEGIN Sneakoscope Codex GX MANAGED BLOCK -->\ntampered managed block\n<!-- END Sneakoscope Codex GX MANAGED BLOCK -->\n'));
2059
+ const stalePluginSkillNames = ['computer-use', 'browser-use', 'browser'];
2060
+ const stalePluginSkillContent = (name) => `---\nname: ${name}\ndescription: Sneakoscope generated stale plugin collision for selftest.\n---\n\nCodex App pipeline activation:\n- stale selftest marker\n`;
2053
2061
  const doctorRepair = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'doctor', '--fix', '--local-only', '--json'], {
2054
2062
  cwd: repairTmp,
2055
2063
  env: { HOME: path.join(repairTmp, 'home'), SKS_DISABLE_UPDATE_CHECK: '1' },
@@ -2065,6 +2073,7 @@ async function selftest() {
2065
2073
  if (!repairedTeamSkill.includes('SKS Team orchestration') || repairedTeamSkill.includes('tampered')) throw new Error('selftest failed: doctor repair did not regenerate team skill');
2066
2074
  if (await exists(path.join(repairTmp, '.agents', 'skills', 'agent-team', 'SKILL.md'))) throw new Error('selftest failed: doctor repair did not remove deprecated agent-team alias skill');
2067
2075
  if (await exists(path.join(repairTmp, '.agents', 'skills', 'stale-sks-generated', 'SKILL.md'))) throw new Error('selftest failed: doctor repair did not prune stale generated skill from previous SKS manifest');
2076
+ if (await exists(path.join(repairTmp, '.agents', 'skills', 'computer-use', 'SKILL.md'))) throw new Error('selftest failed: doctor repair did not remove generated computer-use skill that shadows the first-party plugin');
2068
2077
  if (await exists(path.join(repairTmp, staleCodexAgentRel))) throw new Error('selftest failed: doctor repair did not prune stale generated agent file from previous SKS manifest');
2069
2078
  if (!doctorRepairJson.repair?.project?.skill_install?.removed_stale_generated_skills?.includes('.agents/skills/stale-sks-generated')) throw new Error('selftest failed: doctor repair did not report stale generated skill pruning');
2070
2079
  const generatedCleanupReport = doctorRepairJson.repair?.project?.generated_cleanup || {};
@@ -2079,6 +2088,29 @@ async function selftest() {
2079
2088
  if (repairedPolicy.broken || repairedPolicy.installation?.scope !== 'project' || !repairedPolicy.prompt_pipeline?.dollar_commands?.includes('$Team')) throw new Error('selftest failed: doctor --fix did not regenerate policy');
2080
2089
  const repairedAgentsMd = await safeReadText(path.join(repairTmp, 'AGENTS.md'));
2081
2090
  if (!repairedAgentsMd.includes('Do not create unrequested fallback implementation code') || repairedAgentsMd.includes('tampered managed block')) throw new Error('selftest failed: doctor --fix did not repair AGENTS managed block');
2091
+ const doctorGlobalTmp = tmpdir();
2092
+ await writeJsonAtomic(path.join(doctorGlobalTmp, 'package.json'), { name: 'doctor-global-skill-repair-smoke', version: '0.0.0' });
2093
+ await initProject(doctorGlobalTmp, { installScope: 'global' });
2094
+ const doctorGlobalHome = path.join(doctorGlobalTmp, 'home');
2095
+ for (const name of stalePluginSkillNames) {
2096
+ await ensureDir(path.join(doctorGlobalHome, '.agents', 'skills', name));
2097
+ await writeTextAtomic(path.join(doctorGlobalHome, '.agents', 'skills', name, 'SKILL.md'), stalePluginSkillContent(name));
2098
+ }
2099
+ const doctorGlobalRepair = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'doctor', '--fix', '--json'], {
2100
+ cwd: doctorGlobalTmp,
2101
+ env: { HOME: doctorGlobalHome, SKS_DISABLE_UPDATE_CHECK: '1' },
2102
+ timeoutMs: 30000,
2103
+ maxOutputBytes: 1024 * 1024
2104
+ });
2105
+ if (doctorGlobalRepair.code !== 0) throw new Error(`selftest failed: doctor --fix global skill repair exited ${doctorGlobalRepair.code}: ${doctorGlobalRepair.stderr}`);
2106
+ const doctorGlobalRepairJson = JSON.parse(doctorGlobalRepair.stdout || '{}');
2107
+ for (const name of stalePluginSkillNames) {
2108
+ if (await exists(path.join(doctorGlobalHome, '.agents', 'skills', name, 'SKILL.md'))) throw new Error(`selftest failed: doctor --fix did not remove global generated ${name} plugin shadow skill`);
2109
+ }
2110
+ const doctorGlobalRemoved = doctorGlobalRepairJson.repair?.global_skills?.removed_stale_generated_skills || [];
2111
+ for (const name of stalePluginSkillNames) {
2112
+ if (!doctorGlobalRemoved.includes(`.agents/skills/${name}`)) throw new Error(`selftest failed: doctor --fix did not report global ${name} plugin shadow cleanup`);
2113
+ }
2082
2114
  const conflictTmp = tmpdir();
2083
2115
  await ensureDir(path.join(conflictTmp, '.omx'));
2084
2116
  const conflictScan = await scanHarnessConflicts(conflictTmp, { home: path.join(conflictTmp, 'home') });
@@ -2092,12 +2124,18 @@ async function selftest() {
2092
2124
  const postinstallSetupTmp = tmpdir();
2093
2125
  await writeJsonAtomic(path.join(postinstallSetupTmp, 'package.json'), { name: 'postinstall-setup-smoke', version: '0.0.0' });
2094
2126
  await ensureDir(path.join(postinstallSetupTmp, '.git'));
2127
+ const postinstallSetupHome = path.join(postinstallSetupTmp, 'home');
2128
+ for (const name of stalePluginSkillNames) {
2129
+ await ensureDir(path.join(postinstallSetupHome, '.agents', 'skills', name));
2130
+ await writeTextAtomic(path.join(postinstallSetupHome, '.agents', 'skills', name, 'SKILL.md'), stalePluginSkillContent(name));
2131
+ }
2095
2132
  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 });
2096
2133
  if (postinstallSetup.code !== 0) throw new Error(`selftest failed: postinstall setup exited ${postinstallSetup.code}: ${postinstallSetup.stderr}`);
2097
2134
  if (await exists(path.join(postinstallSetupTmp, '.agents', 'skills', 'agent-team', 'SKILL.md'))) throw new Error('selftest failed: postinstall installed deprecated agent-team fallback skill');
2098
2135
  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');
2099
2136
  if (!(await exists(path.join(postinstallSetupTmp, '.codex', 'hooks.json')))) throw new Error('selftest failed: postinstall did not create project hooks during automatic bootstrap');
2100
2137
  if (!String(postinstallSetup.stdout || '').includes('Codex App global $ skills: installed')) throw new Error('selftest failed: postinstall did not report automatic global Codex App skills');
2138
+ if (!String(postinstallSetup.stdout || '').includes('Removed stale generated skill shadow(s):')) throw new Error('selftest failed: postinstall did not report stale first-party plugin shadow cleanup');
2101
2139
  const postinstallSetupManifest = await readJson(path.join(postinstallSetupTmp, '.sneakoscope', 'manifest.json'));
2102
2140
  if (postinstallSetupManifest.installation?.scope !== 'global') throw new Error('selftest failed: postinstall automatic bootstrap did not use global install scope');
2103
2141
  if (postinstallSetupManifest.design_system_ssot?.authority_file !== DESIGN_SYSTEM_SSOT.authority_file || postinstallSetupManifest.design_system_ssot?.builder_prompt !== DESIGN_SYSTEM_SSOT.builder_prompt) throw new Error('selftest failed: postinstall manifest missing design SSOT policy');
@@ -2108,9 +2146,11 @@ async function selftest() {
2108
2146
  }
2109
2147
  const postinstallSetupGitignore = await safeReadText(path.join(postinstallSetupTmp, '.gitignore'));
2110
2148
  if (!postinstallSetupGitignore.includes('.sneakoscope/') || !postinstallSetupGitignore.includes('.codex/') || !postinstallSetupGitignore.includes('.agents/') || !postinstallSetupGitignore.includes('AGENTS.md')) throw new Error('selftest failed: automatic postinstall bootstrap did not ignore SKS generated files');
2111
- for (const { command } of DOLLAR_COMMANDS) {
2112
- const skillName = command.slice(1).toLowerCase();
2113
- if (!(await exists(path.join(postinstallSetupTmp, 'home', '.agents', 'skills', skillName, 'SKILL.md')))) throw new Error(`selftest failed: postinstall global ${command} skill not installed`);
2149
+ for (const skillName of new Set(DOLLAR_SKILL_NAMES)) {
2150
+ if (!(await exists(path.join(postinstallSetupTmp, 'home', '.agents', 'skills', skillName, 'SKILL.md')))) throw new Error(`selftest failed: postinstall global ${skillName} skill not installed`);
2151
+ }
2152
+ for (const name of stalePluginSkillNames) {
2153
+ if (await exists(path.join(postinstallSetupHome, '.agents', 'skills', name, 'SKILL.md'))) throw new Error(`selftest failed: postinstall global skills shadow the first-party ${name} plugin`);
2114
2154
  }
2115
2155
  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');
2116
2156
  const oldNoBootstrap = process.env.SKS_POSTINSTALL_NO_BOOTSTRAP;
@@ -2375,15 +2415,16 @@ async function selftest() {
2375
2415
  if (globalSkillsResult.status !== 'installed') throw new Error(`selftest failed: global Codex App skills not installed: ${globalSkillsResult.status}`);
2376
2416
  const globalSkillStatus = await checkRequiredSkills(globalSkillsTmp, path.join(globalSkillsTmp, '.agents', 'skills'));
2377
2417
  if (!globalSkillStatus.ok) throw new Error(`selftest failed: global Codex App skills missing: ${globalSkillStatus.missing.join(', ')}`);
2418
+ if (await exists(path.join(globalSkillsTmp, '.agents', 'skills', 'computer-use', 'SKILL.md'))) throw new Error('selftest failed: global generated skills shadow the first-party computer-use plugin');
2378
2419
  const codexSkillMirrorExists = await exists(path.join(tmp, '.codex', 'skills', 'research-discovery', 'SKILL.md'));
2379
2420
  if (codexSkillMirrorExists) throw new Error('selftest failed: generated .codex/skills mirror still installed');
2380
2421
  const codexAppSkillExists = await exists(path.join(tmp, '.agents', 'skills', 'research-discovery', 'SKILL.md'));
2381
2422
  if (!codexAppSkillExists) throw new Error('selftest failed: Codex App skill not installed');
2382
- for (const { command } of DOLLAR_COMMANDS) {
2383
- const skillName = command.slice(1).toLowerCase();
2423
+ for (const skillName of new Set(DOLLAR_SKILL_NAMES)) {
2384
2424
  const dollarSkillExists = await exists(path.join(tmp, '.agents', 'skills', skillName, 'SKILL.md'));
2385
- if (!dollarSkillExists) throw new Error(`selftest failed: ${command} skill not installed`);
2425
+ if (!dollarSkillExists) throw new Error(`selftest failed: ${skillName} skill not installed`);
2386
2426
  }
2427
+ if (await exists(path.join(tmp, '.agents', 'skills', 'computer-use', 'SKILL.md'))) throw new Error('selftest failed: project generated skills shadow the first-party computer-use plugin');
2387
2428
  const promptPipelineSkillExists = await exists(path.join(tmp, '.agents', 'skills', 'prompt-pipeline', 'SKILL.md'));
2388
2429
  if (!promptPipelineSkillExists) throw new Error('selftest failed: prompt pipeline skill not installed');
2389
2430
  const promptPipelineText = await safeReadText(path.join(tmp, '.agents', 'skills', 'prompt-pipeline', 'SKILL.md'));
@@ -2652,6 +2693,19 @@ async function selftest() {
2652
2693
  if (!String(hookUpdateOldContext).includes('Update SKS now') || !String(hookUpdateOldContext).includes('Skip update for this conversation')) throw new Error('selftest failed: hook did not prompt when installed SKS is stale');
2653
2694
  const hookUpdateOldState = await readJson(path.join(hookUpdateOldTmp, '.sneakoscope', 'state', 'update-check.json'), {});
2654
2695
  if (hookUpdateOldState.pending_offer?.latest !== '9.9.9') throw new Error('selftest failed: stale installed SKS did not persist pending update offer');
2696
+ const hookUpdateAcceptPayload = JSON.stringify({ cwd: hookUpdateOldTmp, prompt: 'Update SKS now' });
2697
+ const hookUpdateAcceptResult = await runProcess(process.execPath, [hookBin, 'hook', 'user-prompt-submit'], {
2698
+ cwd: hookUpdateOldTmp,
2699
+ input: hookUpdateAcceptPayload,
2700
+ env: { ...hookUpdateCurrentEnv, SKS_INSTALLED_SKS_VERSION: '0.0.0' },
2701
+ timeoutMs: 15000,
2702
+ maxOutputBytes: 256 * 1024
2703
+ });
2704
+ if (hookUpdateAcceptResult.code !== 0) throw new Error(`selftest failed: accepted update hook exited ${hookUpdateAcceptResult.code}: ${hookUpdateAcceptResult.stderr}`);
2705
+ const hookUpdateAcceptJson = JSON.parse(hookUpdateAcceptResult.stdout);
2706
+ const hookUpdateAcceptContext = hookUpdateAcceptJson.hookSpecificOutput?.additionalContext || '';
2707
+ if (!String(hookUpdateAcceptContext).includes('npm i -g sneakoscope@latest')) throw new Error('selftest failed: accepted update hook did not specify the exact npm latest command');
2708
+ if (String(hookUpdateAcceptContext).includes('sks setup') || String(hookUpdateAcceptContext).includes('sks doctor') || String(hookUpdateAcceptContext).includes('npm i -D sneakoscope')) throw new Error('selftest failed: accepted update hook still routes through setup/doctor/project update commands');
2655
2709
  const hookKoreanSksTmp = tmpdir();
2656
2710
  await initProject(hookKoreanSksTmp, {});
2657
2711
  const hookKoreanSksPayload = JSON.stringify({ cwd: hookKoreanSksTmp, prompt: koreanReadmeInstallPrompt });
@@ -3177,7 +3231,7 @@ async function selftest() {
3177
3231
  await writeTextAtomic(fakeTmuxLog, '');
3178
3232
  const madCockpit = await launchMadTmuxUi(['--workspace', 'sks-mad-selftest-ui', '--no-attach'], { root: tmp, tmux: { ok: true, bin: fakeTmuxBin, version: '3.4' }, codex: { bin: process.execPath }, app: { ok: true, guidance: [] }, missionId: 'M-MAD-SELFTEST' });
3179
3233
  const madTmuxLogText = await safeReadText(fakeTmuxLog);
3180
- if (!madCockpit.created || madCockpit.opened?.panes?.length !== 3 || !madTmuxLogText.includes('new-session') || !madTmuxLogText.includes('split-window')) throw new Error('selftest failed: MAD tmux launch did not create a multi-pane cockpit');
3234
+ if (!madCockpit.created || madCockpit.mode !== 'mad_session' || madCockpit.opened?.panes?.length !== 1 || !madTmuxLogText.includes('new-session') || madTmuxLogText.includes('split-window')) throw new Error('selftest failed: MAD tmux launch should create one pane and leave split panes to Team lanes');
3181
3235
  if (previousFakeTmuxLog === undefined) delete process.env.SKS_FAKE_TMUX_LOG;
3182
3236
  else process.env.SKS_FAKE_TMUX_LOG = previousFakeTmuxLog;
3183
3237
  const codexLaunchArgs = defaultCodexLaunchArgs({ SKS_CODEX_REASONING: 'low' }).join(' ');
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.58';
8
+ export const PACKAGE_VERSION = '0.7.59';
9
9
  export const DEFAULT_PROCESS_TAIL_BYTES = 256 * 1024;
10
10
  export const DEFAULT_PROCESS_TIMEOUT_MS = 30 * 60 * 1000;
11
11
 
@@ -533,7 +533,7 @@ async function updateCheckContext(root, payload, prompt) {
533
533
  pending_offer: null,
534
534
  accepted: { conversation_id: conv, latest: pending.latest, accepted_at: nowIso() }
535
535
  });
536
- return `SKS update check: user accepted update to ${pending.latest}. Before doing other work, run the appropriate update command, then rerun sks setup and sks doctor --fix. Global install: npm i -g sneakoscope. Project install: npm i -D sneakoscope && npx sks setup --install-scope project.`;
536
+ return `SKS update check: user accepted update to ${pending.latest}. Before doing other work, run exactly this command and nothing else: npm i -g sneakoscope@latest. Do not start a pipeline route, run setup, or run doctor for this accepted update command.`;
537
537
  }
538
538
  if (updateState.skipped?.conversation_id === conv && updateState.skipped?.latest) {
539
539
  return `SKS update check: update ${updateState.skipped.latest} was skipped for this conversation only. Do not ask again in this conversation; check again next conversation.`;
@@ -562,7 +562,7 @@ async function updateCheckContext(root, payload, prompt) {
562
562
  pending_offer: { conversation_id: conv, latest: check.latest, offered_at: nowIso() },
563
563
  skipped: updateState.skipped?.conversation_id === conv ? null : updateState.skipped || null
564
564
  });
565
- return `SKS update check: installed ${current}, latest ${check.latest}. Before any other work, ask the user to choose: "Update SKS now" or "Skip update for this conversation". If they choose update, run npm i -g sneakoscope for global installs, or npm i -D sneakoscope && npx sks setup --install-scope project for project installs, then run sks setup and sks doctor --fix. If they skip, do not ask again in this conversation, but check again next conversation.`;
565
+ return `SKS update check: installed ${current}, latest ${check.latest}. Before any other work, ask the user to choose: "Update SKS now" or "Skip update for this conversation". If they choose update, run exactly this command and nothing else: npm i -g sneakoscope@latest. Do not start a pipeline route, run setup, or run doctor for this accepted update command. If they skip, do not ask again in this conversation, but check again next conversation.`;
566
566
  }
567
567
 
568
568
  async function checkLatestVersion() {
package/src/core/init.mjs CHANGED
@@ -7,7 +7,7 @@ import { isHarnessSourceProject, writeHarnessGuardPolicy } from './harness-guard
7
7
  import { repairSksGeneratedArtifacts } from './harness-conflicts.mjs';
8
8
  import { installVersionGitHook } from './version-manager.mjs';
9
9
  import { MIN_TEAM_REVIEWER_LANES, MIN_TEAM_REVIEW_POLICY_TEXT } from './team-review-policy.mjs';
10
- import { AWESOME_DESIGN_MD_REFERENCE, CODEX_APP_IMAGE_GENERATION_DOC_URL, CODEX_COMPUTER_USE_ONLY_POLICY, CODEX_IMAGEGEN_REQUIRED_POLICY, DESIGN_SYSTEM_SSOT, DOLLAR_COMMANDS, DOLLAR_COMMAND_ALIASES, DOLLAR_SKILL_NAMES, FROM_CHAT_IMG_CHECKLIST_ARTIFACT, FROM_CHAT_IMG_COVERAGE_ARTIFACT, FROM_CHAT_IMG_QA_LOOP_ARTIFACT, FROM_CHAT_IMG_TEMP_TRIWIKI_ARTIFACT, FROM_CHAT_IMG_TEMP_TRIWIKI_SESSIONS, GETDESIGN_REFERENCE, PPT_CONDITIONAL_SKILL_ALLOWLIST, PPT_PIPELINE_MCP_ALLOWLIST, PPT_PIPELINE_SKILL_ALLOWLIST, RECOMMENDED_DESIGN_REFERENCES, RECOMMENDED_MCP_SERVERS, RECOMMENDED_SKILLS, chatCaptureIntakeText, context7ConfigToml, getdesignReferencePolicyText, imageUxReviewPipelinePolicyText, outcomeRubricPolicyText, pptPipelineAllowlistPolicyText, speedLanePolicyText, stackCurrentDocsPolicyText, triwikiContextTracking, triwikiContextTrackingText, triwikiStagePolicyText } from './routes.mjs';
10
+ import { AWESOME_DESIGN_MD_REFERENCE, CODEX_APP_IMAGE_GENERATION_DOC_URL, CODEX_COMPUTER_USE_ONLY_POLICY, CODEX_IMAGEGEN_REQUIRED_POLICY, DESIGN_SYSTEM_SSOT, DOLLAR_COMMANDS, DOLLAR_COMMAND_ALIASES, DOLLAR_SKILL_NAMES, FROM_CHAT_IMG_CHECKLIST_ARTIFACT, FROM_CHAT_IMG_COVERAGE_ARTIFACT, FROM_CHAT_IMG_QA_LOOP_ARTIFACT, FROM_CHAT_IMG_TEMP_TRIWIKI_ARTIFACT, FROM_CHAT_IMG_TEMP_TRIWIKI_SESSIONS, GETDESIGN_REFERENCE, PPT_CONDITIONAL_SKILL_ALLOWLIST, PPT_PIPELINE_MCP_ALLOWLIST, PPT_PIPELINE_SKILL_ALLOWLIST, RECOMMENDED_DESIGN_REFERENCES, RECOMMENDED_MCP_SERVERS, RECOMMENDED_SKILLS, RESERVED_CODEX_PLUGIN_SKILL_NAMES, chatCaptureIntakeText, context7ConfigToml, getdesignReferencePolicyText, imageUxReviewPipelinePolicyText, outcomeRubricPolicyText, pptPipelineAllowlistPolicyText, speedLanePolicyText, stackCurrentDocsPolicyText, triwikiContextTracking, triwikiContextTrackingText, triwikiStagePolicyText } from './routes.mjs';
11
11
  import { SKILL_DREAM_POLICY, skillDreamPolicyText } from './skill-forge.mjs';
12
12
 
13
13
  const REFLECTION_MEMORY_PATH = '.sneakoscope/memory/q2_facts/post-route-reflection.md';
@@ -750,7 +750,6 @@ export async function installSkills(root) {
750
750
  'from-chat-img': `---\nname: from-chat-img\ndescription: Explicit $From-Chat-IMG Team alias for chat screenshot plus attachment analysis.\n---\n\nUse only for From-Chat-IMG/$From-Chat-IMG. It enters the normal Team pipeline. Treat uploads as chat screenshot plus originals. Use Codex Computer Use visual inspection when available, list requirements first, match regions to attachments with confidence, write ${FROM_CHAT_IMG_COVERAGE_ARTIFACT}, ${FROM_CHAT_IMG_CHECKLIST_ARTIFACT}, ${FROM_CHAT_IMG_TEMP_TRIWIKI_ARTIFACT}, and ${FROM_CHAT_IMG_QA_LOOP_ARTIFACT}, then continue Team gates, review, reflection, and Honest Mode. ${CODEX_COMPUTER_USE_ONLY_POLICY} The ledger must account for every visible customer request, screenshot image region, and separate attachment; ${FROM_CHAT_IMG_CHECKLIST_ARTIFACT} must have a checked item for each request, image-region/attachment match, work item, scoped QA-LOOP, and verification step; ${FROM_CHAT_IMG_TEMP_TRIWIKI_ARTIFACT} stores temporary TriWiki-backed session context with expires_after_sessions=${FROM_CHAT_IMG_TEMP_TRIWIKI_SESSIONS}. ${FROM_CHAT_IMG_QA_LOOP_ARTIFACT} must prove QA-LOOP ran over the exact customer-request work-order range after implementation, with every work item covered, post-fix verification complete, and zero unresolved findings. team-gate.json cannot pass From-Chat-IMG completion until unresolved_items is empty, every checklist box is checked, and scoped_qa_loop_completed=true.\n`,
751
751
  'qa-loop': `---\nname: qa-loop\ndescription: $QA-LOOP dogfoods UI/API as human proxy with safety gates, Codex Computer Use-only UI evidence, safe fixes, rechecks, and a QA report.\n---\n\nUse only $QA-LOOP. Infer scope, target, mutation policy, and login boundary from the prompt plus TriWiki/current-code defaults; do not surface a prequestion sheet. Credentials are runtime-only; never save secrets. UI-level E2E needs official Codex Computer Use evidence or must be marked unverified; Chrome MCP, Browser Use, Playwright, Selenium, Puppeteer, and other browser automation do not satisfy UI/browser verification. Deployed targets are read-only; destructive removal is forbidden. After answer/run, dogfood real flows, apply safe contract-allowed code/test/docs fixes, recheck, and do not pass qa-gate.json with unresolved findings or without post_fix_verification_complete. Finish qa-ledger, date/version report, gate, completion summary, and Honest Mode.\n`,
752
752
  'ppt': `---\nname: ppt\ndescription: $PPT information-first HTML/PDF presentation pipeline with inferred STP, audience, pain-point, format, research, design-system, and verification contract.\n---\n\nUse only when the user invokes $PPT or asks to create a presentation, deck, slides, pitch deck, proposal deck, HTML presentation, or PDF presentation artifact. Before artifact work, auto-seal presentation-specific answers from prompt, TriWiki/current-code defaults, and conservative policy: delivery context, target audience profile including role/average age/job/industry/topic familiarity/decision power, STP strategy, decision context and objections, and 3+ pain-point to solution mappings with expected aha moments. Do not surface a prequestion sheet. Presentation design must be simple, restrained, and information-first: avoid over-designed decoration, ornamental gradients, nested cards, and effects that compete with the message. Design detail should be embedded through typography hierarchy, spacing, alignment, thin rules, source clarity, and subtle accents. ${pptPipelineAllowlistPolicyText()} Use design.md as the only design decision SSOT. If design.md is missing, use docs/Design-Sys-Prompt.md plus getdesign-reference and curated DESIGN.md examples from ${AWESOME_DESIGN_MD_REFERENCE.url} only as source inputs, then fuse them into route-local PPT style tokens with a recorded design_ssot instead of treating references as parallel authorities. If generated image assets or slide visual critique are needed, use Codex App $imagegen/gpt-image-2 only when that asset/review need is explicitly sealed in the $PPT contract; direct API fallback, placeholder files, and prose-only substitutes do not satisfy the route gate. ${CODEX_IMAGEGEN_REQUIRED_POLICY} Use web or Context7 evidence only when external facts/libraries/current docs are required by the PPT contract, record verified claims in ppt-fact-ledger.json, record generated image asset plans/results/blockers in ppt-image-asset-ledger.json, then create the PDF plus editable source HTML under source-html/, keep independent strategy/render/file-write phases parallel where inputs allow, record ppt-parallel-report.json, run the bounded ppt-review-policy/ppt-review-ledger/ppt-iteration-report loop, and verify readability, overlap, format fit, source coverage, export state, unsupported-claim status, image-asset completion, review-loop termination, and temporary build files cleanup. Finish with reflection and Honest Mode.\n`,
753
- 'computer-use': `---\nname: computer-use\ndescription: Maximum-speed $Computer-Use/$CU lane for Codex Computer Use UI/browser/visual tasks.\n---\n\nUse only when the user invokes $Computer-Use/$CU or asks for a Computer Use-specific fast lane. Skip Team debate, QA-LOOP clarification, upfront TriWiki refresh, Context7, subagents, and reflection unless explicitly requested. Infer the smallest target, use Codex Computer Use directly, and never substitute Playwright, Chrome MCP, Browser Use, Selenium, Puppeteer, or other browser automation for UI/browser evidence. If Computer Use is unavailable, mark UI/browser evidence unverified and stop with the blocker. At the end only, refresh or pack TriWiki, validate it, then provide a concise completion summary plus Honest Mode.\n`,
754
753
  'computer-use-fast': `---\nname: computer-use-fast\ndescription: Alias for the maximum-speed $Computer-Use/$CU Codex Computer Use lane.\n---\n\nUse the same rules as computer-use: skip Team debate, QA-LOOP clarification, upfront TriWiki refresh, Context7, subagents, and reflection unless explicitly requested. Use Codex Computer Use directly; never substitute Playwright, Chrome MCP, Browser Use, Selenium, Puppeteer, or other browser automation for UI/browser evidence. At the end only, refresh/pack TriWiki, validate it, then provide a concise completion summary plus Honest Mode.\n`,
755
754
  'cu': `---\nname: cu\ndescription: Short alias for the maximum-speed $Computer-Use Codex Computer Use lane.\n---\n\nUse the same rules as computer-use. This is a speed lane for focused UI/browser/visual tasks that require Codex Computer Use evidence, with TriWiki refresh/validate and Honest Mode deferred to final closeout.\n`,
756
755
  'goal': `---\nname: goal\ndescription: Fast $Goal/$goal bridge overlay for Codex native persisted /goal workflows.\n---\n\nUse when the user invokes $Goal/$goal or asks to persist a workflow with Codex native /goal continuation. Prepare with sks goal create or the $Goal route, write only the lightweight bridge artifacts, then use native Codex /goal create, pause, resume, and clear controls where available. Goal does not replace Team, QA, DB, or other SKS execution routes; continue implementation through the selected route and use Context7 only when external API/library docs are involved. Do not recreate the old no-question loop.\n`,
@@ -796,11 +795,12 @@ export async function installSkills(root) {
796
795
  }
797
796
  const skillNames = Object.keys(skills);
798
797
  const removedStaleGeneratedSkills = await removeStaleGeneratedSkillsFromManifest(root, skillNames);
798
+ const removedPluginSkillCollisions = await removeGeneratedPluginSkillCollisions(root);
799
799
  await writeGeneratedSkillManifest(root, skillNames);
800
800
  return {
801
801
  installed_skills: skillNames,
802
802
  generated_files: generatedSkillFiles(skillNames),
803
- removed_stale_generated_skills: removedStaleGeneratedSkills,
803
+ removed_stale_generated_skills: [...removedStaleGeneratedSkills, ...removedPluginSkillCollisions].sort(),
804
804
  removed_agent_skill_aliases: await removeGeneratedAgentSkillAliases(root, skillNames),
805
805
  removed_codex_skill_mirrors: await removeGeneratedCodexSkillMirrors(root, skillNames)
806
806
  };
@@ -846,8 +846,29 @@ async function removeStaleGeneratedSkillsFromManifest(root, skillNames) {
846
846
  return removed.sort();
847
847
  }
848
848
 
849
+ async function removeGeneratedPluginSkillCollisions(root) {
850
+ const removed = [];
851
+ for (const name of RESERVED_CODEX_PLUGIN_SKILL_NAMES) {
852
+ const dir = path.join(root, '.agents', 'skills', name);
853
+ const skillPath = path.join(dir, 'SKILL.md');
854
+ const text = await readText(skillPath, null);
855
+ if (!isGeneratedSksPluginCollisionSkill(text, name)) continue;
856
+ await fsp.rm(dir, { recursive: true, force: true });
857
+ removed.push(path.relative(root, dir));
858
+ }
859
+ return removed.sort();
860
+ }
861
+
862
+ function isGeneratedSksPluginCollisionSkill(text, name) {
863
+ if (typeof text !== 'string') return false;
864
+ const s = String(text);
865
+ if (!new RegExp(`^name:\\s*${escapeRegExp(name)}\\s*$`, 'm').test(s)) return false;
866
+ if (/\bnot generated by SKS\b/i.test(s)) return false;
867
+ return /Maximum-speed \$Computer-Use\/\$CU lane|Codex App pipeline activation:|Sneakoscope generated|Dollar-command route generated by SKS/i.test(s);
868
+ }
869
+
849
870
  function enrichSkillContent(name, content) {
850
- if (!['sks', 'answer', 'wiki', 'team', 'qa-loop', 'ppt', 'image-ux-review', 'ux-review', 'visual-review', 'ui-ux-review', 'computer-use', 'computer-use-fast', 'cu', 'goal', 'research', 'autoresearch', 'db', 'gx', 'reflection', 'prompt-pipeline', 'pipeline-runner', 'context7-docs', 'turbo-context-pack', 'hproof-evidence-bind'].includes(name)) return content;
871
+ if (!['sks', 'answer', 'wiki', 'team', 'qa-loop', 'ppt', 'image-ux-review', 'ux-review', 'visual-review', 'ui-ux-review', 'computer-use-fast', 'cu', 'goal', 'research', 'autoresearch', 'db', 'gx', 'reflection', 'prompt-pipeline', 'pipeline-runner', 'context7-docs', 'turbo-context-pack', 'hproof-evidence-bind'].includes(name)) return content;
851
872
  const text = String(content || '').trimEnd();
852
873
  const activation = pipelineActivationText(name);
853
874
  if (text.includes('TriWiki context-tracking SSOT')) {
@@ -865,7 +886,7 @@ Context tracking:
865
886
  }
866
887
 
867
888
  function pipelineActivationText(name) {
868
- const stateful = new Set(['sks', 'team', 'qa-loop', 'ppt', 'image-ux-review', 'ux-review', 'visual-review', 'ui-ux-review', 'computer-use', 'computer-use-fast', 'cu', 'goal', 'research', 'autoresearch', 'db', 'gx', 'prompt-pipeline', 'pipeline-runner']);
889
+ const stateful = new Set(['sks', 'team', 'qa-loop', 'ppt', 'image-ux-review', 'ux-review', 'visual-review', 'ui-ux-review', 'computer-use-fast', 'cu', 'goal', 'research', 'autoresearch', 'db', 'gx', 'prompt-pipeline', 'pipeline-runner']);
869
890
  if (!stateful.has(name)) return '';
870
891
  return `Codex App pipeline activation:
871
892
  - If the SKS UserPromptSubmit hook already injected route context, follow that context.
@@ -875,7 +896,7 @@ function pipelineActivationText(name) {
875
896
  }
876
897
 
877
898
  async function writeSkillMetadata(dir, name) {
878
- const effort = ['computer-use', 'computer-use-fast', 'cu'].includes(name)
899
+ const effort = ['computer-use-fast', 'cu'].includes(name)
879
900
  ? 'low'
880
901
  : ['research', 'autoresearch', 'research-discovery', 'autoresearch-loop', 'from-chat-img'].includes(name)
881
902
  ? 'xhigh'
@@ -14,6 +14,7 @@ export const CODEX_APP_IMAGE_GENERATION_DOC_URL = 'https://developers.openai.com
14
14
  export const OPENAI_IMAGE_GENERATION_DOC_URL = 'https://developers.openai.com/api/docs/guides/image-generation';
15
15
  export const CODEX_COMPUTER_USE_ONLY_POLICY = 'Pipeline UI/browser verification and visual inspection must use Codex Computer Use only. Do not use Playwright, Chrome MCP, Browser Use, Selenium, Puppeteer, or any other browser automation substitute; if Codex Computer Use is unavailable, mark the UI/browser evidence unverified instead of substituting another tool. In Codex App prompts, invoke @Computer or @AppName in a new thread when live Computer Use tools are needed; SKS hooks and skills can require the policy but cannot attach missing host tools to an already-started turn.';
16
16
  export const CODEX_IMAGEGEN_REQUIRED_POLICY = 'Pipeline image generation, raster asset creation/editing, and generated image-review evidence must use real Codex App imagegen/$imagegen with gpt-image-2 when that evidence is required. Do not substitute placeholder SVG/HTML/CSS, prose-only critique, stock-like stand-ins, manually fabricated files, or missing-output ledgers for requested/generated raster assets or required generated review images. If imagegen/gpt-image-2 is unavailable, record the blocker and mark the image asset or review evidence unverified instead of passing the gate. In Codex App prompts, invoke $imagegen when live image generation is needed; SKS hooks and skills can require the policy but cannot attach missing host image-generation tools to an already-started turn.';
17
+ export const RESERVED_CODEX_PLUGIN_SKILL_NAMES = Object.freeze(['computer-use', 'browser', 'browser-use']);
17
18
  export const FORBIDDEN_BROWSER_AUTOMATION_RE = /\b(playwright|chrome\s+mcp|browser\s+use|selenium|puppeteer)\b/i;
18
19
 
19
20
  export function evidenceMentionsForbiddenBrowserAutomation(value, seen = new Set()) {
@@ -115,7 +116,6 @@ export const RECOMMENDED_SKILLS = [
115
116
  'getdesign-reference',
116
117
  'imagegen',
117
118
  'image-ux-review',
118
- 'computer-use',
119
119
  'computer-use-fast',
120
120
  'db-safety-guard',
121
121
  REFLECTION_SKILL_NAME,
@@ -323,7 +323,7 @@ export const ROUTES = [
323
323
  mode: 'IMAGE_UX_REVIEW',
324
324
  route: 'image-generation UI/UX review loop',
325
325
  description: 'Review UI/UX through the imagegen/gpt-image-2 visual critique loop: source screenshots become generated annotated review images, those images become issue ledgers, then fixes are rechecked.',
326
- requiredSkills: ['image-ux-review', 'imagegen', 'computer-use', 'pipeline-runner', REFLECTION_SKILL_NAME, 'honest-mode'],
326
+ requiredSkills: ['image-ux-review', 'imagegen', 'cu', 'pipeline-runner', REFLECTION_SKILL_NAME, 'honest-mode'],
327
327
  dollarAliases: ['$UX-Review'],
328
328
  appSkillAliases: ['ux-review', 'visual-review', 'ui-ux-review'],
329
329
  lifecycle: ['target_and_capture_inventory', 'source_screenshots', 'gpt_image_2_annotated_review_image', 'generated_image_text_extraction', 'issue_ledger', 'optional_safe_fixes', 'changed_screen_recheck', 'post_route_reflection', 'honest_mode'],
@@ -339,7 +339,7 @@ export const ROUTES = [
339
339
  mode: 'COMPUTER_USE',
340
340
  route: 'Computer Use fast lane',
341
341
  description: 'Maximum-speed Codex Computer Use lane for UI/browser/visual tasks: skip Team debate and upfront TriWiki loops, run only focused Computer Use steps, then finish with evidence, TriWiki refresh/validate, and Honest Mode.',
342
- requiredSkills: ['computer-use', 'honest-mode'],
342
+ requiredSkills: ['cu', 'honest-mode'],
343
343
  dollarAliases: ['$CU'],
344
344
  appSkillAliases: ['computer-use-fast', 'cu'],
345
345
  lifecycle: ['fast_intake', 'focused_computer_use_steps', 'evidence_summary', 'final_triwiki_refresh_validate', 'honest_mode'],
@@ -467,13 +467,17 @@ export const DOLLAR_COMMANDS = ROUTES.flatMap(({ command, route, description, do
467
467
  { command, route, description },
468
468
  ...dollarAliases.map((alias) => ({ command: alias, route, description }))
469
469
  ]);
470
- export const DOLLAR_SKILL_NAMES = ROUTES.flatMap((route) => [
471
- dollarSkillName(route.command),
472
- ...(route.appSkillAliases || [])
473
- ]);
470
+ export function routeAppSkillNames(route) {
471
+ const canonical = dollarSkillName(route.command);
472
+ return [
473
+ ...(RESERVED_CODEX_PLUGIN_SKILL_NAMES.includes(canonical) ? [] : [canonical]),
474
+ ...(route.appSkillAliases || [])
475
+ ];
476
+ }
477
+
478
+ export const DOLLAR_SKILL_NAMES = ROUTES.flatMap((route) => routeAppSkillNames(route));
474
479
  export const DOLLAR_COMMAND_ALIASES = ROUTES.flatMap((route) => [
475
- { canonical: route.command, app_skill: `$${dollarSkillName(route.command)}` },
476
- ...(route.appSkillAliases || []).map((alias) => ({ canonical: route.command, app_skill: `$${alias}` }))
480
+ ...routeAppSkillNames(route).map((alias) => ({ canonical: route.command, app_skill: `$${alias}` }))
477
481
  ]);
478
482
 
479
483
  export const COMMAND_CATALOG = [
@@ -34,7 +34,7 @@ const PROTECTED_SKILL_NAMES = new Set([
34
34
  ]);
35
35
 
36
36
  const MERGE_GROUPS = [
37
- { id: 'computer-use-aliases', skills: ['computer-use', 'computer-use-fast', 'cu'], action: 'keep_one_canonical_and_alias_the_rest_when_user_approves' },
37
+ { id: 'computer-use-aliases', skills: ['computer-use-fast', 'cu'], action: 'keep_one_canonical_and_alias_the_rest_when_user_approves; reserve computer-use for the first-party Codex plugin' },
38
38
  { id: 'research-loop-family', skills: ['research', 'research-discovery', 'autoresearch', 'autoresearch-loop'], action: 'compress_overlap_without_removing_distinct_route_semantics' }
39
39
  ];
40
40
 
@@ -368,7 +368,7 @@ export function formatTmuxBanner(status = null) {
368
368
  'CLI-first runtime:',
369
369
  ' sks open or attach the default tmux Codex CLI session',
370
370
  ' sks tmux open open or attach a tmux Codex CLI session with explicit flags',
371
- ' sks --mad open one-shot MAD full-access auto-review tmux cockpit',
371
+ ' sks --mad open one-shot MAD full-access auto-review tmux session',
372
372
  ' sks team "task" prepare Team mission and open the tmux multi-pane live view',
373
373
  '',
374
374
  'Useful terminal commands:',
@@ -506,36 +506,13 @@ export async function launchMadTmuxUi(args = [], opts = {}) {
506
506
  if (args.includes('--status-only')) return { plan };
507
507
  const missionId = opts.missionId || opts.madMissionId || 'latest';
508
508
  const mainCommand = codexLaunchCommand(plan.root, plan.codex.bin, plan.codexArgs);
509
- const statusCommand = [
510
- terminalTitleCommand('mad: permission gate'),
511
- `cd ${shellEscape(plan.root)}`,
512
- 'while :; do clear',
513
- `printf '\\033[1;35mSKS MAD permission gate\\033[0m\\nMission: %s\\n\\n' ${shellEscape(missionId)}`,
514
- `node ${shellEscape(path.join(packageRoot(), 'bin', 'sks.mjs'))} pipeline status ${shellEscape(missionId)} || true`,
515
- 'printf "\\nRefreshes every 3s. Cleanup when done by closing the MAD gate.\\n"',
516
- 'sleep 3',
517
- 'done'
518
- ].join('; ');
519
- const helpCommand = [
520
- terminalTitleCommand('mad: live guide'),
521
- 'clear',
522
- colorizedLaneBannerCommand(['SKS MAD tmux cockpit', 'Panes: Codex CLI | permission gate | live guide', 'Guard: catastrophic DB wipe/all-row/project-management operations remain blocked', ''], 'magenta'),
523
- `cd ${shellEscape(plan.root)}`,
524
- `printf 'Attach: tmux attach-session -t %s\\n' ${shellEscape(plan.session)}`,
525
- `printf 'Mission: %s\\n\\n' ${shellEscape(missionId)}`,
526
- `printf 'Commands:\\n sks pipeline status %s\\n sks db scan\\n sks doctor\\n\\n' ${shellEscape(missionId)}`,
527
- 'printf "This pane stays open so the tmux layout is visibly multi-pane.\\n"',
528
- 'while :; do sleep 3600; done'
529
- ].join('; ');
530
509
  const panes = [
531
- { cwd: plan.root, command: mainCommand, focused: true, role: 'codex', title: 'Codex CLI' },
532
- { cwd: plan.root, command: statusCommand, role: 'mad_gate', title: 'MAD gate', vertical: false },
533
- { cwd: plan.root, command: helpCommand, role: 'mad_guide', title: 'MAD guide', vertical: true }
510
+ { cwd: plan.root, command: mainCommand, focused: true, role: 'codex', title: 'Codex CLI' }
534
511
  ];
535
- const created = await createTmuxSession({ ...plan, command: mainCommand }, panes, { layout: 'tiled', recreate: true });
536
- if (created.ok) await writeTmuxSessionRecord(plan.root, { session: created.session, attach_command: created.attach_command, panes: created.panes, mode: 'mad_cockpit', mission_id: missionId }).catch(() => null);
512
+ const created = await createTmuxSession({ ...plan, command: mainCommand }, panes, { recreate: true });
513
+ if (created.ok) await writeTmuxSessionRecord(plan.root, { session: created.session, attach_command: created.attach_command, panes: created.panes, mode: 'mad_session', mission_id: missionId }).catch(() => null);
537
514
  if (!args.includes('--quiet')) {
538
- console.log(`SKS MAD tmux cockpit: ${created.session || plan.session}`);
515
+ console.log(`SKS MAD tmux session: ${created.session || plan.session}`);
539
516
  if (created.ok) console.log(`tmux: opened ${created.panes.length} pane(s)`);
540
517
  else console.log(`tmux: not created (${created.stderr || 'tmux failed'})`);
541
518
  if (created.ok) console.log(`Attach: ${created.attach_command}`);
@@ -549,7 +526,7 @@ export async function launchMadTmuxUi(args = [], opts = {}) {
549
526
  process.exitCode = attached.status || 1;
550
527
  }
551
528
  }
552
- return { plan, created: Boolean(created.ok), session: created.session || plan.session, opened: created, attached, mode: 'mad_cockpit', mission_id: missionId };
529
+ return { plan, created: Boolean(created.ok), session: created.session || plan.session, opened: created, attached, mode: 'mad_session', mission_id: missionId };
553
530
  }
554
531
 
555
532
  function printTmuxLaunchBlocked(plan, opts = {}) {