sneakoscope 7.3.0 → 7.6.0

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.
Files changed (283) hide show
  1. package/README.md +129 -10
  2. package/crates/sks-core/Cargo.lock +1 -1
  3. package/crates/sks-core/Cargo.toml +1 -1
  4. package/dist/cli/codex-app-command.js +1 -1
  5. package/dist/cli/command-manifest-lite.js +4 -1
  6. package/dist/cli/command-registry.js +2 -0
  7. package/dist/cli/install-helpers-codex-lb-chain.js +42 -0
  8. package/dist/cli/install-helpers-codex-lb-config.js +114 -9
  9. package/dist/cli/install-helpers-install-support.js +6 -16
  10. package/dist/cli/install-helpers.js +788 -600
  11. package/dist/cli/router.js +30 -2
  12. package/dist/commands/codex-app.js +37 -1
  13. package/dist/commands/codex-lb.js +491 -300
  14. package/dist/config/skills-manifest.json +57 -71
  15. package/dist/core/align/align-route.js +127 -0
  16. package/dist/core/codex-app/codex-app-fast-ui-repair.js +8 -4
  17. package/dist/core/codex-app/codex-app-restart.js +83 -0
  18. package/dist/core/codex-app/codex-app-ui-state-snapshot.js +8 -4
  19. package/dist/core/codex-app/desktop-routing-snapshot.js +1 -1
  20. package/dist/core/codex-app/menubar/constants.js +5 -0
  21. package/dist/core/codex-app/menubar/generation-transaction-journal.js +1 -3
  22. package/dist/core/codex-app/menubar/installer.js +18 -0
  23. package/dist/core/codex-app/multi-provider-router.js +22 -10
  24. package/dist/core/codex-app/openrouter-activate.js +26 -1
  25. package/dist/core/codex-app.js +118 -26
  26. package/dist/core/codex-control/codex-app-server-runtime-env.js +86 -0
  27. package/dist/core/codex-control/codex-app-server-v2-client.js +78 -8
  28. package/dist/core/codex-control/codex-task-runner.js +2 -2
  29. package/dist/core/codex-lb/capability-runner.js +204 -0
  30. package/dist/core/codex-lb/capability-types.js +1 -0
  31. package/dist/core/codex-lb/codex-lb-env.js +9 -13
  32. package/dist/core/codex-lb/codex-lb-setup.js +45 -8
  33. package/dist/core/codex-lb/codex-lb-tool-catalog.js +52 -74
  34. package/dist/core/codex-lb/codex-lb-tool-output-recovery.js +1 -1
  35. package/dist/core/codex-lb/desktop-auth-invariant.js +185 -0
  36. package/dist/core/codex-lb/desktop-bridge/client.js +34 -0
  37. package/dist/core/codex-lb/desktop-bridge/header-policy.js +106 -0
  38. package/dist/core/codex-lb/desktop-bridge/http-forward.js +101 -0
  39. package/dist/core/codex-lb/desktop-bridge/index.js +10 -0
  40. package/dist/core/codex-lb/desktop-bridge/launchd.js +81 -0
  41. package/dist/core/codex-lb/desktop-bridge/location-rewrite.js +27 -0
  42. package/dist/core/codex-lb/desktop-bridge/security.js +274 -0
  43. package/dist/core/codex-lb/desktop-bridge/server.js +179 -0
  44. package/dist/core/codex-lb/desktop-bridge/state.js +119 -0
  45. package/dist/core/codex-lb/desktop-bridge/types.js +16 -0
  46. package/dist/core/codex-lb/desktop-bridge/websocket-forward.js +151 -0
  47. package/dist/core/codex-lb/desktop-center-credentials.js +189 -0
  48. package/dist/core/codex-lb/desktop-controller.js +1410 -0
  49. package/dist/core/codex-lb/desktop-mode.js +32 -0
  50. package/dist/core/codex-lb/desktop-real-evidence.js +168 -0
  51. package/dist/core/codex-lb/desktop-service.js +613 -0
  52. package/dist/core/codex-lb/legacy-migration.js +575 -0
  53. package/dist/core/codex-lb/migration-receipt.js +246 -0
  54. package/dist/core/codex-lb/probes/auxiliary-surfaces-probe.js +60 -0
  55. package/dist/core/codex-lb/probes/bridge-probe.js +75 -0
  56. package/dist/core/codex-lb/probes/catalog-probe.js +128 -0
  57. package/dist/core/codex-lb/probes/computer-use-probe.js +50 -0
  58. package/dist/core/codex-lb/probes/image-generation-probe.js +47 -0
  59. package/dist/core/codex-lb/probes/probe-evidence.js +39 -0
  60. package/dist/core/codex-lb/probes/voice-realtime-probe.js +44 -0
  61. package/dist/core/codex-lb/trusted-deep-evidence.js +403 -0
  62. package/dist/core/codex-native/core-skill-manifest.js +22 -6
  63. package/dist/core/codex-runtime/codex-desktop-config-policy.js +2 -1
  64. package/dist/core/commands/align-command.js +220 -0
  65. package/dist/core/commands/image-ux-review-command.js +280 -68
  66. package/dist/core/commands/naruto-command.js +257 -8
  67. package/dist/core/commands/ppt-command.js +2 -2
  68. package/dist/core/commands/telegram-command.js +402 -34
  69. package/dist/core/commands/triwiki-command.js +11 -1
  70. package/dist/core/commands/triwiki-graph-command.js +160 -0
  71. package/dist/core/commands/wiki-command.js +100 -20
  72. package/dist/core/doctor/skill-legacy-surface.js +1 -3
  73. package/dist/core/errors/message.js +3 -0
  74. package/dist/core/evidence/evidence-freshness.js +14 -0
  75. package/dist/core/feature-fixtures.js +9 -1
  76. package/dist/core/feature-registry.js +5 -3
  77. package/dist/core/fsx.js +7 -3
  78. package/dist/core/hooks-runtime/codex-commit-hooks-selftest.js +82 -0
  79. package/dist/core/hooks-runtime/context-graph-freshness-preflight.js +113 -0
  80. package/dist/core/hooks-runtime/hook-context.js +15 -2
  81. package/dist/core/hooks-runtime/managed-skill-generation-heal.js +46 -0
  82. package/dist/core/hooks-runtime/naruto-terminal-finalization.js +1 -0
  83. package/dist/core/hooks-runtime/official-subagent-lifecycle.js +1 -1
  84. package/dist/core/hooks-runtime/tool-output-quarantine.js +1 -1
  85. package/dist/core/hooks-runtime.js +33 -89
  86. package/dist/core/image-ux-review/callout-extraction.js +6 -0
  87. package/dist/core/image-ux-review/imagegen-adapter.js +64 -23
  88. package/dist/core/image-ux-review/real-callout-extractor.js +1 -0
  89. package/dist/core/image-ux-review.js +230 -12
  90. package/dist/core/imagegen/codex-lb-imagegen-target.js +21 -7
  91. package/dist/core/imagegen/imagegen-capability.js +10 -4
  92. package/dist/core/imagegen/require-imagegen.js +29 -5
  93. package/dist/core/init/skills.js +25 -13
  94. package/dist/core/init.js +1 -1
  95. package/dist/core/install/installed-package-smoke.js +157 -4
  96. package/dist/core/managed-assets/managed-assets-manifest.js +2 -1
  97. package/dist/core/managed-path-safety.js +8 -10
  98. package/dist/core/mcp-config/backup.js +2 -4
  99. package/dist/core/mcp-config/config-reader.js +2 -4
  100. package/dist/core/mcp-config/scope.js +3 -5
  101. package/dist/core/naruto/context-graph-advisor-scope.js +242 -0
  102. package/dist/core/naruto/context-graph-advisor.js +0 -0
  103. package/dist/core/perf/release-latency-slo.js +12 -6
  104. package/dist/core/pipeline-internals/runtime-core.js +78 -0
  105. package/dist/core/pipeline-internals/runtime-gates.js +5 -2
  106. package/dist/core/policy/latest-version-guidance.js +113 -0
  107. package/dist/core/ppt.js +8 -8
  108. package/dist/core/preflight/parallel-preflight-engine.js +1 -1
  109. package/dist/core/proof/auto-finalize.js +98 -6
  110. package/dist/core/proof/route-proof-gate.js +12 -1
  111. package/dist/core/proof/route-proof-policy.js +8 -8
  112. package/dist/core/release/gate-manifest.js +32 -1
  113. package/dist/core/release/npm-stage-contract.js +21 -0
  114. package/dist/core/release/package-size-budget.js +2 -2
  115. package/dist/core/release/release-gate-contract.js +5 -0
  116. package/dist/core/release/release-proof-io.js +1 -3
  117. package/dist/core/release/release-real-contract.js +5 -4
  118. package/dist/core/release/release-stamp-proof.js +1 -3
  119. package/dist/core/release/stage-publish.js +27 -0
  120. package/dist/core/remote/audit-idempotency.js +50 -18
  121. package/dist/core/remote/codex-public-activity.js +283 -0
  122. package/dist/core/remote/index.js +1 -0
  123. package/dist/core/remote/ssh-worker-client.js +4 -1
  124. package/dist/core/remote/worker.js +206 -27
  125. package/dist/core/research/research-adversarial-review.js +1 -3
  126. package/dist/core/research/research-claim-synthesizer.js +1 -3
  127. package/dist/core/research/research-falsification-runner.js +1 -3
  128. package/dist/core/research/research-source-evidence.js +1 -3
  129. package/dist/core/responses-stream.js +65 -19
  130. package/dist/core/routes/constants.js +1 -1
  131. package/dist/core/routes/design-policy.js +7 -2
  132. package/dist/core/routes/dollar-manifest-lite.js +2 -4
  133. package/dist/core/routes/evidence.js +1 -1
  134. package/dist/core/routes.js +30 -10
  135. package/dist/core/safety/command-contract/contracts.js +2 -0
  136. package/dist/core/safety/command-contract/types.js +1 -1
  137. package/dist/core/search/context-graph-seeds.js +205 -0
  138. package/dist/core/search/context.js +0 -0
  139. package/dist/core/search/provider.js +2 -2
  140. package/dist/core/secret-redaction.js +6 -0
  141. package/dist/core/subagents/naruto-help-contract.js +2 -1
  142. package/dist/core/subagents/naruto-host-credentials.js +161 -0
  143. package/dist/core/subagents/official-subagent-preparation.js +4 -1
  144. package/dist/core/subagents/official-subagent-prompt.js +44 -2
  145. package/dist/core/subagents/official-subagent-runner.js +26 -8
  146. package/dist/core/subagents/triwiki-attention.js +135 -123
  147. package/dist/core/telegram/bot-api.js +85 -10
  148. package/dist/core/telegram/center-snapshot.js +718 -0
  149. package/dist/core/telegram/config.js +158 -31
  150. package/dist/core/telegram/hub.js +288 -33
  151. package/dist/core/telegram/index.js +2 -0
  152. package/dist/core/telegram/ingress-spool.js +419 -0
  153. package/dist/core/telegram/ledgers.js +188 -29
  154. package/dist/core/telegram/messages.js +26 -6
  155. package/dist/core/telegram/owner-lock.js +10 -7
  156. package/dist/core/telegram/runtime-projection.js +38 -3
  157. package/dist/core/telegram/runtime.js +298 -63
  158. package/dist/core/telegram/service.js +137 -8
  159. package/dist/core/telegram/setup.js +291 -73
  160. package/dist/core/text/strings.js +3 -0
  161. package/dist/core/triwiki/code-pack.js +12 -120
  162. package/dist/core/triwiki/context-graph/compiler/cache-key.js +277 -0
  163. package/dist/core/triwiki/context-graph/compiler/extract.js +104 -0
  164. package/dist/core/triwiki/context-graph/compiler/freshness.js +108 -0
  165. package/dist/core/triwiki/context-graph/compiler/index.js +237 -0
  166. package/dist/core/triwiki/context-graph/compiler/merge.js +0 -0
  167. package/dist/core/triwiki/context-graph/compiler/serialize.js +115 -0
  168. package/dist/core/triwiki/context-graph/contracts.js +236 -0
  169. package/dist/core/triwiki/context-graph/extractors/code/builders.js +150 -0
  170. package/dist/core/triwiki/context-graph/extractors/code/declarations.js +161 -0
  171. package/dist/core/triwiki/context-graph/extractors/code/index.js +265 -0
  172. package/dist/core/triwiki/context-graph/extractors/code/inventory.js +237 -0
  173. package/dist/core/triwiki/context-graph/extractors/code/module-graph.js +252 -0
  174. package/dist/core/triwiki/context-graph/extractors/code/modules.js +79 -0
  175. package/dist/core/triwiki/context-graph/extractors/code/references.js +184 -0
  176. package/dist/core/triwiki/context-graph/extractors/code/selection.js +100 -0
  177. package/dist/core/triwiki/context-graph/extractors/code/ts-config.js +161 -0
  178. package/dist/core/triwiki/context-graph/extractors/code/types.js +18 -0
  179. package/dist/core/triwiki/context-graph/extractors/evidence/claims.js +289 -0
  180. package/dist/core/triwiki/context-graph/extractors/evidence/index.js +54 -0
  181. package/dist/core/triwiki/context-graph/extractors/evidence/proof-index.js +213 -0
  182. package/dist/core/triwiki/context-graph/extractors/evidence/proofs.js +266 -0
  183. package/dist/core/triwiki/context-graph/extractors/evidence/redaction.js +177 -0
  184. package/dist/core/triwiki/context-graph/extractors/evidence/shared.js +0 -0
  185. package/dist/core/triwiki/context-graph/extractors/evidence/sources.js +154 -0
  186. package/dist/core/triwiki/context-graph/extractors/index.js +7 -0
  187. package/dist/core/triwiki/context-graph/extractors/topology/commands.js +207 -0
  188. package/dist/core/triwiki/context-graph/extractors/topology/gate-edges.js +201 -0
  189. package/dist/core/triwiki/context-graph/extractors/topology/gates.js +182 -0
  190. package/dist/core/triwiki/context-graph/extractors/topology/globs.js +124 -0
  191. package/dist/core/triwiki/context-graph/extractors/topology/index.js +92 -0
  192. package/dist/core/triwiki/context-graph/extractors/topology/routes.js +135 -0
  193. package/dist/core/triwiki/context-graph/extractors/topology/shared.js +215 -0
  194. package/dist/core/triwiki/context-graph/graph-index.js +143 -0
  195. package/dist/core/triwiki/context-graph/ids.js +0 -0
  196. package/dist/core/triwiki/context-graph/lint/index.js +0 -0
  197. package/dist/core/triwiki/context-graph/lint/rules.js +232 -0
  198. package/dist/core/triwiki/context-graph/lint/warnings.js +93 -0
  199. package/dist/core/triwiki/context-graph/paths.js +116 -0
  200. package/dist/core/triwiki/context-graph/profiles.js +78 -0
  201. package/dist/core/triwiki/context-graph/projections/anchors.js +91 -0
  202. package/dist/core/triwiki/context-graph/projections/attention.js +58 -0
  203. package/dist/core/triwiki/context-graph/projections/code-pack-workspace.js +62 -0
  204. package/dist/core/triwiki/context-graph/projections/code-pack.js +144 -0
  205. package/dist/core/triwiki/context-graph/projections/index.js +7 -0
  206. package/dist/core/triwiki/context-graph/projections/module-view.js +140 -0
  207. package/dist/core/triwiki/context-graph/projections/node-summary.js +118 -0
  208. package/dist/core/triwiki/context-graph/projections/pack-contract.js +6 -0
  209. package/dist/core/triwiki/context-graph/query/explain.js +0 -0
  210. package/dist/core/triwiki/context-graph/query/index.js +178 -0
  211. package/dist/core/triwiki/context-graph/query/load.js +116 -0
  212. package/dist/core/triwiki/context-graph/query/pack.js +117 -0
  213. package/dist/core/triwiki/context-graph/query/rank.js +142 -0
  214. package/dist/core/triwiki/context-graph/query/ranking-config.js +64 -0
  215. package/dist/core/triwiki/context-graph/query/seeds.js +241 -0
  216. package/dist/core/triwiki/context-graph/query/snapshot-cache.js +72 -0
  217. package/dist/core/triwiki/context-graph/query/traverse.js +154 -0
  218. package/dist/core/triwiki/context-graph/query-types.js +29 -0
  219. package/dist/core/triwiki/context-graph/store/compile-lock.js +21 -0
  220. package/dist/core/triwiki/context-graph/store/event-log.js +48 -0
  221. package/dist/core/triwiki/context-graph/store/fragment-cache.js +85 -0
  222. package/dist/core/triwiki/context-graph/store/graph-status.js +85 -0
  223. package/dist/core/triwiki/context-graph/store/snapshot-store.js +155 -0
  224. package/dist/core/triwiki/triwiki-proof-bank-index-repair.js +115 -0
  225. package/dist/core/triwiki/triwiki-proof-bank-index-store.js +290 -0
  226. package/dist/core/triwiki/triwiki-proof-bank-index.js +126 -0
  227. package/dist/core/triwiki/triwiki-proof-bank.js +7 -1
  228. package/dist/core/verification/context-graph-affected.js +271 -0
  229. package/dist/core/version.js +1 -1
  230. package/dist/native/sks-menubar/Sources/AlertFactory.swift +11 -3
  231. package/dist/native/sks-menubar/Sources/AppDelegate.swift +9 -0
  232. package/dist/native/sks-menubar/Sources/ControlCenterWindowController.swift +25 -3
  233. package/dist/native/sks-menubar/Sources/ControlKit.swift +19 -8
  234. package/dist/native/sks-menubar/Sources/DiagnosticsViewController.swift +1 -2
  235. package/dist/native/sks-menubar/Sources/MCPServersViewController.swift +45 -17
  236. package/dist/native/sks-menubar/Sources/OverviewSummary.swift +162 -0
  237. package/dist/native/sks-menubar/Sources/OverviewViewController.swift +136 -190
  238. package/dist/native/sks-menubar/Sources/ProcessClient.swift +107 -11
  239. package/dist/native/sks-menubar/Sources/ProvidersMultiProvider.swift +5 -1
  240. package/dist/native/sks-menubar/Sources/ProvidersOpenRouter.swift +16 -4
  241. package/dist/native/sks-menubar/Sources/ProvidersRoleModels.swift +4 -1
  242. package/dist/native/sks-menubar/Sources/ProvidersRoutingTruth.swift +131 -0
  243. package/dist/native/sks-menubar/Sources/ProvidersViewController.swift +288 -341
  244. package/dist/native/sks-menubar/Sources/RemoteTelegramCenterState.swift +447 -0
  245. package/dist/native/sks-menubar/Sources/RemoteTelegramRendering.swift +484 -0
  246. package/dist/native/sks-menubar/Sources/RemoteTelegramViewController.swift +364 -115
  247. package/dist/native/sks-menubar/Sources/SettingsViewController.swift +73 -12
  248. package/dist/native/sks-menubar/Sources/SidebarItem.swift +3 -3
  249. package/dist/native/sks-menubar/Sources/StatusItemController.swift +13 -13
  250. package/dist/native/sks-menubar/Sources/TelegramHubSupervisor.swift +469 -0
  251. package/dist/native/sks-menubar/Sources/UpdatesViewController.swift +1 -6
  252. package/dist/native/sks-menubar/Tests/AppRuntimeTestSupport.swift +12 -0
  253. package/dist/native/sks-menubar/Tests/NativeTelegramRegressionRunner.swift +55 -0
  254. package/dist/native/sks-menubar/Tests/ProcessClientTests.swift +33 -0
  255. package/dist/native/sks-menubar/Tests/ProvidersViewControllerTests.swift +142 -0
  256. package/dist/native/sks-menubar/Tests/RemoteTelegramCenterStateTests.swift +56 -0
  257. package/dist/native/sks-menubar/Tests/TelegramHubSupervisorTests.swift +55 -0
  258. package/dist/scripts/codex-lb-catalog-passthrough-check.js +120 -0
  259. package/dist/scripts/codex-lb-desktop-auth-invariant-check.js +169 -0
  260. package/dist/scripts/codex-lb-desktop-bridge-check.js +394 -0
  261. package/dist/scripts/codex-lb-desktop-bridge-latency-check.js +196 -0
  262. package/dist/scripts/codex-lb-desktop-capabilities-check.js +287 -0
  263. package/dist/scripts/codex-lb-desktop-real-evidence-check.js +123 -0
  264. package/dist/scripts/codex-lb-fast-mode-truth-check.js +10 -3
  265. package/dist/scripts/codex-lb-fast-ui-preservation-check.js +81 -40
  266. package/dist/scripts/codex-lb-gpt56-fast-profile-check.js +99 -37
  267. package/dist/scripts/context-graph-check.js +191 -0
  268. package/dist/scripts/doctor-fixes-codex-app-fast-ui-check.js +1 -1
  269. package/dist/scripts/gate-policy-audit-check.js +1 -1
  270. package/dist/scripts/helper-dedup-check.js +0 -2
  271. package/dist/scripts/installed-package-smoke-check.js +4 -1
  272. package/dist/scripts/latest-version-guidance-check.js +74 -0
  273. package/dist/scripts/release-metadata-check.js +21 -4
  274. package/dist/scripts/release-real-check.js +7 -0
  275. package/dist/scripts/ux-review-imagegen-blackbox-check.js +25 -1
  276. package/dist/scripts/ux-review-run-wires-imagegen-check.js +9 -1
  277. package/package.json +8 -3
  278. package/release-gates.v2.json +176 -1
  279. package/runtime-required-scripts.json +30 -0
  280. package/schemas/triwiki/context-graph.schema.json +119 -0
  281. package/dist/cli/install-helpers-codex-lb-selftest-chain.js +0 -112
  282. package/dist/cli/install-helpers-codex-lb-selftest.js +0 -481
  283. package/dist/core/triwiki/code-index-scanner.js +0 -309
@@ -1,12 +1,12 @@
1
1
  import path from 'node:path';
2
2
  import fsp from 'node:fs/promises';
3
3
  import { ensureDir, nowIso, projectRoot, readJson, writeJsonAtomic } from '../fsx.js';
4
- import { createMission, findLatestMission, loadMission } from '../mission.js';
4
+ import { createMission, findLatestMission, loadMission, missionDir } from '../mission.js';
5
5
  import { flag, readOption } from './command-utils.js';
6
6
  import { printJson } from '../../cli/output.js';
7
- import { IMAGE_UX_REVIEW_FIX_LOOP_ARTIFACT, IMAGE_UX_REVIEW_FIX_TASK_PLAN_ARTIFACT, IMAGE_UX_REVIEW_CALLOUT_EXTRACTION_REPORT_ARTIFACT, IMAGE_UX_REVIEW_GENERATED_REVIEW_LEDGER_ARTIFACT, IMAGE_UX_REVIEW_GPT_IMAGE_2_RESPONSE_ARTIFACT, IMAGE_UX_REVIEW_GATE_ARTIFACT, IMAGE_UX_REVIEW_HONEST_MODE_ARTIFACT, IMAGE_UX_REVIEW_GPT_IMAGE_2_REQUEST_ARTIFACT, IMAGE_UX_REVIEW_IMAGEGEN_REQUEST_ARTIFACT, IMAGE_UX_REVIEW_ITERATION_REPORT_ARTIFACT, IMAGE_UX_REVIEW_ISSUE_LEDGER_ARTIFACT, IMAGE_UX_REVIEW_POLICY_ARTIFACT, IMAGE_UX_REVIEW_RECAPTURE_ARTIFACT, IMAGE_UX_REVIEW_SCREEN_INVENTORY_ARTIFACT, imageUxReviewProofEvidence, buildImageUxCalloutExtractionReport, writeImageUxReviewRouteArtifacts } from '../image-ux-review.js';
7
+ import { IMAGE_UX_REVIEW_FIX_LOOP_ARTIFACT, IMAGE_UX_REVIEW_FIX_TASK_PLAN_ARTIFACT, IMAGE_UX_REVIEW_CALLOUT_EXTRACTION_REPORT_ARTIFACT, IMAGE_UX_REVIEW_GENERATED_REVIEW_LEDGER_ARTIFACT, IMAGE_UX_REVIEW_GPT_IMAGE_2_RESPONSE_ARTIFACT, IMAGE_UX_REVIEW_GATE_ARTIFACT, IMAGE_UX_REVIEW_HONEST_MODE_ARTIFACT, IMAGE_UX_REVIEW_GPT_IMAGE_2_REQUEST_ARTIFACT, IMAGE_UX_REVIEW_IMAGEGEN_REQUEST_ARTIFACT, IMAGE_UX_REVIEW_ITERATION_REPORT_ARTIFACT, IMAGE_UX_REVIEW_ISSUE_LEDGER_ARTIFACT, IMAGE_UX_REVIEW_POLICY_ARTIFACT, IMAGE_UX_REVIEW_RECAPTURE_ARTIFACT, IMAGE_UX_REVIEW_SCREEN_INVENTORY_ARTIFACT, imageUxReviewProofEvidence, imageUxReviewCommandOutcome, buildImageUxCalloutExtractionReport, writeImageUxReviewRouteArtifacts } from '../image-ux-review.js';
8
8
  import { maybeFinalizeRoute } from '../proof/auto-finalize.js';
9
- import { generatedImageMetadata, generateGptImage2CalloutReview } from '../image-ux-review/imagegen-adapter.js';
9
+ import { buildCalloutPrompt, generatedImageMetadata, generateGptImage2CalloutReview } from '../image-ux-review/imagegen-adapter.js';
10
10
  import { extractRealCallouts } from '../image-ux-review/real-callout-extractor.js';
11
11
  import { addImageRelation, ingestImage } from '../wiki-image/image-voxel-ledger.js';
12
12
  import { sha256File, imageDimensions } from '../wiki-image/image-hash.js';
@@ -15,28 +15,34 @@ import { codexChromeExtensionStatus } from '../codex-app.js';
15
15
  import { requireCodexImagegen } from '../imagegen/require-imagegen.js';
16
16
  import { imagegenEvidenceClassBlockers, isFullImagegenEvidenceClass, isFullImagegenOutputSource } from '../imagegen/imagegen-evidence.js';
17
17
  import { evaluateGate } from '../stop-gate/gate-evaluator.js';
18
+ import { closeWorkOrderLedgerForRouteResult } from '../work-order-ledger.js';
18
19
  const ONE_BY_ONE_PNG_BASE64 = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMB/axX7V8AAAAASUVORK5CYII=';
20
+ const stableImageUxArtifact = (artifactPath) => ({
21
+ path: artifactPath,
22
+ source: 'real',
23
+ ignoreStale: true
24
+ });
19
25
  const IMAGE_UX_REVIEW_ARTIFACT_PATHS = {
20
- policy: IMAGE_UX_REVIEW_POLICY_ARTIFACT,
21
- inventory: IMAGE_UX_REVIEW_SCREEN_INVENTORY_ARTIFACT,
22
- imagegen_request: IMAGE_UX_REVIEW_IMAGEGEN_REQUEST_ARTIFACT,
23
- gpt_image_2_request: IMAGE_UX_REVIEW_GPT_IMAGE_2_REQUEST_ARTIFACT,
24
- imagegen_response: IMAGE_UX_REVIEW_GPT_IMAGE_2_RESPONSE_ARTIFACT,
25
- generated_review_ledger: IMAGE_UX_REVIEW_GENERATED_REVIEW_LEDGER_ARTIFACT,
26
- issue_ledger: IMAGE_UX_REVIEW_ISSUE_LEDGER_ARTIFACT,
27
- callout_extraction_report: IMAGE_UX_REVIEW_CALLOUT_EXTRACTION_REPORT_ARTIFACT,
28
- fix_task_plan: IMAGE_UX_REVIEW_FIX_TASK_PLAN_ARTIFACT,
29
- fix_loop: IMAGE_UX_REVIEW_FIX_LOOP_ARTIFACT,
30
- recapture_plan: IMAGE_UX_REVIEW_RECAPTURE_ARTIFACT,
31
- iteration_report: IMAGE_UX_REVIEW_ITERATION_REPORT_ARTIFACT,
26
+ policy: stableImageUxArtifact(IMAGE_UX_REVIEW_POLICY_ARTIFACT),
27
+ inventory: stableImageUxArtifact(IMAGE_UX_REVIEW_SCREEN_INVENTORY_ARTIFACT),
28
+ imagegen_request: stableImageUxArtifact(IMAGE_UX_REVIEW_IMAGEGEN_REQUEST_ARTIFACT),
29
+ gpt_image_2_request: stableImageUxArtifact(IMAGE_UX_REVIEW_GPT_IMAGE_2_REQUEST_ARTIFACT),
30
+ imagegen_response: stableImageUxArtifact(IMAGE_UX_REVIEW_GPT_IMAGE_2_RESPONSE_ARTIFACT),
31
+ generated_review_ledger: stableImageUxArtifact(IMAGE_UX_REVIEW_GENERATED_REVIEW_LEDGER_ARTIFACT),
32
+ issue_ledger: stableImageUxArtifact(IMAGE_UX_REVIEW_ISSUE_LEDGER_ARTIFACT),
33
+ callout_extraction_report: stableImageUxArtifact(IMAGE_UX_REVIEW_CALLOUT_EXTRACTION_REPORT_ARTIFACT),
34
+ fix_task_plan: stableImageUxArtifact(IMAGE_UX_REVIEW_FIX_TASK_PLAN_ARTIFACT),
35
+ fix_loop: stableImageUxArtifact(IMAGE_UX_REVIEW_FIX_LOOP_ARTIFACT),
36
+ recapture_plan: stableImageUxArtifact(IMAGE_UX_REVIEW_RECAPTURE_ARTIFACT),
37
+ iteration_report: stableImageUxArtifact(IMAGE_UX_REVIEW_ITERATION_REPORT_ARTIFACT),
32
38
  output_schema: {
33
39
  path: 'schemas/codex/image-ux-issue-ledger.schema.json',
34
40
  kind: 'schema',
35
41
  source: 'real',
36
42
  ignoreStale: true
37
43
  },
38
- honest_mode_evidence: IMAGE_UX_REVIEW_HONEST_MODE_ARTIFACT,
39
- gate: IMAGE_UX_REVIEW_GATE_ARTIFACT
44
+ honest_mode_evidence: stableImageUxArtifact(IMAGE_UX_REVIEW_HONEST_MODE_ARTIFACT),
45
+ gate: stableImageUxArtifact(IMAGE_UX_REVIEW_GATE_ARTIFACT)
40
46
  };
41
47
  export async function imageUxReviewCommand(command, args = []) {
42
48
  const root = await projectRoot();
@@ -66,13 +72,17 @@ export async function imageUxReviewCommand(command, args = []) {
66
72
  async function runImageUxReview(root, command, args = []) {
67
73
  const missionRequested = readOption(args, '--mission', null);
68
74
  const missionId = missionRequested
69
- ? missionRequested === 'latest' ? await findLatestMission(root, { mode: 'image-ux-review', route: '$Image-UX-Review', gateFile: IMAGE_UX_REVIEW_GATE_ARTIFACT }) : missionRequested
75
+ ? missionRequested === 'latest' ? await findLatestMission(root, { mode: 'image-ux-review' }) : missionRequested
70
76
  : null;
71
77
  const imagePath = readOption(args, '--image', null) || readOption(args, '--screenshot', null);
72
78
  const generatedImage = readOption(args, '--generated-image', null);
73
- const shouldGenerateCallouts = flag(args, '--generate-callouts') || flag(args, '--fix');
74
- if (missionId)
79
+ const shouldGenerateCallouts = !generatedImage;
80
+ if (missionId) {
81
+ if (generatedImage) {
82
+ return continueExistingMissionWithGeneratedImage(root, command, missionId, generatedImage, args, 'sks.image-ux-review-run.v1');
83
+ }
75
84
  return rebuildExistingMission(root, command, [missionId, ...args], { fixRequested: flag(args, '--fix') });
85
+ }
76
86
  const sourcePreflight = await imageUxReviewSourcePreflight(args);
77
87
  const { fromChromeExtension, fromComputerUse, chromePreflight } = sourcePreflight;
78
88
  if (sourcePreflight.result) {
@@ -111,7 +121,7 @@ async function runImageUxReview(root, command, args = []) {
111
121
  process.exitCode = 1;
112
122
  if (flag(args, '--json'))
113
123
  return printJson(result);
114
- console.error('UX Review blocked: Codex App imagegen/gpt-image-2 is unavailable.');
124
+ console.error('UX Review blocked: no selected Codex imagegen/gpt-image-2 provider is ready.');
115
125
  for (const action of imagegenRequired.blocker?.next_actions || [])
116
126
  console.error(`- ${action}`);
117
127
  return result;
@@ -133,8 +143,16 @@ async function runImageUxReview(root, command, args = []) {
133
143
  }
134
144
  };
135
145
  await writeJsonAtomic(path.join(dir, 'decision-contract.json'), contract);
136
- if (generatedImage)
146
+ if (generatedImage) {
137
147
  await attachGeneratedReviewImage(root, dir, contract, generatedImage, { realGenerated: !flag(args, '--mock'), mock: flag(args, '--mock') });
148
+ if (!flag(args, '--mock')) {
149
+ await extractAndWriteGeneratedReview(root, dir, {
150
+ generatedImagePath: generatedImage,
151
+ sourceImagePath: sourceRel || imagePath,
152
+ sessionId: readOption(args, '--session', null) || readOption(args, '--session-id', null)
153
+ });
154
+ }
155
+ }
138
156
  if (!generatedImage && shouldGenerateCallouts) {
139
157
  const outputDir = path.join(dir, 'generated-callouts');
140
158
  const missionStartMs = Date.parse(mission.created_at || '') || undefined;
@@ -144,7 +162,9 @@ async function runImageUxReview(root, command, args = []) {
144
162
  source_screen_id: 'screen-1',
145
163
  source_image_path: path.resolve(root, sourceRel || imagePath),
146
164
  output_dir: outputDir,
147
- prompt: promptForRun(command, args),
165
+ prompt: buildCalloutPrompt('screen-1', {
166
+ target: contract.answers.TARGET_SURFACE
167
+ }),
148
168
  requested_fidelity: 'original',
149
169
  privacy: 'local-only'
150
170
  }, {
@@ -153,11 +173,11 @@ async function runImageUxReview(root, command, args = []) {
153
173
  generatedImageMaxAgeMs: maxAgeOverride ? Number(maxAgeOverride) * 60 * 1000 : 30 * 60 * 1000
154
174
  }
155
175
  });
176
+ if (result.request_artifact)
177
+ await fsp.copyFile(result.request_artifact, path.join(dir, IMAGE_UX_REVIEW_GPT_IMAGE_2_REQUEST_ARTIFACT));
178
+ if (result.response_artifact)
179
+ await fsp.copyFile(result.response_artifact, path.join(dir, IMAGE_UX_REVIEW_GPT_IMAGE_2_RESPONSE_ARTIFACT));
156
180
  if (result.generated_image_path) {
157
- if (result.request_artifact)
158
- await fsp.copyFile(result.request_artifact, path.join(dir, IMAGE_UX_REVIEW_GPT_IMAGE_2_REQUEST_ARTIFACT)).catch(() => { });
159
- if (result.response_artifact)
160
- await fsp.copyFile(result.response_artifact, path.join(dir, IMAGE_UX_REVIEW_GPT_IMAGE_2_RESPONSE_ARTIFACT)).catch(() => { });
161
181
  const response = await readImagegenResponse(dir);
162
182
  const evidenceClass = String(response?.evidence_class || '');
163
183
  const fakeGenerated = evidenceClass === 'mock_fixture' || result.provider === 'fake_imagegen_adapter';
@@ -171,25 +191,21 @@ async function runImageUxReview(root, command, args = []) {
171
191
  outputSha256: response?.output_sha256 || response?.output_image_sha256 || null,
172
192
  responseArtifact: result.response_artifact || null
173
193
  });
174
- const extraction = await extractRealCallouts({
175
- root,
194
+ await extractAndWriteGeneratedReview(root, dir, {
176
195
  generatedImagePath: result.generated_image_path,
177
- sourceScreenshot: { id: 'screen-1' },
196
+ sourceImagePath: sourceRel || imagePath,
178
197
  sessionId: readOption(args, '--session', null) || readOption(args, '--session-id', null)
179
198
  });
180
- await writeJsonAtomic(path.join(dir, IMAGE_UX_REVIEW_ISSUE_LEDGER_ARTIFACT), extraction.issue_ledger);
181
- await writeJsonAtomic(path.join(dir, IMAGE_UX_REVIEW_CALLOUT_EXTRACTION_REPORT_ARTIFACT), await buildImageUxCalloutExtractionReport(root, extraction, {
182
- generatedImagePath: result.generated_image_path,
183
- sourceImagePath: sourceRel || imagePath,
184
- provider: extraction.provider
185
- }));
186
199
  }
187
200
  }
188
201
  const artifacts = await writeImageUxReviewRouteArtifacts(dir, contract, { root, wrongnessChecked: true, honestModeComplete: true });
189
202
  artifacts.gate = await enforceImageUxRuntimeGate(dir, artifacts.gate, { mock: flag(args, '--mock') });
190
203
  await writeJsonAtomic(path.join(dir, IMAGE_UX_REVIEW_GATE_ARTIFACT), artifacts.gate);
191
204
  const proof = await finalizeImageUx(root, id, command, artifacts, { mock: flag(args, '--mock'), cmd: `sks ${command} run` });
192
- const result = { schema: 'sks.image-ux-review-run.v1', ok: proof.ok && artifacts.gate?.passed === true, status: artifacts.gate?.status || (artifacts.gate?.passed ? 'passed' : 'blocked'), mission_id: id, artifacts, proof: proof.validation };
205
+ const outcome = imageUxReviewCommandOutcome(artifacts.gate, proof.ok, {
206
+ allowReviewOnlyCompletion: !flag(args, '--fix')
207
+ });
208
+ const result = { schema: 'sks.image-ux-review-run.v1', ...outcome, mission_id: id, artifacts, proof: proof.validation };
193
209
  if (!result.ok)
194
210
  process.exitCode = 1;
195
211
  if (flag(args, '--json'))
@@ -197,10 +213,67 @@ async function runImageUxReview(root, command, args = []) {
197
213
  console.log(`Image UX review: ${result.ok ? 'ok' : 'blocked'} ${id}`);
198
214
  return result;
199
215
  }
200
- export async function imageUxReviewSourcePreflight(args = []) {
216
+ async function extractAndWriteGeneratedReview(root, dir, input) {
217
+ const extraction = await extractRealCallouts({
218
+ root,
219
+ generatedImagePath: input.generatedImagePath,
220
+ sourceScreenshot: { id: 'screen-1' },
221
+ sessionId: input.sessionId || null
222
+ });
223
+ const generatedLedger = await readJson(path.join(dir, IMAGE_UX_REVIEW_GENERATED_REVIEW_LEDGER_ARTIFACT), null).catch(() => null);
224
+ const generatedImages = Array.isArray(generatedLedger?.generated_review_images) ? generatedLedger.generated_review_images : [];
225
+ const localImage = generatedImages.find((image) => image.sha256 && image.sha256 === extraction.generated_image_sha256)
226
+ || generatedImages[0]
227
+ || null;
228
+ const localImageId = localImage?.id || null;
229
+ const generatedSha = localImage?.sha256 || extraction.generated_image_sha256 || null;
230
+ const extractedIssues = Array.isArray(extraction.issue_ledger?.issues) ? extraction.issue_ledger.issues : [];
231
+ const boundIssues = extractedIssues.map((issue) => ({
232
+ ...issue,
233
+ generated_review_image_id: localImageId || issue.generated_review_image_id,
234
+ evidence_image_id: localImageId || issue.evidence_image_id,
235
+ generated_image_sha256: generatedSha || issue.generated_image_sha256
236
+ }));
237
+ const boundIssueLedger = {
238
+ ...extraction.issue_ledger,
239
+ generated_image_id: localImageId,
240
+ generated_image_sha256: generatedSha,
241
+ issues: boundIssues
242
+ };
243
+ const extractionSucceeded = extraction.ok === true
244
+ && boundIssueLedger.validation?.ok === true
245
+ && boundIssues.length > 0;
246
+ if (localImage && generatedLedger) {
247
+ await writeJsonAtomic(path.join(dir, IMAGE_UX_REVIEW_GENERATED_REVIEW_LEDGER_ARTIFACT), {
248
+ ...generatedLedger,
249
+ status: extractionSucceeded ? 'generated_and_extracted' : 'generated_extraction_blocked',
250
+ generated_review_images: generatedImages.map((image) => image.id === localImage.id ? {
251
+ ...image,
252
+ generated_image_sha256: generatedSha,
253
+ extraction_provider: extraction.provider || null,
254
+ extraction_status: extraction.status || (extractionSucceeded ? 'extracted' : 'blocked'),
255
+ callout_extraction_status: extractionSucceeded ? 'succeeded' : 'failed',
256
+ callout_extraction_blocker: extractionSucceeded ? null : extraction.blocker || 'callout_extraction_failed',
257
+ callouts: boundIssues
258
+ } : image)
259
+ });
260
+ }
261
+ await writeJsonAtomic(path.join(dir, IMAGE_UX_REVIEW_ISSUE_LEDGER_ARTIFACT), boundIssueLedger);
262
+ await writeJsonAtomic(path.join(dir, IMAGE_UX_REVIEW_CALLOUT_EXTRACTION_REPORT_ARTIFACT), await buildImageUxCalloutExtractionReport(root, extraction, {
263
+ generatedImagePath: input.generatedImagePath,
264
+ generatedImageId: localImageId,
265
+ sourceImagePath: input.sourceImagePath || null,
266
+ provider: extraction.provider
267
+ }));
268
+ return extraction;
269
+ }
270
+ export async function imageUxReviewSourcePreflight(args = [], opts = {}) {
201
271
  const fromChromeExtension = flag(args, '--from-chrome-extension') || Boolean(readOption(args, '--from-chrome-extension', null));
202
272
  const fromComputerUse = flag(args, '--from-computer-use') || Boolean(readOption(args, '--from-computer-use', null));
203
- const chromePreflight = fromChromeExtension ? await codexChromeExtensionStatus() : null;
273
+ const imagePath = readOption(args, '--image', null) || readOption(args, '--screenshot', null);
274
+ const chromePreflight = fromChromeExtension
275
+ ? await (opts.chromeStatus ? opts.chromeStatus() : codexChromeExtensionStatus())
276
+ : null;
204
277
  if (chromePreflight && !chromePreflight.ok) {
205
278
  return {
206
279
  fromChromeExtension,
@@ -231,6 +304,22 @@ export async function imageUxReviewSourcePreflight(args = []) {
231
304
  }
232
305
  };
233
306
  }
307
+ if ((fromChromeExtension || fromComputerUse) && !imagePath) {
308
+ return {
309
+ fromChromeExtension,
310
+ fromComputerUse,
311
+ chromePreflight,
312
+ result: {
313
+ schema: 'sks.image-ux-review-run.v1',
314
+ ok: false,
315
+ status: 'blocked',
316
+ blocker: 'captured_screenshot_path_required',
317
+ guidance: [
318
+ 'Capture the real page or native-app screen first, then pass its local artifact path with --image <captured-path>.'
319
+ ]
320
+ }
321
+ };
322
+ }
234
323
  return { fromChromeExtension, fromComputerUse, chromePreflight, result: null };
235
324
  }
236
325
  function printImageUxReviewSourcePreflightBlock(result) {
@@ -240,6 +329,10 @@ function printImageUxReviewSourcePreflightBlock(result) {
240
329
  console.error(result.chrome_extension.docs_url);
241
330
  return;
242
331
  }
332
+ if (result.blocker === 'captured_screenshot_path_required') {
333
+ console.error('UX Review blocked: capture completed without a bound screenshot artifact. Pass --image <captured-path>.');
334
+ return;
335
+ }
243
336
  console.error('UX Review blocked: web/browser UX review requires Codex Chrome Extension, not Computer Use. Use --from-chrome-extension after setup, or provide a screenshot with --image.');
244
337
  }
245
338
  async function calloutsImageUxReview(root, command, args = []) {
@@ -255,8 +348,12 @@ async function calloutsImageUxReview(root, command, args = []) {
255
348
  return runImageUxReview(root, command, ['--image', imagePath, '--generate-callouts', '--json', ...(flag(args, '--mock') ? ['--mock'] : [])]);
256
349
  }
257
350
  async function extractIssuesImageUxReview(root, command, args = []) {
351
+ const missionRequested = readOption(args, '--mission', null);
352
+ const missionId = missionRequested
353
+ ? missionRequested === 'latest' ? await findLatestMission(root, { mode: 'image-ux-review' }) : missionRequested
354
+ : null;
258
355
  const generatedImage = readOption(args, '--generated-image', null);
259
- const sourceImage = readOption(args, '--image', null) || readOption(args, '--screenshot', null) || generatedImage;
356
+ const sourceImage = readOption(args, '--image', null) || readOption(args, '--screenshot', null);
260
357
  if (!generatedImage) {
261
358
  const result = { schema: 'sks.image-ux-review-extract-issues.v1', ok: false, status: 'blocked', blocker: 'generated_image_required' };
262
359
  process.exitCode = 1;
@@ -265,6 +362,9 @@ async function extractIssuesImageUxReview(root, command, args = []) {
265
362
  console.error('Usage: sks ux-review extract-issues --generated-image <path> --json');
266
363
  return result;
267
364
  }
365
+ if (missionId) {
366
+ return continueExistingMissionWithGeneratedImage(root, command, missionId, generatedImage, args, 'sks.image-ux-review-extract-issues.v1');
367
+ }
268
368
  const { id, dir, mission } = await createMission(root, { mode: 'image-ux-review', prompt: `Extract UX issues from ${generatedImage}` });
269
369
  const sourceRel = sourceImage ? await stageSourceImage(root, dir, sourceImage) : null;
270
370
  const contract = {
@@ -275,18 +375,11 @@ async function extractIssuesImageUxReview(root, command, args = []) {
275
375
  await writeJsonAtomic(path.join(dir, 'decision-contract.json'), contract);
276
376
  await attachGeneratedReviewImage(root, dir, contract, generatedImage, { realGenerated: !flag(args, '--mock'), mock: flag(args, '--mock') });
277
377
  if (!flag(args, '--mock')) {
278
- const extraction = await extractRealCallouts({
279
- root,
378
+ await extractAndWriteGeneratedReview(root, dir, {
280
379
  generatedImagePath: generatedImage,
281
- sourceScreenshot: { id: 'screen-1' },
380
+ sourceImagePath: sourceRel || sourceImage,
282
381
  sessionId: readOption(args, '--session', null) || readOption(args, '--session-id', null)
283
382
  });
284
- await writeJsonAtomic(path.join(dir, IMAGE_UX_REVIEW_ISSUE_LEDGER_ARTIFACT), extraction.issue_ledger);
285
- await writeJsonAtomic(path.join(dir, IMAGE_UX_REVIEW_CALLOUT_EXTRACTION_REPORT_ARTIFACT), await buildImageUxCalloutExtractionReport(root, extraction, {
286
- generatedImagePath: generatedImage,
287
- sourceImagePath: sourceRel || sourceImage,
288
- provider: extraction.provider
289
- }));
290
383
  }
291
384
  const artifacts = await writeImageUxReviewRouteArtifacts(dir, contract, { root, wrongnessChecked: true, honestModeComplete: true });
292
385
  const proof = await finalizeImageUx(root, id, command, artifacts, { mock: flag(args, '--mock'), cmd: `sks ${command} extract-issues` });
@@ -298,9 +391,56 @@ async function extractIssuesImageUxReview(root, command, args = []) {
298
391
  console.log(`Image UX issue extraction: ${result.ok ? 'ok' : 'blocked'} ${id}`);
299
392
  return result;
300
393
  }
394
+ async function continueExistingMissionWithGeneratedImage(root, command, missionId, generatedImage, args, schema) {
395
+ const { dir, mission } = await loadMission(root, missionId);
396
+ const contract = await readJson(path.join(dir, 'decision-contract.json'), { prompt: mission.prompt, answers: {}, sealed_hash: null });
397
+ const mock = flag(args, '--mock');
398
+ const ledger = await attachGeneratedReviewImage(root, dir, contract, generatedImage, {
399
+ realGenerated: !mock,
400
+ mock
401
+ });
402
+ if (!mock) {
403
+ await extractAndWriteGeneratedReview(root, dir, {
404
+ generatedImagePath: generatedImage,
405
+ sourceImagePath: sourceImageFromContract(contract),
406
+ sessionId: readOption(args, '--session', null) || readOption(args, '--session-id', null)
407
+ });
408
+ }
409
+ const artifacts = await writeImageUxReviewRouteArtifacts(dir, contract, {
410
+ root,
411
+ wrongnessChecked: true,
412
+ honestModeComplete: true
413
+ });
414
+ artifacts.gate = await enforceImageUxRuntimeGate(dir, artifacts.gate, { mock });
415
+ await writeJsonAtomic(path.join(dir, IMAGE_UX_REVIEW_GATE_ARTIFACT), artifacts.gate);
416
+ const proof = await finalizeImageUx(root, missionId, command, artifacts, {
417
+ mock,
418
+ cmd: `sks ${command} ${schema.endsWith('extract-issues.v1') ? 'extract-issues' : 'attach-generated'}`
419
+ });
420
+ const outcome = imageUxReviewCommandOutcome(artifacts.gate, proof.ok, {
421
+ allowReviewOnlyCompletion: !flag(args, '--fix')
422
+ });
423
+ const result = {
424
+ schema,
425
+ ...outcome,
426
+ mission_id: missionId,
427
+ generated_review_ledger: ledger,
428
+ issue_ledger: artifacts.issue_ledger,
429
+ gate: artifacts.gate,
430
+ proof: proof.validation
431
+ };
432
+ if (!result.ok)
433
+ process.exitCode = 1;
434
+ if (flag(args, '--json'))
435
+ return printJson(result);
436
+ console.log(`Image UX review continued: ${result.ok ? 'ok' : 'blocked'} ${missionId}`);
437
+ return result;
438
+ }
301
439
  async function attachGeneratedImageCommand(root, command, args = []) {
302
- const missionArg = args.find((arg) => !String(arg).startsWith('--')) || 'latest';
303
- const missionId = missionArg === 'latest' ? await findLatestMission(root, { mode: 'image-ux-review', route: '$Image-UX-Review', gateFile: IMAGE_UX_REVIEW_GATE_ARTIFACT }) : missionArg;
440
+ const missionArg = readOption(args, '--mission', null)
441
+ || args.find((arg, index) => !String(arg).startsWith('--') && !String(args[index - 1] || '').startsWith('--'))
442
+ || 'latest';
443
+ const missionId = missionArg === 'latest' ? await findLatestMission(root, { mode: 'image-ux-review' }) : missionArg;
304
444
  const imagePath = readOption(args, '--image', null) || readOption(args, '--generated-image', null);
305
445
  if (!missionId || !imagePath) {
306
446
  const result = { schema: 'sks.image-ux-review-attach-generated.v1', ok: false, status: 'blocked', blocker: !missionId ? 'mission_required' : 'generated_image_required' };
@@ -309,15 +449,7 @@ async function attachGeneratedImageCommand(root, command, args = []) {
309
449
  return printJson(result);
310
450
  return result;
311
451
  }
312
- const { dir, mission } = await loadMission(root, missionId);
313
- const contract = await readJson(path.join(dir, 'decision-contract.json'), { prompt: mission.prompt, answers: {}, sealed_hash: null });
314
- const ledger = await attachGeneratedReviewImage(root, dir, contract, imagePath, { realGenerated: !flag(args, '--mock'), mock: flag(args, '--mock') });
315
- const artifacts = await writeImageUxReviewRouteArtifacts(dir, contract, { root, wrongnessChecked: true, honestModeComplete: true });
316
- const result = { schema: 'sks.image-ux-review-attach-generated.v1', ok: artifacts.gate.generated_image_ingested === true, mission_id: missionId, generated_review_ledger: ledger, gate: artifacts.gate };
317
- if (flag(args, '--json'))
318
- return printJson(result);
319
- console.log(`Generated image attached: ${missionId}`);
320
- return result;
452
+ return continueExistingMissionWithGeneratedImage(root, command, missionId, imagePath, args, 'sks.image-ux-review-attach-generated.v1');
321
453
  }
322
454
  async function attachAfterImageCommand(root, command, args = []) {
323
455
  const missionArg = args.find((arg) => !String(arg).startsWith('--')) || 'latest';
@@ -359,13 +491,18 @@ async function rebuildExistingMission(root, command, args = [], opts = {}) {
359
491
  root,
360
492
  wrongnessChecked: true,
361
493
  fixLoop: { requirePatch: opts.fixRequested === true },
362
- recapture: { computerUseAvailable: false },
494
+ recapture: opts.proofRequested === true ? undefined : { computerUseAvailable: false },
495
+ preserveExistingRecapture: opts.proofRequested === true,
363
496
  honestModeComplete: opts.proofRequested === true
364
497
  });
365
498
  artifacts.gate = await enforceImageUxRuntimeGate(dir, artifacts.gate, { mock: flag(args, '--mock') });
366
499
  await writeJsonAtomic(path.join(dir, IMAGE_UX_REVIEW_GATE_ARTIFACT), artifacts.gate);
367
- const proof = await finalizeImageUx(root, missionId, command, artifacts, { mock: flag(args, '--mock'), cmd: `sks ${command} ${opts.fixRequested ? 'fix' : opts.recaptureRequested ? 'recapture' : 'build'}` });
368
- const result = { schema: 'sks.image-ux-review-build.v2', ok: proof.ok && artifacts.gate?.passed === true, status: artifacts.gate?.status || (artifacts.gate?.passed ? 'passed' : 'blocked'), mission_id: missionId, artifacts, proof: proof.validation };
500
+ const proofAction = opts.proofRequested ? 'proof' : opts.fixRequested ? 'fix' : opts.recaptureRequested ? 'recapture' : 'build';
501
+ const proof = await finalizeImageUx(root, missionId, command, artifacts, { mock: flag(args, '--mock'), cmd: `sks ${command} ${proofAction}` });
502
+ const outcome = imageUxReviewCommandOutcome(artifacts.gate, proof.ok, {
503
+ allowReviewOnlyCompletion: false
504
+ });
505
+ const result = { schema: 'sks.image-ux-review-build.v2', ...outcome, mission_id: missionId, artifacts, proof: proof.validation };
369
506
  if (!result.ok)
370
507
  process.exitCode = 1;
371
508
  if (flag(args, '--json'))
@@ -510,10 +647,19 @@ async function attachGeneratedReviewImage(root, dir, contract, imagePath, opts =
510
647
  evidence_class: evidenceClass,
511
648
  output_source: outputSource,
512
649
  output_sha256: outputSha256 || undefined,
650
+ output_id: response?.output_id || null,
513
651
  real_generated: opts.realGenerated === true && isFullImagegenEvidenceClass(evidenceClass),
514
652
  mock: opts.mock === true
515
653
  });
516
- if (!response && opts.realGenerated === true) {
654
+ if (response) {
655
+ await writeJsonAtomic(path.join(dir, IMAGE_UX_REVIEW_GPT_IMAGE_2_RESPONSE_ARTIFACT), {
656
+ ...response,
657
+ generated_review_image_id: metadata.id,
658
+ output_sha256: response.output_sha256 || response.output_image_sha256 || metadata.sha256,
659
+ output_image_sha256: response.output_image_sha256 || response.output_sha256 || metadata.sha256
660
+ });
661
+ }
662
+ else if (opts.realGenerated === true) {
517
663
  await writeJsonAtomic(path.join(dir, IMAGE_UX_REVIEW_GPT_IMAGE_2_RESPONSE_ARTIFACT), {
518
664
  schema: 'sks.image-ux-gpt-image-2-response.v1',
519
665
  created_at: nowIso(),
@@ -526,6 +672,7 @@ async function attachGeneratedReviewImage(root, dir, contract, imagePath, opts =
526
672
  output_image_sha256: metadata.sha256,
527
673
  output_sha256: metadata.sha256,
528
674
  output_id: metadata.output_id || null,
675
+ generated_review_image_id: metadata.id,
529
676
  output_source: 'manual_attach',
530
677
  dimensions: { width: metadata.width, height: metadata.height, format: metadata.format },
531
678
  local_only: true
@@ -582,7 +729,9 @@ async function readImagegenResponse(dir) {
582
729
  }
583
730
  async function enforceImageUxRuntimeGate(dir, gate = {}, opts = {}) {
584
731
  const inventory = await readJson(path.join(dir, IMAGE_UX_REVIEW_SCREEN_INVENTORY_ARTIFACT), null);
732
+ const generatedLedger = await readJson(path.join(dir, IMAGE_UX_REVIEW_GENERATED_REVIEW_LEDGER_ARTIFACT), null);
585
733
  const issueLedger = await readJson(path.join(dir, IMAGE_UX_REVIEW_ISSUE_LEDGER_ARTIFACT), null);
734
+ const extractionReport = await readJson(path.join(dir, IMAGE_UX_REVIEW_CALLOUT_EXTRACTION_REPORT_ARTIFACT), null);
586
735
  const response = await readJson(path.join(dir, IMAGE_UX_REVIEW_GPT_IMAGE_2_RESPONSE_ARTIFACT), null);
587
736
  const blockers = new Set(Array.isArray(gate?.blockers) ? gate.blockers.map(String) : []);
588
737
  const sourceScreens = Array.isArray(inventory?.source_screens) ? inventory.source_screens : [];
@@ -606,19 +755,34 @@ async function enforceImageUxRuntimeGate(dir, gate = {}, opts = {}) {
606
755
  const responseEvidence = await validateImagegenResponseEvidence(response, dir);
607
756
  for (const blocker of responseEvidence.blockers)
608
757
  blockers.add(blocker);
758
+ const bindingBlockers = validateGeneratedImageEvidenceBinding(response, generatedLedger, extractionReport, issueLedger);
759
+ for (const blocker of bindingBlockers)
760
+ blockers.add(blocker);
609
761
  const nextBlockers = [...blockers];
610
762
  const codexGenerated = responseEvidence.ok === true && gate?.gpt_image_2_callout_generated === true;
763
+ const sourceScreenshotMinResolutionPassed = sourceScreens.length > 0
764
+ && sourceScreens.every((screen) => Number(screen?.width || 0) >= 64 && Number(screen?.height || 0) >= 64);
765
+ const issueLedgerRealExtraction = issues.length > 0
766
+ && issues.every((issue) => issue?.extraction_provider !== 'mock_fixture' && issue?.source !== 'mock_fixture');
767
+ const reviewReportCompleted = gate?.review_report_completed === true
768
+ && codexGenerated
769
+ && bindingBlockers.length === 0
770
+ && sourceScreenshotMinResolutionPassed
771
+ && issueLedgerRealExtraction;
611
772
  const passed = gate?.passed === true && codexGenerated && nextBlockers.length === 0;
612
773
  return {
613
774
  ...gate,
614
775
  passed,
615
776
  ok: passed,
616
- status: passed ? 'passed' : 'blocked',
777
+ status: passed ? 'passed' : reviewReportCompleted ? 'review_completed' : 'blocked',
778
+ verified_level: passed ? gate?.verified_level || 'verified' : reviewReportCompleted ? 'verified_partial' : 'blocked',
779
+ review_report_completed: reviewReportCompleted,
780
+ completion_scope: reviewReportCompleted ? 'capture_imagegen_ocr_and_ux_report' : gate?.completion_scope || null,
617
781
  gpt_image_2_callout_generated: codexGenerated,
618
782
  generated_image_evidence: responseEvidence.ok === true,
619
783
  imagegen_response_evidence: responseEvidence,
620
- source_screenshot_min_resolution_passed: sourceScreens.length > 0 && sourceScreens.every((screen) => Number(screen?.width || 0) >= 64 && Number(screen?.height || 0) >= 64),
621
- issue_ledger_real_extraction: issues.length > 0 && issues.every((issue) => issue?.extraction_provider !== 'mock_fixture' && issue?.source !== 'mock_fixture'),
784
+ source_screenshot_min_resolution_passed: sourceScreenshotMinResolutionPassed,
785
+ issue_ledger_real_extraction: issueLedgerRealExtraction,
622
786
  blockers: nextBlockers
623
787
  };
624
788
  }
@@ -631,6 +795,9 @@ async function validateImagegenResponseEvidence(response = null, dir = process.c
631
795
  blockers.push('imagegen_response_schema_invalid');
632
796
  if (response.ok !== true || response.status !== 'generated')
633
797
  blockers.push(response.blocker || 'imagegen_response_not_generated');
798
+ if (response.image_output_partial_frame === true || response.payload_summary?.image_output_partial_frame === true) {
799
+ blockers.push('imagegen_response_partial_image_output');
800
+ }
634
801
  const evidenceClass = String(response.evidence_class || '');
635
802
  blockers.push(...imagegenEvidenceClassBlockers('imagegen_response', evidenceClass));
636
803
  const source = String(response.output_source || '');
@@ -638,6 +805,9 @@ async function validateImagegenResponseEvidence(response = null, dir = process.c
638
805
  blockers.push('imagegen_response_output_source_invalid');
639
806
  const outputPath = String(response.output_image_path || '');
640
807
  const expectedSha = String(response.output_sha256 || response.output_image_sha256 || '');
808
+ const generatedReviewImageId = String(response.generated_review_image_id || '');
809
+ if (!generatedReviewImageId)
810
+ blockers.push('imagegen_response_generated_review_image_id_missing');
641
811
  if (!outputPath)
642
812
  blockers.push('imagegen_response_output_path_missing');
643
813
  if (!expectedSha)
@@ -665,9 +835,36 @@ async function validateImagegenResponseEvidence(response = null, dir = process.c
665
835
  output_source: source || null,
666
836
  output_image_path: outputPath || null,
667
837
  output_sha256: expectedSha || null,
838
+ generated_review_image_id: generatedReviewImageId || null,
668
839
  blockers: [...new Set(blockers)]
669
840
  };
670
841
  }
842
+ function validateGeneratedImageEvidenceBinding(response, generatedLedger, extractionReport, issueLedger) {
843
+ if (!response || response.ok !== true || response.status !== 'generated')
844
+ return [];
845
+ const blockers = [];
846
+ const generatedId = String(response.generated_review_image_id || '');
847
+ const responseSha = String(response.output_sha256 || response.output_image_sha256 || '');
848
+ const images = Array.isArray(generatedLedger?.generated_review_images) ? generatedLedger.generated_review_images : [];
849
+ const image = images.find((row) => String(row?.id || '') === generatedId);
850
+ if (generatedId && !image)
851
+ blockers.push('imagegen_response_generated_review_image_id_not_in_ledger');
852
+ if (image && responseSha && String(image.sha256 || '') !== responseSha)
853
+ blockers.push('imagegen_response_generated_review_image_sha256_mismatch');
854
+ if (extractionReport) {
855
+ if (String(extractionReport.generated_image_id || '') !== generatedId)
856
+ blockers.push('callout_extraction_generated_image_id_mismatch');
857
+ if (responseSha && String(extractionReport.generated_image_sha256 || '') !== responseSha)
858
+ blockers.push('callout_extraction_generated_image_sha256_mismatch');
859
+ }
860
+ for (const issue of Array.isArray(issueLedger?.issues) ? issueLedger.issues : []) {
861
+ if (String(issue.generated_review_image_id || '') !== generatedId)
862
+ blockers.push(`issue_generated_image_id_mismatch:${issue.id || 'unknown'}`);
863
+ if (responseSha && String(issue.generated_image_sha256 || '') !== responseSha)
864
+ blockers.push(`issue_generated_image_sha256_mismatch:${issue.id || 'unknown'}`);
865
+ }
866
+ return [...new Set(blockers)];
867
+ }
671
868
  function rootFromMissionDir(dir) {
672
869
  const marker = `${path.sep}.sneakoscope${path.sep}missions${path.sep}`;
673
870
  const idx = String(dir).indexOf(marker);
@@ -689,7 +886,7 @@ async function finalizeImageUx(root, missionId, command, artifacts, opts = {}) {
689
886
  const artifactList = Object.entries(artifacts)
690
887
  .filter(([, value]) => value !== null && value !== undefined)
691
888
  .map(([key]) => IMAGE_UX_REVIEW_ARTIFACT_PATHS[key] || key);
692
- return maybeFinalizeRoute(root, {
889
+ const finalized = await maybeFinalizeRoute(root, {
693
890
  missionId,
694
891
  route: routeForCommand(command),
695
892
  gateFile: 'image-ux-review-gate.json',
@@ -705,6 +902,17 @@ async function finalizeImageUx(root, missionId, command, artifacts, opts = {}) {
705
902
  allowActiveWrongnessPartial: artifacts.gate?.reference_only === true,
706
903
  command: { cmd: opts.cmd || `sks ${command}`, status: artifacts.gate?.blockers?.length ? 1 : 0 }
707
904
  });
905
+ const proofIssues = Array.isArray(finalized?.validation?.issues)
906
+ ? finalized.validation.issues.map(String)
907
+ : [];
908
+ await closeWorkOrderLedgerForRouteResult(missionDir(root, missionId), {
909
+ ok: artifacts.gate?.passed === true && finalized?.ok === true,
910
+ blockers: [...new Set([
911
+ ...(Array.isArray(artifacts.gate?.blockers) ? artifacts.gate.blockers.map(String) : []),
912
+ ...proofIssues
913
+ ])]
914
+ });
915
+ return finalized;
708
916
  }
709
917
  async function ensureFixtureImageVoxelRelation(root, missionId, imageRel) {
710
918
  await ingestImage(root, imageRel, { missionId, source: 'mock_fixture_source', id: `${missionId}-source-screen` });
@@ -738,6 +946,10 @@ function promptForRun(command, args) {
738
946
  const source = readOption(args, '--image', null) || readOption(args, '--screenshot', null) || readOption(args, '--mission', null) || 'latest Codex Chrome Extension or native Computer Use screenshot';
739
947
  return `$${routeForCommand(command).replace(/^\$/, '')} ${source} with gpt-image-2 callouts${flag(args, '--fix') ? ', then fix the issues' : ''}`;
740
948
  }
949
+ function sourceImageFromContract(contract) {
950
+ const sources = contract?.answers?.IMAGE_UX_REVIEW_SOURCE_IMAGES;
951
+ return Array.isArray(sources) && sources.length ? String(sources[0]) : null;
952
+ }
741
953
  function missingMission(args) {
742
954
  const result = { schema: 'sks.image-ux-review-status.v2', ok: false, status: 'missing_mission' };
743
955
  if (flag(args, '--json'))