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 +6 -4
- package/package.json +1 -1
- package/src/cli/install-helpers.mjs +5 -1
- package/src/cli/main.mjs +66 -12
- package/src/core/fsx.mjs +1 -1
- package/src/core/hooks-runtime.mjs +2 -2
- package/src/core/init.mjs +27 -6
- package/src/core/routes.mjs +13 -9
- package/src/core/skill-forge.mjs +1 -1
- package/src/core/tmux-ui.mjs +6 -29
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
|
|
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
|
|
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
|
|
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.
|
|
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')
|
|
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)
|
|
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
|
|
2112
|
-
|
|
2113
|
-
|
|
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
|
|
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: ${
|
|
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 !==
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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'
|
package/src/core/routes.mjs
CHANGED
|
@@ -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', '
|
|
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: ['
|
|
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
|
|
471
|
-
dollarSkillName(route.command)
|
|
472
|
-
|
|
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: `$${
|
|
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 = [
|
package/src/core/skill-forge.mjs
CHANGED
|
@@ -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
|
|
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
|
|
package/src/core/tmux-ui.mjs
CHANGED
|
@@ -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
|
|
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, {
|
|
536
|
-
if (created.ok) await writeTmuxSessionRecord(plan.root, { session: created.session, attach_command: created.attach_command, panes: created.panes, mode: '
|
|
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
|
|
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: '
|
|
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 = {}) {
|