sneakoscope 0.7.56 → 0.7.57
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 +9 -7
- package/package.json +1 -1
- package/src/cli/install-helpers.mjs +2 -2
- package/src/cli/main.mjs +91 -136
- package/src/cli/maintenance-commands.mjs +181 -21
- package/src/core/auto-review.mjs +2 -0
- package/src/core/fsx.mjs +1 -1
- package/src/core/init.mjs +12 -12
- package/src/core/openclaw.mjs +1 -1
- package/src/core/pipeline.mjs +26 -8
- package/src/core/routes.mjs +53 -13
- package/src/core/team-dag.mjs +23 -0
- package/src/core/team-live.mjs +110 -21
- package/src/core/tmux-ui.mjs +91 -6
package/README.md
CHANGED
|
@@ -42,7 +42,7 @@ sks selftest --mock
|
|
|
42
42
|
|
|
43
43
|
| Area | What it does |
|
|
44
44
|
| --- | --- |
|
|
45
|
-
| CLI runtime | Bare `sks` opens or reuses the default tmux Codex CLI workspace. `sks tmux open` remains the explicit form for session/workspace flags, and `sks --mad` launches the explicit full-access high-reasoning profile. |
|
|
45
|
+
| CLI runtime | Bare `sks` opens or reuses the default tmux Codex CLI workspace. `sks tmux open` remains the explicit form for session/workspace flags, and `sks --mad` launches a multi-pane MAD tmux cockpit with the explicit full-access high-reasoning profile. |
|
|
46
46
|
| Codex App commands | Installs generated skills so `$Team`, `$From-Chat-IMG`, `$DFix`, `$QA-LOOP`, `$PPT`, `$Image-UX-Review`, `$UX-Review`, `$Goal`, `$DB`, `$Wiki`, `$Help`, and related routes are visible in prompt workflows. `sks codex-app remote-control` wraps Codex CLI 0.130.0+ headless remote control without falling back to older app-server internals. |
|
|
47
47
|
| OpenClaw agents | Generates an OpenClaw skill package so OpenClaw agents can attach `sneakoscope-codex`, enable the `shell` tool, and discover/use SKS commands from the target repo root. |
|
|
48
48
|
| Pipeline plans | Writes `pipeline-plan.json` for stateful routes so the runtime lane, kept stages, skipped stages, verification commands, and no-unrequested-fallback invariant are visible with `sks pipeline plan`. |
|
|
@@ -53,7 +53,7 @@ sks selftest --mock
|
|
|
53
53
|
| PPT pipeline | Uses `$PPT` for simple, restrained, information-first HTML/PDF presentation artifacts, first asking delivery context, audience profile, STP strategy, decision context, and 3+ pain-point to solution/aha mappings before source research, design-system work, HTML/PDF export, and render QA. Independent strategy/render/file-write phases run in parallel where inputs allow and are recorded in `ppt-parallel-report.json`; editable source HTML is preserved under `source-html/`, PPT-only temporary build files are cleaned after completion, installed skills/MCPs outside the `$PPT` allowlist are ignored, generated image assets must use real `$imagegen`/`gpt-image-2` output when sealed in the contract, and `ppt-style-tokens.json` records the design SSOT plus fused source inputs. |
|
|
54
54
|
| Image UX Review | Uses `$Image-UX-Review` / `$UX-Review` for UI/UX audits where source screenshots are first turned into generated annotated review images through Codex App `$imagegen`/`gpt-image-2`; those generated images are then read back into `image-ux-issue-ledger.json`, optional requested fixes are rechecked, and missing generated review images or text-only screenshot critique cannot pass `image-ux-review-gate.json`. |
|
|
55
55
|
| Computer Use fast lane | Uses `$Computer-Use` / `$CU` for UI/browser/visual work that needs maximum speed: skip Team debate and upfront TriWiki loops, use Codex Computer Use directly, then refresh/validate TriWiki and run Honest Mode at final closeout. |
|
|
56
|
-
| Goal | Provides a fast SKS bridge overlay for Codex native persisted `/goal` create, pause, resume, and clear controls; implementation continues through the selected SKS execution route. |
|
|
56
|
+
| Goal | Provides a fast SKS bridge overlay for Codex native persisted `/goal` create, pause, resume, and clear controls; an ambient non-disruptive Goal continuation overlay is also recorded in normal pipeline plans while implementation continues through the selected SKS execution route. |
|
|
57
57
|
| TriWiki voxels | Maintains `.sneakoscope/wiki/context-pack.json` as the context SSOT with coordinate anchors, voxel metadata, `attention.use_first`, `attention.hydrate_first`, and prompt-bound mistake recall ledgers. |
|
|
58
58
|
| Context7 | Requires current docs for external packages, APIs, MCPs, SDKs, and framework/runtime behavior when correctness depends on current guidance. |
|
|
59
59
|
| Design SSOT | Treats `design.md` as the only design decision source of truth. `docs/Design-Sys-Prompt.md` is the builder prompt; getdesign.md, official getdesign docs, and curated DESIGN.md examples from `VoltAgent/awesome-design-md` are source inputs that must be fused into `design.md` or route-local style tokens instead of becoming parallel authorities. |
|
|
@@ -80,7 +80,7 @@ The default `sks` runtime checks npm for newer `sneakoscope` and `@openai/codex`
|
|
|
80
80
|
- Checks npm for newer `sneakoscope` and `@openai/codex` versions before launch and asks whether to update when the terminal can answer y/n.
|
|
81
81
|
- Installs the latest Codex CLI with `npm i -g @openai/codex@latest` when it is missing and you approve or pass `--yes`.
|
|
82
82
|
- Requires tmux 3.x or newer before opening the session.
|
|
83
|
-
- Creates
|
|
83
|
+
- Creates a named detached tmux cockpit with multiple panes and prints only the session, gate, attach, and blocker details needed to act.
|
|
84
84
|
|
|
85
85
|
## Installation
|
|
86
86
|
|
|
@@ -167,7 +167,7 @@ sks tmux check
|
|
|
167
167
|
sks tmux status --once
|
|
168
168
|
```
|
|
169
169
|
|
|
170
|
-
Bare `sks` creates or reuses the default named tmux session for Codex CLI and attaches to it in an interactive terminal. By default it launches Codex in
|
|
170
|
+
Bare `sks` creates or reuses the default named tmux session for Codex CLI and attaches to it in an interactive terminal. By default it launches Codex in Fast service tier with `--model gpt-5.5`, `-c service_tier="fast"`, and the selected `model_reasoning_effort` with a static SKS 3D ASCII intro inside tmux; the animated intro is reserved for non-tmux unauthenticated Codex launches and can be disabled with `SKS_TMUX_LOGO_ANIMATION=0`. SKS always forces the model to `gpt-5.5`; `SKS_CODEX_MODEL` and `SKS_CODEX_FAST_HIGH=0` cannot downgrade or remove that model pin. You can still set `SKS_CODEX_REASONING` to change reasoning effort. Use `sks tmux open` when you need explicit `--workspace` / `--session` flags, `sks tmux check` for readiness without launching, and `sks help` for CLI help. Use `--no-attach` or `SKS_TMUX_NO_AUTO_ATTACH=1` when you only want SKS to create/reuse the session and print the manual attach command.
|
|
171
171
|
|
|
172
172
|
Before opening tmux, SKS checks the installed Codex CLI against npm `@openai/codex@latest`. If a newer version exists, it asks `Y/n`; answering `y` updates automatically with `npm i -g @openai/codex@latest` and then opens tmux with the updated Codex CLI.
|
|
173
173
|
|
|
@@ -211,7 +211,7 @@ sks --mad
|
|
|
211
211
|
sks --mad --yes
|
|
212
212
|
```
|
|
213
213
|
|
|
214
|
-
This syncs existing codex-lb/Codex CLI auth before launch, creates/uses the `sks-mad-high` Codex profile for a one-shot full-access, high-reasoning tmux
|
|
214
|
+
This syncs existing codex-lb/Codex CLI auth before launch, creates/uses the `sks-mad-high` Codex profile for a one-shot full-access, high-reasoning tmux cockpit with `sandbox_mode = "danger-full-access"` and `approval_policy = "never"`, opens an active MAD-SKS permission gate for that tmux run, then launches a tiled cockpit: Codex CLI, MAD permission gate status, and live guide panes. The cockpit recreates the named session on launch so stale single-pane sessions do not hide the split layout, then attaches in an interactive terminal. If codex-lb is configured and no explicit `--workspace`/`--session` was passed, SKS opens a fresh tmux session so the repaired key is loaded by the Codex process immediately. While the gate is active, live server work, Supabase MCP database writes, direct SQL, targeted DML, schema cleanup, Supabase MCP `apply_migration`, and required Supabase CLI migration application such as `supabase migration up` or `supabase db push` are allowed. Catastrophic database wipe/all-row/project-management safeguards remain active.
|
|
215
215
|
|
|
216
216
|
MAD does not disable the pipeline contract: stages, executors, reviewers, and auto-review policy still must not invent unrequested fallback implementation code. If the requested path cannot be implemented, SKS should block with evidence rather than add substitute behavior.
|
|
217
217
|
|
|
@@ -239,10 +239,12 @@ sks team log latest
|
|
|
239
239
|
|
|
240
240
|
By default, Team missions keep at least five QA/reviewer lanes active. Use explicit role counts only when you need to raise or otherwise pin the lane mix for a specific mission.
|
|
241
241
|
|
|
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 when tmux is available. 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.
|
|
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
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`.
|
|
245
245
|
|
|
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
|
+
|
|
246
248
|
Agent sessions communicate through the bounded Team transcript. Use `sks team message <mission-id|latest> --from <agent> --to <agent|all> --message "..."` to add direct or broadcast messages; lane panes show messages addressed to that agent plus the fallback global tail.
|
|
247
249
|
|
|
248
250
|
When the Team route reaches `session_cleanup`, SKS marks the tmux session record complete and asks `watch --follow` / `lane --follow` panes to show a cleanup summary and stop. You can also run `sks team cleanup-tmux <mission-id|latest>` manually, or `sks team cleanup-tmux latest --close` to kill the recorded tmux session.
|
|
@@ -412,7 +414,7 @@ Use these inside Codex App or another agent prompt. They are prompt commands, no
|
|
|
412
414
|
| --- | --- |
|
|
413
415
|
| `$Team` | You want implementation, code changes, or substantial repo work. |
|
|
414
416
|
| `$From-Chat-IMG` | You have a chat screenshot plus original attachments and want each visible request mapped to work. |
|
|
415
|
-
| `$DFix` | You need
|
|
417
|
+
| `$DFix` | You need Direct Fix work: tiny copy/config/docs/labels/spacing/translation/simple mechanical edits, with broad implementation still routed to Team and UI design specifics handled by the relevant UI/design route rules. |
|
|
416
418
|
| `$Answer` | You want an answer only and no implementation should start. |
|
|
417
419
|
| `$SKS` | You need setup, status, usage, or workflow help. |
|
|
418
420
|
| `$QA-LOOP` | You want UI/API dogfooding, safe fixes, and rechecks. |
|
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.57",
|
|
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",
|
|
@@ -994,8 +994,8 @@ export async function selftestCodexLb(tmp) {
|
|
|
994
994
|
if (!codexLbLaunch.includes('sks-codex-lb.env')) throw new Error('selftest failed: tmux launch command does not source codex-lb env file');
|
|
995
995
|
if (!codexLbLaunch.includes("'--model' 'gpt-5.5'")) throw new Error('selftest failed: tmux launch command without args did not force GPT-5.5');
|
|
996
996
|
if (!codexLbLaunch.includes('SKS_TMUX_LOGO_ANIMATION') || !codexLbLaunch.includes('SNEAKOSCOPE CODEX')) throw new Error('selftest failed: tmux launch command does not include the animated SKS logo intro');
|
|
997
|
-
const madLaunchSource = await safeReadText(path.join(packageRoot(), 'src', 'cli', '
|
|
998
|
-
if (!madLaunchSource.includes('const lb = await maybePromptCodexLbSetupForLaunch(args)') || !madLaunchSource.includes("const launchLb = lb.status === 'present'") || !madLaunchSource.includes('codexLbImmediateLaunchOpts(cleanArgs, launchLb')) throw new Error('selftest failed: MAD launch does not sync codex-lb auth and fresh-session launch options');
|
|
997
|
+
const madLaunchSource = await safeReadText(path.join(packageRoot(), 'src', 'cli', 'maintenance-commands.mjs'));
|
|
998
|
+
if (!madLaunchSource.includes('const lb = await deps.maybePromptCodexLbSetupForLaunch(args)') || !madLaunchSource.includes("const launchLb = lb.status === 'present'") || !madLaunchSource.includes('codexLbImmediateLaunchOpts(cleanArgs, launchLb')) throw new Error('selftest failed: MAD launch does not sync codex-lb auth and fresh-session launch options');
|
|
999
999
|
|
|
1000
1000
|
}
|
|
1001
1001
|
|
package/src/cli/main.mjs
CHANGED
|
@@ -75,11 +75,11 @@ 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, isTmuxShellSession, runTmuxLaunchPlanSyntaxCheck, shouldAutoAttachTmux, tmuxReadiness, tmuxStatusKind, defaultTmuxSessionName, formatTmuxBanner, launchTmuxTeamView, launchTmuxUi, platformTmuxInstallHint, runTmuxStatus, sanitizeTmuxSessionName, teamLaneStyle } from '../core/tmux-ui.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';
|
|
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';
|
|
82
|
-
import { buildTeamPlan, codeStructureCommand, dbCommand, defaultBeta, defaultVGraph, evalCommand, gcCommand, goalCommand, gxCommand, harnessCommand, hproofCommand, memoryCommand, migrateWikiContextPack, parseTeamCreateArgs, perfCommand, profileCommand, projectWikiClaims, proofFieldCommand, qaLoopCommand, quickstartCommand, researchCommand, skillDreamCommand, statsCommand, team, teamWorkflowMarkdown, validateArtifactsCommand, wikiCommand, wikiVoxelRowCount, writeWikiContextPack } from './maintenance-commands.mjs';
|
|
82
|
+
import { buildTeamPlan, codeStructureCommand, dbCommand, defaultBeta, defaultVGraph, evalCommand, gcCommand, goalCommand, gxCommand, harnessCommand, hproofCommand, madHighCommand as runMadHighCommand, memoryCommand, migrateWikiContextPack, parseTeamCreateArgs, perfCommand, profileCommand, projectWikiClaims, proofFieldCommand, qaLoopCommand, quickstartCommand, researchCommand, skillDreamCommand, statsCommand, team, teamWorkflowMarkdown, validateArtifactsCommand, wikiCommand, wikiVoxelRowCount, writeWikiContextPack } from './maintenance-commands.mjs';
|
|
83
83
|
import { openClawCommand } from './openclaw-command.mjs';
|
|
84
84
|
|
|
85
85
|
const flag = (args, name) => args.includes(name);
|
|
@@ -96,7 +96,7 @@ function installScopeFromArgs(args = [], fallback = 'global') {
|
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
export async function main(args) {
|
|
99
|
-
if (isMadHighLaunch(args)) return
|
|
99
|
+
if (isMadHighLaunch(args)) return runMadHighCommand(args, { maybePromptSksUpdateForLaunch, maybePromptCodexUpdateForLaunch, ensureMadLaunchDependencies, printDepsInstallAction, maybePromptCodexLbSetupForLaunch, packageVersion: PACKAGE_VERSION });
|
|
100
100
|
if (isAutoReviewFlag(args[0])) return autoReviewCommand('start', args.slice(1));
|
|
101
101
|
const [cmd, sub, ...rest] = args;
|
|
102
102
|
const tail = sub === undefined ? [] : [sub, ...rest];
|
|
@@ -143,6 +143,16 @@ async function defaultTmuxCommand(args = []) {
|
|
|
143
143
|
return launchTmuxUi(args, codexLbImmediateLaunchOpts(args, lb, { conciseBlockers: true }));
|
|
144
144
|
}
|
|
145
145
|
|
|
146
|
+
function codexLbImmediateLaunchOpts(args = [], lb = {}, opts = {}) {
|
|
147
|
+
if (!lb?.ok || lb.status !== 'configured') return opts;
|
|
148
|
+
if (readOption(args, '--session', null) || readOption(args, '--workspace', null)) return opts;
|
|
149
|
+
const root = readOption(args, '--root', process.cwd());
|
|
150
|
+
const session = sanitizeTmuxSessionName(`sks-codex-lb-${Date.now().toString(36)}-${defaultTmuxSessionName(root)}`);
|
|
151
|
+
console.log(`codex-lb key loaded for this launch: ${lb.env_path}`);
|
|
152
|
+
console.log(`Using fresh tmux session: ${session}`);
|
|
153
|
+
return { ...opts, session };
|
|
154
|
+
}
|
|
155
|
+
|
|
146
156
|
function help(args = []) {
|
|
147
157
|
const topic = args[0];
|
|
148
158
|
if (topic) return usage([topic]);
|
|
@@ -338,7 +348,7 @@ async function updateCheck(args = []) {
|
|
|
338
348
|
if (result.update_available) console.log('Run: npm i -g sneakoscope');
|
|
339
349
|
}
|
|
340
350
|
|
|
341
|
-
const DOLLAR_DEFAULT_PIPELINE_TEXT = 'Default pipeline: questions -> $Answer,
|
|
351
|
+
const DOLLAR_DEFAULT_PIPELINE_TEXT = 'Default pipeline: questions -> $Answer, tiny Direct Fix edits -> $DFix, presentation/PDF artifacts -> $PPT, image-generation UI/UX reviews -> $Image-UX-Review/$UX-Review, Computer Use UI/browser speed work -> $Computer-Use, code -> $Team. Use $From-Chat-IMG only for chat screenshot plus original attachments. Use $MAD-SKS only as an explicit scoped DB authorization modifier that can be combined with another $ route. No route may invent unrequested fallback implementation code.';
|
|
342
352
|
|
|
343
353
|
function commands(args = []) {
|
|
344
354
|
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));
|
|
@@ -397,18 +407,20 @@ function dollarCommandNames() {
|
|
|
397
407
|
}
|
|
398
408
|
|
|
399
409
|
function dfixHelp() {
|
|
400
|
-
console.log(`SKS
|
|
410
|
+
console.log(`SKS Direct Fix Mode
|
|
401
411
|
|
|
402
412
|
Prompt command:
|
|
403
|
-
$DFix <
|
|
413
|
+
$DFix <tiny direct fix request>
|
|
404
414
|
|
|
405
415
|
Examples:
|
|
406
416
|
$DFix 글자 색 파란색으로 바꿔줘
|
|
407
417
|
$DFix 내용을 영어로 바꿔줘
|
|
408
418
|
$DFix Change the CTA label to "Start"
|
|
419
|
+
$DFix Fix the README typo
|
|
420
|
+
$DFix Update the package version
|
|
409
421
|
|
|
410
422
|
Purpose:
|
|
411
|
-
Fast
|
|
423
|
+
Fast tiny direct edits only. Direct Fix bypasses the general SKS prompt pipeline and uses an ultralight, no-record task list.
|
|
412
424
|
|
|
413
425
|
Rules:
|
|
414
426
|
List the exact micro-edits, inspect only needed files, apply only those edits.
|
|
@@ -1151,130 +1163,6 @@ async function codexLbCommand(action = 'status', args = []) {
|
|
|
1151
1163
|
process.exitCode = 1;
|
|
1152
1164
|
}
|
|
1153
1165
|
|
|
1154
|
-
function codexLbImmediateLaunchOpts(args = [], lb = {}, opts = {}) {
|
|
1155
|
-
if (!lb?.ok || lb.status !== 'configured') return opts;
|
|
1156
|
-
if (readOption(args, '--session', null) || readOption(args, '--workspace', null)) return opts;
|
|
1157
|
-
const root = readOption(args, '--root', process.cwd());
|
|
1158
|
-
const session = sanitizeTmuxSessionName(`sks-codex-lb-${Date.now().toString(36)}-${defaultTmuxSessionName(root)}`);
|
|
1159
|
-
console.log(`codex-lb key loaded for this launch: ${lb.env_path}`);
|
|
1160
|
-
console.log(`Using fresh tmux session: ${session}`);
|
|
1161
|
-
return { ...opts, session };
|
|
1162
|
-
}
|
|
1163
|
-
|
|
1164
|
-
async function madHighCommand(args = []) {
|
|
1165
|
-
const cleanArgs = args.filter((arg) => !['--mad', '--MAD', '--mad-sks', '--high', '--no-auto-install-tmux'].includes(arg));
|
|
1166
|
-
if (flag(args, '--json')) {
|
|
1167
|
-
const profile = await enableMadHighProfile();
|
|
1168
|
-
return console.log(JSON.stringify(profile, null, 2));
|
|
1169
|
-
}
|
|
1170
|
-
const update = await maybePromptSksUpdateForLaunch(args, { label: 'MAD launch' });
|
|
1171
|
-
if (update.status === 'updated') {
|
|
1172
|
-
console.log(`SKS updated from ${PACKAGE_VERSION} to ${update.latest}. Rerun: sks --mad`);
|
|
1173
|
-
return;
|
|
1174
|
-
}
|
|
1175
|
-
if (update.status === 'failed') {
|
|
1176
|
-
console.error(`SKS update failed: ${update.error}`);
|
|
1177
|
-
process.exitCode = 1;
|
|
1178
|
-
return;
|
|
1179
|
-
}
|
|
1180
|
-
const codexUpdate = await maybePromptCodexUpdateForLaunch(args, { label: 'MAD launch' });
|
|
1181
|
-
if (codexUpdate.status === 'failed' || codexUpdate.status === 'updated_not_reflected') {
|
|
1182
|
-
console.error(`Codex CLI update failed: ${codexUpdate.error || 'updated version was not visible on PATH'}`);
|
|
1183
|
-
process.exitCode = 1;
|
|
1184
|
-
return;
|
|
1185
|
-
}
|
|
1186
|
-
const deps = await ensureMadLaunchDependencies(args);
|
|
1187
|
-
if (!deps.ready) {
|
|
1188
|
-
console.error('SKS MAD launch blocked by missing dependencies.');
|
|
1189
|
-
for (const action of deps.actions) printDepsInstallAction(action);
|
|
1190
|
-
process.exitCode = 1;
|
|
1191
|
-
return;
|
|
1192
|
-
}
|
|
1193
|
-
const lb = await maybePromptCodexLbSetupForLaunch(args);
|
|
1194
|
-
if (lb.status === 'missing_api_key') {
|
|
1195
|
-
process.exitCode = 1;
|
|
1196
|
-
return;
|
|
1197
|
-
}
|
|
1198
|
-
const profile = await enableMadHighProfile();
|
|
1199
|
-
const madLaunch = await activateMadTmuxPermissionState(process.cwd());
|
|
1200
|
-
console.log(`SKS MAD ready: ${madHighProfileName()} | gate ${madLaunch.mission_id}`);
|
|
1201
|
-
console.log('Live full-access active; catastrophic DB wipe/all-row/project-management guards remain.');
|
|
1202
|
-
const launchLb = lb.status === 'present' ? { ...lb, status: 'configured' } : lb;
|
|
1203
|
-
const launchOpts = codexLbImmediateLaunchOpts(cleanArgs, launchLb, {
|
|
1204
|
-
codexArgs: profile.launch_args,
|
|
1205
|
-
autoInstallTmux: !flag(args, '--no-auto-install-tmux'),
|
|
1206
|
-
conciseBlockers: true
|
|
1207
|
-
});
|
|
1208
|
-
const workspace = readOption(cleanArgs, '--workspace', readOption(cleanArgs, '--session', launchOpts.session || `sks-mad-${defaultTmuxSessionName(process.cwd())}`));
|
|
1209
|
-
return launchTmuxUi([...cleanArgs, '--workspace', workspace], {
|
|
1210
|
-
...launchOpts,
|
|
1211
|
-
codexArgs: profile.launch_args,
|
|
1212
|
-
autoInstallTmux: !flag(args, '--no-auto-install-tmux'),
|
|
1213
|
-
conciseBlockers: true
|
|
1214
|
-
});
|
|
1215
|
-
}
|
|
1216
|
-
|
|
1217
|
-
async function activateMadTmuxPermissionState(cwd = process.cwd()) {
|
|
1218
|
-
const root = await sksRoot();
|
|
1219
|
-
if (!(await exists(path.join(root, '.sneakoscope')))) await initProject(root, {});
|
|
1220
|
-
const { id, dir } = await createMission(root, { mode: 'mad-sks', prompt: 'sks --mad tmux live full-access session' });
|
|
1221
|
-
const gate = {
|
|
1222
|
-
schema_version: 1,
|
|
1223
|
-
passed: false,
|
|
1224
|
-
mad_sks_permission_active: true,
|
|
1225
|
-
permissions_deactivated: false,
|
|
1226
|
-
live_server_writes_allowed: true,
|
|
1227
|
-
supabase_mcp_schema_cleanup_allowed: true,
|
|
1228
|
-
direct_execute_sql_allowed: true,
|
|
1229
|
-
normal_db_writes_allowed: true,
|
|
1230
|
-
migration_apply_allowed: true,
|
|
1231
|
-
catastrophic_safety_guard_active: true,
|
|
1232
|
-
permission_profile: permissionGateSummary(),
|
|
1233
|
-
activated_by: 'sks --mad',
|
|
1234
|
-
cwd: path.resolve(cwd || process.cwd())
|
|
1235
|
-
};
|
|
1236
|
-
await writeJsonAtomic(path.join(dir, 'mad-sks-gate.json'), gate);
|
|
1237
|
-
await writeJsonAtomic(path.join(dir, 'route-context.json'), {
|
|
1238
|
-
route: 'MadSKS',
|
|
1239
|
-
command: '$MAD-SKS',
|
|
1240
|
-
mode: 'MADSKS',
|
|
1241
|
-
task: gate.activated_by,
|
|
1242
|
-
mad_sks_authorization: true,
|
|
1243
|
-
tmux_launch: true,
|
|
1244
|
-
permission_profile: gate.permission_profile
|
|
1245
|
-
});
|
|
1246
|
-
await appendJsonlBounded(path.join(dir, 'events.jsonl'), {
|
|
1247
|
-
ts: nowIso(),
|
|
1248
|
-
type: 'mad_sks.tmux_permission_opened',
|
|
1249
|
-
route: 'MadSKS',
|
|
1250
|
-
live_server_writes_allowed: true,
|
|
1251
|
-
catastrophic_safety_guard_active: true
|
|
1252
|
-
});
|
|
1253
|
-
await setCurrent(root, {
|
|
1254
|
-
mission_id: id,
|
|
1255
|
-
route: 'MadSKS',
|
|
1256
|
-
route_command: '$MAD-SKS',
|
|
1257
|
-
mode: 'MADSKS',
|
|
1258
|
-
phase: 'MADSKS_TMUX_PERMISSION_ACTIVE',
|
|
1259
|
-
questions_allowed: false,
|
|
1260
|
-
implementation_allowed: true,
|
|
1261
|
-
mad_sks_active: true,
|
|
1262
|
-
mad_sks_modifier: true,
|
|
1263
|
-
mad_sks_gate_file: 'mad-sks-gate.json',
|
|
1264
|
-
mad_sks_gate_ready: true,
|
|
1265
|
-
live_server_writes_allowed: true,
|
|
1266
|
-
supabase_mcp_schema_cleanup_allowed: true,
|
|
1267
|
-
direct_execute_sql_allowed: true,
|
|
1268
|
-
normal_db_writes_allowed: true,
|
|
1269
|
-
migration_apply_allowed: true,
|
|
1270
|
-
catastrophic_safety_guard_active: true,
|
|
1271
|
-
permission_profile: gate.permission_profile,
|
|
1272
|
-
stop_gate: 'mad-sks-gate.json',
|
|
1273
|
-
prompt: gate.activated_by
|
|
1274
|
-
});
|
|
1275
|
-
return { mission_id: id, dir, gate };
|
|
1276
|
-
}
|
|
1277
|
-
|
|
1278
1166
|
async function maybePromptSksUpdateForLaunch(args = [], opts = {}) {
|
|
1279
1167
|
if (flag(args, '--json') || flag(args, '--skip-update-check') || process.env.SKS_SKIP_UPDATE_CHECK === '1') return { status: 'skipped' };
|
|
1280
1168
|
const latest = await npmPackageVersion('sneakoscope');
|
|
@@ -1712,7 +1600,7 @@ async function setup(args) {
|
|
|
1712
1600
|
console.log(`Codex App: .codex/config.toml, .codex/hooks.json, .agents/skills, .codex/agents, .codex/SNEAKOSCOPE.md`);
|
|
1713
1601
|
console.log(`Global $: ${globalSkills.status === 'installed' ? 'ok' : globalSkills.status} ${globalSkills.root || ''}`.trimEnd());
|
|
1714
1602
|
console.log(`App tools: ${appRuntime.ok ? 'ok' : 'needs setup'} Codex App=${appRuntime.app.installed ? 'ok' : 'missing'} Browser Use=${appRuntime.mcp.has_browser_use ? 'ok' : 'missing'} Computer Use=${appRuntime.mcp.has_computer_use ? 'ok' : 'missing'} Image Gen=${appRuntime.features?.image_generation ? 'ok' : 'missing'}`);
|
|
1715
|
-
console.log(`Prompt: intent-first routing, $Answer fact-check route, $DFix ultralight
|
|
1603
|
+
console.log(`Prompt: intent-first routing, $Answer fact-check route, $DFix ultralight Direct Fix route, $PPT HTML/PDF presentation route, Context7 gate`);
|
|
1716
1604
|
console.log(`Skills: .agents/skills`);
|
|
1717
1605
|
console.log(`Next: sks context7 check; sks selftest --mock; sks commands; sks dollar-commands`);
|
|
1718
1606
|
if (cliTools.codex.status === 'failed') console.log(`\nCodex CLI install failed. Run manually: npm i -g @openai/codex. ${cliTools.codex.error || ''}`.trim());
|
|
@@ -2266,7 +2154,7 @@ async function selftest() {
|
|
|
2266
2154
|
const madProfilePath = path.join(tmp, 'mad-codex-config.toml');
|
|
2267
2155
|
const madProfile = await enableMadHighProfile({ configPath: madProfilePath });
|
|
2268
2156
|
const madProfileText = await safeReadText(madProfilePath);
|
|
2269
|
-
if (madProfile.profile_name !== 'sks-mad-high' || !madProfileText.includes('sandbox_mode = "danger-full-access"') || !madProfileText.includes('approval_policy = "never"') || !madProfileText.includes('approvals_reviewer = "auto_review"') || !madProfile.launch_args.includes('--sandbox') || !madProfile.launch_args.includes('danger-full-access') || !madProfile.launch_args.includes('--ask-for-approval') || !madProfile.launch_args.includes('never') || !madProfileText.includes('model_reasoning_effort = "high"') || !madProfileText.includes('unrequested fallback implementation code')) throw new Error('selftest failed: MAD high profile is not Codex full-access high with fallback-code guard');
|
|
2157
|
+
if (madProfile.profile_name !== 'sks-mad-high' || !madProfileText.includes('sandbox_mode = "danger-full-access"') || !madProfileText.includes('approval_policy = "never"') || !madProfileText.includes('approvals_reviewer = "auto_review"') || !madProfileText.includes('service_tier = "fast"') || !madProfile.launch_args.includes('--sandbox') || !madProfile.launch_args.includes('danger-full-access') || !madProfile.launch_args.includes('--ask-for-approval') || !madProfile.launch_args.includes('never') || !madProfileText.includes('model_reasoning_effort = "high"') || !madProfileText.includes('unrequested fallback implementation code')) throw new Error('selftest failed: MAD high profile is not Codex full-access high with fallback-code guard');
|
|
2270
2158
|
if (!isMadHighLaunch(['--mad', '--high']) || isMadHighLaunch(['db', '--mad'])) throw new Error('selftest failed: MAD high launch flag parsing is not top-level only');
|
|
2271
2159
|
const workspacePlan = { session: 'sks-mad-selftest', root: tmp, codexArgs: madProfile.launch_args };
|
|
2272
2160
|
const tmuxSyntax = runTmuxLaunchPlanSyntaxCheck(workspacePlan);
|
|
@@ -2274,11 +2162,11 @@ async function selftest() {
|
|
|
2274
2162
|
const tmuxOpenArgs = buildTmuxOpenArgs(workspacePlan);
|
|
2275
2163
|
if (tmuxOpenArgs.join(' ') !== 'attach-session -t sks-mad-selftest') throw new Error('selftest failed: MAD tmux attach args are not stable by session name');
|
|
2276
2164
|
const defaultFastHighPlan = await buildTmuxLaunchPlan({ root: tmp, tmux: { ok: true, bin: 'tmux', version: '3.4' }, codex: { bin: 'codex', version: 'codex-cli 99.0.0' }, app: { ok: true } });
|
|
2277
|
-
if (defaultFastHighPlan.codexArgs.join(' ') !== '--model gpt-5.5 -c model_reasoning_effort="high"') throw new Error('selftest failed: default sks tmux launch is not fast-high');
|
|
2165
|
+
if (defaultFastHighPlan.codexArgs.join(' ') !== '--model gpt-5.5 -c service_tier="fast" -c model_reasoning_effort="high"') throw new Error('selftest failed: default sks tmux launch is not fast-high');
|
|
2278
2166
|
const forcedModelPlan = await buildTmuxLaunchPlan({ root: tmp, env: { SKS_CODEX_MODEL: 'gpt-5.4-mini', SKS_CODEX_FAST_HIGH: '0', SKS_CODEX_REASONING: 'medium' }, tmux: { ok: true, bin: 'tmux', version: '3.4' }, codex: { bin: 'codex', version: 'codex-cli 99.0.0' }, app: { ok: true } });
|
|
2279
|
-
if (forcedModelPlan.codexArgs.includes('gpt-5.4-mini') || forcedModelPlan.codexArgs.join(' ') !== '--model gpt-5.5 -c model_reasoning_effort="medium"') throw new Error('selftest failed: sks tmux launch allowed a non-GPT-5.5 model override');
|
|
2167
|
+
if (forcedModelPlan.codexArgs.includes('gpt-5.4-mini') || forcedModelPlan.codexArgs.join(' ') !== '--model gpt-5.5 -c service_tier="fast" -c model_reasoning_effort="medium"') throw new Error('selftest failed: sks tmux launch allowed a non-GPT-5.5 model override');
|
|
2280
2168
|
const explicitBadModelPlan = await buildTmuxLaunchPlan({ root: tmp, codexArgs: ['--profile', 'legacy-5.4', '--model', 'gpt-5.4-mini', '-c', 'model="gpt-5.4"', '-c', 'model_reasoning_effort="low"'], tmux: { ok: true, bin: 'tmux', version: '3.4' }, codex: { bin: 'codex', version: 'codex-cli 99.0.0' }, app: { ok: true } });
|
|
2281
|
-
if (explicitBadModelPlan.codexArgs.join(' ').includes('gpt-5.4') || explicitBadModelPlan.codexArgs.join(' ') !== '--model gpt-5.5 --profile legacy-5.4 -c model_reasoning_effort="low"') throw new Error('selftest failed: explicit tmux model override was not forced back to GPT-5.5');
|
|
2169
|
+
if (explicitBadModelPlan.codexArgs.join(' ').includes('gpt-5.4') || explicitBadModelPlan.codexArgs.join(' ') !== '--model gpt-5.5 -c service_tier="fast" --profile legacy-5.4 -c model_reasoning_effort="low"') throw new Error('selftest failed: explicit tmux model override was not forced back to GPT-5.5');
|
|
2282
2170
|
const codexExecArgs = buildCodexExecArgs({ root: tmp, prompt: 'model guard selftest', profile: 'legacy-5.4', extraArgs: ['--model=gpt-5.4-mini', '--config', 'model = "gpt-5.4"', '-c', 'model_reasoning_effort="medium"'] });
|
|
2283
2171
|
if (codexExecArgs.join(' ').includes('gpt-5.4') || !codexExecArgs.includes('gpt-5.5') || codexExecArgs.includes('--model=gpt-5.4-mini')) throw new Error('selftest failed: codex exec args allowed a non-GPT-5.5 model override');
|
|
2284
2172
|
await selftestCodexLb(tmp);
|
|
@@ -2545,8 +2433,27 @@ async function selftest() {
|
|
|
2545
2433
|
if (routePrompt('[$research](/tmp/research/SKILL.md) Codex Computer Use 도구 노출 문제를 QA루프 관점에서 연구')?.id !== 'Research') throw new Error('selftest failed: markdown-linked $Research was not treated as explicit route');
|
|
2546
2434
|
if (routePrompt('$WikiRefresh 갱신')) throw new Error('selftest failed: deprecated $WikiRefresh route still resolved');
|
|
2547
2435
|
if (routePrompt('$MAD-SKS Supabase MCP main 작업')?.id !== 'MadSKS') throw new Error('selftest failed: $MAD-SKS route did not resolve');
|
|
2436
|
+
if (routePrompt('$MAD-SKS 버튼 라벨만 바꿔줘')?.id === 'DFix') throw new Error('selftest failed: $MAD-SKS tiny label fix incorrectly routed to DFix');
|
|
2548
2437
|
if (routePrompt('$MAD-SKS $Team Supabase MCP main 작업')?.id !== 'Team') throw new Error('selftest failed: $MAD-SKS did not compose with $Team');
|
|
2438
|
+
if (routePrompt('$MAD-SKS $Team 버튼 라벨만 바꿔줘')?.id !== 'Team') throw new Error('selftest failed: $MAD-SKS $Team tiny fix did not stay on Team route');
|
|
2549
2439
|
if (routePrompt('$DB Supabase 점검 $MAD-SKS')?.id !== 'DB') throw new Error('selftest failed: trailing $MAD-SKS changed primary route');
|
|
2440
|
+
if (routePrompt('Fix the typo in README')?.id !== 'DFix') throw new Error('selftest failed: inferred typo Direct Fix did not route to DFix');
|
|
2441
|
+
if (routePrompt('Update the package version to 1.2.3')?.id !== 'DFix') throw new Error('selftest failed: inferred package-version Direct Fix did not route to DFix');
|
|
2442
|
+
if (routePrompt('package.json version만 1.2.3으로 바꿔줘')?.id !== 'DFix') throw new Error('selftest failed: inferred package.json version Direct Fix did not route to DFix');
|
|
2443
|
+
if (routePrompt('How do I fix the typo in README?')?.id !== 'Answer') throw new Error('selftest failed: how-to Direct Fix question did not route to Answer');
|
|
2444
|
+
if (routePrompt('How do I change README title?')?.id !== 'Answer') throw new Error('selftest failed: how-to README title question did not route to Answer');
|
|
2445
|
+
if (routePrompt('How do I make a settings page?')?.id !== 'Answer') throw new Error('selftest failed: how-to create question did not route to Answer');
|
|
2446
|
+
if (routePrompt('How to create a new form component?')?.id !== 'Answer') throw new Error('selftest failed: how-to form component question did not route to Answer');
|
|
2447
|
+
if (routePrompt('How can I build a modal?')?.id !== 'Answer') throw new Error('selftest failed: how-can-I build question did not route to Answer');
|
|
2448
|
+
if (routePrompt('Make a button')?.id !== 'Team') throw new Error('selftest failed: create-style button work did not route to Team');
|
|
2449
|
+
if (routePrompt('Make a button that submits the form')?.id !== 'Team') throw new Error('selftest failed: form button creation did not route to Team');
|
|
2450
|
+
if (routePrompt('Change button to submit the form')?.id !== 'Team') throw new Error('selftest failed: form button behavior change did not route to Team');
|
|
2451
|
+
if (routePrompt('버튼이 폼 제출하게 바꿔줘')?.id !== 'Team') throw new Error('selftest failed: Korean form button behavior change did not route to Team');
|
|
2452
|
+
if (routePrompt('Can you change the button to submit the form?')?.id !== 'Team') throw new Error('selftest failed: polite form button behavior request did not route to Team');
|
|
2453
|
+
if (routePrompt('Change button label to Submit')?.id !== 'DFix') throw new Error('selftest failed: button label Direct Fix did not route to DFix');
|
|
2454
|
+
if (routePrompt('Change button text to Submit')?.id !== 'DFix') throw new Error('selftest failed: button text Direct Fix did not route to DFix');
|
|
2455
|
+
if (routePrompt('Can you change the button label to Save?')?.id !== 'DFix') throw new Error('selftest failed: polite button label Direct Fix did not route to DFix');
|
|
2456
|
+
if (routePrompt('Make README generator work')?.id !== 'Team') throw new Error('selftest failed: README generator implementation did not route to Team');
|
|
2550
2457
|
const imageUxRoute = routePrompt('$Image-UX-Review localhost 화면 검수');
|
|
2551
2458
|
if (imageUxRoute?.id !== 'ImageUXReview') throw new Error('selftest failed: $Image-UX-Review did not route to ImageUXReview');
|
|
2552
2459
|
if (routePrompt('$UX-Review 스크린샷 gpt-image-2 콜아웃 리뷰')?.id !== 'ImageUXReview') throw new Error('selftest failed: $UX-Review did not route to ImageUXReview');
|
|
@@ -2939,6 +2846,16 @@ async function selftest() {
|
|
|
2939
2846
|
if (await exists(path.join(hookDfixTmp, '.sneakoscope', 'state', 'light-route-stop.json'))) throw new Error('selftest failed: $DFix hook created persistent light-route state');
|
|
2940
2847
|
const hookDfixState = await readJson(stateFile(hookDfixTmp), {});
|
|
2941
2848
|
if (String(hookDfixState.phase || '').includes('CLARIFICATION_AWAITING_ANSWERS')) throw new Error('selftest failed: $DFix state entered clarification gate');
|
|
2849
|
+
const explicitDfixDirectTmp = tmpdir();
|
|
2850
|
+
await initProject(explicitDfixDirectTmp, {});
|
|
2851
|
+
const explicitDfixDirectPayload = JSON.stringify({ cwd: explicitDfixDirectTmp, prompt: '$DFix Update the docs config wording to Direct Fix' });
|
|
2852
|
+
const explicitDfixDirectResult = await runProcess(process.execPath, [hookBin, 'hook', 'user-prompt-submit'], { cwd: explicitDfixDirectTmp, input: explicitDfixDirectPayload, env: { SKS_DISABLE_UPDATE_CHECK: '1' }, timeoutMs: 15000, maxOutputBytes: 128 * 1024 });
|
|
2853
|
+
if (explicitDfixDirectResult.code !== 0) throw new Error(`selftest failed: explicit Direct Fix docs/config hook exited ${explicitDfixDirectResult.code}: ${explicitDfixDirectResult.stderr}`);
|
|
2854
|
+
const explicitDfixDirectJson = JSON.parse(explicitDfixDirectResult.stdout);
|
|
2855
|
+
const explicitDfixDirectContext = explicitDfixDirectJson.hookSpecificOutput?.additionalContext || '';
|
|
2856
|
+
if (!explicitDfixDirectContext.includes('DFix ultralight pipeline active')) throw new Error('selftest failed: explicit Direct Fix docs/config request did not use ultralight hook');
|
|
2857
|
+
if (explicitDfixDirectContext.includes('SKS skill-first pipeline active') || explicitDfixDirectContext.includes('Mission:')) throw new Error('selftest failed: explicit Direct Fix docs/config request leaked general pipeline context');
|
|
2858
|
+
if (await exists(path.join(explicitDfixDirectTmp, '.sneakoscope', 'state', 'light-route-stop.json'))) throw new Error('selftest failed: explicit Direct Fix docs/config hook created persistent light-route state');
|
|
2942
2859
|
const inferredDfixPayload = JSON.stringify({ cwd: hookTeamTmp, prompt: '버튼 라벨 바꿔줘' });
|
|
2943
2860
|
const inferredDfixResult = await runProcess(process.execPath, [hookBin, 'hook', 'user-prompt-submit'], { cwd: hookTeamTmp, input: inferredDfixPayload, env: { SKS_DISABLE_UPDATE_CHECK: '1' }, timeoutMs: 15000, maxOutputBytes: 128 * 1024 });
|
|
2944
2861
|
if (inferredDfixResult.code !== 0) throw new Error(`selftest failed: inferred DFix hook exited ${inferredDfixResult.code}: ${inferredDfixResult.stderr}`);
|
|
@@ -3196,6 +3113,15 @@ async function selftest() {
|
|
|
3196
3113
|
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');
|
|
3197
3114
|
if (teamPlan.codex_config_required?.features?.hooks !== true || teamPlan.codex_config_required?.features?.codex_hooks === true) throw new Error('selftest failed: team plan Codex config still uses legacy hooks feature flag');
|
|
3198
3115
|
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');
|
|
3116
|
+
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');
|
|
3117
|
+
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');
|
|
3118
|
+
if (!teamPlan.roster.analysis_team.every((agent) => agent.service_tier === 'fast' && agent.reasoning_effort && agent.reasoning_profile)) throw new Error('selftest failed: analysis scouts missing dynamic Fast reasoning metadata');
|
|
3119
|
+
const simpleTeamPlan = buildTeamPlan(teamId, '$Team 간단한 코드 수정');
|
|
3120
|
+
if (!simpleTeamPlan.roster.analysis_team.some((agent) => agent.reasoning_effort === 'low')) throw new Error('selftest failed: simple Team prompt did not allow low-reasoning scouts');
|
|
3121
|
+
const toolingTeamPlan = buildTeamPlan(teamId, '$Team tmux CLI tool-calling runtime fix');
|
|
3122
|
+
if (!toolingTeamPlan.roster.analysis_team.some((agent) => agent.reasoning_effort === 'medium')) throw new Error('selftest failed: tool-heavy Team prompt did not assign medium-reasoning scouts');
|
|
3123
|
+
const researchTeamPlan = buildTeamPlan(teamId, '$Team external library research and current docs update');
|
|
3124
|
+
if (!researchTeamPlan.roster.analysis_team.some((agent) => agent.reasoning_effort === 'high')) throw new Error('selftest failed: research/docs Team prompt did not assign high-reasoning scouts');
|
|
3199
3125
|
const underProvisionedReviewCount = 2;
|
|
3200
3126
|
const blockedReviewGate = evaluateTeamReviewPolicyGate({ roleCounts: { reviewer: underProvisionedReviewCount }, agentSessions: 3, roster: { validation_team: [{ id: 'reviewer_1', role: 'reviewer' }] } });
|
|
3201
3127
|
if (blockedReviewGate.passed || !blockedReviewGate.blockers.includes('validation_team_reviewers_below_required')) throw new Error('selftest failed: team review policy gate did not block under-provisioned review');
|
|
@@ -3237,6 +3163,8 @@ async function selftest() {
|
|
|
3237
3163
|
if (roleParsed.roleCounts.executor !== 5 || roleParsed.roleCounts.reviewer !== 6 || roleParsed.agentSessions !== 6 || roleParsed.prompt !== '작업') throw new Error('selftest failed: team role-count parsing');
|
|
3238
3164
|
const openTmuxFlagParsed = parseTeamCreateArgs(['--open-tmux', '작업']);
|
|
3239
3165
|
if (openTmuxFlagParsed.prompt !== '작업') throw new Error('selftest failed: team --open-tmux leaked into prompt');
|
|
3166
|
+
const noOpenTmuxFlagParsed = parseTeamCreateArgs(['--no-open-tmux', '작업']);
|
|
3167
|
+
if (noOpenTmuxFlagParsed.prompt !== '작업') throw new Error('selftest failed: team --no-open-tmux leaked into prompt');
|
|
3240
3168
|
const roleTeamPlan = buildTeamPlan(teamId, '역할 팀 테스트', { roleCounts: roleParsed.roleCounts });
|
|
3241
3169
|
if (roleTeamPlan.roster.debate_team.length !== 5) throw new Error('selftest failed: executor role count not reflected in debate team size');
|
|
3242
3170
|
if (roleTeamPlan.roster.analysis_team.length !== 5) throw new Error('selftest failed: executor role count not reflected in analysis scout team');
|
|
@@ -3251,6 +3179,28 @@ async function selftest() {
|
|
|
3251
3179
|
if (teamLaneStyle('analysis_scout_1').role !== 'scout' || teamLaneStyle('executor_1').role !== 'execution' || teamLaneStyle('reviewer_1').role !== 'review') throw new Error('selftest failed: Team tmux role palette did not classify lane roles');
|
|
3252
3180
|
if (!String(tmuxTeam.cleanup_policy || '').includes('mark-complete') || !tmuxTeam.lanes.every((entry) => entry.style?.color && entry.title)) throw new Error('selftest failed: Team tmux view did not expose color/title metadata and cleanup policy');
|
|
3253
3181
|
if (tmuxTeam.session !== `sks-team-${teamId}` || !tmuxTeam.attach_command?.includes(`sks-team-${teamId}`)) throw new Error('selftest failed: Team tmux session is not named for visibility');
|
|
3182
|
+
const fakeTmuxDir = path.join(tmp, 'fake-tmux');
|
|
3183
|
+
await ensureDir(fakeTmuxDir);
|
|
3184
|
+
const fakeTmuxLog = path.join(fakeTmuxDir, 'tmux.log');
|
|
3185
|
+
const fakeTmuxBin = path.join(fakeTmuxDir, 'tmux');
|
|
3186
|
+
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`);
|
|
3187
|
+
await fsp.chmod(fakeTmuxBin, 0o755);
|
|
3188
|
+
const previousFakeTmuxLog = process.env.SKS_FAKE_TMUX_LOG;
|
|
3189
|
+
process.env.SKS_FAKE_TMUX_LOG = fakeTmuxLog;
|
|
3190
|
+
const recreatedTmux = await createTmuxSession({ root: tmp, session: 'sks-existing-selftest', tmux: { bin: fakeTmuxBin }, codex: { bin: process.execPath } }, [
|
|
3191
|
+
{ cwd: tmp, command: 'pwd', role: 'overview' },
|
|
3192
|
+
{ cwd: tmp, command: 'pwd', role: 'lane' }
|
|
3193
|
+
], { recreate: true });
|
|
3194
|
+
const fakeTmuxLogText = await safeReadText(fakeTmuxLog);
|
|
3195
|
+
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');
|
|
3196
|
+
await writeTextAtomic(fakeTmuxLog, '');
|
|
3197
|
+
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' });
|
|
3198
|
+
const madTmuxLogText = await safeReadText(fakeTmuxLog);
|
|
3199
|
+
if (!madCockpit.created || madCockpit.opened?.panes?.length !== 3 || !madTmuxLogText.includes('new-session') || !madTmuxLogText.includes('split-window')) throw new Error('selftest failed: MAD tmux launch did not create a multi-pane cockpit');
|
|
3200
|
+
if (previousFakeTmuxLog === undefined) delete process.env.SKS_FAKE_TMUX_LOG;
|
|
3201
|
+
else process.env.SKS_FAKE_TMUX_LOG = previousFakeTmuxLog;
|
|
3202
|
+
const codexLaunchArgs = defaultCodexLaunchArgs({ SKS_CODEX_REASONING: 'low' }).join(' ');
|
|
3203
|
+
if (!codexLaunchArgs.includes('service_tier="fast"') || !codexLaunchArgs.includes('model_reasoning_effort="low"')) throw new Error('selftest failed: Codex tmux launch args do not force Fast service tier plus dynamic reasoning');
|
|
3254
3204
|
await initTeamLive(teamId, teamDir, '역할 팀 테스트', { agentSessions: roleTeamPlan.agent_session_count, roleCounts: roleTeamPlan.role_counts, roster: roleTeamPlan.roster });
|
|
3255
3205
|
const teamWatch = await renderTeamWatch(teamDir, { missionId: teamId });
|
|
3256
3206
|
if (!roleTeamPlan.roster.analysis_team.every((agent) => teamWatch.includes(`- ${agent.id}:`))) throw new Error('selftest failed: Team watch overview collapsed numbered analysis scout lanes');
|
|
@@ -3258,6 +3208,9 @@ async function selftest() {
|
|
|
3258
3208
|
if (routeReasoning(routePrompt('$From-Chat-IMG 채팅 이미지 작업'), '$From-Chat-IMG 채팅 이미지 작업').effort !== 'xhigh') throw new Error('selftest failed: From-Chat-IMG reasoning not xhigh');
|
|
3259
3209
|
if (routeReasoning(routePrompt('$Computer-Use localhost UI smoke'), '$Computer-Use localhost UI smoke').effort !== 'low') throw new Error('selftest failed: Computer Use fast lane reasoning not low');
|
|
3260
3210
|
if (routeReasoning(routePrompt('$DB migration'), '$DB migration').effort !== 'high') throw new Error('selftest failed: logical reasoning not high');
|
|
3211
|
+
if (routeReasoning(routePrompt('$Team 간단한 코드 수정'), '$Team 간단한 코드 수정').effort !== 'low') throw new Error('selftest failed: simple Team reasoning not low');
|
|
3212
|
+
if (routeReasoning(routePrompt('$Team tmux CLI tool-calling fix'), '$Team tmux CLI tool-calling fix').effort !== 'medium') throw new Error('selftest failed: tool-heavy Team reasoning not medium');
|
|
3213
|
+
if (routeReasoning(routePrompt('$Team library research current docs'), '$Team library research current docs').effort !== 'high') throw new Error('selftest failed: research/docs Team reasoning not high');
|
|
3261
3214
|
const lowReasoning = routeReasoning({ id: 'LowSmoke', reasoningPolicy: 'low' }, 'small metadata read');
|
|
3262
3215
|
if (lowReasoning.effort !== 'low' || lowReasoning.profile !== 'sks-task-low') throw new Error('selftest failed: low reasoning did not route to sks-task-low');
|
|
3263
3216
|
const forensicEffort = selectEffort({ mission_id: 'selftest', task_id: 'TASK-IMG', route: 'from-chat-img', prompt: '$From-Chat-IMG screenshot match' });
|
|
@@ -3296,6 +3249,8 @@ async function selftest() {
|
|
|
3296
3249
|
if (routePrompt('근데 왜 팀원 구성을 안하고 작업을 하는 경우가 이렇게 많지?')?.id !== 'Team') throw new Error('selftest failed: question-shaped Team complaint did not route to Team');
|
|
3297
3250
|
if (routePrompt('$DF button label')) throw new Error('selftest failed: deprecated $DF route still resolved');
|
|
3298
3251
|
if (routePrompt('implement feature')?.id !== 'Team') throw new Error('selftest failed: implementation prompt did not default to Team');
|
|
3252
|
+
const broadMadTeamGoalPrompt = 'sks --mad tmux multi pane scout reasoning commit push $team $goal';
|
|
3253
|
+
if (routePrompt(broadMadTeamGoalPrompt)?.id !== 'Team') throw new Error('selftest failed: broad MAD/Team/Goal tmux request was misrouted away from Team');
|
|
3299
3254
|
if (routePrompt('$SKS implement feature')?.id !== 'Team') throw new Error('selftest failed: $SKS implementation prompt did not promote to Team');
|
|
3300
3255
|
if (routePrompt('$From-Chat-IMG 채팅 기록 이미지와 첨부 이미지로 고객사 요청 수정 작업 수행해줘')?.id !== 'Team') throw new Error('selftest failed: explicit chat capture client work did not promote to Team');
|
|
3301
3256
|
if (routePrompt('$Computer-Use localhost 화면 빠르게 검증해줘')?.id !== 'ComputerUse') throw new Error('selftest failed: $Computer-Use did not route to ComputerUse fast lane');
|