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
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
-
import { projectRoot, readJson, writeJsonAtomic, appendJsonl, nowIso,
|
|
2
|
+
import { projectRoot, readJson, writeJsonAtomic, appendJsonl, nowIso, sha256, packageRoot } from './fsx.js';
|
|
3
3
|
import { looksInteractiveCommand, interactiveCommandReason } from './no-question-guard.js';
|
|
4
4
|
import { loadStateForSession, missionDir, sessionStateKey, setCurrent, stateFileForSession } from './mission.js';
|
|
5
5
|
import { checkDbOperation, dbBlockReason, handleMadSksUserConfirmation } from './db-safety.js';
|
|
@@ -42,6 +42,7 @@ import { activeAuthoritativeSksSkillRefresh, activeGoalOverlayContext, attachAut
|
|
|
42
42
|
import { sealedSubagentRoutingContext, subagentRouteContext } from './hooks-runtime/subagent-context.js';
|
|
43
43
|
export { loadHookPayload, normalizeHookResult };
|
|
44
44
|
export { refreshOfficialSubagentCompletionArtifacts };
|
|
45
|
+
export { selftestCodexCommitHooks } from './hooks-runtime/codex-commit-hooks-selftest.js';
|
|
45
46
|
async function loadState(root, payload = {}) {
|
|
46
47
|
const sessionKey = conversationId(payload);
|
|
47
48
|
if (!explicitConversationId(payload))
|
|
@@ -863,8 +864,9 @@ async function hookStop(root, state, payload, noQuestion, sessionKey = null) {
|
|
|
863
864
|
await refreshOfficialSubagentCompletionArtifacts(root, state, last, sessionKey).catch(() => null);
|
|
864
865
|
}
|
|
865
866
|
const routeDecision = await evaluateStop(root, state, payload, { noQuestion });
|
|
866
|
-
if (routeDecision)
|
|
867
|
+
if (routeDecision && !successfulAppNarutoStopNeedsVisibleSummary(state, routeDecision)) {
|
|
867
868
|
return routeDecision;
|
|
869
|
+
}
|
|
868
870
|
if (!noQuestion) {
|
|
869
871
|
const languageBasis = state?.prompt || state?.task || extractUserPrompt(payload) || last;
|
|
870
872
|
if (!hasHonestMode(last)) {
|
|
@@ -892,13 +894,23 @@ async function hookStop(root, state, payload, noQuestion, sessionKey = null) {
|
|
|
892
894
|
}
|
|
893
895
|
if (state?.honest_loop_required)
|
|
894
896
|
await resolveHonestModeLoopback(root, state, sessionKey);
|
|
895
|
-
return { continue: true };
|
|
897
|
+
return routeDecision || { continue: true };
|
|
896
898
|
}
|
|
897
899
|
return {
|
|
898
900
|
decision: 'block',
|
|
899
901
|
reason: 'SKS no-question run is not done. Continue autonomously, fix failing checks, update the active gate file, and do not ask the user.'
|
|
900
902
|
};
|
|
901
903
|
}
|
|
904
|
+
function successfulAppNarutoStopNeedsVisibleSummary(state = {}, routeDecision = {}) {
|
|
905
|
+
const route = String(state?.route || state?.route_command || state?.mode || '')
|
|
906
|
+
.replace(/^\$/, '')
|
|
907
|
+
.replace(/[-_]/g, '')
|
|
908
|
+
.toUpperCase();
|
|
909
|
+
return route === 'NARUTO'
|
|
910
|
+
&& Boolean(state?.session_scope)
|
|
911
|
+
&& routeDecision?.continue === true
|
|
912
|
+
&& !['hard_blocked', 'route_closed'].includes(String(routeDecision?.action || ''));
|
|
913
|
+
}
|
|
902
914
|
async function consumeLightRouteStop(root, payload = {}) {
|
|
903
915
|
const file = path.join(root, '.sneakoscope', 'state', LIGHT_ROUTE_STOP_ARTIFACT);
|
|
904
916
|
const record = await readJson(file, null).catch(() => null);
|
|
@@ -982,12 +994,22 @@ function hasHonestModeUnresolvedGap(text) {
|
|
|
982
994
|
}
|
|
983
995
|
export function honestModeGapLines(text) {
|
|
984
996
|
const issue = /(gap|remaining|unverified|not verified|not run|not complete|incomplete|failed|blocked|blocker|could not|couldn't|missing|미완료|미검증|미실행|실패|차단|누락|못했|못 했|안 했|안함|아직|남은)/i;
|
|
985
|
-
|
|
997
|
+
const lines = String(text || '')
|
|
986
998
|
.split(/\n/)
|
|
987
|
-
.map((line) => line.trim())
|
|
988
|
-
|
|
999
|
+
.map((line) => line.trim());
|
|
1000
|
+
return lines
|
|
1001
|
+
.filter((line, index) => issue.test(line)
|
|
1002
|
+
&& !honestGapLineResolved(line)
|
|
1003
|
+
&& !honestGapHeadingResolved(line, lines.slice(index + 1).find((candidate) => candidate.length > 0)))
|
|
989
1004
|
.slice(0, 12);
|
|
990
1005
|
}
|
|
1006
|
+
function honestGapHeadingResolved(line, nextLine) {
|
|
1007
|
+
const heading = String(line || '').replace(/^#{1,6}\s*/, '').replace(/\s*[::]\s*$/, '').trim();
|
|
1008
|
+
if (!/^(?:remaining|unresolved)\s+(?:gaps?|issues?)$|^(?:남은|미해결)\s*(?:문제|항목|갭|gap)$/i.test(heading)) {
|
|
1009
|
+
return false;
|
|
1010
|
+
}
|
|
1011
|
+
return /^(?:없음|없습니다|없다|none|no(?:ne)?|0|0개)[.!。]?$/i.test(String(nextLine || '').trim());
|
|
1012
|
+
}
|
|
991
1013
|
function honestGapLineResolved(line) {
|
|
992
1014
|
if (/(?:unverified|미검증)\s*:\s*\[\s*\]/i.test(line) && /blockers?\s*:\s*\[\s*\]/i.test(line))
|
|
993
1015
|
return true;
|
|
@@ -1080,83 +1102,3 @@ export async function emitHook(name) {
|
|
|
1080
1102
|
const result = await hookMain(name);
|
|
1081
1103
|
process.stdout.write(`${JSON.stringify(normalizeHookResult(name, result))}\n`);
|
|
1082
1104
|
}
|
|
1083
|
-
export async function selftestCodexCommitHooks() {
|
|
1084
|
-
const root = tmpdir();
|
|
1085
|
-
const hookBin = path.join(packageRoot(), 'dist', 'bin', 'sks.js');
|
|
1086
|
-
const env = { SKS_DISABLE_UPDATE_CHECK: '1' };
|
|
1087
|
-
const setup = await runProcess(process.execPath, [hookBin, 'setup', '--install-scope', 'project'], { cwd: root, env, timeoutMs: 15000, maxOutputBytes: 128 * 1024 });
|
|
1088
|
-
if (setup.code !== 0)
|
|
1089
|
-
throw new Error(`selftest failed: commit setup ${setup.code}: ${setup.stderr}`);
|
|
1090
|
-
const runHook = (name, payload) => runProcess(process.execPath, [hookBin, 'hook', name], { cwd: root, input: JSON.stringify({ cwd: root, ...payload }), env, timeoutMs: 15000, maxOutputBytes: 128 * 1024 });
|
|
1091
|
-
const id = 'commit-selftest';
|
|
1092
|
-
const hook = await runHook('user-prompt-submit', { conversation_id: id, action: 'codex_git_commit', prompt: 'Generate a git commit message for the staged diff.' });
|
|
1093
|
-
if (hook.code !== 0)
|
|
1094
|
-
throw new Error(`selftest failed: commit hook ${hook.code}: ${hook.stderr}`);
|
|
1095
|
-
const hookJson = JSON.parse(hook.stdout);
|
|
1096
|
-
if (hookJson.decision === 'block' || hookJson.hookSpecificOutput?.additionalContext || !String(hookJson.systemMessage || '').includes('git action'))
|
|
1097
|
-
throw new Error('selftest failed: commit route bypass');
|
|
1098
|
-
const stop = await runHook('stop', { conversation_id: id, last_assistant_message: 'Fix Codex App commit message hook bypass' });
|
|
1099
|
-
if (stop.code !== 0)
|
|
1100
|
-
throw new Error(`selftest failed: commit stop ${stop.code}: ${stop.stderr}`);
|
|
1101
|
-
const stopJson = JSON.parse(stop.stdout);
|
|
1102
|
-
if (stopJson.decision === 'block' || !String(stopJson.systemMessage || '').includes('accepted without route finalization'))
|
|
1103
|
-
throw new Error('selftest failed: commit stop bypass');
|
|
1104
|
-
const commitPushId = 'commit-push-selftest';
|
|
1105
|
-
const appCommitPushHook = await runHook('user-prompt-submit', { conversation_id: commitPushId, action: 'Codex App Git Actions Commit and Push', prompt: 'Commit and push changes.' });
|
|
1106
|
-
if (appCommitPushHook.code !== 0)
|
|
1107
|
-
throw new Error(`selftest failed: app commit-push hook ${appCommitPushHook.code}: ${appCommitPushHook.stderr}`);
|
|
1108
|
-
const appCommitPushJson = JSON.parse(appCommitPushHook.stdout);
|
|
1109
|
-
if (appCommitPushJson.decision === 'block' || appCommitPushJson.hookSpecificOutput?.additionalContext || !String(appCommitPushJson.systemMessage || '').includes('git action'))
|
|
1110
|
-
throw new Error('selftest failed: app commit-push route bypass');
|
|
1111
|
-
const appCommitPushStop = await runHook('stop', { conversation_id: commitPushId, last_assistant_message: 'Commit and push complete.' });
|
|
1112
|
-
if (appCommitPushStop.code !== 0)
|
|
1113
|
-
throw new Error(`selftest failed: app commit-push stop ${appCommitPushStop.code}: ${appCommitPushStop.stderr}`);
|
|
1114
|
-
if (JSON.parse(appCommitPushStop.stdout).decision === 'block')
|
|
1115
|
-
throw new Error('selftest failed: app commit-push stop bypass');
|
|
1116
|
-
const appPushId = 'app-push-selftest';
|
|
1117
|
-
const appPushHook = await runHook('user-prompt-submit', { conversation_id: appPushId, metadata: { source: 'codex_app', action: 'Git Actions Push' }, prompt: 'Push changes.' });
|
|
1118
|
-
if (appPushHook.code !== 0)
|
|
1119
|
-
throw new Error(`selftest failed: app push hook ${appPushHook.code}: ${appPushHook.stderr}`);
|
|
1120
|
-
const appPushJson = JSON.parse(appPushHook.stdout);
|
|
1121
|
-
if (appPushJson.decision === 'block' || appPushJson.hookSpecificOutput?.additionalContext || !String(appPushJson.systemMessage || '').includes('git action'))
|
|
1122
|
-
throw new Error('selftest failed: app push metadata route bypass');
|
|
1123
|
-
const appPushStop = await runHook('stop', { conversation_id: appPushId, metadata: { source: 'codex_app', action: 'Git Actions Push' }, last_assistant_message: 'Done.' });
|
|
1124
|
-
if (appPushStop.code !== 0)
|
|
1125
|
-
throw new Error(`selftest failed: app push stop ${appPushStop.code}: ${appPushStop.stderr}`);
|
|
1126
|
-
if (JSON.parse(appPushStop.stdout).decision === 'block')
|
|
1127
|
-
throw new Error('selftest failed: app push metadata stop bypass');
|
|
1128
|
-
const metadataLightId = 'metadata-light-commit-push-selftest';
|
|
1129
|
-
const metadataLightHook = await runHook('user-prompt-submit', { conversation_id: metadataLightId, prompt: 'Commit and push changes.' });
|
|
1130
|
-
if (metadataLightHook.code !== 0)
|
|
1131
|
-
throw new Error(`selftest failed: metadata-light commit-push hook ${metadataLightHook.code}: ${metadataLightHook.stderr}`);
|
|
1132
|
-
const metadataLightJson = JSON.parse(metadataLightHook.stdout);
|
|
1133
|
-
if (metadataLightJson.decision === 'block' || metadataLightJson.hookSpecificOutput?.additionalContext || !String(metadataLightJson.systemMessage || '').includes('git action'))
|
|
1134
|
-
throw new Error('selftest failed: metadata-light app commit-push route bypass');
|
|
1135
|
-
const metadataLightStop = await runHook('stop', { conversation_id: metadataLightId, last_assistant_message: 'Commit and push complete.' });
|
|
1136
|
-
if (metadataLightStop.code !== 0)
|
|
1137
|
-
throw new Error(`selftest failed: metadata-light commit-push stop ${metadataLightStop.code}: ${metadataLightStop.stderr}`);
|
|
1138
|
-
if (JSON.parse(metadataLightStop.stdout).decision === 'block')
|
|
1139
|
-
throw new Error('selftest failed: metadata-light commit-push stop bypass');
|
|
1140
|
-
const settingsHook = await runHook('user-prompt-submit', { model: 'future-codex-catalog-model', metadata: { source: 'codex_app_settings', feature: 'speed profile' } });
|
|
1141
|
-
if (settingsHook.code !== 0)
|
|
1142
|
-
throw new Error(`selftest failed: settings hook ${settingsHook.code}: ${settingsHook.stderr}`);
|
|
1143
|
-
const settingsJson = JSON.parse(settingsHook.stdout);
|
|
1144
|
-
if (settingsJson.decision === 'block' || settingsJson.hookSpecificOutput?.additionalContext || !String(settingsJson.systemMessage || '').includes('settings/profile event ignored'))
|
|
1145
|
-
throw new Error('selftest failed: settings/profile event should not route or block');
|
|
1146
|
-
const userHook = await runHook('user-prompt-submit', { prompt: '[커밋 메시지를 생성하지 못했습니다.] 코덱스 앱에서 이 버그 수정해줘' });
|
|
1147
|
-
if (userHook.code !== 0)
|
|
1148
|
-
throw new Error(`selftest failed: user commit hook ${userHook.code}: ${userHook.stderr}`);
|
|
1149
|
-
if (!JSON.parse(userHook.stdout).hookSpecificOutput?.additionalContext?.includes('$Naruto route prepared'))
|
|
1150
|
-
throw new Error('selftest failed: user prompt route');
|
|
1151
|
-
const userCommitPushHook = await runHook('user-prompt-submit', { prompt: '배포하게 커밋하고 푸쉬해줘' });
|
|
1152
|
-
if (userCommitPushHook.code !== 0)
|
|
1153
|
-
throw new Error(`selftest failed: user commit-push hook ${userCommitPushHook.code}: ${userCommitPushHook.stderr}`);
|
|
1154
|
-
const userCommitPushJson = JSON.parse(userCommitPushHook.stdout);
|
|
1155
|
-
if (userCommitPushJson.decision === 'block' || userCommitPushJson.hookSpecificOutput?.additionalContext || !String(userCommitPushJson.systemMessage || '').includes('git action'))
|
|
1156
|
-
throw new Error('selftest failed: user commit-push prompt should bypass route');
|
|
1157
|
-
const userCommitPushStop = await runHook('stop', { last_assistant_message: 'Commit and push complete.' });
|
|
1158
|
-
if (userCommitPushStop.code !== 0)
|
|
1159
|
-
throw new Error(`selftest failed: user commit-push stop ${userCommitPushStop.code}: ${userCommitPushStop.stderr}`);
|
|
1160
|
-
if (JSON.parse(userCommitPushStop.stdout).decision === 'block')
|
|
1161
|
-
throw new Error('selftest failed: user commit-push stop bypass');
|
|
1162
|
-
}
|
|
@@ -27,12 +27,18 @@ export function buildIssueLedgerFromGeneratedCallouts(generatedReviewLedger = {}
|
|
|
27
27
|
blockers.push('ux_review_text_only_fallback');
|
|
28
28
|
if (generated.length && !issues.length)
|
|
29
29
|
blockers.push(generated.some((image) => image.callout_extraction_status === 'pending') ? 'callout_extraction_pending' : 'callout_extraction_schema_failed');
|
|
30
|
+
const primaryGenerated = generated.find((image) => image.id === existing?.generated_image_id)
|
|
31
|
+
|| generated.find((image) => image.id === issues[0]?.generated_review_image_id)
|
|
32
|
+
|| generated[0]
|
|
33
|
+
|| null;
|
|
30
34
|
return {
|
|
31
35
|
schema: 'sks.image-ux-issue-ledger.v3',
|
|
32
36
|
schema_version: 3,
|
|
33
37
|
extraction_source: 'image-ux-generated-review-ledger.json',
|
|
34
38
|
extraction_method: 'codex_exec_resume_output_schema_preferred',
|
|
35
39
|
extraction_schema: 'schemas/codex/image-ux-issue-ledger.schema.json',
|
|
40
|
+
generated_image_id: primaryGenerated?.id || existing?.generated_image_id || issues[0]?.generated_review_image_id || null,
|
|
41
|
+
generated_image_sha256: primaryGenerated?.sha256 || existing?.generated_image_sha256 || issues[0]?.generated_image_sha256 || null,
|
|
36
42
|
extracted_from_generated_callout: issues.length > 0,
|
|
37
43
|
issues,
|
|
38
44
|
blocking_issue_count: issues.filter((issue) => ['P0', 'P1'].includes(issue.severity) && !['fixed', 'accepted_not_applicable'].includes(issue.status)).length,
|
|
@@ -12,9 +12,11 @@ import { discoverCodexAppGeneratedImage } from './codex-app-generated-image-disc
|
|
|
12
12
|
import { writeImageArtifactPathContract } from '../image/image-artifact-path-contract.js';
|
|
13
13
|
import { registerImageArtifact } from '../image/image-artifact-registry.js';
|
|
14
14
|
const DEFAULT_OPENAI_IMAGE_EDITS_ENDPOINT = 'https://api.openai.com/v1/images/edits';
|
|
15
|
+
export const DEFAULT_IMAGEGEN_FETCH_TIMEOUT_MS = 180000;
|
|
16
|
+
const responseDeadlines = new WeakMap();
|
|
15
17
|
export function buildCalloutPrompt(sourceScreenId, context = {}) {
|
|
16
18
|
return [
|
|
17
|
-
'Review this UI screenshot as a senior product
|
|
19
|
+
'Review this UI screenshot as a senior Toss UI/UX product designer.',
|
|
18
20
|
`Screenshot source id: ${sourceScreenId}.`,
|
|
19
21
|
'Output must be a new image artifact, not prose.',
|
|
20
22
|
'Text-only response is invalid.',
|
|
@@ -373,7 +375,7 @@ export function createOpenAIImagesApiAdapter(opts = {}) {
|
|
|
373
375
|
status: 'blocked',
|
|
374
376
|
blocker: auth.blocker,
|
|
375
377
|
setup_guidance: auth.auth_source === 'CODEX_LB_API_KEY'
|
|
376
|
-
? '
|
|
378
|
+
? 'Repair the selected codex-lb credential binding. Completed output from a selected provider can satisfy full evidence; codex-lb used only as a fallback while another provider is selected cannot.'
|
|
377
379
|
: 'Set OPENAI_API_KEY only for an explicit non-Codex Images API fallback, or attach a real Codex App $imagegen output image for full SKS verification.',
|
|
378
380
|
local_only: true
|
|
379
381
|
};
|
|
@@ -463,6 +465,8 @@ export function createOpenAIImagesApiAdapter(opts = {}) {
|
|
|
463
465
|
output_id: meta.output_id,
|
|
464
466
|
output_source: responsesOutputSource,
|
|
465
467
|
image_output_recovered_from_stream: payload?.image_output_recovered_from_stream === true,
|
|
468
|
+
image_output_provenance: payload?.image_output_provenance || 'response.output',
|
|
469
|
+
image_output_partial_frame: false,
|
|
466
470
|
image_artifact_path_contract: imageContract?.artifact_path || null,
|
|
467
471
|
dimensions: { width: meta.width, height: meta.height, format: meta.format },
|
|
468
472
|
latency_ms: Date.now() - started,
|
|
@@ -605,6 +609,8 @@ function codexLbTargetInputs(source = {}) {
|
|
|
605
609
|
const inputs = {};
|
|
606
610
|
if (source?.home)
|
|
607
611
|
inputs.home = source.home;
|
|
612
|
+
if (source?.codexHome)
|
|
613
|
+
inputs.codexHome = source.codexHome;
|
|
608
614
|
if (source?.env)
|
|
609
615
|
inputs.env = source.env;
|
|
610
616
|
if (typeof source?.configText === 'string')
|
|
@@ -625,15 +631,22 @@ export function imagegenCapabilityBlocker(surface = 'Codex App $imagegen') {
|
|
|
625
631
|
blocker: 'imagegen_capability_missing',
|
|
626
632
|
surface,
|
|
627
633
|
model: 'gpt-image-2',
|
|
628
|
-
guidance: 'Run the request
|
|
634
|
+
guidance: 'Run the request with gpt-image-2 through Codex App $imagegen or a selected, ready codex-lb provider and bind the completed generated image path. OPENAI_API_KEY, or codex-lb used while another provider is selected, remains non-Codex fallback evidence. SKS must not fabricate or substitute a text-only review.'
|
|
629
635
|
};
|
|
630
636
|
}
|
|
631
637
|
async function resolveImagesApiAuth(opts = {}) {
|
|
638
|
+
const codexLb = opts.codexLb?.available === true ? opts.codexLb : null;
|
|
639
|
+
const target = opts.codexLbTarget || (codexLb
|
|
640
|
+
? await resolveCodexLbImagegenTarget(codexLbTargetInputs(opts)).catch(() => null)
|
|
641
|
+
: null);
|
|
642
|
+
if (target?.selected === true)
|
|
643
|
+
return codexLbImagesApiAuth(opts, codexLb, target);
|
|
632
644
|
const openAiKey = String(opts.apiKey || process.env.OPENAI_API_KEY || '').trim();
|
|
633
645
|
if (openAiKey) {
|
|
634
646
|
return {
|
|
635
647
|
apiKey: openAiKey,
|
|
636
648
|
auth_source: 'OPENAI_API_KEY',
|
|
649
|
+
api_key_source: null,
|
|
637
650
|
responses_model: responsesImagegenModel(opts),
|
|
638
651
|
responses_model_source: responsesImagegenModel(opts) ? 'explicit' : null,
|
|
639
652
|
endpoint: imageEditsEndpoint(opts.baseUrl || 'https://api.openai.com/v1'),
|
|
@@ -641,11 +654,11 @@ async function resolveImagesApiAuth(opts = {}) {
|
|
|
641
654
|
blocker: null
|
|
642
655
|
};
|
|
643
656
|
}
|
|
644
|
-
const codexLb = opts.codexLb?.available === true ? opts.codexLb : null;
|
|
645
657
|
if (!codexLb) {
|
|
646
658
|
return {
|
|
647
659
|
apiKey: null,
|
|
648
660
|
auth_source: null,
|
|
661
|
+
api_key_source: null,
|
|
649
662
|
responses_model: responsesImagegenModel(opts),
|
|
650
663
|
responses_model_source: null,
|
|
651
664
|
endpoint: imageEditsEndpoint(opts.baseUrl || 'https://api.openai.com/v1'),
|
|
@@ -653,10 +666,12 @@ async function resolveImagesApiAuth(opts = {}) {
|
|
|
653
666
|
blocker: 'openai_api_key_missing'
|
|
654
667
|
};
|
|
655
668
|
}
|
|
656
|
-
|
|
657
|
-
|
|
669
|
+
return codexLbImagesApiAuth(opts, codexLb, target);
|
|
670
|
+
}
|
|
671
|
+
function codexLbImagesApiAuth(opts, codexLb, target) {
|
|
672
|
+
const envKey = codexLb?.env_key || 'CODEX_LB_API_KEY';
|
|
658
673
|
const codexLbKey = String(opts.codexLbApiKey || target?.api_key || '').trim();
|
|
659
|
-
const baseUrl = target?.base_url || codexLb
|
|
674
|
+
const baseUrl = target?.base_url || codexLb?.base_url || opts.baseUrl || '';
|
|
660
675
|
return {
|
|
661
676
|
apiKey: codexLbKey || null,
|
|
662
677
|
auth_source: envKey,
|
|
@@ -677,23 +692,27 @@ function responsesEndpoint(baseUrl = '') {
|
|
|
677
692
|
return /\/responses$/i.test(base) ? base : `${base}/responses`;
|
|
678
693
|
}
|
|
679
694
|
function responsesImagegenModel(opts = {}) {
|
|
680
|
-
return String(opts.responsesModel || process.env.SKS_IMAGEGEN_RESPONSES_MODEL ||
|
|
695
|
+
return String(opts.responsesModel || process.env.SKS_IMAGEGEN_RESPONSES_MODEL || '').trim();
|
|
681
696
|
}
|
|
682
697
|
function imagegenFetchTimeoutMs(opts = {}) {
|
|
683
|
-
const value = Number(opts.fetchTimeoutMs || process.env.SKS_IMAGEGEN_FETCH_TIMEOUT_MS ||
|
|
684
|
-
return Number.isFinite(value) && value > 0 ? value :
|
|
698
|
+
const value = Number(opts.fetchTimeoutMs || process.env.SKS_IMAGEGEN_FETCH_TIMEOUT_MS || DEFAULT_IMAGEGEN_FETCH_TIMEOUT_MS);
|
|
699
|
+
return Number.isFinite(value) && value > 0 ? value : DEFAULT_IMAGEGEN_FETCH_TIMEOUT_MS;
|
|
685
700
|
}
|
|
686
701
|
async function fetchWithTimeout(url, init, timeoutMs) {
|
|
687
702
|
const controller = new AbortController();
|
|
703
|
+
const startedAt = Date.now();
|
|
688
704
|
const timeout = setTimeout(() => controller.abort(new Error(`imagegen_fetch_timeout_${timeoutMs}ms`)), timeoutMs);
|
|
689
705
|
try {
|
|
690
|
-
|
|
706
|
+
const response = await fetch(url, { ...init, signal: controller.signal });
|
|
707
|
+
responseDeadlines.set(response, { controller, startedAt, timeoutMs, timer: timeout });
|
|
708
|
+
return response;
|
|
691
709
|
}
|
|
692
|
-
|
|
710
|
+
catch (err) {
|
|
693
711
|
clearTimeout(timeout);
|
|
712
|
+
throw err;
|
|
694
713
|
}
|
|
695
714
|
}
|
|
696
|
-
async function readResponsePayload(response, timeoutMs =
|
|
715
|
+
async function readResponsePayload(response, timeoutMs = DEFAULT_IMAGEGEN_FETCH_TIMEOUT_MS) {
|
|
697
716
|
const text = await textWithTimeout(response, timeoutMs);
|
|
698
717
|
if (!text)
|
|
699
718
|
return {};
|
|
@@ -708,23 +727,39 @@ async function readResponsePayload(response, timeoutMs = 90000) {
|
|
|
708
727
|
}
|
|
709
728
|
}
|
|
710
729
|
async function textWithTimeout(response, timeoutMs) {
|
|
730
|
+
const deadline = responseDeadlines.get(response);
|
|
731
|
+
if (deadline)
|
|
732
|
+
clearTimeout(deadline.timer);
|
|
733
|
+
const remainingMs = deadline
|
|
734
|
+
? Math.max(1, deadline.timeoutMs - (Date.now() - deadline.startedAt))
|
|
735
|
+
: timeoutMs;
|
|
711
736
|
let timeout = null;
|
|
712
737
|
try {
|
|
713
738
|
return await Promise.race([
|
|
714
739
|
response.text(),
|
|
715
740
|
new Promise((_, reject) => {
|
|
716
|
-
timeout = setTimeout(() =>
|
|
741
|
+
timeout = setTimeout(() => {
|
|
742
|
+
const error = new Error(`imagegen_response_read_timeout_${timeoutMs}ms`);
|
|
743
|
+
deadline?.controller.abort(error);
|
|
744
|
+
void response.body?.cancel(error).catch(() => { });
|
|
745
|
+
reject(error);
|
|
746
|
+
}, remainingMs);
|
|
717
747
|
})
|
|
718
748
|
]);
|
|
719
749
|
}
|
|
720
750
|
finally {
|
|
721
751
|
if (timeout)
|
|
722
752
|
clearTimeout(timeout);
|
|
753
|
+
responseDeadlines.delete(response);
|
|
723
754
|
}
|
|
724
755
|
}
|
|
725
756
|
function findResponsesImageGenerationOutput(payload) {
|
|
757
|
+
if (payload?.image_output_partial_frame === true)
|
|
758
|
+
return null;
|
|
726
759
|
for (const output of Array.isArray(payload?.output) ? payload.output : []) {
|
|
727
760
|
if (String(output?.type || '') === 'image_generation_call') {
|
|
761
|
+
if (String(output?.status || '') === 'partial')
|
|
762
|
+
continue;
|
|
728
763
|
const b64 = typeof output?.result === 'string' ? output.result : output?.result?.b64_json || output?.b64_json || null;
|
|
729
764
|
if (b64)
|
|
730
765
|
return { b64: String(b64), id: output?.id || payload?.id || null };
|
|
@@ -813,10 +848,8 @@ function imagegenRetryOptions(opts = {}) {
|
|
|
813
848
|
return {
|
|
814
849
|
sleep: opts.retrySleep,
|
|
815
850
|
classifyError: (err) => {
|
|
816
|
-
const
|
|
817
|
-
|
|
818
|
-
|| err?.message
|
|
819
|
-
|| '');
|
|
851
|
+
const row = err;
|
|
852
|
+
const code = [row?.code, row?.name, row?.message].filter(Boolean).join(' ');
|
|
820
853
|
if (/AbortError|timeout|abort/i.test(code))
|
|
821
854
|
return { code: 'ETIMEDOUT', status: null };
|
|
822
855
|
if (/ECONNRESET|EAI_AGAIN|ETIMEDOUT|ENOTFOUND|ECONNREFUSED|fetch failed/i.test(code))
|
|
@@ -834,6 +867,9 @@ function summarizeImagegenPayload(payload) {
|
|
|
834
867
|
model: payload?.model || null,
|
|
835
868
|
error_type: payload?.error?.type || null,
|
|
836
869
|
error_code: payload?.error?.code || null,
|
|
870
|
+
image_output_partial_frame: payload?.image_output_partial_frame === true,
|
|
871
|
+
partial_image_output_present: payload?.partial_image_output_present === true,
|
|
872
|
+
image_output_provenance: payload?.image_output_provenance || null,
|
|
837
873
|
output_count: outputs.length,
|
|
838
874
|
output: outputs.slice(0, 8).map((output) => ({
|
|
839
875
|
id: output?.id || null,
|
|
@@ -130,6 +130,7 @@ export function buildRealCalloutExtractionPrompt(input) {
|
|
|
130
130
|
const source = input.sourceScreenshot || {};
|
|
131
131
|
return [
|
|
132
132
|
'Analyze the generated UX review image pixels directly.',
|
|
133
|
+
'The source screenshot is context only; do not analyze it instead of the generated annotated image.',
|
|
133
134
|
'Return only visible numbered callouts from the generated image.',
|
|
134
135
|
'Do not invent issues, requirements, or invisible callouts.',
|
|
135
136
|
'Return bbox coordinates in the generated image coordinate system as [x,y,width,height].',
|