sneakoscope 0.7.59 → 0.7.62
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 +2 -2
- package/package.json +5 -2
- package/src/cli/install-helpers.mjs +17 -3
- package/src/cli/main.mjs +45 -20
- package/src/cli/maintenance-commands.mjs +1 -1
- package/src/core/fsx.mjs +1 -1
- package/src/core/hooks-runtime.mjs +20 -0
- package/src/core/init.mjs +7 -6
- package/src/core/pipeline.mjs +14 -2
- package/src/core/routes.mjs +26 -4
- package/src/core/skill-forge.mjs +2 -2
- package/src/core/tmux-ui.mjs +91 -108
package/README.md
CHANGED
|
@@ -241,7 +241,7 @@ By default, Team missions keep at least five QA/reviewer lanes active. Use expli
|
|
|
241
241
|
|
|
242
242
|
Team mode prepares the mission, records live events, compiles runtime tasks and worker inboxes, writes schema-backed effort/work-order/dashboard artifacts, and opens a named tmux Team session with split live lanes by default when tmux is available. Use `--no-open-tmux` for artifact-only mission creation. The default terminal output stays compact: mission id, agent count, role count, tmux status, watch command, and artifact directory. `sks team dashboard` renders the cockpit panes for mission overview, agent lanes, task DAG, QA/dogfood, artifacts/evidence, and performance.
|
|
243
243
|
|
|
244
|
-
The tmux Team launch is a live orchestration screen in one tmux window: the first pane follows `sks team watch <mission-id> --follow` as the mission overview, and neighboring split panes follow individual `sks team lane <mission-id> --agent <name> --follow` views. Pane headers show only mission, lane, phase, follow command, and cleanup command. SKS gives lanes role-specific colors, labels, and terminal titles, so scouts, planning/debate voices, executors, reviewers, and safety lanes are visually distinct while detailed evidence is mirrored into `team-transcript.jsonl`, `team-live.md`, and `team-dashboard.json`.
|
|
244
|
+
The tmux Team launch is a live orchestration screen in one tmux window: the first pane follows `sks team watch <mission-id> --follow` as the mission overview, and neighboring split panes follow individual `sks team lane <mission-id> --agent <name> --follow` views. Pane headers show only mission, lane, phase, follow command, and cleanup command. SKS sets the Team window to `window-size latest`, installs `client-attached` and `client-resized` hooks, reapplies `resize-window -A`, preserves `window-size latest`, and recalculates the tiled layout so split panes keep fitting when Warp or another terminal is resized. SKS gives lanes role-specific colors, labels, and terminal titles, so scouts, planning/debate voices, executors, reviewers, and safety lanes are visually distinct while detailed evidence is mirrored into `team-transcript.jsonl`, `team-live.md`, and `team-dashboard.json`.
|
|
245
245
|
|
|
246
246
|
Team roster and runtime artifacts now include per-agent Fast reasoning metadata. Simple bounded Team lanes can use low reasoning, tool-heavy runtime/CLI/tmux work uses medium, and knowledge, current-docs, safety, DB, release, commit, or research-heavy lanes use high or xhigh as appropriate instead of opening every scout at high.
|
|
247
247
|
|
|
@@ -283,7 +283,7 @@ SKS no longer starts from a fixed checklist such as `GOAL_PRECISE` and `ACCEPTAN
|
|
|
283
283
|
|
|
284
284
|
The design borrows two useful ideas from external planning systems without copying their route weight: Ouroboros-style ambiguity thresholds decide whether the prompt is clear enough to proceed, while Prometheus/Hyperplan-style adversarial lenses challenge framing, remove unnecessary surface, demand evidence, test integration risk, and consider a simpler alternative before Team work starts.
|
|
285
285
|
|
|
286
|
-
`sks skill-dream` keeps generated skill complexity bounded without doing a heavy evaluation on every prompt. Route use writes compact counters to `.sneakoscope/skills/dream-state.json`; after the configured
|
|
286
|
+
`sks skill-dream` keeps generated skill complexity bounded without doing a heavy evaluation on every prompt. Route use writes compact counters to `.sneakoscope/skills/dream-state.json`; after the configured 10-route-event threshold and cooldown, or when you run `sks skill-dream run`, SKS scans `.agents/skills` and writes `.sneakoscope/reports/skill-dream-latest.json` with keep, merge, prune, and improvement candidates. The report is intentionally advisory: deleting or merging skills requires explicit approval.
|
|
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
|
|
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.62",
|
|
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",
|
|
@@ -89,5 +89,8 @@
|
|
|
89
89
|
"mcp-safety",
|
|
90
90
|
"db-guardian"
|
|
91
91
|
],
|
|
92
|
-
"license": "MIT"
|
|
92
|
+
"license": "MIT",
|
|
93
|
+
"dependencies": {
|
|
94
|
+
"figlet": "^1.11.0"
|
|
95
|
+
}
|
|
93
96
|
}
|
|
@@ -307,10 +307,10 @@ export async function ensureGlobalCodexFastModeDuringInstall(opts = {}) {
|
|
|
307
307
|
export function normalizeCodexFastModeUiConfig(text = '') {
|
|
308
308
|
let next = removeLegacyTopLevelCodexModeLocks(text);
|
|
309
309
|
next = removeTomlTableKey(next, 'notice', 'fast_default_opt_out');
|
|
310
|
-
next = removeTomlTableKey(next, 'features', '
|
|
310
|
+
next = removeTomlTableKey(next, 'features', 'hooks');
|
|
311
311
|
next = upsertTopLevelTomlString(next, 'model', 'gpt-5.5');
|
|
312
312
|
next = upsertTopLevelTomlString(next, 'service_tier', 'fast');
|
|
313
|
-
next = upsertTomlTableKey(next, 'features', '
|
|
313
|
+
next = upsertTomlTableKey(next, 'features', 'codex_hooks = true');
|
|
314
314
|
next = upsertTomlTableKey(next, 'features', 'fast_mode = true');
|
|
315
315
|
next = upsertTomlTableKey(next, 'features', 'fast_mode_ui = true');
|
|
316
316
|
next = upsertTomlTableKey(next, 'user.fast_mode', 'visible = true');
|
|
@@ -993,7 +993,7 @@ export async function selftestCodexLb(tmp) {
|
|
|
993
993
|
if (codexLbNotConfigured.code !== 0 || String(codexLbNotConfigured.stdout || '').includes('codex-lb auth:')) throw new Error('selftest failed: postinstall should stay quiet when codex-lb is not configured');
|
|
994
994
|
const codexLbStatusText = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'codex-lb', 'status'], { cwd: tmp, env: codexLbEnvForSelftest, timeoutMs: 15000, maxOutputBytes: 64 * 1024 });
|
|
995
995
|
if (!String(codexLbStatusText.stdout || '').includes('Repair auth: sks codex-lb repair')) throw new Error('selftest failed: codex-lb status did not advertise repair command');
|
|
996
|
-
if (!/^model = "gpt-5\.5"/m.test(codexLbConfig) || !codexLbConfig.includes('service_tier = "fast"') || !codexLbConfig.includes('
|
|
996
|
+
if (!/^model = "gpt-5\.5"/m.test(codexLbConfig) || !codexLbConfig.includes('service_tier = "fast"') || !codexLbConfig.includes('codex_hooks = true') || hasLegacyHooksFeatureFlag(codexLbConfig) || !codexLbConfig.includes('fast_mode = true') || !codexLbConfig.includes('fast_mode_ui = true') || !codexLbConfig.includes('[user.fast_mode]') || !codexLbConfig.includes('visible = true') || !codexLbConfig.includes('enabled = true') || !codexLbConfig.includes('default_profile = "sks-fast-high"') || !/\[profiles\.sks-fast-high\][\s\S]*?service_tier = "fast"/.test(codexLbConfig) || codexLbConfig.includes('fast_default_opt_out = true') || hasTopLevelCodexModeLock(codexLbConfig)) throw new Error('selftest failed: codex-lb setup did not preserve Codex App Fast mode defaults, force GPT-5.5, or migrate the hooks feature flag');
|
|
997
997
|
const codexLbLaunch = codexLaunchCommand(tmp, 'codex', []);
|
|
998
998
|
if (!codexLbLaunch.includes('sks-codex-lb.env')) throw new Error('selftest failed: tmux launch command does not source codex-lb env file');
|
|
999
999
|
if (!codexLbLaunch.includes("'--model' 'gpt-5.5'")) throw new Error('selftest failed: tmux launch command without args did not force GPT-5.5');
|
|
@@ -1006,3 +1006,17 @@ export async function selftestCodexLb(tmp) {
|
|
|
1006
1006
|
function hasTopLevelCodexModeLock(text = '') {
|
|
1007
1007
|
return /(^|\n)\s*model\s*=\s*"codex-lb"\s*(\n|$)/.test(text) || /(^|\n)\s*model_provider\s*=\s*"openai"\s*(\n|$)/.test(text);
|
|
1008
1008
|
}
|
|
1009
|
+
|
|
1010
|
+
function hasLegacyHooksFeatureFlag(text = '') {
|
|
1011
|
+
const lines = String(text || '').split('\n');
|
|
1012
|
+
const start = lines.findIndex((line) => line.trim() === '[features]');
|
|
1013
|
+
if (start === -1) return false;
|
|
1014
|
+
let end = lines.length;
|
|
1015
|
+
for (let i = start + 1; i < lines.length; i += 1) {
|
|
1016
|
+
if (/^\s*\[.+\]\s*$/.test(lines[i])) {
|
|
1017
|
+
end = i;
|
|
1018
|
+
break;
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
return lines.slice(start + 1, end).some((line) => /^\s*hooks\s*=/.test(line));
|
|
1022
|
+
}
|
package/src/cli/main.mjs
CHANGED
|
@@ -75,7 +75,7 @@ import { GOAL_WORKFLOW_ARTIFACT } from '../core/goal-workflow.mjs';
|
|
|
75
75
|
import { CODEX_APP_DOCS_URL, codexAppIntegrationStatus, formatCodexAppStatus } from '../core/codex-app.mjs';
|
|
76
76
|
import { codexAppRemoteControlCommand } from './codex-app-command.mjs';
|
|
77
77
|
import { OPENCLAW_SKILL_NAME, installOpenClawSkill } from '../core/openclaw.mjs';
|
|
78
|
-
import { buildTmuxLaunchPlan, buildTmuxOpenArgs, codexLaunchCommand, createTmuxSession, defaultCodexLaunchArgs, isTmuxShellSession, runTmuxLaunchPlanSyntaxCheck, shouldAutoAttachTmux, tmuxReadiness, tmuxStatusKind, defaultTmuxSessionName, formatTmuxBanner, launchMadTmuxUi, launchTmuxTeamView, launchTmuxUi, platformTmuxInstallHint, runTmuxStatus, sanitizeTmuxSessionName, teamLaneStyle } from '../core/tmux-ui.mjs';
|
|
78
|
+
import { buildTmuxLaunchPlan, buildTmuxOpenArgs, codexLaunchCommand, createTmuxSession, defaultCodexLaunchArgs, isTmuxShellSession, runTmuxLaunchPlanSyntaxCheck, shouldAutoAttachTmux, sksAsciiLogo, tmuxReadiness, tmuxStatusKind, defaultTmuxSessionName, formatTmuxBanner, launchMadTmuxUi, launchTmuxTeamView, launchTmuxUi, platformTmuxInstallHint, runTmuxStatus, sanitizeTmuxSessionName, teamLaneStyle } from '../core/tmux-ui.mjs';
|
|
79
79
|
import { autoReviewProfileName, autoReviewStatus, autoReviewSummary, enableAutoReview, disableAutoReview, enableMadHighProfile, madHighProfileName } from '../core/auto-review.mjs';
|
|
80
80
|
import { context7Command } from './context7-command.mjs';
|
|
81
81
|
import { askPostinstallQuestion, checkContext7, checkRequiredSkills, codexLbStatus, configureCodexLb, ensureCodexCliTool, ensureGlobalCodexSkillsDuringInstall, ensureProjectContext7Config, ensureRelatedCliTools, ensureSksCommandDuringInstall, ensureTmuxCliTool, globalCodexSkillsRoot, maybePromptCodexLbSetupForLaunch, maybePromptCodexUpdateForLaunch, postinstall, postinstallBootstrapDecision, repairCodexLbAuth, selftestCodexLb, shouldAutoApproveInstall } from './install-helpers.mjs';
|
|
@@ -156,8 +156,7 @@ function codexLbImmediateLaunchOpts(args = [], lb = {}, opts = {}) {
|
|
|
156
156
|
function help(args = []) {
|
|
157
157
|
const topic = args[0];
|
|
158
158
|
if (topic) return usage([topic]);
|
|
159
|
-
console.log(
|
|
160
|
-
Sneakoscope Codex
|
|
159
|
+
console.log(`${sksAsciiLogo()}
|
|
161
160
|
|
|
162
161
|
Usage:
|
|
163
162
|
sks
|
|
@@ -272,7 +271,7 @@ async function wizard(args = []) {
|
|
|
272
271
|
if (!shouldShowWizard() && !flag(args, '--force')) return help();
|
|
273
272
|
const rl = readline.createInterface({ input, output });
|
|
274
273
|
try {
|
|
275
|
-
console.log(
|
|
274
|
+
console.log(`${sksAsciiLogo()}\nSetup UI\n`);
|
|
276
275
|
const currentPackage = await effectivePackageVersion();
|
|
277
276
|
console.log(`Current package: ${currentPackage}`);
|
|
278
277
|
const latest = await npmPackageVersion('sneakoscope');
|
|
@@ -340,7 +339,7 @@ async function updateCheck(args = []) {
|
|
|
340
339
|
error: latest.error || null
|
|
341
340
|
};
|
|
342
341
|
if (flag(args, '--json')) return console.log(JSON.stringify(result, null, 2));
|
|
343
|
-
console.log(
|
|
342
|
+
console.log(`${sksAsciiLogo()}\nUpdate Check`);
|
|
344
343
|
console.log(`Current: ${result.current}`);
|
|
345
344
|
console.log(`Latest: ${result.latest || 'unknown'}`);
|
|
346
345
|
console.log(`Update: ${result.update_available ? 'available' : 'not needed'}`);
|
|
@@ -352,7 +351,7 @@ const DOLLAR_DEFAULT_PIPELINE_TEXT = 'Default pipeline: direct answers -> $Answe
|
|
|
352
351
|
|
|
353
352
|
function commands(args = []) {
|
|
354
353
|
if (flag(args, '--json')) return console.log(JSON.stringify({ aliases: ['sks', 'sneakoscope'], dollar_commands: DOLLAR_COMMANDS, app_skill_aliases: DOLLAR_COMMAND_ALIASES, commands: COMMAND_CATALOG }, null, 2));
|
|
355
|
-
console.log(
|
|
354
|
+
console.log(`${sksAsciiLogo()}\nCommands\n`);
|
|
356
355
|
console.log('Aliases: sks, sneakoscope\n');
|
|
357
356
|
const width = Math.max(...COMMAND_CATALOG.map((c) => c.usage.length));
|
|
358
357
|
for (const c of COMMAND_CATALOG) console.log(`${c.usage.padEnd(width)} ${c.description}`);
|
|
@@ -375,7 +374,7 @@ async function rootCommand(args = []) {
|
|
|
375
374
|
using_global_root: !project
|
|
376
375
|
};
|
|
377
376
|
if (flag(args, '--json')) return console.log(JSON.stringify(result, null, 2));
|
|
378
|
-
console.log(
|
|
377
|
+
console.log(`${sksAsciiLogo()}\nRoot\n`);
|
|
379
378
|
console.log(`Mode: ${result.mode}`);
|
|
380
379
|
console.log(`Active root: ${active}`);
|
|
381
380
|
console.log(`Project: ${project || 'none'}`);
|
|
@@ -385,7 +384,7 @@ async function rootCommand(args = []) {
|
|
|
385
384
|
|
|
386
385
|
function dollarCommands(args = []) {
|
|
387
386
|
if (flag(args, '--json')) return console.log(JSON.stringify({ dollar_commands: DOLLAR_COMMANDS, app_skill_aliases: DOLLAR_COMMAND_ALIASES }, null, 2));
|
|
388
|
-
console.log(
|
|
387
|
+
console.log(`${sksAsciiLogo()}\n$ Commands\n`);
|
|
389
388
|
console.log('Use these inside Codex App or another agent prompt. Shells treat $ as variable syntax, so these are prompt commands, not terminal commands.\n');
|
|
390
389
|
console.log(formatDollarCommandsDetailed());
|
|
391
390
|
console.log(`\nCanonical Codex App picker skills: ${DOLLAR_COMMAND_ALIASES.map((x) => x.app_skill).join(', ')}`);
|
|
@@ -1406,7 +1405,8 @@ async function codexAppHelp(args = []) {
|
|
|
1406
1405
|
const status = await codexAppIntegrationStatus();
|
|
1407
1406
|
const skills = await codexAppSkillReadiness();
|
|
1408
1407
|
console.log([
|
|
1409
|
-
|
|
1408
|
+
sksAsciiLogo(), '',
|
|
1409
|
+
'Codex App', '',
|
|
1410
1410
|
formatCodexAppStatus(status), '',
|
|
1411
1411
|
`Skills: project=${skills.project.ok ? 'ok' : `missing ${skills.project.missing.length}`} global=${skills.global.ok ? 'ok' : `missing ${skills.global.missing.length}`}`, '',
|
|
1412
1412
|
'Setup:', ' sks bootstrap', ' sks deps check', ' sks codex-app check', ' sks codex-app remote-control --status', ' sks tmux check', '',
|
|
@@ -1417,7 +1417,9 @@ async function codexAppHelp(args = []) {
|
|
|
1417
1417
|
}
|
|
1418
1418
|
|
|
1419
1419
|
function aliases() {
|
|
1420
|
-
console.log(
|
|
1420
|
+
console.log(`${sksAsciiLogo()}
|
|
1421
|
+
|
|
1422
|
+
Aliases
|
|
1421
1423
|
|
|
1422
1424
|
Binary aliases:
|
|
1423
1425
|
sks
|
|
@@ -1443,7 +1445,7 @@ Examples:
|
|
|
1443
1445
|
function usage(args = []) {
|
|
1444
1446
|
const topic = String(args[0] || 'overview').toLowerCase();
|
|
1445
1447
|
const blocks = {
|
|
1446
|
-
overview: ['
|
|
1448
|
+
overview: [sksAsciiLogo(), '', 'Usage', '', 'Discover:', ' sks commands', ' sks quickstart', ' sks root', ' sks bootstrap', ' sks deps check', ' sks codex-app check', ' sks tmux check', ' sks dollar-commands', '', `Topics: ${USAGE_TOPICS}`],
|
|
1447
1449
|
install: ['Install', '', '1. Global install:', ' npm i -g sneakoscope', '', '2. Bootstrap and check dependencies:', ' sks bootstrap', ' sks deps check', '', '3. Confirm Codex App commands:', ' sks codex-app check', ' sks dollar-commands', '', '4. Optional codex-lb key setup for CLI sks runs:', ' sks codex-lb setup --host <domain> --api-key <key>', ' sks codex-lb repair', ' sks', '', 'Fallback:', ' npx -y -p sneakoscope sks root', '', 'Project:', ' npm i -D sneakoscope', ' npx sks setup --install-scope project'],
|
|
1448
1450
|
bootstrap: ['Bootstrap', '', ' sks bootstrap', ' sks setup --bootstrap', '', 'Creates project SKS files, Codex App skills/hooks/config, state/guard files, then checks Codex App, Context7, and tmux.'],
|
|
1449
1451
|
root: ['Root', '', ' sks root [--json]', '', 'Inside a project, SKS uses that project root. Outside any project marker, runtime commands use the per-user global SKS root instead of writing .sneakoscope into the current random folder.'],
|
|
@@ -1589,7 +1591,7 @@ async function setup(args) {
|
|
|
1589
1591
|
next: ['sks context7 check', 'sks selftest --mock', 'sks doctor', 'sks commands']
|
|
1590
1592
|
};
|
|
1591
1593
|
if (flag(args, '--json')) return console.log(JSON.stringify(result, null, 2));
|
|
1592
|
-
console.log(
|
|
1594
|
+
console.log(`${sksAsciiLogo()}\nSetup\n`);
|
|
1593
1595
|
console.log(`Project: ${root}`);
|
|
1594
1596
|
console.log(`Install: ${install.ok ? 'ok' : 'missing'} ${install.scope} (${install.command_prefix})`);
|
|
1595
1597
|
console.log(`CLI tools: Codex ${formatCodexCliToolStatus(cliTools.codex)}; tmux ${tmuxStatusKind(cliTools.tmux)} ${cliTools.tmux.version || cliTools.tmux.error || ''}`.trimEnd());
|
|
@@ -1713,7 +1715,7 @@ async function doctor(args) {
|
|
|
1713
1715
|
result.ready = !result.harness_conflicts.hard_block && nodeOk && Boolean(codex.bin) && install.ok && result.sneakoscope.ok && result.context7.ok && appRuntime.ok && result.runtime.tmux.ok && result.harness_guard.ok && result.versioning.ok && result.db_guard.ok && result.codex_app.ok && result.skills.ok && result.global_skills.ok;
|
|
1714
1716
|
if (result.harness_conflicts.hard_block) process.exitCode = 1;
|
|
1715
1717
|
if (flag(args, '--json')) return console.log(JSON.stringify(result, null, 2));
|
|
1716
|
-
console.log(
|
|
1718
|
+
console.log(`${sksAsciiLogo()}\nDoctor\n`);
|
|
1717
1719
|
console.log(`Node: ${nodeOk ? 'ok' : 'fail'} ${process.version}`);
|
|
1718
1720
|
console.log(`Project: ${root}`);
|
|
1719
1721
|
console.log(`Codex: ${codex.bin ? 'ok' : 'missing'} ${codex.version || ''}`);
|
|
@@ -1779,7 +1781,7 @@ async function init(args) {
|
|
|
1779
1781
|
const localOnly = flag(args, '--local-only');
|
|
1780
1782
|
const globalCommand = await globalSksCommand();
|
|
1781
1783
|
const res = await initProject(root, { force: flag(args, '--force'), installScope, globalCommand, localOnly });
|
|
1782
|
-
console.log(`Initialized
|
|
1784
|
+
console.log(`Initialized SKS in ${root}`);
|
|
1783
1785
|
console.log(`Install scope: ${installScope} (${sksCommandPrefix(installScope, { globalCommand })})`);
|
|
1784
1786
|
if (localOnly) console.log('Git mode: local-only (.git/info/exclude)');
|
|
1785
1787
|
else console.log('Git mode: shared .gitignore');
|
|
@@ -1920,6 +1922,20 @@ function hasTopLevelCodexModeLock(text = '') {
|
|
|
1920
1922
|
return (Boolean(model) && model !== 'gpt-5.5') || /^model_reasoning_effort\s*=/m.test(top);
|
|
1921
1923
|
}
|
|
1922
1924
|
|
|
1925
|
+
function hasLegacyHooksFeatureFlag(text = '') {
|
|
1926
|
+
const lines = String(text || '').split('\n');
|
|
1927
|
+
const start = lines.findIndex((line) => line.trim() === '[features]');
|
|
1928
|
+
if (start === -1) return false;
|
|
1929
|
+
let end = lines.length;
|
|
1930
|
+
for (let i = start + 1; i < lines.length; i += 1) {
|
|
1931
|
+
if (/^\s*\[.+\]\s*$/.test(lines[i])) {
|
|
1932
|
+
end = i;
|
|
1933
|
+
break;
|
|
1934
|
+
}
|
|
1935
|
+
}
|
|
1936
|
+
return lines.slice(start + 1, end).some((line) => /^\s*hooks\s*=/.test(line));
|
|
1937
|
+
}
|
|
1938
|
+
|
|
1923
1939
|
async function resolveMissionId(root, arg) { return (!arg || arg === 'latest') ? findLatestMission(root) : arg; }
|
|
1924
1940
|
function readMaxCycles(args, fallback) {
|
|
1925
1941
|
const i = args.indexOf('--max-cycles');
|
|
@@ -2460,6 +2476,13 @@ async function selftest() {
|
|
|
2460
2476
|
const camelHookGuardResult = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'hook', 'pre-tool'], { cwd: tmp, input: camelHookGuardPayload, env: { SKS_DISABLE_UPDATE_CHECK: '1' }, timeoutMs: 15000, maxOutputBytes: 64 * 1024 });
|
|
2461
2477
|
const camelHookGuardJson = JSON.parse(camelHookGuardResult.stdout);
|
|
2462
2478
|
if (camelHookGuardJson.decision !== 'block') throw new Error('selftest failed: hook did not block camelCase Codex tool payload');
|
|
2479
|
+
await setCurrent(tmp, { mode: 'QALOOP', phase: 'QALOOP_RUNNING_NO_QUESTIONS', route: 'QALoop', implementation_allowed: true });
|
|
2480
|
+
const codexGitPermissionResult = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'hook', 'permission-request'], { cwd: tmp, input: JSON.stringify({ cwd: tmp, command: 'git push origin dev', action: 'Codex App Git Actions Push' }), env: { SKS_DISABLE_UPDATE_CHECK: '1' }, timeoutMs: 15000, maxOutputBytes: 64 * 1024 });
|
|
2481
|
+
const codexGitPermissionJson = JSON.parse(codexGitPermissionResult.stdout);
|
|
2482
|
+
if (codexGitPermissionJson.hookSpecificOutput?.decision?.behavior === 'deny') throw new Error('selftest failed: Codex App git push permission was denied during no-question mode');
|
|
2483
|
+
const codexGitForcePermissionResult = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'hook', 'permission-request'], { cwd: tmp, input: JSON.stringify({ cwd: tmp, command: 'git push --force origin dev', action: 'Codex App Git Actions Push' }), env: { SKS_DISABLE_UPDATE_CHECK: '1' }, timeoutMs: 15000, maxOutputBytes: 64 * 1024 });
|
|
2484
|
+
const codexGitForcePermissionJson = JSON.parse(codexGitForcePermissionResult.stdout);
|
|
2485
|
+
if (codexGitForcePermissionJson.hookSpecificOutput?.decision?.behavior !== 'deny') throw new Error('selftest failed: force-push permission should stay denied during no-question mode');
|
|
2463
2486
|
if (new Set(DOLLAR_COMMANDS.map((c) => c.command)).size !== DOLLAR_COMMANDS.length) throw new Error('selftest failed: duplicate dollar commands');
|
|
2464
2487
|
if (!DOLLAR_COMMAND_ALIASES.some((alias) => alias.canonical === '$QA-LOOP' && alias.app_skill === '$qa-loop')) throw new Error('selftest failed: $QA-LOOP picker skill missing');
|
|
2465
2488
|
if (!DOLLAR_COMMAND_ALIASES.some((alias) => alias.canonical === '$Team' && alias.app_skill === '$from-chat-img')) throw new Error('selftest failed: $From-Chat-IMG picker skill missing');
|
|
@@ -2916,7 +2939,7 @@ async function selftest() {
|
|
|
2916
2939
|
if (!wikiJson.systemMessage?.includes('wiki refresh')) throw new Error('selftest failed: Wiki route missing system message');
|
|
2917
2940
|
const codexConfigText = await safeReadText(path.join(tmp, '.codex', 'config.toml'));
|
|
2918
2941
|
if (!codexConfigText.includes('multi_agent = true')) throw new Error('selftest failed: multi_agent not enabled');
|
|
2919
|
-
if (!codexConfigText.includes('
|
|
2942
|
+
if (!codexConfigText.includes('codex_hooks = true') || hasLegacyHooksFeatureFlag(codexConfigText)) throw new Error('selftest failed: Codex hooks feature flag not aligned with current codex_hooks setting');
|
|
2920
2943
|
if (!hasContext7ConfigText(codexConfigText)) throw new Error('selftest failed: Context7 MCP not configured');
|
|
2921
2944
|
if (!codexConfigText.includes('[profiles.sks-task-low]') || !codexConfigText.includes('[profiles.sks-task-medium]') || !codexConfigText.includes('[profiles.sks-logic-high]') || !codexConfigText.includes('[profiles.sks-fast-high]') || !codexConfigText.includes('[profiles.sks-research-xhigh]') || !codexConfigText.includes('[profiles.sks-mad-high]')) throw new Error('selftest failed: GPT-5.5 reasoning profiles not configured');
|
|
2922
2945
|
if (!/\[profiles\.sks-mad-high\][\s\S]*?approval_policy = "never"[\s\S]*?sandbox_mode = "danger-full-access"/.test(codexConfigText)) throw new Error('selftest failed: generated sks-mad-high profile is not full access');
|
|
@@ -2924,12 +2947,12 @@ async function selftest() {
|
|
|
2924
2947
|
if (!codexConfigText.includes('[agents.team_consensus]')) throw new Error('selftest failed: team_consensus agent not configured');
|
|
2925
2948
|
const preservedConfigTmp = tmpdir();
|
|
2926
2949
|
await ensureDir(path.join(preservedConfigTmp, '.codex'));
|
|
2927
|
-
await writeTextAtomic(path.join(preservedConfigTmp, '.codex', 'config.toml'), 'model = "gpt-5.5"\nmodel_reasoning_effort = "high"\nservice_tier = "fast"\n\n[notice]\nfast_default_opt_out = true\nkeep = true\n\n[features]\
|
|
2950
|
+
await writeTextAtomic(path.join(preservedConfigTmp, '.codex', 'config.toml'), 'model = "gpt-5.5"\nmodel_reasoning_effort = "high"\nservice_tier = "fast"\n\n[notice]\nfast_default_opt_out = true\nkeep = true\n\n[features]\nhooks = true\nfast_mode_ui = true\n\n[user.fast_mode]\nvisible = true\n');
|
|
2928
2951
|
await initProject(preservedConfigTmp, {});
|
|
2929
2952
|
const preservedConfig = await safeReadText(path.join(preservedConfigTmp, '.codex', 'config.toml'));
|
|
2930
2953
|
if (!/^model = "gpt-5\.5"/m.test(preservedConfig) || !preservedConfig.includes('service_tier = "fast"') || !preservedConfig.includes('fast_mode = true') || !preservedConfig.includes('fast_mode_ui = true') || !preservedConfig.includes('[user.fast_mode]') || !preservedConfig.includes('visible = true') || !preservedConfig.includes('enabled = true') || !preservedConfig.includes('default_profile = "sks-fast-high"') || !/\[profiles\.sks-fast-high\][\s\S]*?service_tier = "fast"/.test(preservedConfig)) throw new Error('selftest failed: Codex config merge dropped or failed to enable Fast mode defaults and GPT-5.5');
|
|
2931
2954
|
if (preservedConfig.includes('fast_default_opt_out = true') || !preservedConfig.includes('keep = true')) throw new Error('selftest failed: Codex config merge did not remove stale Fast opt-out notice while preserving other notice keys');
|
|
2932
|
-
if (!preservedConfig.includes('
|
|
2955
|
+
if (!preservedConfig.includes('codex_hooks = true') || hasLegacyHooksFeatureFlag(preservedConfig) || !preservedConfig.includes('[profiles.sks-fast-high]')) throw new Error('selftest failed: Codex config merge did not add current SKS hook settings or remove the legacy hooks flag');
|
|
2933
2956
|
if (hasTopLevelCodexModeLock(preservedConfig)) throw new Error('selftest failed: Codex config merge left top-level legacy model/reasoning locks that hide Fast mode UI');
|
|
2934
2957
|
const autoReviewHome = path.join(tmp, 'auto-review-home');
|
|
2935
2958
|
const autoReviewEnv = { HOME: autoReviewHome };
|
|
@@ -3146,7 +3169,7 @@ async function selftest() {
|
|
|
3146
3169
|
await writeJsonAtomic(path.join(teamDir, 'team-plan.json'), teamPlan);
|
|
3147
3170
|
if (teamPlan.agent_session_count !== 5) throw new Error('selftest failed: team default sessions not 5');
|
|
3148
3171
|
if (teamPlan.role_counts.executor !== 3 || teamPlan.role_counts.user !== 1 || teamPlan.role_counts.reviewer !== 5) throw new Error('selftest failed: team default role counts invalid');
|
|
3149
|
-
if (teamPlan.codex_config_required?.features?.
|
|
3172
|
+
if (teamPlan.codex_config_required?.features?.codex_hooks !== true || teamPlan.codex_config_required?.features?.hooks === true) throw new Error('selftest failed: team plan Codex config still uses legacy hooks feature flag');
|
|
3150
3173
|
if (!teamPlan.review_gate?.passed || teamPlan.review_gate.required_reviewer_lanes !== 5) throw new Error('selftest failed: team review policy gate did not pass default plan');
|
|
3151
3174
|
if (teamPlan.codex_config_required?.service_tier !== 'fast' || teamPlan.reasoning?.service_tier !== 'fast') throw new Error('selftest failed: team plan did not require Fast service tier');
|
|
3152
3175
|
if (!teamPlan.goal_continuation?.enabled || teamPlan.goal_continuation?.mode !== 'ambient_codex_native_goal_overlay') throw new Error('selftest failed: Team plan did not include ambient Goal continuation');
|
|
@@ -3218,7 +3241,7 @@ async function selftest() {
|
|
|
3218
3241
|
await ensureDir(fakeTmuxDir);
|
|
3219
3242
|
const fakeTmuxLog = path.join(fakeTmuxDir, 'tmux.log');
|
|
3220
3243
|
const fakeTmuxBin = path.join(fakeTmuxDir, 'tmux');
|
|
3221
|
-
await writeTextAtomic(fakeTmuxBin, `#!/usr/bin/env node\nconst fs = require('node:fs');\nconst log = process.env.SKS_FAKE_TMUX_LOG;\nif (log) fs.appendFileSync(log, process.argv.slice(2).join(' ') + '\\n');\nconst cmd = process.argv[2];\nif (cmd === 'has-session') process.exit(0);\nif (cmd === 'kill-session') process.exit(0);\nif (cmd === 'new-session') { console.log('%1'); process.exit(0); }\nif (cmd === 'split-window') { console.log('%2'); process.exit(0); }\nprocess.exit(0);\n`);
|
|
3244
|
+
await writeTextAtomic(fakeTmuxBin, `#!/usr/bin/env node\nconst fs = require('node:fs');\nconst log = process.env.SKS_FAKE_TMUX_LOG;\nif (log) fs.appendFileSync(log, process.argv.slice(2).join(' ') + '\\n');\nconst cmd = process.argv[2];\nif (cmd === 'has-session') process.exit(0);\nif (cmd === 'kill-session') process.exit(0);\nif (cmd === 'new-session') { console.log('%1'); process.exit(0); }\nif (cmd === 'split-window') { console.log('%2'); process.exit(0); }\nif (cmd === 'list-windows') { console.log('@1'); process.exit(0); }\nprocess.exit(0);\n`);
|
|
3222
3245
|
await fsp.chmod(fakeTmuxBin, 0o755);
|
|
3223
3246
|
const previousFakeTmuxLog = process.env.SKS_FAKE_TMUX_LOG;
|
|
3224
3247
|
process.env.SKS_FAKE_TMUX_LOG = fakeTmuxLog;
|
|
@@ -3228,6 +3251,8 @@ async function selftest() {
|
|
|
3228
3251
|
], { recreate: true });
|
|
3229
3252
|
const fakeTmuxLogText = await safeReadText(fakeTmuxLog);
|
|
3230
3253
|
if (!recreatedTmux.ok || !fakeTmuxLogText.includes('kill-session -t sks-existing-selftest') || !fakeTmuxLogText.includes('new-session') || !fakeTmuxLogText.includes('split-window')) throw new Error('selftest failed: tmux recreate did not replace stale existing session with split panes');
|
|
3254
|
+
if (!recreatedTmux.dynamic_resize?.enabled || !fakeTmuxLogText.includes('list-windows -t sks-existing-selftest -F #{window_id}') || !fakeTmuxLogText.includes('set-window-option -t @1 window-size latest') || !fakeTmuxLogText.includes('set-hook -t sks-existing-selftest client-resized') || !fakeTmuxLogText.includes('resize-window -t @1 -A')) throw new Error('selftest failed: tmux dynamic resize hooks were not installed for split panes');
|
|
3255
|
+
if (recreatedTmux.layout !== 'tiled' || Number(recreatedTmux.initial_size?.width || 0) < 120 || Number(recreatedTmux.initial_size?.height || 0) < 36) throw new Error('selftest failed: tmux dynamic resize metadata missing normalized initial size/layout');
|
|
3231
3256
|
await writeTextAtomic(fakeTmuxLog, '');
|
|
3232
3257
|
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' });
|
|
3233
3258
|
const madTmuxLogText = await safeReadText(fakeTmuxLog);
|
|
@@ -3655,6 +3680,6 @@ async function selftest() {
|
|
|
3655
3680
|
const gc = await enforceRetention(tmp, { dryRun: true });
|
|
3656
3681
|
if (!gc.report.exists) throw new Error('selftest failed: storage report');
|
|
3657
3682
|
if (!gc.actions.some((action) => action.action === 'remove_from_chat_img_temp_triwiki')) throw new Error('selftest failed: From-Chat-IMG temporary TriWiki retention action missing');
|
|
3658
|
-
console.log(
|
|
3683
|
+
console.log(`${sksAsciiLogo()}\nselftest passed.`);
|
|
3659
3684
|
console.log(`temp: ${tmp}`);
|
|
3660
3685
|
}
|
|
@@ -1778,7 +1778,7 @@ export function buildTeamPlan(id, prompt, opts = {}) {
|
|
|
1778
1778
|
reasoning: teamReasoningPolicy(prompt, roster),
|
|
1779
1779
|
codex_config_required: {
|
|
1780
1780
|
service_tier: 'fast',
|
|
1781
|
-
features: { multi_agent: true,
|
|
1781
|
+
features: { multi_agent: true, codex_hooks: true },
|
|
1782
1782
|
agents: { max_threads: 6, max_depth: 1 },
|
|
1783
1783
|
custom_agents_dir: '.codex/agents'
|
|
1784
1784
|
},
|
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.62';
|
|
9
9
|
export const DEFAULT_PROCESS_TAIL_BYTES = 256 * 1024;
|
|
10
10
|
export const DEFAULT_PROCESS_TIMEOUT_MS = 30 * 60 * 1000;
|
|
11
11
|
|
|
@@ -281,6 +281,7 @@ async function hookPermission(root, state, payload, noQuestion) {
|
|
|
281
281
|
if (clarificationGateLocked(state) && !clarificationAnswerToolAllowed(payload)) {
|
|
282
282
|
return { decision: 'deny', permissionDecision: 'deny', reason: clarificationPauseBlockReason(state) };
|
|
283
283
|
}
|
|
284
|
+
if (noQuestion && looksLikeUserGitAction(payload)) return { continue: true };
|
|
284
285
|
if (!noQuestion) return { continue: true };
|
|
285
286
|
return {
|
|
286
287
|
decision: 'deny',
|
|
@@ -289,6 +290,25 @@ async function hookPermission(root, state, payload, noQuestion) {
|
|
|
289
290
|
};
|
|
290
291
|
}
|
|
291
292
|
|
|
293
|
+
function looksLikeUserGitAction(payload = {}) {
|
|
294
|
+
const command = extractCommand(payload);
|
|
295
|
+
const haystack = [
|
|
296
|
+
command,
|
|
297
|
+
payload.action,
|
|
298
|
+
payload.intent,
|
|
299
|
+
payload.operation,
|
|
300
|
+
payload.permission,
|
|
301
|
+
payload.description,
|
|
302
|
+
payload.message,
|
|
303
|
+
payload.tool_name,
|
|
304
|
+
payload.toolName
|
|
305
|
+
].filter(Boolean).join(' ');
|
|
306
|
+
if (/\b(?:reset\s+--hard|clean\s+-[^\s]*f|checkout\s+--|restore\s+|rm\s+|push\s+--force|push\s+-[^\s]*f)\b/i.test(command)) return false;
|
|
307
|
+
if (/\bcodex\b[\s_-]*(?:app\s*)?(?:git\s*)?(?:action|commit|push|pr)\b/i.test(haystack)) return true;
|
|
308
|
+
if (!/^\s*git\s+/i.test(command)) return false;
|
|
309
|
+
return /\bgit\s+(?:status|diff|add|commit|push|branch|remote|rev-parse|log)\b/i.test(command);
|
|
310
|
+
}
|
|
311
|
+
|
|
292
312
|
function clarificationGateLocked(state = {}) {
|
|
293
313
|
if (isBlockingClarificationAwaiting(state)) return true;
|
|
294
314
|
return Boolean(
|
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, RESERVED_CODEX_PLUGIN_SKILL_NAMES, 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, SOLUTION_SCOUT_SKILL_NAME, chatCaptureIntakeText, context7ConfigToml, getdesignReferencePolicyText, imageUxReviewPipelinePolicyText, outcomeRubricPolicyText, pptPipelineAllowlistPolicyText, solutionScoutPolicyText, 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';
|
|
@@ -94,7 +94,7 @@ function isSksManagedHook(hook) {
|
|
|
94
94
|
return hook.type === 'command' && /\bhook\s+(?:user-prompt-submit|pre-tool|post-tool|permission-request|stop)\b/.test(command) && /\b(?:sks|sneakoscope|sks\.mjs)\b/.test(command);
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
-
const AGENTS_BLOCK = "\n# Sneakoscope Codex Managed Rules\n\nThis repository uses Sneakoscope Codex.\n\n## Core Rules\n\n- Codex native `/goal` workflows are the persisted continuation surface; Ralph is removed from the user-facing SKS surface.\n- Keep runtime state bounded: raw logs go to files, prompts get tails/summaries, and `sks gc` may prune stale artifacts.\n- Before substantive work, SKS checks npm for a newer package. If newer, ask update-now vs skip-for-this-conversation.\n- Versioning is managed by the SKS pre-commit hook; check `sks versioning status`. Bypass only with `SKS_DISABLE_VERSIONING=1`.\n- Installed harness files are immutable to LLM edits: `.codex/*`, `.agents/skills/`, `.codex/agents/`, `.sneakoscope/*policy*.json`, `AGENTS.md`, and `node_modules/sneakoscope`. The Sneakoscope engine source repo is the only automatic exception.\n- OMX/DCodex conflicts block setup/doctor. Show `sks conflicts prompt`; cleanup requires explicit human approval.\n- Do not stop at a plan when implementation was requested. Finish, verify, or report the hard blocker.\n- Do not create unrequested fallback implementation code. If the requested path is impossible, block with evidence instead of inventing substitute behavior.\n\n## Routes\n\n- General execution/code-changing prompts default to `$Team`: analysis scouts, TriWiki refresh/validate, read-only debate, consensus, concrete runtime task graph/inboxes, fresh executor team, minimum five-lane Team review, integration, Honest Mode.\n- `$Computer-Use` / `$CU` is the maximum-speed Codex Computer Use lane for UI/browser/visual tasks: skip Team debate and upfront TriWiki loops, use Codex Computer Use directly, then refresh/validate TriWiki and run Honest Mode at final closeout.\n- `$Goal` is a fast bridge/overlay for Codex native `/goal` create/pause/resume/clear persistence controls; implementation continues through the selected SKS execution route.\n- TriWiki recall must stay bounded. Use `sks wiki sweep` to record demote, soft-forget, archive, delete, promote-to-skill, and promote-to-rule candidates instead of injecting every old claim.\n- Team missions must keep schema-backed evidence current: `work-order-ledger.json`, `effort-decision.json`, `team-dashboard-state.json`, and route-specific visual/dogfood artifacts where applicable. Team completion requires at least five independent reviewer/QA validation lanes before integration or final, even when a prompt requests fewer reviewers. Use `sks validate-artifacts latest` before claiming those artifacts pass.\n- `$DFix` is Direct Fix: only tiny copy/config/docs/labels/spacing/translation/simple mechanical edits, bypassing the main pipeline, Team, TriWiki/TriFix/reflection recording, and persistent route state; it still uses a one-line DFix-specific Honest check before final. Broad implementation stays on `$Team`, while UI design specifics follow the relevant design/UI route rules. `$PPT` is the restrained, information-first HTML/PDF presentation route and must seal delivery context, audience profile, STP, decision context, and 3+ pain-point/solution/aha mappings before design/render work. It must avoid over-designed visuals, carry detail through hierarchy, spacing, alignment, thin rules, source clarity, and subtle accents, preserve editable source HTML under `source-html/`, record `ppt-parallel-report.json`, and clean PPT-only temporary build files before completion. `$Answer`, `$Help`, and `$Wiki` stay lightweight.\n- For code work, surface route/guard/write scopes first, split independent worker scopes when available, and keep parent-owned integration and verification.\n- Design work reads `design.md` as the only design decision SSOT. If missing, create it through `design-system-builder` from `docs/Design-Sys-Prompt.md`; getdesign.md, getdesign-reference, and curated DESIGN.md examples from https://github.com/VoltAgent/awesome-design-md are source inputs to fuse into that SSOT or route-local style tokens, not parallel design authorities. Image/logo/raster assets use `imagegen`, which must prefer official Codex App built-in image generation via `$imagegen` / `gpt-image-2` before API generation and must not be replaced by placeholder SVG/HTML/CSS, prose-only reviews, or fabricated files when generated raster evidence is required.\n- Research, AutoResearch, performance, token, accuracy, SEO/GEO, or workflow-improvement claims need experiment/eval evidence. Do not claim live model accuracy without a scored dataset.\n- Treat handwritten files above 3000 lines as split-review risks. Run `sks code-structure scan` and prefer extraction before adding substantial logic.\n- Skill dreaming stays lightweight: route use records JSON counters in `.sneakoscope/skills/dream-state.json`, and full skill inventory/recommendation runs only after the configured
|
|
97
|
+
const AGENTS_BLOCK = "\n# Sneakoscope Codex Managed Rules\n\nThis repository uses Sneakoscope Codex.\n\n## Core Rules\n\n- Codex native `/goal` workflows are the persisted continuation surface; Ralph is removed from the user-facing SKS surface.\n- Keep runtime state bounded: raw logs go to files, prompts get tails/summaries, and `sks gc` may prune stale artifacts.\n- Before substantive work, SKS checks npm for a newer package. If newer, ask update-now vs skip-for-this-conversation.\n- Versioning is managed by the SKS pre-commit hook; check `sks versioning status`. Bypass only with `SKS_DISABLE_VERSIONING=1`.\n- Installed harness files are immutable to LLM edits: `.codex/*`, `.agents/skills/`, `.codex/agents/`, `.sneakoscope/*policy*.json`, `AGENTS.md`, and `node_modules/sneakoscope`. The Sneakoscope engine source repo is the only automatic exception.\n- OMX/DCodex conflicts block setup/doctor. Show `sks conflicts prompt`; cleanup requires explicit human approval.\n- Do not stop at a plan when implementation was requested. Finish, verify, or report the hard blocker.\n- Do not create unrequested fallback implementation code. If the requested path is impossible, block with evidence instead of inventing substitute behavior.\n\n## Routes\n\n- General execution/code-changing prompts default to `$Team`: analysis scouts, TriWiki refresh/validate, read-only debate, consensus, concrete runtime task graph/inboxes, fresh executor team, minimum five-lane Team review, integration, Honest Mode.\n- `$Computer-Use` / `$CU` is the maximum-speed Codex Computer Use lane for UI/browser/visual tasks: skip Team debate and upfront TriWiki loops, use Codex Computer Use directly, then refresh/validate TriWiki and run Honest Mode at final closeout.\n- `$Goal` is a fast bridge/overlay for Codex native `/goal` create/pause/resume/clear persistence controls; implementation continues through the selected SKS execution route.\n- TriWiki recall must stay bounded. Use `sks wiki sweep` to record demote, soft-forget, archive, delete, promote-to-skill, and promote-to-rule candidates instead of injecting every old claim.\n- Team missions must keep schema-backed evidence current: `work-order-ledger.json`, `effort-decision.json`, `team-dashboard-state.json`, and route-specific visual/dogfood artifacts where applicable. Team completion requires at least five independent reviewer/QA validation lanes before integration or final, even when a prompt requests fewer reviewers. Use `sks validate-artifacts latest` before claiming those artifacts pass.\n- `$DFix` is Direct Fix: only tiny copy/config/docs/labels/spacing/translation/simple mechanical edits, bypassing the main pipeline, Team, TriWiki/TriFix/reflection recording, and persistent route state; it still uses a one-line DFix-specific Honest check before final. Broad implementation stays on `$Team`, while UI design specifics follow the relevant design/UI route rules. `$PPT` is the restrained, information-first HTML/PDF presentation route and must seal delivery context, audience profile, STP, decision context, and 3+ pain-point/solution/aha mappings before design/render work. It must avoid over-designed visuals, carry detail through hierarchy, spacing, alignment, thin rules, source clarity, and subtle accents, preserve editable source HTML under `source-html/`, record `ppt-parallel-report.json`, and clean PPT-only temporary build files before completion. `$Answer`, `$Help`, and `$Wiki` stay lightweight.\n- For code work, surface route/guard/write scopes first, split independent worker scopes when available, and keep parent-owned integration and verification.\n- Design work reads `design.md` as the only design decision SSOT. If missing, create it through `design-system-builder` from `docs/Design-Sys-Prompt.md`; getdesign.md, getdesign-reference, and curated DESIGN.md examples from https://github.com/VoltAgent/awesome-design-md are source inputs to fuse into that SSOT or route-local style tokens, not parallel design authorities. Image/logo/raster assets use `imagegen`, which must prefer official Codex App built-in image generation via `$imagegen` / `gpt-image-2` before API generation and must not be replaced by placeholder SVG/HTML/CSS, prose-only reviews, or fabricated files when generated raster evidence is required.\n- Research, AutoResearch, performance, token, accuracy, SEO/GEO, or workflow-improvement claims need experiment/eval evidence. Do not claim live model accuracy without a scored dataset.\n- Treat handwritten files above 3000 lines as split-review risks. Run `sks code-structure scan` and prefer extraction before adding substantial logic.\n- Skill dreaming stays lightweight: route use records JSON counters in `.sneakoscope/skills/dream-state.json`, and full skill inventory/recommendation runs only after the configured 10-route-event threshold and cooldown. Reports are recommendation-only; deleting or merging skills needs explicit user approval.\n\n## Evidence And Context\n\n- Context7 is required for external libraries, APIs, MCPs, package managers, SDKs, and generated docs: resolve-library-id then query-docs.\n- When tech stack, framework, package, runtime, or deployment-platform versions change, use Context7 or official vendor web docs, record current syntax/security/limit guidance as high-priority TriWiki claims, then refresh and validate before coding.\n- TriWiki is the context-tracking SSOT for long-running missions, Team handoffs, and context-pressure recovery. Read `.sneakoscope/wiki/context-pack.json` before each stage, use `attention.use_first` for compact high-trust recall, hydrate `attention.hydrate_first` from source before risky or lower-trust decisions, refresh after findings or artifact changes, and validate before handoffs/final claims.\n- Source priority: current code/tests/config, decision contract, vgraph, beta, GX render/snapshot metadata, LLM Wiki coordinate index, then model knowledge only if allowed.\n- Final response before stop: summarize what was done, what changed for the user/repo, what was verified, and what remains unverified or blocked; then run Honest Mode. Say what passed and what was not verified.\n- `$From-Chat-IMG` uses forensic visual effort, not ordinary Team effort. Completion is blocked until source inventory, visual mapping, work-order coverage, scoped dogfood/QA, and post-fix verification artifacts are present and valid.\n\n## Safety\n\n- Database access is high risk. Use read-only inspection by default; live data mutation is out of scope unless a sealed contract allows local or branch-only migration files.\n- MAD and MAD-SKS widen only explicit scoped permissions; they still do not authorize unrequested fallback implementation code.\n- Task completion requires relevant tests or justification, zero unsupported critical claims, accepted visual/wiki drift, and final evidence.\n\n## Codex App\n\nUse `.codex/SNEAKOSCOPE.md`, generated `.agents/skills`, `.codex/hooks.json`, and SKS dollar commands (`$sks`, `$team`, `$computer-use`, `$cu`, `$ppt`, `$goal`, `$dfix`, `$qa-loop`, etc.) as the app control surface.\n";
|
|
98
98
|
|
|
99
99
|
function agentsBlockText() {
|
|
100
100
|
return AGENTS_BLOCK
|
|
@@ -440,10 +440,10 @@ function installPolicy(scope, commandPrefix) {
|
|
|
440
440
|
function mergeManagedCodexConfigToml(existingContent = '') {
|
|
441
441
|
let next = removeLegacyTopLevelCodexModeLocks(String(existingContent || '').trimEnd());
|
|
442
442
|
next = removeTomlTableKey(next, 'notice', 'fast_default_opt_out');
|
|
443
|
-
next = removeTomlTableKey(next, 'features', '
|
|
443
|
+
next = removeTomlTableKey(next, 'features', 'hooks');
|
|
444
444
|
next = upsertTopLevelTomlString(next, 'model', 'gpt-5.5');
|
|
445
445
|
next = upsertTopLevelTomlString(next, 'service_tier', 'fast');
|
|
446
|
-
next = upsertTomlTableKey(next, 'features', '
|
|
446
|
+
next = upsertTomlTableKey(next, 'features', 'codex_hooks = true');
|
|
447
447
|
next = upsertTomlTableKey(next, 'features', 'multi_agent = true');
|
|
448
448
|
next = upsertTomlTableKey(next, 'features', 'fast_mode = true');
|
|
449
449
|
next = upsertTomlTableKey(next, 'features', 'fast_mode_ui = true');
|
|
@@ -746,7 +746,7 @@ export async function installSkills(root) {
|
|
|
746
746
|
'answer': `---\nname: answer\ndescription: Answer-only research route for ordinary questions that should not start implementation.\n---\n\nUse for explanations, comparisons, status, facts, source-backed research, or docs guidance. Use repo/TriWiki first for project-local facts; hydrate low-trust claims from source. Browse or use Context7 for current external package/API/framework/MCP docs. End with a concise answer summary plus Honest Mode; do not create missions, subagents, or file edits.\n`,
|
|
747
747
|
'sks': `---\nname: sks\ndescription: General Sneakoscope Codex command route for $SKS or $sks usage, setup, status, and workflow help.\n---\n\nUse local SKS commands: bootstrap, deps, commands, quickstart, codex-app, context7, guard, conflicts, reasoning, wiki, pipeline status, pipeline plan, skill-dream. Promote code-changing work to Team unless Answer/DFix/Help/Wiki/safety route fits. Surface route/guard/scope, use TriWiki, do not edit installed harness files outside this engine repo, and require human-approved conflict cleanup. ${skillDreamPolicyText()}\n`,
|
|
748
748
|
'wiki': `---\nname: wiki\ndescription: Dollar-command route for $Wiki TriWiki refresh, pack, validate, and prune commands.\n---\n\nUse for $Wiki or Korean wiki-refresh requests. Refresh/update/갱신: run sks wiki refresh, then validate .sneakoscope/wiki/context-pack.json. Pack: run sks wiki pack, then validate. Prune/clean/정리: use sks wiki refresh --prune, or sks wiki prune --dry-run for inspection. Report claims, anchors, trust, attention.use_first/hydrate_first, validation, and blockers. Do not start ambiguity-gated implementation, subagents, or unrelated work.\n`,
|
|
749
|
-
'team': `---\nname: team\ndescription: SKS Team orchestration for $Team/code work; $From-Chat-IMG is the explicit chat-image alias.\n---\n\nUse for $Team/code work. Auto-seal the route contract from prompt, TriWiki/current-code defaults, and conservative policy; do not surface a prequestion sheet. Read pipeline-plan.json or run sks pipeline plan to see the runtime lane, kept/skipped stages, and verification before implementation. Write team-roster.json; team-gate.json needs team_roster_confirmed=true. executor:N means N scouts, N debate voices, then fresh N executors. ${MIN_TEAM_REVIEW_POLICY_TEXT} After consensus, compile team-graph.json, team-runtime-tasks.json, team-decomposition-report.json, and team-inbox/ so worker handoff uses concrete runtime task ids with role/path/domain/lane hints. Refresh/validate TriWiki before debate, implementation, review, and final; consume attention.use_first and hydrate attention.hydrate_first before risky decisions. ${outcomeRubricPolicyText()} ${speedLanePolicyText()} ${skillDreamPolicyText()} Log events and use sks team message for bounded inter-agent communication in transcript/lane panes. Color-coded tmux lanes distinguish overview/scout/planning/execution/review/safety sessions in one tmux window using split panes when tmux is available. $Team/$team plus sks --mad uses the MAD-SKS permission gate module: live server work, normal DB writes, Supabase MCP writes, direct SQL, schema cleanup, and needed migrations are open for the active invocation; only catastrophic DB wipe/all-row/project-management guards remain. End with cleanup-tmux or a cleanup event so follow panes show cleanup and stop; pass team-session-cleanup.json, then reflection and Honest Mode. Parent integrates/verifies.\n\n${chatCaptureIntakeText()}\n`,
|
|
749
|
+
'team': `---\nname: team\ndescription: SKS Team orchestration for $Team/code work; $From-Chat-IMG is the explicit chat-image alias.\n---\n\nUse for $Team/code work. Auto-seal the route contract from prompt, TriWiki/current-code defaults, and conservative policy; do not surface a prequestion sheet. Read pipeline-plan.json or run sks pipeline plan to see the runtime lane, kept/skipped stages, and verification before implementation. Write team-roster.json; team-gate.json needs team_roster_confirmed=true. executor:N means N scouts, N debate voices, then fresh N executors. ${MIN_TEAM_REVIEW_POLICY_TEXT} After consensus, compile team-graph.json, team-runtime-tasks.json, team-decomposition-report.json, and team-inbox/ so worker handoff uses concrete runtime task ids with role/path/domain/lane hints. Refresh/validate TriWiki before debate, implementation, review, and final; consume attention.use_first and hydrate attention.hydrate_first before risky decisions. ${outcomeRubricPolicyText()} ${speedLanePolicyText()} ${solutionScoutPolicyText('fix this broken behavior')} ${skillDreamPolicyText()} Log events and use sks team message for bounded inter-agent communication in transcript/lane panes. Color-coded tmux lanes distinguish overview/scout/planning/execution/review/safety sessions in one tmux window using split panes when tmux is available. $Team/$team plus sks --mad uses the MAD-SKS permission gate module: live server work, normal DB writes, Supabase MCP writes, direct SQL, schema cleanup, and needed migrations are open for the active invocation; only catastrophic DB wipe/all-row/project-management guards remain. End with cleanup-tmux or a cleanup event so follow panes show cleanup and stop; pass team-session-cleanup.json, then reflection and Honest Mode. Parent integrates/verifies.\n\n${chatCaptureIntakeText()}\n`,
|
|
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`,
|
|
@@ -759,7 +759,8 @@ export async function installSkills(root) {
|
|
|
759
759
|
'mad-sks': `---\nname: mad-sks\ndescription: Explicit high-risk authorization modifier for $MAD-SKS scoped Supabase MCP DB permission widening.\n---\n\nUse only when the user explicitly invokes $MAD-SKS or top-level sks --mad. It can be combined with another route, such as $MAD-SKS $Team or $DB ... $MAD-SKS; in that case the other command remains the primary workflow and MAD-SKS is only the temporary permission grant. The widened permission applies only while the active mission gate is open, must be deactivated when the task ends, and opens live server work, Supabase MCP database writes, column/schema cleanup, direct execute SQL, migration application when required, and normal targeted DB writes. Keep only catastrophic safeguards: whole database/schema/table removal, truncate, all-row delete/update, reset, dangerous project/branch management, credential exfiltration, persistent security weakening, and unrequested fallback implementation remain blocked. Do not carry MAD-SKS permission into later prompts or routes. The permission profile is centralized in src/core/permission-gates.mjs so skill/hook/MCP-style gates share one decision function.\n`,
|
|
760
760
|
'gx': `---\nname: gx\ndescription: Dollar-command route for $GX or $gx deterministic GX visual context cartridges.\n---\n\nUse when the user invokes $GX/$gx or asks for architecture/context visualization through SKS. Prefer sks gx init, render, validate, drift, and snapshot. vgraph.json remains the source of truth.\n`,
|
|
761
761
|
'help': `---\nname: help\ndescription: Dollar-command route for $Help or $help explaining installed SKS commands and workflows.\n---\n\nUse when the user invokes $Help/$help or asks what commands exist. Prefer concise output from sks commands, sks usage <topic>, sks quickstart, sks aliases, and sks codex-app.\n`,
|
|
762
|
-
'prompt-pipeline': `---\nname: prompt-pipeline\ndescription: Default SKS prompt optimization pipeline for execution prompts; Answer and DFix bypass it.\n---\n\nClassify intent: Answer only for real questions; question-shaped implicit instructions, complaints, and mandatory-policy statements route to Team. DFix handles Direct Fix work: tiny copy/config/docs/labels/spacing/translation/simple mechanical edits; code and broad implementation default to Team unless safety/research/GX route fits. Infer goal, target, constraints, acceptance, risk, and smallest safe route from prompt, TriWiki/current-code defaults, and conservative SKS policy. Do not surface a prequestion sheet. Materialize pipeline-plan.json for the runtime lane, kept/skipped stages, no-fallback invariant, and verification; inspect with sks pipeline plan, adding --proof-field when changed files are known. Code work surfaces route/guard/scopes, materializes team-roster.json from default or explicit counts before implementation, compiles concrete Team runtime graph/inbox artifacts after consensus, and parent owns integration/tests/Context7/Honest Mode. ${outcomeRubricPolicyText()} ${speedLanePolicyText()} ${skillDreamPolicyText()}\n\n${chatCaptureIntakeText()}\n\nDesign: non-PPT UI/UX reads design.md; if missing use design-system-builder; use imagegen for image/logo/raster, and imagegen must prefer Codex App built-in image generation (${CODEX_APP_IMAGE_GENERATION_DOC_URL}) before API generation. ${CODEX_IMAGEGEN_REQUIRED_POLICY} For UI/UX review/audit requests that mention image generation, gpt-image-2, callouts, or annotated review images, route to $Image-UX-Review/$UX-Review and require generated annotated review image evidence before issue extraction; do not satisfy that route with text-only critique. For $PPT, ${pptPipelineAllowlistPolicyText()} ${getdesignReferencePolicyText()} TriWiki context-tracking SSOT: .sneakoscope/wiki/context-pack.json; read only the latest coordinate+voxel overlay pack before every route stage, run sks wiki refresh/pack after changes, validate before handoffs/final.\n`,
|
|
762
|
+
'prompt-pipeline': `---\nname: prompt-pipeline\ndescription: Default SKS prompt optimization pipeline for execution prompts; Answer and DFix bypass it.\n---\n\nClassify intent: Answer only for real questions; question-shaped implicit instructions, complaints, and mandatory-policy statements route to Team. DFix handles Direct Fix work: tiny copy/config/docs/labels/spacing/translation/simple mechanical edits; code and broad implementation default to Team unless safety/research/GX route fits. Infer goal, target, constraints, acceptance, risk, and smallest safe route from prompt, TriWiki/current-code defaults, and conservative SKS policy. Do not surface a prequestion sheet. Materialize pipeline-plan.json for the runtime lane, kept/skipped stages, no-fallback invariant, and verification; inspect with sks pipeline plan, adding --proof-field when changed files are known. Code work surfaces route/guard/scopes, materializes team-roster.json from default or explicit counts before implementation, compiles concrete Team runtime graph/inbox artifacts after consensus, and parent owns integration/tests/Context7/Honest Mode. ${outcomeRubricPolicyText()} ${speedLanePolicyText()} ${solutionScoutPolicyText('fix this broken behavior')} ${skillDreamPolicyText()}\n\n${chatCaptureIntakeText()}\n\nDesign: non-PPT UI/UX reads design.md; if missing use design-system-builder; use imagegen for image/logo/raster, and imagegen must prefer Codex App built-in image generation (${CODEX_APP_IMAGE_GENERATION_DOC_URL}) before API generation. ${CODEX_IMAGEGEN_REQUIRED_POLICY} For UI/UX review/audit requests that mention image generation, gpt-image-2, callouts, or annotated review images, route to $Image-UX-Review/$UX-Review and require generated annotated review image evidence before issue extraction; do not satisfy that route with text-only critique. For $PPT, ${pptPipelineAllowlistPolicyText()} ${getdesignReferencePolicyText()} TriWiki context-tracking SSOT: .sneakoscope/wiki/context-pack.json; read only the latest coordinate+voxel overlay pack before every route stage, run sks wiki refresh/pack after changes, validate before handoffs/final.\n`,
|
|
763
|
+
[SOLUTION_SCOUT_SKILL_NAME]: `---\nname: ${SOLUTION_SCOUT_SKILL_NAME}\ndescription: Web-similarity scout hook for SKS problem-solving and repair requests.\n---\n\n${solutionScoutPolicyText('fix this broken behavior')}\n\nUse this as a pipeline hook, not as a standalone route: when a user asks to solve, fix, repair, troubleshoot, or investigate broken behavior, search first for similar resolution cases, summarize the useful patterns with sources, then combine them with current repo evidence before editing. If browsing is unavailable, mark the external scout unverified and continue with local evidence only.\n`,
|
|
763
764
|
'reasoning-router': `---\nname: reasoning-router\ndescription: Temporary SKS reasoning-effort routing for every command and pipeline route.\n---\n\nmedium: simple copy/color/discovery/setup/mechanical edits. high: logic, safety, architecture, DB, orchestration, refactor, multi-file work. xhigh: research, AutoResearch, falsification, benchmarks, SEO/GEO, open-ended discovery, and From-Chat-IMG image work-order analysis. Routing is temporary; return to default after the gate. Inspect with sks reasoning and sks pipeline status.\n`,
|
|
764
765
|
'pipeline-runner': `---\nname: pipeline-runner\ndescription: Execute SKS dollar-command routes as stateful pipelines with mission artifacts, route gates, Context7 evidence, temporary reasoning routing, reflection, and Honest Mode.\n---\n\nEvery $ command is a route. Use current.json, mission artifacts, and pipeline-plan.json as the execution plan: it records the lane, skipped stages, kept stages, verification, and no-unrequested-fallback invariant. Use temporary reasoning, TriWiki before stages, source hydration, Context7 when required, Team cleanup before reflection, reflection for full routes, and completion summary plus Honest Mode before final. Surface guard/scopes, record evidence, refresh/pack/validate TriWiki, and check sks pipeline status/resume/plan. ${speedLanePolicyText()} ${skillDreamPolicyText()}\n`,
|
|
765
766
|
'context7-docs': `---\nname: context7-docs\ndescription: Enforce Context7 MCP documentation evidence for SKS routes that depend on external libraries, frameworks, APIs, MCPs, package managers, DB SDKs, or generated docs.\n---\n\nWhen required, resolve-library-id, then query-docs for the resolved id. Legacy get-library-docs evidence is accepted. Prefer sks context7 tools/resolve/docs/evidence and finish only after both evidence stages exist. Check setup with sks context7 check.\n`,
|
package/src/core/pipeline.mjs
CHANGED
|
@@ -18,7 +18,7 @@ import { writeQaLoopArtifacts } from './qa-loop.mjs';
|
|
|
18
18
|
import { IMAGE_UX_REVIEW_GATE_ARTIFACT, IMAGE_UX_REVIEW_POLICY_ARTIFACT, IMAGE_UX_REVIEW_SCREEN_INVENTORY_ARTIFACT, IMAGE_UX_REVIEW_GENERATED_REVIEW_LEDGER_ARTIFACT, IMAGE_UX_REVIEW_ISSUE_LEDGER_ARTIFACT, IMAGE_UX_REVIEW_ITERATION_REPORT_ARTIFACT, IMAGE_UX_REVIEW_REQUIRED_GATE_FIELDS, writeImageUxReviewRouteArtifacts } from './image-ux-review.mjs';
|
|
19
19
|
import { SPEED_LANE_POLICY } from './proof-field.mjs';
|
|
20
20
|
import { permissionGateSummary } from './permission-gates.mjs';
|
|
21
|
-
import { CODEX_APP_IMAGE_GENERATION_DOC_URL, CODEX_COMPUTER_USE_EVIDENCE_SOURCE, CODEX_COMPUTER_USE_ONLY_POLICY, CODEX_IMAGEGEN_REQUIRED_POLICY, 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, chatCaptureIntakeText, context7RequirementText, dollarCommand, evidenceMentionsForbiddenBrowserAutomation, getdesignReferencePolicyText, hasFromChatImgSignal, hasMadSksSignal, imageUxReviewPipelinePolicyText, noUnrequestedFallbackCodePolicyText, outcomeRubricPolicyText, pptPipelineAllowlistPolicyText, reflectionRequiredForRoute, reasoningInstruction, routeNeedsContext7, routePrompt, routeReasoning, routeRequiresSubagents, speedLanePolicyText, stripDollarCommand, stripMadSksSignal, stripVisibleDecisionAnswerBlocks, subagentExecutionPolicyText, stackCurrentDocsPolicyText, triwikiContextTracking, triwikiContextTrackingText, triwikiStagePolicyText } from './routes.mjs';
|
|
21
|
+
import { CODEX_APP_IMAGE_GENERATION_DOC_URL, CODEX_COMPUTER_USE_EVIDENCE_SOURCE, CODEX_COMPUTER_USE_ONLY_POLICY, CODEX_IMAGEGEN_REQUIRED_POLICY, 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, SOLUTION_SCOUT_STAGE_ID, chatCaptureIntakeText, context7RequirementText, dollarCommand, evidenceMentionsForbiddenBrowserAutomation, getdesignReferencePolicyText, hasFromChatImgSignal, hasMadSksSignal, imageUxReviewPipelinePolicyText, looksLikeProblemSolvingRequest, noUnrequestedFallbackCodePolicyText, outcomeRubricPolicyText, pptPipelineAllowlistPolicyText, reflectionRequiredForRoute, reasoningInstruction, routeNeedsContext7, routePrompt, routeReasoning, routeRequiresSubagents, solutionScoutPolicyText, speedLanePolicyText, stripDollarCommand, stripMadSksSignal, stripVisibleDecisionAnswerBlocks, subagentExecutionPolicyText, stackCurrentDocsPolicyText, triwikiContextTracking, triwikiContextTrackingText, triwikiStagePolicyText } from './routes.mjs';
|
|
22
22
|
import { TEAM_DECOMPOSITION_ARTIFACT, TEAM_GRAPH_ARTIFACT, TEAM_INBOX_DIR, TEAM_RUNTIME_TASKS_ARTIFACT, teamRuntimePlanMetadata, teamRuntimeRequiredArtifacts, validateTeamRuntimeArtifacts, writeTeamRuntimeArtifacts } from './team-dag.mjs';
|
|
23
23
|
import { formatAgentReasoning, formatRoleCounts, initTeamLive, parseTeamSpecText, teamReasoningPolicy } from './team-live.mjs';
|
|
24
24
|
import { evaluateTeamReviewPolicyGate, MIN_TEAM_REVIEWER_LANES, MIN_TEAM_REVIEW_POLICY_TEXT, teamReviewPolicy } from './team-review-policy.mjs';
|
|
@@ -50,6 +50,7 @@ const QUESTION_GATE_ROUTES = new Set(['QALoop', 'PPT']);
|
|
|
50
50
|
const LIGHTWEIGHT_ROUTES = new Set(['Answer', 'DFix', 'Help', 'Wiki']);
|
|
51
51
|
const FULL_ROUTE_STAGES = Object.freeze([
|
|
52
52
|
'route_classification',
|
|
53
|
+
SOLUTION_SCOUT_STAGE_ID,
|
|
53
54
|
'skill_dream_counter',
|
|
54
55
|
'ambiguity_gate',
|
|
55
56
|
'pipeline_plan',
|
|
@@ -234,6 +235,9 @@ function buildPipelineStages(route, task, ambiguity, lane, context7Required) {
|
|
|
234
235
|
}
|
|
235
236
|
|
|
236
237
|
function optionalStage(route, task, ambiguity, context7Required, id) {
|
|
238
|
+
if (id === SOLUTION_SCOUT_STAGE_ID && !looksLikeProblemSolvingRequest(task)) return { skip: true, notApplicable: true, reason: 'no_problem_solving_signal' };
|
|
239
|
+
if (id === SOLUTION_SCOUT_STAGE_ID && ['Answer', 'Help', 'Wiki'].includes(route?.id)) return { skip: true, notApplicable: true, reason: 'route_not_code_repair' };
|
|
240
|
+
if (id === SOLUTION_SCOUT_STAGE_ID) return { skip: false, reason: 'problem_solving_request_requires_web_similarity_scout' };
|
|
237
241
|
if (id === 'ambiguity_gate' && ambiguity?.required === false) return { skip: true, notApplicable: true, reason: 'ambiguity_gate_not_required_for_entrypoint' };
|
|
238
242
|
if (id === 'ambiguity_gate' && CLARIFICATION_BYPASS_ROUTES.has(route?.id)) return { skip: true, notApplicable: true, reason: 'route_bypasses_clarification' };
|
|
239
243
|
if (id === 'context7_evidence' && !context7Required) return { skip: true, notApplicable: true, reason: 'context7_not_required_by_route' };
|
|
@@ -252,9 +256,16 @@ function planVerification(route, proof) {
|
|
|
252
256
|
}
|
|
253
257
|
|
|
254
258
|
function planNextActions(route, task, ambiguity, lane) {
|
|
255
|
-
if (ambiguity.required && !ambiguity.passed)
|
|
259
|
+
if (ambiguity.required && !ambiguity.passed) {
|
|
260
|
+
return [
|
|
261
|
+
'auto-seal execution contract from inferred answers',
|
|
262
|
+
...(looksLikeProblemSolvingRequest(task) ? ['run Solution Scout web search for similar fixes before editing'] : []),
|
|
263
|
+
'continue with decision-contract.json'
|
|
264
|
+
];
|
|
265
|
+
}
|
|
256
266
|
const actions = ['read pipeline-plan.json before work', 'execute kept stages only', 'run listed verification'];
|
|
257
267
|
if (!lane.fast_lane_allowed && routeRequiresSubagents(route, task)) actions.splice(1, 0, 'materialize full Team artifacts before implementation');
|
|
268
|
+
if (looksLikeProblemSolvingRequest(task)) actions.splice(1, 0, 'run Solution Scout web search for similar fixes before editing');
|
|
258
269
|
actions.push('refresh/validate TriWiki when required', 'finish with completion summary and Honest Mode');
|
|
259
270
|
return actions;
|
|
260
271
|
}
|
|
@@ -283,6 +294,7 @@ export function promptPipelineContext(prompt, route = null) {
|
|
|
283
294
|
'Default execution routing: general implementation/code-changing prompts promote to Team so the normal path is parallel analysis, TriWiki refresh, debate/consensus, then fresh parallel executors. Answer, DFix, Help, Wiki maintenance, and safety-specific routes are intentional exceptions.',
|
|
284
295
|
'Stance: infer the user intent aggressively from rough wording, local context, TriWiki, and conservative defaults; do not surface prequestion sheets before work.',
|
|
285
296
|
subagentExecutionPolicyText(route, cleanPrompt),
|
|
297
|
+
solutionScoutPolicyText(cleanPrompt),
|
|
286
298
|
noUnrequestedFallbackCodePolicyText(),
|
|
287
299
|
outcomeRubricPolicyText(),
|
|
288
300
|
speedLanePolicyText(),
|
package/src/core/routes.mjs
CHANGED
|
@@ -1,4 +1,25 @@
|
|
|
1
1
|
const REFLECTION_SKILL_NAME = 'reflection';
|
|
2
|
+
export const SOLUTION_SCOUT_SKILL_NAME = 'solution-scout';
|
|
3
|
+
export const SOLUTION_SCOUT_STAGE_ID = 'solution_scout';
|
|
4
|
+
|
|
5
|
+
export function looksLikeProblemSolvingRequest(prompt = '') {
|
|
6
|
+
const text = String(prompt || '').trim();
|
|
7
|
+
if (!text) return false;
|
|
8
|
+
const problemCue = /(문제|오류|에러|버그|고장|깨짐|실패|안\s*(?:됨|돼|되|나옴|보임|돌아|먹)|작동\s*안|해결|고쳐|수정|복구|troubleshoot|not\s+working|broken|bug|error|failure|fails?|crash|fix|repair|resolve|solve)/i.test(text);
|
|
9
|
+
const actionCue = /(해줘|해달|해라|되게|찾아|검색|기반|수정|진행|apply|implement|fix|repair|resolve|solve|troubleshoot|patch|update|change)/i.test(text);
|
|
10
|
+
return problemCue && actionCue;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function solutionScoutPolicyText(prompt = '') {
|
|
14
|
+
if (!looksLikeProblemSolvingRequest(prompt)) return '';
|
|
15
|
+
return [
|
|
16
|
+
'Solution Scout hook: this prompt looks like a problem-solving or repair request.',
|
|
17
|
+
'Before code edits, run a short web search for similar error reports, bug fixes, docs notes, or prior resolution patterns using the concrete symptom, stack, package, and error text from the repo.',
|
|
18
|
+
'Prefer primary sources and official docs for package/API behavior; use Context7 when the fix depends on a library, SDK, MCP, package manager, or generated documentation.',
|
|
19
|
+
'Summarize the relevant external patterns in 2-3 bullets, then design the local SKS fix from current code/tests plus those patterns. Do not copy a workaround blindly.',
|
|
20
|
+
'If web search is unavailable or the issue is fully local and trivial, state that the external-similarity search is unverified and continue from local evidence only.'
|
|
21
|
+
].join('\n');
|
|
22
|
+
}
|
|
2
23
|
export const FROM_CHAT_IMG_COVERAGE_ARTIFACT = 'from-chat-img-coverage-ledger.json';
|
|
3
24
|
export const FROM_CHAT_IMG_WORK_ORDER_ARTIFACT = 'from-chat-img-work-order.md';
|
|
4
25
|
export const FROM_CHAT_IMG_SOURCE_INVENTORY_ARTIFACT = 'from-chat-img-source-inventory.json';
|
|
@@ -12,7 +33,7 @@ export const CODEX_COMPUTER_USE_EVIDENCE_SOURCE = 'codex_computer_use';
|
|
|
12
33
|
export const CODEX_IMAGEGEN_EVIDENCE_SOURCE = 'codex_app_imagegen_gpt_image_2';
|
|
13
34
|
export const CODEX_APP_IMAGE_GENERATION_DOC_URL = 'https://developers.openai.com/codex/app/features#image-generation';
|
|
14
35
|
export const OPENAI_IMAGE_GENERATION_DOC_URL = 'https://developers.openai.com/api/docs/guides/image-generation';
|
|
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.';
|
|
36
|
+
export const CODEX_COMPUTER_USE_ONLY_POLICY = 'Pipeline UI/browser verification and visual inspection must use Codex Computer Use only. Do not use or install Playwright packages, 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
37
|
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
38
|
export const RESERVED_CODEX_PLUGIN_SKILL_NAMES = Object.freeze(['computer-use', 'browser', 'browser-use']);
|
|
18
39
|
export const FORBIDDEN_BROWSER_AUTOMATION_RE = /\b(playwright|chrome\s+mcp|browser\s+use|selenium|puppeteer)\b/i;
|
|
@@ -106,6 +127,7 @@ export function imageUxReviewPipelinePolicyText() {
|
|
|
106
127
|
export const RECOMMENDED_SKILLS = [
|
|
107
128
|
'reasoning-router',
|
|
108
129
|
'pipeline-runner',
|
|
130
|
+
'solution-scout',
|
|
109
131
|
'context7-docs',
|
|
110
132
|
'seo-geo-optimizer',
|
|
111
133
|
'autoresearch-loop',
|
|
@@ -717,8 +739,8 @@ export function looksLikeDirectWorkRequest(prompt = '') {
|
|
|
717
739
|
return looksLikeCodeChangingWork(text)
|
|
718
740
|
|| looksLikeChatCaptureRequest(text)
|
|
719
741
|
|| looksLikeQuestionShapedDirective(text)
|
|
720
|
-
|| /(
|
|
721
|
-
|| /(
|
|
742
|
+
|| /(작업|파이프라인|구현|수정|변경|추가|적용|반영|처리|수행|검수|설치|해결|리드미|README).*(해줘|해달|해라|해야|되게|줘야|줘야지|달라)/i.test(text)
|
|
743
|
+
|| /(진행해|수행해|작업해|처리해|적용해|반영해|검수해|고쳐줘|바꿔줘|해결해줘|만들어줘|해줘야|해줘야지|해달라|해야지|되게 해|install|run|execute|test|deploy|commit|push)/i.test(text);
|
|
722
744
|
}
|
|
723
745
|
|
|
724
746
|
export function routeNeedsContext7(route, prompt = '') {
|
|
@@ -748,7 +770,7 @@ export function reflectionRequiredForRoute(route) {
|
|
|
748
770
|
}
|
|
749
771
|
|
|
750
772
|
export function looksLikeCodeChangingWork(prompt = '') {
|
|
751
|
-
return /\b(implement|build|make|add|edit|modify|change|fix|refactor|rewrite|migrate|create|delete|remove|rename|update|patch
|
|
773
|
+
return /\b(implement|build|make|add|edit|modify|change|fix|refactor|rewrite|migrate|create|delete|remove|rename|update|patch|코드|구현|개발|수정|변경|추가|삭제|해결|고쳐|바꿔|리팩터|마이그레이션)\b/i.test(String(prompt || ''));
|
|
752
774
|
}
|
|
753
775
|
|
|
754
776
|
export function looksLikeExecutionWork(prompt = '') {
|
package/src/core/skill-forge.mjs
CHANGED
|
@@ -8,7 +8,7 @@ export const SKILL_DREAM_POLICY = Object.freeze({
|
|
|
8
8
|
schema_version: 1,
|
|
9
9
|
state_path: '.sneakoscope/skills/dream-state.json',
|
|
10
10
|
latest_report_path: '.sneakoscope/reports/skill-dream-latest.json',
|
|
11
|
-
min_events_between_runs:
|
|
11
|
+
min_events_between_runs: 10,
|
|
12
12
|
min_interval_hours: 24,
|
|
13
13
|
max_events_retained: 160,
|
|
14
14
|
max_skill_lines_before_compression: 80,
|
|
@@ -39,7 +39,7 @@ const MERGE_GROUPS = [
|
|
|
39
39
|
];
|
|
40
40
|
|
|
41
41
|
export function skillDreamPolicyText() {
|
|
42
|
-
return 'Skill dreaming policy: record only cheap route/skill usage counters in `.sneakoscope/skills/dream-state.json`; do not evaluate every conversation. Run `sks skill-dream run` or the automatic due check only after the configured event count and cooldown. Reports are recommendation-only: keep/merge/prune/improve candidates may update future generated skill wording, but skill deletion or merge requires explicit user approval.';
|
|
42
|
+
return 'Skill dreaming policy: record only cheap route/skill usage counters in `.sneakoscope/skills/dream-state.json`; do not evaluate every conversation. Run `sks skill-dream run` or the automatic due check only after the configured event count and cooldown, defaulting to one due check every 10 route events subject to cooldown. Reports are recommendation-only: keep/merge/prune/improve candidates may update future generated skill wording, but skill deletion or merge requires explicit user approval.';
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
export function createSkillCandidate(opts = {}) {
|
package/src/core/tmux-ui.mjs
CHANGED
|
@@ -1,119 +1,48 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import fsp from 'node:fs/promises';
|
|
3
3
|
import { spawnSync } from 'node:child_process';
|
|
4
|
-
import
|
|
4
|
+
import figlet from 'figlet';
|
|
5
|
+
import { exists, nowIso, PACKAGE_VERSION, packageRoot, readJson, runProcess, sha256, sksRoot, which, writeJsonAtomic } from './fsx.mjs';
|
|
5
6
|
import { getCodexInfo } from './codex-adapter.mjs';
|
|
6
7
|
import { codexAppIntegrationStatus, formatCodexAppStatus } from './codex-app.mjs';
|
|
7
8
|
import { REQUIRED_CODEX_MODEL, forceGpt55CodexArgs } from './codex-model-guard.mjs';
|
|
8
9
|
import { MIN_TEAM_REVIEWER_LANES } from './team-review-policy.mjs';
|
|
9
10
|
import { appendTeamEvent } from './team-live.mjs';
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
12
|
+
const SKS_FIGLET_FONT = 'Standard';
|
|
13
|
+
|
|
14
|
+
function trimFiglet(text = '') {
|
|
15
|
+
return String(text || '')
|
|
16
|
+
.split(/\r?\n/)
|
|
17
|
+
.map((line) => line.replace(/\s+$/g, ''))
|
|
18
|
+
.join('\n')
|
|
19
|
+
.replace(/\n+$/g, '');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function sksAsciiLogo(opts = {}) {
|
|
23
|
+
const version = opts.version || PACKAGE_VERSION;
|
|
24
|
+
const subtitle = opts.subtitle || 'SNEAKOSCOPE CODEX';
|
|
25
|
+
let logo = '';
|
|
26
|
+
try {
|
|
27
|
+
logo = figlet.textSync('SKS', {
|
|
28
|
+
font: SKS_FIGLET_FONT,
|
|
29
|
+
horizontalLayout: 'fitted',
|
|
30
|
+
verticalLayout: 'default'
|
|
31
|
+
});
|
|
32
|
+
} catch {
|
|
33
|
+
logo = ' ____ _ __ ____\n / ___| | |/ // ___|\n \\___ \\ | \' / \\___ \\\n ___) || . \\ ___) |\n |____/ |_|\\_\\|____/';
|
|
34
|
+
}
|
|
35
|
+
return `${trimFiglet(logo)}\n${subtitle} v${version}`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const SKS_TMUX_LOGO = sksAsciiLogo();
|
|
21
39
|
|
|
22
40
|
const SKS_TMUX_LOGO_FRAMES = [
|
|
23
|
-
[
|
|
24
|
-
' ||',
|
|
25
|
-
' ||',
|
|
26
|
-
' ||',
|
|
27
|
-
' ||',
|
|
28
|
-
' ||',
|
|
29
|
-
' ||',
|
|
30
|
-
' SKS',
|
|
31
|
-
' SNEAKOSCOPE CODEX'
|
|
32
|
-
].join('\n'),
|
|
33
|
-
[
|
|
34
|
-
' //||',
|
|
35
|
-
' // || .',
|
|
36
|
-
' // || .:',
|
|
37
|
-
' // || .::',
|
|
38
|
-
' // || .:::',
|
|
39
|
-
' // ||.::::',
|
|
40
|
-
' S K S',
|
|
41
|
-
' SNEAKOSCOPE CODEX'
|
|
42
|
-
].join('\n'),
|
|
43
|
-
[
|
|
44
|
-
' _______ __ __ _______',
|
|
45
|
-
' / _____/| / /_/ /| / _____/|',
|
|
46
|
-
' / /____| | / __ / | / /____| |',
|
|
47
|
-
' \\____ \\ | / / / /| | \\____ \\ |',
|
|
48
|
-
' ____/ / | |/_/ /_/ / | |____/ / | |',
|
|
49
|
-
'/_____/ |//_/\\__/ / |//_____/ |/',
|
|
50
|
-
'\\_____\\___/ \\_\\ \\_\\/___/ \\_____\\___/',
|
|
51
|
-
' SNEAKOSCOPE CODEX'
|
|
52
|
-
].join('\n'),
|
|
53
|
-
[
|
|
54
|
-
' _______ __ __ _______',
|
|
55
|
-
' / _____/ / /_/ / / _____/|',
|
|
56
|
-
' / /____ / __ / / /____ | |',
|
|
57
|
-
' \\____ \\ / / / / \\____ \\| |',
|
|
58
|
-
' ____/ / /_/ /_/ / ____/ / | |',
|
|
59
|
-
'/_____/ /_/\\__/ /_____/ |/',
|
|
60
|
-
' \\_____\\ \\_\\ \\_\\ \\_____\\___/',
|
|
61
|
-
' SNEAKOSCOPE CODEX'
|
|
62
|
-
].join('\n'),
|
|
63
|
-
[
|
|
64
|
-
' _______ __ __ _______',
|
|
65
|
-
' / _____/| / /_/ /| / _____/|',
|
|
66
|
-
' / /____| | / __ / | / /____| |',
|
|
67
|
-
' \\____ \\ | / / / /| | \\____ \\ |',
|
|
68
|
-
' ____/ / | |/_/ /_/ / | |____/ / | |',
|
|
69
|
-
'/_____/ |//_/\\__/ / |//_____/ |/',
|
|
70
|
-
'\\_____\\___/ \\_\\ \\_\\/___/ \\_____\\___/',
|
|
71
|
-
' SNEAKOSCOPE CODEX'
|
|
72
|
-
].join('\n'),
|
|
73
|
-
[
|
|
74
|
-
' _______ __ __ _______',
|
|
75
|
-
' |\\_____ \\ / /_/ / |\\_____ \\',
|
|
76
|
-
' | |____\\ \\/ __ / | |____\\ \\',
|
|
77
|
-
' | |\\____\\/ / / / | |\\____\\ \\',
|
|
78
|
-
' | | |___/ /_/ /__ | | |___/ /',
|
|
79
|
-
' \\|_|/____/\\__/__/ \\|_|/____/',
|
|
80
|
-
' S K S',
|
|
81
|
-
' SNEAKOSCOPE CODEX'
|
|
82
|
-
].join('\n'),
|
|
83
|
-
[
|
|
84
|
-
' ||\\\\',
|
|
85
|
-
' . || \\\\',
|
|
86
|
-
' ::. || \\\\',
|
|
87
|
-
' ::::. || \\\\',
|
|
88
|
-
' ::::::. || \\\\',
|
|
89
|
-
' :::::::::|| \\\\',
|
|
90
|
-
' S K S',
|
|
91
|
-
' SNEAKOSCOPE CODEX'
|
|
92
|
-
].join('\n'),
|
|
93
|
-
[
|
|
94
|
-
' ||',
|
|
95
|
-
' ||',
|
|
96
|
-
' ||',
|
|
97
|
-
' ||',
|
|
98
|
-
' ||',
|
|
99
|
-
' ||',
|
|
100
|
-
' SKS',
|
|
101
|
-
' SNEAKOSCOPE CODEX'
|
|
102
|
-
].join('\n'),
|
|
103
41
|
SKS_TMUX_LOGO
|
|
104
42
|
];
|
|
105
43
|
|
|
106
44
|
const SKS_TMUX_LOGO_ANIMATION_STEPS = Object.freeze([
|
|
107
|
-
{ frame: 0, color: '
|
|
108
|
-
{ frame: 1, color: '39', bold: false, delay: '0.045' },
|
|
109
|
-
{ frame: 2, color: '45', bold: false, delay: '0.05' },
|
|
110
|
-
{ frame: 3, color: '51', bold: false, delay: '0.055' },
|
|
111
|
-
{ frame: 4, color: '51', bold: true, delay: '0.07' },
|
|
112
|
-
{ frame: 5, color: '51', bold: true, delay: '0.07' },
|
|
113
|
-
{ frame: 6, color: '45', bold: false, delay: '0.05' },
|
|
114
|
-
{ frame: 7, color: '39', bold: false, delay: '0.045' },
|
|
115
|
-
{ frame: 8, color: '39', bold: false, delay: '0.045' },
|
|
116
|
-
{ frame: 9, color: '51', bold: true, delay: '0.16' }
|
|
45
|
+
{ frame: 0, color: '51', bold: true, delay: '0.16' }
|
|
117
46
|
]);
|
|
118
47
|
|
|
119
48
|
export const DEFAULT_SKS_CODEX_MODEL = REQUIRED_CODEX_MODEL;
|
|
@@ -360,7 +289,7 @@ export function formatTmuxBanner(status = null) {
|
|
|
360
289
|
const lines = [
|
|
361
290
|
SKS_TMUX_LOGO,
|
|
362
291
|
'',
|
|
363
|
-
'
|
|
292
|
+
'SKS tmux runtime',
|
|
364
293
|
'',
|
|
365
294
|
'Canonical prompt commands:',
|
|
366
295
|
' $DFix $Answer $SKS $Team $QA-LOOP $PPT $Goal $Research $AutoResearch $DB $GX $Wiki $Help',
|
|
@@ -391,11 +320,62 @@ function paneId(stdout = '') {
|
|
|
391
320
|
return id.startsWith('%') ? id : null;
|
|
392
321
|
}
|
|
393
322
|
|
|
323
|
+
function currentTerminalDimensions(opts = {}) {
|
|
324
|
+
const width = Number(opts.width || opts.detachedWidth || process.stdout?.columns || process.env.COLUMNS || 0);
|
|
325
|
+
const height = Number(opts.height || opts.detachedHeight || process.stdout?.rows || process.env.LINES || 0);
|
|
326
|
+
return {
|
|
327
|
+
width: String(Math.max(120, Number.isFinite(width) && width > 0 ? width : 120)),
|
|
328
|
+
height: String(Math.max(36, Number.isFinite(height) && height > 0 ? height : 36))
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
|
|
394
332
|
async function hasTmuxSession(bin, session) {
|
|
395
333
|
const run = await tmuxRun(bin, ['has-session', '-t', session], { timeoutMs: 5000 });
|
|
396
334
|
return run.code === 0;
|
|
397
335
|
}
|
|
398
336
|
|
|
337
|
+
async function tmuxWindowTarget(bin, session) {
|
|
338
|
+
const fallback = sanitizeTmuxSessionName(session);
|
|
339
|
+
const run = await tmuxRun(bin, ['list-windows', '-t', fallback, '-F', '#{window_id}'], { timeoutMs: 5000, maxOutputBytes: 4096 });
|
|
340
|
+
if (run.code !== 0) return fallback;
|
|
341
|
+
const windowId = String(run.stdout || '').split(/\r?\n/).map((line) => line.trim()).find((line) => /^@\d+$/.test(line));
|
|
342
|
+
return windowId || fallback;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function tmuxLayoutName(value = 'tiled') {
|
|
346
|
+
const layout = String(value || 'tiled').trim();
|
|
347
|
+
return /^(tiled|even-horizontal|even-vertical|main-horizontal|main-horizontal-mirrored|main-vertical|main-vertical-mirrored)$/.test(layout)
|
|
348
|
+
? layout
|
|
349
|
+
: 'tiled';
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
async function enableTmuxDynamicResize(tmuxBin, session, opts = {}) {
|
|
353
|
+
const layout = tmuxLayoutName(opts.layout || 'tiled');
|
|
354
|
+
const safeSession = sanitizeTmuxSessionName(session);
|
|
355
|
+
const target = await tmuxWindowTarget(tmuxBin, safeSession);
|
|
356
|
+
const relayout = `resize-window -t ${target} -A; set-window-option -t ${target} window-size latest; select-layout -t ${target} ${layout}; select-layout -t ${target} -E; set-window-option -t ${target} window-size latest`;
|
|
357
|
+
const commands = [
|
|
358
|
+
['set-window-option', '-t', target, 'window-size', 'latest'],
|
|
359
|
+
['set-window-option', '-t', target, 'aggressive-resize', 'on'],
|
|
360
|
+
['set-hook', '-t', safeSession, 'client-attached', relayout],
|
|
361
|
+
['set-hook', '-t', safeSession, 'client-resized', relayout],
|
|
362
|
+
['resize-window', '-t', target, '-A'],
|
|
363
|
+
['set-window-option', '-t', target, 'window-size', 'latest'],
|
|
364
|
+
['select-layout', '-t', target, layout],
|
|
365
|
+
['select-layout', '-t', target, '-E'],
|
|
366
|
+
['set-window-option', '-t', target, 'window-size', 'latest']
|
|
367
|
+
];
|
|
368
|
+
const applied = [];
|
|
369
|
+
const failed = [];
|
|
370
|
+
for (const args of commands) {
|
|
371
|
+
const run = await tmuxRun(tmuxBin, args, { timeoutMs: 5000 });
|
|
372
|
+
const command = [path.basename(tmuxBin), ...args].join(' ');
|
|
373
|
+
if (run.code === 0) applied.push(command);
|
|
374
|
+
else failed.push({ command, stderr: run.stderr || run.stdout || 'tmux command failed' });
|
|
375
|
+
}
|
|
376
|
+
return { enabled: failed.length === 0, layout, applied, failed };
|
|
377
|
+
}
|
|
378
|
+
|
|
399
379
|
export function buildTmuxOpenArgs(plan = {}) {
|
|
400
380
|
return ['attach-session', '-t', sanitizeTmuxSessionName(plan.session || plan.workspace || defaultTmuxSessionName(plan.root))];
|
|
401
381
|
}
|
|
@@ -445,19 +425,19 @@ export async function createTmuxSession(plan = {}, panes = [], opts = {}) {
|
|
|
445
425
|
}
|
|
446
426
|
}
|
|
447
427
|
const first = normalizedPanes[0] || { cwd: root, command: 'pwd' };
|
|
448
|
-
const
|
|
449
|
-
const
|
|
450
|
-
const create = await tmuxRun(tmuxBin, ['new-session', '-d', '-x',
|
|
428
|
+
const dimensions = currentTerminalDimensions(opts);
|
|
429
|
+
const layout = tmuxLayoutName(opts.layout || 'tiled');
|
|
430
|
+
const create = await tmuxRun(tmuxBin, ['new-session', '-d', '-x', dimensions.width, '-y', dimensions.height, '-s', session, '-c', path.resolve(first.cwd || root), '-n', 'sks', '-P', '-F', '#{pane_id}', first.command || 'pwd']);
|
|
451
431
|
if (create.code !== 0) return { ok: false, session, panes: [], stderr: create.stderr || create.stdout || 'tmux new-session failed' };
|
|
452
432
|
const created = [{ pane_id: paneId(create.stdout), role: first.role || 'overview', title: first.title || 'overview' }];
|
|
453
433
|
for (const pane of normalizedPanes.slice(1)) {
|
|
454
434
|
const split = await tmuxRun(tmuxBin, ['split-window', '-t', session, pane.vertical ? '-v' : '-h', '-d', '-P', '-F', '#{pane_id}', '-c', path.resolve(pane.cwd || root), pane.command || 'pwd']);
|
|
455
435
|
if (split.code !== 0) return { ok: false, session, panes: created, stderr: split.stderr || split.stdout || 'tmux split-window failed' };
|
|
456
436
|
created.push({ pane_id: paneId(split.stdout), role: pane.role || 'lane', title: pane.title || null });
|
|
457
|
-
await tmuxRun(tmuxBin, ['select-layout', '-t', session,
|
|
437
|
+
await tmuxRun(tmuxBin, ['select-layout', '-t', session, layout]).catch(() => null);
|
|
458
438
|
}
|
|
459
|
-
await
|
|
460
|
-
return { ok: true, reused: false, session, panes: created, attach_command: `tmux attach-session -t ${session}
|
|
439
|
+
const dynamic_resize = await enableTmuxDynamicResize(tmuxBin, session, { layout });
|
|
440
|
+
return { ok: true, reused: false, session, panes: created, attach_command: `tmux attach-session -t ${session}`, layout, initial_size: dimensions, dynamic_resize };
|
|
461
441
|
}
|
|
462
442
|
|
|
463
443
|
export async function launchTmuxUi(args = [], opts = {}) {
|
|
@@ -593,6 +573,9 @@ export async function launchTmuxTeamView({ root, missionId, plan = {}, promptFil
|
|
|
593
573
|
mode: 'single_window_split_panes',
|
|
594
574
|
window: 'sks',
|
|
595
575
|
layout: 'tiled',
|
|
576
|
+
dynamic_resize: true,
|
|
577
|
+
window_size: 'latest',
|
|
578
|
+
resize_hooks: ['client-attached', 'client-resized'],
|
|
596
579
|
live_updates: true,
|
|
597
580
|
panes_show: ['overview', 'scout', 'planning', 'execution', 'review', 'safety'],
|
|
598
581
|
user_attach_command: launch.attach_command
|