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
@@ -264,6 +264,7 @@ export function simulateNarutoActivePool(input) {
264
264
  let maxObserved = 0;
265
265
  let maxObservedWriteLeaseConflicts = 0;
266
266
  let conflictItemsEnqueued = 0;
267
+ const repairWaves = [];
267
268
  while (pending.length || active.size) {
268
269
  let launched = 0;
269
270
  for (;;) {
@@ -293,7 +294,15 @@ export function simulateNarutoActivePool(input) {
293
294
  if (shouldFail) {
294
295
  failed.add(generation.work_item_id);
295
296
  conflictItemsEnqueued += 1;
296
- const followup = conflictResolutionFollowup(generation.work_item_id, input.graph.work_items.length + conflictItemsEnqueued);
297
+ const attempt = executed.get(generation.work_item_id) || 1;
298
+ const followup = conflictResolutionFollowup(generation.work_item_id, input.graph.work_items.length + conflictItemsEnqueued, attempt);
299
+ repairWaves.push({
300
+ failed_work_item_id: generation.work_item_id,
301
+ followup_work_item_id: followup.id,
302
+ attempt,
303
+ requires_hypothesis: true,
304
+ tournament_forced: Boolean(followup.tournament && followup.tournament >= 2)
305
+ });
297
306
  pending.push(followup);
298
307
  byId.set(followup.id, followup);
299
308
  }
@@ -325,6 +334,7 @@ export function simulateNarutoActivePool(input) {
325
334
  duplicate_execution_count: duplicateExecutionCount,
326
335
  conflict_items_enqueued: conflictItemsEnqueued,
327
336
  max_observed_write_lease_conflicts: maxObservedWriteLeaseConflicts,
337
+ repair_waves: repairWaves,
328
338
  timeline,
329
339
  blockers
330
340
  };
@@ -359,12 +369,12 @@ function countActiveWriteLeaseConflicts(active, byId) {
359
369
  }
360
370
  return [...counts.values()].filter((count) => count > 1).reduce((sum, count) => sum + count - 1, 0);
361
371
  }
362
- function conflictResolutionFollowup(failedId, index) {
372
+ function conflictResolutionFollowup(failedId, index, attempt = 1) {
363
373
  const id = `NW-CONFLICT-${String(index).padStart(4, '0')}`;
364
374
  return {
365
375
  id,
366
376
  kind: 'conflict_resolution',
367
- title: `Resolve failed work item ${failedId}`,
377
+ title: `Resolve failed work item ${failedId} with repair-hypothesis.json before patching`,
368
378
  target_paths: [],
369
379
  readonly_paths: [],
370
380
  write_paths: [`.sneakoscope/naruto/conflicts/${id}.json`],
@@ -376,7 +386,8 @@ function conflictResolutionFollowup(failedId, index) {
376
386
  conflicts_with: [],
377
387
  estimated_cost: { tokens: 4000, latency_ms: 45000, cpu_weight: 1, memory_mb: 256, gpu_weight: 0 },
378
388
  lease_requirements: [{ path: `.sneakoscope/naruto/conflicts/${id}.json`, kind: 'write' }],
379
- acceptance: { requires_patch_envelope: true, requires_verification: true, requires_gpt_final: true }
389
+ acceptance: { requires_patch_envelope: true, requires_verification: true, requires_gpt_final: true },
390
+ ...(attempt >= 3 ? { tournament: 3 } : {})
380
391
  };
381
392
  }
382
393
  //# sourceMappingURL=naruto-active-pool.js.map
@@ -136,7 +136,7 @@ function startDeadmanHeartbeat(input) {
136
136
  schema: 'sks.naruto-actual-worker-heartbeat.v1',
137
137
  ts: nowIso(),
138
138
  item_id: input.intake.item.id,
139
- status: 'blocked',
139
+ status: 'timed_out',
140
140
  elapsed_ms: elapsedMs,
141
141
  progress: null
142
142
  }, 2 * 1024 * 1024).catch(() => undefined);
@@ -180,9 +180,19 @@ function buildNarutoWorkerPrompt(item, parentPrompt) {
180
180
  writeAllowed
181
181
  ? 'If changes are needed, return model-authored patch_envelopes scoped to write paths.'
182
182
  : 'This is read-only work. Do not mutate files and return an empty patch_envelopes array.',
183
+ writeAllowed
184
+ ? 'Before changing exported signatures, inspect references and include cochanged callers in the same patch; only set cochange_acknowledged with a concrete compatibility reason.'
185
+ : null,
186
+ /bug|fix|regression|broken|버그|수정/i.test(String(item?.kind || '') + ' ' + String(item?.title || ''))
187
+ ? 'Bugfix protocol: first add a regression test, record failed_before and passed_after in regression_proof, then patch the bug.'
188
+ : null,
189
+ /repair|conflict|fix/i.test(String(item?.kind || '') + ' ' + String(item?.title || ''))
190
+ ? 'Repair protocol: write repair_hypothesis before patching, including failure, hypotheses, chosen evidence, and minimal_probe.'
191
+ : null,
183
192
  writeAllowed
184
193
  ? null
185
194
  : 'For read-only work, inspect requested files/artifacts only; do not run package scripts, build commands, tests, or temp-file-creating checks unless the parent objective explicitly requires them.',
195
+ 'Impact scan, machine feedback, diff-quality, mistake-rule, TDD, and repair-hypothesis gates run before patch queue acceptance.',
186
196
  'Include verification checks, rollback notes, blockers, findings, and changed_files.'
187
197
  ].filter(Boolean).join('\n');
188
198
  }
@@ -17,6 +17,8 @@ export function narutoRoleAllowsWrite(role) {
17
17
  }
18
18
  export function mapWorkKindToNarutoRole(kind) {
19
19
  switch (kind) {
20
+ case 'bugfix':
21
+ case 'feature':
20
22
  case 'implementation':
21
23
  return 'implementer';
22
24
  case 'code_modification':
@@ -34,6 +36,7 @@ export function mapWorkKindToNarutoRole(kind) {
34
36
  case 'research':
35
37
  return 'researcher';
36
38
  case 'documentation':
39
+ case 'chore':
37
40
  return 'modifier';
38
41
  case 'conflict_resolution':
39
42
  return 'conflict_resolver';
@@ -38,6 +38,16 @@ export function pathPrefix(pathValue) {
38
38
  return parts.slice(0, 3).join('/');
39
39
  return parts.slice(0, 2).join('/');
40
40
  }
41
+ export function classifyNarutoDeliveryKind(text) {
42
+ const hay = String(text || '').toLowerCase();
43
+ if (/(^|\b)(fix|bug|broken|regression|failure|failing|crash|error)(\b|$)|버그|고쳐|수정|회귀/.test(hay))
44
+ return 'bugfix';
45
+ if (/(^|\b)(refactor|cleanup|simplify|restructure)(\b|$)|리팩터|정리/.test(hay))
46
+ return 'refactor';
47
+ if (/(^|\b)(chore|docs?|config|metadata|version)(\b|$)|문서|설정/.test(hay))
48
+ return 'chore';
49
+ return 'feature';
50
+ }
41
51
  function normalizePaths(paths) {
42
52
  return [...new Set(paths.map((file) => normalizeNarutoPath(String(file || ''))).filter(Boolean))].sort();
43
53
  }
@@ -1,5 +1,6 @@
1
1
  import { mapWorkKindToNarutoRole } from './naruto-role-policy.js';
2
2
  import { isNarutoWriteKind, normalizeNarutoPath } from './naruto-work-item.js';
3
+ import { classifyNarutoDeliveryKind } from './naruto-task-hints.js';
3
4
  const WRITE_CAPABLE_KIND_CYCLE = [
4
5
  'implementation',
5
6
  'code_modification',
@@ -53,7 +54,11 @@ export function buildNarutoWorkGraph(input = {}) {
53
54
  }
54
55
  for (let index = 0; index < totalWorkItems; index += 1) {
55
56
  const id = `NW-${String(index + 1).padStart(6, '0')}`;
56
- const kind = kindCycle[index % kindCycle.length] || 'verification';
57
+ const baseKind = kindCycle[index % kindCycle.length] || 'verification';
58
+ const deliveryKind = classifyNarutoDeliveryKind(input.prompt || '');
59
+ const kind = writeCapable && index === 0 && ['implementation', 'code_modification', 'refactor'].includes(baseKind)
60
+ ? deliveryKind
61
+ : baseKind;
57
62
  const kindWrites = writeCapable && isNarutoWriteKind(kind);
58
63
  const selectedTarget = targetPaths.length ? targetPaths[index % targetPaths.length] || targetPaths[0] || '' : `${basePath}/${id}.json`;
59
64
  const writePaths = kindWrites ? [selectedTarget].filter(Boolean) : [];
@@ -95,7 +100,8 @@ export function buildNarutoWorkGraph(input = {}) {
95
100
  required: worktreePolicy.required,
96
101
  allocation_required: worktreePolicy.mode === 'git-worktree'
97
102
  }
98
- } : {})
103
+ } : {}),
104
+ ...(writePaths.length > 0 && input.tournament && input.tournament >= 2 ? { tournament: Math.min(4, Math.max(2, Math.floor(input.tournament))) } : {})
99
105
  });
100
106
  }
101
107
  const activeWaves = planNarutoWorkWaves(workItems, Math.max(1, normalizePositiveInt(input.maxActiveWorkers, requestedClones)));
@@ -1,7 +1,10 @@
1
1
  export const NARUTO_WORK_KINDS = [
2
+ 'bugfix',
3
+ 'feature',
2
4
  'implementation',
3
5
  'code_modification',
4
6
  'refactor',
7
+ 'chore',
5
8
  'test_generation',
6
9
  'test_execution',
7
10
  'verification',
@@ -17,9 +20,12 @@ export const NARUTO_WORK_KINDS = [
17
20
  'final_review_input_pack'
18
21
  ];
19
22
  export const NARUTO_WRITE_WORK_KINDS = new Set([
23
+ 'bugfix',
24
+ 'feature',
20
25
  'implementation',
21
26
  'code_modification',
22
27
  'refactor',
28
+ 'chore',
23
29
  'test_generation',
24
30
  'documentation',
25
31
  'conflict_resolution',
@@ -0,0 +1,101 @@
1
+ import { runMachineFeedback } from '../verification/machine-feedback.js';
2
+ import { scanImpact } from '../verification/impact-scan.js';
3
+ export const APPROACHES = [
4
+ '기존 코드 재사용을 극대화하는 최소 diff 접근',
5
+ '해당 모듈의 지배적 패턴을 따르는 정석 접근',
6
+ '근본 원인을 한 단계 위에서 해결하는 구조적 접근',
7
+ '테스트와 관찰 가능성을 먼저 고정하는 증거 중심 접근'
8
+ ];
9
+ export async function runSolutionTournament(input) {
10
+ const n = Math.min(Math.max(input.n ?? 3, 2), 4);
11
+ const candidates = await Promise.all(APPROACHES.slice(0, n).map((approach, index) => input.spawnWorker({ ...input.item, id: `${input.item.id}-cand${index + 1}`, approach_directive: approach, isolation: 'worktree' })
12
+ .then((candidate) => ({ ...candidate, id: candidate.id || `${input.item.id}-cand${index + 1}`, approach }))));
13
+ await Promise.all(candidates.map(async (candidate) => {
14
+ candidate.score = await scoreCandidate(input.root, candidate);
15
+ }));
16
+ const alive = candidates
17
+ .filter((candidate) => candidate.score?.machine_ok && candidate.score.impact_breaks === 0)
18
+ .sort((a, b) => rank(a.score) - rank(b.score));
19
+ if (alive.length === 0)
20
+ return { schema: 'sks.solution-tournament.v1', winner: null, reason: 'all_candidates_failed_machine_checks', candidates };
21
+ if (alive.length === 1)
22
+ return { schema: 'sks.solution-tournament.v1', winner: alive[0] || null, reason: 'single_survivor', candidates };
23
+ const verdict = await input.judgeWorker({
24
+ a: alive[0],
25
+ b: alive[1],
26
+ prompt: '두 패치 중 6개월 뒤 유지보수자가 고마워할 쪽을 골라라. 근거는 코드 사실만.'
27
+ });
28
+ return {
29
+ schema: 'sks.solution-tournament.v1',
30
+ winner: verdict.pick === 'b' ? alive[1] : alive[0],
31
+ reason: `judge:${verdict.reason_digest}`,
32
+ candidates
33
+ };
34
+ }
35
+ export async function scoreCandidate(root, candidate) {
36
+ const patchText = patchTextFromCandidate(candidate);
37
+ const changedFiles = changedFilesFromCandidate(candidate);
38
+ const feedbackRoot = candidate.worktree || root;
39
+ const [feedback, impact] = await Promise.all([
40
+ runMachineFeedback(feedbackRoot, changedFiles, { timeoutMs: 60_000 }),
41
+ scanImpact(feedbackRoot, changedFiles, patchText)
42
+ ]);
43
+ const score = {
44
+ machine_ok: feedback.ok,
45
+ tests_passed: feedback.tests.ok ? feedback.tests.selected.length : 0,
46
+ tests_failed: feedback.tests.failed.length,
47
+ diff_lines: diffLineCount(patchText),
48
+ new_symbols: newExportCount(patchText),
49
+ impact_breaks: impact.cochange_required.length,
50
+ total: 0
51
+ };
52
+ score.total = rank(score);
53
+ return score;
54
+ }
55
+ export function summarizeTournament(result) {
56
+ return {
57
+ schema: result.schema,
58
+ winner_id: result.winner?.id || null,
59
+ reason: result.reason,
60
+ candidates: result.candidates.map((candidate) => ({
61
+ id: candidate.id,
62
+ approach: candidate.approach,
63
+ worktree: candidate.worktree,
64
+ score: candidate.score
65
+ }))
66
+ };
67
+ }
68
+ export function rank(s) {
69
+ return s.tests_failed * 1000 + s.impact_breaks * 500 + s.new_symbols * 20 + s.diff_lines;
70
+ }
71
+ function changedFilesFromCandidate(candidate) {
72
+ const patch = candidate.patch || {};
73
+ if (Array.isArray(patch.changed_files))
74
+ return patch.changed_files.map(String);
75
+ if (Array.isArray(patch.git_worktree?.changed_files))
76
+ return patch.git_worktree.changed_files.map(String);
77
+ if (Array.isArray(patch.operations))
78
+ return [...new Set(patch.operations.map((operation) => String(operation.path || '')).filter(Boolean))];
79
+ if (Array.isArray(patch.patch_envelopes))
80
+ return [...new Set(patch.patch_envelopes.flatMap((envelope) => changedFilesFromCandidate({ ...candidate, patch: envelope })))];
81
+ return [];
82
+ }
83
+ function patchTextFromCandidate(candidate) {
84
+ const patch = candidate.patch || {};
85
+ if (typeof patch.diff === 'string')
86
+ return patch.diff;
87
+ if (typeof patch.patch_text === 'string')
88
+ return patch.patch_text;
89
+ if (Array.isArray(patch.operations))
90
+ return patch.operations.map((operation) => operation.diff || operation.content || operation.replace || '').join('\n');
91
+ if (Array.isArray(patch.patch_envelopes))
92
+ return patch.patch_envelopes.map((envelope) => patchTextFromCandidate({ ...candidate, patch: envelope })).join('\n');
93
+ return '';
94
+ }
95
+ function diffLineCount(patchText) {
96
+ return String(patchText || '').split(/\r?\n/).filter((line) => /^[+-]/.test(line) && !/^(---|\+\+\+)/.test(line)).length;
97
+ }
98
+ function newExportCount(patchText) {
99
+ return String(patchText || '').split(/\r?\n/).filter((line) => /^\+\s*export\s+(?:async\s+)?(?:function|const|class|interface|type)\s+\w+/.test(line)).length;
100
+ }
101
+ //# sourceMappingURL=solution-tournament.js.map
@@ -86,6 +86,36 @@ export function madSksCatastrophicDbReasons(cls = {}) {
86
86
  blocked.push('dangerous_project_or_branch_management');
87
87
  return [...new Set(blocked)];
88
88
  }
89
+ export function madSksCatastrophicSqlLiteralRequirements(cls = {}) {
90
+ const reasons = madSksCatastrophicDbReasons(cls);
91
+ const requirements = {
92
+ drop_database: /\bdrop\s+database\b/i,
93
+ drop_schema: /\bdrop\s+schema\b/i,
94
+ drop_table: /\bdrop\s+table\b/i,
95
+ truncate: /\btruncate\b/i,
96
+ delete_without_where: /\bdelete\s+from\b(?![\s\S]*\bwhere\b)/i,
97
+ update_without_where: /\bupdate\b[\s\S]*\bset\b(?![\s\S]*\bwhere\b)/i
98
+ };
99
+ return reasons
100
+ .flatMap((reason) => {
101
+ const pattern = requirements[reason];
102
+ return pattern ? [{ reason, pattern }] : [];
103
+ });
104
+ }
105
+ export function madSksCatastrophicSqlExplicitlyRequested({ classification, userIntent, sql } = {}) {
106
+ const requirements = madSksCatastrophicSqlLiteralRequirements(classification || {});
107
+ if (!requirements.length)
108
+ return { required: false, ok: true, missing: [] };
109
+ // The SQL text can be generated by the agent. Only the user's literal request
110
+ // or CLI statement counts as explicit catastrophic authorization.
111
+ const haystack = String(userIntent || '');
112
+ const missing = requirements.filter((entry) => !entry.pattern.test(haystack)).map((entry) => entry.reason);
113
+ return {
114
+ required: true,
115
+ ok: missing.length === 0,
116
+ missing
117
+ };
118
+ }
89
119
  export function evaluatePermission({ action, scope, profile, classification, active = false, env = process.env } = {}) {
90
120
  const selectedProfile = normalizePermissionProfile(profile, env);
91
121
  const actionId = String(action || scope || '').trim();
@@ -870,6 +870,13 @@ async function materializeAutoSealedMadSks(dir, id, route, routeContext = {}, co
870
870
  normal_db_writes_allowed: true,
871
871
  live_server_writes_allowed: true,
872
872
  migration_apply_allowed: true,
873
+ sql_plane: {
874
+ requested: false,
875
+ capability_id: null,
876
+ operation_classes: [],
877
+ read_back_passed: false,
878
+ profile_closed: false
879
+ },
873
880
  catastrophic_safety_guard_active: true,
874
881
  permission_profile: permissionGateSummary(),
875
882
  contract_hash: contract.sealed_hash || null
@@ -962,7 +969,7 @@ async function prepareDb(root, route, task, required, opts = {}) {
962
969
  return routeContext(route, id, task, required, 'Run sks db policy/scan/check as needed, keep DB operations read-only, record safe MCP policy, and pass db-review.json.');
963
970
  }
964
971
  async function prepareMadDb(root, route, task, required, opts = {}) {
965
- const prepared = await prepareMadDbMission({ root, task, verifyTools: false, sessionKey: opts.sessionKey });
972
+ const prepared = await prepareMadDbMission({ root, task, verifyTools: false, sessionKey: opts.sessionKey, route: 'MadSKS', routeCommand: '$MAD-SKS' });
966
973
  const dir = missionDir(root, prepared.mission_id);
967
974
  const pipelinePlan = await writePipelinePlan(dir, {
968
975
  missionId: prepared.mission_id,
@@ -978,7 +985,7 @@ async function prepareMadDb(root, route, task, required, opts = {}) {
978
985
  cycle_id: prepared.cycle_id,
979
986
  blockers: prepared.blockers
980
987
  });
981
- await setCurrent(root, routeState(prepared.mission_id, route, prepared.ok ? 'MADDB_SQL_PLANE_CAPABILITY_ACTIVE' : 'MADDB_BLOCKED', required, {
988
+ await setCurrent(root, routeState(prepared.mission_id, route, prepared.ok ? 'MADSKS_SQL_PLANE_CAPABILITY_ACTIVE' : 'MADSKS_SQL_PLANE_BLOCKED', required, {
982
989
  prompt: task,
983
990
  questions_allowed: false,
984
991
  implementation_allowed: prepared.ok,
@@ -990,7 +997,8 @@ async function prepareMadDb(root, route, task, required, opts = {}) {
990
997
  mad_db_profile_sha256: prepared.capability.transport.profile_sha256,
991
998
  mad_db_capability_mission_id: prepared.mission_id,
992
999
  mad_db_capability_file: 'mad-db-capability.json',
993
- stop_gate: 'mad-db-gate.json',
1000
+ mad_sks_gate_file: 'mad-sks-gate.json',
1001
+ stop_gate: 'mad-sks-gate.json',
994
1002
  pipeline_plan_ready: validatePipelinePlan(pipelinePlan).ok,
995
1003
  pipeline_plan_path: PIPELINE_PLAN_ARTIFACT
996
1004
  }), { sessionKey: opts.sessionKey });
@@ -18,6 +18,7 @@ import { SSOT_GUARD_ARTIFACT, validateSsotGuardArtifact } from '../safety/ssot-g
18
18
  import { validateTeamRuntimeArtifacts } from '../team-dag.js';
19
19
  import { checkStopGate } from '../stop-gate/stop-gate-check.js';
20
20
  import { clarificationStopReason, context7Evidence, hasContext7DocsEvidence, hasSubagentEvidence, subagentEvidence, } from './runtime-core.js';
21
+ import { projectTriwikiToAgentsMd } from '../triwiki/agents-md-projector.js';
21
22
  const REFLECTION_ARTIFACT = 'reflection.md';
22
23
  const REFLECTION_GATE = 'reflection-gate.json';
23
24
  const REFLECTION_MEMORY_PATH = '.sneakoscope/memory/q2_facts/post-route-reflection.md';
@@ -289,8 +290,34 @@ export async function evaluateStop(root, state, payload, opts = {}) {
289
290
  const reflection = await reflectionGateStatus(root, state, jsonCache);
290
291
  if (!reflection.ok)
291
292
  return complianceBlock(root, state, reflectionStopReason(state, reflection), { gate: 'reflection', missing: reflection.missing });
293
+ fireAndForgetProjectMemory(root, state);
292
294
  return null;
293
295
  }
296
+ function fireAndForgetProjectMemory(root, state = {}) {
297
+ if (!state?.mission_id)
298
+ return;
299
+ void projectTriwikiToAgentsMd(String(root)).then((report) => {
300
+ const id = state.mission_id;
301
+ if (!id)
302
+ return null;
303
+ return appendJsonl(path.join(missionDir(root, id), 'events.jsonl'), {
304
+ ts: nowIso(),
305
+ type: 'triwiki.agents_md_projected',
306
+ ok: report.ok,
307
+ reason: report.reason,
308
+ written: report.written
309
+ });
310
+ }).catch((err) => {
311
+ const id = state.mission_id;
312
+ if (!id)
313
+ return null;
314
+ return appendJsonl(path.join(missionDir(root, id), 'events.jsonl'), {
315
+ ts: nowIso(),
316
+ type: 'triwiki.agents_md_project_failed',
317
+ error: err?.message || String(err)
318
+ }).catch(() => undefined);
319
+ });
320
+ }
294
321
  async function routeProofGateStatus(root, state = {}) {
295
322
  const route = routeFromState(state);
296
323
  const required = state.proof_required === true || routeRequiresCompletionProof(route);
@@ -2,7 +2,7 @@ import path from 'node:path';
2
2
  import { nowIso, readJson, writeJsonAtomic } from '../fsx.js';
3
3
  import { addImageRelation, ingestImage } from '../wiki-image/image-voxel-ledger.js';
4
4
  import { exportSlidesToImages, PPT_DECK_INVENTORY_ARTIFACT, PPT_SLIDE_EXPORT_LEDGER_ARTIFACT } from './slide-exporter.js';
5
- import { buildSlideImagegenRequestArtifact, buildSlideImagegenResponseArtifact, generateSlideCalloutReviews, PPT_SLIDE_CALLOUT_LEDGER_ARTIFACT, PPT_SLIDE_IMAGEGEN_REQUEST_ARTIFACT, PPT_SLIDE_IMAGEGEN_RESPONSE_ARTIFACT } from './slide-imagegen-review.js';
5
+ import { buildSlideImagegenRequestArtifact, buildSlideImagegenResponseArtifact, buildSlideImagegenEvidence, generateSlideCalloutReviews, PPT_SLIDE_CALLOUT_LEDGER_ARTIFACT, PPT_SLIDE_IMAGEGEN_REQUEST_ARTIFACT, PPT_SLIDE_IMAGEGEN_RESPONSE_ARTIFACT } from './slide-imagegen-review.js';
6
6
  import { extractSlideIssues, PPT_DECK_ISSUE_LEDGER_ARTIFACT, PPT_SLIDE_EXTRACTION_REPORT_ARTIFACT, PPT_SLIDE_ISSUE_LEDGER_ARTIFACT } from './slide-issue-extraction.js';
7
7
  import { writePptFixTaskPlan, PPT_FIX_TASK_PLAN_ARTIFACT } from './ppt-fix-task-planner.js';
8
8
  import { writePptPatchHandoff, PPT_PATCH_HANDOFF_ARTIFACT, PPT_PATCH_RESULT_ARTIFACT } from './ppt-patch-handoff.js';
@@ -117,6 +117,9 @@ function buildPptImagegenReviewGate(parts = {}) {
117
117
  const p0p1 = Number(parts.deckIssueLedger.p0_p1_count || parts.deckIssueLedger.p0_p1_open_count || 0);
118
118
  const patchRequested = parts.fixRequested || p0p1 > 0 || parts.patchHandoff.result?.re_export_required === true;
119
119
  const changedSlidesRechecked = !patchRequested || parts.recheckReport.status === 'complete' || parts.mock === true;
120
+ const imagegenEvidence = buildSlideImagegenEvidence(parts.calloutLedger);
121
+ if (imagegenEvidence.required && imagegenEvidence.passed !== true)
122
+ blockers.push(...imagegenEvidence.blockers);
120
123
  const gate = {
121
124
  schema: 'sks.ppt-imagegen-review-gate.v1',
122
125
  created_at: nowIso(),
@@ -129,6 +132,7 @@ function buildPptImagegenReviewGate(parts = {}) {
129
132
  patch_requested: patchRequested,
130
133
  changed_slides_rechecked: changedSlidesRechecked,
131
134
  image_voxel_relations_created: parts.imageVoxelRelationsCreated === true,
135
+ imagegen_evidence: imagegenEvidence,
132
136
  wrongness_checked: true,
133
137
  honest_mode_complete: true,
134
138
  mock_fixture: parts.mock === true,
@@ -144,6 +148,7 @@ function buildPptImagegenReviewGate(parts = {}) {
144
148
  && gate.p0_p1_zero_after_fix
145
149
  && gate.changed_slides_rechecked
146
150
  && gate.image_voxel_relations_created
151
+ && gate.imagegen_evidence.passed === true
147
152
  && gate.blockers.length === 0;
148
153
  return gate;
149
154
  }
@@ -1,6 +1,6 @@
1
1
  import path from 'node:path';
2
2
  import fsp from 'node:fs/promises';
3
- import { nowIso } from '../fsx.js';
3
+ import { nowIso, readJson } from '../fsx.js';
4
4
  import { CODEX_APP_IMAGE_GENERATION_DOC_URL, CODEX_IMAGEGEN_REQUIRED_POLICY } from '../routes.js';
5
5
  import { sha256File, imageDimensions } from '../wiki-image/image-hash.js';
6
6
  import { generateGptImage2CalloutReview } from '../image-ux-review/imagegen-adapter.js';
@@ -45,6 +45,9 @@ export async function generateSlideCalloutReviews({ root, dir, slideExportLedger
45
45
  ...metadata,
46
46
  status: 'generated',
47
47
  source: 'mock_fixture',
48
+ evidence_class: 'mock_fixture',
49
+ output_source: 'mock_fixture',
50
+ output_sha256: metadata.sha256,
48
51
  callout_extraction_status: 'succeeded',
49
52
  callouts: [{
50
53
  callout_id: 'callout-1',
@@ -62,8 +65,15 @@ export async function generateSlideCalloutReviews({ root, dir, slideExportLedger
62
65
  else if (generatedSlideImage && slides.length > 0) {
63
66
  const slide = slides[0];
64
67
  const generatedPath = await stageGeneratedSlideReview(root, dir, path.resolve(root, generatedSlideImage), path.basename(generatedSlideImage));
68
+ const metadata = await generatedSlideMetadata(root, generatedPath, slide, {
69
+ mock: false,
70
+ realGenerated: true,
71
+ providerSurface: 'codex_app_imagegen',
72
+ evidenceClass: 'codex_app_imagegen',
73
+ outputSource: 'manual_attach'
74
+ });
65
75
  generatedReviewImages.push({
66
- ...await generatedSlideMetadata(root, generatedPath, slide, { mock: false, realGenerated: true }),
76
+ ...metadata,
67
77
  status: 'attached_generated_review',
68
78
  source: 'user_attached_generated_slide_review',
69
79
  callout_extraction_status: 'pending',
@@ -85,15 +95,23 @@ export async function generateSlideCalloutReviews({ root, dir, slideExportLedger
85
95
  blockers.push(generated.blocker || 'ppt_imagegen_callouts_missing');
86
96
  continue;
87
97
  }
88
- const fakeGenerated = generated.provider === 'fake_imagegen_adapter';
98
+ const response = generated.response_artifact ? await readJson(generated.response_artifact, null).catch(() => null) : null;
99
+ const evidenceClass = String(response?.evidence_class || '');
100
+ const fakeGenerated = evidenceClass === 'mock_fixture' || generated.provider === 'fake_imagegen_adapter';
101
+ const codexGenerated = evidenceClass === 'codex_app_imagegen';
102
+ if (!codexGenerated)
103
+ blockers.push(evidenceClass === 'non_codex_api_fallback' ? 'ppt_imagegen_non_codex_api_fallback_not_full_evidence' : fakeGenerated ? 'ppt_imagegen_mock_fixture_not_full_evidence' : 'ppt_imagegen_evidence_class_missing');
89
104
  generatedReviewImages.push({
90
105
  ...await generatedSlideMetadata(root, generated.generated_image_path, slide, {
91
106
  mock: fakeGenerated,
92
- realGenerated: !fakeGenerated,
93
- providerSurface: generated.provider || 'gpt-image-2'
107
+ realGenerated: codexGenerated,
108
+ providerSurface: generated.provider || 'gpt-image-2',
109
+ evidenceClass,
110
+ outputSource: response?.output_source || null,
111
+ outputSha256: response?.output_sha256 || response?.output_image_sha256 || null
94
112
  }),
95
113
  status: 'generated',
96
- source: fakeGenerated ? 'mock_fixture' : 'real_gpt_image_2_callout',
114
+ source: fakeGenerated ? 'mock_fixture' : codexGenerated ? 'real_gpt_image_2_callout' : 'non_codex_api_fallback',
97
115
  callout_extraction_status: fakeGenerated ? 'succeeded' : 'pending',
98
116
  callouts: fakeGenerated ? [{
99
117
  callout_id: 'fake-slide-callout-1',
@@ -159,9 +177,51 @@ export function buildSlideImagegenResponseArtifact(calloutLedger = {}) {
159
177
  schema: 'sks.ppt-slide-imagegen-response.v1',
160
178
  created_at: nowIso(),
161
179
  generated_review_images: calloutLedger.generated_review_images || [],
180
+ imagegen_evidence: buildSlideImagegenEvidence(calloutLedger),
162
181
  blockers: calloutLedger.blockers || []
163
182
  };
164
183
  }
184
+ export function buildSlideImagegenEvidence(calloutLedger = {}) {
185
+ const images = Array.isArray(calloutLedger.generated_review_images) ? calloutLedger.generated_review_images : [];
186
+ const requiredCount = Number(calloutLedger.required_count || images.length || 0);
187
+ const blockers = [];
188
+ for (const image of images) {
189
+ const evidenceClass = String(image.evidence_class || '');
190
+ const outputSource = String(image.output_source || '');
191
+ const outputSha = String(image.output_sha256 || '');
192
+ if (evidenceClass !== 'codex_app_imagegen')
193
+ blockers.push(evidenceClass ? `ppt_slide_imagegen_evidence_class_not_codex_app:${evidenceClass}` : 'ppt_slide_imagegen_evidence_class_missing');
194
+ if (!['manual_attach', 'auto_discovered_generated_images'].includes(outputSource))
195
+ blockers.push(`ppt_slide_imagegen_output_source_invalid:${outputSource || 'missing'}`);
196
+ if (!outputSha)
197
+ blockers.push('ppt_slide_imagegen_output_sha256_missing');
198
+ else if (image.sha256 && image.sha256 !== outputSha)
199
+ blockers.push('ppt_slide_imagegen_output_sha256_mismatch');
200
+ if (image.real_generated !== true)
201
+ blockers.push('ppt_slide_imagegen_not_real_codex_app_output');
202
+ }
203
+ if (requiredCount > 0 && images.length < requiredCount)
204
+ blockers.push('ppt_slide_imagegen_required_outputs_missing');
205
+ return {
206
+ schema: 'sks.ppt-slide-imagegen-evidence.v1',
207
+ required: requiredCount > 0,
208
+ required_count: requiredCount,
209
+ generated_count: images.length,
210
+ codex_app_generated_count: images.filter((image) => image.evidence_class === 'codex_app_imagegen' && image.real_generated === true).length,
211
+ images: images.map((image) => ({
212
+ image_path: image.image_path || image.path || null,
213
+ output_source: image.output_source || null,
214
+ output_sha256: image.output_sha256 || null,
215
+ sha256: image.sha256 || null,
216
+ evidence_class: image.evidence_class || null,
217
+ real_generated: image.real_generated === true,
218
+ mock: image.mock === true,
219
+ response_artifact: image.imagegen_response_artifact || null
220
+ })),
221
+ blockers: [...new Set(blockers)],
222
+ passed: requiredCount > 0 && images.length >= requiredCount && blockers.length === 0
223
+ };
224
+ }
165
225
  async function stageGeneratedSlideReview(root, dir, source, preferredName) {
166
226
  const dest = path.join(dir, 'generated-slide-reviews', preferredName);
167
227
  await fsp.mkdir(path.dirname(dest), { recursive: true });
@@ -170,21 +230,26 @@ async function stageGeneratedSlideReview(root, dir, source, preferredName) {
170
230
  return path.relative(root, dest).split(path.sep).join('/');
171
231
  }
172
232
  async function generatedSlideMetadata(root, relPath, slide, opts = {}) {
173
- const absolute = path.resolve(root, relPath);
233
+ const absolute = path.isAbsolute(relPath) ? relPath : path.resolve(root, relPath);
174
234
  const dims = await imageDimensions(absolute);
235
+ const normalizedPath = path.relative(root, absolute).split(path.sep).join('/');
236
+ const sha = await sha256File(absolute);
175
237
  return {
176
238
  id: opts.mock ? `ppt-generated-review-fixture-${slide.slide_index || 1}` : `ppt-generated-review-${slide.slide_index || 1}`,
177
239
  generated_review_image_id: opts.mock ? `ppt-generated-review-fixture-${slide.slide_index || 1}` : `ppt-generated-review-${slide.slide_index || 1}`,
178
240
  slide_id: slide.slide_id,
179
241
  slide_index: slide.slide_index,
180
242
  source_slide_image_path: slide.image_path,
181
- image_path: relPath,
182
- sha256: await sha256File(absolute),
243
+ image_path: normalizedPath,
244
+ sha256: sha,
245
+ output_sha256: opts.outputSha256 || sha,
246
+ output_source: opts.outputSource || (opts.mock ? 'mock_fixture' : 'manual_attach'),
247
+ evidence_class: opts.evidenceClass || (opts.mock ? 'mock_fixture' : opts.realGenerated ? 'codex_app_imagegen' : 'non_codex_api_fallback'),
183
248
  width: dims.width,
184
249
  height: dims.height,
185
250
  format: dims.format,
186
251
  provider_surface: opts.mock ? 'mock_fixture' : (opts.providerSurface || 'Codex App $imagegen'),
187
- real_generated: opts.realGenerated === true,
252
+ real_generated: opts.realGenerated === true && (opts.evidenceClass || (opts.mock ? 'mock_fixture' : 'codex_app_imagegen')) === 'codex_app_imagegen',
188
253
  mock: opts.mock === true,
189
254
  local_only: true
190
255
  };