sneakoscope 0.7.6 → 0.7.13
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 +31 -27
- package/package.json +2 -2
- package/src/cli/context7-command.mjs +163 -0
- package/src/cli/install-helpers.mjs +325 -0
- package/src/cli/main.mjs +245 -509
- package/src/cli/maintenance-commands.mjs +31 -31
- package/src/core/artifact-schemas.mjs +6 -6
- package/src/core/codex-app.mjs +1 -1
- package/src/core/db-safety.mjs +1 -1
- package/src/core/evaluation.mjs +3 -3
- package/src/core/fsx.mjs +1 -1
- package/src/core/init.mjs +20 -14
- package/src/core/pipeline.mjs +14 -3
- package/src/core/ppt.mjs +694 -0
- package/src/core/questions.mjs +51 -7
- package/src/core/routes.mjs +60 -10
- package/src/core/team-live.mjs +8 -8
- package/src/core/tmux-ui.mjs +447 -0
- package/src/core/warp-ui.mjs +0 -557
package/src/cli/main.mjs
CHANGED
|
@@ -23,9 +23,22 @@ import { rustInfo } from '../core/rust-accelerator.mjs';
|
|
|
23
23
|
import { renderCartridge, validateCartridge, driftCartridge, snapshotCartridge } from '../core/gx-renderer.mjs';
|
|
24
24
|
import { defaultEvaluationScenario, runEvaluationBenchmark } from '../core/evaluation.mjs';
|
|
25
25
|
import { buildResearchPrompt, evaluateResearchGate, writeMockResearchResult, writeResearchPlan } from '../core/research.mjs';
|
|
26
|
+
import {
|
|
27
|
+
PPT_AUDIENCE_STRATEGY_ARTIFACT,
|
|
28
|
+
PPT_CLEANUP_REPORT_ARTIFACT,
|
|
29
|
+
PPT_GATE_ARTIFACT,
|
|
30
|
+
PPT_HTML_ARTIFACT,
|
|
31
|
+
PPT_PARALLEL_REPORT_ARTIFACT,
|
|
32
|
+
PPT_PDF_ARTIFACT,
|
|
33
|
+
PPT_RENDER_REPORT_ARTIFACT,
|
|
34
|
+
PPT_SOURCE_HTML_DIR,
|
|
35
|
+
PPT_TEMP_DIR,
|
|
36
|
+
writePptBuildArtifacts,
|
|
37
|
+
writePptRouteArtifacts
|
|
38
|
+
} from '../core/ppt.mjs';
|
|
26
39
|
import { contextCapsule } from '../core/triwiki-attention.mjs';
|
|
27
40
|
import { rgbaKey, rgbaToWikiCoord, validateWikiCoordinateIndex } from '../core/wiki-coordinate.mjs';
|
|
28
|
-
import { ALLOWED_REASONING_EFFORTS, CODEX_COMPUTER_USE_EVIDENCE_SOURCE, CODEX_COMPUTER_USE_ONLY_POLICY, COMMAND_CATALOG, DOLLAR_COMMAND_ALIASES, DOLLAR_COMMANDS, DOLLAR_SKILL_NAMES, FROM_CHAT_IMG_CHECKLIST_ARTIFACT, FROM_CHAT_IMG_COVERAGE_ARTIFACT, FROM_CHAT_IMG_QA_LOOP_ARTIFACT, FROM_CHAT_IMG_SOURCE_INVENTORY_ARTIFACT, FROM_CHAT_IMG_TEMP_TRIWIKI_ARTIFACT, FROM_CHAT_IMG_TEMP_TRIWIKI_SESSIONS, FROM_CHAT_IMG_VISUAL_MAP_ARTIFACT, FROM_CHAT_IMG_WORK_ORDER_ARTIFACT, RECOMMENDED_SKILLS, ROUTES, USAGE_TOPICS, context7ConfigToml, hasContext7ConfigText, hasFromChatImgSignal, looksLikeAnswerOnlyRequest, noUnrequestedFallbackCodePolicyText, reflectionRequiredForRoute, reasoningInstruction, routePrompt, routeReasoning, routeRequiresSubagents, speedLanePolicyText, stackCurrentDocsPolicy, triwikiContextTracking } from '../core/routes.mjs';
|
|
41
|
+
import { ALLOWED_REASONING_EFFORTS, CODEX_APP_IMAGE_GENERATION_DOC_URL, CODEX_COMPUTER_USE_EVIDENCE_SOURCE, CODEX_COMPUTER_USE_ONLY_POLICY, COMMAND_CATALOG, DOLLAR_COMMAND_ALIASES, DOLLAR_COMMANDS, DOLLAR_SKILL_NAMES, FROM_CHAT_IMG_CHECKLIST_ARTIFACT, FROM_CHAT_IMG_COVERAGE_ARTIFACT, FROM_CHAT_IMG_QA_LOOP_ARTIFACT, FROM_CHAT_IMG_SOURCE_INVENTORY_ARTIFACT, FROM_CHAT_IMG_TEMP_TRIWIKI_ARTIFACT, FROM_CHAT_IMG_TEMP_TRIWIKI_SESSIONS, FROM_CHAT_IMG_VISUAL_MAP_ARTIFACT, FROM_CHAT_IMG_WORK_ORDER_ARTIFACT, GETDESIGN_REFERENCE, RECOMMENDED_SKILLS, ROUTES, USAGE_TOPICS, context7ConfigToml, hasContext7ConfigText, hasFromChatImgSignal, looksLikeAnswerOnlyRequest, noUnrequestedFallbackCodePolicyText, reflectionRequiredForRoute, reasoningInstruction, routePrompt, routeReasoning, routeRequiresSubagents, speedLanePolicyText, stackCurrentDocsPolicy, triwikiContextTracking } from '../core/routes.mjs';
|
|
29
42
|
import { PIPELINE_PLAN_ARTIFACT, buildPipelinePlan, context7Evidence, evaluateStop, recordContext7Evidence, recordSubagentEvidence, validatePipelinePlan, writePipelinePlan } from '../core/pipeline.mjs';
|
|
30
43
|
import { TEAM_DECOMPOSITION_ARTIFACT, TEAM_GRAPH_ARTIFACT, TEAM_INBOX_DIR, TEAM_RUNTIME_TASKS_ARTIFACT, validateTeamRuntimeArtifacts, writeTeamRuntimeArtifacts } from '../core/team-dag.mjs';
|
|
31
44
|
import { appendTeamEvent, initTeamLive, parseTeamSpecText, readTeamDashboard, readTeamLive, readTeamTranscriptTail, renderTeamAgentLane } from '../core/team-live.mjs';
|
|
@@ -43,8 +56,10 @@ import { buildPromptContext } from '../core/prompt-context-builder.mjs';
|
|
|
43
56
|
import { renderTeamDashboardState, writeTeamDashboardState } from '../core/team-dashboard-renderer.mjs';
|
|
44
57
|
import { GOAL_WORKFLOW_ARTIFACT } from '../core/goal-workflow.mjs';
|
|
45
58
|
import { CODEX_APP_DOCS_URL, codexAppIntegrationStatus, formatCodexAppStatus } from '../core/codex-app.mjs';
|
|
46
|
-
import {
|
|
59
|
+
import { buildTmuxLaunchPlan, buildTmuxOpenArgs, createTmuxSession, isTmuxShellSession, runTmuxLaunchPlanSyntaxCheck, tmuxReadiness, tmuxStatusKind, defaultTmuxSessionName, formatTmuxBanner, launchTmuxTeamView, launchTmuxUi, platformTmuxInstallHint, runTmuxStatus, sanitizeTmuxSessionName, teamLaneStyle } from '../core/tmux-ui.mjs';
|
|
47
60
|
import { autoReviewProfileName, autoReviewStatus, autoReviewSummary, enableAutoReview, disableAutoReview, enableMadHighProfile, madHighProfileName } from '../core/auto-review.mjs';
|
|
61
|
+
import { context7Command } from './context7-command.mjs';
|
|
62
|
+
import { askPostinstallQuestion, checkContext7, checkRequiredSkills, ensureCodexCliTool, ensureGlobalCodexSkillsDuringInstall, ensureProjectContext7Config, ensureRelatedCliTools, ensureSksCommandDuringInstall, globalCodexSkillsRoot, postinstall, postinstallBootstrapDecision } from './install-helpers.mjs';
|
|
48
63
|
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';
|
|
49
64
|
|
|
50
65
|
const flag = (args, name) => args.includes(name);
|
|
@@ -68,9 +83,9 @@ export async function main(args) {
|
|
|
68
83
|
if (!cmd) return help();
|
|
69
84
|
if (cmd === '--help' || cmd === '-h') return help();
|
|
70
85
|
if (cmd === '--version' || cmd === '-v' || cmd === 'version') return version();
|
|
71
|
-
if (cmd === 'postinstall') return postinstall();
|
|
86
|
+
if (cmd === 'postinstall') return postinstall({ bootstrap });
|
|
72
87
|
if (cmd === 'wizard' || cmd === 'ui') return wizard(tail);
|
|
73
|
-
if (cmd === '
|
|
88
|
+
if (cmd === 'tmux') return !sub || String(sub).startsWith('--') ? tmuxCommand('check', tail) : tmuxCommand(sub, rest);
|
|
74
89
|
if (cmd === 'auto-review' || cmd === 'autoreview') return autoReviewCommand(sub, rest);
|
|
75
90
|
if (cmd === 'update-check') return updateCheck(tail);
|
|
76
91
|
if (cmd === 'help') return help(tail);
|
|
@@ -84,7 +99,8 @@ export async function main(args) {
|
|
|
84
99
|
if (cmd === 'dollar-commands' || cmd === 'dollars' || cmd === '$') return dollarCommands(tail);
|
|
85
100
|
if (String(cmd).toLowerCase() === 'dfix') return dfixHelp();
|
|
86
101
|
if (cmd === 'qa-loop') return qaLoopCommand(sub, rest);
|
|
87
|
-
if (cmd === '
|
|
102
|
+
if (cmd === 'ppt') return pptCommand(sub, rest);
|
|
103
|
+
if (cmd === 'context7') return context7Command(sub, rest);
|
|
88
104
|
if (cmd === 'pipeline') return pipeline(sub, rest);
|
|
89
105
|
if (cmd === 'guard') return guard(sub, rest);
|
|
90
106
|
if (cmd === 'conflicts') return conflicts(sub, rest);
|
|
@@ -135,19 +151,21 @@ Usage:
|
|
|
135
151
|
sks root [--json]
|
|
136
152
|
sks quickstart
|
|
137
153
|
sks bootstrap [--install-scope global|project] [--local-only] [--json]
|
|
138
|
-
sks deps check|install [
|
|
154
|
+
sks deps check|install [tmux|codex|context7|all] [--yes] [--json]
|
|
139
155
|
sks codex-app
|
|
140
156
|
sks --mad [--high]
|
|
141
157
|
sks auto-review status|enable|start [--high]
|
|
142
158
|
sks --Auto-review [--high]
|
|
143
|
-
sks
|
|
144
|
-
sks
|
|
159
|
+
sks tmux open [--workspace name]
|
|
160
|
+
sks tmux status [--once]
|
|
145
161
|
sks dollar-commands [--json]
|
|
146
162
|
sks dfix
|
|
147
163
|
sks qa-loop prepare "target"
|
|
148
164
|
sks qa-loop answer <mission-id|latest> <answers.json>
|
|
149
165
|
sks qa-loop run <mission-id|latest> [--mock] [--max-cycles N]
|
|
150
166
|
sks qa-loop status <mission-id|latest>
|
|
167
|
+
sks ppt build <mission-id|latest> [--json]
|
|
168
|
+
sks ppt status <mission-id|latest> [--json]
|
|
151
169
|
sks context7 check|setup|tools|resolve|docs|evidence ...
|
|
152
170
|
sks pipeline status|resume|plan [--json] [--proof-field]
|
|
153
171
|
sks pipeline answer <mission-id|latest> <answers.json>
|
|
@@ -168,7 +186,7 @@ Usage:
|
|
|
168
186
|
sks team log|tail|watch|lane|status|dashboard [mission-id|latest]
|
|
169
187
|
sks team event [mission-id|latest] --agent <name> --phase <phase> --message "..."
|
|
170
188
|
sks team message [mission-id|latest] --from <agent> --to <agent|all> --message "..."
|
|
171
|
-
sks team cleanup-
|
|
189
|
+
sks team cleanup-tmux [mission-id|latest]
|
|
172
190
|
sks research prepare "topic" [--depth frontier]
|
|
173
191
|
sks research run <mission-id|latest> [--mock] [--max-cycles N]
|
|
174
192
|
sks research status <mission-id|latest>
|
|
@@ -228,230 +246,6 @@ function isMadHighLaunch(args = []) {
|
|
|
228
246
|
return /^--(?:mad|MAD|mad-sks)$/i.test(String(args[0] || ''));
|
|
229
247
|
}
|
|
230
248
|
|
|
231
|
-
async function postinstall() {
|
|
232
|
-
const installRoot = path.resolve(process.env.INIT_CWD || process.cwd());
|
|
233
|
-
const conflictScan = await scanHarnessConflicts(installRoot);
|
|
234
|
-
if (conflictScan.hard_block) {
|
|
235
|
-
await postinstallHarnessConflictNotice(conflictScan);
|
|
236
|
-
return;
|
|
237
|
-
}
|
|
238
|
-
console.log('\nSKS installed.');
|
|
239
|
-
const shim = await ensureSksCommandDuringInstall();
|
|
240
|
-
if (shim.status === 'present') console.log(`SKS command: available (${shim.command}).`);
|
|
241
|
-
else if (shim.status === 'created') console.log(`SKS command: shim created at ${shim.command}.`);
|
|
242
|
-
else if (shim.status === 'created_not_on_path') console.log(`SKS command: shim created at ${shim.command}. Add ${path.dirname(shim.command)} to PATH, or run npx -y -p sneakoscope sks.`);
|
|
243
|
-
else if (shim.status === 'skipped') console.log(`SKS command: skipped (${shim.reason}).`);
|
|
244
|
-
else console.log(`SKS command: shim unavailable. Use npx -y -p sneakoscope sks. ${shim.error || ''}`.trim());
|
|
245
|
-
const context7Install = await ensureGlobalContext7DuringInstall();
|
|
246
|
-
if (context7Install.status === 'present') console.log('Context7 MCP: already configured for Codex.');
|
|
247
|
-
else if (context7Install.status === 'installed') console.log('Context7 MCP: configured for Codex.');
|
|
248
|
-
else if (context7Install.status === 'codex_missing') console.log('Context7 MCP: Codex CLI missing. Install @openai/codex or set SKS_CODEX_BIN, then run `sks context7 setup --scope global` or `sks setup` in a project.');
|
|
249
|
-
else if (context7Install.status === 'skipped') console.log(`Context7 MCP: skipped (${context7Install.reason}).`);
|
|
250
|
-
else if (context7Install.status === 'failed') console.log(`Context7 MCP: auto setup failed. Run \`sks context7 setup --scope global\` or \`sks setup\`. ${context7Install.error || ''}`.trim());
|
|
251
|
-
const globalSkills = await ensureGlobalCodexSkillsDuringInstall();
|
|
252
|
-
if (globalSkills.status === 'installed') console.log(`Codex App global $ skills: installed in ${globalSkills.root} (${globalSkills.installed_count} skills).`);
|
|
253
|
-
else if (globalSkills.status === 'partial') console.log(`Codex App global $ skills: partial in ${globalSkills.root}; missing ${globalSkills.missing_skills.join(', ')}. Run \`sks doctor --fix\`.`);
|
|
254
|
-
else if (globalSkills.status === 'skipped') console.log(`Codex App global $ skills: skipped (${globalSkills.reason}).`);
|
|
255
|
-
else if (globalSkills.status === 'failed') console.log(`Codex App global $ skills: auto setup failed. Run \`sks doctor --fix\`. ${globalSkills.error || ''}`.trim());
|
|
256
|
-
const bootstrapDecision = await postinstallBootstrapDecision(installRoot);
|
|
257
|
-
if (bootstrapDecision.run) {
|
|
258
|
-
console.log(`SKS bootstrap: ${bootstrapDecision.reason}.`);
|
|
259
|
-
await runPostinstallBootstrap(installRoot);
|
|
260
|
-
return;
|
|
261
|
-
}
|
|
262
|
-
console.log('\nNext:');
|
|
263
|
-
console.log(' sks bootstrap');
|
|
264
|
-
console.log(`\nSKS bootstrap was not run automatically: ${bootstrapDecision.reason}.`);
|
|
265
|
-
console.log('This initializes the current project, installs SKS Codex App skills, verifies Codex App/Context7 readiness, and checks warp/runtime dependencies.');
|
|
266
|
-
console.log('Dependency repair: sks deps check; sks deps install warp');
|
|
267
|
-
console.log('Open runtime after readiness is green: sks\n');
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
async function postinstallHarnessConflictNotice(conflictScan) {
|
|
271
|
-
console.log('\nSneakoscope Codex package installed, but SKS setup is blocked.');
|
|
272
|
-
console.log(formatHarnessConflictReport(conflictScan, { includePrompt: false }));
|
|
273
|
-
console.log('\nWhat this means: npm can finish installing the package, but `sks setup` and `sks doctor --fix` will refuse to activate SKS until the conflicting harness is removed with human approval.');
|
|
274
|
-
console.log('No files were removed by postinstall.');
|
|
275
|
-
console.log('Cleanup requires a human-approved Codex App session. Recommended model: GPT-5.5, reasoning: high.');
|
|
276
|
-
if (shouldAskPostinstallQuestion()) {
|
|
277
|
-
const answer = await askPostinstallQuestion('Show the cleanup prompt now? [y/N] ');
|
|
278
|
-
if (/^(y|yes|예|네|응)$/i.test(answer.trim())) {
|
|
279
|
-
console.log('\nCleanup prompt:\n');
|
|
280
|
-
console.log(llmHarnessCleanupPrompt(conflictScan));
|
|
281
|
-
} else {
|
|
282
|
-
console.log('Cleanup prompt skipped. You can print it later with: sks conflicts prompt');
|
|
283
|
-
}
|
|
284
|
-
} else {
|
|
285
|
-
console.log('Print the cleanup prompt later with: sks conflicts prompt');
|
|
286
|
-
}
|
|
287
|
-
console.log('After approved cleanup, rerun: sks setup && sks doctor --fix && sks selftest --mock\n');
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
function shouldAskPostinstallQuestion() {
|
|
291
|
-
if (process.env.SKS_POSTINSTALL_PROMPT === '1') return true;
|
|
292
|
-
return Boolean(input.isTTY && output.isTTY && process.env.CI !== 'true' && process.env.SKS_POSTINSTALL_NO_PROMPT !== '1');
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
async function postinstallBootstrapDecision(root) {
|
|
296
|
-
if (process.env.SKS_POSTINSTALL_NO_BOOTSTRAP === '1') return { run: false, reason: 'SKS_POSTINSTALL_NO_BOOTSTRAP=1' };
|
|
297
|
-
if (process.env.SKS_POSTINSTALL_BOOTSTRAP === '0') return { run: false, reason: 'SKS_POSTINSTALL_BOOTSTRAP=0' };
|
|
298
|
-
const candidate = await isProjectSetupCandidate(path.resolve(root || process.cwd()));
|
|
299
|
-
if (!candidate && process.env.SKS_POSTINSTALL_BOOTSTRAP !== '1') return { run: false, reason: 'no project marker found in install cwd' };
|
|
300
|
-
if (process.env.SKS_POSTINSTALL_BOOTSTRAP === '1') return { run: true, reason: 'forced by SKS_POSTINSTALL_BOOTSTRAP=1' };
|
|
301
|
-
return { run: true, reason: 'auto-running sks setup --bootstrap --install-scope global --force' };
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
async function runPostinstallBootstrap(root) {
|
|
305
|
-
const previousCwd = process.cwd();
|
|
306
|
-
process.chdir(path.resolve(root || previousCwd));
|
|
307
|
-
try {
|
|
308
|
-
await bootstrap(['--from-postinstall', '--install-scope', 'global', '--force']);
|
|
309
|
-
} finally {
|
|
310
|
-
process.chdir(previousCwd);
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
async function askPostinstallQuestion(question) {
|
|
315
|
-
const rl = readline.createInterface({ input, output });
|
|
316
|
-
try {
|
|
317
|
-
return await rl.question(question);
|
|
318
|
-
} finally {
|
|
319
|
-
rl.close();
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
async function ensureSksCommandDuringInstall(opts = {}) {
|
|
324
|
-
if (process.env.SKS_SKIP_POSTINSTALL_SHIM === '1' && !opts.force) return { status: 'skipped', reason: 'SKS_SKIP_POSTINSTALL_SHIM=1' };
|
|
325
|
-
const pathEnv = opts.pathEnv ?? process.env.PATH ?? '';
|
|
326
|
-
const existing = await findCommandOnPath('sks', pathEnv);
|
|
327
|
-
if (isStableSksBin(existing)) return { status: 'present', command: existing };
|
|
328
|
-
const nodeBin = opts.nodeBin || process.execPath;
|
|
329
|
-
const target = opts.target || path.join(packageRoot(), 'bin', 'sks.mjs');
|
|
330
|
-
const dirs = candidateShimDirs(pathEnv, opts.home || process.env.HOME);
|
|
331
|
-
const script = process.platform === 'win32'
|
|
332
|
-
? `@echo off\r\n"${nodeBin}" "${target}" %*\r\n`
|
|
333
|
-
: `#!/bin/sh\nexec "${nodeBin}" "${target}" "$@"\n`;
|
|
334
|
-
const suffix = process.platform === 'win32' ? '.cmd' : '';
|
|
335
|
-
let createdFallback = null;
|
|
336
|
-
let lastError = '';
|
|
337
|
-
for (const entry of dirs) {
|
|
338
|
-
const dest = path.join(entry.dir, `sks${suffix}`);
|
|
339
|
-
try {
|
|
340
|
-
await ensureDir(entry.dir);
|
|
341
|
-
await writeTextAtomic(dest, script);
|
|
342
|
-
if (process.platform !== 'win32') await fsp.chmod(dest, 0o755).catch(() => {});
|
|
343
|
-
if (entry.onPath) return { status: 'created', command: dest };
|
|
344
|
-
createdFallback ||= dest;
|
|
345
|
-
} catch (err) {
|
|
346
|
-
lastError = err.message;
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
if (createdFallback) return { status: 'created_not_on_path', command: createdFallback };
|
|
350
|
-
return { status: 'failed', error: lastError };
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
function candidateShimDirs(pathEnv, home) {
|
|
354
|
-
const seen = new Set();
|
|
355
|
-
const out = [];
|
|
356
|
-
for (const raw of String(pathEnv || '').split(path.delimiter).filter(Boolean)) {
|
|
357
|
-
const dir = path.resolve(raw);
|
|
358
|
-
if (seen.has(dir) || isTransientNpmBinPath(dir)) continue;
|
|
359
|
-
seen.add(dir);
|
|
360
|
-
out.push({ dir, onPath: true });
|
|
361
|
-
}
|
|
362
|
-
for (const raw of [home && path.join(home, '.local', 'bin'), home && path.join(home, 'bin')].filter(Boolean)) {
|
|
363
|
-
const dir = path.resolve(raw);
|
|
364
|
-
if (seen.has(dir)) continue;
|
|
365
|
-
seen.add(dir);
|
|
366
|
-
out.push({ dir, onPath: false });
|
|
367
|
-
}
|
|
368
|
-
return out;
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
async function findCommandOnPath(name, pathEnv) {
|
|
372
|
-
const suffixes = process.platform === 'win32' ? ['.cmd', '.exe', ''] : [''];
|
|
373
|
-
for (const dir of String(pathEnv || '').split(path.delimiter).filter(Boolean)) {
|
|
374
|
-
for (const suffix of suffixes) {
|
|
375
|
-
const candidate = path.join(dir, `${name}${suffix}`);
|
|
376
|
-
if (await exists(candidate)) return candidate;
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
return null;
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
async function ensureGlobalContext7DuringInstall() {
|
|
383
|
-
if (process.env.SKS_SKIP_POSTINSTALL_CONTEXT7 === '1') return { status: 'skipped', reason: 'SKS_SKIP_POSTINSTALL_CONTEXT7=1' };
|
|
384
|
-
const codex = await getCodexInfo().catch(() => ({}));
|
|
385
|
-
if (!codex.bin) return { status: 'codex_missing' };
|
|
386
|
-
const list = await runProcess(codex.bin, ['mcp', 'list'], { timeoutMs: 8000, maxOutputBytes: 32 * 1024 }).catch((err) => ({ code: 1, stderr: err.message, stdout: '' }));
|
|
387
|
-
if (list.code === 0 && /context7/i.test(`${list.stdout}\n${list.stderr}`)) return { status: 'present' };
|
|
388
|
-
const add = await runProcess(codex.bin, ['mcp', 'add', 'context7', '--', 'npx', '-y', '@upstash/context7-mcp@latest'], { timeoutMs: 30000, maxOutputBytes: 64 * 1024 }).catch((err) => ({ code: 1, stderr: err.message, stdout: '' }));
|
|
389
|
-
if (add.code === 0) return { status: 'installed' };
|
|
390
|
-
return { status: 'failed', error: `${add.stderr || add.stdout || 'codex mcp add failed'}`.trim() };
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
async function ensureGlobalCodexSkillsDuringInstall(opts = {}) {
|
|
394
|
-
if (process.env.SKS_SKIP_POSTINSTALL_GLOBAL_SKILLS === '1' && !opts.force) return { status: 'skipped', reason: 'SKS_SKIP_POSTINSTALL_GLOBAL_SKILLS=1' };
|
|
395
|
-
const home = opts.home || process.env.HOME || os.homedir();
|
|
396
|
-
if (!home) return { status: 'skipped', reason: 'home directory unavailable' };
|
|
397
|
-
const root = globalCodexSkillsRoot(home);
|
|
398
|
-
try {
|
|
399
|
-
const install = await installSkills(home);
|
|
400
|
-
const skills = await checkRequiredSkills(home, root);
|
|
401
|
-
return { status: skills.ok ? 'installed' : 'partial', root, installed_count: install.installed_skills.length, removed_aliases: install.removed_agent_skill_aliases, missing_skills: skills.missing };
|
|
402
|
-
} catch (err) {
|
|
403
|
-
return { status: 'failed', root, error: err.message };
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
async function ensureRelatedCliTools(args = []) {
|
|
408
|
-
const skip = flag(args, '--skip-cli-tools') || process.env.SKS_SKIP_CLI_TOOLS === '1';
|
|
409
|
-
const codex = await ensureCodexCliTool({ skip });
|
|
410
|
-
const warp = await warpReadiness().catch((err) => ({ ok: false, version: null, error: err.message }));
|
|
411
|
-
return {
|
|
412
|
-
codex,
|
|
413
|
-
warp: {
|
|
414
|
-
ok: Boolean(warp.ok),
|
|
415
|
-
app: warp.app || null,
|
|
416
|
-
cli: warp.cli || null,
|
|
417
|
-
version: warp.version || null,
|
|
418
|
-
launch_config_dir: warp.launch_config_dir || null,
|
|
419
|
-
uri_scheme: warp.uri_scheme || null,
|
|
420
|
-
install_hint: warp.ok ? null : platformWarpInstallHint(),
|
|
421
|
-
error: warp.error || null
|
|
422
|
-
}
|
|
423
|
-
};
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
async function ensureCodexCliTool({ skip = false } = {}) {
|
|
427
|
-
if (skip) return { status: 'skipped', reason: 'SKS_SKIP_CLI_TOOLS=1 or --skip-cli-tools' };
|
|
428
|
-
const before = await getCodexInfo().catch(() => ({}));
|
|
429
|
-
if (before.bin) return { status: 'present', bin: before.bin, version: before.version || null };
|
|
430
|
-
const npmBin = await which('npm');
|
|
431
|
-
if (!npmBin) return { status: 'failed', error: 'npm not found on PATH; install Codex CLI manually with npm i -g @openai/codex@latest.' };
|
|
432
|
-
const install = await runProcess(npmBin, ['i', '-g', '@openai/codex@latest'], {
|
|
433
|
-
timeoutMs: 120000,
|
|
434
|
-
maxOutputBytes: 128 * 1024
|
|
435
|
-
}).catch((err) => ({ code: 1, stdout: '', stderr: err.message }));
|
|
436
|
-
if (install.code !== 0) {
|
|
437
|
-
return { status: 'failed', error: `${install.stderr || install.stdout || 'npm i -g @openai/codex@latest failed'}`.trim() };
|
|
438
|
-
}
|
|
439
|
-
const after = await getCodexInfo().catch(() => ({}));
|
|
440
|
-
return {
|
|
441
|
-
status: after.bin ? 'installed' : 'installed_not_on_path',
|
|
442
|
-
bin: after.bin || null,
|
|
443
|
-
version: after.version || null,
|
|
444
|
-
hint: after.bin ? null : 'npm completed, but codex is not on PATH. Restart the shell or set SKS_CODEX_BIN.'
|
|
445
|
-
};
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
async function isProjectSetupCandidate(root) {
|
|
449
|
-
for (const marker of ['package.json', '.git', '.codex', '.agents', 'AGENTS.md']) {
|
|
450
|
-
if (await exists(path.join(root, marker))) return true;
|
|
451
|
-
}
|
|
452
|
-
return false;
|
|
453
|
-
}
|
|
454
|
-
|
|
455
249
|
async function wizard(args = []) {
|
|
456
250
|
if (!shouldShowWizard() && !flag(args, '--force')) return help();
|
|
457
251
|
const rl = readline.createInterface({ input, output });
|
|
@@ -532,7 +326,7 @@ async function updateCheck(args = []) {
|
|
|
532
326
|
if (result.update_available) console.log('Run: npm i -g sneakoscope');
|
|
533
327
|
}
|
|
534
328
|
|
|
535
|
-
const DOLLAR_DEFAULT_PIPELINE_TEXT = 'Default pipeline: questions -> $Answer, small design/content -> $DFix, 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.';
|
|
329
|
+
const DOLLAR_DEFAULT_PIPELINE_TEXT = 'Default pipeline: questions -> $Answer, small design/content -> $DFix, presentation/PDF artifacts -> $PPT, 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.';
|
|
536
330
|
|
|
537
331
|
function commands(args = []) {
|
|
538
332
|
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));
|
|
@@ -612,129 +406,59 @@ Rules:
|
|
|
612
406
|
`);
|
|
613
407
|
}
|
|
614
408
|
|
|
615
|
-
async function
|
|
616
|
-
const
|
|
617
|
-
const
|
|
618
|
-
const
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
if (
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
console.log(
|
|
634
|
-
console.log(`
|
|
635
|
-
console.log(`
|
|
636
|
-
console.log(`Tools: ${result.tool_names.join(', ') || 'none'}`);
|
|
637
|
-
if (!result.tool_names.includes('resolve-library-id') || !result.tool_names.some((name) => name === 'query-docs' || name === 'get-library-docs')) {
|
|
638
|
-
process.exitCode = 1;
|
|
639
|
-
console.log('\nContext7 local MCP is missing the required resolve/docs tools.');
|
|
640
|
-
}
|
|
641
|
-
return;
|
|
642
|
-
}
|
|
643
|
-
if (action === 'resolve') {
|
|
644
|
-
const positional = positionalArgs(args);
|
|
645
|
-
const libraryName = positional.join(' ').trim();
|
|
646
|
-
if (!libraryName) throw new Error('Usage: sks context7 resolve <library-name> [--query "..."] [--json]');
|
|
647
|
-
const result = await context7Resolve(libraryName, {
|
|
648
|
-
query: readOption(args, '--query', libraryName),
|
|
649
|
-
timeoutMs: readNumberOption(args, '--timeout-ms', 30000)
|
|
650
|
-
});
|
|
651
|
-
if (flag(args, '--json')) return console.log(JSON.stringify(result, null, 2));
|
|
652
|
-
console.log('SKS Context7 Resolve\n');
|
|
653
|
-
console.log(`Library: ${libraryName}`);
|
|
654
|
-
console.log(`ID: ${result.library_id || 'not resolved'}`);
|
|
655
|
-
console.log(`Server: ${result.server.info?.name || 'context7'} ${result.server.info?.version || ''}`.trim());
|
|
656
|
-
const text = context7Text(result.result).split(/\n/).slice(0, 24).join('\n').trim();
|
|
657
|
-
if (text) console.log(`\n${text}`);
|
|
658
|
-
if (!result.ok || !result.library_id) process.exitCode = 1;
|
|
659
|
-
return;
|
|
660
|
-
}
|
|
661
|
-
if (action === 'docs') {
|
|
662
|
-
const positional = positionalArgs(args);
|
|
663
|
-
const libraryNameOrId = positional.join(' ').trim();
|
|
664
|
-
if (!libraryNameOrId) throw new Error('Usage: sks context7 docs <library-name|/org/project> [--query "..."] [--topic "..."] [--tokens N] [--json]');
|
|
665
|
-
const result = await context7Docs(libraryNameOrId, {
|
|
666
|
-
query: readOption(args, '--query', readOption(args, '--topic', libraryNameOrId)),
|
|
667
|
-
topic: readOption(args, '--topic', libraryNameOrId),
|
|
668
|
-
tokens: readNumberOption(args, '--tokens', 2000),
|
|
669
|
-
timeoutMs: readNumberOption(args, '--timeout-ms', 30000)
|
|
670
|
-
});
|
|
671
|
-
if (flag(args, '--json')) return console.log(JSON.stringify(result, null, 2));
|
|
672
|
-
printContext7DocsResult(result, { title: 'SKS Context7 Docs' });
|
|
673
|
-
if (!result.ok) process.exitCode = 1;
|
|
674
|
-
return;
|
|
675
|
-
}
|
|
676
|
-
if (action === 'evidence') {
|
|
677
|
-
const positional = positionalArgs(args);
|
|
678
|
-
const missionArg = positional.shift();
|
|
679
|
-
const libraryNameOrId = positional.join(' ').trim();
|
|
680
|
-
if (!missionArg || !libraryNameOrId) throw new Error('Usage: sks context7 evidence <mission-id|latest> <library-name|/org/project> [--query "..."] [--topic "..."] [--tokens N] [--json]');
|
|
681
|
-
const missionId = await resolveMissionId(root, missionArg);
|
|
682
|
-
if (!missionId) throw new Error('No mission found for Context7 evidence.');
|
|
683
|
-
const result = await context7Docs(libraryNameOrId, {
|
|
684
|
-
query: readOption(args, '--query', readOption(args, '--topic', libraryNameOrId)),
|
|
685
|
-
topic: readOption(args, '--topic', libraryNameOrId),
|
|
686
|
-
tokens: readNumberOption(args, '--tokens', 2000),
|
|
687
|
-
timeoutMs: readNumberOption(args, '--timeout-ms', 30000)
|
|
688
|
-
});
|
|
689
|
-
const state = { ...(await readJson(stateFile(root), {})), mission_id: missionId };
|
|
690
|
-
await recordContext7Evidence(root, state, { tool_name: 'resolve-library-id', library: libraryNameOrId, library_id: result.library_id, source: result.resolve ? 'sks context7 evidence' : 'sks context7 evidence explicit-library-id' });
|
|
691
|
-
if (result.docs_tool) {
|
|
692
|
-
await recordContext7Evidence(root, state, { tool_name: result.docs_tool, library_id: result.library_id, source: 'sks context7 evidence' });
|
|
693
|
-
}
|
|
694
|
-
const evidence = await context7Evidence(root, state);
|
|
695
|
-
const out = { ...result, mission_id: missionId, evidence };
|
|
696
|
-
if (flag(args, '--json')) return console.log(JSON.stringify(out, null, 2));
|
|
697
|
-
printContext7DocsResult(result, { title: 'SKS Context7 Evidence' });
|
|
698
|
-
console.log(`\nMission: ${missionId}`);
|
|
699
|
-
console.log(`Evidence: ${evidence.ok ? 'ok' : 'missing'} resolve=${evidence.resolve ? 'yes' : 'no'} docs=${evidence.docs ? 'yes' : 'no'} events=${evidence.count}`);
|
|
700
|
-
if (!result.ok || !evidence.ok) process.exitCode = 1;
|
|
409
|
+
async function pptCommand(sub = 'status', args = []) {
|
|
410
|
+
const root = await sksRoot();
|
|
411
|
+
const action = sub || 'status';
|
|
412
|
+
const missionArg = args.find((arg) => !String(arg).startsWith('--')) || 'latest';
|
|
413
|
+
const id = await resolveMissionId(root, missionArg);
|
|
414
|
+
if (!id) throw new Error('Usage: sks ppt build|status <mission-id|latest> [--json]');
|
|
415
|
+
const { dir } = await loadMission(root, id);
|
|
416
|
+
if (action === 'build') {
|
|
417
|
+
const contract = await readJson(path.join(dir, 'decision-contract.json'), null);
|
|
418
|
+
if (!contract) throw new Error(`PPT build requires a sealed decision-contract.json for ${id}`);
|
|
419
|
+
const result = await writePptBuildArtifacts(dir, contract);
|
|
420
|
+
await appendJsonlBounded(path.join(dir, 'events.jsonl'), { ts: nowIso(), type: 'ppt.build.completed', ok: result.ok, files: result.files });
|
|
421
|
+
if (flag(args, '--json')) return console.log(JSON.stringify({ ok: result.ok, mission_id: id, files: result.files, gate: result.gate, report: result.report }, null, 2));
|
|
422
|
+
console.log('SKS PPT build\n');
|
|
423
|
+
console.log(`Mission: ${id}`);
|
|
424
|
+
console.log(`HTML: ${path.relative(root, result.files.html)}`);
|
|
425
|
+
console.log(`PDF: ${path.relative(root, result.files.pdf)}`);
|
|
426
|
+
console.log(`Report: ${path.relative(root, result.files.render_report)}`);
|
|
427
|
+
console.log(`Cleanup: ${path.relative(root, result.files.cleanup_report)}`);
|
|
428
|
+
console.log(`Parallel:${' '.repeat(1)}${path.relative(root, result.files.parallel_report)}`);
|
|
429
|
+
console.log(`Gate: ${result.ok ? 'passed' : 'blocked'} (${path.relative(root, result.files.gate)})`);
|
|
701
430
|
return;
|
|
702
431
|
}
|
|
703
|
-
if (action === '
|
|
704
|
-
const
|
|
705
|
-
const
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
console.log('
|
|
432
|
+
if (action === 'status') {
|
|
433
|
+
const gate = await readJson(path.join(dir, PPT_GATE_ARTIFACT), null);
|
|
434
|
+
const report = await readJson(path.join(dir, PPT_RENDER_REPORT_ARTIFACT), null);
|
|
435
|
+
const status = {
|
|
436
|
+
ok: Boolean(gate?.passed),
|
|
437
|
+
mission_id: id,
|
|
438
|
+
gate,
|
|
439
|
+
report,
|
|
440
|
+
files: {
|
|
441
|
+
html: path.join(dir, PPT_HTML_ARTIFACT),
|
|
442
|
+
source_html: path.join(dir, PPT_HTML_ARTIFACT),
|
|
443
|
+
pdf: path.join(dir, PPT_PDF_ARTIFACT),
|
|
444
|
+
render_report: path.join(dir, PPT_RENDER_REPORT_ARTIFACT),
|
|
445
|
+
cleanup_report: path.join(dir, PPT_CLEANUP_REPORT_ARTIFACT),
|
|
446
|
+
parallel_report: path.join(dir, PPT_PARALLEL_REPORT_ARTIFACT),
|
|
447
|
+
gate: path.join(dir, PPT_GATE_ARTIFACT)
|
|
448
|
+
}
|
|
449
|
+
};
|
|
450
|
+
if (flag(args, '--json')) return console.log(JSON.stringify(status, null, 2));
|
|
451
|
+
console.log('SKS PPT status\n');
|
|
452
|
+
console.log(`Mission: ${id}`);
|
|
453
|
+
console.log(`Gate: ${status.ok ? 'passed' : 'not passed'}`);
|
|
454
|
+
console.log(`HTML: ${path.relative(root, status.files.html)}`);
|
|
455
|
+
console.log(`PDF: ${path.relative(root, status.files.pdf)}`);
|
|
456
|
+
console.log(`Report: ${path.relative(root, status.files.render_report)}`);
|
|
457
|
+
console.log(`Cleanup: ${path.relative(root, status.files.cleanup_report)}`);
|
|
458
|
+
console.log(`Parallel:${' '.repeat(1)}${path.relative(root, status.files.parallel_report)}`);
|
|
725
459
|
return;
|
|
726
460
|
}
|
|
727
|
-
throw new Error(`Unknown
|
|
728
|
-
}
|
|
729
|
-
|
|
730
|
-
function printContext7DocsResult(result, opts = {}) {
|
|
731
|
-
console.log(`${opts.title || 'SKS Context7 Docs'}\n`);
|
|
732
|
-
console.log(`Library ID: ${result.library_id || 'not resolved'}`);
|
|
733
|
-
console.log(`Docs tool: ${result.docs_tool || 'missing'}`);
|
|
734
|
-
console.log(`Server: ${result.server?.info?.name || 'context7'} ${result.server?.info?.version || ''}`.trim());
|
|
735
|
-
const text = context7Text(result.docs).split(/\n/).slice(0, 48).join('\n').trim();
|
|
736
|
-
if (text) console.log(`\n${text}`);
|
|
737
|
-
if (result.error) console.log(`\nError: ${result.error}`);
|
|
461
|
+
throw new Error(`Unknown ppt command: ${action}`);
|
|
738
462
|
}
|
|
739
463
|
|
|
740
464
|
async function pipeline(sub = 'status', args = []) {
|
|
@@ -937,6 +661,25 @@ async function materializeAfterPipelineAnswer(root, id, dir, mission, route, rou
|
|
|
937
661
|
}
|
|
938
662
|
};
|
|
939
663
|
}
|
|
664
|
+
if (route?.id === 'PPT') {
|
|
665
|
+
await writePptRouteArtifacts(dir, contract);
|
|
666
|
+
await appendJsonlBounded(path.join(dir, 'events.jsonl'), {
|
|
667
|
+
ts: nowIso(),
|
|
668
|
+
type: 'ppt.materialized_after_ambiguity_gate',
|
|
669
|
+
route: route.id,
|
|
670
|
+
audience_strategy_artifact: PPT_AUDIENCE_STRATEGY_ARTIFACT,
|
|
671
|
+
gate: PPT_GATE_ARTIFACT
|
|
672
|
+
});
|
|
673
|
+
return {
|
|
674
|
+
phase: 'PPT_AUDIENCE_STRATEGY_READY',
|
|
675
|
+
prompt: routeContext.task || mission.prompt || '',
|
|
676
|
+
state: {
|
|
677
|
+
ppt_audience_strategy_ready: true,
|
|
678
|
+
ppt_gate_ready: true,
|
|
679
|
+
...madSksState
|
|
680
|
+
}
|
|
681
|
+
};
|
|
682
|
+
}
|
|
940
683
|
if (route?.id !== 'Team') return Object.keys(madSksState).length ? { state: madSksState } : {};
|
|
941
684
|
const spec = parseTeamSpecText(routeContext.task || mission.prompt || '');
|
|
942
685
|
const prompt = spec.prompt || routeContext.task || mission.prompt || '';
|
|
@@ -1120,43 +863,6 @@ async function reasoningCommand(args = []) {
|
|
|
1120
863
|
console.log('Lifecycle: temporary; return to default/user-selected profile after the route gate passes');
|
|
1121
864
|
}
|
|
1122
865
|
|
|
1123
|
-
async function checkContext7(root) {
|
|
1124
|
-
const projectPath = path.join(root, '.codex', 'config.toml');
|
|
1125
|
-
const globalPath = path.join(process.env.HOME || '', '.codex', 'config.toml');
|
|
1126
|
-
const projectText = await safeReadText(projectPath);
|
|
1127
|
-
const globalText = await safeReadText(globalPath);
|
|
1128
|
-
const codex = await getCodexInfo().catch(() => ({}));
|
|
1129
|
-
let list = { checked: false, ok: false, stdout: '', stderr: '' };
|
|
1130
|
-
if (codex.bin) {
|
|
1131
|
-
const out = await runProcess(codex.bin, ['mcp', 'list'], { timeoutMs: 8000, maxOutputBytes: 32 * 1024 }).catch((err) => ({ code: 1, stderr: err.message, stdout: '' }));
|
|
1132
|
-
list = { checked: true, ok: out.code === 0 && /context7/i.test(`${out.stdout}\n${out.stderr}`), stdout: out.stdout || '', stderr: out.stderr || '' };
|
|
1133
|
-
}
|
|
1134
|
-
const result = {
|
|
1135
|
-
project: { path: projectPath, ok: hasContext7ConfigText(projectText) },
|
|
1136
|
-
global: { path: globalPath, ok: hasContext7ConfigText(globalText) },
|
|
1137
|
-
codex_mcp_list: list
|
|
1138
|
-
};
|
|
1139
|
-
result.ok = result.project.ok || result.codex_mcp_list.ok || (result.global.ok && !list.checked);
|
|
1140
|
-
return result;
|
|
1141
|
-
}
|
|
1142
|
-
|
|
1143
|
-
async function ensureProjectContext7Config(root, transport = 'local') {
|
|
1144
|
-
const configPath = path.join(root, '.codex', 'config.toml');
|
|
1145
|
-
await ensureDir(path.dirname(configPath));
|
|
1146
|
-
const current = await safeReadText(configPath);
|
|
1147
|
-
const block = context7ConfigToml(transport).trim();
|
|
1148
|
-
const existingBlock = /(^|\n)\[mcp_servers\.context7\]\n[\s\S]*?(?=\n\[[^\]]+\]|\s*$)/;
|
|
1149
|
-
if (existingBlock.test(current)) {
|
|
1150
|
-
const next = current.replace(existingBlock, `$1${block}\n`);
|
|
1151
|
-
if (next === current) return false;
|
|
1152
|
-
await writeTextAtomic(configPath, next.endsWith('\n') ? next : `${next}\n`);
|
|
1153
|
-
return true;
|
|
1154
|
-
}
|
|
1155
|
-
if (hasContext7ConfigText(current)) return false;
|
|
1156
|
-
await writeTextAtomic(configPath, `${current.trimEnd()}${current.trim() ? '\n\n' : ''}${block}\n`);
|
|
1157
|
-
return true;
|
|
1158
|
-
}
|
|
1159
|
-
|
|
1160
866
|
function readOption(args, name, fallback) {
|
|
1161
867
|
const i = args.indexOf(name);
|
|
1162
868
|
return i >= 0 && args[i + 1] ? args[i + 1] : fallback;
|
|
@@ -1169,23 +875,23 @@ function readNumberOption(args, name, fallback) {
|
|
|
1169
875
|
return Number.isFinite(value) && value > 0 ? value : fallback;
|
|
1170
876
|
}
|
|
1171
877
|
|
|
1172
|
-
async function
|
|
878
|
+
async function tmuxCommand(sub = 'start', args = []) {
|
|
1173
879
|
const action = sub || 'start';
|
|
1174
880
|
if (action === 'status' || action === 'banner') {
|
|
1175
881
|
if (flag(args, '--json')) {
|
|
1176
882
|
const status = await codexAppIntegrationStatus();
|
|
1177
883
|
return console.log(JSON.stringify(status, null, 2));
|
|
1178
884
|
}
|
|
1179
|
-
await
|
|
885
|
+
await runTmuxStatus(action === 'banner' ? ['--once', ...args] : args);
|
|
1180
886
|
return;
|
|
1181
887
|
}
|
|
1182
888
|
if (action === 'check') {
|
|
1183
889
|
const root = await sksRoot();
|
|
1184
|
-
const plan = await
|
|
890
|
+
const plan = await buildTmuxLaunchPlan({ root, session: readOption(args, '--session', null) });
|
|
1185
891
|
if (flag(args, '--json')) return console.log(JSON.stringify(plan, null, 2));
|
|
1186
|
-
console.log(
|
|
892
|
+
console.log(formatTmuxBanner(plan.app));
|
|
1187
893
|
console.log('');
|
|
1188
|
-
console.log(`
|
|
894
|
+
console.log(`tmux: ${plan.tmux.ok ? 'ok' : 'missing'} ${plan.tmux.version || ''}`.trim());
|
|
1189
895
|
console.log(`Workspace: ${plan.workspace}`);
|
|
1190
896
|
console.log(`Project: ${plan.root}`);
|
|
1191
897
|
console.log(`Ready: ${plan.ready ? 'yes' : 'no'}`);
|
|
@@ -1197,16 +903,16 @@ async function warpCommand(sub = 'start', args = []) {
|
|
|
1197
903
|
return;
|
|
1198
904
|
}
|
|
1199
905
|
if (['start', 'attach', 'connect', 'open'].includes(action)) {
|
|
1200
|
-
const result = await
|
|
906
|
+
const result = await launchTmuxUi(args);
|
|
1201
907
|
if (flag(args, '--json')) console.log(JSON.stringify(result, null, 2));
|
|
1202
908
|
return;
|
|
1203
909
|
}
|
|
1204
|
-
console.error('Usage: sks
|
|
910
|
+
console.error('Usage: sks tmux open|start|check|status|banner [--workspace name]');
|
|
1205
911
|
process.exitCode = 1;
|
|
1206
912
|
}
|
|
1207
913
|
|
|
1208
914
|
async function madHighCommand(args = []) {
|
|
1209
|
-
const cleanArgs = args.filter((arg) => !['--mad', '--MAD', '--mad-sks', '--high', '--no-auto-install-
|
|
915
|
+
const cleanArgs = args.filter((arg) => !['--mad', '--MAD', '--mad-sks', '--high', '--no-auto-install-tmux'].includes(arg));
|
|
1210
916
|
if (flag(args, '--json')) {
|
|
1211
917
|
const profile = await enableMadHighProfile();
|
|
1212
918
|
return console.log(JSON.stringify(profile, null, 2));
|
|
@@ -1230,11 +936,11 @@ async function madHighCommand(args = []) {
|
|
|
1230
936
|
}
|
|
1231
937
|
const profile = await enableMadHighProfile();
|
|
1232
938
|
console.log(`SKS MAD auto-review profile ready: ${madHighProfileName()}`);
|
|
1233
|
-
console.log('Scope: explicit
|
|
1234
|
-
const workspace = readOption(cleanArgs, '--workspace', readOption(cleanArgs, '--session', `sks-mad-${
|
|
1235
|
-
return
|
|
939
|
+
console.log('Scope: explicit tmux launch only; full access uses Codex auto_review approvals when approval prompts are raised.');
|
|
940
|
+
const workspace = readOption(cleanArgs, '--workspace', readOption(cleanArgs, '--session', `sks-mad-${defaultTmuxSessionName(process.cwd())}`));
|
|
941
|
+
return launchTmuxUi([...cleanArgs, '--workspace', workspace], {
|
|
1236
942
|
codexArgs: ['--profile', profile.profile_name],
|
|
1237
|
-
|
|
943
|
+
autoInstallTmux: !flag(args, '--no-auto-install-tmux'),
|
|
1238
944
|
conciseBlockers: true
|
|
1239
945
|
});
|
|
1240
946
|
}
|
|
@@ -1270,12 +976,12 @@ async function ensureMadLaunchDependencies(args = []) {
|
|
|
1270
976
|
const codex = await getCodexInfo().catch(() => ({}));
|
|
1271
977
|
if (!codex.bin) actions.push(await installCodexDependency(args, { prompt: 'Codex CLI missing. Install latest Codex CLI with npm i -g @openai/codex@latest?' }));
|
|
1272
978
|
}
|
|
1273
|
-
if (!flag(args, '--no-auto-install-
|
|
1274
|
-
const
|
|
1275
|
-
if (!
|
|
979
|
+
if (!flag(args, '--no-auto-install-tmux')) {
|
|
980
|
+
const tmux = await tmuxReadiness().catch(() => ({ ok: false }));
|
|
981
|
+
if (!tmux.ok) actions.push(await installTmuxDependency(args));
|
|
1276
982
|
}
|
|
1277
983
|
const status = await depsStatus(await sksRoot());
|
|
1278
|
-
return { ready: Boolean(status.codex_cli.ok && status.
|
|
984
|
+
return { ready: Boolean(status.codex_cli.ok && status.tmux.ok), actions, status };
|
|
1279
985
|
}
|
|
1280
986
|
|
|
1281
987
|
async function deps(sub = 'check', args = []) {
|
|
@@ -1289,7 +995,7 @@ async function deps(sub = 'check', args = []) {
|
|
|
1289
995
|
return;
|
|
1290
996
|
}
|
|
1291
997
|
if (action === 'install') return depsInstall(args);
|
|
1292
|
-
console.error('Usage: sks deps check|install [
|
|
998
|
+
console.error('Usage: sks deps check|install [tmux|codex|context7|all] [--yes] [--json]');
|
|
1293
999
|
process.exitCode = 1;
|
|
1294
1000
|
}
|
|
1295
1001
|
|
|
@@ -1299,7 +1005,7 @@ async function depsStatus(root = null, opts = {}) {
|
|
|
1299
1005
|
const codex = opts.codex || await getCodexInfo().catch(() => ({}));
|
|
1300
1006
|
const app = opts.codexApp || await codexAppIntegrationStatus({ codex });
|
|
1301
1007
|
const context7 = opts.context7 || await checkContext7(root);
|
|
1302
|
-
const
|
|
1008
|
+
const tmux = opts.tmux || await tmuxReadiness().catch((err) => ({ ok: false, version: null, error: err.message }));
|
|
1303
1009
|
const brew = process.platform === 'darwin' ? await which('brew').catch(() => null) : null;
|
|
1304
1010
|
const globalBin = await discoverGlobalSksCommand();
|
|
1305
1011
|
const npmPrefix = npmBin ? await runProcess(npmBin, ['prefix', '-g'], { timeoutMs: 8000, maxOutputBytes: 4096 }).catch(() => null) : null;
|
|
@@ -1307,10 +1013,10 @@ async function depsStatus(root = null, opts = {}) {
|
|
|
1307
1013
|
const npmPrefixDir = npmPrefix?.code === 0 ? npmPrefix.stdout.trim().split(/\r?\n/).pop() : null;
|
|
1308
1014
|
const npmBinDir = npmPrefixDir ? (process.platform === 'win32' ? npmPrefixDir : path.join(npmPrefixDir, 'bin')) : null;
|
|
1309
1015
|
const nodeOk = Number(process.versions.node.split('.')[0]) >= 20;
|
|
1310
|
-
const homebrewNeeded = process.platform === 'darwin' && !
|
|
1016
|
+
const homebrewNeeded = process.platform === 'darwin' && !tmux.ok;
|
|
1311
1017
|
return {
|
|
1312
1018
|
root,
|
|
1313
|
-
ready: Boolean(nodeOk && npmBin && globalBin && codex.bin && context7.ok &&
|
|
1019
|
+
ready: Boolean(nodeOk && npmBin && globalBin && codex.bin && context7.ok && tmux.ok),
|
|
1314
1020
|
node: { ok: nodeOk, version: process.version },
|
|
1315
1021
|
npm: { ok: Boolean(npmBin), bin: npmBin, global_bin_dir: npmBinDir, global_bin_on_path: npmBinDir ? pathText.split(path.delimiter).includes(npmBinDir) : null },
|
|
1316
1022
|
sneakoscope: { ok: Boolean(globalBin), bin: globalBin },
|
|
@@ -1319,13 +1025,13 @@ async function depsStatus(root = null, opts = {}) {
|
|
|
1319
1025
|
context7,
|
|
1320
1026
|
browser_use: { ok: app.mcp.has_browser_use, cache: app.plugins.browser_use_cache },
|
|
1321
1027
|
computer_use: { ok: app.mcp.has_computer_use, cache: app.plugins.computer_use_cache },
|
|
1322
|
-
|
|
1323
|
-
homebrew: process.platform === 'darwin' ? { ok: Boolean(brew), bin: brew,
|
|
1324
|
-
next_actions: depsNextActions({ npmBin, globalBin, codex, app, context7,
|
|
1028
|
+
tmux: { ok: Boolean(tmux.ok), bin: tmux.bin || null, version: tmux.version || null, min_version: tmux.min_version || '3.0', current_session: Boolean(tmux.current_session), install_hint: tmux.ok ? null : platformTmuxInstallHint(), error: tmux.error || null },
|
|
1029
|
+
homebrew: process.platform === 'darwin' ? { ok: Boolean(brew), bin: brew, required_for_tmux_install: homebrewNeeded } : { ok: null, bin: null, required_for_tmux_install: false },
|
|
1030
|
+
next_actions: depsNextActions({ npmBin, globalBin, codex, app, context7, tmux, brew, nodeOk })
|
|
1325
1031
|
};
|
|
1326
1032
|
}
|
|
1327
1033
|
|
|
1328
|
-
function depsNextActions({ npmBin, globalBin, codex, app, context7,
|
|
1034
|
+
function depsNextActions({ npmBin, globalBin, codex, app, context7, tmux, brew, nodeOk }) {
|
|
1329
1035
|
const out = [];
|
|
1330
1036
|
if (!nodeOk) out.push('Install Node.js 20.11+.');
|
|
1331
1037
|
if (!npmBin) out.push('Install npm or use a Node.js distribution that includes npm.');
|
|
@@ -1333,7 +1039,7 @@ function depsNextActions({ npmBin, globalBin, codex, app, context7, warp, brew,
|
|
|
1333
1039
|
if (!codex.bin) out.push('Run: sks deps install codex');
|
|
1334
1040
|
if (!context7.ok) out.push('Run: sks deps install context7');
|
|
1335
1041
|
if (!app.ok) out.push('Run: sks codex-app check');
|
|
1336
|
-
if (!
|
|
1042
|
+
if (!tmux.ok) out.push(process.platform === 'darwin' && !brew ? 'Install tmux from https://www.tmux.dev/download, or install Homebrew then run: sks deps install tmux' : 'Run: sks deps install tmux');
|
|
1337
1043
|
return out;
|
|
1338
1044
|
}
|
|
1339
1045
|
|
|
@@ -1348,7 +1054,7 @@ function printDepsStatus(status) {
|
|
|
1348
1054
|
console.log(`Context7: ${status.context7.ok ? 'ok' : 'missing'}`);
|
|
1349
1055
|
console.log(`Browser Use: ${status.browser_use.ok ? 'ok' : 'missing'}`);
|
|
1350
1056
|
console.log(`Computer Use:${status.computer_use.ok ? ' ok' : ' missing'}`);
|
|
1351
|
-
console.log(`
|
|
1057
|
+
console.log(`tmux: ${tmuxStatusKind(status.tmux)} ${status.tmux.version || status.tmux.error || ''}`.trimEnd());
|
|
1352
1058
|
if (process.platform === 'darwin') console.log(`Homebrew: ${status.homebrew.ok ? 'ok' : 'missing'} ${status.homebrew.bin || ''}`.trimEnd());
|
|
1353
1059
|
console.log(`Ready: ${status.ready ? 'true' : 'false'}`);
|
|
1354
1060
|
if (status.next_actions.length) {
|
|
@@ -1360,11 +1066,11 @@ function printDepsStatus(status) {
|
|
|
1360
1066
|
async function depsInstall(args = []) {
|
|
1361
1067
|
const root = await sksRoot();
|
|
1362
1068
|
const target = positionalArgs(args)[0] || 'all';
|
|
1363
|
-
const wants = target === 'all' ? ['codex', 'context7', '
|
|
1069
|
+
const wants = target === 'all' ? ['codex', 'context7', 'tmux'] : [target];
|
|
1364
1070
|
const actions = [];
|
|
1365
1071
|
if (wants.includes('codex')) actions.push(await installCodexDependency(args));
|
|
1366
1072
|
if (wants.includes('context7')) actions.push(await installContext7Dependency(root));
|
|
1367
|
-
if (wants.includes('
|
|
1073
|
+
if (wants.includes('tmux')) actions.push(await installTmuxDependency(args));
|
|
1368
1074
|
const status = await depsStatus(root);
|
|
1369
1075
|
const result = { target, actions, status };
|
|
1370
1076
|
if (flag(args, '--json')) return console.log(JSON.stringify(result, null, 2));
|
|
@@ -1389,12 +1095,12 @@ async function installContext7Dependency(root) {
|
|
|
1389
1095
|
return { target: 'context7', status: changed ? 'project_configured' : 'already_configured', command: 'sks context7 check' };
|
|
1390
1096
|
}
|
|
1391
1097
|
|
|
1392
|
-
async function
|
|
1393
|
-
const before = await
|
|
1394
|
-
if (before.ok) return { target: '
|
|
1395
|
-
const command = process.platform === 'darwin' ? 'brew install
|
|
1396
|
-
if (flag(args, '--dry-run')) return { target: '
|
|
1397
|
-
return { target: '
|
|
1098
|
+
async function installTmuxDependency(args = []) {
|
|
1099
|
+
const before = await tmuxReadiness().catch(() => ({ ok: false }));
|
|
1100
|
+
if (before.ok) return { target: 'tmux', status: 'present', version: before.version || null, app: before.app || null, cli: before.cli || null };
|
|
1101
|
+
const command = process.platform === 'darwin' ? 'brew install tmux' : platformTmuxInstallHint();
|
|
1102
|
+
if (flag(args, '--dry-run')) return { target: 'tmux', status: 'dry_run', command };
|
|
1103
|
+
return { target: 'tmux', status: 'manual_required', command, error: before.error || 'tmux not found' };
|
|
1398
1104
|
}
|
|
1399
1105
|
|
|
1400
1106
|
async function confirmInstall(question, args = []) {
|
|
@@ -1444,8 +1150,8 @@ async function autoReviewCommand(sub = 'status', args = []) {
|
|
|
1444
1150
|
if (flag(args, '--json')) return console.log(JSON.stringify(status, null, 2));
|
|
1445
1151
|
console.log(`SKS Auto-Review enabled: ${profile}`);
|
|
1446
1152
|
const sessionArg = readOption(cleanArgs, '--session', null);
|
|
1447
|
-
const session = sessionArg ||
|
|
1448
|
-
return
|
|
1153
|
+
const session = sessionArg || sanitizeTmuxSessionName(`${profile}-${defaultTmuxSessionName(process.cwd())}`);
|
|
1154
|
+
return launchTmuxUi([...cleanArgs, '--session', session], { codexArgs: ['--profile', profile] });
|
|
1449
1155
|
}
|
|
1450
1156
|
console.error('Usage: sks auto-review status|enable|disable|start [--high] [--json]');
|
|
1451
1157
|
console.error('Alias: sks --Auto-review [--high]');
|
|
@@ -1480,7 +1186,7 @@ async function codexAppHelp(args = []) {
|
|
|
1480
1186
|
'ㅅㅋㅅ Codex App', '',
|
|
1481
1187
|
formatCodexAppStatus(status), '',
|
|
1482
1188
|
`Skills: project=${skills.project.ok ? 'ok' : `missing ${skills.project.missing.length}`} global=${skills.global.ok ? 'ok' : `missing ${skills.global.missing.length}`}`, '',
|
|
1483
|
-
'Setup:', ' sks bootstrap', ' sks deps check', ' sks codex-app check', ' sks
|
|
1189
|
+
'Setup:', ' sks bootstrap', ' sks deps check', ' sks codex-app check', ' sks tmux check', '',
|
|
1484
1190
|
'Generated files:', ' .codex/config.toml', ' .codex/hooks.json', ' .agents/skills/', ' .codex/agents/', ' .codex/SNEAKOSCOPE.md', ' AGENTS.md', '',
|
|
1485
1191
|
'Git ignore:', ' default setup writes .gitignore entries for .sneakoscope/, .codex/, .agents/, AGENTS.md', ' --local-only writes those patterns to .git/info/exclude instead', '',
|
|
1486
1192
|
'Prompt routes:', formatDollarCommandsCompact(' ')
|
|
@@ -1513,19 +1219,20 @@ Examples:
|
|
|
1513
1219
|
function usage(args = []) {
|
|
1514
1220
|
const topic = String(args[0] || 'overview').toLowerCase();
|
|
1515
1221
|
const blocks = {
|
|
1516
|
-
overview: ['ㅅㅋㅅ Usage', '', 'Discover:', ' sks commands', ' sks quickstart', ' sks root', ' sks bootstrap', ' sks deps check', ' sks codex-app check', ' sks
|
|
1222
|
+
overview: ['ㅅㅋㅅ 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}`],
|
|
1517
1223
|
install: ['Install', '', ' npm i -g sneakoscope', ' sks root', ' sks', '', 'Project bootstrap:', ' sks bootstrap', '', 'Fallback:', ' npx -y -p sneakoscope sks root', '', 'Project:', ' npm i -D sneakoscope', ' npx sks setup --install-scope project'],
|
|
1518
|
-
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
|
|
1224
|
+
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.'],
|
|
1519
1225
|
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.'],
|
|
1520
|
-
deps: ['Dependencies', '', ' sks deps check [--json]', ' sks deps install [
|
|
1521
|
-
|
|
1522
|
-
team: ['Team', '', ' sks team "task" executor:5 reviewer:2 user:1', ' sks team watch latest', ' sks team lane latest --agent analysis_scout_1 --follow', ' sks team message latest --from analysis_scout_1 --to executor_1 --message "handoff note"', ' sks team cleanup-
|
|
1226
|
+
deps: ['Dependencies', '', ' sks deps check [--json]', ' sks deps install [tmux|codex|context7|all] [--yes]', '', 'tmux on macOS uses Homebrew only after approval.'],
|
|
1227
|
+
tmux: ['tmux', '', ' sks tmux open', ' sks tmux check', ' sks tmux status --once', ' sks deps install tmux', '', 'tmux launch is explicit. Running bare `sks` prints help and never opens tmux by itself.'],
|
|
1228
|
+
team: ['Team', '', ' sks team "task" executor:5 reviewer:2 user:1', ' sks team watch latest', ' sks team lane latest --agent analysis_scout_1 --follow', ' sks team message latest --from analysis_scout_1 --to executor_1 --message "handoff note"', ' sks team cleanup-tmux latest', '', '$Team runs questions -> contract -> scouts -> TriWiki attention -> debate -> runtime graph/inbox -> fresh executors -> review -> cleanup -> reflection -> Honest.'],
|
|
1523
1229
|
'qa-loop': ['QA-LOOP', '', ' sks qa-loop prepare "QA this app"', ' sks qa-loop answer <MISSION_ID> answers.json', ' sks qa-loop run <MISSION_ID> --max-cycles 8', '', 'Report: YYYY-MM-DD-v<version>-qa-report.md'],
|
|
1230
|
+
ppt: ['PPT', '', ' $PPT 투자자용 피치덱을 HTML 기반 PDF로 만들어줘', ' $PPT 우리 SaaS 소개자료 만들어줘', ' sks ppt build latest --json', ' sks ppt status latest --json', '', '$PPT asks delivery context, audience profile, STP strategy, decision context, and 3+ pain-point/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. The visual system must stay simple, restrained, and information-first; editable source HTML is kept under source-html/, PPT-only temporary build files are cleaned, and generated image assets prefer Codex App built-in image generation through imagegen.'],
|
|
1524
1231
|
goal: ['Goal', '', ' sks goal create "task"', ' sks goal status latest', ' sks goal pause latest', ' sks goal resume latest', ' sks goal clear latest'],
|
|
1525
1232
|
'codex-app': ['Codex App', '', ' sks bootstrap', ' sks codex-app check', ' sks dollar-commands', ' cat .codex/SNEAKOSCOPE.md'],
|
|
1526
1233
|
dollar: ['Dollar Commands', '', formatDollarCommandsCompact(' '), '', 'Terminal: sks dollar-commands [--json]'],
|
|
1527
1234
|
wiki: ['TriWiki', '', ' sks wiki pack', ' sks wiki refresh [--prune]', ' sks wiki sweep latest --json', ' sks wiki validate .sneakoscope/wiki/context-pack.json', ' sks wiki prune --dry-run --json', '', 'Packs include attention.use_first and attention.hydrate_first for compact recall plus source hydration. Sweep records intentional forgetting and promotion candidates.'],
|
|
1528
|
-
harness: ['Harness Growth', '', ' sks harness fixture --json', ' sks harness review --json', '', 'Runs deterministic fixtures for deliberate forgetting, skill cards, harness experiments, tool error taxonomy, permission profiles, MultiAgentV2, and
|
|
1235
|
+
harness: ['Harness Growth', '', ' sks harness fixture --json', ' sks harness review --json', '', 'Runs deterministic fixtures for deliberate forgetting, skill cards, harness experiments, tool error taxonomy, permission profiles, MultiAgentV2, and tmux cockpit views.'],
|
|
1529
1236
|
'skill-dream': ['Skill Dreaming', '', ' sks skill-dream status', ' sks skill-dream run --json', ' sks skill-dream record --route team --skills team,prompt-pipeline', '', 'Records cheap JSON usage counters in .sneakoscope/skills/dream-state.json and periodically writes recommendation-only keep/merge/prune/improve reports. It never deletes or merges skills automatically.'],
|
|
1530
1237
|
'code-structure': ['Code Structure', '', ' sks code-structure scan', ' sks code-structure scan --json', '', 'Flags handwritten source files above 1000/2000/3000-line thresholds and records split-review exceptions.'],
|
|
1531
1238
|
gx: ['GX', '', ' sks gx init architecture-atlas', ' sks gx render architecture-atlas --format all', ' sks gx validate architecture-atlas']
|
|
@@ -1550,13 +1257,13 @@ async function bootstrap(args = []) {
|
|
|
1550
1257
|
const cliTools = await ensureRelatedCliTools(args);
|
|
1551
1258
|
const context7Status = await checkContext7(root);
|
|
1552
1259
|
const appRuntime = await codexAppIntegrationStatus({ codex: await getCodexInfo().catch(() => ({})) });
|
|
1553
|
-
const deps = await depsStatus(root, { context7: context7Status, codexApp: appRuntime,
|
|
1260
|
+
const deps = await depsStatus(root, { context7: context7Status, codexApp: appRuntime, tmux: cliTools.tmux });
|
|
1554
1261
|
const install = await installStatus(root, installScope, { globalCommand });
|
|
1555
1262
|
const versioningInfo = await versioningStatus(root);
|
|
1556
1263
|
const skills = await checkRequiredSkills(root);
|
|
1557
1264
|
const guard = await harnessGuardStatus(root);
|
|
1558
1265
|
const files = await codexAppFilesStatus(root, skills, versioningInfo);
|
|
1559
|
-
const ready = Boolean(!conflicts.hard_block && install.ok && files.ok && skills.ok && guard.ok && context7Status.ok && appRuntime.ok && deps.
|
|
1266
|
+
const ready = Boolean(!conflicts.hard_block && install.ok && files.ok && skills.ok && guard.ok && context7Status.ok && appRuntime.ok && deps.tmux.ok);
|
|
1560
1267
|
const result = {
|
|
1561
1268
|
root,
|
|
1562
1269
|
ready,
|
|
@@ -1567,7 +1274,7 @@ async function bootstrap(args = []) {
|
|
|
1567
1274
|
codex_app: appRuntime,
|
|
1568
1275
|
global_skills: globalSkills,
|
|
1569
1276
|
context7: context7Status,
|
|
1570
|
-
|
|
1277
|
+
tmux: deps.tmux,
|
|
1571
1278
|
harness_guard: guard,
|
|
1572
1279
|
deps,
|
|
1573
1280
|
next: ready ? ['sks', '$Team implement ...', '$QA-LOOP run ...'] : deps.next_actions
|
|
@@ -1580,7 +1287,7 @@ async function bootstrap(args = []) {
|
|
|
1580
1287
|
console.log(`Hooks: ${files.hooks.ok ? 'ok' : 'missing'}`);
|
|
1581
1288
|
console.log(`Harness guard: ${guard.ok ? 'ok' : 'blocked'}`);
|
|
1582
1289
|
console.log(`Context7: ${context7Status.ok ? 'ok' : 'missing'}`);
|
|
1583
|
-
console.log(`
|
|
1290
|
+
console.log(`tmux: ${deps.tmux.ok ? 'ok' : 'missing'}${deps.tmux.version ? ` ${deps.tmux.version}` : ''}`);
|
|
1584
1291
|
console.log(`ready: ${ready ? 'true' : 'false'}`);
|
|
1585
1292
|
if (!ready) {
|
|
1586
1293
|
console.log('\nNext:');
|
|
@@ -1659,7 +1366,7 @@ async function setup(args) {
|
|
|
1659
1366
|
console.log('ㅅㅋㅅ Setup\n');
|
|
1660
1367
|
console.log(`Project: ${root}`);
|
|
1661
1368
|
console.log(`Install: ${install.ok ? 'ok' : 'missing'} ${install.scope} (${install.command_prefix})`);
|
|
1662
|
-
console.log(`CLI tools: Codex ${formatCodexCliToolStatus(cliTools.codex)};
|
|
1369
|
+
console.log(`CLI tools: Codex ${formatCodexCliToolStatus(cliTools.codex)}; tmux ${tmuxStatusKind(cliTools.tmux)} ${cliTools.tmux.version || cliTools.tmux.error || ''}`.trimEnd());
|
|
1663
1370
|
console.log(`Hooks: ${path.relative(root, hooksPath)}`);
|
|
1664
1371
|
console.log(`Version: ${versioningInfo.enabled ? (versioningInfo.hook_installed ? 'auto-bump enabled' : 'auto-bump hook missing') : 'not enabled'}${versioningInfo.package_version ? ` (${versioningInfo.package_version})` : ''}`);
|
|
1665
1372
|
if (localOnly) console.log('Git: local-only (.git/info/exclude; user AGENTS preserved, SKS managed block refreshed)');
|
|
@@ -1667,12 +1374,12 @@ async function setup(args) {
|
|
|
1667
1374
|
console.log(`Codex App: .codex/config.toml, .codex/hooks.json, .agents/skills, .codex/agents, .codex/SNEAKOSCOPE.md`);
|
|
1668
1375
|
console.log(`Global $: ${globalSkills.status === 'installed' ? 'ok' : globalSkills.status} ${globalSkills.root || ''}`.trimEnd());
|
|
1669
1376
|
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'}`);
|
|
1670
|
-
console.log(`Prompt: intent-first routing, $Answer fact-check route, $DFix ultralight design/content route, Context7 gate`);
|
|
1377
|
+
console.log(`Prompt: intent-first routing, $Answer fact-check route, $DFix ultralight design/content route, $PPT HTML/PDF presentation route, Context7 gate`);
|
|
1671
1378
|
console.log(`Skills: .agents/skills`);
|
|
1672
1379
|
console.log(`Next: sks context7 check; sks selftest --mock; sks commands; sks dollar-commands`);
|
|
1673
1380
|
if (cliTools.codex.status === 'failed') console.log(`\nCodex CLI install failed. Run manually: npm i -g @openai/codex. ${cliTools.codex.error || ''}`.trim());
|
|
1674
1381
|
if (cliTools.codex.status === 'installed_not_on_path') console.log(`\nCodex CLI installed but not on PATH. ${cliTools.codex.hint}`);
|
|
1675
|
-
if (!cliTools.
|
|
1382
|
+
if (!cliTools.tmux.ok) console.log(`\ntmux ${tmuxStatusKind(cliTools.tmux)}. Install: ${cliTools.tmux.install_hint}`);
|
|
1676
1383
|
if (!install.ok && install.scope === 'global') console.log('\nGlobal command missing. Run: npm i -g sneakoscope');
|
|
1677
1384
|
if (!install.ok && install.scope === 'project') console.log('\nProject package missing. Run: npm i -D sneakoscope');
|
|
1678
1385
|
if (!appRuntime.ok) console.log('\nCodex App and first-party Codex Computer Use are required for SKS QA/visual evidence; Browser Use is not a UI verification substitute. Run: sks codex-app check');
|
|
@@ -1743,7 +1450,7 @@ async function doctor(args) {
|
|
|
1743
1450
|
const dbScan = await scanDbSafety(root).catch((err) => ({ ok: false, findings: [{ id: 'db_safety_scan_failed', severity: 'high', reason: err.message }] }));
|
|
1744
1451
|
const context7Status = await checkContext7(root);
|
|
1745
1452
|
const appRuntime = await codexAppIntegrationStatus({ codex });
|
|
1746
|
-
const
|
|
1453
|
+
const tmuxStatus = await tmuxReadiness().catch((err) => ({ ok: false, version: null, error: err.message }));
|
|
1747
1454
|
const skillStatus = await checkRequiredSkills(root);
|
|
1748
1455
|
const globalSkillStatus = await checkRequiredSkills(null, globalCodexSkillsRoot());
|
|
1749
1456
|
const guardStatus = await harnessGuardStatus(root);
|
|
@@ -1764,7 +1471,7 @@ async function doctor(args) {
|
|
|
1764
1471
|
sneakoscope: { ok: await exists(path.join(root, '.sneakoscope')) },
|
|
1765
1472
|
context7: context7Status,
|
|
1766
1473
|
codex_app_runtime: appRuntime,
|
|
1767
|
-
runtime: {
|
|
1474
|
+
runtime: { tmux: { ok: Boolean(tmuxStatus.ok), bin: tmuxStatus.bin || null, version: tmuxStatus.version || null, min_version: tmuxStatus.min_version || '3.0', current_session: Boolean(tmuxStatus.current_session), install_hint: tmuxStatus.ok ? null : platformTmuxInstallHint(), error: tmuxStatus.error || null } },
|
|
1768
1475
|
harness_guard: guardStatus,
|
|
1769
1476
|
versioning: versioningInfo,
|
|
1770
1477
|
db_guard: { ok: dbPolicyExists && dbScan.ok, policy: dbPolicyExists ? await loadDbSafetyPolicy(root) : null, scan: dbScan },
|
|
@@ -1776,7 +1483,7 @@ async function doctor(args) {
|
|
|
1776
1483
|
},
|
|
1777
1484
|
package: { bytes: pkgBytes, human: formatBytes(pkgBytes) }, storage
|
|
1778
1485
|
};
|
|
1779
|
-
result.ready = !result.harness_conflicts.hard_block && nodeOk && Boolean(codex.bin) && install.ok && result.sneakoscope.ok && result.context7.ok && appRuntime.ok && result.runtime.
|
|
1486
|
+
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;
|
|
1780
1487
|
if (result.harness_conflicts.hard_block) process.exitCode = 1;
|
|
1781
1488
|
if (flag(args, '--json')) return console.log(JSON.stringify(result, null, 2));
|
|
1782
1489
|
console.log('ㅅㅋㅅ Doctor\n');
|
|
@@ -1792,7 +1499,7 @@ async function doctor(args) {
|
|
|
1792
1499
|
console.log(`State: ${result.sneakoscope.ok ? 'ok' : 'missing .sneakoscope'}`);
|
|
1793
1500
|
console.log(`Context7: ${result.context7.ok ? 'ok' : 'missing MCP config'} project=${result.context7.project.ok ? 'ok' : 'missing'} global=${result.context7.global.ok ? 'ok' : 'missing'}`);
|
|
1794
1501
|
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'}`);
|
|
1795
|
-
console.log(`
|
|
1502
|
+
console.log(`tmux: ${tmuxStatusKind(result.runtime.tmux)} ${result.runtime.tmux.version || result.runtime.tmux.error || ''}`.trimEnd());
|
|
1796
1503
|
console.log(`Guard: ${result.harness_guard.ok ? 'ok' : 'blocked'}${result.harness_guard.source_exception ? ' source-exception' : ''}`);
|
|
1797
1504
|
console.log(`Version: ${result.versioning.ok ? 'ok' : 'missing'}${result.versioning.enabled ? ` ${result.versioning.package_version || ''}` : ` ${result.versioning.reason || 'disabled'}`}`);
|
|
1798
1505
|
console.log(`DB Guard: ${result.db_guard.ok ? 'ok' : 'blocked'} ${dbScan.findings?.length || 0} finding(s)`);
|
|
@@ -1809,13 +1516,13 @@ async function doctor(args) {
|
|
|
1809
1516
|
if (result.harness_conflicts.hard_block) console.log(`\n${formatHarnessConflictReport(conflictScan)}`);
|
|
1810
1517
|
if (!result.context7.ok) console.log('Context7 MCP missing. Run: sks context7 setup --scope project');
|
|
1811
1518
|
if (!appRuntime.ok) console.log('Codex App or first-party MCP/plugin tools missing. Run: sks codex-app check');
|
|
1812
|
-
if (!result.runtime.
|
|
1519
|
+
if (!result.runtime.tmux.ok) console.log('tmux missing. Run: sks deps install tmux');
|
|
1813
1520
|
if (!result.harness_guard.ok) console.log('Harness guard failed. Run: sks setup from a real terminal, then sks guard check.');
|
|
1814
1521
|
if (!result.versioning.ok) console.log('Versioning hook missing. Run: sks versioning hook, or sks doctor --fix.');
|
|
1815
1522
|
if (!result.skills.ok) console.log(`Missing skills: ${result.skills.missing.join(', ')}. Run: sks setup`);
|
|
1816
1523
|
if (!result.global_skills.ok) console.log(`Missing global $ skills: ${result.global_skills.missing.join(', ')}. Run: npm i -g sneakoscope, or sks setup from a non-local-only run.`);
|
|
1817
1524
|
const blocked = [];
|
|
1818
|
-
if (!result.runtime.
|
|
1525
|
+
if (!result.runtime.tmux.ok) blocked.push(['tmux is missing', 'sks deps install tmux']);
|
|
1819
1526
|
if (!appRuntime.ok) blocked.push(['Codex App or first-party MCP/plugin tools need setup', 'sks codex-app check']);
|
|
1820
1527
|
if (blocked.length) {
|
|
1821
1528
|
console.log('\nBlocked:');
|
|
@@ -1826,18 +1533,6 @@ async function doctor(args) {
|
|
|
1826
1533
|
if (!result.ready && !flag(args, '--fix')) console.log('Run: sks doctor --fix');
|
|
1827
1534
|
}
|
|
1828
1535
|
|
|
1829
|
-
async function checkRequiredSkills(root, skillRoot = path.join(root, '.agents', 'skills')) {
|
|
1830
|
-
const expected = Array.from(new Set([
|
|
1831
|
-
...DOLLAR_SKILL_NAMES,
|
|
1832
|
-
...RECOMMENDED_SKILLS
|
|
1833
|
-
])).sort();
|
|
1834
|
-
const missing = [];
|
|
1835
|
-
for (const name of expected) {
|
|
1836
|
-
if (!(await exists(path.join(skillRoot, name, 'SKILL.md')))) missing.push(name);
|
|
1837
|
-
}
|
|
1838
|
-
return { ok: missing.length === 0, root: skillRoot, expected, missing };
|
|
1839
|
-
}
|
|
1840
|
-
|
|
1841
1536
|
async function codexAppSkillReadiness(root = null) {
|
|
1842
1537
|
root ||= await sksRoot();
|
|
1843
1538
|
const project = await checkRequiredSkills(root);
|
|
@@ -1845,10 +1540,6 @@ async function codexAppSkillReadiness(root = null) {
|
|
|
1845
1540
|
return { ok: project.ok || global.ok, project, global };
|
|
1846
1541
|
}
|
|
1847
1542
|
|
|
1848
|
-
function globalCodexSkillsRoot(home = process.env.HOME || os.homedir()) {
|
|
1849
|
-
return path.join(home, '.agents', 'skills');
|
|
1850
|
-
}
|
|
1851
|
-
|
|
1852
1543
|
async function init(args) {
|
|
1853
1544
|
const root = await projectRoot();
|
|
1854
1545
|
const conflicts = await scanHarnessConflicts(root);
|
|
@@ -2100,15 +1791,15 @@ async function selftest() {
|
|
|
2100
1791
|
await ensureDir(path.join(conflictTmp, '.omx'));
|
|
2101
1792
|
const conflictScan = await scanHarnessConflicts(conflictTmp, { home: path.join(conflictTmp, 'home') });
|
|
2102
1793
|
if (!conflictScan.hard_block || !formatHarnessConflictReport(conflictScan).includes('GPT-5.5')) throw new Error('selftest failed: OMX conflict did not block with cleanup prompt');
|
|
2103
|
-
const postinstallConflict = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'postinstall'], { cwd: conflictTmp, env: { INIT_CWD: conflictTmp, HOME: path.join(conflictTmp, 'home'), SKS_SKIP_POSTINSTALL_SHIM: '1', SKS_SKIP_POSTINSTALL_CONTEXT7: '1' }, timeoutMs: 15000, maxOutputBytes: 128 * 1024 });
|
|
1794
|
+
const postinstallConflict = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'postinstall'], { cwd: conflictTmp, env: { INIT_CWD: conflictTmp, HOME: path.join(conflictTmp, 'home'), SKS_SKIP_POSTINSTALL_SHIM: '1', SKS_SKIP_POSTINSTALL_CONTEXT7: '1', SKS_SKIP_POSTINSTALL_GETDESIGN: '1' }, timeoutMs: 15000, maxOutputBytes: 128 * 1024 });
|
|
2104
1795
|
if (postinstallConflict.code !== 0) throw new Error('selftest failed: postinstall conflict notice should not make npm install fail');
|
|
2105
1796
|
const postinstallConflictOutput = String(`${postinstallConflict.stdout}\n${postinstallConflict.stderr}`);
|
|
2106
1797
|
if (!postinstallConflictOutput.includes('SKS setup is blocked') || postinstallConflictOutput.includes('Cleanup prompt:')) throw new Error('selftest failed: postinstall conflict notice did not stay informational');
|
|
2107
|
-
const postinstallConflictPrompt = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'postinstall'], { cwd: conflictTmp, input: 'y\n', env: { INIT_CWD: conflictTmp, HOME: path.join(conflictTmp, 'home'), SKS_SKIP_POSTINSTALL_SHIM: '1', SKS_SKIP_POSTINSTALL_CONTEXT7: '1', SKS_POSTINSTALL_PROMPT: '1' }, timeoutMs: 15000, maxOutputBytes: 128 * 1024 });
|
|
1798
|
+
const postinstallConflictPrompt = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'postinstall'], { cwd: conflictTmp, input: 'y\n', env: { INIT_CWD: conflictTmp, HOME: path.join(conflictTmp, 'home'), SKS_SKIP_POSTINSTALL_SHIM: '1', SKS_SKIP_POSTINSTALL_CONTEXT7: '1', SKS_SKIP_POSTINSTALL_GETDESIGN: '1', SKS_POSTINSTALL_PROMPT: '1' }, timeoutMs: 15000, maxOutputBytes: 128 * 1024 });
|
|
2108
1799
|
if (postinstallConflictPrompt.code !== 0 || !String(postinstallConflictPrompt.stdout || '').includes('Goal: completely remove the conflicting Codex harnesses')) throw new Error('selftest failed: interactive postinstall prompt did not print cleanup prompt');
|
|
2109
1800
|
const postinstallSetupTmp = tmpdir();
|
|
2110
1801
|
await writeJsonAtomic(path.join(postinstallSetupTmp, 'package.json'), { name: 'postinstall-setup-smoke', version: '0.0.0' });
|
|
2111
|
-
const postinstallSetup = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'postinstall'], { cwd: postinstallSetupTmp, env: { INIT_CWD: postinstallSetupTmp, HOME: path.join(postinstallSetupTmp, 'home'), SKS_SKIP_POSTINSTALL_SHIM: '1', SKS_SKIP_POSTINSTALL_CONTEXT7: '1', SKS_SKIP_CLI_TOOLS: '1' }, timeoutMs: 30000, maxOutputBytes: 256 * 1024 });
|
|
1802
|
+
const postinstallSetup = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'postinstall'], { cwd: postinstallSetupTmp, env: { INIT_CWD: postinstallSetupTmp, HOME: path.join(postinstallSetupTmp, 'home'), SKS_SKIP_POSTINSTALL_SHIM: '1', SKS_SKIP_POSTINSTALL_CONTEXT7: '1', SKS_SKIP_POSTINSTALL_GETDESIGN: '1', SKS_SKIP_CLI_TOOLS: '1' }, timeoutMs: 30000, maxOutputBytes: 256 * 1024 });
|
|
2112
1803
|
if (postinstallSetup.code !== 0) throw new Error(`selftest failed: postinstall setup exited ${postinstallSetup.code}: ${postinstallSetup.stderr}`);
|
|
2113
1804
|
if (await exists(path.join(postinstallSetupTmp, '.agents', 'skills', 'agent-team', 'SKILL.md'))) throw new Error('selftest failed: postinstall installed deprecated agent-team fallback skill');
|
|
2114
1805
|
if (!String(postinstallSetup.stdout || '').includes('SKS bootstrap: auto-running sks setup --bootstrap --install-scope global --force') || !String(postinstallSetup.stdout || '').includes('SKS Ready')) throw new Error('selftest failed: postinstall did not auto-run global forced bootstrap');
|
|
@@ -2116,6 +1807,7 @@ async function selftest() {
|
|
|
2116
1807
|
if (!String(postinstallSetup.stdout || '').includes('Codex App global $ skills: installed')) throw new Error('selftest failed: postinstall did not report automatic global Codex App skills');
|
|
2117
1808
|
const postinstallSetupManifest = await readJson(path.join(postinstallSetupTmp, '.sneakoscope', 'manifest.json'));
|
|
2118
1809
|
if (postinstallSetupManifest.installation?.scope !== 'global') throw new Error('selftest failed: postinstall automatic bootstrap did not use global install scope');
|
|
1810
|
+
if (!postinstallSetupManifest.recommended_design_references?.some((entry) => entry.id === 'getdesign' && entry.codex_skill_install === GETDESIGN_REFERENCE.codex_skill_install)) throw new Error('selftest failed: postinstall manifest missing getdesign reference');
|
|
2119
1811
|
for (const rel of ['.agents/skills/team/SKILL.md', '.codex/config.toml', '.codex/hooks.json', '.sneakoscope/harness-guard.json', '.codex/SNEAKOSCOPE.md', 'AGENTS.md', '.gitignore']) {
|
|
2120
1812
|
if (!(await exists(path.join(postinstallSetupTmp, rel)))) throw new Error(`selftest failed: automatic postinstall bootstrap did not create ${rel}`);
|
|
2121
1813
|
}
|
|
@@ -2125,6 +1817,7 @@ async function selftest() {
|
|
|
2125
1817
|
const skillName = command.slice(1).toLowerCase();
|
|
2126
1818
|
if (!(await exists(path.join(postinstallSetupTmp, 'home', '.agents', 'skills', skillName, 'SKILL.md')))) throw new Error(`selftest failed: postinstall global ${command} skill not installed`);
|
|
2127
1819
|
}
|
|
1820
|
+
if (!(await exists(path.join(postinstallSetupTmp, 'home', '.agents', 'skills', 'getdesign-reference', 'SKILL.md')))) throw new Error('selftest failed: postinstall global getdesign-reference skill not installed');
|
|
2128
1821
|
const oldNoBootstrap = process.env.SKS_POSTINSTALL_NO_BOOTSTRAP;
|
|
2129
1822
|
process.env.SKS_POSTINSTALL_NO_BOOTSTRAP = '1';
|
|
2130
1823
|
const noBootstrapDecision = await postinstallBootstrapDecision(postinstallSetupTmp);
|
|
@@ -2138,7 +1831,7 @@ async function selftest() {
|
|
|
2138
1831
|
if (!bootstrapResult.project_setup?.ok || typeof bootstrapResult.ready !== 'boolean') throw new Error('selftest failed: bootstrap json did not report project setup and ready boolean');
|
|
2139
1832
|
const depsCheck = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'deps', 'check', '--json'], { cwd: bootstrapJsonTmp, env: { HOME: path.join(bootstrapJsonTmp, 'home') }, timeoutMs: 20000, maxOutputBytes: 256 * 1024 });
|
|
2140
1833
|
const depsResult = JSON.parse(depsCheck.stdout);
|
|
2141
|
-
if (!depsResult.node?.ok || !('
|
|
1834
|
+
if (!depsResult.node?.ok || !('tmux' in depsResult) || !('homebrew' in depsResult)) throw new Error('selftest failed: deps check json missing expected fields');
|
|
2142
1835
|
const globalCwd = tmpdir();
|
|
2143
1836
|
const globalRuntimeRoot = path.join(tmpdir(), 'sks-global-root');
|
|
2144
1837
|
const globalRootProbe = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'root', '--json'], { cwd: globalCwd, env: { SKS_GLOBAL_ROOT: globalRuntimeRoot }, timeoutMs: 15000, maxOutputBytes: 64 * 1024 });
|
|
@@ -2156,32 +1849,13 @@ async function selftest() {
|
|
|
2156
1849
|
const madProfileText = await safeReadText(madProfilePath);
|
|
2157
1850
|
if (madProfile.profile_name !== 'sks-mad-high' || !madProfileText.includes('sandbox_mode = "danger-full-access"') || !madProfileText.includes('approval_policy = "on-request"') || !madProfileText.includes('approvals_reviewer = "auto_review"') || !madProfileText.includes('model_reasoning_effort = "high"') || !madProfileText.includes('unrequested fallback implementation code')) throw new Error('selftest failed: MAD high profile is not full-access auto-review high with fallback-code guard');
|
|
2158
1851
|
if (!isMadHighLaunch(['--mad', '--high']) || isMadHighLaunch(['db', '--mad'])) throw new Error('selftest failed: MAD high launch flag parsing is not top-level only');
|
|
2159
|
-
const workspacePlan = {
|
|
2160
|
-
const
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
if (!
|
|
2165
|
-
if (
|
|
2166
|
-
const warpNestedDecision = warpOpenLaunchDecision({ env: { TERM_PROGRAM: 'WarpTerminal' } });
|
|
2167
|
-
if (warpNestedDecision.open || !warpNestedDecision.current_session) throw new Error('selftest failed: nested Warp launch was not redirected to current session');
|
|
2168
|
-
const oldWarpConfigDir = process.env.SKS_WARP_LAUNCH_CONFIG_DIR;
|
|
2169
|
-
process.env.SKS_WARP_LAUNCH_CONFIG_DIR = path.join(tmp, 'warp-launch-configs');
|
|
2170
|
-
const writtenWarpConfig = await writeWarpLaunchConfig({ ...workspacePlan, command: 'codex', title: 'sks-mad-selftest' }, [{ cwd: tmp, command: 'codex --profile sks-mad-high', focused: true }]);
|
|
2171
|
-
if (!(await exists(writtenWarpConfig.config_path)) || !writtenWarpConfig.record.launch_uri.includes('warp://launch/')) throw new Error('selftest failed: Warp launch configuration was not persisted for URI launch');
|
|
2172
|
-
const currentSessionLaunch = await launchWarpUi(['--workspace', 'sks-current-session-selftest'], {
|
|
2173
|
-
root: tmp,
|
|
2174
|
-
codex: { bin: 'printf', version: 'mock' },
|
|
2175
|
-
app: { ok: true, guidance: [] },
|
|
2176
|
-
warp: { ok: true, version: 'Warp.app' },
|
|
2177
|
-
env: { TERM_PROGRAM: 'WarpTerminal' },
|
|
2178
|
-
dryRunCurrentSession: true,
|
|
2179
|
-
quiet: true
|
|
2180
|
-
});
|
|
2181
|
-
if (!currentSessionLaunch.opened?.current_session || currentSessionLaunch.opened?.skipped) throw new Error('selftest failed: Warp shell launch did not stay in the current session');
|
|
2182
|
-
if (oldWarpConfigDir === undefined) delete process.env.SKS_WARP_LAUNCH_CONFIG_DIR;
|
|
2183
|
-
else process.env.SKS_WARP_LAUNCH_CONFIG_DIR = oldWarpConfigDir;
|
|
2184
|
-
if (warpStatusKind({ ok: false, bin: null }) !== 'missing') throw new Error('selftest failed: missing warp was not labeled missing');
|
|
1852
|
+
const workspacePlan = { session: 'sks-mad-selftest', root: tmp, codexArgs: ['--profile', 'sks-mad-high'] };
|
|
1853
|
+
const tmuxSyntax = runTmuxLaunchPlanSyntaxCheck(workspacePlan);
|
|
1854
|
+
if (!tmuxSyntax.ok || !tmuxSyntax.command.includes('tmux attach-session -t sks-mad-selftest')) throw new Error('selftest failed: MAD tmux attach plan is not stable by session name');
|
|
1855
|
+
const tmuxOpenArgs = buildTmuxOpenArgs(workspacePlan);
|
|
1856
|
+
if (tmuxOpenArgs.join(' ') !== 'attach-session -t sks-mad-selftest') throw new Error('selftest failed: MAD tmux attach args are not stable by session name');
|
|
1857
|
+
if (!isTmuxShellSession({ TMUX: '/tmp/tmux-501/default,1,0' })) throw new Error('selftest failed: tmux shell session env was not detected');
|
|
1858
|
+
if (tmuxStatusKind({ ok: false, bin: null }) !== 'missing') throw new Error('selftest failed: missing tmux was not labeled missing');
|
|
2185
1859
|
const guardBlocked = await checkHarnessModification(tmp, { tool_name: 'apply_patch', command: '*** Update File: .agents/skills/team/SKILL.md\n+tamper\n' });
|
|
2186
1860
|
if (guardBlocked.action !== 'block') throw new Error('selftest failed: harness guard allowed skill tampering');
|
|
2187
1861
|
const setupBlocked = await checkHarnessModification(tmp, { command: 'sks setup --force' });
|
|
@@ -2325,16 +1999,19 @@ async function selftest() {
|
|
|
2325
1999
|
const promptPipelineText = await safeReadText(path.join(tmp, '.agents', 'skills', 'prompt-pipeline', 'SKILL.md'));
|
|
2326
2000
|
if (!promptPipelineText.includes('TriWiki context-tracking SSOT')) throw new Error('selftest failed: prompt pipeline missing TriWiki context-tracking SSOT');
|
|
2327
2001
|
if (!promptPipelineText.includes('before every route stage') || !promptPipelineText.includes('sks wiki refresh')) throw new Error('selftest failed: prompt pipeline missing per-stage TriWiki policy');
|
|
2328
|
-
if (!promptPipelineText.includes('design.md') || !promptPipelineText.includes('imagegen')) throw new Error('selftest failed: prompt pipeline missing design/image
|
|
2002
|
+
if (!promptPipelineText.includes('design.md') || !promptPipelineText.includes('imagegen') || !promptPipelineText.includes('getdesign-reference')) throw new Error('selftest failed: prompt pipeline missing design/image/getdesign routing');
|
|
2003
|
+
if (!promptPipelineText.includes(CODEX_APP_IMAGE_GENERATION_DOC_URL)) throw new Error('selftest failed: prompt pipeline missing Codex App image generation policy');
|
|
2329
2004
|
if (!promptPipelineText.includes('From-Chat-IMG') || !promptPipelineText.includes('Do not assume ordinary image prompts are chat captures')) throw new Error('selftest failed: prompt pipeline missing explicit From-Chat-IMG gating');
|
|
2330
2005
|
const fromChatImgSkillText = await safeReadText(path.join(tmp, '.agents', 'skills', 'from-chat-img', 'SKILL.md'));
|
|
2331
2006
|
if (!fromChatImgSkillText.includes('normal Team pipeline') || !fromChatImgSkillText.includes('Codex Computer Use visual inspection') || !fromChatImgSkillText.includes(CODEX_COMPUTER_USE_ONLY_POLICY) || !fromChatImgSkillText.includes(FROM_CHAT_IMG_CHECKLIST_ARTIFACT) || !fromChatImgSkillText.includes(FROM_CHAT_IMG_TEMP_TRIWIKI_ARTIFACT) || !fromChatImgSkillText.includes(FROM_CHAT_IMG_QA_LOOP_ARTIFACT)) throw new Error('selftest failed: from-chat-img skill missing Team/Computer Use-only inspection checklist guidance');
|
|
2332
2007
|
if (fromChatImgSkillText.includes('Computer Use/browser visual inspection')) throw new Error('selftest failed: from-chat-img skill still allows browser visual inspection wording');
|
|
2333
2008
|
const fromChatImgSkillMeta = await safeReadText(path.join(tmp, '.agents', 'skills', 'from-chat-img', 'agents', 'openai.yaml'));
|
|
2334
2009
|
if (!fromChatImgSkillMeta.includes('model_reasoning_effort: xhigh')) throw new Error('selftest failed: from-chat-img skill metadata is not xhigh');
|
|
2335
|
-
for (const supportSkill of ['reasoning-router', 'pipeline-runner', 'context7-docs', 'seo-geo-optimizer', 'reflection', 'design-system-builder', 'design-ui-editor', 'imagegen']) {
|
|
2010
|
+
for (const supportSkill of ['reasoning-router', 'pipeline-runner', 'context7-docs', 'seo-geo-optimizer', 'reflection', 'design-system-builder', 'design-ui-editor', 'getdesign-reference', 'imagegen']) {
|
|
2336
2011
|
if (!(await exists(path.join(tmp, '.agents', 'skills', supportSkill, 'SKILL.md')))) throw new Error(`selftest failed: ${supportSkill} skill not installed`);
|
|
2337
2012
|
}
|
|
2013
|
+
const imagegenSkillText = await safeReadText(path.join(tmp, '.agents', 'skills', 'imagegen', 'SKILL.md'));
|
|
2014
|
+
if (!imagegenSkillText.includes(CODEX_APP_IMAGE_GENERATION_DOC_URL) || !imagegenSkillText.includes('$imagegen') || !imagegenSkillText.includes('gpt-image-2') || !imagegenSkillText.includes('OPENAI_API_KEY')) throw new Error('selftest failed: imagegen skill missing official Codex App image generation priority');
|
|
2338
2015
|
if (!(await exists(path.join(tmp, '.agents', 'skills', 'reasoning-router', 'agents', 'openai.yaml')))) throw new Error('selftest failed: skill metadata missing');
|
|
2339
2016
|
const hookGuardPayload = JSON.stringify({ cwd: tmp, tool_name: 'apply_patch', command: '*** Update File: .agents/skills/team/SKILL.md\n+tamper\n' });
|
|
2340
2017
|
const hookGuardResult = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'hook', 'pre-tool'], { cwd: tmp, input: hookGuardPayload, env: { SKS_DISABLE_UPDATE_CHECK: '1' }, timeoutMs: 15000, maxOutputBytes: 64 * 1024 });
|
|
@@ -2402,7 +2079,7 @@ async function selftest() {
|
|
|
2402
2079
|
if (!(await exists(path.join(missionDir(hookGoalTmp, hookState.mission_id), GOAL_WORKFLOW_ARTIFACT)))) throw new Error('selftest failed: $Goal hook did not write goal workflow artifact');
|
|
2403
2080
|
const hookGoalDelegationTmp = tmpdir();
|
|
2404
2081
|
await initProject(hookGoalDelegationTmp, {});
|
|
2405
|
-
const hookGoalDelegationPayload = JSON.stringify({ cwd: hookGoalDelegationTmp, prompt: '$Goal
|
|
2082
|
+
const hookGoalDelegationPayload = JSON.stringify({ cwd: hookGoalDelegationTmp, prompt: '$Goal 결제 재시도 정책 근본적으로 구현 수정해줘' });
|
|
2406
2083
|
const hookGoalDelegationResult = await runProcess(process.execPath, [hookBin, 'hook', 'user-prompt-submit'], { cwd: hookGoalDelegationTmp, input: hookGoalDelegationPayload, env: { SKS_DISABLE_UPDATE_CHECK: '1' }, timeoutMs: 15000, maxOutputBytes: 256 * 1024 });
|
|
2407
2084
|
if (hookGoalDelegationResult.code !== 0) throw new Error(`selftest failed: $Goal implementation delegation hook exited ${hookGoalDelegationResult.code}: ${hookGoalDelegationResult.stderr}`);
|
|
2408
2085
|
const hookGoalDelegationJson = JSON.parse(hookGoalDelegationResult.stdout);
|
|
@@ -2414,7 +2091,7 @@ async function selftest() {
|
|
|
2414
2091
|
if (hookGoalDelegationState.mode !== 'TEAM' || hookGoalDelegationState.phase !== 'TEAM_CLARIFICATION_AWAITING_ANSWERS' || hookGoalDelegationState.implementation_allowed !== false) throw new Error('selftest failed: $Goal implementation delegation did not leave Team gate current');
|
|
2415
2092
|
if (!(await exists(path.join(missionDir(hookGoalDelegationTmp, hookGoalDelegationBridgeMatch[1]), GOAL_WORKFLOW_ARTIFACT)))) throw new Error('selftest failed: $Goal implementation delegation did not write bridge workflow artifact');
|
|
2416
2093
|
const activeGoalMissionId = hookState.mission_id;
|
|
2417
|
-
const hookGoalOverlayPayload = JSON.stringify({ cwd: hookGoalTmp, prompt: '
|
|
2094
|
+
const hookGoalOverlayPayload = JSON.stringify({ cwd: hookGoalTmp, prompt: '결제 재시도 정책 근본적으로 구현 수정해줘' });
|
|
2418
2095
|
const hookGoalOverlayResult = await runProcess(process.execPath, [hookBin, 'hook', 'user-prompt-submit'], { cwd: hookGoalTmp, input: hookGoalOverlayPayload, env: { SKS_DISABLE_UPDATE_CHECK: '1' }, timeoutMs: 15000, maxOutputBytes: 256 * 1024 });
|
|
2419
2096
|
if (hookGoalOverlayResult.code !== 0) throw new Error(`selftest failed: active Goal overlay hook exited ${hookGoalOverlayResult.code}: ${hookGoalOverlayResult.stderr}`);
|
|
2420
2097
|
const hookGoalOverlayJson = JSON.parse(hookGoalOverlayResult.stdout);
|
|
@@ -2514,14 +2191,14 @@ async function selftest() {
|
|
|
2514
2191
|
if (hookKoreanSksState.phase !== 'TEAM_CLARIFICATION_CONTRACT_SEALED' || hookKoreanSksState.implementation_allowed !== true || !hookKoreanSksState.ambiguity_gate_passed) throw new Error('selftest failed: Korean Team auto-seal');
|
|
2515
2192
|
const hookTeamTmp = tmpdir();
|
|
2516
2193
|
await initProject(hookTeamTmp, {});
|
|
2517
|
-
const hookTeamPayload = JSON.stringify({ cwd: hookTeamTmp, prompt: '$Team
|
|
2194
|
+
const hookTeamPayload = JSON.stringify({ cwd: hookTeamTmp, prompt: '$Team 결제 재시도 정책 수정 executor:2 reviewer:1 user:1' });
|
|
2518
2195
|
const hookTeamResult = await runProcess(process.execPath, [hookBin, 'hook', 'user-prompt-submit'], { cwd: hookTeamTmp, input: hookTeamPayload, env: { SKS_DISABLE_UPDATE_CHECK: '1' }, timeoutMs: 15000, maxOutputBytes: 256 * 1024 });
|
|
2519
2196
|
if (hookTeamResult.code !== 0) throw new Error(`selftest failed: $Team hook exited ${hookTeamResult.code}: ${hookTeamResult.stderr}`);
|
|
2520
2197
|
const hookTeamJson = JSON.parse(hookTeamResult.stdout);
|
|
2521
2198
|
if (!hookTeamJson.hookSpecificOutput?.additionalContext?.includes('MANDATORY ambiguity-removal gate activated')) throw new Error('selftest failed: $Team hook did not force ambiguity gate before Team execution');
|
|
2522
2199
|
if (!hookTeamJson.hookSpecificOutput?.additionalContext?.includes('VISIBLE RESPONSE CONTRACT') || !String(hookTeamJson.systemMessage || '').includes('clarification questions')) throw new Error('selftest failed: $Team ambiguity gate did not force visible question response');
|
|
2523
2200
|
if (hookTeamJson.hookSpecificOutput?.additionalContext?.includes('GOAL_PRECISE: 이번 작업의 최종 목표')) throw new Error('selftest failed: static Team goal');
|
|
2524
|
-
if (!hookTeamJson.hookSpecificOutput?.additionalContext?.includes('
|
|
2201
|
+
if (!hookTeamJson.hookSpecificOutput?.additionalContext?.includes('PAYMENT_RETRY_POLICY')) throw new Error('selftest failed: missing Team payment question');
|
|
2525
2202
|
if (!hookTeamJson.hookSpecificOutput?.additionalContext?.includes('Codex plan-tool interaction')) throw new Error('selftest failed: $Team ambiguity gate did not inject plan-tool guidance');
|
|
2526
2203
|
const hookTeamState = await readJson(stateFile(hookTeamTmp), {});
|
|
2527
2204
|
if (hookTeamState.phase !== 'TEAM_CLARIFICATION_AWAITING_ANSWERS' || hookTeamState.implementation_allowed !== false) throw new Error('selftest failed: $Team hook did not lock execution behind ambiguity gate');
|
|
@@ -2533,13 +2210,13 @@ async function selftest() {
|
|
|
2533
2210
|
const hookTeamPendingState = await readJson(stateFile(hookTeamTmp), {});
|
|
2534
2211
|
const hookTeamPendingContext = hookTeamPendingJson.hookSpecificOutput?.additionalContext || '';
|
|
2535
2212
|
if (hookTeamPendingState.mission_id !== hookTeamState.mission_id) throw new Error('selftest failed: pending clarification allowed a new route mission to replace the visible question sheet');
|
|
2536
|
-
if (!hookTeamPendingContext.includes('Required questions still pending') || !hookTeamPendingContext.includes('VISIBLE RESPONSE CONTRACT') || !hookTeamPendingContext.includes('
|
|
2213
|
+
if (!hookTeamPendingContext.includes('Required questions still pending') || !hookTeamPendingContext.includes('VISIBLE RESPONSE CONTRACT') || !hookTeamPendingContext.includes('PAYMENT_RETRY_POLICY')) throw new Error('selftest failed: pending clarification did not re-expose the question sheet');
|
|
2537
2214
|
if (hookTeamPendingContext.includes('MANDATORY ambiguity-removal gate activated')) throw new Error('selftest failed: pending clarification prepared a new ambiguity gate instead of reusing the active one');
|
|
2538
2215
|
const hookTeamStopResult = await runProcess(process.execPath, [hookBin, 'hook', 'stop'], { cwd: hookTeamTmp, input: JSON.stringify({ cwd: hookTeamTmp, last_assistant_message: 'I need three decisions before implementation, but I will not paste the Required questions block.' }), env: { SKS_DISABLE_UPDATE_CHECK: '1' }, timeoutMs: 15000, maxOutputBytes: 128 * 1024 });
|
|
2539
2216
|
if (hookTeamStopResult.code !== 0) throw new Error(`selftest failed: Team stop hook exited ${hookTeamStopResult.code}: ${hookTeamStopResult.stderr}`);
|
|
2540
2217
|
const hookTeamStopJson = JSON.parse(hookTeamStopResult.stdout);
|
|
2541
2218
|
if (hookTeamStopJson.decision !== 'block' || !String(hookTeamStopJson.reason || '').includes('mandatory ambiguity-removal')) throw new Error('selftest failed: Stop hook did not block missing Team ambiguity answers');
|
|
2542
|
-
if (!String(hookTeamStopJson.reason || '').includes('Required questions') || !String(hookTeamStopJson.reason || '').includes('
|
|
2219
|
+
if (!String(hookTeamStopJson.reason || '').includes('Required questions') || !String(hookTeamStopJson.reason || '').includes('PAYMENT_RETRY_POLICY')) throw new Error('selftest failed: missing Team stop payment question');
|
|
2543
2220
|
if (String(hookTeamStopJson.reason || '').includes('GOAL_PRECISE: 이번 작업의 최종 목표')) throw new Error('selftest failed: static Team stop goal');
|
|
2544
2221
|
if (!String(hookTeamStopJson.reason || '').includes('sks pipeline answer')) throw new Error('selftest failed: Stop hook did not provide pipeline answer command');
|
|
2545
2222
|
if (!String(hookTeamStopJson.reason || '').includes('Codex plan-tool interaction')) throw new Error('selftest failed: Stop hook did not reprint plan-tool guidance');
|
|
@@ -2972,19 +2649,19 @@ async function selftest() {
|
|
|
2972
2649
|
if (maxTextParsed.agentSessions !== 6 || maxTextParsed.roleCounts.executor !== 6) throw new Error('selftest failed: team max-agent text parsing');
|
|
2973
2650
|
const roleParsed = parseTeamCreateArgs(['executor:5', 'reviewer:2', 'user:1', '작업']);
|
|
2974
2651
|
if (roleParsed.roleCounts.executor !== 5 || roleParsed.roleCounts.reviewer !== 2 || roleParsed.agentSessions !== 5 || roleParsed.prompt !== '작업') throw new Error('selftest failed: team role-count parsing');
|
|
2975
|
-
const
|
|
2976
|
-
if (
|
|
2652
|
+
const openTmuxFlagParsed = parseTeamCreateArgs(['--open-tmux', '작업']);
|
|
2653
|
+
if (openTmuxFlagParsed.prompt !== '작업') throw new Error('selftest failed: team --open-tmux leaked into prompt');
|
|
2977
2654
|
const roleTeamPlan = buildTeamPlan(teamId, '역할 팀 테스트', { roleCounts: roleParsed.roleCounts });
|
|
2978
2655
|
if (roleTeamPlan.roster.debate_team.length !== 5) throw new Error('selftest failed: executor role count not reflected in debate team size');
|
|
2979
2656
|
if (roleTeamPlan.roster.analysis_team.length !== 5) throw new Error('selftest failed: executor role count not reflected in analysis scout team');
|
|
2980
2657
|
if (roleTeamPlan.roster.development_team.filter((agent) => agent.role === 'executor').length !== 5) throw new Error('selftest failed: executor role count not reflected in development team');
|
|
2981
2658
|
if (!roleTeamPlan.roster.debate_team.some((agent) => /inconvenience/.test(agent.persona))) throw new Error('selftest failed: user friction persona missing from debate team');
|
|
2982
|
-
const
|
|
2983
|
-
if (!
|
|
2984
|
-
if (!
|
|
2985
|
-
if (teamLaneStyle('analysis_scout_1').role !== 'scout' || teamLaneStyle('executor_1').role !== 'execution' || teamLaneStyle('reviewer_1').role !== 'review') throw new Error('selftest failed: Team
|
|
2986
|
-
if (!String(
|
|
2987
|
-
if (!
|
|
2659
|
+
const tmuxTeam = await launchTmuxTeamView({ root: tmp, missionId: teamId, plan: roleTeamPlan, json: true });
|
|
2660
|
+
if (!tmuxTeam.agents?.length || !tmuxTeam.agents.some((entry) => entry.agent === 'analysis_scout_1') || !tmuxTeam.agents.every((entry) => String(entry.command || '').includes('team lane') && String(entry.command || '').includes('--agent'))) throw new Error('selftest failed: Team tmux view did not expose agent live lanes');
|
|
2661
|
+
if (!tmuxTeam.overview?.command?.includes('team watch') || !tmuxTeam.lanes?.some((entry) => entry.role === 'overview') || !tmuxTeam.lanes?.some((entry) => entry.agent === 'analysis_scout_1')) throw new Error('selftest failed: Team tmux view did not expose orchestration overview plus agent lanes');
|
|
2662
|
+
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');
|
|
2663
|
+
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');
|
|
2664
|
+
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');
|
|
2988
2665
|
if (routeReasoning(routePrompt('$Research frontier idea'), '$Research frontier idea').effort !== 'xhigh') throw new Error('selftest failed: research reasoning not xhigh');
|
|
2989
2666
|
if (routeReasoning(routePrompt('$From-Chat-IMG 채팅 이미지 작업'), '$From-Chat-IMG 채팅 이미지 작업').effort !== 'xhigh') throw new Error('selftest failed: From-Chat-IMG reasoning not xhigh');
|
|
2990
2667
|
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');
|
|
@@ -3068,6 +2745,63 @@ async function selftest() {
|
|
|
3068
2745
|
const teamLaneCli = await runProcess(process.execPath, [hookBin, 'team', 'lane', teamId, '--agent', 'analysis_scout_1', '--lines', '4'], { cwd: tmp, env: { SKS_DISABLE_UPDATE_CHECK: '1' }, timeoutMs: 15000, maxOutputBytes: 64 * 1024 });
|
|
3069
2746
|
if (teamLaneCli.code !== 0 || !String(teamLaneCli.stdout || '').includes('SKS Team Agent Lane') || !String(teamLaneCli.stdout || '').includes('analysis_scout_1')) throw new Error('selftest failed: sks team lane CLI did not render an agent lane');
|
|
3070
2747
|
await writeTextAtomic(path.join(teamDir, 'team-analysis.md'), '- claim: analysis scout mapped route registry | source: src/core/routes.mjs | risk: high | confidence: supported\n');
|
|
2748
|
+
const buttonUxSchema = buildQuestionSchema('$Team 버튼 UX 수정');
|
|
2749
|
+
const buttonUxSlotIds = buttonUxSchema.slots.map((s) => s.id);
|
|
2750
|
+
if (buttonUxSlotIds.includes('UI_STATE_BEHAVIOR') || buttonUxSlotIds.includes('VISUAL_REGRESSION_REQUIRED')) throw new Error('selftest failed: predictable UI defaults should be inferred, not asked');
|
|
2751
|
+
if (buttonUxSchema.inferred_answers.UI_STATE_BEHAVIOR !== 'infer_from_task_context_and_existing_design_system; preserve existing loading/error/empty/retry behavior unless explicitly requested; add only standard states required by the touched surface') throw new Error('selftest failed: UI state default inference missing');
|
|
2752
|
+
if (buttonUxSchema.inferred_answers.VISUAL_REGRESSION_REQUIRED !== 'yes_if_available') throw new Error('selftest failed: visual regression default inference missing');
|
|
2753
|
+
const pptRoute = routePrompt('$PPT 투자자용 피치덱 만들어줘');
|
|
2754
|
+
if (pptRoute?.id !== 'PPT') throw new Error('selftest failed: $PPT did not route to presentation pipeline');
|
|
2755
|
+
const pptSchema = buildQuestionSchema('$PPT 투자자용 피치덱 만들어줘');
|
|
2756
|
+
const pptSlotIds = pptSchema.slots.map((s) => s.id);
|
|
2757
|
+
for (const id of ['PRESENTATION_DELIVERY_CONTEXT', 'PRESENTATION_AUDIENCE_PROFILE', 'PRESENTATION_STP_STRATEGY', 'PRESENTATION_PAINPOINT_SOLUTION_MAP', 'PRESENTATION_DECISION_CONTEXT']) {
|
|
2758
|
+
if (!pptSlotIds.includes(id)) throw new Error(`selftest failed: PPT schema missing ${id}`);
|
|
2759
|
+
}
|
|
2760
|
+
const pptSkillText = await safeReadText(path.join(tmp, '.agents', 'skills', 'ppt', 'SKILL.md'));
|
|
2761
|
+
if (!pptSkillText.includes('STP') || !pptSkillText.includes('target audience profile') || !pptSkillText.includes('decision context') || !pptSkillText.includes('3+ pain-point to solution mappings')) throw new Error('selftest failed: generated PPT skill missing STP/audience/pain-point guidance');
|
|
2762
|
+
if (!pptSkillText.includes('simple, restrained, and information-first') || !pptSkillText.includes('over-designed decoration') || !pptSkillText.includes(CODEX_APP_IMAGE_GENERATION_DOC_URL)) throw new Error('selftest failed: generated PPT skill missing restrained design/imagegen guidance');
|
|
2763
|
+
if (!pptSkillText.includes('source-html/') || !pptSkillText.includes('temporary build files') || !pptSkillText.includes('ppt-parallel-report.json')) throw new Error('selftest failed: generated PPT skill missing source preservation/temp cleanup/parallel guidance');
|
|
2764
|
+
if (routeRequiresSubagents(pptRoute, '$PPT 투자자용 피치덱 만들어줘')) throw new Error('selftest failed: PPT route should not require subagents by default');
|
|
2765
|
+
if (!reflectionRequiredForRoute(pptRoute)) throw new Error('selftest failed: PPT route should require reflection');
|
|
2766
|
+
const pptMission = await createMission(tmp, { mode: 'ppt', prompt: '$PPT 투자자용 피치덱 만들어줘' });
|
|
2767
|
+
await writeQuestions(pptMission.dir, pptSchema);
|
|
2768
|
+
const pptAnswers = {
|
|
2769
|
+
PRESENTATION_DELIVERY_CONTEXT: '대형 화면 16:9 발표, 한국어, 10분',
|
|
2770
|
+
PRESENTATION_AUDIENCE_PROFILE: '투자자, 평균 40대, VC/전략투자 직무, SaaS 이해도 높음, 의사결정권 있음',
|
|
2771
|
+
PRESENTATION_STP_STRATEGY: 'Segmentation: 초기 B2B SaaS 투자자; Targeting: 운영 효율 SaaS에 관심 있는 VC; Positioning: 작은 도입으로 반복 운영비를 줄이는 팀',
|
|
2772
|
+
PRESENTATION_PAINPOINT_SOLUTION_MAP: ['반복 리서치 비용 -> 자동화된 근거 수집 -> 비용 절감 아하', '검토 자료 품질 편차 -> 표준화된 스토리보드 -> 신뢰 아하', '도입 리스크 -> 작은 파일럿 -> 낮은 리스크 아하'],
|
|
2773
|
+
PRESENTATION_DECISION_CONTEXT: '파일럿 투자 승인이 목표이며, 시장 차별성과 실행 리스크가 주요 반대논리'
|
|
2774
|
+
};
|
|
2775
|
+
await writeJsonAtomic(path.join(pptMission.dir, 'answers.json'), pptAnswers);
|
|
2776
|
+
const pptSeal = await sealContract(pptMission.dir, pptMission.mission);
|
|
2777
|
+
if (!pptSeal.ok) throw new Error('selftest failed: PPT answers rejected');
|
|
2778
|
+
await materializeAfterPipelineAnswer(tmp, pptMission.id, pptMission.dir, pptMission.mission, pptRoute, { route: 'PPT', command: '$PPT', mode: 'PPT', task: pptMission.mission.prompt, context7_required: false }, pptSeal.contract);
|
|
2779
|
+
const pptAudienceStrategy = await readJson(path.join(pptMission.dir, PPT_AUDIENCE_STRATEGY_ARTIFACT));
|
|
2780
|
+
if (!pptAudienceStrategy?.source_answers?.PRESENTATION_STP_STRATEGY || pptAudienceStrategy.painpoint_solution_map.length !== 3) throw new Error('selftest failed: PPT audience strategy was not materialized from sealed answers');
|
|
2781
|
+
const pptGate = await readJson(path.join(pptMission.dir, PPT_GATE_ARTIFACT));
|
|
2782
|
+
if (pptGate.passed !== false || pptGate.audience_strategy_sealed !== true || pptGate.painpoint_count !== 3) throw new Error('selftest failed: PPT gate did not initialize with sealed audience strategy');
|
|
2783
|
+
const pptBuildResult = await runProcess(process.execPath, [hookBin, 'ppt', 'build', pptMission.id, '--json'], { cwd: tmp, env: { SKS_DISABLE_UPDATE_CHECK: '1' }, timeoutMs: 15000, maxOutputBytes: 128 * 1024 });
|
|
2784
|
+
if (pptBuildResult.code !== 0) throw new Error(`selftest failed: sks ppt build failed: ${pptBuildResult.stderr || pptBuildResult.stdout}`);
|
|
2785
|
+
const pptBuild = JSON.parse(pptBuildResult.stdout);
|
|
2786
|
+
if (!pptBuild.ok || !pptBuild.gate?.passed || !pptBuild.gate?.parallel_build_recorded || !pptBuild.gate?.html_artifact_created || !pptBuild.gate?.source_html_preserved || !pptBuild.gate?.pdf_exported_or_explicitly_deferred || !pptBuild.gate?.render_qa_recorded || !pptBuild.gate?.temp_cleanup_recorded) throw new Error('selftest failed: PPT build did not pass artifact gate');
|
|
2787
|
+
if (!PPT_HTML_ARTIFACT.startsWith(`${PPT_SOURCE_HTML_DIR}/`)) throw new Error('selftest failed: PPT HTML source must be stored in source-html folder');
|
|
2788
|
+
const pptHtml = await safeReadText(path.join(pptMission.dir, PPT_HTML_ARTIFACT));
|
|
2789
|
+
if (!pptHtml.includes('<html') || pptHtml.includes('gradient')) throw new Error('selftest failed: PPT HTML artifact missing or over-designed');
|
|
2790
|
+
const audienceScript = pptHtml.match(/id="ppt-audience-strategy">([^<]+)<\/script>/);
|
|
2791
|
+
if (!audienceScript) throw new Error('selftest failed: PPT HTML missing audience strategy script data');
|
|
2792
|
+
JSON.parse(audienceScript[1]);
|
|
2793
|
+
const pptPdfBytes = await fsp.readFile(path.join(pptMission.dir, PPT_PDF_ARTIFACT));
|
|
2794
|
+
if (pptPdfBytes.subarray(0, 5).toString('utf8') !== '%PDF-') throw new Error('selftest failed: PPT PDF artifact does not have a PDF header');
|
|
2795
|
+
const pptRenderReport = await readJson(path.join(pptMission.dir, PPT_RENDER_REPORT_ARTIFACT));
|
|
2796
|
+
if (!pptRenderReport.passed || !pptRenderReport.design_policy_checks.every((check) => check.passed)) throw new Error('selftest failed: PPT render report did not pass design policy checks');
|
|
2797
|
+
const pptParallelReport = await readJson(path.join(pptMission.dir, PPT_PARALLEL_REPORT_ARTIFACT));
|
|
2798
|
+
if (!pptParallelReport.passed || pptParallelReport.parallel_group_count < 2 || !pptParallelReport.parallel_groups.some((group) => group.id === 'render_targets' && group.executed_in_parallel)) throw new Error('selftest failed: PPT parallel report did not record parallel build groups');
|
|
2799
|
+
const pptCleanupReport = await readJson(path.join(pptMission.dir, PPT_CLEANUP_REPORT_ARTIFACT));
|
|
2800
|
+
if (!pptCleanupReport.source_html_preserved || !pptCleanupReport.temp_cleanup_completed || pptCleanupReport.source_html_path !== PPT_HTML_ARTIFACT) throw new Error('selftest failed: PPT cleanup report did not preserve source HTML');
|
|
2801
|
+
if (await exists(path.join(pptMission.dir, PPT_TEMP_DIR))) throw new Error('selftest failed: PPT temp directory was not cleaned');
|
|
2802
|
+
if (await exists(path.join(pptMission.dir, 'artifact.html'))) throw new Error('selftest failed: legacy root PPT HTML should not remain after source-html preservation');
|
|
2803
|
+
const pptStatusResult = await runProcess(process.execPath, [hookBin, 'ppt', 'status', pptMission.id, '--json'], { cwd: tmp, env: { SKS_DISABLE_UPDATE_CHECK: '1' }, timeoutMs: 15000, maxOutputBytes: 128 * 1024 });
|
|
2804
|
+
if (pptStatusResult.code !== 0 || !JSON.parse(pptStatusResult.stdout).ok) throw new Error('selftest failed: sks ppt status did not report the built gate');
|
|
3071
2805
|
const installUxSchema = buildQuestionSchema('SKS first install/bootstrap UX and Context7 MCP setup improvement');
|
|
3072
2806
|
const installUxSlotIds = installUxSchema.slots.map((s) => s.id);
|
|
3073
2807
|
if (installUxSchema.domain_hints.includes('uiux') || installUxSlotIds.includes('VISUAL_REGRESSION_REQUIRED')) throw new Error('selftest failed: CLI UX install prompt should not ask visual UI questions');
|
|
@@ -3091,6 +2825,8 @@ async function selftest() {
|
|
|
3091
2825
|
if (classifyCommand('supabase db reset').level !== 'destructive') throw new Error('selftest failed: supabase db reset not detected');
|
|
3092
2826
|
const dbDecision = await checkDbOperation(tmp, { mission_id: id }, { tool_name: 'mcp__supabase__execute_sql', sql: 'drop table users;' }, { duringNoQuestion: true });
|
|
3093
2827
|
if (dbDecision.action !== 'block') throw new Error('selftest failed: destructive MCP SQL allowed');
|
|
2828
|
+
const computerUseDecision = await checkDbOperation(tmp, { mission_id: id }, { tool_name: 'mcp__computer_use__open_app', bundle_id: 'com.microsoft.edgemac', action: 'open_app' }, { duringNoQuestion: true });
|
|
2829
|
+
if (computerUseDecision.action !== 'allow') throw new Error('selftest failed: Computer Use MCP was blocked by DB safety gate');
|
|
3094
2830
|
const madMission = await createMission(tmp, { mode: 'mad-sks', prompt: '$MAD-SKS selftest scoped DB override' });
|
|
3095
2831
|
await writeJsonAtomic(path.join(madMission.dir, 'team-gate.json'), { schema_version: 1, passed: false, team_roster_confirmed: true });
|
|
3096
2832
|
const madState = { mission_id: madMission.id, mode: 'TEAM', route_command: '$Team', stop_gate: 'team-gate.json', mad_sks_active: true, mad_sks_modifier: true, mad_sks_gate_file: 'team-gate.json' };
|
|
@@ -3113,7 +2849,7 @@ async function selftest() {
|
|
|
3113
2849
|
if (!evalReport.candidate.wiki?.valid) throw new Error('selftest failed: wiki coordinate index invalid in eval');
|
|
3114
2850
|
if (evalReport.candidate.wiki?.voxel_schema !== 'sks.wiki-voxel.v1' || evalReport.candidate.wiki?.voxel_rows < 1) throw new Error('selftest failed: eval did not include voxel overlay metrics');
|
|
3115
2851
|
const harnessReport = harnessGrowthReport({});
|
|
3116
|
-
if (!harnessReport.forgetting.fixture.passed || !harnessReport.
|
|
2852
|
+
if (!harnessReport.forgetting.fixture.passed || !harnessReport.tmux.views.includes('Harness Experiments View') || !harnessReport.reliability.tool_error_taxonomy.includes('Unknown')) throw new Error('selftest failed: harness growth fixture incomplete');
|
|
3117
2853
|
const proofField = await proofFieldFixture();
|
|
3118
2854
|
if (!proofField.validation.ok || !validateProofFieldReport(proofField.report).ok) throw new Error('selftest failed: proof field report invalid');
|
|
3119
2855
|
if (!proofField.checks.route_cone_selected || !proofField.checks.cli_cone_selected || !proofField.checks.catastrophic_guard_present || !proofField.checks.negative_release_work_recorded || !proofField.checks.outcome_rubric_present || !proofField.checks.adversarial_lenses_present || !proofField.checks.simplicity_score_usable || !proofField.checks.execution_fast_lane_selected) throw new Error('selftest failed: proof field fixture checks incomplete');
|