sneakoscope 4.8.7 → 5.3.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 (261) hide show
  1. package/README.md +51 -726
  2. package/config/bench-baseline.json +7 -0
  3. package/crates/sks-core/Cargo.lock +1 -1
  4. package/crates/sks-core/Cargo.toml +1 -1
  5. package/crates/sks-core/src/main.rs +1 -1
  6. package/dist/bin/install.js +35 -0
  7. package/dist/bin/sks.js +1 -1
  8. package/dist/cli/cli-theme.js +51 -0
  9. package/dist/cli/command-registry.js +15 -4
  10. package/dist/cli/help-fast.js +18 -8
  11. package/dist/cli/insane-search-command.js +36 -8
  12. package/dist/cli/install-helpers.js +32 -0
  13. package/dist/commands/codex-lb.js +2 -0
  14. package/dist/commands/doctor.js +94 -9
  15. package/dist/commands/proof.js +6 -3
  16. package/dist/config/skills-manifest.json +82 -54
  17. package/dist/core/agents/agent-conflict-graph.js +5 -0
  18. package/dist/core/agents/agent-lease.js +5 -0
  19. package/dist/core/agents/agent-ledger-schemas.js +1 -0
  20. package/dist/core/agents/agent-lifecycle.js +5 -3
  21. package/dist/core/agents/agent-orchestrator.js +268 -62
  22. package/dist/core/agents/agent-output-validator.js +14 -3
  23. package/dist/core/agents/agent-patch-queue-store.js +95 -2
  24. package/dist/core/agents/agent-patch-queue.js +6 -3
  25. package/dist/core/agents/agent-patch-schema.js +13 -0
  26. package/dist/core/agents/agent-proof-evidence.js +6 -1
  27. package/dist/core/agents/agent-runner-fake.js +40 -0
  28. package/dist/core/agents/agent-worker-pipeline.js +36 -2
  29. package/dist/core/agents/codex-exec-worker-adapter.js +2 -0
  30. package/dist/core/agents/native-cli-worker.js +0 -1
  31. package/dist/core/agents/native-worker-backend-router.js +58 -1
  32. package/dist/core/codex-app/sks-menubar.js +477 -132
  33. package/dist/core/codex-app.js +1 -0
  34. package/dist/core/codex-control/codex-sdk-config-policy.js +2 -1
  35. package/dist/core/codex-control/codex-sdk-env-policy.js +1 -1
  36. package/dist/core/codex-control/schemas/agent-worker-result.schema.js +12 -3
  37. package/dist/core/codex-hooks/codex-hook-managed-install.js +5 -0
  38. package/dist/core/codex-hooks/codex-hook-state-writer.js +70 -11
  39. package/dist/core/codex-hooks/codex-hook-trust-doctor.js +2 -2
  40. package/dist/core/codex-lb/codex-lb-env.js +20 -1
  41. package/dist/core/commands/basic-cli.js +18 -1
  42. package/dist/core/commands/check-command.js +37 -5
  43. package/dist/core/commands/computer-use-command.js +62 -7
  44. package/dist/core/commands/db-command.js +31 -5
  45. package/dist/core/commands/fast-mode-command.js +3 -0
  46. package/dist/core/commands/gate-result-contract.js +43 -0
  47. package/dist/core/commands/gates-command.js +6 -1
  48. package/dist/core/commands/gc-command.js +29 -2
  49. package/dist/core/commands/goal-command.js +9 -2
  50. package/dist/core/commands/gx-command.js +79 -7
  51. package/dist/core/commands/image-ux-review-command.js +188 -13
  52. package/dist/core/commands/mad-db-command.js +85 -176
  53. package/dist/core/commands/mad-sks-command.js +230 -29
  54. package/dist/core/commands/menubar-command.js +24 -2
  55. package/dist/core/commands/naruto-command.js +64 -10
  56. package/dist/core/commands/plan-command.js +76 -0
  57. package/dist/core/commands/ppt-command.js +159 -24
  58. package/dist/core/commands/qa-loop-command.js +6 -2
  59. package/dist/core/commands/release-command.js +55 -2
  60. package/dist/core/commands/research-command.js +1 -1
  61. package/dist/core/commands/review-command.js +217 -0
  62. package/dist/core/commands/route-success-helpers.js +59 -0
  63. package/dist/core/commands/run-command.js +19 -7
  64. package/dist/core/commands/seo-command.js +49 -1
  65. package/dist/core/commands/ui-command.js +161 -0
  66. package/dist/core/commands/uninstall-command.js +15 -1
  67. package/dist/core/db-safety.js +2 -2
  68. package/dist/core/doctor/doctor-dirty-planner.js +51 -7
  69. package/dist/core/doctor/doctor-transaction.js +8 -0
  70. package/dist/core/doctor/doctor-zellij-repair.js +1 -0
  71. package/dist/core/doctor/imagegen-repair.js +161 -0
  72. package/dist/core/feature-fixture-runner.js +2 -2
  73. package/dist/core/feature-fixtures.js +58 -37
  74. package/dist/core/feature-registry.js +102 -2
  75. package/dist/core/fsx.js +39 -6
  76. package/dist/core/image-ux-review/imagegen-adapter.js +48 -11
  77. package/dist/core/image-ux-review.js +16 -0
  78. package/dist/core/imagegen/imagegen-capability.js +11 -5
  79. package/dist/core/imagegen/require-imagegen.js +57 -0
  80. package/dist/core/init/skills.js +10 -5
  81. package/dist/core/mad-db/mad-db-coordinator.js +94 -18
  82. package/dist/core/mad-db/mad-db-policy.js +12 -10
  83. package/dist/core/mad-sks/executors/executor-base.js +8 -2
  84. package/dist/core/mad-sks/executors/index.js +4 -0
  85. package/dist/core/mad-sks/executors/sql-plane-executor.js +194 -0
  86. package/dist/core/naruto/naruto-active-pool.js +15 -4
  87. package/dist/core/naruto/naruto-real-worker-child.js +11 -1
  88. package/dist/core/naruto/naruto-role-policy.js +3 -0
  89. package/dist/core/naruto/naruto-task-hints.js +10 -0
  90. package/dist/core/naruto/naruto-work-graph.js +8 -2
  91. package/dist/core/naruto/naruto-work-item.js +6 -0
  92. package/dist/core/naruto/solution-tournament.js +101 -0
  93. package/dist/core/permission-gates.js +30 -0
  94. package/dist/core/pipeline-internals/runtime-core.js +11 -3
  95. package/dist/core/pipeline-internals/runtime-gates.js +27 -0
  96. package/dist/core/ppt-review/index.js +6 -1
  97. package/dist/core/ppt-review/slide-imagegen-review.js +75 -10
  98. package/dist/core/ppt.js +70 -2
  99. package/dist/core/proof/auto-finalize.js +70 -11
  100. package/dist/core/proof/proof-schema.js +2 -0
  101. package/dist/core/proof/route-adapter.js +5 -1
  102. package/dist/core/proof/route-finalizer.js +8 -5
  103. package/dist/core/proof/selftest-proof-fixtures.js +18 -5
  104. package/dist/core/proof/validation.js +2 -0
  105. package/dist/core/provider/model-router.js +53 -0
  106. package/dist/core/release/release-gate-node.js +3 -0
  107. package/dist/core/release/release-gate-resource-governor.js +1 -0
  108. package/dist/core/routes/constants.js +1 -1
  109. package/dist/core/routes.js +65 -18
  110. package/dist/core/safety/mutation-guard.js +5 -1
  111. package/dist/core/stop-gate/gate-evaluator.js +102 -0
  112. package/dist/core/stop-gate/stop-gate-check.js +16 -1
  113. package/dist/core/stop-gate/stop-gate-writer.js +4 -0
  114. package/dist/core/triwiki/agents-md-projector.js +184 -0
  115. package/dist/core/triwiki-wrongness/wrongness-schema.js +3 -1
  116. package/dist/core/trust-kernel/trust-kernel-schema.js +1 -0
  117. package/dist/core/trust-kernel/trust-report.js +1 -1
  118. package/dist/core/trust-kernel/trust-status.js +2 -0
  119. package/dist/core/ui/dashboard-html.js +111 -0
  120. package/dist/core/update/update-migration-state.js +386 -15
  121. package/dist/core/update-check.js +178 -81
  122. package/dist/core/verification/diff-quality.js +100 -0
  123. package/dist/core/verification/impact-scan.js +164 -0
  124. package/dist/core/verification/machine-feedback.js +146 -0
  125. package/dist/core/verification/mistake-rule-compiler.js +195 -0
  126. package/dist/core/version.js +1 -1
  127. package/dist/core/zellij/zellij-self-heal.js +9 -6
  128. package/dist/core/zellij/zellij-slot-telemetry.js +1 -1
  129. package/dist/scripts/check-feature-quality.js +2 -2
  130. package/dist/scripts/cli-output-consistency-check.js +57 -0
  131. package/dist/scripts/coding-bench-check.js +136 -0
  132. package/dist/scripts/doctor-imagegen-repair-check.js +62 -0
  133. package/dist/scripts/ensure-bin-executable.js +11 -3
  134. package/dist/scripts/harness-benchmark-check.js +104 -0
  135. package/dist/scripts/legacy-update-e2e-check.js +228 -0
  136. package/dist/scripts/mad-db-command-check.js +11 -8
  137. package/dist/scripts/mad-db-route-identity-check.js +8 -6
  138. package/dist/scripts/mad-db-safety-conflict-matrix-check.js +31 -8
  139. package/dist/scripts/mad-db-skill-policy-snapshot-check.js +4 -5
  140. package/dist/scripts/naruto-real-active-pool-runtime-check.js +26 -1
  141. package/dist/scripts/ppt-real-imagegen-wiring-check.js +5 -1
  142. package/dist/scripts/proof-root-cause-policy-check.js +2 -0
  143. package/dist/scripts/release-dag-full-coverage-check.js +13 -4
  144. package/dist/scripts/release-gate-dag-runner.js +14 -0
  145. package/dist/scripts/release-gate-existence-audit.js +2 -1
  146. package/dist/scripts/release-gate-planner.js +2 -1
  147. package/dist/scripts/release-gate-script-parity-check.js +45 -17
  148. package/dist/scripts/release-metadata-1-19-check.js +2 -1
  149. package/dist/scripts/seo-geo-feature-fixture-quality-check.js +1 -1
  150. package/dist/scripts/sks-1-11-gate-lib.js +8 -6
  151. package/dist/scripts/sks-menubar-install-check.js +57 -20
  152. package/dist/scripts/ux-review-real-loop-fixture-check.js +9 -4
  153. package/dist/scripts/ux-review-run-wires-imagegen-check.js +4 -0
  154. package/dist/scripts/zellij-slot-telemetry-runtime-check.js +1 -0
  155. package/docs/assets/sneakoscope-architecture-pipeline.jpg +0 -0
  156. package/docs/demo.tape +28 -0
  157. package/package.json +31 -24
  158. package/schemas/codex/completion-proof.schema.json +3 -2
  159. package/schemas/release/release-gate-node.schema.json +2 -1
  160. package/dist/scripts/agent-backfill-route-blackbox.js +0 -5
  161. package/dist/scripts/agent-dynamic-pool-route-blackbox.js +0 -5
  162. package/dist/scripts/agent-parallel-write-blackbox.js +0 -56
  163. package/dist/scripts/agent-patch-swarm-route-blackbox.js +0 -10
  164. package/dist/scripts/agent-route-blackbox-lib.js +0 -132
  165. package/dist/scripts/blackbox-command-import-smoke.js +0 -143
  166. package/dist/scripts/blackbox-global-shim.js +0 -77
  167. package/dist/scripts/blackbox-matrix.js +0 -70
  168. package/dist/scripts/blackbox-npx-one-shot.js +0 -69
  169. package/dist/scripts/blackbox-pack-install.js +0 -174
  170. package/dist/scripts/brand-neutrality-zero-leakage-blackbox.js +0 -4
  171. package/dist/scripts/build-once-runner-blackbox.js +0 -34
  172. package/dist/scripts/codex-0140-integration-blackbox.js +0 -13
  173. package/dist/scripts/codex-agent-type-blackbox.js +0 -4
  174. package/dist/scripts/codex-app-harness-blackbox.js +0 -4
  175. package/dist/scripts/codex-app-skill-agent-blackbox.js +0 -4
  176. package/dist/scripts/codex-hook-approval-blackbox.js +0 -4
  177. package/dist/scripts/codex-init-deep-directory-local-blackbox.js +0 -4
  178. package/dist/scripts/codex-native-feature-broker-blackbox.js +0 -4
  179. package/dist/scripts/codex-native-pattern-analysis-blackbox.js +0 -4
  180. package/dist/scripts/codex-native-read-repair-split-blackbox.js +0 -55
  181. package/dist/scripts/codex-native-reference-cache-blackbox.js +0 -41
  182. package/dist/scripts/core-skill-integrity-blackbox.js +0 -32
  183. package/dist/scripts/dfix-fast-blackbox-check.js +0 -37
  184. package/dist/scripts/dfix-parallel-write-blackbox.js +0 -48
  185. package/dist/scripts/dfix-patch-swarm-route-blackbox.js +0 -10
  186. package/dist/scripts/doctor-dirty-repair-blackbox.js +0 -22
  187. package/dist/scripts/doctor-dirty-semantic-blackbox.js +0 -8
  188. package/dist/scripts/doctor-native-capability-repair-blackbox.js +0 -39
  189. package/dist/scripts/doctor-transaction-engine-blackbox.js +0 -28
  190. package/dist/scripts/doctor-zellij-fix-blackbox.js +0 -4
  191. package/dist/scripts/doctor-zellij-no-homebrew-blackbox.js +0 -4
  192. package/dist/scripts/doctor-zellij-upgrade-blackbox.js +0 -4
  193. package/dist/scripts/gate-pack-runner-blackbox.js +0 -27
  194. package/dist/scripts/gate-pack-v2-blackbox.js +0 -18
  195. package/dist/scripts/geo-cli-blackbox-check.js +0 -18
  196. package/dist/scripts/loop-collision-blackbox.js +0 -3
  197. package/dist/scripts/loop-concurrency-oversubscription-blackbox.js +0 -3
  198. package/dist/scripts/loop-fixture-production-misuse-blackbox.js +0 -3
  199. package/dist/scripts/loop-kill-interrupt-real-blackbox.js +0 -3
  200. package/dist/scripts/loop-merge-strategy-blackbox.js +0 -3
  201. package/dist/scripts/loop-mesh-production-e2e-blackbox.js +0 -3
  202. package/dist/scripts/loop-side-effect-blackbox.js +0 -3
  203. package/dist/scripts/mad-db-operation-lifecycle-blackbox.js +0 -29
  204. package/dist/scripts/mad-sks-actual-executor-blackbox.js +0 -5
  205. package/dist/scripts/mad-zellij-headless-fallback-blackbox.js +0 -4
  206. package/dist/scripts/mad-zellij-self-heal-blackbox.js +0 -4
  207. package/dist/scripts/naruto-loop-mesh-blackbox.js +0 -3
  208. package/dist/scripts/naruto-real-parallelism-blackbox.js +0 -307
  209. package/dist/scripts/naruto-worktree-coding-blackbox.js +0 -29
  210. package/dist/scripts/parallel-runtime-real-blackbox.js +0 -44
  211. package/dist/scripts/pipeline-codex-native-doctor-mad-routing-real-blackbox.js +0 -77
  212. package/dist/scripts/pipeline-codex-native-e2e-blackbox.js +0 -13
  213. package/dist/scripts/pipeline-codex-native-image-routing-real-blackbox.js +0 -50
  214. package/dist/scripts/pipeline-codex-native-loop-routing-real-blackbox.js +0 -74
  215. package/dist/scripts/pipeline-codex-native-qa-routing-real-blackbox.js +0 -51
  216. package/dist/scripts/pipeline-codex-native-research-routing-real-blackbox.js +0 -45
  217. package/dist/scripts/pipeline-execution-profile-routing-blackbox.js +0 -4
  218. package/dist/scripts/postinstall-global-doctor-blackbox.js +0 -12
  219. package/dist/scripts/ppt-full-e2e-blackbox-check.js +0 -109
  220. package/dist/scripts/ppt-imagegen-blackbox-check.js +0 -46
  221. package/dist/scripts/qa-backfill-route-blackbox.js +0 -5
  222. package/dist/scripts/qa-patch-swarm-route-blackbox.js +0 -10
  223. package/dist/scripts/release-full-parallelism-blackbox.js +0 -41
  224. package/dist/scripts/release-triwiki-first-runner-blackbox.js +0 -48
  225. package/dist/scripts/release-wiring-3110-blackbox.js +0 -27
  226. package/dist/scripts/release-wiring-3112-blackbox.js +0 -17
  227. package/dist/scripts/release-wiring-3113-blackbox.js +0 -17
  228. package/dist/scripts/research-backfill-route-blackbox.js +0 -5
  229. package/dist/scripts/research-final-reviewer-blackbox.js +0 -70
  230. package/dist/scripts/research-stage-cycle-runtime-blackbox.js +0 -40
  231. package/dist/scripts/research-synthesis-writer-blackbox.js +0 -24
  232. package/dist/scripts/route-blackbox-realism-check.js +0 -21
  233. package/dist/scripts/scheduler-resource-claim-blackbox.js +0 -24
  234. package/dist/scripts/seo-cli-blackbox-check.js +0 -18
  235. package/dist/scripts/sks-3110-all-feature-regression-blackbox.js +0 -116
  236. package/dist/scripts/sks-3112-all-feature-regression-blackbox.js +0 -29
  237. package/dist/scripts/sks-3113-all-feature-regression-blackbox.js +0 -17
  238. package/dist/scripts/sks-400-all-feature-regression-blackbox.js +0 -21
  239. package/dist/scripts/sks-400-extreme-parallel-blackbox.js +0 -8
  240. package/dist/scripts/sks-400-five-minute-blackbox.js +0 -9
  241. package/dist/scripts/sks-400-legacy-purge-blackbox.js +0 -8
  242. package/dist/scripts/sks-401-all-feature-regression-blackbox.js +0 -46
  243. package/dist/scripts/sks-401-five-minute-actual-blackbox.js +0 -23
  244. package/dist/scripts/sks-402-all-feature-regression-blackbox.js +0 -9
  245. package/dist/scripts/sks-402-five-minute-real-blackbox.js +0 -22
  246. package/dist/scripts/sksd-warm-cache-blackbox.js +0 -12
  247. package/dist/scripts/team-backfill-route-blackbox.js +0 -5
  248. package/dist/scripts/team-parallel-write-blackbox.js +0 -55
  249. package/dist/scripts/team-patch-swarm-route-blackbox.js +0 -10
  250. package/dist/scripts/triwiki-affected-graph-blackbox.js +0 -28
  251. package/dist/scripts/triwiki-proof-bank-blackbox.js +0 -30
  252. package/dist/scripts/triwiki-proof-bank-lock-blackbox.js +0 -7
  253. package/dist/scripts/ux-patch-swarm-route-blackbox.js +0 -10
  254. package/dist/scripts/ux-review-imagegen-blackbox-check.js +0 -67
  255. package/dist/scripts/zellij-initial-main-only-blackbox.js +0 -28
  256. package/dist/scripts/zellij-pane-lock-concurrency-blackbox.js +0 -80
  257. package/dist/scripts/zellij-pane-lock-open-worker-integration-blackbox.js +0 -137
  258. package/dist/scripts/zellij-self-heal-typed-blackbox.js +0 -4
  259. package/dist/scripts/zellij-slot-telemetry-real-blackbox.js +0 -20
  260. package/dist/scripts/zellij-stacked-fallback-integration-blackbox.js +0 -81
  261. package/dist/scripts/zellij-worker-pane-real-ui-blackbox.js +0 -202
@@ -1,7 +1,27 @@
1
- import { dirSize, formatBytes, packageRoot, sksRoot } from '../fsx.js';
1
+ import { dirSize, formatBytes, packageRoot, projectRoot, sksRoot } from '../fsx.js';
2
2
  import { enforceRetention, storageReport } from '../retention.js';
3
3
  import { flag } from './command-utils.js';
4
- export async function memoryCommand(_sub, args = []) {
4
+ import { projectTriwikiToAgentsMd } from '../triwiki/agents-md-projector.js';
5
+ import { compileMistakeRules } from '../verification/mistake-rule-compiler.js';
6
+ export async function memoryCommand(sub, args = []) {
7
+ const action = String(sub || '').toLowerCase();
8
+ if (['build', 'project', 'agents', 'agents-md'].includes(action)) {
9
+ const root = await projectRoot();
10
+ const [result, rules] = await Promise.all([
11
+ projectTriwikiToAgentsMd(root, { maxLocalFiles: Number(readOption(args, '--max-local-files', 8)) }),
12
+ compileMistakeRules(root).catch((err) => ({ compiled: [], skipped: [`compile_failed:${err instanceof Error ? err.message : String(err)}`] }))
13
+ ]);
14
+ const output = { ...result, mistake_rules: rules };
15
+ if (flag(args, '--json'))
16
+ return console.log(JSON.stringify(output, null, 2));
17
+ console.log(`SKS memory build: ${result.ok ? 'ok' : result.reason}`);
18
+ for (const file of result.written)
19
+ console.log(`- ${file}`);
20
+ console.log(`- mistake rules compiled: ${rules.compiled.length}, skipped: ${rules.skipped.length}`);
21
+ if (!result.ok)
22
+ process.exitCode = 1;
23
+ return output;
24
+ }
5
25
  return gcCommand(args || []);
6
26
  }
7
27
  export async function gcCommand(args = []) {
@@ -16,6 +36,13 @@ export async function gcCommand(args = []) {
16
36
  for (const a of res.actions.slice(0, 20))
17
37
  console.log(`- ${a.action} ${a.path || a.mission || ''} ${a.bytes ? formatBytes(a.bytes) : ''}`);
18
38
  }
39
+ function readOption(args = [], name, fallback = null) {
40
+ const index = args.indexOf(name);
41
+ if (index >= 0 && args[index + 1] && !String(args[index + 1]).startsWith('--'))
42
+ return args[index + 1];
43
+ const prefixed = args.find((arg) => String(arg).startsWith(`${name}=`));
44
+ return prefixed ? String(prefixed).slice(name.length + 1) : fallback;
45
+ }
19
46
  export async function statsCommand(args = []) {
20
47
  const root = await sksRoot();
21
48
  const report = await storageReport(root);
@@ -6,6 +6,7 @@ import { GOAL_BRIDGE_ARTIFACT, GOAL_WORKFLOW_ARTIFACT, updateGoalWorkflow, write
6
6
  import { flag, promptOf, resolveMissionId } from './command-utils.js';
7
7
  import { compileGoalToLoopPlan } from '../loops/goal-to-loop-compat.js';
8
8
  import { runLoopPlan } from '../loops/loop-runtime.js';
9
+ import { evaluateLocalGate } from './route-success-helpers.js';
9
10
  export async function goalCommand(sub, args = []) {
10
11
  const known = new Set(['create', 'pause', 'resume', 'clear', 'status', 'help', '--help', '-h']);
11
12
  const action = known.has(sub) ? sub : 'create';
@@ -39,10 +40,16 @@ async function goalCreate(args) {
39
40
  const workflow = await writeGoalWorkflow(dir, mission, { action: 'create', prompt });
40
41
  const plan = await compileGoalToLoopPlan({ root, missionId: id, goalText: prompt, legacyGoalOptions: { native_goal: workflow.native_goal } });
41
42
  const result = await runLoopPlan({ root, plan, parallelism: 'balanced' });
42
- await setCurrent(root, { mission_id: id, mode: 'GOAL', route: 'Goal', route_command: '$Goal', phase: result.ok ? 'GOAL_LOOP_COMPLETED' : 'GOAL_LOOP_BLOCKED', questions_allowed: true, implementation_allowed: true, native_goal: workflow.native_goal, stop_gate: 'loop-graph-proof.json' }, { replace: true });
43
+ const gate = await evaluateLocalGate({ root, missionId: id, gateFile: 'loop-graph-proof.json' });
44
+ const ok = result.ok === true && gate.ok === true;
45
+ await setCurrent(root, { mission_id: id, mode: 'GOAL', route: 'Goal', route_command: '$Goal', phase: ok ? 'GOAL_LOOP_COMPLETED' : 'GOAL_LOOP_BLOCKED', questions_allowed: true, implementation_allowed: true, native_goal: workflow.native_goal, stop_gate: 'loop-graph-proof.json', stop_gate_blockers: gate.blockers }, { replace: true });
46
+ if (!ok)
47
+ process.exitCode = 1;
43
48
  if (flag(args, '--json'))
44
- return console.log(JSON.stringify({ schema: 'sks.goal-create.v1', ok: result.ok, mission_id: id, workflow, loop_plan: plan, loop_result: result }, null, 2));
49
+ return console.log(JSON.stringify({ schema: 'sks.goal-create.v1', ok, mission_id: id, workflow, loop_plan: plan, loop_result: result, gate }, null, 2));
45
50
  console.log(`Goal compiled to Loop Graph: ${id}`);
51
+ if (!ok)
52
+ console.log(`Gate blocked: ${gate.blockers.join(', ') || 'loop_result_not_ok'}`);
46
53
  console.log('Use `sks loop status latest` to inspect.');
47
54
  console.log(`Artifact: ${path.relative(root, path.join(dir, GOAL_WORKFLOW_ARTIFACT))}`);
48
55
  console.log(`Bridge: ${path.relative(root, path.join(dir, GOAL_BRIDGE_ARTIFACT))}`);
@@ -1,9 +1,10 @@
1
1
  import path from 'node:path';
2
- import { sksRoot, writeJsonAtomic } from '../fsx.js';
2
+ import { readJson, sksRoot, writeJsonAtomic } from '../fsx.js';
3
3
  import { createMission } from '../mission.js';
4
4
  import { driftCartridge, renderCartridge, snapshotCartridge, validateCartridge } from '../gx-renderer.js';
5
5
  import { maybeFinalizeRoute } from '../proof/auto-finalize.js';
6
6
  import { flag, positionalArgs, readFlagValue } from './command-utils.js';
7
+ import { context7EvidenceStatus } from './route-success-helpers.js';
7
8
  export async function gxCommand(sub, args = []) {
8
9
  const root = await sksRoot();
9
10
  const name = cartridgeName(args);
@@ -36,8 +37,38 @@ export async function gxCommand(sub, args = []) {
36
37
  if (name === 'fixture' && flag(args, '--mock'))
37
38
  return gxValidateFixture(root, args);
38
39
  const validation = await validateCartridge(dir);
39
- console.log(JSON.stringify(validation, null, 2));
40
- process.exitCode = validation.ok ? 0 : 2;
40
+ const vgraph = await readJson(path.join(dir, 'vgraph.json'), null);
41
+ const context7 = await context7EvidenceStatus(root);
42
+ const schemaBlockers = validateGxVGraph(vgraph);
43
+ const blockers = [
44
+ ...(Array.isArray(validation.issues) ? validation.issues : []),
45
+ ...schemaBlockers,
46
+ ...(context7.ok ? [] : [context7.blocker])
47
+ ].filter(Boolean);
48
+ const result = {
49
+ ...validation,
50
+ ok: validation.ok === true && blockers.length === 0,
51
+ status: validation.ok === true && blockers.length === 0 ? 'pass' : 'blocked',
52
+ context7_policy: context7.policy,
53
+ context7_evidence: context7.evidence,
54
+ vgraph_schema_valid: schemaBlockers.length === 0,
55
+ blockers
56
+ };
57
+ const gate = {
58
+ schema: 'sks.gx-gate.v1',
59
+ schema_version: 1,
60
+ passed: result.ok,
61
+ ok: result.ok,
62
+ status: result.ok ? 'pass' : 'blocked',
63
+ gx_validation: 'gx-validation.json',
64
+ context7_policy: context7.policy,
65
+ vgraph_schema_valid: schemaBlockers.length === 0,
66
+ blockers
67
+ };
68
+ await writeJsonAtomic(path.join(dir, 'gx-validation.json'), result);
69
+ await writeJsonAtomic(path.join(dir, 'gx-gate.json'), gate);
70
+ console.log(JSON.stringify(result, null, 2));
71
+ process.exitCode = result.ok ? 0 : 2;
41
72
  return;
42
73
  }
43
74
  if (sub === 'drift') {
@@ -56,12 +87,53 @@ export async function gxCommand(sub, args = []) {
56
87
  }
57
88
  async function gxValidateFixture(root, args) {
58
89
  const { id, dir } = await createMission(root, { mode: 'gx', prompt: 'GX validate fixture' });
59
- const validation = { schema: 'sks.gx-validation.v1', ok: true, status: 'pass', fixture: true, cartridge: 'fixture' };
90
+ const validation = {
91
+ schema: 'sks.gx-validation.v1',
92
+ ok: false,
93
+ status: 'blocked',
94
+ execution_class: 'mock_fixture',
95
+ fixture: true,
96
+ cartridge: 'fixture',
97
+ blockers: ['gx_fixture_mode_cannot_claim_real']
98
+ };
60
99
  await writeJsonAtomic(path.join(dir, 'gx-validation.json'), validation);
61
- const gate = { schema_version: 1, passed: true, ok: true, gx_validation: 'gx-validation.json', visual_claim: true };
100
+ const gate = {
101
+ schema: 'sks.gx-gate.v1',
102
+ schema_version: 1,
103
+ passed: false,
104
+ ok: false,
105
+ status: 'blocked',
106
+ execution_class: 'mock_fixture',
107
+ gx_validation: 'gx-validation.json',
108
+ visual_claim: false,
109
+ blockers: ['gx_fixture_mode_cannot_claim_real']
110
+ };
62
111
  await writeJsonAtomic(path.join(dir, 'gx-gate.json'), gate);
63
- const proof = await maybeFinalizeRoute(root, { missionId: id, route: '$GX', gateFile: 'gx-gate.json', gate, mock: true, visual: true, artifacts: ['gx-validation.json', 'image-voxel-ledger.json', 'completion-proof.json'], claims: [{ id: 'gx-validate-fixture', status: 'verified_partial' }], command: { cmd: 'sks gx validate fixture --mock', status: 0 } });
64
- console.log(JSON.stringify({ schema: 'sks.gx-validate-fixture.v1', ok: proof.ok, mission_id: id, validation, proof: proof.validation }, null, 2));
112
+ const proof = await maybeFinalizeRoute(root, { missionId: id, route: '$GX', gateFile: 'gx-gate.json', gate, mock: true, visual: true, statusHint: 'blocked', blockers: gate.blockers, artifacts: ['gx-validation.json', 'image-voxel-ledger.json', 'completion-proof.json'], claims: [{ id: 'gx-validate-fixture', status: 'blocked' }], command: { cmd: 'sks gx validate fixture --mock', status: 1 } });
113
+ const result = { schema: 'sks.gx-validate-fixture.v1', ok: false, mission_id: id, validation, gate, proof: proof.validation };
114
+ console.log(JSON.stringify(result, null, 2));
115
+ process.exitCode = 1;
116
+ }
117
+ function validateGxVGraph(vgraph) {
118
+ const blockers = [];
119
+ if (!vgraph || typeof vgraph !== 'object')
120
+ return ['vgraph_missing_or_invalid_json'];
121
+ if (!String(vgraph.id || vgraph.name || '').trim())
122
+ blockers.push('vgraph_id_missing');
123
+ if (!Array.isArray(vgraph.nodes) || vgraph.nodes.length === 0)
124
+ blockers.push('vgraph_nodes_missing');
125
+ if (!Array.isArray(vgraph.edges))
126
+ blockers.push('vgraph_edges_missing');
127
+ const ids = new Set((Array.isArray(vgraph.nodes) ? vgraph.nodes : []).map((node) => String(node?.id || '').trim()).filter(Boolean));
128
+ if (ids.size !== (Array.isArray(vgraph.nodes) ? vgraph.nodes.length : 0))
129
+ blockers.push('vgraph_node_ids_missing_or_duplicate');
130
+ for (const edge of Array.isArray(vgraph.edges) ? vgraph.edges : []) {
131
+ const from = String(edge?.from || edge?.source || '').trim();
132
+ const to = String(edge?.to || edge?.target || '').trim();
133
+ if (!from || !to || !ids.has(from) || !ids.has(to))
134
+ blockers.push('vgraph_edge_endpoint_invalid');
135
+ }
136
+ return [...new Set(blockers)];
65
137
  }
66
138
  function cartridgeName(args, fallback = 'architecture-atlas') {
67
139
  const raw = positionalArgs(args)[0] || fallback;
@@ -12,6 +12,7 @@ import { addImageRelation, ingestImage } from '../wiki-image/image-voxel-ledger.
12
12
  import { sha256File, imageDimensions } from '../wiki-image/image-hash.js';
13
13
  import { writeRouteCollaborationArtifacts } from '../agents/route-collaboration-ledger.js';
14
14
  import { codexChromeExtensionStatus } from '../codex-app.js';
15
+ import { requireCodexImagegen } from '../imagegen/require-imagegen.js';
15
16
  const ONE_BY_ONE_PNG_BASE64 = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMB/axX7V8AAAAASUVORK5CYII=';
16
17
  const IMAGE_UX_REVIEW_ARTIFACT_PATHS = {
17
18
  policy: IMAGE_UX_REVIEW_POLICY_ARTIFACT,
@@ -107,6 +108,32 @@ async function runImageUxReview(root, command, args = []) {
107
108
  console.error('UX Review blocked: screenshot_required');
108
109
  return result;
109
110
  }
111
+ if (!flag(args, '--mock') && !generatedImage) {
112
+ const imagegenRequired = await requireCodexImagegen(root, { autoRepair: true, applyRepair: true });
113
+ if (!imagegenRequired.ok) {
114
+ const result = {
115
+ schema: 'sks.image-ux-review-run.v1',
116
+ ok: false,
117
+ status: 'blocked',
118
+ blocker: 'codex_imagegen_unavailable',
119
+ imagegen_required: imagegenRequired,
120
+ gate: {
121
+ schema: 'sks.image-ux-review-gate.v2',
122
+ passed: false,
123
+ status: 'blocked',
124
+ blockers: ['codex_imagegen_unavailable'],
125
+ generated_image_evidence: false
126
+ }
127
+ };
128
+ process.exitCode = 1;
129
+ if (flag(args, '--json'))
130
+ return printJson(result);
131
+ console.error('UX Review blocked: Codex App imagegen/gpt-image-2 is unavailable.');
132
+ for (const action of imagegenRequired.blocker?.next_actions || [])
133
+ console.error(`- ${action}`);
134
+ return result;
135
+ }
136
+ }
110
137
  const { id, dir, mission } = await createMission(root, { mode: 'image-ux-review', prompt: promptForRun(command, args) });
111
138
  const sourceRel = imagePath ? await stageSourceImage(root, dir, imagePath) : null;
112
139
  const contract = {
@@ -148,12 +175,23 @@ async function runImageUxReview(root, command, args = []) {
148
175
  }
149
176
  });
150
177
  if (result.generated_image_path) {
151
- const fakeGenerated = result.provider === 'fake_imagegen_adapter';
152
178
  if (result.request_artifact)
153
179
  await fsp.copyFile(result.request_artifact, path.join(dir, IMAGE_UX_REVIEW_GPT_IMAGE_2_REQUEST_ARTIFACT)).catch(() => { });
154
180
  if (result.response_artifact)
155
181
  await fsp.copyFile(result.response_artifact, path.join(dir, IMAGE_UX_REVIEW_GPT_IMAGE_2_RESPONSE_ARTIFACT)).catch(() => { });
156
- await attachGeneratedReviewImage(root, dir, contract, result.generated_image_path, { realGenerated: !fakeGenerated, mock: fakeGenerated, providerSurface: result.provider });
182
+ const response = await readImagegenResponse(dir);
183
+ const evidenceClass = String(response?.evidence_class || '');
184
+ const fakeGenerated = evidenceClass === 'mock_fixture' || result.provider === 'fake_imagegen_adapter';
185
+ const realGenerated = evidenceClass === 'codex_app_imagegen';
186
+ await attachGeneratedReviewImage(root, dir, contract, result.generated_image_path, {
187
+ realGenerated,
188
+ mock: fakeGenerated,
189
+ providerSurface: result.provider,
190
+ evidenceClass,
191
+ outputSource: response?.output_source || null,
192
+ outputSha256: response?.output_sha256 || response?.output_image_sha256 || null,
193
+ responseArtifact: result.response_artifact || null
194
+ });
157
195
  const extraction = await extractRealCallouts({
158
196
  root,
159
197
  generatedImagePath: result.generated_image_path,
@@ -169,6 +207,8 @@ async function runImageUxReview(root, command, args = []) {
169
207
  }
170
208
  }
171
209
  const artifacts = await writeImageUxReviewRouteArtifacts(dir, contract, { root, wrongnessChecked: true, honestModeComplete: true });
210
+ artifacts.gate = await enforceImageUxRuntimeGate(dir, artifacts.gate, { mock: flag(args, '--mock') });
211
+ await writeJsonAtomic(path.join(dir, IMAGE_UX_REVIEW_GATE_ARTIFACT), artifacts.gate);
172
212
  const proof = await finalizeImageUx(root, id, command, artifacts, { mock: flag(args, '--mock'), cmd: `sks ${command} run` });
173
213
  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 };
174
214
  if (!result.ok)
@@ -298,6 +338,8 @@ async function rebuildExistingMission(root, command, args = [], opts = {}) {
298
338
  recapture: { computerUseAvailable: false },
299
339
  honestModeComplete: opts.proofRequested === true
300
340
  });
341
+ artifacts.gate = await enforceImageUxRuntimeGate(dir, artifacts.gate, { mock: flag(args, '--mock') });
342
+ await writeJsonAtomic(path.join(dir, IMAGE_UX_REVIEW_GATE_ARTIFACT), artifacts.gate);
301
343
  const proof = await finalizeImageUx(root, missionId, command, artifacts, { mock: flag(args, '--mock'), cmd: `sks ${command} ${opts.fixRequested ? 'fix' : opts.recaptureRequested ? 'recapture' : 'build'}` });
302
344
  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 };
303
345
  if (!result.ok)
@@ -380,7 +422,7 @@ async function imageUxFixture(root, command, args) {
380
422
  fix_action: 'No-op fixture recheck',
381
423
  target_surface: 'fixture',
382
424
  candidate_files: [],
383
- status: 'fixed',
425
+ status: 'mock',
384
426
  confidence: 0.5,
385
427
  source: 'mock_fixture',
386
428
  extracted_from_generated_image: true,
@@ -392,18 +434,21 @@ async function imageUxFixture(root, command, args) {
392
434
  severity_visible: true,
393
435
  callout_number_visible: true,
394
436
  text_ocr_confidence: 0.5,
395
- fix_verification_status: 'recheck_verified',
437
+ fix_verification_status: 'mock',
396
438
  post_fix_recheck_issue_id: null
397
439
  }]
398
440
  });
399
441
  const artifacts = await writeImageUxReviewRouteArtifacts(dir, contract, { root, imageVoxelRelationsCreated: true, wrongnessChecked: true, honestModeComplete: true });
400
442
  const gate = {
401
443
  ...artifacts.gate,
402
- passed: true,
444
+ passed: false,
445
+ ok: false,
446
+ status: 'blocked',
447
+ execution_class: 'mock_fixture',
403
448
  honest_mode_complete: true,
404
- blockers: [],
449
+ blockers: ['image_ux_fixture_mode_cannot_claim_real'],
405
450
  fixture: true,
406
- verified_level: 'verified_partial',
451
+ verified_level: 'unverified_fixture',
407
452
  mock_fixture_cannot_claim_real: true
408
453
  };
409
454
  artifacts.gate = gate;
@@ -417,23 +462,49 @@ async function imageUxFixture(root, command, args) {
417
462
  mode: 'IMAGE_UX_REVIEW'
418
463
  });
419
464
  const proof = await finalizeImageUx(root, id, command, artifacts, { mock: true, requireRelation: flag(args, '--require-relation'), cmd: `sks ${command} fixture --mock` });
420
- const result = { schema: 'sks.image-ux-review-fixture.v2', ok: proof.ok && native.ok, mission_id: id, artifacts, native_agent_collaboration: native, proof: proof.validation };
465
+ const result = { schema: 'sks.image-ux-review-fixture.v2', ok: false, mission_id: id, artifacts, native_agent_collaboration: native, proof: proof.validation };
466
+ process.exitCode = 1;
421
467
  if (flag(args, '--json'))
422
468
  return printJson(result);
423
- console.log(`Image UX fixture: ${proof.ok ? 'ok' : 'blocked'} ${id}`);
469
+ console.log(`Image UX fixture: blocked ${id}`);
424
470
  return result;
425
471
  }
426
472
  async function attachGeneratedReviewImage(root, dir, contract, imagePath, opts = {}) {
427
473
  const inventory = await readJson(path.join(dir, 'image-ux-screen-inventory.json'), null).catch(() => null);
428
474
  const sourceScreen = inventory?.source_screens?.[0] || { id: 'screen-1' };
429
475
  const staged = await stageGeneratedImage(root, dir, imagePath, opts.mock ? 'generated-review-fixture.png' : null);
476
+ const response = await readImagegenResponse(dir);
477
+ const evidenceClass = opts.evidenceClass || response?.evidence_class || (opts.mock ? 'mock_fixture' : opts.realGenerated ? 'codex_app_imagegen' : null);
478
+ const outputSource = opts.outputSource || response?.output_source || (opts.mock ? 'mock_fixture' : opts.realGenerated ? 'manual_attach' : null);
479
+ const outputSha256 = opts.outputSha256 || response?.output_sha256 || response?.output_image_sha256 || null;
430
480
  const metadata = await generatedImageMetadata(root, staged, {
431
481
  id: opts.mock ? 'generated-review-fixture-1' : undefined,
432
482
  source_screen_id: sourceScreen.id || 'screen-1',
433
483
  provider_surface: opts.providerSurface || 'Codex App $imagegen',
434
- real_generated: opts.realGenerated === true,
484
+ evidence_class: evidenceClass,
485
+ output_source: outputSource,
486
+ output_sha256: outputSha256 || undefined,
487
+ real_generated: opts.realGenerated === true && evidenceClass === 'codex_app_imagegen',
435
488
  mock: opts.mock === true
436
489
  });
490
+ if (!response && opts.realGenerated === true) {
491
+ await writeJsonAtomic(path.join(dir, IMAGE_UX_REVIEW_GPT_IMAGE_2_RESPONSE_ARTIFACT), {
492
+ schema: 'sks.image-ux-gpt-image-2-response.v1',
493
+ created_at: nowIso(),
494
+ provider: 'codex_app_imagegen',
495
+ evidence_class: 'codex_app_imagegen',
496
+ model: 'gpt-image-2',
497
+ ok: true,
498
+ status: 'generated',
499
+ output_image_path: path.resolve(root, metadata.path),
500
+ output_image_sha256: metadata.sha256,
501
+ output_sha256: metadata.sha256,
502
+ output_id: metadata.output_id || null,
503
+ output_source: 'manual_attach',
504
+ dimensions: { width: metadata.width, height: metadata.height, format: metadata.format },
505
+ local_only: true
506
+ });
507
+ }
437
508
  const ledger = {
438
509
  schema: 'sks.image-ux-generated-review-ledger.v2',
439
510
  schema_version: 2,
@@ -444,6 +515,7 @@ async function attachGeneratedReviewImage(root, dir, contract, imagePath, opts =
444
515
  ...metadata,
445
516
  source_screen_id: 'screen-1',
446
517
  status: 'generated',
518
+ imagegen_response_artifact: opts.responseArtifact || (response || opts.realGenerated === true ? IMAGE_UX_REVIEW_GPT_IMAGE_2_RESPONSE_ARTIFACT : null),
447
519
  image_voxel_relation: 'generated_callout_review_of',
448
520
  callout_extraction_status: opts.mock ? 'succeeded' : 'pending',
449
521
  callouts: opts.mock ? [{
@@ -455,7 +527,7 @@ async function attachGeneratedReviewImage(root, dir, contract, imagePath, opts =
455
527
  title: 'Mock fixture callout',
456
528
  detail: 'Mock fixture callout for schema validation.',
457
529
  fix_action: 'Apply targeted UI adjustment, then recapture and re-review.',
458
- status: opts.mock ? 'fixed' : 'open',
530
+ status: opts.mock ? 'mock' : 'open',
459
531
  source: opts.mock ? 'mock_fixture' : 'real_gpt_image_2_callout',
460
532
  confidence: opts.mock ? 0.5 : 0.82,
461
533
  extraction_provider: 'mock_fixture',
@@ -466,19 +538,122 @@ async function attachGeneratedReviewImage(root, dir, contract, imagePath, opts =
466
538
  severity_visible: true,
467
539
  callout_number_visible: true,
468
540
  text_ocr_confidence: 0.5,
469
- fix_verification_status: 'recheck_verified',
541
+ fix_verification_status: opts.mock ? 'mock' : 'not_rechecked',
470
542
  post_fix_recheck_issue_id: null
471
543
  }] : []
472
544
  }],
473
545
  generated_count: 1,
474
546
  required_count: 1,
475
547
  blockers: [],
476
- passed: opts.realGenerated === true && opts.mock !== true,
548
+ passed: metadata.real_generated === true && opts.mock !== true,
477
549
  contract_hash: contract.sealed_hash || null
478
550
  };
479
551
  await writeJsonAtomic(path.join(dir, IMAGE_UX_REVIEW_GENERATED_REVIEW_LEDGER_ARTIFACT), ledger);
480
552
  return ledger;
481
553
  }
554
+ async function readImagegenResponse(dir) {
555
+ return readJson(path.join(dir, IMAGE_UX_REVIEW_GPT_IMAGE_2_RESPONSE_ARTIFACT), null).catch(() => null);
556
+ }
557
+ async function enforceImageUxRuntimeGate(dir, gate = {}, opts = {}) {
558
+ const inventory = await readJson(path.join(dir, IMAGE_UX_REVIEW_SCREEN_INVENTORY_ARTIFACT), null);
559
+ const issueLedger = await readJson(path.join(dir, IMAGE_UX_REVIEW_ISSUE_LEDGER_ARTIFACT), null);
560
+ const response = await readJson(path.join(dir, IMAGE_UX_REVIEW_GPT_IMAGE_2_RESPONSE_ARTIFACT), null);
561
+ const blockers = new Set(Array.isArray(gate?.blockers) ? gate.blockers.map(String) : []);
562
+ const sourceScreens = Array.isArray(inventory?.source_screens) ? inventory.source_screens : [];
563
+ if (!opts.mock) {
564
+ if (sourceScreens.length === 0)
565
+ blockers.add('no_source_screenshots_for_imagegen_review');
566
+ for (const screen of sourceScreens) {
567
+ const width = Number(screen?.width || screen?.original_resolution?.width || 0);
568
+ const height = Number(screen?.height || screen?.original_resolution?.height || 0);
569
+ if (screen?.status !== 'captured')
570
+ blockers.add(`source_screenshot_unreadable:${screen?.id || 'unknown'}`);
571
+ if (!Number.isFinite(width) || !Number.isFinite(height) || width < 64 || height < 64)
572
+ blockers.add(`source_screenshot_below_min_resolution:${screen?.id || 'unknown'}`);
573
+ }
574
+ }
575
+ const issues = Array.isArray(issueLedger?.issues) ? issueLedger.issues : [];
576
+ if (issues.some((issue) => issue?.extraction_provider === 'mock_fixture' || issue?.source === 'mock_fixture'))
577
+ blockers.add('mock_issue_extraction_cannot_pass_gate');
578
+ if (opts.mock)
579
+ blockers.add('image_ux_mock_mode_cannot_claim_real');
580
+ const responseEvidence = await validateImagegenResponseEvidence(response, dir);
581
+ for (const blocker of responseEvidence.blockers)
582
+ blockers.add(blocker);
583
+ const nextBlockers = [...blockers];
584
+ const codexGenerated = responseEvidence.ok === true && gate?.gpt_image_2_callout_generated === true;
585
+ const passed = gate?.passed === true && codexGenerated && nextBlockers.length === 0;
586
+ return {
587
+ ...gate,
588
+ passed,
589
+ ok: passed,
590
+ status: passed ? 'passed' : 'blocked',
591
+ gpt_image_2_callout_generated: codexGenerated,
592
+ generated_image_evidence: responseEvidence.ok === true,
593
+ imagegen_response_evidence: responseEvidence,
594
+ source_screenshot_min_resolution_passed: sourceScreens.length > 0 && sourceScreens.every((screen) => Number(screen?.width || 0) >= 64 && Number(screen?.height || 0) >= 64),
595
+ issue_ledger_real_extraction: issues.length > 0 && issues.every((issue) => issue?.extraction_provider !== 'mock_fixture' && issue?.source !== 'mock_fixture'),
596
+ blockers: nextBlockers
597
+ };
598
+ }
599
+ async function validateImagegenResponseEvidence(response = null, dir = process.cwd()) {
600
+ const blockers = [];
601
+ if (!response || typeof response !== 'object') {
602
+ return { ok: false, blockers: ['imagegen_response_artifact_missing'] };
603
+ }
604
+ if (response.schema !== 'sks.image-ux-gpt-image-2-response.v1')
605
+ blockers.push('imagegen_response_schema_invalid');
606
+ if (response.ok !== true || response.status !== 'generated')
607
+ blockers.push(response.blocker || 'imagegen_response_not_generated');
608
+ const evidenceClass = String(response.evidence_class || '');
609
+ if (!evidenceClass)
610
+ blockers.push('imagegen_response_evidence_class_missing');
611
+ if (evidenceClass === 'mock_fixture')
612
+ blockers.push('imagegen_response_mock_fixture_not_full_evidence');
613
+ if (evidenceClass === 'non_codex_api_fallback')
614
+ blockers.push('imagegen_response_non_codex_api_fallback_not_full_evidence');
615
+ if (evidenceClass && evidenceClass !== 'codex_app_imagegen')
616
+ blockers.push(`imagegen_response_evidence_class_not_codex_app:${evidenceClass}`);
617
+ const source = String(response.output_source || '');
618
+ if (!['manual_attach', 'auto_discovered_generated_images'].includes(source))
619
+ blockers.push('imagegen_response_output_source_invalid');
620
+ const outputPath = String(response.output_image_path || '');
621
+ const expectedSha = String(response.output_sha256 || response.output_image_sha256 || '');
622
+ if (!outputPath)
623
+ blockers.push('imagegen_response_output_path_missing');
624
+ if (!expectedSha)
625
+ blockers.push('imagegen_response_output_sha256_missing');
626
+ if (outputPath) {
627
+ try {
628
+ const root = rootFromMissionDir(dir);
629
+ const absolute = path.isAbsolute(outputPath) ? outputPath : path.resolve(root, outputPath);
630
+ const actualSha = await sha256File(absolute);
631
+ if (expectedSha && actualSha !== expectedSha)
632
+ blockers.push('imagegen_response_output_sha256_mismatch');
633
+ const dims = await imageDimensions(absolute);
634
+ if (!Number.isFinite(Number(dims.width)) || !Number.isFinite(Number(dims.height)) || Number(dims.width) <= 0 || Number(dims.height) <= 0) {
635
+ blockers.push('imagegen_response_output_dimensions_invalid');
636
+ }
637
+ }
638
+ catch {
639
+ blockers.push('imagegen_response_output_file_unreadable');
640
+ }
641
+ }
642
+ return {
643
+ ok: blockers.length === 0,
644
+ provider: response.provider || null,
645
+ evidence_class: evidenceClass || null,
646
+ output_source: source || null,
647
+ output_image_path: outputPath || null,
648
+ output_sha256: expectedSha || null,
649
+ blockers: [...new Set(blockers)]
650
+ };
651
+ }
652
+ function rootFromMissionDir(dir) {
653
+ const marker = `${path.sep}.sneakoscope${path.sep}missions${path.sep}`;
654
+ const idx = String(dir).indexOf(marker);
655
+ return idx >= 0 ? String(dir).slice(0, idx) : process.cwd();
656
+ }
482
657
  function nextActionForGate(gate = {}) {
483
658
  const blockers = gate?.blockers || [];
484
659
  if (blockers.includes('missing_generated_annotated_review_images'))