sneakoscope 7.3.0 → 7.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (283) hide show
  1. package/README.md +129 -10
  2. package/crates/sks-core/Cargo.lock +1 -1
  3. package/crates/sks-core/Cargo.toml +1 -1
  4. package/dist/cli/codex-app-command.js +1 -1
  5. package/dist/cli/command-manifest-lite.js +4 -1
  6. package/dist/cli/command-registry.js +2 -0
  7. package/dist/cli/install-helpers-codex-lb-chain.js +42 -0
  8. package/dist/cli/install-helpers-codex-lb-config.js +114 -9
  9. package/dist/cli/install-helpers-install-support.js +6 -16
  10. package/dist/cli/install-helpers.js +788 -600
  11. package/dist/cli/router.js +30 -2
  12. package/dist/commands/codex-app.js +37 -1
  13. package/dist/commands/codex-lb.js +491 -300
  14. package/dist/config/skills-manifest.json +57 -71
  15. package/dist/core/align/align-route.js +127 -0
  16. package/dist/core/codex-app/codex-app-fast-ui-repair.js +8 -4
  17. package/dist/core/codex-app/codex-app-restart.js +83 -0
  18. package/dist/core/codex-app/codex-app-ui-state-snapshot.js +8 -4
  19. package/dist/core/codex-app/desktop-routing-snapshot.js +1 -1
  20. package/dist/core/codex-app/menubar/constants.js +5 -0
  21. package/dist/core/codex-app/menubar/generation-transaction-journal.js +1 -3
  22. package/dist/core/codex-app/menubar/installer.js +18 -0
  23. package/dist/core/codex-app/multi-provider-router.js +22 -10
  24. package/dist/core/codex-app/openrouter-activate.js +26 -1
  25. package/dist/core/codex-app.js +118 -26
  26. package/dist/core/codex-control/codex-app-server-runtime-env.js +86 -0
  27. package/dist/core/codex-control/codex-app-server-v2-client.js +78 -8
  28. package/dist/core/codex-control/codex-task-runner.js +2 -2
  29. package/dist/core/codex-lb/capability-runner.js +204 -0
  30. package/dist/core/codex-lb/capability-types.js +1 -0
  31. package/dist/core/codex-lb/codex-lb-env.js +9 -13
  32. package/dist/core/codex-lb/codex-lb-setup.js +45 -8
  33. package/dist/core/codex-lb/codex-lb-tool-catalog.js +52 -74
  34. package/dist/core/codex-lb/codex-lb-tool-output-recovery.js +1 -1
  35. package/dist/core/codex-lb/desktop-auth-invariant.js +185 -0
  36. package/dist/core/codex-lb/desktop-bridge/client.js +34 -0
  37. package/dist/core/codex-lb/desktop-bridge/header-policy.js +106 -0
  38. package/dist/core/codex-lb/desktop-bridge/http-forward.js +101 -0
  39. package/dist/core/codex-lb/desktop-bridge/index.js +10 -0
  40. package/dist/core/codex-lb/desktop-bridge/launchd.js +81 -0
  41. package/dist/core/codex-lb/desktop-bridge/location-rewrite.js +27 -0
  42. package/dist/core/codex-lb/desktop-bridge/security.js +274 -0
  43. package/dist/core/codex-lb/desktop-bridge/server.js +179 -0
  44. package/dist/core/codex-lb/desktop-bridge/state.js +119 -0
  45. package/dist/core/codex-lb/desktop-bridge/types.js +16 -0
  46. package/dist/core/codex-lb/desktop-bridge/websocket-forward.js +151 -0
  47. package/dist/core/codex-lb/desktop-center-credentials.js +189 -0
  48. package/dist/core/codex-lb/desktop-controller.js +1410 -0
  49. package/dist/core/codex-lb/desktop-mode.js +32 -0
  50. package/dist/core/codex-lb/desktop-real-evidence.js +168 -0
  51. package/dist/core/codex-lb/desktop-service.js +613 -0
  52. package/dist/core/codex-lb/legacy-migration.js +575 -0
  53. package/dist/core/codex-lb/migration-receipt.js +246 -0
  54. package/dist/core/codex-lb/probes/auxiliary-surfaces-probe.js +60 -0
  55. package/dist/core/codex-lb/probes/bridge-probe.js +75 -0
  56. package/dist/core/codex-lb/probes/catalog-probe.js +128 -0
  57. package/dist/core/codex-lb/probes/computer-use-probe.js +50 -0
  58. package/dist/core/codex-lb/probes/image-generation-probe.js +47 -0
  59. package/dist/core/codex-lb/probes/probe-evidence.js +39 -0
  60. package/dist/core/codex-lb/probes/voice-realtime-probe.js +44 -0
  61. package/dist/core/codex-lb/trusted-deep-evidence.js +403 -0
  62. package/dist/core/codex-native/core-skill-manifest.js +22 -6
  63. package/dist/core/codex-runtime/codex-desktop-config-policy.js +2 -1
  64. package/dist/core/commands/align-command.js +220 -0
  65. package/dist/core/commands/image-ux-review-command.js +280 -68
  66. package/dist/core/commands/naruto-command.js +257 -8
  67. package/dist/core/commands/ppt-command.js +2 -2
  68. package/dist/core/commands/telegram-command.js +402 -34
  69. package/dist/core/commands/triwiki-command.js +11 -1
  70. package/dist/core/commands/triwiki-graph-command.js +160 -0
  71. package/dist/core/commands/wiki-command.js +100 -20
  72. package/dist/core/doctor/skill-legacy-surface.js +1 -3
  73. package/dist/core/errors/message.js +3 -0
  74. package/dist/core/evidence/evidence-freshness.js +14 -0
  75. package/dist/core/feature-fixtures.js +9 -1
  76. package/dist/core/feature-registry.js +5 -3
  77. package/dist/core/fsx.js +7 -3
  78. package/dist/core/hooks-runtime/codex-commit-hooks-selftest.js +82 -0
  79. package/dist/core/hooks-runtime/context-graph-freshness-preflight.js +113 -0
  80. package/dist/core/hooks-runtime/hook-context.js +15 -2
  81. package/dist/core/hooks-runtime/managed-skill-generation-heal.js +46 -0
  82. package/dist/core/hooks-runtime/naruto-terminal-finalization.js +1 -0
  83. package/dist/core/hooks-runtime/official-subagent-lifecycle.js +1 -1
  84. package/dist/core/hooks-runtime/tool-output-quarantine.js +1 -1
  85. package/dist/core/hooks-runtime.js +33 -89
  86. package/dist/core/image-ux-review/callout-extraction.js +6 -0
  87. package/dist/core/image-ux-review/imagegen-adapter.js +64 -23
  88. package/dist/core/image-ux-review/real-callout-extractor.js +1 -0
  89. package/dist/core/image-ux-review.js +230 -12
  90. package/dist/core/imagegen/codex-lb-imagegen-target.js +21 -7
  91. package/dist/core/imagegen/imagegen-capability.js +10 -4
  92. package/dist/core/imagegen/require-imagegen.js +29 -5
  93. package/dist/core/init/skills.js +25 -13
  94. package/dist/core/init.js +1 -1
  95. package/dist/core/install/installed-package-smoke.js +157 -4
  96. package/dist/core/managed-assets/managed-assets-manifest.js +2 -1
  97. package/dist/core/managed-path-safety.js +8 -10
  98. package/dist/core/mcp-config/backup.js +2 -4
  99. package/dist/core/mcp-config/config-reader.js +2 -4
  100. package/dist/core/mcp-config/scope.js +3 -5
  101. package/dist/core/naruto/context-graph-advisor-scope.js +242 -0
  102. package/dist/core/naruto/context-graph-advisor.js +0 -0
  103. package/dist/core/perf/release-latency-slo.js +12 -6
  104. package/dist/core/pipeline-internals/runtime-core.js +78 -0
  105. package/dist/core/pipeline-internals/runtime-gates.js +5 -2
  106. package/dist/core/policy/latest-version-guidance.js +113 -0
  107. package/dist/core/ppt.js +8 -8
  108. package/dist/core/preflight/parallel-preflight-engine.js +1 -1
  109. package/dist/core/proof/auto-finalize.js +98 -6
  110. package/dist/core/proof/route-proof-gate.js +12 -1
  111. package/dist/core/proof/route-proof-policy.js +8 -8
  112. package/dist/core/release/gate-manifest.js +32 -1
  113. package/dist/core/release/npm-stage-contract.js +21 -0
  114. package/dist/core/release/package-size-budget.js +2 -2
  115. package/dist/core/release/release-gate-contract.js +5 -0
  116. package/dist/core/release/release-proof-io.js +1 -3
  117. package/dist/core/release/release-real-contract.js +5 -4
  118. package/dist/core/release/release-stamp-proof.js +1 -3
  119. package/dist/core/release/stage-publish.js +27 -0
  120. package/dist/core/remote/audit-idempotency.js +50 -18
  121. package/dist/core/remote/codex-public-activity.js +283 -0
  122. package/dist/core/remote/index.js +1 -0
  123. package/dist/core/remote/ssh-worker-client.js +4 -1
  124. package/dist/core/remote/worker.js +206 -27
  125. package/dist/core/research/research-adversarial-review.js +1 -3
  126. package/dist/core/research/research-claim-synthesizer.js +1 -3
  127. package/dist/core/research/research-falsification-runner.js +1 -3
  128. package/dist/core/research/research-source-evidence.js +1 -3
  129. package/dist/core/responses-stream.js +65 -19
  130. package/dist/core/routes/constants.js +1 -1
  131. package/dist/core/routes/design-policy.js +7 -2
  132. package/dist/core/routes/dollar-manifest-lite.js +2 -4
  133. package/dist/core/routes/evidence.js +1 -1
  134. package/dist/core/routes.js +30 -10
  135. package/dist/core/safety/command-contract/contracts.js +2 -0
  136. package/dist/core/safety/command-contract/types.js +1 -1
  137. package/dist/core/search/context-graph-seeds.js +205 -0
  138. package/dist/core/search/context.js +0 -0
  139. package/dist/core/search/provider.js +2 -2
  140. package/dist/core/secret-redaction.js +6 -0
  141. package/dist/core/subagents/naruto-help-contract.js +2 -1
  142. package/dist/core/subagents/naruto-host-credentials.js +161 -0
  143. package/dist/core/subagents/official-subagent-preparation.js +4 -1
  144. package/dist/core/subagents/official-subagent-prompt.js +44 -2
  145. package/dist/core/subagents/official-subagent-runner.js +26 -8
  146. package/dist/core/subagents/triwiki-attention.js +135 -123
  147. package/dist/core/telegram/bot-api.js +85 -10
  148. package/dist/core/telegram/center-snapshot.js +718 -0
  149. package/dist/core/telegram/config.js +158 -31
  150. package/dist/core/telegram/hub.js +288 -33
  151. package/dist/core/telegram/index.js +2 -0
  152. package/dist/core/telegram/ingress-spool.js +419 -0
  153. package/dist/core/telegram/ledgers.js +188 -29
  154. package/dist/core/telegram/messages.js +26 -6
  155. package/dist/core/telegram/owner-lock.js +10 -7
  156. package/dist/core/telegram/runtime-projection.js +38 -3
  157. package/dist/core/telegram/runtime.js +298 -63
  158. package/dist/core/telegram/service.js +137 -8
  159. package/dist/core/telegram/setup.js +291 -73
  160. package/dist/core/text/strings.js +3 -0
  161. package/dist/core/triwiki/code-pack.js +12 -120
  162. package/dist/core/triwiki/context-graph/compiler/cache-key.js +277 -0
  163. package/dist/core/triwiki/context-graph/compiler/extract.js +104 -0
  164. package/dist/core/triwiki/context-graph/compiler/freshness.js +108 -0
  165. package/dist/core/triwiki/context-graph/compiler/index.js +237 -0
  166. package/dist/core/triwiki/context-graph/compiler/merge.js +0 -0
  167. package/dist/core/triwiki/context-graph/compiler/serialize.js +115 -0
  168. package/dist/core/triwiki/context-graph/contracts.js +236 -0
  169. package/dist/core/triwiki/context-graph/extractors/code/builders.js +150 -0
  170. package/dist/core/triwiki/context-graph/extractors/code/declarations.js +161 -0
  171. package/dist/core/triwiki/context-graph/extractors/code/index.js +265 -0
  172. package/dist/core/triwiki/context-graph/extractors/code/inventory.js +237 -0
  173. package/dist/core/triwiki/context-graph/extractors/code/module-graph.js +252 -0
  174. package/dist/core/triwiki/context-graph/extractors/code/modules.js +79 -0
  175. package/dist/core/triwiki/context-graph/extractors/code/references.js +184 -0
  176. package/dist/core/triwiki/context-graph/extractors/code/selection.js +100 -0
  177. package/dist/core/triwiki/context-graph/extractors/code/ts-config.js +161 -0
  178. package/dist/core/triwiki/context-graph/extractors/code/types.js +18 -0
  179. package/dist/core/triwiki/context-graph/extractors/evidence/claims.js +289 -0
  180. package/dist/core/triwiki/context-graph/extractors/evidence/index.js +54 -0
  181. package/dist/core/triwiki/context-graph/extractors/evidence/proof-index.js +213 -0
  182. package/dist/core/triwiki/context-graph/extractors/evidence/proofs.js +266 -0
  183. package/dist/core/triwiki/context-graph/extractors/evidence/redaction.js +177 -0
  184. package/dist/core/triwiki/context-graph/extractors/evidence/shared.js +0 -0
  185. package/dist/core/triwiki/context-graph/extractors/evidence/sources.js +154 -0
  186. package/dist/core/triwiki/context-graph/extractors/index.js +7 -0
  187. package/dist/core/triwiki/context-graph/extractors/topology/commands.js +207 -0
  188. package/dist/core/triwiki/context-graph/extractors/topology/gate-edges.js +201 -0
  189. package/dist/core/triwiki/context-graph/extractors/topology/gates.js +182 -0
  190. package/dist/core/triwiki/context-graph/extractors/topology/globs.js +124 -0
  191. package/dist/core/triwiki/context-graph/extractors/topology/index.js +92 -0
  192. package/dist/core/triwiki/context-graph/extractors/topology/routes.js +135 -0
  193. package/dist/core/triwiki/context-graph/extractors/topology/shared.js +215 -0
  194. package/dist/core/triwiki/context-graph/graph-index.js +143 -0
  195. package/dist/core/triwiki/context-graph/ids.js +0 -0
  196. package/dist/core/triwiki/context-graph/lint/index.js +0 -0
  197. package/dist/core/triwiki/context-graph/lint/rules.js +232 -0
  198. package/dist/core/triwiki/context-graph/lint/warnings.js +93 -0
  199. package/dist/core/triwiki/context-graph/paths.js +116 -0
  200. package/dist/core/triwiki/context-graph/profiles.js +78 -0
  201. package/dist/core/triwiki/context-graph/projections/anchors.js +91 -0
  202. package/dist/core/triwiki/context-graph/projections/attention.js +58 -0
  203. package/dist/core/triwiki/context-graph/projections/code-pack-workspace.js +62 -0
  204. package/dist/core/triwiki/context-graph/projections/code-pack.js +144 -0
  205. package/dist/core/triwiki/context-graph/projections/index.js +7 -0
  206. package/dist/core/triwiki/context-graph/projections/module-view.js +140 -0
  207. package/dist/core/triwiki/context-graph/projections/node-summary.js +118 -0
  208. package/dist/core/triwiki/context-graph/projections/pack-contract.js +6 -0
  209. package/dist/core/triwiki/context-graph/query/explain.js +0 -0
  210. package/dist/core/triwiki/context-graph/query/index.js +178 -0
  211. package/dist/core/triwiki/context-graph/query/load.js +116 -0
  212. package/dist/core/triwiki/context-graph/query/pack.js +117 -0
  213. package/dist/core/triwiki/context-graph/query/rank.js +142 -0
  214. package/dist/core/triwiki/context-graph/query/ranking-config.js +64 -0
  215. package/dist/core/triwiki/context-graph/query/seeds.js +241 -0
  216. package/dist/core/triwiki/context-graph/query/snapshot-cache.js +72 -0
  217. package/dist/core/triwiki/context-graph/query/traverse.js +154 -0
  218. package/dist/core/triwiki/context-graph/query-types.js +29 -0
  219. package/dist/core/triwiki/context-graph/store/compile-lock.js +21 -0
  220. package/dist/core/triwiki/context-graph/store/event-log.js +48 -0
  221. package/dist/core/triwiki/context-graph/store/fragment-cache.js +85 -0
  222. package/dist/core/triwiki/context-graph/store/graph-status.js +85 -0
  223. package/dist/core/triwiki/context-graph/store/snapshot-store.js +155 -0
  224. package/dist/core/triwiki/triwiki-proof-bank-index-repair.js +115 -0
  225. package/dist/core/triwiki/triwiki-proof-bank-index-store.js +290 -0
  226. package/dist/core/triwiki/triwiki-proof-bank-index.js +126 -0
  227. package/dist/core/triwiki/triwiki-proof-bank.js +7 -1
  228. package/dist/core/verification/context-graph-affected.js +271 -0
  229. package/dist/core/version.js +1 -1
  230. package/dist/native/sks-menubar/Sources/AlertFactory.swift +11 -3
  231. package/dist/native/sks-menubar/Sources/AppDelegate.swift +9 -0
  232. package/dist/native/sks-menubar/Sources/ControlCenterWindowController.swift +25 -3
  233. package/dist/native/sks-menubar/Sources/ControlKit.swift +19 -8
  234. package/dist/native/sks-menubar/Sources/DiagnosticsViewController.swift +1 -2
  235. package/dist/native/sks-menubar/Sources/MCPServersViewController.swift +45 -17
  236. package/dist/native/sks-menubar/Sources/OverviewSummary.swift +162 -0
  237. package/dist/native/sks-menubar/Sources/OverviewViewController.swift +136 -190
  238. package/dist/native/sks-menubar/Sources/ProcessClient.swift +107 -11
  239. package/dist/native/sks-menubar/Sources/ProvidersMultiProvider.swift +5 -1
  240. package/dist/native/sks-menubar/Sources/ProvidersOpenRouter.swift +16 -4
  241. package/dist/native/sks-menubar/Sources/ProvidersRoleModels.swift +4 -1
  242. package/dist/native/sks-menubar/Sources/ProvidersRoutingTruth.swift +131 -0
  243. package/dist/native/sks-menubar/Sources/ProvidersViewController.swift +288 -341
  244. package/dist/native/sks-menubar/Sources/RemoteTelegramCenterState.swift +447 -0
  245. package/dist/native/sks-menubar/Sources/RemoteTelegramRendering.swift +484 -0
  246. package/dist/native/sks-menubar/Sources/RemoteTelegramViewController.swift +364 -115
  247. package/dist/native/sks-menubar/Sources/SettingsViewController.swift +73 -12
  248. package/dist/native/sks-menubar/Sources/SidebarItem.swift +3 -3
  249. package/dist/native/sks-menubar/Sources/StatusItemController.swift +13 -13
  250. package/dist/native/sks-menubar/Sources/TelegramHubSupervisor.swift +469 -0
  251. package/dist/native/sks-menubar/Sources/UpdatesViewController.swift +1 -6
  252. package/dist/native/sks-menubar/Tests/AppRuntimeTestSupport.swift +12 -0
  253. package/dist/native/sks-menubar/Tests/NativeTelegramRegressionRunner.swift +55 -0
  254. package/dist/native/sks-menubar/Tests/ProcessClientTests.swift +33 -0
  255. package/dist/native/sks-menubar/Tests/ProvidersViewControllerTests.swift +142 -0
  256. package/dist/native/sks-menubar/Tests/RemoteTelegramCenterStateTests.swift +56 -0
  257. package/dist/native/sks-menubar/Tests/TelegramHubSupervisorTests.swift +55 -0
  258. package/dist/scripts/codex-lb-catalog-passthrough-check.js +120 -0
  259. package/dist/scripts/codex-lb-desktop-auth-invariant-check.js +169 -0
  260. package/dist/scripts/codex-lb-desktop-bridge-check.js +394 -0
  261. package/dist/scripts/codex-lb-desktop-bridge-latency-check.js +196 -0
  262. package/dist/scripts/codex-lb-desktop-capabilities-check.js +287 -0
  263. package/dist/scripts/codex-lb-desktop-real-evidence-check.js +123 -0
  264. package/dist/scripts/codex-lb-fast-mode-truth-check.js +10 -3
  265. package/dist/scripts/codex-lb-fast-ui-preservation-check.js +81 -40
  266. package/dist/scripts/codex-lb-gpt56-fast-profile-check.js +99 -37
  267. package/dist/scripts/context-graph-check.js +191 -0
  268. package/dist/scripts/doctor-fixes-codex-app-fast-ui-check.js +1 -1
  269. package/dist/scripts/gate-policy-audit-check.js +1 -1
  270. package/dist/scripts/helper-dedup-check.js +0 -2
  271. package/dist/scripts/installed-package-smoke-check.js +4 -1
  272. package/dist/scripts/latest-version-guidance-check.js +74 -0
  273. package/dist/scripts/release-metadata-check.js +21 -4
  274. package/dist/scripts/release-real-check.js +7 -0
  275. package/dist/scripts/ux-review-imagegen-blackbox-check.js +25 -1
  276. package/dist/scripts/ux-review-run-wires-imagegen-check.js +9 -1
  277. package/package.json +8 -3
  278. package/release-gates.v2.json +176 -1
  279. package/runtime-required-scripts.json +30 -0
  280. package/schemas/triwiki/context-graph.schema.json +119 -0
  281. package/dist/cli/install-helpers-codex-lb-selftest-chain.js +0 -112
  282. package/dist/cli/install-helpers-codex-lb-selftest.js +0 -481
  283. package/dist/core/triwiki/code-index-scanner.js +0 -309
@@ -0,0 +1,113 @@
1
+ import { CONTEXT_GRAPH_REPAIR_COMMAND } from '../triwiki/context-graph/contracts.js';
2
+ import { contextGraphCacheKey } from '../triwiki/context-graph/compiler/cache-key.js';
3
+ import { contextGraphStatus } from '../triwiki/context-graph/store/graph-status.js';
4
+ import { readContextGraphMeta } from '../triwiki/context-graph/store/snapshot-store.js';
5
+ import { contextGraphExtractors } from '../triwiki/context-graph/extractors/index.js';
6
+ export const CONTEXT_GRAPH_FRESHNESS_PREFLIGHT_SCHEMA = 'sks.context-graph-freshness-preflight.v1';
7
+ const CACHE_KEY_DERIVED_REASONS = [
8
+ 'head_changed',
9
+ 'dirty_fingerprint_changed',
10
+ 'schema_revision_changed',
11
+ 'tsconfig_changed',
12
+ 'command_manifest_changed',
13
+ 'gate_manifest_changed',
14
+ 'proof_index_changed',
15
+ 'wiki_context_changed',
16
+ 'git_state_unknown',
17
+ 'cache_key_changed'
18
+ ];
19
+ const PLACEHOLDER_CACHE_KEY_PARTS = {
20
+ workspaceIdentity: 'unevaluated',
21
+ head: null,
22
+ gitState: 'unknown',
23
+ trackedDirtyFingerprint: 'unevaluated',
24
+ untrackedFingerprint: 'unevaluated',
25
+ schemaRevision: 'unevaluated',
26
+ tsconfigHash: 'unevaluated',
27
+ commandManifestHash: 'unevaluated',
28
+ gateManifestHash: 'unevaluated',
29
+ proofIndexHash: 'unevaluated',
30
+ wikiContextHash: 'unevaluated'
31
+ };
32
+ export async function contextGraphFreshnessPreflight(root, options = {}) {
33
+ const verifySources = options.verifySources !== false;
34
+ const extractors = options.extractors ?? contextGraphExtractors();
35
+ const supplied = options.cacheKey ?? null;
36
+ const metaLoad = await readContextGraphMeta(root);
37
+ const meta = metaLoad.status === 'ok' ? metaLoad.meta : null;
38
+ const cacheKey = supplied ?? neutralCacheKey(meta?.cacheKeyParts ?? PLACEHOLDER_CACHE_KEY_PARTS);
39
+ const recordedSources = meta ? Object.keys(meta.inputHashes ?? {}).length : 0;
40
+ const status = await contextGraphStatus(root, {
41
+ extractors,
42
+ cacheKey,
43
+ verifySources,
44
+ ...(options.maxVerifiedSources === undefined ? {} : { maxVerifiedSources: options.maxVerifiedSources })
45
+ });
46
+ const sourcesVerified = verifySources
47
+ && (options.maxVerifiedSources === undefined || options.maxVerifiedSources >= recordedSources);
48
+ const unverified = collectUnverifiedReasons(status.reasons, Boolean(supplied), sourcesVerified);
49
+ return {
50
+ schema: CONTEXT_GRAPH_FRESHNESS_PREFLIGHT_SCHEMA,
51
+ usable: status.status === 'fresh',
52
+ status: status.status,
53
+ reasons: status.reasons,
54
+ unverified_reasons: unverified,
55
+ coverage: supplied ? 'full' : sourcesVerified ? 'artifacts_and_sources' : 'artifacts_only',
56
+ snapshot_hash: status.snapshotHash,
57
+ generated_at: status.generatedAt,
58
+ node_count: status.nodeCount,
59
+ edge_count: status.edgeCount,
60
+ error_code: status.errorCode,
61
+ repair_command: CONTEXT_GRAPH_REPAIR_COMMAND
62
+ };
63
+ }
64
+ export async function contextGraphFreshnessNote(root, options = {}) {
65
+ const budgetMs = Math.max(1, options.budgetMs ?? 750);
66
+ const preflight = await raceWithTimeout(contextGraphFreshnessPreflight(root, options), budgetMs).catch(() => null);
67
+ if (!preflight)
68
+ return null;
69
+ return contextGraphFreshnessNoteFor(preflight);
70
+ }
71
+ export function contextGraphFreshnessNoteFor(preflight) {
72
+ if (preflight.status === 'fresh')
73
+ return null;
74
+ if (preflight.status === 'missing') {
75
+ return `SKS note: the codebase context graph has not been built (${preflight.error_code}). Run \`${preflight.repair_command}\` before relying on graph-cited context.`;
76
+ }
77
+ if (preflight.status === 'corrupt') {
78
+ return `SKS note: the codebase context graph is unusable (${preflight.error_code}). Run \`${preflight.repair_command}\` to rebuild it; no older generation is substituted.`;
79
+ }
80
+ const reasons = preflight.reasons.join(', ');
81
+ return `SKS note: the codebase context graph is stale (${preflight.error_code}: ${reasons}). Run \`${preflight.repair_command}\` to refresh graph-cited context.`;
82
+ }
83
+ function neutralCacheKey(parts) {
84
+ return { key: contextGraphCacheKey(parts), parts, reusable: true, reasons: [], dirtyPaths: [] };
85
+ }
86
+ function collectUnverifiedReasons(fired, cacheKeySupplied, sourcesVerified) {
87
+ const firedSet = new Set(fired);
88
+ const out = [];
89
+ if (!cacheKeySupplied) {
90
+ for (const reason of CACHE_KEY_DERIVED_REASONS) {
91
+ if (!firedSet.has(reason))
92
+ out.push(reason);
93
+ }
94
+ }
95
+ if (!sourcesVerified && !firedSet.has('source_hash_mismatch'))
96
+ out.push('source_hash_mismatch');
97
+ return out;
98
+ }
99
+ async function raceWithTimeout(work, ms) {
100
+ let timer = null;
101
+ const timeout = new Promise((resolve) => {
102
+ timer = setTimeout(() => resolve(null), ms);
103
+ if (timer.unref)
104
+ timer.unref();
105
+ });
106
+ try {
107
+ return await Promise.race([work, timeout]);
108
+ }
109
+ finally {
110
+ if (timer)
111
+ clearTimeout(timer);
112
+ }
113
+ }
@@ -5,6 +5,7 @@ import { routePrompt } from '../pipeline.js';
5
5
  import { allowlistedManagedRouteSkillNames, dollarCommand, INVALID_EXPLICIT_MANAGED_SKILL_NAME, managedSkillNamesForPrompt, routeRequiresSubagents, stripVisibleDecisionAnswerBlocks } from '../routes.js';
6
6
  import { inspectConfinedPath } from '../managed-path-safety.js';
7
7
  import { renderAuthoritativeSksSkillContext, resolveAuthoritativeSksSkillSources } from '../codex-native/sks-skill-paths.js';
8
+ import { healStaleGlobalManagedSkillGeneration, managedSkillResolutionIsHealable } from './managed-skill-generation-heal.js';
8
9
  import { looksLikeActiveContinuationPrompt } from './naruto-decision-gate.js';
9
10
  import { extractUserPrompt, looksLikeCodexGitAction, looksLikeCodexUiSettingsEvent } from './payload-signals.js';
10
11
  const STANDALONE_PARENT_BASE_SKILLS = [
@@ -100,7 +101,7 @@ async function readStandaloneParentRouteContext(root, missionId) {
100
101
  }
101
102
  }
102
103
  export async function authoritativeSksSkillAdmission(root, skillNames) {
103
- const resolution = await resolveAuthoritativeSksSkillSources({ root, skillNames }).catch(() => null);
104
+ let resolution = await resolveAuthoritativeSksSkillSources({ root, skillNames }).catch(() => null);
104
105
  if (!resolution) {
105
106
  return {
106
107
  resolution: null,
@@ -111,16 +112,28 @@ export async function authoritativeSksSkillAdmission(root, skillNames) {
111
112
  }
112
113
  };
113
114
  }
115
+ let healedGeneration = null;
116
+ if (managedSkillResolutionIsHealable(resolution)) {
117
+ healedGeneration = await healStaleGlobalManagedSkillGeneration().catch(() => null);
118
+ if (healedGeneration) {
119
+ const rechecked = await resolveAuthoritativeSksSkillSources({ root, skillNames }).catch(() => null);
120
+ if (rechecked)
121
+ resolution = rechecked;
122
+ }
123
+ }
114
124
  if (resolution.unresolved.length || resolution.blockers.length) {
115
125
  const details = [
116
126
  resolution.unresolved.length ? `unavailable=${resolution.unresolved.join(',')}` : '',
117
127
  resolution.blockers.length ? `rejected=${resolution.blockers.join(',')}` : ''
118
128
  ].filter(Boolean).join('; ');
129
+ const repair = healedGeneration
130
+ ? `Global managed skills were regenerated from sneakoscope ${healedGeneration.installed_version} to ${healedGeneration.runtime_version} and still do not match this runtime, so \`sks doctor --fix\` cannot clear it. Ask the user to install the sneakoscope build that matches this runtime (${healedGeneration.runtime_version}) and retry.`
131
+ : 'Ask the user to run `sks doctor --fix` (do not run it yourself), then retry.';
119
132
  return {
120
133
  resolution,
121
134
  blocked: {
122
135
  decision: 'block',
123
- reason: `SKS managed skill availability check failed (${details}). Ask the user to run \`sks doctor --fix\` (do not run it yourself), then retry.`,
136
+ reason: `SKS managed skill availability check failed (${details}). ${repair}`,
124
137
  systemMessage: 'SKS: managed skill availability check blocked this turn.'
125
138
  }
126
139
  };
@@ -0,0 +1,46 @@
1
+ import os from 'node:os';
2
+ import path from 'node:path';
3
+ import { PACKAGE_VERSION, readJson } from '../fsx.js';
4
+ const HEALABLE_BLOCKER_STATUSES = new Set([
5
+ 'content_digest_mismatch',
6
+ 'canonical_name_mismatch',
7
+ 'not_sks_managed'
8
+ ]);
9
+ const healedGenerations = new Map();
10
+ export function managedSkillResolutionIsHealable(resolution) {
11
+ if (!resolution)
12
+ return false;
13
+ const blockers = resolution.blockers || [];
14
+ if (blockers.some((blocker) => !HEALABLE_BLOCKER_STATUSES.has(String(blocker).split(':')[0] || '')))
15
+ return false;
16
+ return Boolean(resolution.unresolved.length || blockers.length);
17
+ }
18
+ export async function staleGlobalManagedSkillGeneration(home) {
19
+ const resolvedHome = path.resolve(home || process.env.HOME || os.homedir());
20
+ const marker = await readJson(path.join(resolvedHome, '.agents', 'skills', '.sks-generated.json'), null);
21
+ if (String(marker?.generated_by || '') !== 'sneakoscope')
22
+ return null;
23
+ const installedVersion = String(marker?.version || '').trim();
24
+ if (!installedVersion || installedVersion === PACKAGE_VERSION)
25
+ return null;
26
+ return { home: resolvedHome, installed_version: installedVersion, runtime_version: PACKAGE_VERSION };
27
+ }
28
+ export async function healStaleGlobalManagedSkillGeneration(home) {
29
+ const stale = await staleGlobalManagedSkillGeneration(home);
30
+ if (!stale)
31
+ return null;
32
+ const cached = healedGenerations.get(stale.home);
33
+ if (cached)
34
+ return cached;
35
+ const work = (async () => {
36
+ const { reconcileSkills } = await import('../init/skills.js');
37
+ const report = await reconcileSkills({
38
+ targetDir: path.join(stale.home, '.agents', 'skills'),
39
+ scope: 'global',
40
+ fix: true
41
+ }).catch(() => null);
42
+ return report?.ok === true ? stale : null;
43
+ })();
44
+ healedGenerations.set(stale.home, work);
45
+ return work;
46
+ }
@@ -160,6 +160,7 @@ async function resolveStaleOfficialSubagentComplianceBlocker(input) {
160
160
  await appendJsonl(path.join(input.dir, 'events.jsonl'), {
161
161
  ts: nowIso(),
162
162
  type: 'pipeline.compliance_loop_guard.resolved',
163
+ proof_invalidating: false,
163
164
  gate: 'official-subagent-evidence',
164
165
  workflow_run_id: input.workflowRunId,
165
166
  resolution_artifact: RESOLVED_HARD_BLOCKER_ARTIFACT
@@ -350,7 +350,7 @@ async function rebuildHostCapabilityEvidenceForFinalization(input) {
350
350
  blockers: bound.blockers
351
351
  };
352
352
  }
353
- function completeNarutoTerminalBundle(input) {
353
+ export function completeNarutoTerminalBundle(input) {
354
354
  const runId = String(input.workflowRunId || '').trim();
355
355
  const parent = normalizeSubagentParentSummary(input.parentSummary);
356
356
  return Boolean(runId
@@ -87,6 +87,6 @@ export function interruptedToolOutputRecoveryBlockReason(input = {}) {
87
87
  `SKS blocked same-thread continuation because custom tool call ${callId} has no correlated output (active mission: ${missionId}).`,
88
88
  'The current Codex conversation state may be structurally invalid, so additional context cannot repair the pending Responses request.',
89
89
  'Treat the call result as unknown and do not replay a possibly mutating action.',
90
- 'Upgrade the selected codex-lb to 1.21.0-beta.3 or later, or explicitly switch with `sks codex-lb use-oauth`, then open a fresh Codex thread and continue the persisted mission after inspecting side effects.'
90
+ 'Upgrade the selected codex-lb to the official latest stable release (`sks codex-lb status --json` reports whether the deployment satisfies tool-heavy continuation), or explicitly disable only codex-lb routing with `sks codex-lb disable`, then open a fresh Codex thread and continue the persisted mission after inspecting side effects. ChatGPT OAuth is not replaced by either action.'
91
91
  ].join(' ');
92
92
  }
@@ -1,5 +1,5 @@
1
1
  import path from 'node:path';
2
- import { projectRoot, readJson, writeJsonAtomic, appendJsonl, nowIso, runProcess, sha256, packageRoot, tmpdir } from './fsx.js';
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
- return String(text || '')
997
+ const lines = String(text || '')
986
998
  .split(/\n/)
987
- .map((line) => line.trim())
988
- .filter((line) => issue.test(line) && !honestGapLineResolved(line))
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;
@@ -1003,7 +1025,9 @@ function honestGapLineResolved(line) {
1003
1025
  return true;
1004
1026
  if (/no\s+active\s+blocking\s+route\s+gate\s+detected/i.test(line))
1005
1027
  return true;
1006
- if (/(non[-\s]?blocker|non[-\s]?blocking|not\s+(?:a\s+)?blocker|no\s+blocker|does\s+not\s+block|not\s+blocking|blocker\s*(?:는|가)?\s*(?:아님|아닙니다|없음)|차단(?:하지|하진|하지는)\s*않|막(?:지|지는)\s*않)/i.test(line))
1028
+ if (/(?:blockers?|차단(?:\s*(?:항목|요소|건))?)\s*(?:[:=]\s*)?0(?:건|개)?\b/i.test(line))
1029
+ return true;
1030
+ if (/(non[-\s]?blocker|non[-\s]?blocking|not\s+(?:a\s+)?blocker|no\s+blocker|does\s+not\s+block|not\s+blocking|비\s*차단|blocker\s*(?:는|가)?\s*(?:아님|아닙니다|없음)|차단(?:하지|하진|하지는)\s*않|막(?:지|지는)\s*않)/i.test(line))
1007
1031
  return true;
1008
1032
  if (/(요약\s*(?:없으면|없는\s*경우).*(?:차단|block).*(?:요약\s*(?:있으면|있는\s*경우)|통과|pass)|(?:missing|without)\s+summary.*(?:block|blocked).*(?:with\s+summary|pass|accepted))/i.test(line))
1009
1033
  return true;
@@ -1050,7 +1074,7 @@ async function recordHonestModeLoopback(root, state = {}, lastMessage = '', sess
1050
1074
  };
1051
1075
  const file = path.join(dir, 'honest-loopback.json');
1052
1076
  await writeJsonAtomic(file, artifact);
1053
- await appendJsonl(path.join(dir, 'events.jsonl'), { ts: nowIso(), type: 'pipeline.honest_mode.loopback', previous_phase: previousPhase, phase, attempt, issues: artifact.issue_lines });
1077
+ await appendJsonl(path.join(dir, 'events.jsonl'), { ts: nowIso(), type: 'pipeline.honest_mode.loopback', proof_invalidating: false, previous_phase: previousPhase, phase, attempt, issues: artifact.issue_lines });
1054
1078
  await setCurrent(root, {
1055
1079
  phase,
1056
1080
  honest_loop_required: true,
@@ -1068,7 +1092,7 @@ async function resolveHonestModeLoopback(root, state = {}, sessionKey = null) {
1068
1092
  const id = state.mission_id;
1069
1093
  const mode = String(state.mode || state.route || 'SKS').toUpperCase();
1070
1094
  if (id)
1071
- await appendJsonl(path.join(missionDir(root, id), 'events.jsonl'), { ts: nowIso(), type: 'pipeline.honest_mode.loopback_resolved', previous_phase: state.phase || null });
1095
+ await appendJsonl(path.join(missionDir(root, id), 'events.jsonl'), { ts: nowIso(), type: 'pipeline.honest_mode.loopback_resolved', proof_invalidating: false, previous_phase: state.phase || null });
1072
1096
  await setCurrent(root, {
1073
1097
  phase: `${mode}_HONEST_COMPLETE`,
1074
1098
  honest_loop_required: false,
@@ -1080,83 +1104,3 @@ export async function emitHook(name) {
1080
1104
  const result = await hookMain(name);
1081
1105
  process.stdout.write(`${JSON.stringify(normalizeHookResult(name, result))}\n`);
1082
1106
  }
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,