sneakoscope 0.7.50 → 0.7.52

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  ![](https://github.com/mandarange/Sneakoscope-Codex/raw/dev/docs/assets/sneakoscope-codex-logo.png)
4
4
 
5
- Sneakoscope Codex (`sks`, displayed as `ㅅㅋㅅ`) is a Codex CLI/App harness for repeatable agent workflows. It adds terminal commands, Codex App `$` prompt commands, tmux-native CLI workspaces, Team/QA/Research routes, inspectable pipeline plans, a maximum-speed Computer Use lane, a fast Goal bridge for native `/goal` persistence, Context7 evidence checks, DB safety, TriWiki context tracking, design-system SSOT routing, lightweight skill dreaming, Honest Mode, and release-readiness gates.
5
+ Sneakoscope Codex (`sks`, displayed as `ㅅㅋㅅ`) is a Codex CLI/App harness for repeatable agent workflows. It adds terminal commands, Codex App `$` prompt commands, tmux-native CLI workspaces, Team/QA/Research routes, inspectable pipeline plans, a maximum-speed Computer Use lane, an imagegen/gpt-image-2 UI/UX review route, a fast Goal bridge for native `/goal` persistence, Context7 evidence checks, DB safety, TriWiki context tracking, design-system SSOT routing, lightweight skill dreaming, Honest Mode, and release-readiness gates.
6
6
 
7
7
  ## Quick Start
8
8
 
@@ -43,14 +43,15 @@ sks selftest --mock
43
43
  | Area | What it does |
44
44
  | --- | --- |
45
45
  | CLI runtime | Bare `sks` opens or reuses the default tmux Codex CLI workspace. `sks tmux open` remains the explicit form for session/workspace flags, and `sks --mad` launches the explicit full-access high-reasoning profile. |
46
- | Codex App commands | Installs generated skills so `$Team`, `$From-Chat-IMG`, `$DFix`, `$QA-LOOP`, `$PPT`, `$Goal`, `$DB`, `$Wiki`, `$Help`, and related routes are visible in prompt workflows. `sks codex-app remote-control` wraps Codex CLI 0.130.0+ headless remote control without falling back to older app-server internals. |
46
+ | Codex App commands | Installs generated skills so `$Team`, `$From-Chat-IMG`, `$DFix`, `$QA-LOOP`, `$PPT`, `$Image-UX-Review`, `$UX-Review`, `$Goal`, `$DB`, `$Wiki`, `$Help`, and related routes are visible in prompt workflows. `sks codex-app remote-control` wraps Codex CLI 0.130.0+ headless remote control without falling back to older app-server internals. |
47
47
  | OpenClaw agents | Generates an OpenClaw skill package so OpenClaw agents can attach `sneakoscope-codex`, enable the `shell` tool, and discover/use SKS commands from the target repo root. |
48
48
  | Pipeline plans | Writes `pipeline-plan.json` for stateful routes so the runtime lane, kept stages, skipped stages, verification commands, and no-unrequested-fallback invariant are visible with `sks pipeline plan`. |
49
49
  | Team orchestration | Runs substantial work through score-based ambiguity handling, scouts, TriWiki refresh, debate, runtime task graphs, worker inboxes, implementation, review, cleanup, reflection, and Honest Mode; narrow work should use Proof Field evidence to skip unrelated pipeline work instead of expanding Team. |
50
50
  | Skill dreaming | Records cheap generated-skill usage counters in JSON and only periodically scans `.agents/skills` for keep, merge, prune, and improvement candidates. Reports are recommendation-only and never delete skills automatically. |
51
51
  | From-Chat-IMG | Turns chat screenshots plus original attachments into source-bound work orders, then requires scoped QA evidence before completion. |
52
52
  | QA loop | Dogfoods UI/API behavior with safety gates, Codex Computer Use-only UI evidence, safe fixes, and rechecks. |
53
- | PPT pipeline | Uses `$PPT` for simple, restrained, information-first HTML/PDF presentation artifacts, first asking delivery context, audience profile, STP strategy, decision context, and 3+ pain-point to solution/aha mappings before source research, design-system work, HTML/PDF export, and render QA. Independent strategy/render/file-write phases run in parallel where inputs allow and are recorded in `ppt-parallel-report.json`; editable source HTML is preserved under `source-html/`, PPT-only temporary build files are cleaned after completion, installed skills/MCPs outside the `$PPT` allowlist are ignored, generated image assets may use `$imagegen` only when sealed in the contract, and `ppt-style-tokens.json` records the design SSOT plus fused source inputs. |
53
+ | PPT pipeline | Uses `$PPT` for simple, restrained, information-first HTML/PDF presentation artifacts, first asking delivery context, audience profile, STP strategy, decision context, and 3+ pain-point to solution/aha mappings before source research, design-system work, HTML/PDF export, and render QA. Independent strategy/render/file-write phases run in parallel where inputs allow and are recorded in `ppt-parallel-report.json`; editable source HTML is preserved under `source-html/`, PPT-only temporary build files are cleaned after completion, installed skills/MCPs outside the `$PPT` allowlist are ignored, generated image assets must use real `$imagegen`/`gpt-image-2` output when sealed in the contract, and `ppt-style-tokens.json` records the design SSOT plus fused source inputs. |
54
+ | Image UX Review | Uses `$Image-UX-Review` / `$UX-Review` for UI/UX audits where source screenshots are first turned into generated annotated review images through Codex App `$imagegen`/`gpt-image-2`; those generated images are then read back into `image-ux-issue-ledger.json`, optional requested fixes are rechecked, and missing generated review images or text-only screenshot critique cannot pass `image-ux-review-gate.json`. |
54
55
  | Computer Use fast lane | Uses `$Computer-Use` / `$CU` for UI/browser/visual work that needs maximum speed: skip Team debate and upfront TriWiki loops, use Codex Computer Use directly, then refresh/validate TriWiki and run Honest Mode at final closeout. |
55
56
  | Goal | Provides a fast SKS bridge overlay for Codex native persisted `/goal` create, pause, resume, and clear controls; implementation continues through the selected SKS execution route. |
56
57
  | TriWiki voxels | Maintains `.sneakoscope/wiki/context-pack.json` as the context SSOT with coordinate anchors, voxel metadata, `attention.use_first`, `attention.hydrate_first`, and prompt-bound mistake recall ledgers. |
@@ -64,7 +65,7 @@ sks selftest --mock
64
65
  - Node.js `>=20.11`
65
66
  - npm
66
67
  - Codex CLI for terminal workflows
67
- - Codex App for app-facing workflows, with Codex Computer Use required for UI/browser evidence
68
+ - Codex App for app-facing workflows, with Codex Computer Use required for UI/browser evidence and `$imagegen`/`gpt-image-2` required for generated raster assets or generated image-review evidence
68
69
  - tmux for the CLI-first runtime
69
70
  - Context7 MCP for current-docs-gated routes
70
71
 
@@ -550,7 +551,7 @@ sks codex-app check
550
551
  codex mcp list
551
552
  ```
552
553
 
553
- Codex App workflows need the app installed. QA and visual-evidence workflows require first-party Codex Computer Use; Browser Use may support non-UI browser context, but it is not valid UI/browser verification evidence.
554
+ Codex App workflows need the app installed. QA and UI/browser visual-evidence workflows require first-party Codex Computer Use; Browser Use may support non-UI browser context, but it is not valid UI/browser verification evidence. Generated raster assets and image-review evidence require real Codex App `$imagegen`/`gpt-image-2` output, or the route must stay blocked/unverified.
554
555
 
555
556
  ### Setup is blocked by another harness
556
557
 
@@ -585,7 +586,7 @@ npm run sizecheck
585
586
  npm run release:check
586
587
  ```
587
588
 
588
- Package pipeline UI/browser verification and visual inspection evidence must come from Codex Computer Use only. Do not use Playwright, Chrome MCP, Browser Use, Selenium, Puppeteer, or other browser automation as substitutes for that evidence.
589
+ Package pipeline UI/browser verification and visual inspection evidence must come from Codex Computer Use only. Do not use Playwright, Chrome MCP, Browser Use, Selenium, Puppeteer, or other browser automation as substitutes for that evidence. Package image-generation evidence must come from real `$imagegen`/`gpt-image-2` output when generated raster assets or generated image-review evidence are required.
589
590
 
590
591
  Dry-run publish:
591
592
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "sneakoscope",
3
3
  "displayName": "ㅅㅋㅅ",
4
- "version": "0.7.50",
4
+ "version": "0.7.52",
5
5
  "description": "Sneakoscope Codex: database-safe Codex CLI/App harness with Team, Goal, AutoResearch, TriWiki, and Honest Mode.",
6
6
  "type": "module",
7
7
  "homepage": "https://github.com/mandarange/Sneakoscope-Codex#readme",
package/src/cli/main.mjs CHANGED
@@ -41,9 +41,18 @@ import {
41
41
  writePptBuildArtifacts,
42
42
  writePptRouteArtifacts
43
43
  } from '../core/ppt.mjs';
44
+ import {
45
+ IMAGE_UX_REVIEW_GATE_ARTIFACT,
46
+ IMAGE_UX_REVIEW_GENERATED_REVIEW_LEDGER_ARTIFACT,
47
+ IMAGE_UX_REVIEW_ISSUE_LEDGER_ARTIFACT,
48
+ IMAGE_UX_REVIEW_ITERATION_REPORT_ARTIFACT,
49
+ IMAGE_UX_REVIEW_POLICY_ARTIFACT,
50
+ IMAGE_UX_REVIEW_SCREEN_INVENTORY_ARTIFACT,
51
+ writeImageUxReviewRouteArtifacts
52
+ } from '../core/image-ux-review.mjs';
44
53
  import { contextCapsule } from '../core/triwiki-attention.mjs';
45
54
  import { rgbaKey, rgbaToWikiCoord, validateWikiCoordinateIndex } from '../core/wiki-coordinate.mjs';
46
- import { ALLOWED_REASONING_EFFORTS, AWESOME_DESIGN_MD_REFERENCE, CODEX_APP_IMAGE_GENERATION_DOC_URL, CODEX_COMPUTER_USE_EVIDENCE_SOURCE, CODEX_COMPUTER_USE_ONLY_POLICY, COMMAND_CATALOG, DESIGN_SYSTEM_SSOT, 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, PPT_PIPELINE_SKILL_ALLOWLIST, RECOMMENDED_SKILLS, ROUTES, USAGE_TOPICS, context7ConfigToml, hasContext7ConfigText, hasFromChatImgSignal, looksLikeAnswerOnlyRequest, noUnrequestedFallbackCodePolicyText, reflectionRequiredForRoute, reasoningInstruction, routePrompt, routeReasoning, routeRequiresSubagents, speedLanePolicyText, stackCurrentDocsPolicy, triwikiContextTracking } from '../core/routes.mjs';
55
+ import { ALLOWED_REASONING_EFFORTS, AWESOME_DESIGN_MD_REFERENCE, CODEX_APP_IMAGE_GENERATION_DOC_URL, CODEX_COMPUTER_USE_EVIDENCE_SOURCE, CODEX_COMPUTER_USE_ONLY_POLICY, CODEX_IMAGEGEN_REQUIRED_POLICY, COMMAND_CATALOG, DESIGN_SYSTEM_SSOT, 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, PPT_PIPELINE_SKILL_ALLOWLIST, RECOMMENDED_SKILLS, ROUTES, USAGE_TOPICS, context7ConfigToml, hasContext7ConfigText, hasFromChatImgSignal, looksLikeAnswerOnlyRequest, noUnrequestedFallbackCodePolicyText, reflectionRequiredForRoute, reasoningInstruction, routePrompt, routeReasoning, routeRequiresSubagents, speedLanePolicyText, stackCurrentDocsPolicy, triwikiContextTracking } from '../core/routes.mjs';
47
56
  import { PIPELINE_PLAN_ARTIFACT, buildPipelinePlan, context7Evidence, evaluateStop, projectGateStatus, recordContext7Evidence, recordSubagentEvidence, validatePipelinePlan, writePipelinePlan } from '../core/pipeline.mjs';
48
57
  import { TEAM_DECOMPOSITION_ARTIFACT, TEAM_GRAPH_ARTIFACT, TEAM_INBOX_DIR, TEAM_RUNTIME_TASKS_ARTIFACT, validateTeamRuntimeArtifacts, writeTeamRuntimeArtifacts } from '../core/team-dag.mjs';
49
58
  import { appendTeamEvent, initTeamLive, parseTeamSpecText, readTeamDashboard, readTeamLive, readTeamTranscriptTail, renderTeamAgentLane } from '../core/team-live.mjs';
@@ -100,7 +109,7 @@ export async function main(args) {
100
109
  if (String(cmd).toLowerCase() === 'dfix') return dfixHelp();
101
110
  const handlers = {
102
111
  postinstall: () => postinstall({ bootstrap }), wizard: () => wizard(tail), ui: () => wizard(tail), 'update-check': () => updateCheck(tail), help: () => help(tail), commands: () => commands(tail), usage: () => usage(tail), root: () => rootCommand(tail), quickstart: () => quickstartCommand(), 'codex-app': () => codexAppHelp(tail), 'codex-lb': () => codexLbCommand(sub, rest), auth: () => codexLbCommand(sub, rest), openclaw: () => openClawCommand(tail), bootstrap: () => bootstrap(tail), deps: () => deps(sub, rest),
103
- 'qa-loop': () => qaLoopCommand(sub, rest), ppt: () => pptCommand(sub, rest), context7: () => context7Command(sub, rest), pipeline: () => pipeline(sub, rest), guard: () => guard(sub, rest), conflicts: () => conflicts(sub, rest), versioning: () => versioning(sub, rest), reasoning: () => reasoningCommand(tail), aliases: () => aliases(), setup: () => setup(tail), 'fix-path': () => fixPath(tail), doctor: () => doctor(tail), init: () => init(tail), selftest: () => selftest(tail),
112
+ 'qa-loop': () => qaLoopCommand(sub, rest), ppt: () => pptCommand(sub, rest), 'image-ux-review': () => imageUxReviewCommand(sub, rest), 'ux-review': () => imageUxReviewCommand(sub, rest), 'visual-review': () => imageUxReviewCommand(sub, rest), 'ui-ux-review': () => imageUxReviewCommand(sub, rest), context7: () => context7Command(sub, rest), pipeline: () => pipeline(sub, rest), guard: () => guard(sub, rest), conflicts: () => conflicts(sub, rest), versioning: () => versioning(sub, rest), reasoning: () => reasoningCommand(tail), aliases: () => aliases(), setup: () => setup(tail), 'fix-path': () => fixPath(tail), doctor: () => doctor(tail), init: () => init(tail), selftest: () => selftest(tail),
104
113
  goal: () => goalCommand(sub, rest), research: () => researchCommand(sub, rest), hook: () => emitHook(sub), profile: () => profileCommand(sub, rest), hproof: () => hproofCommand(sub, rest), 'validate-artifacts': () => validateArtifactsCommand(tail), perf: () => perfCommand(sub, rest), 'proof-field': () => proofFieldCommand(sub, rest), 'skill-dream': () => skillDreamCommand(sub, rest), 'code-structure': () => codeStructureCommand(sub, rest), memory: () => memoryCommand(sub, rest), gx: () => gxCommand(sub, rest),
105
114
  team: () => team(tail), db: () => dbCommand(sub, rest), eval: () => evalCommand(sub, rest), harness: () => harnessCommand(sub, rest), wiki: () => wikiCommand(sub, rest), gc: () => gcCommand(tail), stats: () => statsCommand(tail)
106
115
  };
@@ -329,7 +338,7 @@ async function updateCheck(args = []) {
329
338
  if (result.update_available) console.log('Run: npm i -g sneakoscope');
330
339
  }
331
340
 
332
- 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.';
341
+ const DOLLAR_DEFAULT_PIPELINE_TEXT = 'Default pipeline: questions -> $Answer, small design/content -> $DFix, presentation/PDF artifacts -> $PPT, image-generation UI/UX reviews -> $Image-UX-Review/$UX-Review, Computer Use UI/browser speed work -> $Computer-Use, code -> $Team. Use $From-Chat-IMG only for chat screenshot plus original attachments. Use $MAD-SKS only as an explicit scoped DB authorization modifier that can be combined with another $ route. No route may invent unrequested fallback implementation code.';
333
342
 
334
343
  function commands(args = []) {
335
344
  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));
@@ -477,6 +486,66 @@ async function pptCommand(sub = 'status', args = []) {
477
486
  throw new Error(`Unknown ppt command: ${action}`);
478
487
  }
479
488
 
489
+ async function imageUxReviewCommand(sub = 'status', args = []) {
490
+ const root = await sksRoot();
491
+ const action = sub || 'status';
492
+ if (action === 'help' || action === '--help' || action === '-h') {
493
+ console.log(`SKS Image UX Review
494
+
495
+ Prompt commands:
496
+ $Image-UX-Review <target>
497
+ $UX-Review <target>
498
+
499
+ Inspect artifacts:
500
+ sks image-ux-review status latest --json
501
+
502
+ Core loop:
503
+ source UI screenshot -> $imagegen/gpt-image-2 generated annotated review image -> image-ux-issue-ledger.json -> optional requested fixes -> changed-screen recheck
504
+ `);
505
+ return;
506
+ }
507
+ if (action !== 'status') throw new Error(`Unknown image-ux-review command: ${action}`);
508
+ const missionArg = args.find((arg) => !String(arg).startsWith('--')) || 'latest';
509
+ const id = await resolveMissionId(root, missionArg);
510
+ if (!id) throw new Error('Usage: sks image-ux-review status <mission-id|latest> [--json]');
511
+ const { dir } = await loadMission(root, id);
512
+ const gate = await readJson(path.join(dir, IMAGE_UX_REVIEW_GATE_ARTIFACT), null);
513
+ const policy = await readJson(path.join(dir, IMAGE_UX_REVIEW_POLICY_ARTIFACT), null);
514
+ const inventory = await readJson(path.join(dir, IMAGE_UX_REVIEW_SCREEN_INVENTORY_ARTIFACT), null);
515
+ const generatedReviewLedger = await readJson(path.join(dir, IMAGE_UX_REVIEW_GENERATED_REVIEW_LEDGER_ARTIFACT), null);
516
+ const issueLedger = await readJson(path.join(dir, IMAGE_UX_REVIEW_ISSUE_LEDGER_ARTIFACT), null);
517
+ const iterationReport = await readJson(path.join(dir, IMAGE_UX_REVIEW_ITERATION_REPORT_ARTIFACT), null);
518
+ const status = {
519
+ ok: Boolean(gate?.passed),
520
+ mission_id: id,
521
+ gate,
522
+ policy,
523
+ inventory,
524
+ generated_review_ledger: generatedReviewLedger,
525
+ issue_ledger: issueLedger,
526
+ iteration_report: iterationReport,
527
+ files: {
528
+ policy: path.join(dir, IMAGE_UX_REVIEW_POLICY_ARTIFACT),
529
+ inventory: path.join(dir, IMAGE_UX_REVIEW_SCREEN_INVENTORY_ARTIFACT),
530
+ generated_review_ledger: path.join(dir, IMAGE_UX_REVIEW_GENERATED_REVIEW_LEDGER_ARTIFACT),
531
+ issue_ledger: path.join(dir, IMAGE_UX_REVIEW_ISSUE_LEDGER_ARTIFACT),
532
+ iteration_report: path.join(dir, IMAGE_UX_REVIEW_ITERATION_REPORT_ARTIFACT),
533
+ gate: path.join(dir, IMAGE_UX_REVIEW_GATE_ARTIFACT)
534
+ }
535
+ };
536
+ if (flag(args, '--json')) return console.log(JSON.stringify(status, null, 2));
537
+ console.log('SKS Image UX Review status\n');
538
+ console.log(`Mission: ${id}`);
539
+ console.log(`Gate: ${status.ok ? 'passed' : 'not passed'}`);
540
+ console.log(`Policy: ${path.relative(root, status.files.policy)}`);
541
+ console.log(`Screens: ${path.relative(root, status.files.inventory)}`);
542
+ console.log(`Images: ${path.relative(root, status.files.generated_review_ledger)}`);
543
+ console.log(`Issues: ${path.relative(root, status.files.issue_ledger)}`);
544
+ console.log(`Loop: ${path.relative(root, status.files.iteration_report)}`);
545
+ console.log(`Gate: ${path.relative(root, status.files.gate)}`);
546
+ if (gate?.blockers?.length) console.log(`Blockers:${' '.repeat(1)}${gate.blockers.join(', ')}`);
547
+ }
548
+
480
549
  async function pipeline(sub = 'status', args = []) {
481
550
  const root = await sksRoot();
482
551
  const action = sub || 'status';
@@ -754,6 +823,25 @@ async function materializeAfterPipelineAnswer(root, id, dir, mission, route, rou
754
823
  }
755
824
  };
756
825
  }
826
+ if (route?.id === 'ImageUXReview') {
827
+ await writeImageUxReviewRouteArtifacts(dir, contract);
828
+ await appendJsonlBounded(path.join(dir, 'events.jsonl'), {
829
+ ts: nowIso(),
830
+ type: 'image_ux_review.materialized',
831
+ route: route.id,
832
+ gate: IMAGE_UX_REVIEW_GATE_ARTIFACT,
833
+ generated_review_ledger: IMAGE_UX_REVIEW_GENERATED_REVIEW_LEDGER_ARTIFACT
834
+ });
835
+ return {
836
+ phase: 'IMAGE_UX_REVIEW_READY',
837
+ prompt: routeContext.task || mission.prompt || '',
838
+ state: {
839
+ image_ux_review_gate_ready: true,
840
+ image_ux_review_policy_ready: true,
841
+ ...madSksState
842
+ }
843
+ };
844
+ }
757
845
  if (route?.id !== 'Team') return Object.keys(madSksState).length ? { state: madSksState } : {};
758
846
  const spec = parseTeamSpecText(routeContext.task || mission.prompt || '');
759
847
  const prompt = spec.prompt || routeContext.task || mission.prompt || '';
@@ -1294,6 +1382,7 @@ function printDepsStatus(status) {
1294
1382
  console.log(`SKS bin: ${status.sneakoscope.ok ? 'ok' : 'missing'} ${status.sneakoscope.bin || ''}`.trimEnd());
1295
1383
  console.log(`Codex CLI: ${status.codex_cli.ok ? 'ok' : 'missing'} ${status.codex_cli.version || status.codex_cli.bin || ''}`.trimEnd());
1296
1384
  console.log(`Codex App: ${status.codex_app.app.installed ? 'ok' : 'missing'}`);
1385
+ console.log(`Image Gen: ${status.codex_app.features?.image_generation ? 'ok' : 'missing'}`);
1297
1386
  console.log(`Context7: ${status.context7.ok ? 'ok' : 'missing'}`);
1298
1387
  console.log(`Browser Use: ${status.browser_use.ok ? 'ok' : 'missing'}`);
1299
1388
  console.log(`Computer Use:${status.computer_use.ok ? ' ok' : ' missing'}`);
@@ -1475,7 +1564,8 @@ function usage(args = []) {
1475
1564
  openclaw: ['OpenClaw', '', ' sks openclaw install', ' sks openclaw path', ' sks openclaw print SKILL.md', '', 'Installs an OpenClaw skill package under ~/.openclaw/skills/sneakoscope-codex so OpenClaw agents can attach skills: [sneakoscope-codex] with the shell tool and call local SKS commands from a project root.'],
1476
1565
  team: ['Team', '', ' sks team "task" executor:5 reviewer:6 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.'],
1477
1566
  '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'],
1478
- 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, render QA, fact-ledger validation, and bounded review-loop validation. 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 installed skills/MCPs outside the $PPT allowlist are ignored. Design uses getdesign-reference plus the built-in PPT design pipeline; imagegen/gpt-image-2 and Context7 are conditional only when the sealed PPT contract needs raster assets, slide visual critique, or current external docs. Missing required image-review evidence blocks instead of being simulated.'],
1567
+ 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, render QA, fact-ledger validation, and bounded review-loop validation. 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 installed skills/MCPs outside the $PPT allowlist are ignored. Design uses getdesign-reference plus the built-in PPT design pipeline; imagegen/gpt-image-2 and Context7 are conditional only when the sealed PPT contract needs raster assets, slide visual critique, or current external docs. Missing required $imagegen/gpt-image-2 output blocks instead of being simulated.'],
1568
+ 'image-ux-review': ['Image UX Review', '', ' $Image-UX-Review localhost 화면을 이미지 생성 리뷰 루프로 검수해줘', ' $UX-Review 이 스크린샷을 gpt-image-2 콜아웃 리뷰로 분석하고 고쳐줘', ' sks image-ux-review status latest --json', '', '$Image-UX-Review captures or receives source UI screenshots, runs Codex App $imagegen/gpt-image-2 to create generated annotated review images with numbered callouts, then extracts those generated images into image-ux-issue-ledger.json. Text-only screenshot critique cannot pass image-ux-review-gate.json; missing generated review images remain an explicit blocker.'],
1479
1569
  goal: ['Goal', '', ' sks goal create "task"', ' sks goal status latest', ' sks goal pause latest', ' sks goal resume latest', ' sks goal clear latest'],
1480
1570
  'codex-app': ['Codex App', '', ' sks bootstrap', ' sks codex-app check', ' sks codex-app remote-control --status', ' sks dollar-commands', ' cat .codex/SNEAKOSCOPE.md'],
1481
1571
  dollar: ['Dollar Commands', '', formatDollarCommandsCompact(' '), '', 'Terminal: sks dollar-commands [--json]'],
@@ -1621,7 +1711,7 @@ async function setup(args) {
1621
1711
  else console.log('Git: .gitignore ignores SKS generated files');
1622
1712
  console.log(`Codex App: .codex/config.toml, .codex/hooks.json, .agents/skills, .codex/agents, .codex/SNEAKOSCOPE.md`);
1623
1713
  console.log(`Global $: ${globalSkills.status === 'installed' ? 'ok' : globalSkills.status} ${globalSkills.root || ''}`.trimEnd());
1624
- 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'}`);
1714
+ console.log(`App tools: ${appRuntime.ok ? 'ok' : 'needs setup'} Codex App=${appRuntime.app.installed ? 'ok' : 'missing'} Browser Use=${appRuntime.mcp.has_browser_use ? 'ok' : 'missing'} Computer Use=${appRuntime.mcp.has_computer_use ? 'ok' : 'missing'} Image Gen=${appRuntime.features?.image_generation ? 'ok' : 'missing'}`);
1625
1715
  console.log(`Prompt: intent-first routing, $Answer fact-check route, $DFix ultralight design/content route, $PPT HTML/PDF presentation route, Context7 gate`);
1626
1716
  console.log(`Skills: .agents/skills`);
1627
1717
  console.log(`Next: sks context7 check; sks selftest --mock; sks commands; sks dollar-commands`);
@@ -1630,7 +1720,7 @@ async function setup(args) {
1630
1720
  if (!cliTools.tmux.ok) console.log(`\ntmux ${tmuxStatusKind(cliTools.tmux)}. Install: ${cliTools.tmux.install_hint}`);
1631
1721
  if (!install.ok && install.scope === 'global') console.log('\nGlobal command missing. Run: npm i -g sneakoscope');
1632
1722
  if (!install.ok && install.scope === 'project') console.log('\nProject package missing. Run: npm i -D sneakoscope');
1633
- 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');
1723
+ if (!appRuntime.ok) console.log('\nCodex App, first-party Codex Computer Use, and $imagegen/gpt-image-2 are required for SKS visual evidence; Browser Use is not a UI verification substitute. Run: sks codex-app check');
1634
1724
  }
1635
1725
 
1636
1726
  function formatCodexCliToolStatus(status = {}) {
@@ -1747,7 +1837,7 @@ async function doctor(args) {
1747
1837
  console.log(`Rust acc.: ${rust.available ? rust.version : 'optional-missing'}`);
1748
1838
  console.log(`State: ${result.sneakoscope.ok ? 'ok' : 'missing .sneakoscope'}`);
1749
1839
  console.log(`Context7: ${result.context7.ok ? 'ok' : 'missing MCP config'} project=${result.context7.project.ok ? 'ok' : 'missing'} global=${result.context7.global.ok ? 'ok' : 'missing'}`);
1750
- 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'}`);
1840
+ console.log(`App tools: ${appRuntime.ok ? 'ok' : 'needs setup'} Codex App=${appRuntime.app.installed ? 'ok' : 'missing'} Browser Use=${appRuntime.mcp.has_browser_use ? 'ok' : 'missing'} Computer Use=${appRuntime.mcp.has_computer_use ? 'ok' : 'missing'} Image Gen=${appRuntime.features?.image_generation ? 'ok' : 'missing'}`);
1751
1841
  console.log(`tmux: ${tmuxStatusKind(result.runtime.tmux)} ${result.runtime.tmux.version || result.runtime.tmux.error || ''}`.trimEnd());
1752
1842
  console.log(`Guard: ${result.harness_guard.ok ? 'ok' : 'blocked'}${result.harness_guard.source_exception ? ' source-exception' : ''}`);
1753
1843
  console.log(`Version: ${result.versioning.ok ? 'ok' : 'missing'}${result.versioning.enabled ? ` ${result.versioning.package_version || ''}` : ` ${result.versioning.reason || 'disabled'}`}`);
@@ -2435,18 +2525,20 @@ async function selftest() {
2435
2525
  if (!teamSkillText.includes('Codex App pipeline activation:') || !teamSkillText.includes('sks pipeline status') || !teamSkillText.includes('mission/pipeline artifacts')) throw new Error('selftest failed: Team skill missing pipeline activation fallback');
2436
2526
  if (!promptPipelineText.includes('before every route stage') || !promptPipelineText.includes('sks wiki refresh')) throw new Error('selftest failed: prompt pipeline missing per-stage TriWiki policy');
2437
2527
  if (!promptPipelineText.includes('single design decision authority') || !promptPipelineText.includes('imagegen') || !promptPipelineText.includes('getdesign-reference') || !promptPipelineText.includes(AWESOME_DESIGN_MD_REFERENCE.url) || !promptPipelineText.includes('not parallel authorities')) throw new Error('selftest failed: prompt pipeline missing design SSOT/source-input routing');
2438
- if (!promptPipelineText.includes(CODEX_APP_IMAGE_GENERATION_DOC_URL)) throw new Error('selftest failed: prompt pipeline missing Codex App image generation policy');
2528
+ if (!promptPipelineText.includes(CODEX_APP_IMAGE_GENERATION_DOC_URL) || !promptPipelineText.includes(CODEX_IMAGEGEN_REQUIRED_POLICY)) throw new Error('selftest failed: prompt pipeline missing Codex App image generation policy');
2439
2529
  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');
2440
2530
  const fromChatImgSkillText = await safeReadText(path.join(tmp, '.agents', 'skills', 'from-chat-img', 'SKILL.md'));
2441
2531
  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');
2442
2532
  if (fromChatImgSkillText.includes('Computer Use/browser visual inspection')) throw new Error('selftest failed: from-chat-img skill still allows browser visual inspection wording');
2443
2533
  const fromChatImgSkillMeta = await safeReadText(path.join(tmp, '.agents', 'skills', 'from-chat-img', 'agents', 'openai.yaml'));
2444
2534
  if (!fromChatImgSkillMeta.includes('model_reasoning_effort: xhigh')) throw new Error('selftest failed: from-chat-img skill metadata is not xhigh');
2445
- for (const supportSkill of ['reasoning-router', 'pipeline-runner', 'context7-docs', 'seo-geo-optimizer', 'reflection', 'design-system-builder', 'design-ui-editor', 'getdesign-reference', 'imagegen']) {
2535
+ for (const supportSkill of ['reasoning-router', 'pipeline-runner', 'context7-docs', 'seo-geo-optimizer', 'reflection', 'design-system-builder', 'design-ui-editor', 'getdesign-reference', 'imagegen', 'image-ux-review', 'visual-review', 'ui-ux-review']) {
2446
2536
  if (!(await exists(path.join(tmp, '.agents', 'skills', supportSkill, 'SKILL.md')))) throw new Error(`selftest failed: ${supportSkill} skill not installed`);
2447
2537
  }
2448
2538
  const imagegenSkillText = await safeReadText(path.join(tmp, '.agents', 'skills', 'imagegen', 'SKILL.md'));
2449
- 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');
2539
+ if (!imagegenSkillText.includes(CODEX_APP_IMAGE_GENERATION_DOC_URL) || !imagegenSkillText.includes('$imagegen') || !imagegenSkillText.includes('gpt-image-2') || !imagegenSkillText.includes('OPENAI_API_KEY') || !imagegenSkillText.includes(CODEX_IMAGEGEN_REQUIRED_POLICY)) throw new Error('selftest failed: imagegen skill missing official Codex App image generation priority');
2540
+ const imageUxReviewSkillText = await safeReadText(path.join(tmp, '.agents', 'skills', 'image-ux-review', 'SKILL.md'));
2541
+ if (!imageUxReviewSkillText.includes('gpt-image-2') || !imageUxReviewSkillText.includes('$imagegen') || !imageUxReviewSkillText.includes('generated annotated review image') || !imageUxReviewSkillText.includes('Text-only screenshot critique cannot satisfy this route') || !imageUxReviewSkillText.includes(IMAGE_UX_REVIEW_GATE_ARTIFACT) || !imageUxReviewSkillText.includes(IMAGE_UX_REVIEW_ISSUE_LEDGER_ARTIFACT) || !imageUxReviewSkillText.includes(CODEX_IMAGEGEN_REQUIRED_POLICY)) throw new Error('selftest failed: image-ux-review skill missing gpt-image-2 generated-image review gate guidance');
2450
2542
  const getdesignSkillText = await safeReadText(path.join(tmp, '.agents', 'skills', 'getdesign-reference', 'SKILL.md')); if (!getdesignSkillText.includes(AWESOME_DESIGN_MD_REFERENCE.url) || !getdesignSkillText.includes('only design decision SSOT') || !getdesignSkillText.includes('source inputs')) throw new Error('selftest failed: getdesign-reference skill missing design SSOT source-input guidance');
2451
2543
  const designSystemBuilderSkillText = await safeReadText(path.join(tmp, '.agents', 'skills', 'design-system-builder', 'SKILL.md')); if (!designSystemBuilderSkillText.includes(AWESOME_DESIGN_MD_REFERENCE.url) || !designSystemBuilderSkillText.includes('Fuse those inputs into one design.md SSOT') || !designSystemBuilderSkillText.includes('competing authorities')) throw new Error('selftest failed: design-system-builder skill missing fused design SSOT guidance');
2452
2544
  const designSysPromptText = await safeReadText(path.join(packageRoot(), 'docs', 'Design-Sys-Prompt.md')); if (!designSysPromptText.includes('Design SSOT contract') || !designSysPromptText.includes('builder prompt') || !designSysPromptText.includes('not a competing design authority')) throw new Error('selftest failed: Design-Sys-Prompt missing design SSOT contract');
@@ -2462,14 +2554,24 @@ async function selftest() {
2462
2554
  if (new Set(DOLLAR_COMMANDS.map((c) => c.command)).size !== DOLLAR_COMMANDS.length) throw new Error('selftest failed: duplicate dollar commands');
2463
2555
  if (!DOLLAR_COMMAND_ALIASES.some((alias) => alias.canonical === '$QA-LOOP' && alias.app_skill === '$qa-loop')) throw new Error('selftest failed: $QA-LOOP picker skill missing');
2464
2556
  if (!DOLLAR_COMMAND_ALIASES.some((alias) => alias.canonical === '$Team' && alias.app_skill === '$from-chat-img')) throw new Error('selftest failed: $From-Chat-IMG picker skill missing');
2557
+ if (!DOLLAR_COMMAND_ALIASES.some((alias) => alias.canonical === '$Image-UX-Review' && alias.app_skill === '$visual-review')) throw new Error('selftest failed: $Image-UX-Review picker alias missing');
2465
2558
  if (!DOLLAR_COMMANDS.some((entry) => entry.command === '$From-Chat-IMG')) throw new Error('selftest failed: $From-Chat-IMG missing from dollar command list');
2559
+ if (!DOLLAR_COMMANDS.some((entry) => entry.command === '$Image-UX-Review') || !DOLLAR_COMMANDS.some((entry) => entry.command === '$UX-Review')) throw new Error('selftest failed: Image UX Review missing from dollar command list');
2466
2560
  if (DOLLAR_COMMAND_ALIASES.some((alias) => ['$agent-team', '$qaloop', '$wiki-refresh', '$wikirefresh'].includes(alias.app_skill))) throw new Error('selftest failed: duplicate picker aliases still present');
2467
2561
  if (routePrompt('$agent-team run specialists')) throw new Error('selftest failed: deprecated $agent-team route still resolved');
2468
2562
  if (routePrompt('$QA-LOOP run UI E2E')?.id !== 'QALoop' || routePrompt('$QALoop deployed smoke')) throw new Error('selftest failed: QA-LOOP route is not standardized to $QA-LOOP');
2563
+ if (routePrompt('[$qa-loop](/tmp/qa-loop/SKILL.md) localhost UI 검증, Codex Computer Use만 사용')?.id !== 'QALoop') throw new Error('selftest failed: markdown-linked $QA-LOOP was hijacked by heuristic routing');
2564
+ if (routePrompt('[$research](/tmp/research/SKILL.md) Codex Computer Use 도구 노출 문제를 QA루프 관점에서 연구')?.id !== 'Research') throw new Error('selftest failed: markdown-linked $Research was not treated as explicit route');
2469
2565
  if (routePrompt('$WikiRefresh 갱신')) throw new Error('selftest failed: deprecated $WikiRefresh route still resolved');
2470
2566
  if (routePrompt('$MAD-SKS Supabase MCP main 작업')?.id !== 'MadSKS') throw new Error('selftest failed: $MAD-SKS route did not resolve');
2471
2567
  if (routePrompt('$MAD-SKS $Team Supabase MCP main 작업')?.id !== 'Team') throw new Error('selftest failed: $MAD-SKS did not compose with $Team');
2472
2568
  if (routePrompt('$DB Supabase 점검 $MAD-SKS')?.id !== 'DB') throw new Error('selftest failed: trailing $MAD-SKS changed primary route');
2569
+ const imageUxRoute = routePrompt('$Image-UX-Review localhost 화면 검수');
2570
+ if (imageUxRoute?.id !== 'ImageUXReview') throw new Error('selftest failed: $Image-UX-Review did not route to ImageUXReview');
2571
+ if (routePrompt('$UX-Review 스크린샷 gpt-image-2 콜아웃 리뷰')?.id !== 'ImageUXReview') throw new Error('selftest failed: $UX-Review did not route to ImageUXReview');
2572
+ if (routePrompt('UI UX를 gpt-image-2 이미지 생성 콜아웃으로 리뷰해줘')?.id !== 'ImageUXReview') throw new Error('selftest failed: image-generation UI/UX review prompt did not route to ImageUXReview');
2573
+ if (routeRequiresSubagents(imageUxRoute, '$Image-UX-Review localhost 화면 검수')) throw new Error('selftest failed: ImageUXReview route should not require subagents');
2574
+ if (!reflectionRequiredForRoute(imageUxRoute)) throw new Error('selftest failed: ImageUXReview route should require reflection');
2473
2575
  const madStandaloneTmp = tmpdir();
2474
2576
  await initProject(madStandaloneTmp, {});
2475
2577
  const madStandalonePayload = JSON.stringify({ cwd: madStandaloneTmp, prompt: '$MAD-SKS main 권한 열어줘' });
@@ -2498,7 +2600,8 @@ async function selftest() {
2498
2600
  if (!DOLLAR_DEFAULT_PIPELINE_TEXT.includes('$Team')) throw new Error('selftest failed: dollar-commands missing Team default routing guidance');
2499
2601
  if (!DOLLAR_DEFAULT_PIPELINE_TEXT.includes('$From-Chat-IMG')) throw new Error('selftest failed: dollar-commands missing From-Chat-IMG guidance');
2500
2602
  if (!DOLLAR_DEFAULT_PIPELINE_TEXT.includes('$MAD-SKS')) throw new Error('selftest failed: dollar-commands missing MAD-SKS scoped override guidance');
2501
- if (!COMMAND_CATALOG.some((c) => c.name === 'context7') || !COMMAND_CATALOG.some((c) => c.name === 'pipeline') || !COMMAND_CATALOG.some((c) => c.name === 'qa-loop') || !COMMAND_CATALOG.some((c) => c.name === 'root') || !COMMAND_CATALOG.some((c) => c.name === 'openclaw')) throw new Error('selftest failed: context7/pipeline/qa-loop/root/openclaw commands missing from catalog');
2603
+ if (!DOLLAR_DEFAULT_PIPELINE_TEXT.includes('$Image-UX-Review')) throw new Error('selftest failed: dollar-commands missing Image UX Review guidance');
2604
+ if (!COMMAND_CATALOG.some((c) => c.name === 'context7') || !COMMAND_CATALOG.some((c) => c.name === 'pipeline') || !COMMAND_CATALOG.some((c) => c.name === 'qa-loop') || !COMMAND_CATALOG.some((c) => c.name === 'image-ux-review') || !COMMAND_CATALOG.some((c) => c.name === 'root') || !COMMAND_CATALOG.some((c) => c.name === 'openclaw')) throw new Error('selftest failed: context7/pipeline/qa-loop/image-ux-review/root/openclaw commands missing from catalog');
2502
2605
  const openClawTmp = tmpdir();
2503
2606
  const openClawResult = await installOpenClawSkill({ targetDir: path.join(openClawTmp, 'skills', OPENCLAW_SKILL_NAME) });
2504
2607
  if (!openClawResult.ok) throw new Error(`selftest failed: OpenClaw skill install blocked: ${openClawResult.reason}`);
@@ -2529,6 +2632,25 @@ async function selftest() {
2529
2632
  const hookGoalTmp = tmpdir();
2530
2633
  await initProject(hookGoalTmp, {});
2531
2634
  const hookBin = path.join(packageRoot(), 'bin', 'sks.mjs');
2635
+ const hookImageUxTmp = tmpdir();
2636
+ await initProject(hookImageUxTmp, {});
2637
+ const hookImageUxPayload = JSON.stringify({ cwd: hookImageUxTmp, prompt: '$Image-UX-Review localhost 화면을 gpt-image-2 콜아웃 리뷰로 검수해줘' });
2638
+ const hookImageUxResult = await runProcess(process.execPath, [hookBin, 'hook', 'user-prompt-submit'], { cwd: hookImageUxTmp, input: hookImageUxPayload, env: { SKS_DISABLE_UPDATE_CHECK: '1' }, timeoutMs: 15000, maxOutputBytes: 256 * 1024 });
2639
+ if (hookImageUxResult.code !== 0) throw new Error(`selftest failed: $Image-UX-Review hook exited ${hookImageUxResult.code}: ${hookImageUxResult.stderr}`);
2640
+ const hookImageUxJson = JSON.parse(hookImageUxResult.stdout);
2641
+ const imageUxContext = hookImageUxJson.hookSpecificOutput?.additionalContext || '';
2642
+ if (!imageUxContext.includes('$Image-UX-Review route prepared') || !imageUxContext.includes('Codex App $imagegen/gpt-image-2')) throw new Error('selftest failed: $Image-UX-Review hook did not prepare imagegen loop context');
2643
+ const hookImageUxState = await readJson(stateFile(hookImageUxTmp), {});
2644
+ if (hookImageUxState.mode !== 'IMAGE_UX_REVIEW' || hookImageUxState.stop_gate !== IMAGE_UX_REVIEW_GATE_ARTIFACT || hookImageUxState.subagents_required !== false || hookImageUxState.reflection_required !== true) throw new Error('selftest failed: $Image-UX-Review hook did not set direct image UX review state');
2645
+ const imageUxMissionDir = missionDir(hookImageUxTmp, hookImageUxState.mission_id);
2646
+ const imageUxGate = await readJson(path.join(imageUxMissionDir, IMAGE_UX_REVIEW_GATE_ARTIFACT));
2647
+ const imageUxGeneratedLedger = await readJson(path.join(imageUxMissionDir, IMAGE_UX_REVIEW_GENERATED_REVIEW_LEDGER_ARTIFACT));
2648
+ if (imageUxGate.passed || imageUxGate.imagegen_review_images_generated || !imageUxGate.blockers?.includes('source_screenshots_not_captured_yet') || !imageUxGate.blockers?.includes('no_source_screenshots_for_imagegen_review')) throw new Error('selftest failed: Image UX review gate did not block missing source/generated review images');
2649
+ if (imageUxGeneratedLedger.provider?.model !== 'gpt-image-2' || imageUxGeneratedLedger.passed) throw new Error('selftest failed: Image UX generated review ledger did not record required gpt-image-2 blocker state');
2650
+ const imageUxStatusResult = await runProcess(process.execPath, [hookBin, 'image-ux-review', 'status', 'latest', '--json'], { cwd: hookImageUxTmp, env: { SKS_DISABLE_UPDATE_CHECK: '1' }, timeoutMs: 15000, maxOutputBytes: 128 * 1024 });
2651
+ if (imageUxStatusResult.code !== 0) throw new Error(`selftest failed: sks image-ux-review status failed: ${imageUxStatusResult.stderr || imageUxStatusResult.stdout}`);
2652
+ const imageUxStatus = JSON.parse(imageUxStatusResult.stdout);
2653
+ if (imageUxStatus.ok || imageUxStatus.generated_review_ledger?.provider?.model !== 'gpt-image-2' || !imageUxStatus.files?.gate?.endsWith(IMAGE_UX_REVIEW_GATE_ARTIFACT)) throw new Error('selftest failed: sks image-ux-review status did not report gpt-image-2 gate blockers');
2532
2654
  const hookPayload = JSON.stringify({ cwd: hookGoalTmp, prompt: '$Goal 로그인 세션 만료 UX 개선' });
2533
2655
  const hookResult = await runProcess(process.execPath, [hookBin, 'hook', 'user-prompt-submit'], { cwd: hookGoalTmp, input: hookPayload, env: { SKS_DISABLE_UPDATE_CHECK: '1' }, timeoutMs: 15000, maxOutputBytes: 256 * 1024 });
2534
2656
  if (hookResult.code !== 0) throw new Error(`selftest failed: $Goal hook exited ${hookResult.code}: ${hookResult.stderr}`);
@@ -3250,7 +3372,7 @@ async function selftest() {
3250
3372
  }
3251
3373
  const pptSkillText = await safeReadText(path.join(tmp, '.agents', 'skills', 'ppt', 'SKILL.md'));
3252
3374
  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');
3253
- if (!pptSkillText.includes('simple, restrained, and information-first') || !pptSkillText.includes('over-designed decoration') || !pptSkillText.includes(CODEX_APP_IMAGE_GENERATION_DOC_URL) || !pptSkillText.includes(AWESOME_DESIGN_MD_REFERENCE.url) || !pptSkillText.includes('only design decision SSOT') || !pptSkillText.includes('instead of treating references as parallel authorities')) throw new Error('selftest failed: generated PPT skill missing restrained design/imagegen/fused-SSOT guidance');
3375
+ if (!pptSkillText.includes('simple, restrained, and information-first') || !pptSkillText.includes('over-designed decoration') || !pptSkillText.includes(CODEX_APP_IMAGE_GENERATION_DOC_URL) || !pptSkillText.includes(CODEX_IMAGEGEN_REQUIRED_POLICY) || !pptSkillText.includes(AWESOME_DESIGN_MD_REFERENCE.url) || !pptSkillText.includes('only design decision SSOT') || !pptSkillText.includes('instead of treating references as parallel authorities')) throw new Error('selftest failed: generated PPT skill missing restrained design/imagegen/fused-SSOT guidance');
3254
3376
  if (!pptSkillText.includes('PPT pipeline allowlist') || !pptSkillText.includes('ignore installed skills and MCPs') || !pptSkillText.includes('prevent AI-like generic presentation design') || !pptSkillText.includes('Do not use generic design skills such as design-artifact-expert')) throw new Error('selftest failed: generated PPT skill missing pipeline allowlist enforcement');
3255
3377
  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');
3256
3378
  if (!pptSkillText.includes('ppt-fact-ledger.json') || !pptSkillText.includes('ppt-image-asset-ledger.json') || !pptSkillText.includes('OpenAI Image API') || !pptSkillText.includes('ppt-review-ledger.json') || !pptSkillText.includes('ppt-iteration-report.json') || !pptSkillText.includes('never simulate missing gpt-image-2 output')) throw new Error('selftest failed: generated PPT skill missing fact/image/review loop anti-fake guidance');
@@ -78,18 +78,38 @@ export async function codexMcpList(opts = {}) {
78
78
  };
79
79
  }
80
80
 
81
+ export async function codexFeatureList(opts = {}) {
82
+ const codex = opts.codex || await getCodexInfo().catch(() => ({}));
83
+ if (!codex.bin) return { ok: false, checked: false, stdout: '', stderr: 'Codex CLI missing.' };
84
+ const out = await runProcess(codex.bin, ['features', 'list'], {
85
+ timeoutMs: opts.timeoutMs || 10000,
86
+ maxOutputBytes: 64 * 1024
87
+ }).catch((err) => ({ code: 1, stdout: '', stderr: err.message }));
88
+ return {
89
+ ok: out.code === 0,
90
+ checked: true,
91
+ stdout: out.stdout || '',
92
+ stderr: out.stderr || ''
93
+ };
94
+ }
95
+
81
96
  export async function codexAppIntegrationStatus(opts = {}) {
82
97
  const appPath = await findCodexApp(opts);
83
98
  const codex = opts.codex || await getCodexInfo().catch(() => ({}));
84
99
  const mcpList = await codexMcpList({ ...opts, codex });
100
+ const featureList = await codexFeatureList({ ...opts, codex });
85
101
  const remoteControl = codexRemoteControlStatusFromInfo(codex);
86
102
  const mcpText = `${mcpList.stdout}\n${mcpList.stderr}`;
103
+ const featureText = `${featureList.stdout}\n${featureList.stderr}`;
87
104
  const browserUsePath = await findPluginCache('browser-use', opts);
88
105
  const computerUsePath = await findPluginCache('computer-use', opts);
89
- const computerUseReady = /computer[-_ ]?use/i.test(mcpText) || Boolean(computerUsePath);
90
- const browserUseReady = /browser[-_ ]?use/i.test(mcpText) || Boolean(browserUsePath);
106
+ const computerUseMcpListed = /computer[-_ ]?use/i.test(mcpText);
107
+ const browserUseMcpListed = /browser[-_ ]?use/i.test(mcpText);
108
+ const imageGenerationReady = codexFeatureEnabled(featureText, 'image_generation');
109
+ const computerUseReady = computerUseMcpListed || Boolean(computerUsePath);
110
+ const browserUseReady = browserUseMcpListed || Boolean(browserUsePath);
91
111
  const appInstalled = Boolean(appPath);
92
- const ready = appInstalled && Boolean(codex.bin) && mcpList.ok && computerUseReady && browserUseReady;
112
+ const ready = appInstalled && Boolean(codex.bin) && mcpList.ok && featureList.ok && imageGenerationReady && computerUseReady && browserUseReady;
93
113
  return {
94
114
  ok: ready,
95
115
  app: {
@@ -108,14 +128,24 @@ export async function codexAppIntegrationStatus(opts = {}) {
108
128
  ok: mcpList.ok,
109
129
  has_computer_use: computerUseReady,
110
130
  has_browser_use: browserUseReady,
131
+ computer_use_source: computerUseMcpListed ? 'mcp_list' : computerUsePath ? 'plugin_cache' : 'missing',
132
+ browser_use_source: browserUseMcpListed ? 'mcp_list' : browserUsePath ? 'plugin_cache' : 'missing',
111
133
  stdout: mcpList.stdout,
112
134
  stderr: mcpList.stderr
113
135
  },
136
+ features: {
137
+ checked: featureList.checked,
138
+ ok: featureList.ok,
139
+ image_generation: imageGenerationReady,
140
+ image_generation_source: imageGenerationReady ? 'codex_features_list' : 'missing',
141
+ stdout: featureList.stdout,
142
+ stderr: featureList.stderr
143
+ },
114
144
  plugins: {
115
145
  computer_use_cache: computerUsePath,
116
146
  browser_use_cache: browserUsePath
117
147
  },
118
- guidance: codexAppGuidance({ appInstalled, codex, mcpList, computerUseReady, browserUseReady, remoteControl })
148
+ guidance: codexAppGuidance({ appInstalled, codex, mcpList, featureList, imageGenerationReady, computerUseReady, browserUseReady, computerUseMcpListed, browserUseMcpListed, remoteControl })
119
149
  };
120
150
  }
121
151
 
@@ -170,10 +200,10 @@ export function formatCodexRemoteControlStatus(status) {
170
200
  return lines.filter(Boolean).join('\n');
171
201
  }
172
202
 
173
- export function codexAppGuidance({ appInstalled, codex, mcpList, computerUseReady, browserUseReady, remoteControl }) {
203
+ export function codexAppGuidance({ appInstalled, codex, mcpList, featureList, imageGenerationReady, computerUseReady, browserUseReady, computerUseMcpListed, browserUseMcpListed, remoteControl }) {
174
204
  const lines = [];
175
205
  if (!appInstalled) {
176
- lines.push('Install and open Codex App for first-party MCP/plugin tools. SKS tmux launch can still run with Codex CLI alone, but Codex Computer Use evidence will be unavailable until Codex App is ready.');
206
+ lines.push('Install and open Codex App for first-party MCP/plugin tools. SKS tmux launch can still run with Codex CLI alone, but Codex Computer Use and imagegen/gpt-image-2 evidence will be unavailable until Codex App is ready.');
177
207
  lines.push(`Docs: ${CODEX_APP_DOCS_URL}`);
178
208
  }
179
209
  if (!codex?.bin) lines.push('Install Codex CLI too: npm i -g @openai/codex, or set SKS_CODEX_BIN.');
@@ -187,12 +217,27 @@ export function codexAppGuidance({ appInstalled, codex, mcpList, computerUseRead
187
217
  lines.push(`Codex MCP/config check failed: ${summarizeCodexMcpError(mcpList.stderr || mcpList.stdout)}`);
188
218
  lines.push('Verify with: codex mcp list');
189
219
  }
220
+ if (featureList?.checked && !featureList.ok) {
221
+ lines.push(`Codex feature check failed: ${summarizeCodexMcpError(featureList.stderr || featureList.stdout)}`);
222
+ lines.push('Verify with: codex features list');
223
+ }
190
224
  if (appInstalled && (!computerUseReady || !browserUseReady)) {
191
225
  lines.push('Open Codex App settings and enable recommended MCP/plugin tools. Codex CLI 0.130.0+ remote-control/app-server sessions can pick up config changes live; restart older CLI/TUI sessions.');
192
226
  lines.push('Required for SKS QA-LOOP UI/browser evidence: Codex Computer Use only. Browser Use can support non-UI browser context, but it does not satisfy UI-level E2E verification.');
193
227
  lines.push('Verify with: codex mcp list');
194
228
  }
195
- if (!lines.length) lines.push('Codex App, Codex CLI, Computer Use, and Browser Use checks look ready. UI-level E2E and visual verification still require Codex Computer Use evidence.');
229
+ if (imageGenerationReady) {
230
+ lines.push('Image generation is enabled; required raster assets and generated image-review evidence must invoke $imagegen/gpt-image-2 and record real output.');
231
+ } else if (appInstalled || codex?.bin) {
232
+ lines.push('Codex image_generation was not visible from `codex features list`. Required imagegen/gpt-image-2 evidence must stay blocked or unverified until $imagegen is available in Codex App.');
233
+ }
234
+ if (computerUseReady && !computerUseMcpListed) {
235
+ lines.push('Computer Use plugin files are installed, but this check cannot prove the current thread exposes the live Computer Use tools. Start a new Codex App thread and invoke @Computer or @AppName; if the tool is still absent from the model tool list, mark UI/browser evidence unverified.');
236
+ }
237
+ if (browserUseReady && !browserUseMcpListed) {
238
+ lines.push('Browser Use plugin files are installed, but `codex mcp list` does not list a browser-use MCP server. Treat Browser Use as plugin-scoped, not as SKS UI verification evidence.');
239
+ }
240
+ if (!lines.length) lines.push('Codex App, Codex CLI, Computer Use, Browser Use, and image generation checks look ready. UI-level E2E still requires Codex Computer Use evidence; generated image evidence still requires $imagegen/gpt-image-2 output.');
196
241
  return lines;
197
242
  }
198
243
 
@@ -203,13 +248,15 @@ export function formatCodexAppStatus(status, { includeRaw = false } = {}) {
203
248
  `Codex App: ${status.app.installed ? 'ok' : 'missing'}${status.app.path ? ` ${status.app.path}` : ''}`,
204
249
  `Codex CLI: ${status.codex_cli.ok ? 'ok' : 'missing'}${status.codex_cli.version ? ` ${status.codex_cli.version}` : ''}`,
205
250
  `Remote Ctrl: ${status.remote_control?.ok ? 'ok' : 'missing'}${status.remote_control?.codex_cli?.version_number ? ` min ${status.remote_control.min_version}` : ''}`,
206
- `Computer Use:${status.mcp.has_computer_use ? ' ok' : ' missing'}`,
207
- `Browser Use: ${status.mcp.has_browser_use ? 'ok' : 'missing'}`,
251
+ `Computer Use:${status.mcp.has_computer_use ? status.mcp.computer_use_source === 'plugin_cache' ? ' installed (verify @Computer in thread)' : ' ok' : ' missing'}`,
252
+ `Browser Use: ${status.mcp.has_browser_use ? status.mcp.browser_use_source === 'plugin_cache' ? 'installed (plugin scoped)' : 'ok' : 'missing'}`,
253
+ `Image Gen: ${status.features?.image_generation ? 'ok ($imagegen/gpt-image-2)' : status.features?.checked ? 'missing' : 'not checked'}`,
208
254
  `Ready: ${status.ok ? 'yes' : 'no'}`,
209
255
  '',
210
256
  ...status.guidance.map((line) => `- ${line}`)
211
257
  ];
212
258
  if (includeRaw && status.mcp.stdout) lines.push('', status.mcp.stdout.trim());
259
+ if (includeRaw && status.features?.stdout) lines.push('', status.features.stdout.trim());
213
260
  return lines.join('\n');
214
261
  }
215
262
 
@@ -225,6 +272,15 @@ function summarizeCodexMcpError(text) {
225
272
  return variantLine || errorLine || cleanLines[0] || 'codex mcp list failed';
226
273
  }
227
274
 
275
+ function codexFeatureEnabled(text, featureName) {
276
+ const name = escapeRegExp(featureName);
277
+ return new RegExp(`(?:^|\\n)\\s*${name}\\s+\\S+\\s+true\\b`, 'i').test(String(text || ''));
278
+ }
279
+
280
+ function escapeRegExp(value) {
281
+ return String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
282
+ }
283
+
228
284
  function remoteControlGuidance(status = {}) {
229
285
  if (!status.codex_cli?.ok) return 'Codex remote-control requires Codex CLI 0.130.0+. Install with: npm i -g @openai/codex@latest';
230
286
  if (status.reason === 'codex_cli_version_unknown') return 'Codex remote-control requires Codex CLI 0.130.0+, but the installed CLI version could not be parsed. Check: codex --version';
package/src/core/fsx.mjs CHANGED
@@ -5,7 +5,7 @@ import os from 'node:os';
5
5
  import crypto from 'node:crypto';
6
6
  import { spawn } from 'node:child_process';
7
7
 
8
- export const PACKAGE_VERSION = '0.7.50';
8
+ export const PACKAGE_VERSION = '0.7.52';
9
9
  export const DEFAULT_PROCESS_TAIL_BYTES = 256 * 1024;
10
10
  export const DEFAULT_PROCESS_TIMEOUT_MS = 30 * 60 * 1000;
11
11