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
@@ -6,13 +6,19 @@ export const CODEX_LB_IMAGEGEN_TARGET_SCHEMA = 'sks.codex-lb-imagegen-target.v1'
6
6
  export async function resolveCodexLbImagegenTarget(opts = {}) {
7
7
  const env = opts.env || process.env;
8
8
  const home = opts.home || env.HOME || os.homedir();
9
+ const codexHome = opts.codexHome || env.CODEX_HOME || path.join(home, '.codex');
9
10
  const configText = typeof opts.configText === 'string'
10
11
  ? opts.configText
11
- : await readText(path.join(home, '.codex', 'config.toml'), '').catch(() => '');
12
+ : await readText(path.join(codexHome, 'config.toml'), '').catch(() => '');
12
13
  const selected = topLevelTomlString(configText, 'model_provider') === 'codex-lb';
13
- const loaded = await loadCodexLbEnv({ home, processEnv: env }).catch(() => null);
14
- const catalogModels = await readCatalogModels(configText, home);
15
- const explicit = String(opts.explicitModel || env.SKS_IMAGEGEN_RESPONSES_MODEL || env.OPENAI_MODEL || '').trim();
14
+ const loaded = await loadCodexLbEnv({
15
+ home,
16
+ processEnv: {},
17
+ envPath: path.join(codexHome, 'sks-codex-lb.env'),
18
+ metadataPath: path.join(codexHome, 'sks-codex-lb.json')
19
+ }).catch(() => null);
20
+ const catalogModels = await readCatalogModels(configText, home, codexHome);
21
+ const explicit = String(opts.explicitModel || env.SKS_IMAGEGEN_RESPONSES_MODEL || '').trim();
16
22
  const configuredModel = topLevelTomlString(configText, 'model');
17
23
  const { model, model_source } = explicit
18
24
  ? { model: explicit, model_source: 'explicit' }
@@ -46,16 +52,17 @@ function codexLbImagegenBlocker(state) {
46
52
  return 'codex_lb_imagegen_model_unresolved';
47
53
  return null;
48
54
  }
49
- async function readCatalogModels(configText, home) {
55
+ async function readCatalogModels(configText, home, codexHome) {
50
56
  const configured = topLevelTomlString(configText, 'model_catalog_json');
51
57
  const candidates = [
52
- configured ? path.resolve(configured.replace(/^~(?=\/|$)/, home)) : '',
53
- path.join(home, '.codex', 'sks-codex-lb-tool-catalog.json')
58
+ configured ? resolveCatalogPath(configured, home, codexHome) : '',
59
+ path.join(codexHome, 'sks-codex-lb-tool-catalog.json')
54
60
  ].filter(Boolean);
55
61
  for (const file of candidates) {
56
62
  const payload = await readJson(file, null).catch(() => null);
57
63
  const rows = Array.isArray(payload?.models) ? payload.models : Array.isArray(payload?.data) ? payload.data : [];
58
64
  const slugs = rows
65
+ .filter((row) => row?.supported_in_api !== false && !hiddenCatalogRow(row))
59
66
  .map((row) => String(row?.slug || row?.id || row?.model || '').trim())
60
67
  .filter(Boolean);
61
68
  if (slugs.length)
@@ -63,6 +70,13 @@ async function readCatalogModels(configText, home) {
63
70
  }
64
71
  return [];
65
72
  }
73
+ function resolveCatalogPath(configured, home, codexHome) {
74
+ const expanded = configured.replace(/^~(?=\/|$)/, home);
75
+ return path.isAbsolute(expanded) ? expanded : path.resolve(codexHome, expanded);
76
+ }
77
+ function hiddenCatalogRow(row) {
78
+ return ['hide', 'hidden', 'internal', 'none', 'unavailable'].includes(String(row?.visibility || '').trim().toLowerCase());
79
+ }
66
80
  function topLevelTomlString(text, key) {
67
81
  const topLevel = String(text || '').split(/\n\s*\[/)[0] || '';
68
82
  const re = new RegExp(`(^|\\n)\\s*${key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\s*=\\s*"([^"]*)"\\s*(?:#.*)?(?=\\n|$)`);
@@ -1,6 +1,6 @@
1
1
  import os from 'node:os';
2
2
  import path from 'node:path';
3
- import { codexLbEnvPath, loadCodexLbEnv, parseShellEnvValue } from '../codex-lb/codex-lb-env.js';
3
+ import { loadCodexLbEnv, parseShellEnvValue } from '../codex-lb/codex-lb-env.js';
4
4
  import { nowIso, readText, runProcess, which } from '../fsx.js';
5
5
  import { redactSecrets, redactString } from '../secret-redaction.js';
6
6
  import { evaluateImagegenAuthReadiness } from './imagegen-auth-readiness.js';
@@ -97,7 +97,8 @@ export async function detectImagegenCapability(opts = {}) {
97
97
  }
98
98
  async function detectCodexLbImagegenAuth(opts = {}, env = process.env) {
99
99
  const home = opts.home || env.HOME || process.env.HOME || os.homedir();
100
- const configPath = opts.configPath || path.join(home, '.codex', 'config.toml');
100
+ const codexHome = opts.codexHome || env.CODEX_HOME || path.join(home, '.codex');
101
+ const configPath = opts.configPath || path.join(codexHome, 'config.toml');
101
102
  const configText = typeof opts.configText === 'string'
102
103
  ? opts.configText
103
104
  : await readText(configPath, '').catch(() => '');
@@ -107,11 +108,16 @@ async function detectCodexLbImagegenAuth(opts = {}, env = process.env) {
107
108
  const requiresOpenAiAuth = tomlBoolean(block, 'requires_openai_auth');
108
109
  const envKey = tomlString(block, 'env_key');
109
110
  const baseUrl = tomlString(block, 'base_url') || String(env.CODEX_LB_BASE_URL || '').trim();
110
- const envPath = opts.codexLbEnvPath || codexLbEnvPath(home);
111
+ const envPath = opts.codexLbEnvPath || path.join(codexHome, 'sks-codex-lb.env');
111
112
  const injectedEnvText = typeof opts.codexLbEnvText === 'string' ? opts.codexLbEnvText : null;
112
113
  const keyFromInjectedFile = injectedEnvText !== null && envKey ? parseShellEnvValue(injectedEnvText, envKey) : '';
113
114
  const loaded = injectedEnvText === null
114
- ? await loadCodexLbEnv({ home, processEnv: env, envPath }).catch(() => null)
115
+ ? await loadCodexLbEnv({
116
+ home,
117
+ processEnv: {},
118
+ envPath,
119
+ metadataPath: path.join(codexHome, 'sks-codex-lb.json')
120
+ }).catch(() => null)
115
121
  : null;
116
122
  const apiKeyPresent = Boolean(opts.codexLbApiKey || keyFromInjectedFile || loaded?.secret_api_key);
117
123
  const apiKeySource = opts.codexLbApiKey
@@ -1,15 +1,27 @@
1
1
  import { detectImagegenCapability } from './imagegen-capability.js';
2
2
  import { repairCodexImagegen } from '../doctor/imagegen-repair.js';
3
3
  export async function requireCodexImagegen(root, opts = {}) {
4
- const capability = await detectImagegenCapability({
5
- codexBin: opts.codexBin || undefined,
4
+ const capabilityOpts = {
6
5
  timeoutMs: opts.timeoutMs || 5000
7
- }).catch((err) => ({
6
+ };
7
+ if (opts.codexBin !== undefined && opts.codexBin !== null)
8
+ capabilityOpts.codexBin = opts.codexBin;
9
+ if (opts.home !== undefined)
10
+ capabilityOpts.home = opts.home;
11
+ if (opts.codexHome !== undefined)
12
+ capabilityOpts.codexHome = opts.codexHome;
13
+ if (opts.env !== undefined)
14
+ capabilityOpts.env = opts.env;
15
+ if (opts.configText !== undefined)
16
+ capabilityOpts.configText = opts.configText;
17
+ if (opts.codexLbEnvText !== undefined)
18
+ capabilityOpts.codexLbEnvText = opts.codexLbEnvText;
19
+ const capability = await detectImagegenCapability(capabilityOpts).catch((err) => ({
8
20
  ok: false,
9
21
  core_ready: false,
10
22
  blockers: [err instanceof Error ? err.message : String(err)]
11
23
  }));
12
- const capabilityReadyBeforeRepair = capability.core_ready === true;
24
+ const capabilityReadyBeforeRepair = imagegenPreflightReady(capability);
13
25
  const repair = opts.autoRepair === true && !capabilityReadyBeforeRepair
14
26
  ? await repairCodexImagegen({
15
27
  root,
@@ -25,7 +37,10 @@ export async function requireCodexImagegen(root, opts = {}) {
25
37
  const finalCapability = repair
26
38
  ? repair.after || capability
27
39
  : capability;
28
- const capabilityReady = finalCapability.core_ready === true || repair?.capability_ready === true;
40
+ const capabilityReady = imagegenPreflightReady(finalCapability) || repair?.capability_ready === true;
41
+ const preflightProvider = finalCapability.core_ready === true
42
+ ? 'codex_app_builtin'
43
+ : codexLbImagegenReady(finalCapability) ? 'codex_lb' : null;
29
44
  const currentTaskToolManifestVerified = repair?.current_task_tool_manifest_verified === true;
30
45
  const generatedOutputVerified = finalCapability.real_output_verified_by_capability_check === true
31
46
  || repair?.real_generation_verified === true;
@@ -55,6 +70,7 @@ export async function requireCodexImagegen(root, opts = {}) {
55
70
  preflight_ready: preflightReady,
56
71
  preflight_only: true,
57
72
  preflight_does_not_satisfy_generated_output_proof: true,
73
+ preflight_provider: preflightProvider,
58
74
  capability_ready: capabilityReady,
59
75
  route_ready: routeReady,
60
76
  current_task_tool_manifest_verified: currentTaskToolManifestVerified,
@@ -80,3 +96,11 @@ export async function requireCodexImagegen(root, opts = {}) {
80
96
  blockers
81
97
  };
82
98
  }
99
+ function imagegenPreflightReady(capability) {
100
+ return capability?.core_ready === true || codexLbImagegenReady(capability);
101
+ }
102
+ function codexLbImagegenReady(capability) {
103
+ return capability?.codex_lb?.selected === true
104
+ && capability?.codex_lb?.available === true
105
+ && capability?.codex_lb?.blocker == null;
106
+ }
@@ -12,7 +12,7 @@ import { reconcileRetiredAgentRoleResidue } from '../agents/agent-role-config.js
12
12
  import { ensureConfinedDirectory, inspectConfinedPath, moveConfinedPath, publicPathError, removeConfinedDirectoryIfEmpty, removeManagedPathVerified, uniqueConfinedPath } from '../managed-path-safety.js';
13
13
  import { collectNestedProjectRoots } from '../doctor/current-project-guidance-nested.js';
14
14
  import { coreEngineeringDirectiveReferenceText, engineeringSanityPolicyText } from '../lean-engineering-policy.js';
15
- import { AWESOME_DESIGN_MD_REFERENCE, CODEX_APP_IMAGE_GENERATION_DOC_URL, CODEX_COMPUTER_USE_ONLY_POLICY, CODEX_IMAGEGEN_EVIDENCE_SOURCE, CODEX_IMAGEGEN_REQUIRED_POLICY, CODEX_WEB_VERIFICATION_POLICY, DEFAULT_CODEX_APP_PLUGINS, DESIGN_SYSTEM_SSOT, DOLLAR_COMMANDS, DOLLAR_SKILL_NAMES, FROM_CHAT_IMG_CHECKLIST_ARTIFACT, FROM_CHAT_IMG_COVERAGE_ARTIFACT, FROM_CHAT_IMG_QA_LOOP_ARTIFACT, FROM_CHAT_IMG_TEMP_TRIWIKI_ARTIFACT, FROM_CHAT_IMG_TEMP_TRIWIKI_SESSIONS, GETDESIGN_REFERENCE, IMAGEGEN_SOCIAL_SOURCE_POLICY, LEGACY_DOLLAR_SKILL_NAMES, OPENAI_CHATGPT_IMAGES_2_DOC_URL, OPENAI_GPT_IMAGE_2_MODEL_DOC_URL, OPENAI_IMAGE_GENERATION_DOC_URL, PPT_CONDITIONAL_SKILL_ALLOWLIST, PPT_PIPELINE_MCP_ALLOWLIST, PPT_PIPELINE_SKILL_ALLOWLIST, RECOMMENDED_SKILLS, RESERVED_CODEX_PLUGIN_SKILL_NAMES, SOLUTION_SCOUT_SKILL_NAME, chatCaptureIntakeText, context7ConfigToml, getdesignReferencePolicyText, imageUxReviewPipelinePolicyText, outcomeRubricPolicyText, pptPipelineAllowlistPolicyText, productDesignPluginPolicyText, speedLanePolicyText, stackCurrentDocsPolicyText, triwikiContextTrackingText, triwikiStagePolicyText } from '../routes.js';
15
+ import { AWESOME_DESIGN_MD_REFERENCE, CODEX_APP_IMAGE_GENERATION_DOC_URL, CODEX_COMPUTER_USE_ONLY_POLICY, CODEX_IMAGEGEN_EVIDENCE_SOURCE, CODEX_IMAGEGEN_REQUIRED_POLICY, CODEX_WEB_VERIFICATION_POLICY, DEFAULT_CODEX_APP_PLUGINS, DESIGN_SYSTEM_SSOT, DOLLAR_COMMANDS, DOLLAR_SKILL_NAMES, FROM_CHAT_IMG_CHECKLIST_ARTIFACT, FROM_CHAT_IMG_COVERAGE_ARTIFACT, FROM_CHAT_IMG_QA_LOOP_ARTIFACT, FROM_CHAT_IMG_TEMP_TRIWIKI_ARTIFACT, FROM_CHAT_IMG_TEMP_TRIWIKI_SESSIONS, GETDESIGN_REFERENCE, IMAGEGEN_SOCIAL_SOURCE_POLICY, LEGACY_DOLLAR_SKILL_NAMES, OPENAI_CHATGPT_IMAGES_2_DOC_URL, OPENAI_GPT_IMAGE_2_MODEL_DOC_URL, OPENAI_IMAGE_GENERATION_DOC_URL, PPT_CONDITIONAL_SKILL_ALLOWLIST, PPT_PIPELINE_MCP_ALLOWLIST, PPT_PIPELINE_SKILL_ALLOWLIST, RECOMMENDED_SKILLS, RESERVED_CODEX_PLUGIN_SKILL_NAMES, SOLUTION_SCOUT_SKILL_NAME, chatCaptureIntakeText, context7ConfigToml, getdesignReferencePolicyText, outcomeRubricPolicyText, pptPipelineAllowlistPolicyText, productDesignPluginPolicyText, speedLanePolicyText, stackCurrentDocsPolicyText, triwikiContextTrackingText, triwikiStagePolicyText } from '../routes.js';
16
16
  import { prefixKnownSksDollarReferences, sksPrefixedSkillName } from '../routes/dollar-prefix.js';
17
17
  import { escapeRegExp } from '../text/regex.js';
18
18
  const SKS_SKILL_MANIFEST_FILE = '.sks-generated.json';
@@ -21,6 +21,12 @@ const GENERATED_PRUNE_POLICY = 'remove_previous_sks_generated_paths_absent_from_
21
21
  const REFLECTION_MEMORY_PATH = '.sneakoscope/memory/q2_facts/post-route-reflection.md';
22
22
  const MANAGED_SKILL_MARKER_RE = /BEGIN SKS (?:IMMUTABLE CORE|MANAGED) SKILL/;
23
23
  const FORGE_SKILL_MARKER_RE = /BEGIN SKS FORGE SKILL/;
24
+ const RETIRED_PUBLIC_IMAGE_UX_REVIEW_SKILL_ALIASES = [
25
+ 'sks-ux-review',
26
+ 'sks-visual-review',
27
+ 'sks-ui-ux-review'
28
+ ];
29
+ const RETIRED_PUBLIC_IMAGE_UX_REVIEW_SKILL_ALIAS_SET = new Set(RETIRED_PUBLIC_IMAGE_UX_REVIEW_SKILL_ALIASES);
24
30
  export const REMOVED_SKS_SKILL_NAMES = [
25
31
  'old-workflow',
26
32
  'team-legacy',
@@ -85,7 +91,6 @@ function reflectionInstructionText(commandPrefix = 'sks') {
85
91
  async function installOfficialSkills(root) {
86
92
  const quarantinedUserCollisions = [];
87
93
  const quarantinedManifestCollisions = await prepareReservedSkillManifestsForWrite(root);
88
- const imageUxReviewSkill = (name) => `---\nname: ${name}\ndescription: $Image-UX-Review/$UX-Review imagegen/gpt-image-2 annotated UI/UX review loop.\n---\n\nUse only for $Image-UX-Review, $UX-Review, $visual-review, or $ui-ux-review UI/UX review requests. ${imageUxReviewPipelinePolicyText()} Route start must check Codex App imagegen capability and run the SKS imagegen repair loop once; if $imagegen/gpt-image-2 is still unavailable, stop with codex_imagegen_unavailable instead of doing text-only review or direct API substitution. Core loop: capture or attach source UI screenshots, then invoke Codex App $imagegen with gpt-image-2 to create a new generated annotated review image from each source screenshot, then analyze the generated review image with vision/OCR into image-ux-issue-ledger.json, then apply only requested safe fixes and recheck changed screens. Text-only screenshot critique cannot satisfy full verification; missing generated annotated review images keep full image-ux-review-gate.json verification blocked, but may close verified_partial/reference-only when source screenshots plus hashes, docs evidence, source Image Voxel anchors, and Honest Mode evidence exist. For live web/browser/webapp capture use Codex Chrome Extension first and halt if it is not installed/enabled; use Codex Computer Use only for native Mac/non-web app screens. Required artifacts: image-ux-review-policy.json, image-ux-screen-inventory.json, image-ux-generated-review-ledger.json, image-ux-issue-ledger.json, image-ux-iteration-report.json, image-ux-review-gate.json. Finish with reflection and Honest Mode.\n`;
89
94
  const canonicalMadSksSqlPlanePolicy = madSksSqlPlanePolicyText()
90
95
  .replace(/^---[\s\S]*?---\s*/, '')
91
96
  .trim();
@@ -96,8 +101,8 @@ async function installOfficialSkills(root) {
96
101
  'plan': `---\nname: plan\ndescription: Plan scaffold only - writes a fixed-template .sneakoscope/plans/<slug>.md, never touches code. Not project-specific decision-complete planning. 예: $Plan "결제 모듈 리팩터"\n---\n\nUse when the user invokes $Plan or asks for a plan-only frontdoor. Produce a concrete plan artifact under .sneakoscope/plans/<slug>.md with goal, scope, files to inspect, implementation steps, acceptance checks, and rollback notes. Do not edit product/source files, generated harness files, package metadata, or docs beyond the plan artifact. Keep implementation_allowed=false and hand off execution to $Work only after the user or route explicitly moves from planning to work. Finish with what is planned, what remains unimplemented, and Honest Mode.\n`,
97
102
  'work': `---\nname: work\ndescription: Execute the latest plan with evidence-gated completion. 예: $Work\n---\n\nUse when the user invokes $Work or asks to execute the latest SKS plan. Resolve the newest .sneakoscope/plans/*.md, route execution through Naruto evidence gates, keep leases and verification artifacts current, and do not claim completion without machine evidence or explicit blocker evidence. If no plan exists, block with a clear next action: run $Plan first or provide a task.\n`,
98
103
  'review': `---\nname: review\ndescription: Parallel diff review with machine-evidence first findings. 예: $Review 또는 sks review --staged\n---\n\nUse when the user asks for $Review or sks review. Review the selected diff read-only unless --fix is explicitly supplied. Machine evidence such as TypeScript, lint, tests, conflict markers, or secret scans outranks LLM findings and must be tagged evidence: machine; judgment-only findings must be tagged evidence: llm. --fix may attempt at most one machine-evidence fix pass and must re-run verification once. Do not mutate code for LLM-only opinions.\n`,
99
- 'fast-mode': `---\nname: fast-mode\ndescription: Dollar-command route for $Fast-Mode, $Fast-On, and $Fast-Off global Codex Desktop Fast mode toggles.\n---\n\nUse when the user invokes $Fast-Mode, $Fast-On, $Fast-Off, or asks to turn SKS Fast mode on/off. Prefer \`sks fast-mode on|off|status|clear --json\`. By default on/off updates the global Codex Desktop config so GPT 5.5 Fast persists and also keeps .sneakoscope/state/fast-mode.json in sync for SKS workers. Use \`--project\` only when the user explicitly wants project-local worker preference without touching global Codex config. Explicit runtime flags still win: \`--fast\`, \`--no-fast\`, and \`--service-tier standard|fast\` override the saved preference for that run. Finish with a short status and Honest Mode; do not start Naruto or broad implementation for a toggle-only request.\n`,
100
- 'fast-on': `---\nname: fast-on\ndescription: Alias for $Fast-On global Codex Desktop GPT 5.5 Fast enablement.\n---\n\nUse the same rules as fast-mode. Run or instruct \`sks fast-mode on --json\`, then report Global (desktop), Project (sks workers), state file, and the fact that explicit per-run flags still override the saved preference.\n`,
104
+ 'fast-mode': `---\nname: fast-mode\ndescription: Dollar-command route for $Fast-Mode, $Fast-On, and $Fast-Off global Codex Desktop Fast mode toggles.\n---\n\nUse when the user invokes $Fast-Mode, $Fast-On, $Fast-Off, or asks to turn SKS Fast mode on/off. Prefer \`sks fast-mode on|off|status|clear --json\`. By default on/off updates the global Codex Desktop config so GPT-5.6 Fast (official service_tier) persists and also keeps .sneakoscope/state/fast-mode.json in sync for SKS workers. Use \`--project\` only when the user explicitly wants project-local worker preference without touching global Codex config. Explicit runtime flags still win: \`--fast\`, \`--no-fast\`, and \`--service-tier standard|fast\` override the saved preference for that run. Finish with a short status and Honest Mode; do not start Naruto or broad implementation for a toggle-only request.\n`,
105
+ 'fast-on': `---\nname: fast-on\ndescription: Alias for $Fast-On global Codex Desktop GPT-5.6 Fast enablement.\n---\n\nUse the same rules as fast-mode. Run or instruct \`sks fast-mode on --json\`, then report Global (desktop), Project (sks workers), state file, and the fact that explicit per-run flags still override the saved preference.\n`,
101
106
  'fast-off': `---\nname: fast-off\ndescription: Alias for $Fast-Off global Codex Desktop Fast mode disablement.\n---\n\nUse the same rules as fast-mode. Run or instruct \`sks fast-mode off --json\`, then report Global (desktop), Project (sks workers), state file, and the fact that explicit per-run flags still override the saved preference.\n`,
102
107
  'with-local-llm-on': `---\nname: with-local-llm-on\ndescription: Dollar-command route for $with-local-llm-on local Ollama worker enablement.\n---\n\nUse when the user invokes $with-local-llm-on or asks to enable the optional local Ollama worker backend. Prefer \`sks with-local-llm on --json\`. The command writes the machine-local config at \`~/.sneakoscope/local-model.json\`. Default off means SKS stays GPT-only until this command enables local workers. Enabled mode only lets policy-eligible simple code patch-envelope or read-only collection worker slices use Ollama; GPT/Codex still owns strategy, planning, design, review, verification, safety, and integration. \`--no-ollama\` and \`SKS_OLLAMA_WORKERS=0\` still force local workers off for a run. Finish with a short status and Honest Mode; do not start Naruto for a toggle-only request.\n`,
103
108
  'with-local-llm-off': `---\nname: with-local-llm-off\ndescription: Dollar-command route for $with-local-llm-off local Ollama worker disablement.\n---\n\nUse when the user invokes $with-local-llm-off or asks to disable the optional local Ollama worker backend. Prefer \`sks with-local-llm off --json\`. The command writes the machine-local config at \`~/.sneakoscope/local-model.json\`. Disabled mode keeps SKS GPT-only by default. Strategy, planning, design, review, verification, safety, and integration remain GPT/Codex-owned regardless of this toggle. Finish with a short status and Honest Mode; do not start Naruto for a toggle-only request.\n`,
@@ -119,7 +124,7 @@ async function installOfficialSkills(root) {
119
124
  'help': `---\nname: help\ndescription: Dollar-command route for $Help or $help explaining installed SKS commands and workflows.\n---\n\nUse when the user invokes $Help/$help or asks what commands exist. Prefer concise output from sks commands, sks usage <topic>, sks quickstart, sks aliases, and sks codex-app.\n`,
120
125
  'prompt-pipeline': `---\nname: prompt-pipeline\ndescription: Default SKS prompt routing for execution requests; Answer and DFix remain lightweight.\n---\n\nClassify only the route and the constraints that change execution. Answer handles real information requests; DFix handles tiny copy/config/docs/labels/spacing/translation/mechanical edits; general code-changing work uses Naruto with Codex official subagents. Preserve the literal user request; TriWiki may enrich it but never replace an explicit requirement. ${coreEngineeringDirectiveReferenceText()} ${engineeringSanityPolicyText()} For code work, identify the requested outcome, actual code/data flow, exact write scope, acceptance criteria, and the smallest meaningful verification. Load route-specific Design, PPT, image, browser, research, DB, release, or Context7 rules only when that route or external contract requires them. Materialize pipeline-plan.json with route, scopes, verification, and blockers, then finish the requested work instead of stopping at the plan.\n`,
121
126
  [SOLUTION_SCOUT_SKILL_NAME]: `---\nname: ${SOLUTION_SCOUT_SKILL_NAME}\ndescription: Optional source scout for fixes that materially depend on external precedent.\n---\n\nUse only when the user asks for similar cases or when a package, API, platform, or externally documented behavior makes outside precedent materially useful. Search with the concrete symptom and current stack, prefer primary sources, and keep the local code path authoritative. Do not add this step to ordinary local bugfixes. If external evidence is unavailable, record that gap instead of inventing a workaround.\n`,
122
- 'reasoning-router': `---\nname: reasoning-router\ndescription: Temporary SKS reasoning-effort routing for every command and pipeline route.\n---\n\nmedium: simple copy/color/discovery/setup/mechanical edits. high: logic, safety, architecture, DB, orchestration, refactor, multi-file work. xhigh: research, AutoResearch, falsification, benchmarks, SEO/GEO, open-ended discovery, and From-Chat-IMG image work-order analysis. Routing is temporary; return to default after the gate. Inspect with sks reasoning and sks pipeline status.\n`,
127
+ 'reasoning-router': `---\nname: reasoning-router\ndescription: Temporary SKS reasoning-effort routing for every command and pipeline route on the GPT-5.6 family.\n---\n\nGPT-5.6 efforts: none/low/medium/high/xhigh/max. Route medium for simple copy/color/discovery/setup/mechanical edits; high for logic, safety, architecture, DB, orchestration, refactor, multi-file work; xhigh for research, AutoResearch, falsification, benchmarks, SEO/GEO, open-ended discovery, and From-Chat-IMG analysis; max only for hardest quality-first judgment. Prefer gpt-5.6-luna / terra / sol role defaults over legacy model aliases. Routing is temporary; return to default after the gate. Inspect with sks reasoning and sks pipeline status.\n`,
123
128
  'pipeline-runner': `---\nname: pipeline-runner\ndescription: Execute SKS routes with bounded state, evidence, reflection, and Honest Mode.\n---\n\nEvery $ command selects a route. Use current.json, mission artifacts, and pipeline-plan.json as bounded execution state; load only the selected route skill. ${coreEngineeringDirectiveReferenceText()} ${engineeringSanityPolicyText()} Codex native /goal is the only persisted goal owner. Use official Codex subagents only when the route requires independent slices, keep parent-owned integration, use bounded TriWiki context, fetch current external docs only when relevant, record real blockers, refresh and validate evidence, run reflection for full routes, and finish with a completion summary plus Honest Mode.\n`,
124
129
  'context7-docs': `---\nname: context7-docs\ndescription: Enforce Context7 MCP documentation evidence for SKS routes that depend on external libraries, frameworks, APIs, MCPs, package managers, DB SDKs, or generated docs.\n---\n\nWhen required, resolve-library-id, then query-docs for the resolved id. Legacy get-library-docs evidence is accepted. Prefer sks context7 tools/resolve/docs/evidence and finish only after both evidence stages exist. Check setup with sks context7 check.\n`,
125
130
  'super-search': `---\nname: super-search\ndescription: Dollar-command route for $Super-Search provider-independent source intelligence.\n---\n\nUse when the user invokes $Super-Search or asks for Super-Search source intelligence, source acquisition, X-search-style collection, URL acquisition, source normalization, claim ledgers, or citation proof. Prefer \`sks super-search doctor --json\` for readiness and \`sks super-search run "<query>" --mode balanced --json\` for provider-independent source proof; use \`sks super-search x "<query>" --json\` for X-search intent and \`sks super-search fetch "<url>" --json\` for URL acquisition. Context7 is required only when the query depends on current package/API/framework/MCP/generated documentation behavior. Provider-specific credentials are optional and must not be required for route readiness. Evidence/artifacts remain under \`.sneakoscope/missions/<super-search-* or route mission>/super-search/\`: intent.json, axes.json, query-variants.json, provider-plan.json, source-ledger.json, lead-ledger.json, claim-ledger.json, attempt-ledger.json, synthesis.md, super-search-proof.json, super-search-gate.json, and super-search-result.json. Do not turn weak discovery into supported claims; finish with an Honest Mode summary of verified sources, blockers, and unverified external coverage.\n`,
@@ -136,10 +141,6 @@ async function installOfficialSkills(root) {
136
141
  'gx-visual-validate': `---\nname: gx-visual-validate\ndescription: Validate render metadata against vgraph.json and beta.json.\n---\n\nRun sks gx validate and drift; fail stale or incomplete hashes, nodes, edges, invariants, or anchors.\n`,
137
142
  'turbo-context-pack': `---\nname: turbo-context-pack\ndescription: Build ultra-low-token context packet with Q4 bits, Q3 tags, top-K claims, and minimal evidence.\n---\n\nDefault to Q4/Q3 plus TriWiki RGBA anchors and attention.use_first. Add Q2/Q1 only when needed or when attention.hydrate_first says source hydration is required. Keep id, hash, path, and coordinate tuple for hydration.\n`,
138
143
  'performance-evaluator': `---\nname: performance-evaluator\ndescription: Evaluate SKS performance, evidence-backed token usage, serialized-size proxies, accuracy-proxy, context-compression, or workflow improvements.\n---\n\nUse sks eval run/compare before claims. Treat serialized_size_bytes as a deterministic non-token proxy. Claim token savings only when actual baseline and candidate token counts include an evidence source; otherwise report token savings as not measured. Report accuracy_delta/proxy, required_recall, support, and meaningful_improvement without substituting proxy size for token evidence.\n`,
139
- 'image-ux-review': imageUxReviewSkill('image-ux-review'),
140
- 'ux-review': imageUxReviewSkill('ux-review'),
141
- 'visual-review': imageUxReviewSkill('visual-review'),
142
- 'ui-ux-review': imageUxReviewSkill('ui-ux-review'),
143
144
  'imagegen': `---\nname: imagegen\ndescription: Required bridge to Codex App built-in image generation for logos, image assets, raster visuals, and image edits.\n---\n\nUse for generated or edited image assets: logo, product image, illustration, sprite, mockup, texture, cutout, or bitmap. Prefer the official Codex App built-in image generation feature documented at ${CODEX_APP_IMAGE_GENERATION_DOC_URL}: ask naturally or invoke \`$imagegen\`. SKS route code checks capability before image-dependent routes, attempts doctor imagegen repair once, and reports codex_imagegen_unavailable if Codex App $imagegen still is not ready. For newest-model requests, make the prompt explicit: "Use ChatGPT Images 2.0 / GPT Image 2.0 with gpt-image-2." Useful official references are ${OPENAI_CHATGPT_IMAGES_2_DOC_URL}, ${OPENAI_GPT_IMAGE_2_MODEL_DOC_URL}, and ${OPENAI_IMAGE_GENERATION_DOC_URL}. Codex App image generation counts against Codex usage limits. Capability detection is not output proof; full SKS evidence requires a real selected raster output path or generated review image artifact. Direct OpenAI API fallback is non-Codex evidence and does not satisfy SKS route evidence unless a separate non-Codex API task is explicitly requested. ${IMAGEGEN_SOCIAL_SOURCE_POLICY} ${CODEX_IMAGEGEN_REQUIRED_POLICY} Do not substitute placeholder SVG/HTML/CSS for requested raster assets; follow design.md when relevant.\n`,
144
145
  'imagegen-source-scout': `---\nname: imagegen-source-scout\ndescription: Source scout for current GPT Image 2.0/gpt-image-2 prompt guidance, official docs, and X/social workflow signals.\n---\n\nUse when the user asks for the latest imagegen docs, ChatGPT Images 2.0 / GPT Image 2.0 / gpt-image-2 behavior, X/social reactions, prompt examples, or community workflow hints before creating an image prompt or SKS imagegen policy. Source order: official OpenAI announcement (${OPENAI_CHATGPT_IMAGES_2_DOC_URL}), Codex App image generation docs (${CODEX_APP_IMAGE_GENERATION_DOC_URL}), gpt-image-2 model docs (${OPENAI_GPT_IMAGE_2_MODEL_DOC_URL}), OpenAI Image Generation API docs (${OPENAI_IMAGE_GENERATION_DOC_URL}), then public X/social/community search for prompt-quality heuristics only. ${IMAGEGEN_SOCIAL_SOURCE_POLICY} If social or web search is unavailable, record that social coverage is unverified and continue from official docs. Output a compact evidence split: official capability/evidence rules, prompt heuristics, social/workflow signals, and blockers. Do not generate images itself; pair this with the imagegen skill for actual raster output.\n`,
145
146
  'getdesign-reference': `---\nname: getdesign-reference\ndescription: Use getdesign.md official design reference as an input to the design.md SSOT for UI/UX, presentation, and HTML/PDF systems.\n---\n\nUse when creating or improving design.md, UI/UX design systems, deck-like HTML artifacts, presentation PDFs, or brand-inspired visual systems. design.md is the only design decision SSOT; reference ${GETDESIGN_REFERENCE.url}, ${GETDESIGN_REFERENCE.docs_url}, and ${AWESOME_DESIGN_MD_REFERENCE.url} only as source inputs to synthesize or update that SSOT or a route-local style-token artifact. Prefer the official Codex skill if available with \`${GETDESIGN_REFERENCE.codex_skill_install}\`. If the skill CLI is unavailable, use this generated skill plus official docs/API/CLI/SDK references and curated DESIGN.md examples as inputs. Do not claim getdesign MCP is configured unless a current official MCP surface is actually installed.\n`,
@@ -907,7 +908,9 @@ function buildFallbackSkillsManifest() {
907
908
  ...RECOMMENDED_SKILLS.map((name) => canonicalSkillNameFromValue(name)),
908
909
  ...DOLLAR_COMMANDS.map((command) => canonicalSkillNameFromValue(String(command.command || '').replace(/^\$/, ''))),
909
910
  ...buildSksCoreSkillManifest().skills.map((skill) => skill.canonical_name)
910
- ].filter((name) => Boolean(name) && !REMOVED_SKS_SKILL_NAME_SET.has(String(name))));
911
+ ].filter((name) => Boolean(name)
912
+ && !REMOVED_SKS_SKILL_NAME_SET.has(String(name))
913
+ && !RETIRED_PUBLIC_IMAGE_UX_REVIEW_SKILL_ALIAS_SET.has(String(name))));
911
914
  return {
912
915
  schema: PACKAGED_SKILLS_MANIFEST_SCHEMA,
913
916
  package_version: PACKAGE_VERSION,
@@ -1161,7 +1164,7 @@ function isGeneratedSksPluginCollisionSkill(text, name) {
1161
1164
  return /Maximum-speed \$Computer-Use\/\$CU lane|Codex App pipeline activation:|Sneakoscope generated|Dollar-command route generated by SKS/i.test(s);
1162
1165
  }
1163
1166
  function enrichSkillContent(name, content) {
1164
- if (!['sks', 'answer', 'wiki', 'qa-loop', 'ppt', 'image-ux-review', 'ux-review', 'visual-review', 'ui-ux-review', 'computer-use-fast', 'cu', 'goal', 'research', 'autoresearch', 'db', 'gx', 'reflection', 'prompt-pipeline', 'pipeline-runner', 'context7-docs', 'turbo-context-pack', 'hproof-evidence-bind'].includes(name))
1167
+ if (!['sks', 'answer', 'wiki', 'qa-loop', 'ppt', 'image-ux-review', 'computer-use-fast', 'cu', 'goal', 'research', 'autoresearch', 'db', 'gx', 'reflection', 'prompt-pipeline', 'pipeline-runner', 'context7-docs', 'turbo-context-pack', 'hproof-evidence-bind'].includes(name))
1165
1168
  return content;
1166
1169
  const text = String(content || '').trimEnd();
1167
1170
  const activation = pipelineActivationText(name);
@@ -1179,7 +1182,7 @@ Context tracking:
1179
1182
  `;
1180
1183
  }
1181
1184
  function pipelineActivationText(name) {
1182
- const stateful = new Set(['sks', 'qa-loop', 'ppt', 'image-ux-review', 'ux-review', 'visual-review', 'ui-ux-review', 'computer-use-fast', 'cu', 'goal', 'research', 'autoresearch', 'db', 'gx', 'prompt-pipeline', 'pipeline-runner']);
1185
+ const stateful = new Set(['sks', 'qa-loop', 'ppt', 'image-ux-review', 'computer-use-fast', 'cu', 'goal', 'research', 'autoresearch', 'db', 'gx', 'prompt-pipeline', 'pipeline-runner']);
1183
1186
  if (!stateful.has(name))
1184
1187
  return '';
1185
1188
  return `Codex App pipeline activation:
@@ -1229,6 +1232,7 @@ async function removeGeneratedAgentSkillAliases(root, skillNames) {
1229
1232
  const current = new Set(skillNames);
1230
1233
  const obsolete = [
1231
1234
  ...SKS_SKILL_NAMES_TO_CLEAN_UP,
1235
+ ...RETIRED_PUBLIC_IMAGE_UX_REVIEW_SKILL_ALIASES,
1232
1236
  'qaloop',
1233
1237
  'wiki-refresh',
1234
1238
  'wikirefresh',
@@ -1242,7 +1246,7 @@ async function removeGeneratedAgentSkillAliases(root, skillNames) {
1242
1246
  if (current.has(name))
1243
1247
  continue;
1244
1248
  const dir = path.join(root, '.agents', 'skills', name);
1245
- const action = await cleanupGeneratedSkillResidue(root, dir, name, (text) => isGeneratedSksAgentSkill(text, name), 'generated-agent-skill-user-content-collision');
1249
+ const action = await cleanupGeneratedSkillResidue(root, dir, name, (text) => isGeneratedSksAgentSkill(text, name) || isRetiredGeneratedImageUxReviewAlias(text, name), 'generated-agent-skill-user-content-collision');
1246
1250
  if (action === 'removed')
1247
1251
  removed.push(path.relative(root, dir));
1248
1252
  if (action === 'quarantined')
@@ -1282,6 +1286,14 @@ function isGeneratedSksAgentSkill(text, name) {
1282
1286
  return false;
1283
1287
  return /Sneakoscope generated|Fallback Codex App picker alias|Codex App picker alias for|Dollar-command route generated by SKS/i.test(s);
1284
1288
  }
1289
+ function isRetiredGeneratedImageUxReviewAlias(text, name) {
1290
+ if (!RETIRED_PUBLIC_IMAGE_UX_REVIEW_SKILL_ALIAS_SET.has(String(name)))
1291
+ return false;
1292
+ const s = String(text || '');
1293
+ return MANAGED_SKILL_MARKER_RE.test(s)
1294
+ && new RegExp(`^name:\\s*${escapeRegExp(name)}\\s*$`, 'm').test(s)
1295
+ && /imagegen\/gpt-image-2 annotated UI\/UX review loop/i.test(s);
1296
+ }
1285
1297
  function isGeneratedSksLegacySkill(text, name) {
1286
1298
  if (typeof text !== 'string')
1287
1299
  return false;
package/dist/core/init.js CHANGED
@@ -695,7 +695,7 @@ export async function initProject(root, opts = {}) {
695
695
  next = shouldSelectCodexLb
696
696
  ? upsertTopLevelTomlString(next, 'model_provider', 'codex-lb')
697
697
  : removeTopLevelTomlKeyIfValue(next, 'model_provider', 'codex-lb');
698
- next = upsertTomlTable(next, 'model_providers.codex-lb', `[model_providers.codex-lb]\nname = "openai"\nbase_url = "${baseUrl}"\nwire_api = "responses"\nenv_key = "CODEX_LB_API_KEY"\nsupports_websockets = true\nrequires_openai_auth = true`);
698
+ next = upsertTomlTable(next, 'model_providers.codex-lb', `[model_providers.codex-lb]\nname = "codex-lb"\nbase_url = "${baseUrl}"\nwire_api = "responses"\nenv_key = "CODEX_LB_API_KEY"\nsupports_websockets = true\nrequires_openai_auth = false`);
699
699
  return `${next.trim()}\n`;
700
700
  }
701
701
  function parseCodexLbEnvKey(text = '') {
@@ -30,8 +30,31 @@ export async function runInstalledPackageSmoke(root, options = {}) {
30
30
  const npmCache = path.join(tmp, 'npm-cache');
31
31
  const installPrefix = path.join(tmp, 'prefix');
32
32
  const packDestination = path.join(tmp, 'pack');
33
- await Promise.all([home, codexHome, npmCache, installPrefix, packDestination, path.join(home, '.tmp')]
33
+ const globalRoot = path.join(tmp, 'global-sks');
34
+ const installCwd = path.join(tmp, 'install-cwd');
35
+ const runtimeTmp = path.join(tmp, 'runtime-tmp');
36
+ const stubBin = path.join(tmp, 'bin');
37
+ const launchctlLog = path.join(tmp, 'launchctl-calls.log');
38
+ const launchctlStub = path.join(stubBin, process.platform === 'win32' ? 'launchctl.cmd' : 'launchctl');
39
+ const npmUserConfig = path.join(tmp, 'npmrc');
40
+ const npmGlobalConfig = path.join(tmp, 'npm-globalrc');
41
+ await Promise.all([home, codexHome, npmCache, installPrefix, packDestination, globalRoot, installCwd, runtimeTmp, stubBin, path.join(home, '.tmp')]
34
42
  .map((dir) => fs.mkdir(dir, { recursive: true })));
43
+ await Promise.all([
44
+ fs.writeFile(launchctlLog, ''),
45
+ fs.writeFile(launchctlStub, launchctlStubSource()),
46
+ fs.writeFile(npmUserConfig, ''),
47
+ fs.writeFile(npmGlobalConfig, '')
48
+ ]);
49
+ await fs.chmod(launchctlStub, 0o700).catch(() => { });
50
+ const externalRoots = [
51
+ { label: 'home', root: home },
52
+ { label: 'codex-home', root: codexHome },
53
+ { label: 'global-sks', root: globalRoot },
54
+ { label: 'install-cwd', root: installCwd },
55
+ { label: 'runtime-tmp', root: runtimeTmp }
56
+ ];
57
+ const externalBeforeInstall = await snapshotTrees(externalRoots);
35
58
  const blockers = [];
36
59
  const commands = [];
37
60
  let tarball = null;
@@ -40,6 +63,14 @@ export async function runInstalledPackageSmoke(root, options = {}) {
40
63
  let packedVersion = null;
41
64
  let expectedSha256 = normalizeSha256(options.expectedSha256);
42
65
  let receiptPath = null;
66
+ let postinstallDefault = {
67
+ scripts_enabled: true,
68
+ external_snapshot_match: false,
69
+ external_findings: [],
70
+ launchctl_calls: [],
71
+ package_local_stamp_present: false,
72
+ opt_in_guidance_present: false
73
+ };
43
74
  if (options.expectedSha256 && !expectedSha256)
44
75
  blockers.push('expected_sha256_invalid');
45
76
  if (options.receipt && !options.tarball)
@@ -85,12 +116,66 @@ export async function runInstalledPackageSmoke(root, options = {}) {
85
116
  }
86
117
  }
87
118
  if (tarball && blockers.length === 0) {
88
- const install = await runJsonCommand(tmp, [
89
- 'npm', 'install', '--global', '--prefix', installPrefix, '--ignore-scripts', '--no-audit', '--no-fund', tarball
90
- ], { home, codexHome, npmCache });
119
+ const install = await runJsonCommand(installCwd, [
120
+ 'npm', 'install', '--global', '--prefix', installPrefix, '--foreground-scripts', '--no-audit', '--no-fund', tarball
121
+ ], {
122
+ home,
123
+ codexHome,
124
+ npmCache,
125
+ env: {
126
+ HOME: home,
127
+ USERPROFILE: home,
128
+ CODEX_HOME: codexHome,
129
+ XDG_CONFIG_HOME: path.join(home, '.config'),
130
+ XDG_CACHE_HOME: path.join(home, '.cache'),
131
+ XDG_DATA_HOME: path.join(home, '.local', 'share'),
132
+ INIT_CWD: installCwd,
133
+ TMPDIR: runtimeTmp,
134
+ PATH: `${stubBin}${path.delimiter}${String(process.env.PATH || '')}`,
135
+ NODE_DISABLE_COMPILE_CACHE: '1',
136
+ NODE_COMPILE_CACHE: '',
137
+ SKS_GLOBAL_ROOT: globalRoot,
138
+ SKS_MENUBAR_LAUNCHCTL: launchctlStub,
139
+ SKS_INSTALLED_SMOKE_LAUNCHCTL_LOG: launchctlLog,
140
+ SKS_POSTINSTALL_BOOTSTRAP: '',
141
+ SKS_POSTINSTALL_NO_BOOTSTRAP: '',
142
+ SKS_POSTINSTALL_PROMPT: '',
143
+ npm_config_userconfig: npmUserConfig,
144
+ NPM_CONFIG_USERCONFIG: npmUserConfig,
145
+ npm_config_globalconfig: npmGlobalConfig,
146
+ NPM_CONFIG_GLOBALCONFIG: npmGlobalConfig,
147
+ npm_config_ignore_scripts: 'false',
148
+ NPM_CONFIG_IGNORE_SCRIPTS: 'false',
149
+ NO_UPDATE_NOTIFIER: '1'
150
+ }
151
+ });
91
152
  commands.push(summarizeInstalledSmokeCommand(install.command, 'install'));
92
153
  if (install.exit_code !== 0)
93
154
  blockers.push('npm_install_tarball_failed');
155
+ const externalAfterInstall = await snapshotTrees(externalRoots);
156
+ const externalFindings = diffTreeSnapshots(externalBeforeInstall, externalAfterInstall);
157
+ const launchctlCalls = await nonEmptyLines(launchctlLog);
158
+ const packageLocalStamp = path.join(installedPackageRoot(installPrefix), 'dist', '.sks-build-stamp.json');
159
+ const packageLocalStampPresent = await regularFileExists(packageLocalStamp);
160
+ const installOutput = `${install.stdout}\n${install.command.stderr_tail}`;
161
+ const optInGuidancePresent = /Automatic bootstrap was not run/.test(installOutput)
162
+ && /SKS_POSTINSTALL_BOOTSTRAP=1/.test(installOutput);
163
+ postinstallDefault = {
164
+ scripts_enabled: true,
165
+ external_snapshot_match: externalFindings.length === 0,
166
+ external_findings: externalFindings,
167
+ launchctl_calls: launchctlCalls,
168
+ package_local_stamp_present: packageLocalStampPresent,
169
+ opt_in_guidance_present: optInGuidancePresent
170
+ };
171
+ if (externalFindings.length)
172
+ blockers.push(...externalFindings.map((finding) => `postinstall_default_external_mutation:${finding}`));
173
+ if (launchctlCalls.length)
174
+ blockers.push('postinstall_default_launchctl_called');
175
+ if (!packageLocalStampPresent)
176
+ blockers.push('postinstall_package_local_stamp_missing');
177
+ if (!optInGuidancePresent)
178
+ blockers.push('postinstall_default_opt_in_guidance_missing');
94
179
  }
95
180
  const bin = process.platform === 'win32'
96
181
  ? path.join(installPrefix, 'sks.cmd')
@@ -187,6 +272,7 @@ export async function runInstalledPackageSmoke(root, options = {}) {
187
272
  required_dollar_commands: [...INSTALLED_REQUIRED_DOLLAR_COMMANDS],
188
273
  closure
189
274
  },
275
+ postinstall_default: postinstallDefault,
190
276
  forbidden_findings: forbiddenFindings,
191
277
  blockers: [...new Set(blockers)]
192
278
  };
@@ -253,6 +339,7 @@ async function runJsonCommand(cwd, argv, opts) {
253
339
  env.npm_config_cache = opts.npmCache;
254
340
  env.NPM_CONFIG_CACHE = opts.npmCache;
255
341
  }
342
+ Object.assign(env, opts.env || {});
256
343
  const res = await runProcess(command || 'node', args, {
257
344
  cwd,
258
345
  timeoutMs: 120_000,
@@ -275,6 +362,72 @@ async function runJsonCommand(cwd, argv, opts) {
275
362
  }
276
363
  };
277
364
  }
365
+ async function snapshotTrees(roots) {
366
+ const snapshot = new Map();
367
+ for (const entry of roots)
368
+ await walkSnapshot(entry.root, entry.root, entry.label, snapshot);
369
+ return snapshot;
370
+ }
371
+ async function walkSnapshot(root, current, label, snapshot) {
372
+ const entries = await fs.readdir(current, { withFileTypes: true }).catch(() => []);
373
+ entries.sort((a, b) => a.name.localeCompare(b.name));
374
+ for (const entry of entries) {
375
+ const absolute = path.join(current, entry.name);
376
+ const relative = path.relative(root, absolute) || '.';
377
+ const key = `${label}:${relative}`;
378
+ const stat = await fs.lstat(absolute);
379
+ const mode = (stat.mode & 0o777).toString(8).padStart(3, '0');
380
+ if (entry.isDirectory()) {
381
+ snapshot.set(key, `directory:${mode}`);
382
+ await walkSnapshot(root, absolute, label, snapshot);
383
+ }
384
+ else if (entry.isFile()) {
385
+ const bytes = await fs.readFile(absolute);
386
+ snapshot.set(key, `file:${mode}:${bytes.length}:${crypto.createHash('sha256').update(bytes).digest('hex')}`);
387
+ }
388
+ else if (entry.isSymbolicLink()) {
389
+ snapshot.set(key, `symlink:${mode}:${await fs.readlink(absolute)}`);
390
+ }
391
+ else {
392
+ snapshot.set(key, `other:${mode}:${stat.size}`);
393
+ }
394
+ }
395
+ }
396
+ function diffTreeSnapshots(before, after) {
397
+ const findings = [];
398
+ const keys = [...new Set([...before.keys(), ...after.keys()])].sort();
399
+ for (const key of keys) {
400
+ if (!before.has(key))
401
+ findings.push(`added:${key}`);
402
+ else if (!after.has(key))
403
+ findings.push(`removed:${key}`);
404
+ else if (before.get(key) !== after.get(key))
405
+ findings.push(`changed:${key}`);
406
+ }
407
+ return findings;
408
+ }
409
+ async function nonEmptyLines(file) {
410
+ const value = await fs.readFile(file, 'utf8').catch(() => '');
411
+ return value.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
412
+ }
413
+ async function regularFileExists(file) {
414
+ const stat = await fs.lstat(file).catch(() => null);
415
+ return Boolean(stat?.isFile() && !stat.isSymbolicLink());
416
+ }
417
+ function installedPackageRoot(prefix) {
418
+ return process.platform === 'win32'
419
+ ? path.join(prefix, 'node_modules', 'sneakoscope')
420
+ : path.join(prefix, 'lib', 'node_modules', 'sneakoscope');
421
+ }
422
+ function launchctlStubSource() {
423
+ if (process.platform === 'win32') {
424
+ return '@echo %*>>\"%SKS_INSTALLED_SMOKE_LAUNCHCTL_LOG%\"\r\n@exit /b 0\r\n';
425
+ }
426
+ return `#!/bin/sh
427
+ printf '%s\n' "$*" >> "$SKS_INSTALLED_SMOKE_LAUNCHCTL_LOG"
428
+ exit 0
429
+ `;
430
+ }
278
431
  export function validateInstalledPublicSurface(commandManifest, dollarManifest) {
279
432
  const commands = Array.isArray(commandManifest?.commands)
280
433
  ? commandManifest.commands.map((row) => String(row?.name || '')) : [];
@@ -454,7 +454,8 @@ export const MANAGED_SKILLS = Object.freeze([
454
454
  'dfix',
455
455
  'image-ux-review',
456
456
  'computer-use',
457
- 'init-deep'
457
+ 'init-deep',
458
+ 'align'
458
459
  ].map((id) => ({ id, required_for: ['codex-native-runtime'] })));
459
460
  export const MANAGED_HOOKS = Object.freeze([
460
461
  { id: 'version-guard', required_for: ['managed-state-current'], risk: 'managed-write' },
@@ -1,5 +1,6 @@
1
1
  import fsp from 'node:fs/promises';
2
2
  import path from 'node:path';
3
+ import { errorCodeOf } from './errors/message.js';
3
4
  export class ManagedPathSafetyError extends Error {
4
5
  code;
5
6
  target;
@@ -73,7 +74,7 @@ export async function ensureConfinedDirectory(boundary, directory) {
73
74
  await fsp.mkdir(current);
74
75
  }
75
76
  catch (error) {
76
- if (errorCode(error) !== 'EEXIST')
77
+ if (errorCodeOf(error) !== 'EEXIST')
77
78
  throw error;
78
79
  }
79
80
  stat = await lstatOrNull(current);
@@ -111,7 +112,7 @@ export async function walkConfinedEntries(boundary, root) {
111
112
  children = await fsp.readdir(current, { withFileTypes: true, encoding: 'utf8' });
112
113
  }
113
114
  catch (error) {
114
- errors.push(`${errorCode(error) || 'managed_path_readdir_failed'}:${current}`);
115
+ errors.push(`${errorCodeOf(error) || 'managed_path_readdir_failed'}:${current}`);
115
116
  return;
116
117
  }
117
118
  children.sort((left, right) => left.name.localeCompare(right.name));
@@ -222,7 +223,7 @@ export async function removeEmptyTreeVerified(boundary, root) {
222
223
  children = await fsp.readdir(current, { withFileTypes: true, encoding: 'utf8' });
223
224
  }
224
225
  catch (error) {
225
- errors.push(`${errorCode(error) || 'managed_path_readdir_failed'}:${current}`);
226
+ errors.push(`${errorCodeOf(error) || 'managed_path_readdir_failed'}:${current}`);
226
227
  remainingPaths.push(current);
227
228
  return;
228
229
  }
@@ -239,7 +240,7 @@ export async function removeEmptyTreeVerified(boundary, root) {
239
240
  after = await fsp.readdir(current);
240
241
  }
241
242
  catch (error) {
242
- errors.push(`${errorCode(error) || 'managed_path_readdir_failed'}:${current}`);
243
+ errors.push(`${errorCodeOf(error) || 'managed_path_readdir_failed'}:${current}`);
243
244
  remainingPaths.push(current);
244
245
  return;
245
246
  }
@@ -251,7 +252,7 @@ export async function removeEmptyTreeVerified(boundary, root) {
251
252
  await fsp.rmdir(current);
252
253
  }
253
254
  catch (error) {
254
- errors.push(`${errorCode(error) || 'managed_path_rmdir_failed'}:${current}`);
255
+ errors.push(`${errorCodeOf(error) || 'managed_path_rmdir_failed'}:${current}`);
255
256
  remainingPaths.push(current);
256
257
  return;
257
258
  }
@@ -270,18 +271,15 @@ export async function lstatConfinedOrNull(boundary, target) {
270
271
  export function publicPathError(error, fallback) {
271
272
  if (error instanceof ManagedPathSafetyError)
272
273
  return `${error.code}:${error.target}`;
273
- return `${errorCode(error) || 'managed_path_operation_failed'}:${fallback}`;
274
+ return `${errorCodeOf(error) || 'managed_path_operation_failed'}:${fallback}`;
274
275
  }
275
276
  async function lstatOrNull(target) {
276
277
  try {
277
278
  return await fsp.lstat(target);
278
279
  }
279
280
  catch (error) {
280
- if (errorCode(error) === 'ENOENT')
281
+ if (errorCodeOf(error) === 'ENOENT')
281
282
  return null;
282
283
  throw error;
283
284
  }
284
285
  }
285
- function errorCode(error) {
286
- return error && typeof error === 'object' && 'code' in error ? String(error.code) : '';
287
- }
@@ -1,6 +1,7 @@
1
1
  import crypto from 'node:crypto';
2
2
  import fsp from 'node:fs/promises';
3
3
  import path from 'node:path';
4
+ import { errorCodeOf } from '../errors/message.js';
4
5
  import { nowIso, sha256, writeTextAtomic } from '../fsx.js';
5
6
  import { MCP_BACKUP_SCHEMA } from './types.js';
6
7
  export async function createMcpBackup(ref, before, operation, server) {
@@ -86,7 +87,7 @@ async function assertSafeBackupDirectory(directory, codexHome) {
86
87
  if (relative.startsWith('..') || path.isAbsolute(relative))
87
88
  throw new Error('mcp_backup_path_escape');
88
89
  for (const candidate of [path.join(codexHome, 'backups'), directory]) {
89
- const stat = await fsp.lstat(candidate).catch((error) => errorCode(error) === 'ENOENT' ? null : Promise.reject(error));
90
+ const stat = await fsp.lstat(candidate).catch((error) => errorCodeOf(error) === 'ENOENT' ? null : Promise.reject(error));
90
91
  if (stat?.isSymbolicLink() || (stat && !stat.isDirectory()))
91
92
  throw new Error('mcp_backup_directory_invalid');
92
93
  if (stat) {
@@ -97,6 +98,3 @@ async function assertSafeBackupDirectory(directory, codexHome) {
97
98
  }
98
99
  }
99
100
  }
100
- function errorCode(error) {
101
- return error && typeof error === 'object' && 'code' in error ? String(error.code) : '';
102
- }