sneakoscope 7.3.0 → 7.5.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.
- package/README.md +117 -5
- package/crates/sks-core/Cargo.lock +1 -1
- package/crates/sks-core/Cargo.toml +1 -1
- package/dist/cli/codex-app-command.js +1 -1
- package/dist/cli/command-manifest-lite.js +2 -1
- package/dist/cli/install-helpers-codex-lb-selftest.js +2 -1
- package/dist/cli/install-helpers-install-support.js +6 -16
- package/dist/cli/install-helpers.js +59 -50
- package/dist/cli/router.js +9 -1
- package/dist/config/skills-manifest.json +50 -71
- package/dist/core/codex-app.js +5 -5
- package/dist/core/codex-native/core-skill-manifest.js +9 -6
- package/dist/core/commands/image-ux-review-command.js +243 -50
- package/dist/core/commands/naruto-command.js +245 -8
- package/dist/core/commands/ppt-command.js +2 -2
- package/dist/core/commands/telegram-command.js +123 -10
- package/dist/core/commands/triwiki-command.js +11 -1
- package/dist/core/commands/triwiki-graph-command.js +160 -0
- package/dist/core/commands/wiki-command.js +100 -20
- package/dist/core/errors/message.js +3 -0
- package/dist/core/feature-registry.js +2 -2
- package/dist/core/fsx.js +7 -3
- package/dist/core/hooks-runtime/codex-commit-hooks-selftest.js +82 -0
- package/dist/core/hooks-runtime/context-graph-freshness-preflight.js +113 -0
- package/dist/core/hooks-runtime/official-subagent-lifecycle.js +1 -1
- package/dist/core/hooks-runtime/tool-output-quarantine.js +1 -1
- package/dist/core/hooks-runtime.js +28 -86
- package/dist/core/image-ux-review/callout-extraction.js +6 -0
- package/dist/core/image-ux-review/imagegen-adapter.js +54 -18
- package/dist/core/image-ux-review/real-callout-extractor.js +1 -0
- package/dist/core/image-ux-review.js +182 -7
- package/dist/core/imagegen/codex-lb-imagegen-target.js +22 -7
- package/dist/core/imagegen/imagegen-capability.js +11 -4
- package/dist/core/imagegen/require-imagegen.js +29 -5
- package/dist/core/init/skills.js +22 -10
- package/dist/core/install/installed-package-smoke.js +157 -4
- package/dist/core/managed-path-safety.js +8 -10
- package/dist/core/mcp-config/backup.js +2 -4
- package/dist/core/mcp-config/config-reader.js +2 -4
- package/dist/core/mcp-config/scope.js +3 -5
- package/dist/core/naruto/context-graph-advisor-scope.js +242 -0
- package/dist/core/naruto/context-graph-advisor.js +0 -0
- package/dist/core/pipeline-internals/runtime-core.js +44 -0
- package/dist/core/policy/latest-version-guidance.js +113 -0
- package/dist/core/ppt.js +8 -8
- package/dist/core/release/gate-manifest.js +32 -1
- package/dist/core/release/npm-stage-contract.js +21 -0
- package/dist/core/release/package-size-budget.js +2 -2
- package/dist/core/release/release-gate-contract.js +5 -0
- package/dist/core/release/stage-publish.js +27 -0
- package/dist/core/responses-stream.js +65 -19
- package/dist/core/routes/design-policy.js +7 -2
- package/dist/core/routes/dollar-manifest-lite.js +0 -4
- package/dist/core/routes/evidence.js +1 -1
- package/dist/core/routes.js +12 -8
- package/dist/core/safety/command-contract/contracts.js +2 -0
- package/dist/core/safety/command-contract/types.js +1 -1
- package/dist/core/search/context-graph-seeds.js +205 -0
- package/dist/core/search/context.js +0 -0
- package/dist/core/search/provider.js +2 -2
- package/dist/core/subagents/naruto-help-contract.js +2 -1
- package/dist/core/subagents/naruto-host-credentials.js +161 -0
- package/dist/core/subagents/official-subagent-preparation.js +4 -1
- package/dist/core/subagents/official-subagent-prompt.js +44 -2
- package/dist/core/subagents/official-subagent-runner.js +26 -8
- package/dist/core/subagents/triwiki-attention.js +135 -123
- package/dist/core/telegram/bot-api.js +59 -4
- package/dist/core/telegram/config.js +3 -2
- package/dist/core/telegram/hub.js +3 -2
- package/dist/core/telegram/owner-lock.js +4 -6
- package/dist/core/telegram/setup.js +223 -73
- package/dist/core/triwiki/code-pack.js +12 -120
- package/dist/core/triwiki/context-graph/compiler/cache-key.js +277 -0
- package/dist/core/triwiki/context-graph/compiler/extract.js +104 -0
- package/dist/core/triwiki/context-graph/compiler/freshness.js +108 -0
- package/dist/core/triwiki/context-graph/compiler/index.js +237 -0
- package/dist/core/triwiki/context-graph/compiler/merge.js +0 -0
- package/dist/core/triwiki/context-graph/compiler/serialize.js +115 -0
- package/dist/core/triwiki/context-graph/contracts.js +236 -0
- package/dist/core/triwiki/context-graph/extractors/code/builders.js +150 -0
- package/dist/core/triwiki/context-graph/extractors/code/declarations.js +161 -0
- package/dist/core/triwiki/context-graph/extractors/code/index.js +265 -0
- package/dist/core/triwiki/context-graph/extractors/code/inventory.js +237 -0
- package/dist/core/triwiki/context-graph/extractors/code/module-graph.js +252 -0
- package/dist/core/triwiki/context-graph/extractors/code/modules.js +79 -0
- package/dist/core/triwiki/context-graph/extractors/code/references.js +184 -0
- package/dist/core/triwiki/context-graph/extractors/code/selection.js +100 -0
- package/dist/core/triwiki/context-graph/extractors/code/ts-config.js +161 -0
- package/dist/core/triwiki/context-graph/extractors/code/types.js +18 -0
- package/dist/core/triwiki/context-graph/extractors/evidence/claims.js +289 -0
- package/dist/core/triwiki/context-graph/extractors/evidence/index.js +54 -0
- package/dist/core/triwiki/context-graph/extractors/evidence/proof-index.js +213 -0
- package/dist/core/triwiki/context-graph/extractors/evidence/proofs.js +266 -0
- package/dist/core/triwiki/context-graph/extractors/evidence/redaction.js +177 -0
- package/dist/core/triwiki/context-graph/extractors/evidence/shared.js +0 -0
- package/dist/core/triwiki/context-graph/extractors/evidence/sources.js +154 -0
- package/dist/core/triwiki/context-graph/extractors/index.js +7 -0
- package/dist/core/triwiki/context-graph/extractors/topology/commands.js +207 -0
- package/dist/core/triwiki/context-graph/extractors/topology/gate-edges.js +201 -0
- package/dist/core/triwiki/context-graph/extractors/topology/gates.js +182 -0
- package/dist/core/triwiki/context-graph/extractors/topology/globs.js +124 -0
- package/dist/core/triwiki/context-graph/extractors/topology/index.js +92 -0
- package/dist/core/triwiki/context-graph/extractors/topology/routes.js +135 -0
- package/dist/core/triwiki/context-graph/extractors/topology/shared.js +215 -0
- package/dist/core/triwiki/context-graph/graph-index.js +143 -0
- package/dist/core/triwiki/context-graph/ids.js +0 -0
- package/dist/core/triwiki/context-graph/lint/index.js +0 -0
- package/dist/core/triwiki/context-graph/lint/rules.js +232 -0
- package/dist/core/triwiki/context-graph/lint/warnings.js +93 -0
- package/dist/core/triwiki/context-graph/paths.js +116 -0
- package/dist/core/triwiki/context-graph/profiles.js +78 -0
- package/dist/core/triwiki/context-graph/projections/anchors.js +91 -0
- package/dist/core/triwiki/context-graph/projections/attention.js +58 -0
- package/dist/core/triwiki/context-graph/projections/code-pack-workspace.js +62 -0
- package/dist/core/triwiki/context-graph/projections/code-pack.js +144 -0
- package/dist/core/triwiki/context-graph/projections/index.js +7 -0
- package/dist/core/triwiki/context-graph/projections/module-view.js +140 -0
- package/dist/core/triwiki/context-graph/projections/node-summary.js +118 -0
- package/dist/core/triwiki/context-graph/projections/pack-contract.js +6 -0
- package/dist/core/triwiki/context-graph/query/explain.js +0 -0
- package/dist/core/triwiki/context-graph/query/index.js +178 -0
- package/dist/core/triwiki/context-graph/query/load.js +116 -0
- package/dist/core/triwiki/context-graph/query/pack.js +117 -0
- package/dist/core/triwiki/context-graph/query/rank.js +142 -0
- package/dist/core/triwiki/context-graph/query/ranking-config.js +64 -0
- package/dist/core/triwiki/context-graph/query/seeds.js +241 -0
- package/dist/core/triwiki/context-graph/query/snapshot-cache.js +72 -0
- package/dist/core/triwiki/context-graph/query/traverse.js +154 -0
- package/dist/core/triwiki/context-graph/query-types.js +29 -0
- package/dist/core/triwiki/context-graph/store/compile-lock.js +21 -0
- package/dist/core/triwiki/context-graph/store/event-log.js +48 -0
- package/dist/core/triwiki/context-graph/store/fragment-cache.js +85 -0
- package/dist/core/triwiki/context-graph/store/graph-status.js +85 -0
- package/dist/core/triwiki/context-graph/store/snapshot-store.js +155 -0
- package/dist/core/triwiki/triwiki-proof-bank-index-repair.js +115 -0
- package/dist/core/triwiki/triwiki-proof-bank-index-store.js +290 -0
- package/dist/core/triwiki/triwiki-proof-bank-index.js +126 -0
- package/dist/core/triwiki/triwiki-proof-bank.js +7 -1
- package/dist/core/verification/context-graph-affected.js +271 -0
- package/dist/core/version.js +1 -1
- package/dist/scripts/context-graph-check.js +191 -0
- package/dist/scripts/helper-dedup-check.js +0 -1
- package/dist/scripts/installed-package-smoke-check.js +4 -1
- package/dist/scripts/latest-version-guidance-check.js +74 -0
- package/dist/scripts/release-metadata-check.js +20 -3
- package/dist/scripts/ux-review-imagegen-blackbox-check.js +25 -1
- package/dist/scripts/ux-review-run-wires-imagegen-check.js +9 -1
- package/package.json +5 -3
- package/release-gates.v2.json +174 -0
- package/schemas/triwiki/context-graph.schema.json +119 -0
- package/dist/core/triwiki/code-index-scanner.js +0 -309
|
@@ -4,9 +4,9 @@ import { ensureDir, nowIso, projectRoot, readJson, writeJsonAtomic } from '../fs
|
|
|
4
4
|
import { createMission, findLatestMission, loadMission } from '../mission.js';
|
|
5
5
|
import { flag, readOption } from './command-utils.js';
|
|
6
6
|
import { printJson } from '../../cli/output.js';
|
|
7
|
-
import { IMAGE_UX_REVIEW_FIX_LOOP_ARTIFACT, IMAGE_UX_REVIEW_FIX_TASK_PLAN_ARTIFACT, IMAGE_UX_REVIEW_CALLOUT_EXTRACTION_REPORT_ARTIFACT, IMAGE_UX_REVIEW_GENERATED_REVIEW_LEDGER_ARTIFACT, IMAGE_UX_REVIEW_GPT_IMAGE_2_RESPONSE_ARTIFACT, IMAGE_UX_REVIEW_GATE_ARTIFACT, IMAGE_UX_REVIEW_HONEST_MODE_ARTIFACT, IMAGE_UX_REVIEW_GPT_IMAGE_2_REQUEST_ARTIFACT, IMAGE_UX_REVIEW_IMAGEGEN_REQUEST_ARTIFACT, IMAGE_UX_REVIEW_ITERATION_REPORT_ARTIFACT, IMAGE_UX_REVIEW_ISSUE_LEDGER_ARTIFACT, IMAGE_UX_REVIEW_POLICY_ARTIFACT, IMAGE_UX_REVIEW_RECAPTURE_ARTIFACT, IMAGE_UX_REVIEW_SCREEN_INVENTORY_ARTIFACT, imageUxReviewProofEvidence, buildImageUxCalloutExtractionReport, writeImageUxReviewRouteArtifacts } from '../image-ux-review.js';
|
|
7
|
+
import { IMAGE_UX_REVIEW_FIX_LOOP_ARTIFACT, IMAGE_UX_REVIEW_FIX_TASK_PLAN_ARTIFACT, IMAGE_UX_REVIEW_CALLOUT_EXTRACTION_REPORT_ARTIFACT, IMAGE_UX_REVIEW_GENERATED_REVIEW_LEDGER_ARTIFACT, IMAGE_UX_REVIEW_GPT_IMAGE_2_RESPONSE_ARTIFACT, IMAGE_UX_REVIEW_GATE_ARTIFACT, IMAGE_UX_REVIEW_HONEST_MODE_ARTIFACT, IMAGE_UX_REVIEW_GPT_IMAGE_2_REQUEST_ARTIFACT, IMAGE_UX_REVIEW_IMAGEGEN_REQUEST_ARTIFACT, IMAGE_UX_REVIEW_ITERATION_REPORT_ARTIFACT, IMAGE_UX_REVIEW_ISSUE_LEDGER_ARTIFACT, IMAGE_UX_REVIEW_POLICY_ARTIFACT, IMAGE_UX_REVIEW_RECAPTURE_ARTIFACT, IMAGE_UX_REVIEW_SCREEN_INVENTORY_ARTIFACT, imageUxReviewProofEvidence, imageUxReviewCommandOutcome, buildImageUxCalloutExtractionReport, writeImageUxReviewRouteArtifacts } from '../image-ux-review.js';
|
|
8
8
|
import { maybeFinalizeRoute } from '../proof/auto-finalize.js';
|
|
9
|
-
import { generatedImageMetadata, generateGptImage2CalloutReview } from '../image-ux-review/imagegen-adapter.js';
|
|
9
|
+
import { buildCalloutPrompt, generatedImageMetadata, generateGptImage2CalloutReview } from '../image-ux-review/imagegen-adapter.js';
|
|
10
10
|
import { extractRealCallouts } from '../image-ux-review/real-callout-extractor.js';
|
|
11
11
|
import { addImageRelation, ingestImage } from '../wiki-image/image-voxel-ledger.js';
|
|
12
12
|
import { sha256File, imageDimensions } from '../wiki-image/image-hash.js';
|
|
@@ -66,13 +66,17 @@ export async function imageUxReviewCommand(command, args = []) {
|
|
|
66
66
|
async function runImageUxReview(root, command, args = []) {
|
|
67
67
|
const missionRequested = readOption(args, '--mission', null);
|
|
68
68
|
const missionId = missionRequested
|
|
69
|
-
? missionRequested === 'latest' ? await findLatestMission(root, { mode: 'image-ux-review'
|
|
69
|
+
? missionRequested === 'latest' ? await findLatestMission(root, { mode: 'image-ux-review' }) : missionRequested
|
|
70
70
|
: null;
|
|
71
71
|
const imagePath = readOption(args, '--image', null) || readOption(args, '--screenshot', null);
|
|
72
72
|
const generatedImage = readOption(args, '--generated-image', null);
|
|
73
|
-
const shouldGenerateCallouts =
|
|
74
|
-
if (missionId)
|
|
73
|
+
const shouldGenerateCallouts = !generatedImage;
|
|
74
|
+
if (missionId) {
|
|
75
|
+
if (generatedImage) {
|
|
76
|
+
return continueExistingMissionWithGeneratedImage(root, command, missionId, generatedImage, args, 'sks.image-ux-review-run.v1');
|
|
77
|
+
}
|
|
75
78
|
return rebuildExistingMission(root, command, [missionId, ...args], { fixRequested: flag(args, '--fix') });
|
|
79
|
+
}
|
|
76
80
|
const sourcePreflight = await imageUxReviewSourcePreflight(args);
|
|
77
81
|
const { fromChromeExtension, fromComputerUse, chromePreflight } = sourcePreflight;
|
|
78
82
|
if (sourcePreflight.result) {
|
|
@@ -111,7 +115,7 @@ async function runImageUxReview(root, command, args = []) {
|
|
|
111
115
|
process.exitCode = 1;
|
|
112
116
|
if (flag(args, '--json'))
|
|
113
117
|
return printJson(result);
|
|
114
|
-
console.error('UX Review blocked: Codex
|
|
118
|
+
console.error('UX Review blocked: no selected Codex imagegen/gpt-image-2 provider is ready.');
|
|
115
119
|
for (const action of imagegenRequired.blocker?.next_actions || [])
|
|
116
120
|
console.error(`- ${action}`);
|
|
117
121
|
return result;
|
|
@@ -133,8 +137,16 @@ async function runImageUxReview(root, command, args = []) {
|
|
|
133
137
|
}
|
|
134
138
|
};
|
|
135
139
|
await writeJsonAtomic(path.join(dir, 'decision-contract.json'), contract);
|
|
136
|
-
if (generatedImage)
|
|
140
|
+
if (generatedImage) {
|
|
137
141
|
await attachGeneratedReviewImage(root, dir, contract, generatedImage, { realGenerated: !flag(args, '--mock'), mock: flag(args, '--mock') });
|
|
142
|
+
if (!flag(args, '--mock')) {
|
|
143
|
+
await extractAndWriteGeneratedReview(root, dir, {
|
|
144
|
+
generatedImagePath: generatedImage,
|
|
145
|
+
sourceImagePath: sourceRel || imagePath,
|
|
146
|
+
sessionId: readOption(args, '--session', null) || readOption(args, '--session-id', null)
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
}
|
|
138
150
|
if (!generatedImage && shouldGenerateCallouts) {
|
|
139
151
|
const outputDir = path.join(dir, 'generated-callouts');
|
|
140
152
|
const missionStartMs = Date.parse(mission.created_at || '') || undefined;
|
|
@@ -144,7 +156,9 @@ async function runImageUxReview(root, command, args = []) {
|
|
|
144
156
|
source_screen_id: 'screen-1',
|
|
145
157
|
source_image_path: path.resolve(root, sourceRel || imagePath),
|
|
146
158
|
output_dir: outputDir,
|
|
147
|
-
prompt:
|
|
159
|
+
prompt: buildCalloutPrompt('screen-1', {
|
|
160
|
+
target: contract.answers.TARGET_SURFACE
|
|
161
|
+
}),
|
|
148
162
|
requested_fidelity: 'original',
|
|
149
163
|
privacy: 'local-only'
|
|
150
164
|
}, {
|
|
@@ -153,11 +167,11 @@ async function runImageUxReview(root, command, args = []) {
|
|
|
153
167
|
generatedImageMaxAgeMs: maxAgeOverride ? Number(maxAgeOverride) * 60 * 1000 : 30 * 60 * 1000
|
|
154
168
|
}
|
|
155
169
|
});
|
|
170
|
+
if (result.request_artifact)
|
|
171
|
+
await fsp.copyFile(result.request_artifact, path.join(dir, IMAGE_UX_REVIEW_GPT_IMAGE_2_REQUEST_ARTIFACT));
|
|
172
|
+
if (result.response_artifact)
|
|
173
|
+
await fsp.copyFile(result.response_artifact, path.join(dir, IMAGE_UX_REVIEW_GPT_IMAGE_2_RESPONSE_ARTIFACT));
|
|
156
174
|
if (result.generated_image_path) {
|
|
157
|
-
if (result.request_artifact)
|
|
158
|
-
await fsp.copyFile(result.request_artifact, path.join(dir, IMAGE_UX_REVIEW_GPT_IMAGE_2_REQUEST_ARTIFACT)).catch(() => { });
|
|
159
|
-
if (result.response_artifact)
|
|
160
|
-
await fsp.copyFile(result.response_artifact, path.join(dir, IMAGE_UX_REVIEW_GPT_IMAGE_2_RESPONSE_ARTIFACT)).catch(() => { });
|
|
161
175
|
const response = await readImagegenResponse(dir);
|
|
162
176
|
const evidenceClass = String(response?.evidence_class || '');
|
|
163
177
|
const fakeGenerated = evidenceClass === 'mock_fixture' || result.provider === 'fake_imagegen_adapter';
|
|
@@ -171,25 +185,21 @@ async function runImageUxReview(root, command, args = []) {
|
|
|
171
185
|
outputSha256: response?.output_sha256 || response?.output_image_sha256 || null,
|
|
172
186
|
responseArtifact: result.response_artifact || null
|
|
173
187
|
});
|
|
174
|
-
|
|
175
|
-
root,
|
|
188
|
+
await extractAndWriteGeneratedReview(root, dir, {
|
|
176
189
|
generatedImagePath: result.generated_image_path,
|
|
177
|
-
|
|
190
|
+
sourceImagePath: sourceRel || imagePath,
|
|
178
191
|
sessionId: readOption(args, '--session', null) || readOption(args, '--session-id', null)
|
|
179
192
|
});
|
|
180
|
-
await writeJsonAtomic(path.join(dir, IMAGE_UX_REVIEW_ISSUE_LEDGER_ARTIFACT), extraction.issue_ledger);
|
|
181
|
-
await writeJsonAtomic(path.join(dir, IMAGE_UX_REVIEW_CALLOUT_EXTRACTION_REPORT_ARTIFACT), await buildImageUxCalloutExtractionReport(root, extraction, {
|
|
182
|
-
generatedImagePath: result.generated_image_path,
|
|
183
|
-
sourceImagePath: sourceRel || imagePath,
|
|
184
|
-
provider: extraction.provider
|
|
185
|
-
}));
|
|
186
193
|
}
|
|
187
194
|
}
|
|
188
195
|
const artifacts = await writeImageUxReviewRouteArtifacts(dir, contract, { root, wrongnessChecked: true, honestModeComplete: true });
|
|
189
196
|
artifacts.gate = await enforceImageUxRuntimeGate(dir, artifacts.gate, { mock: flag(args, '--mock') });
|
|
190
197
|
await writeJsonAtomic(path.join(dir, IMAGE_UX_REVIEW_GATE_ARTIFACT), artifacts.gate);
|
|
191
198
|
const proof = await finalizeImageUx(root, id, command, artifacts, { mock: flag(args, '--mock'), cmd: `sks ${command} run` });
|
|
192
|
-
const
|
|
199
|
+
const outcome = imageUxReviewCommandOutcome(artifacts.gate, proof.ok, {
|
|
200
|
+
allowReviewOnlyCompletion: !flag(args, '--fix')
|
|
201
|
+
});
|
|
202
|
+
const result = { schema: 'sks.image-ux-review-run.v1', ...outcome, mission_id: id, artifacts, proof: proof.validation };
|
|
193
203
|
if (!result.ok)
|
|
194
204
|
process.exitCode = 1;
|
|
195
205
|
if (flag(args, '--json'))
|
|
@@ -197,10 +207,67 @@ async function runImageUxReview(root, command, args = []) {
|
|
|
197
207
|
console.log(`Image UX review: ${result.ok ? 'ok' : 'blocked'} ${id}`);
|
|
198
208
|
return result;
|
|
199
209
|
}
|
|
200
|
-
|
|
210
|
+
async function extractAndWriteGeneratedReview(root, dir, input) {
|
|
211
|
+
const extraction = await extractRealCallouts({
|
|
212
|
+
root,
|
|
213
|
+
generatedImagePath: input.generatedImagePath,
|
|
214
|
+
sourceScreenshot: { id: 'screen-1' },
|
|
215
|
+
sessionId: input.sessionId || null
|
|
216
|
+
});
|
|
217
|
+
const generatedLedger = await readJson(path.join(dir, IMAGE_UX_REVIEW_GENERATED_REVIEW_LEDGER_ARTIFACT), null).catch(() => null);
|
|
218
|
+
const generatedImages = Array.isArray(generatedLedger?.generated_review_images) ? generatedLedger.generated_review_images : [];
|
|
219
|
+
const localImage = generatedImages.find((image) => image.sha256 && image.sha256 === extraction.generated_image_sha256)
|
|
220
|
+
|| generatedImages[0]
|
|
221
|
+
|| null;
|
|
222
|
+
const localImageId = localImage?.id || null;
|
|
223
|
+
const generatedSha = localImage?.sha256 || extraction.generated_image_sha256 || null;
|
|
224
|
+
const extractedIssues = Array.isArray(extraction.issue_ledger?.issues) ? extraction.issue_ledger.issues : [];
|
|
225
|
+
const boundIssues = extractedIssues.map((issue) => ({
|
|
226
|
+
...issue,
|
|
227
|
+
generated_review_image_id: localImageId || issue.generated_review_image_id,
|
|
228
|
+
evidence_image_id: localImageId || issue.evidence_image_id,
|
|
229
|
+
generated_image_sha256: generatedSha || issue.generated_image_sha256
|
|
230
|
+
}));
|
|
231
|
+
const boundIssueLedger = {
|
|
232
|
+
...extraction.issue_ledger,
|
|
233
|
+
generated_image_id: localImageId,
|
|
234
|
+
generated_image_sha256: generatedSha,
|
|
235
|
+
issues: boundIssues
|
|
236
|
+
};
|
|
237
|
+
const extractionSucceeded = extraction.ok === true
|
|
238
|
+
&& boundIssueLedger.validation?.ok === true
|
|
239
|
+
&& boundIssues.length > 0;
|
|
240
|
+
if (localImage && generatedLedger) {
|
|
241
|
+
await writeJsonAtomic(path.join(dir, IMAGE_UX_REVIEW_GENERATED_REVIEW_LEDGER_ARTIFACT), {
|
|
242
|
+
...generatedLedger,
|
|
243
|
+
status: extractionSucceeded ? 'generated_and_extracted' : 'generated_extraction_blocked',
|
|
244
|
+
generated_review_images: generatedImages.map((image) => image.id === localImage.id ? {
|
|
245
|
+
...image,
|
|
246
|
+
generated_image_sha256: generatedSha,
|
|
247
|
+
extraction_provider: extraction.provider || null,
|
|
248
|
+
extraction_status: extraction.status || (extractionSucceeded ? 'extracted' : 'blocked'),
|
|
249
|
+
callout_extraction_status: extractionSucceeded ? 'succeeded' : 'failed',
|
|
250
|
+
callout_extraction_blocker: extractionSucceeded ? null : extraction.blocker || 'callout_extraction_failed',
|
|
251
|
+
callouts: boundIssues
|
|
252
|
+
} : image)
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
await writeJsonAtomic(path.join(dir, IMAGE_UX_REVIEW_ISSUE_LEDGER_ARTIFACT), boundIssueLedger);
|
|
256
|
+
await writeJsonAtomic(path.join(dir, IMAGE_UX_REVIEW_CALLOUT_EXTRACTION_REPORT_ARTIFACT), await buildImageUxCalloutExtractionReport(root, extraction, {
|
|
257
|
+
generatedImagePath: input.generatedImagePath,
|
|
258
|
+
generatedImageId: localImageId,
|
|
259
|
+
sourceImagePath: input.sourceImagePath || null,
|
|
260
|
+
provider: extraction.provider
|
|
261
|
+
}));
|
|
262
|
+
return extraction;
|
|
263
|
+
}
|
|
264
|
+
export async function imageUxReviewSourcePreflight(args = [], opts = {}) {
|
|
201
265
|
const fromChromeExtension = flag(args, '--from-chrome-extension') || Boolean(readOption(args, '--from-chrome-extension', null));
|
|
202
266
|
const fromComputerUse = flag(args, '--from-computer-use') || Boolean(readOption(args, '--from-computer-use', null));
|
|
203
|
-
const
|
|
267
|
+
const imagePath = readOption(args, '--image', null) || readOption(args, '--screenshot', null);
|
|
268
|
+
const chromePreflight = fromChromeExtension
|
|
269
|
+
? await (opts.chromeStatus ? opts.chromeStatus() : codexChromeExtensionStatus())
|
|
270
|
+
: null;
|
|
204
271
|
if (chromePreflight && !chromePreflight.ok) {
|
|
205
272
|
return {
|
|
206
273
|
fromChromeExtension,
|
|
@@ -231,6 +298,22 @@ export async function imageUxReviewSourcePreflight(args = []) {
|
|
|
231
298
|
}
|
|
232
299
|
};
|
|
233
300
|
}
|
|
301
|
+
if ((fromChromeExtension || fromComputerUse) && !imagePath) {
|
|
302
|
+
return {
|
|
303
|
+
fromChromeExtension,
|
|
304
|
+
fromComputerUse,
|
|
305
|
+
chromePreflight,
|
|
306
|
+
result: {
|
|
307
|
+
schema: 'sks.image-ux-review-run.v1',
|
|
308
|
+
ok: false,
|
|
309
|
+
status: 'blocked',
|
|
310
|
+
blocker: 'captured_screenshot_path_required',
|
|
311
|
+
guidance: [
|
|
312
|
+
'Capture the real page or native-app screen first, then pass its local artifact path with --image <captured-path>.'
|
|
313
|
+
]
|
|
314
|
+
}
|
|
315
|
+
};
|
|
316
|
+
}
|
|
234
317
|
return { fromChromeExtension, fromComputerUse, chromePreflight, result: null };
|
|
235
318
|
}
|
|
236
319
|
function printImageUxReviewSourcePreflightBlock(result) {
|
|
@@ -240,6 +323,10 @@ function printImageUxReviewSourcePreflightBlock(result) {
|
|
|
240
323
|
console.error(result.chrome_extension.docs_url);
|
|
241
324
|
return;
|
|
242
325
|
}
|
|
326
|
+
if (result.blocker === 'captured_screenshot_path_required') {
|
|
327
|
+
console.error('UX Review blocked: capture completed without a bound screenshot artifact. Pass --image <captured-path>.');
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
243
330
|
console.error('UX Review blocked: web/browser UX review requires Codex Chrome Extension, not Computer Use. Use --from-chrome-extension after setup, or provide a screenshot with --image.');
|
|
244
331
|
}
|
|
245
332
|
async function calloutsImageUxReview(root, command, args = []) {
|
|
@@ -255,8 +342,12 @@ async function calloutsImageUxReview(root, command, args = []) {
|
|
|
255
342
|
return runImageUxReview(root, command, ['--image', imagePath, '--generate-callouts', '--json', ...(flag(args, '--mock') ? ['--mock'] : [])]);
|
|
256
343
|
}
|
|
257
344
|
async function extractIssuesImageUxReview(root, command, args = []) {
|
|
345
|
+
const missionRequested = readOption(args, '--mission', null);
|
|
346
|
+
const missionId = missionRequested
|
|
347
|
+
? missionRequested === 'latest' ? await findLatestMission(root, { mode: 'image-ux-review' }) : missionRequested
|
|
348
|
+
: null;
|
|
258
349
|
const generatedImage = readOption(args, '--generated-image', null);
|
|
259
|
-
const sourceImage = readOption(args, '--image', null) || readOption(args, '--screenshot', null)
|
|
350
|
+
const sourceImage = readOption(args, '--image', null) || readOption(args, '--screenshot', null);
|
|
260
351
|
if (!generatedImage) {
|
|
261
352
|
const result = { schema: 'sks.image-ux-review-extract-issues.v1', ok: false, status: 'blocked', blocker: 'generated_image_required' };
|
|
262
353
|
process.exitCode = 1;
|
|
@@ -265,6 +356,9 @@ async function extractIssuesImageUxReview(root, command, args = []) {
|
|
|
265
356
|
console.error('Usage: sks ux-review extract-issues --generated-image <path> --json');
|
|
266
357
|
return result;
|
|
267
358
|
}
|
|
359
|
+
if (missionId) {
|
|
360
|
+
return continueExistingMissionWithGeneratedImage(root, command, missionId, generatedImage, args, 'sks.image-ux-review-extract-issues.v1');
|
|
361
|
+
}
|
|
268
362
|
const { id, dir, mission } = await createMission(root, { mode: 'image-ux-review', prompt: `Extract UX issues from ${generatedImage}` });
|
|
269
363
|
const sourceRel = sourceImage ? await stageSourceImage(root, dir, sourceImage) : null;
|
|
270
364
|
const contract = {
|
|
@@ -275,18 +369,11 @@ async function extractIssuesImageUxReview(root, command, args = []) {
|
|
|
275
369
|
await writeJsonAtomic(path.join(dir, 'decision-contract.json'), contract);
|
|
276
370
|
await attachGeneratedReviewImage(root, dir, contract, generatedImage, { realGenerated: !flag(args, '--mock'), mock: flag(args, '--mock') });
|
|
277
371
|
if (!flag(args, '--mock')) {
|
|
278
|
-
|
|
279
|
-
root,
|
|
372
|
+
await extractAndWriteGeneratedReview(root, dir, {
|
|
280
373
|
generatedImagePath: generatedImage,
|
|
281
|
-
|
|
374
|
+
sourceImagePath: sourceRel || sourceImage,
|
|
282
375
|
sessionId: readOption(args, '--session', null) || readOption(args, '--session-id', null)
|
|
283
376
|
});
|
|
284
|
-
await writeJsonAtomic(path.join(dir, IMAGE_UX_REVIEW_ISSUE_LEDGER_ARTIFACT), extraction.issue_ledger);
|
|
285
|
-
await writeJsonAtomic(path.join(dir, IMAGE_UX_REVIEW_CALLOUT_EXTRACTION_REPORT_ARTIFACT), await buildImageUxCalloutExtractionReport(root, extraction, {
|
|
286
|
-
generatedImagePath: generatedImage,
|
|
287
|
-
sourceImagePath: sourceRel || sourceImage,
|
|
288
|
-
provider: extraction.provider
|
|
289
|
-
}));
|
|
290
377
|
}
|
|
291
378
|
const artifacts = await writeImageUxReviewRouteArtifacts(dir, contract, { root, wrongnessChecked: true, honestModeComplete: true });
|
|
292
379
|
const proof = await finalizeImageUx(root, id, command, artifacts, { mock: flag(args, '--mock'), cmd: `sks ${command} extract-issues` });
|
|
@@ -298,9 +385,56 @@ async function extractIssuesImageUxReview(root, command, args = []) {
|
|
|
298
385
|
console.log(`Image UX issue extraction: ${result.ok ? 'ok' : 'blocked'} ${id}`);
|
|
299
386
|
return result;
|
|
300
387
|
}
|
|
388
|
+
async function continueExistingMissionWithGeneratedImage(root, command, missionId, generatedImage, args, schema) {
|
|
389
|
+
const { dir, mission } = await loadMission(root, missionId);
|
|
390
|
+
const contract = await readJson(path.join(dir, 'decision-contract.json'), { prompt: mission.prompt, answers: {}, sealed_hash: null });
|
|
391
|
+
const mock = flag(args, '--mock');
|
|
392
|
+
const ledger = await attachGeneratedReviewImage(root, dir, contract, generatedImage, {
|
|
393
|
+
realGenerated: !mock,
|
|
394
|
+
mock
|
|
395
|
+
});
|
|
396
|
+
if (!mock) {
|
|
397
|
+
await extractAndWriteGeneratedReview(root, dir, {
|
|
398
|
+
generatedImagePath: generatedImage,
|
|
399
|
+
sourceImagePath: sourceImageFromContract(contract),
|
|
400
|
+
sessionId: readOption(args, '--session', null) || readOption(args, '--session-id', null)
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
const artifacts = await writeImageUxReviewRouteArtifacts(dir, contract, {
|
|
404
|
+
root,
|
|
405
|
+
wrongnessChecked: true,
|
|
406
|
+
honestModeComplete: true
|
|
407
|
+
});
|
|
408
|
+
artifacts.gate = await enforceImageUxRuntimeGate(dir, artifacts.gate, { mock });
|
|
409
|
+
await writeJsonAtomic(path.join(dir, IMAGE_UX_REVIEW_GATE_ARTIFACT), artifacts.gate);
|
|
410
|
+
const proof = await finalizeImageUx(root, missionId, command, artifacts, {
|
|
411
|
+
mock,
|
|
412
|
+
cmd: `sks ${command} ${schema.endsWith('extract-issues.v1') ? 'extract-issues' : 'attach-generated'}`
|
|
413
|
+
});
|
|
414
|
+
const outcome = imageUxReviewCommandOutcome(artifacts.gate, proof.ok, {
|
|
415
|
+
allowReviewOnlyCompletion: !flag(args, '--fix')
|
|
416
|
+
});
|
|
417
|
+
const result = {
|
|
418
|
+
schema,
|
|
419
|
+
...outcome,
|
|
420
|
+
mission_id: missionId,
|
|
421
|
+
generated_review_ledger: ledger,
|
|
422
|
+
issue_ledger: artifacts.issue_ledger,
|
|
423
|
+
gate: artifacts.gate,
|
|
424
|
+
proof: proof.validation
|
|
425
|
+
};
|
|
426
|
+
if (!result.ok)
|
|
427
|
+
process.exitCode = 1;
|
|
428
|
+
if (flag(args, '--json'))
|
|
429
|
+
return printJson(result);
|
|
430
|
+
console.log(`Image UX review continued: ${result.ok ? 'ok' : 'blocked'} ${missionId}`);
|
|
431
|
+
return result;
|
|
432
|
+
}
|
|
301
433
|
async function attachGeneratedImageCommand(root, command, args = []) {
|
|
302
|
-
const missionArg = args
|
|
303
|
-
|
|
434
|
+
const missionArg = readOption(args, '--mission', null)
|
|
435
|
+
|| args.find((arg, index) => !String(arg).startsWith('--') && !String(args[index - 1] || '').startsWith('--'))
|
|
436
|
+
|| 'latest';
|
|
437
|
+
const missionId = missionArg === 'latest' ? await findLatestMission(root, { mode: 'image-ux-review' }) : missionArg;
|
|
304
438
|
const imagePath = readOption(args, '--image', null) || readOption(args, '--generated-image', null);
|
|
305
439
|
if (!missionId || !imagePath) {
|
|
306
440
|
const result = { schema: 'sks.image-ux-review-attach-generated.v1', ok: false, status: 'blocked', blocker: !missionId ? 'mission_required' : 'generated_image_required' };
|
|
@@ -309,15 +443,7 @@ async function attachGeneratedImageCommand(root, command, args = []) {
|
|
|
309
443
|
return printJson(result);
|
|
310
444
|
return result;
|
|
311
445
|
}
|
|
312
|
-
|
|
313
|
-
const contract = await readJson(path.join(dir, 'decision-contract.json'), { prompt: mission.prompt, answers: {}, sealed_hash: null });
|
|
314
|
-
const ledger = await attachGeneratedReviewImage(root, dir, contract, imagePath, { realGenerated: !flag(args, '--mock'), mock: flag(args, '--mock') });
|
|
315
|
-
const artifacts = await writeImageUxReviewRouteArtifacts(dir, contract, { root, wrongnessChecked: true, honestModeComplete: true });
|
|
316
|
-
const result = { schema: 'sks.image-ux-review-attach-generated.v1', ok: artifacts.gate.generated_image_ingested === true, mission_id: missionId, generated_review_ledger: ledger, gate: artifacts.gate };
|
|
317
|
-
if (flag(args, '--json'))
|
|
318
|
-
return printJson(result);
|
|
319
|
-
console.log(`Generated image attached: ${missionId}`);
|
|
320
|
-
return result;
|
|
446
|
+
return continueExistingMissionWithGeneratedImage(root, command, missionId, imagePath, args, 'sks.image-ux-review-attach-generated.v1');
|
|
321
447
|
}
|
|
322
448
|
async function attachAfterImageCommand(root, command, args = []) {
|
|
323
449
|
const missionArg = args.find((arg) => !String(arg).startsWith('--')) || 'latest';
|
|
@@ -365,7 +491,10 @@ async function rebuildExistingMission(root, command, args = [], opts = {}) {
|
|
|
365
491
|
artifacts.gate = await enforceImageUxRuntimeGate(dir, artifacts.gate, { mock: flag(args, '--mock') });
|
|
366
492
|
await writeJsonAtomic(path.join(dir, IMAGE_UX_REVIEW_GATE_ARTIFACT), artifacts.gate);
|
|
367
493
|
const proof = await finalizeImageUx(root, missionId, command, artifacts, { mock: flag(args, '--mock'), cmd: `sks ${command} ${opts.fixRequested ? 'fix' : opts.recaptureRequested ? 'recapture' : 'build'}` });
|
|
368
|
-
const
|
|
494
|
+
const outcome = imageUxReviewCommandOutcome(artifacts.gate, proof.ok, {
|
|
495
|
+
allowReviewOnlyCompletion: false
|
|
496
|
+
});
|
|
497
|
+
const result = { schema: 'sks.image-ux-review-build.v2', ...outcome, mission_id: missionId, artifacts, proof: proof.validation };
|
|
369
498
|
if (!result.ok)
|
|
370
499
|
process.exitCode = 1;
|
|
371
500
|
if (flag(args, '--json'))
|
|
@@ -510,10 +639,19 @@ async function attachGeneratedReviewImage(root, dir, contract, imagePath, opts =
|
|
|
510
639
|
evidence_class: evidenceClass,
|
|
511
640
|
output_source: outputSource,
|
|
512
641
|
output_sha256: outputSha256 || undefined,
|
|
642
|
+
output_id: response?.output_id || null,
|
|
513
643
|
real_generated: opts.realGenerated === true && isFullImagegenEvidenceClass(evidenceClass),
|
|
514
644
|
mock: opts.mock === true
|
|
515
645
|
});
|
|
516
|
-
if (
|
|
646
|
+
if (response) {
|
|
647
|
+
await writeJsonAtomic(path.join(dir, IMAGE_UX_REVIEW_GPT_IMAGE_2_RESPONSE_ARTIFACT), {
|
|
648
|
+
...response,
|
|
649
|
+
generated_review_image_id: metadata.id,
|
|
650
|
+
output_sha256: response.output_sha256 || response.output_image_sha256 || metadata.sha256,
|
|
651
|
+
output_image_sha256: response.output_image_sha256 || response.output_sha256 || metadata.sha256
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
else if (opts.realGenerated === true) {
|
|
517
655
|
await writeJsonAtomic(path.join(dir, IMAGE_UX_REVIEW_GPT_IMAGE_2_RESPONSE_ARTIFACT), {
|
|
518
656
|
schema: 'sks.image-ux-gpt-image-2-response.v1',
|
|
519
657
|
created_at: nowIso(),
|
|
@@ -526,6 +664,7 @@ async function attachGeneratedReviewImage(root, dir, contract, imagePath, opts =
|
|
|
526
664
|
output_image_sha256: metadata.sha256,
|
|
527
665
|
output_sha256: metadata.sha256,
|
|
528
666
|
output_id: metadata.output_id || null,
|
|
667
|
+
generated_review_image_id: metadata.id,
|
|
529
668
|
output_source: 'manual_attach',
|
|
530
669
|
dimensions: { width: metadata.width, height: metadata.height, format: metadata.format },
|
|
531
670
|
local_only: true
|
|
@@ -582,7 +721,9 @@ async function readImagegenResponse(dir) {
|
|
|
582
721
|
}
|
|
583
722
|
async function enforceImageUxRuntimeGate(dir, gate = {}, opts = {}) {
|
|
584
723
|
const inventory = await readJson(path.join(dir, IMAGE_UX_REVIEW_SCREEN_INVENTORY_ARTIFACT), null);
|
|
724
|
+
const generatedLedger = await readJson(path.join(dir, IMAGE_UX_REVIEW_GENERATED_REVIEW_LEDGER_ARTIFACT), null);
|
|
585
725
|
const issueLedger = await readJson(path.join(dir, IMAGE_UX_REVIEW_ISSUE_LEDGER_ARTIFACT), null);
|
|
726
|
+
const extractionReport = await readJson(path.join(dir, IMAGE_UX_REVIEW_CALLOUT_EXTRACTION_REPORT_ARTIFACT), null);
|
|
586
727
|
const response = await readJson(path.join(dir, IMAGE_UX_REVIEW_GPT_IMAGE_2_RESPONSE_ARTIFACT), null);
|
|
587
728
|
const blockers = new Set(Array.isArray(gate?.blockers) ? gate.blockers.map(String) : []);
|
|
588
729
|
const sourceScreens = Array.isArray(inventory?.source_screens) ? inventory.source_screens : [];
|
|
@@ -606,19 +747,34 @@ async function enforceImageUxRuntimeGate(dir, gate = {}, opts = {}) {
|
|
|
606
747
|
const responseEvidence = await validateImagegenResponseEvidence(response, dir);
|
|
607
748
|
for (const blocker of responseEvidence.blockers)
|
|
608
749
|
blockers.add(blocker);
|
|
750
|
+
const bindingBlockers = validateGeneratedImageEvidenceBinding(response, generatedLedger, extractionReport, issueLedger);
|
|
751
|
+
for (const blocker of bindingBlockers)
|
|
752
|
+
blockers.add(blocker);
|
|
609
753
|
const nextBlockers = [...blockers];
|
|
610
754
|
const codexGenerated = responseEvidence.ok === true && gate?.gpt_image_2_callout_generated === true;
|
|
755
|
+
const sourceScreenshotMinResolutionPassed = sourceScreens.length > 0
|
|
756
|
+
&& sourceScreens.every((screen) => Number(screen?.width || 0) >= 64 && Number(screen?.height || 0) >= 64);
|
|
757
|
+
const issueLedgerRealExtraction = issues.length > 0
|
|
758
|
+
&& issues.every((issue) => issue?.extraction_provider !== 'mock_fixture' && issue?.source !== 'mock_fixture');
|
|
759
|
+
const reviewReportCompleted = gate?.review_report_completed === true
|
|
760
|
+
&& codexGenerated
|
|
761
|
+
&& bindingBlockers.length === 0
|
|
762
|
+
&& sourceScreenshotMinResolutionPassed
|
|
763
|
+
&& issueLedgerRealExtraction;
|
|
611
764
|
const passed = gate?.passed === true && codexGenerated && nextBlockers.length === 0;
|
|
612
765
|
return {
|
|
613
766
|
...gate,
|
|
614
767
|
passed,
|
|
615
768
|
ok: passed,
|
|
616
|
-
status: passed ? 'passed' : 'blocked',
|
|
769
|
+
status: passed ? 'passed' : reviewReportCompleted ? 'review_completed' : 'blocked',
|
|
770
|
+
verified_level: passed ? gate?.verified_level || 'verified' : reviewReportCompleted ? 'verified_partial' : 'blocked',
|
|
771
|
+
review_report_completed: reviewReportCompleted,
|
|
772
|
+
completion_scope: reviewReportCompleted ? 'capture_imagegen_ocr_and_ux_report' : gate?.completion_scope || null,
|
|
617
773
|
gpt_image_2_callout_generated: codexGenerated,
|
|
618
774
|
generated_image_evidence: responseEvidence.ok === true,
|
|
619
775
|
imagegen_response_evidence: responseEvidence,
|
|
620
|
-
source_screenshot_min_resolution_passed:
|
|
621
|
-
issue_ledger_real_extraction:
|
|
776
|
+
source_screenshot_min_resolution_passed: sourceScreenshotMinResolutionPassed,
|
|
777
|
+
issue_ledger_real_extraction: issueLedgerRealExtraction,
|
|
622
778
|
blockers: nextBlockers
|
|
623
779
|
};
|
|
624
780
|
}
|
|
@@ -631,6 +787,9 @@ async function validateImagegenResponseEvidence(response = null, dir = process.c
|
|
|
631
787
|
blockers.push('imagegen_response_schema_invalid');
|
|
632
788
|
if (response.ok !== true || response.status !== 'generated')
|
|
633
789
|
blockers.push(response.blocker || 'imagegen_response_not_generated');
|
|
790
|
+
if (response.image_output_partial_frame === true || response.payload_summary?.image_output_partial_frame === true) {
|
|
791
|
+
blockers.push('imagegen_response_partial_image_output');
|
|
792
|
+
}
|
|
634
793
|
const evidenceClass = String(response.evidence_class || '');
|
|
635
794
|
blockers.push(...imagegenEvidenceClassBlockers('imagegen_response', evidenceClass));
|
|
636
795
|
const source = String(response.output_source || '');
|
|
@@ -638,6 +797,9 @@ async function validateImagegenResponseEvidence(response = null, dir = process.c
|
|
|
638
797
|
blockers.push('imagegen_response_output_source_invalid');
|
|
639
798
|
const outputPath = String(response.output_image_path || '');
|
|
640
799
|
const expectedSha = String(response.output_sha256 || response.output_image_sha256 || '');
|
|
800
|
+
const generatedReviewImageId = String(response.generated_review_image_id || '');
|
|
801
|
+
if (!generatedReviewImageId)
|
|
802
|
+
blockers.push('imagegen_response_generated_review_image_id_missing');
|
|
641
803
|
if (!outputPath)
|
|
642
804
|
blockers.push('imagegen_response_output_path_missing');
|
|
643
805
|
if (!expectedSha)
|
|
@@ -665,9 +827,36 @@ async function validateImagegenResponseEvidence(response = null, dir = process.c
|
|
|
665
827
|
output_source: source || null,
|
|
666
828
|
output_image_path: outputPath || null,
|
|
667
829
|
output_sha256: expectedSha || null,
|
|
830
|
+
generated_review_image_id: generatedReviewImageId || null,
|
|
668
831
|
blockers: [...new Set(blockers)]
|
|
669
832
|
};
|
|
670
833
|
}
|
|
834
|
+
function validateGeneratedImageEvidenceBinding(response, generatedLedger, extractionReport, issueLedger) {
|
|
835
|
+
if (!response || response.ok !== true || response.status !== 'generated')
|
|
836
|
+
return [];
|
|
837
|
+
const blockers = [];
|
|
838
|
+
const generatedId = String(response.generated_review_image_id || '');
|
|
839
|
+
const responseSha = String(response.output_sha256 || response.output_image_sha256 || '');
|
|
840
|
+
const images = Array.isArray(generatedLedger?.generated_review_images) ? generatedLedger.generated_review_images : [];
|
|
841
|
+
const image = images.find((row) => String(row?.id || '') === generatedId);
|
|
842
|
+
if (generatedId && !image)
|
|
843
|
+
blockers.push('imagegen_response_generated_review_image_id_not_in_ledger');
|
|
844
|
+
if (image && responseSha && String(image.sha256 || '') !== responseSha)
|
|
845
|
+
blockers.push('imagegen_response_generated_review_image_sha256_mismatch');
|
|
846
|
+
if (extractionReport) {
|
|
847
|
+
if (String(extractionReport.generated_image_id || '') !== generatedId)
|
|
848
|
+
blockers.push('callout_extraction_generated_image_id_mismatch');
|
|
849
|
+
if (responseSha && String(extractionReport.generated_image_sha256 || '') !== responseSha)
|
|
850
|
+
blockers.push('callout_extraction_generated_image_sha256_mismatch');
|
|
851
|
+
}
|
|
852
|
+
for (const issue of Array.isArray(issueLedger?.issues) ? issueLedger.issues : []) {
|
|
853
|
+
if (String(issue.generated_review_image_id || '') !== generatedId)
|
|
854
|
+
blockers.push(`issue_generated_image_id_mismatch:${issue.id || 'unknown'}`);
|
|
855
|
+
if (responseSha && String(issue.generated_image_sha256 || '') !== responseSha)
|
|
856
|
+
blockers.push(`issue_generated_image_sha256_mismatch:${issue.id || 'unknown'}`);
|
|
857
|
+
}
|
|
858
|
+
return [...new Set(blockers)];
|
|
859
|
+
}
|
|
671
860
|
function rootFromMissionDir(dir) {
|
|
672
861
|
const marker = `${path.sep}.sneakoscope${path.sep}missions${path.sep}`;
|
|
673
862
|
const idx = String(dir).indexOf(marker);
|
|
@@ -738,6 +927,10 @@ function promptForRun(command, args) {
|
|
|
738
927
|
const source = readOption(args, '--image', null) || readOption(args, '--screenshot', null) || readOption(args, '--mission', null) || 'latest Codex Chrome Extension or native Computer Use screenshot';
|
|
739
928
|
return `$${routeForCommand(command).replace(/^\$/, '')} ${source} with gpt-image-2 callouts${flag(args, '--fix') ? ', then fix the issues' : ''}`;
|
|
740
929
|
}
|
|
930
|
+
function sourceImageFromContract(contract) {
|
|
931
|
+
const sources = contract?.answers?.IMAGE_UX_REVIEW_SOURCE_IMAGES;
|
|
932
|
+
return Array.isArray(sources) && sources.length ? String(sources[0]) : null;
|
|
933
|
+
}
|
|
741
934
|
function missingMission(args) {
|
|
742
935
|
const result = { schema: 'sks.image-ux-review-status.v2', ok: false, status: 'missing_mission' };
|
|
743
936
|
if (flag(args, '--json'))
|