sneakoscope 4.8.7 → 5.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (261) hide show
  1. package/README.md +51 -726
  2. package/config/bench-baseline.json +7 -0
  3. package/crates/sks-core/Cargo.lock +1 -1
  4. package/crates/sks-core/Cargo.toml +1 -1
  5. package/crates/sks-core/src/main.rs +1 -1
  6. package/dist/bin/install.js +35 -0
  7. package/dist/bin/sks.js +1 -1
  8. package/dist/cli/cli-theme.js +51 -0
  9. package/dist/cli/command-registry.js +15 -4
  10. package/dist/cli/help-fast.js +18 -8
  11. package/dist/cli/insane-search-command.js +36 -8
  12. package/dist/cli/install-helpers.js +32 -0
  13. package/dist/commands/codex-lb.js +2 -0
  14. package/dist/commands/doctor.js +94 -9
  15. package/dist/commands/proof.js +6 -3
  16. package/dist/config/skills-manifest.json +82 -54
  17. package/dist/core/agents/agent-conflict-graph.js +5 -0
  18. package/dist/core/agents/agent-lease.js +5 -0
  19. package/dist/core/agents/agent-ledger-schemas.js +1 -0
  20. package/dist/core/agents/agent-lifecycle.js +5 -3
  21. package/dist/core/agents/agent-orchestrator.js +268 -62
  22. package/dist/core/agents/agent-output-validator.js +14 -3
  23. package/dist/core/agents/agent-patch-queue-store.js +95 -2
  24. package/dist/core/agents/agent-patch-queue.js +6 -3
  25. package/dist/core/agents/agent-patch-schema.js +13 -0
  26. package/dist/core/agents/agent-proof-evidence.js +6 -1
  27. package/dist/core/agents/agent-runner-fake.js +40 -0
  28. package/dist/core/agents/agent-worker-pipeline.js +36 -2
  29. package/dist/core/agents/codex-exec-worker-adapter.js +2 -0
  30. package/dist/core/agents/native-cli-worker.js +0 -1
  31. package/dist/core/agents/native-worker-backend-router.js +58 -1
  32. package/dist/core/codex-app/sks-menubar.js +477 -132
  33. package/dist/core/codex-app.js +1 -0
  34. package/dist/core/codex-control/codex-sdk-config-policy.js +2 -1
  35. package/dist/core/codex-control/codex-sdk-env-policy.js +1 -1
  36. package/dist/core/codex-control/schemas/agent-worker-result.schema.js +12 -3
  37. package/dist/core/codex-hooks/codex-hook-managed-install.js +5 -0
  38. package/dist/core/codex-hooks/codex-hook-state-writer.js +70 -11
  39. package/dist/core/codex-hooks/codex-hook-trust-doctor.js +2 -2
  40. package/dist/core/codex-lb/codex-lb-env.js +20 -1
  41. package/dist/core/commands/basic-cli.js +18 -1
  42. package/dist/core/commands/check-command.js +37 -5
  43. package/dist/core/commands/computer-use-command.js +62 -7
  44. package/dist/core/commands/db-command.js +31 -5
  45. package/dist/core/commands/fast-mode-command.js +3 -0
  46. package/dist/core/commands/gate-result-contract.js +43 -0
  47. package/dist/core/commands/gates-command.js +6 -1
  48. package/dist/core/commands/gc-command.js +29 -2
  49. package/dist/core/commands/goal-command.js +9 -2
  50. package/dist/core/commands/gx-command.js +79 -7
  51. package/dist/core/commands/image-ux-review-command.js +188 -13
  52. package/dist/core/commands/mad-db-command.js +85 -176
  53. package/dist/core/commands/mad-sks-command.js +230 -29
  54. package/dist/core/commands/menubar-command.js +24 -2
  55. package/dist/core/commands/naruto-command.js +64 -10
  56. package/dist/core/commands/plan-command.js +76 -0
  57. package/dist/core/commands/ppt-command.js +159 -24
  58. package/dist/core/commands/qa-loop-command.js +6 -2
  59. package/dist/core/commands/release-command.js +55 -2
  60. package/dist/core/commands/research-command.js +1 -1
  61. package/dist/core/commands/review-command.js +217 -0
  62. package/dist/core/commands/route-success-helpers.js +59 -0
  63. package/dist/core/commands/run-command.js +19 -7
  64. package/dist/core/commands/seo-command.js +49 -1
  65. package/dist/core/commands/ui-command.js +161 -0
  66. package/dist/core/commands/uninstall-command.js +15 -1
  67. package/dist/core/db-safety.js +2 -2
  68. package/dist/core/doctor/doctor-dirty-planner.js +51 -7
  69. package/dist/core/doctor/doctor-transaction.js +8 -0
  70. package/dist/core/doctor/doctor-zellij-repair.js +1 -0
  71. package/dist/core/doctor/imagegen-repair.js +161 -0
  72. package/dist/core/feature-fixture-runner.js +2 -2
  73. package/dist/core/feature-fixtures.js +58 -37
  74. package/dist/core/feature-registry.js +102 -2
  75. package/dist/core/fsx.js +39 -6
  76. package/dist/core/image-ux-review/imagegen-adapter.js +48 -11
  77. package/dist/core/image-ux-review.js +16 -0
  78. package/dist/core/imagegen/imagegen-capability.js +11 -5
  79. package/dist/core/imagegen/require-imagegen.js +57 -0
  80. package/dist/core/init/skills.js +10 -5
  81. package/dist/core/mad-db/mad-db-coordinator.js +94 -18
  82. package/dist/core/mad-db/mad-db-policy.js +12 -10
  83. package/dist/core/mad-sks/executors/executor-base.js +8 -2
  84. package/dist/core/mad-sks/executors/index.js +4 -0
  85. package/dist/core/mad-sks/executors/sql-plane-executor.js +194 -0
  86. package/dist/core/naruto/naruto-active-pool.js +15 -4
  87. package/dist/core/naruto/naruto-real-worker-child.js +11 -1
  88. package/dist/core/naruto/naruto-role-policy.js +3 -0
  89. package/dist/core/naruto/naruto-task-hints.js +10 -0
  90. package/dist/core/naruto/naruto-work-graph.js +8 -2
  91. package/dist/core/naruto/naruto-work-item.js +6 -0
  92. package/dist/core/naruto/solution-tournament.js +101 -0
  93. package/dist/core/permission-gates.js +30 -0
  94. package/dist/core/pipeline-internals/runtime-core.js +11 -3
  95. package/dist/core/pipeline-internals/runtime-gates.js +27 -0
  96. package/dist/core/ppt-review/index.js +6 -1
  97. package/dist/core/ppt-review/slide-imagegen-review.js +75 -10
  98. package/dist/core/ppt.js +70 -2
  99. package/dist/core/proof/auto-finalize.js +70 -11
  100. package/dist/core/proof/proof-schema.js +2 -0
  101. package/dist/core/proof/route-adapter.js +5 -1
  102. package/dist/core/proof/route-finalizer.js +8 -5
  103. package/dist/core/proof/selftest-proof-fixtures.js +18 -5
  104. package/dist/core/proof/validation.js +2 -0
  105. package/dist/core/provider/model-router.js +53 -0
  106. package/dist/core/release/release-gate-node.js +3 -0
  107. package/dist/core/release/release-gate-resource-governor.js +1 -0
  108. package/dist/core/routes/constants.js +1 -1
  109. package/dist/core/routes.js +65 -18
  110. package/dist/core/safety/mutation-guard.js +5 -1
  111. package/dist/core/stop-gate/gate-evaluator.js +102 -0
  112. package/dist/core/stop-gate/stop-gate-check.js +16 -1
  113. package/dist/core/stop-gate/stop-gate-writer.js +4 -0
  114. package/dist/core/triwiki/agents-md-projector.js +184 -0
  115. package/dist/core/triwiki-wrongness/wrongness-schema.js +3 -1
  116. package/dist/core/trust-kernel/trust-kernel-schema.js +1 -0
  117. package/dist/core/trust-kernel/trust-report.js +1 -1
  118. package/dist/core/trust-kernel/trust-status.js +2 -0
  119. package/dist/core/ui/dashboard-html.js +111 -0
  120. package/dist/core/update/update-migration-state.js +386 -15
  121. package/dist/core/update-check.js +178 -81
  122. package/dist/core/verification/diff-quality.js +100 -0
  123. package/dist/core/verification/impact-scan.js +164 -0
  124. package/dist/core/verification/machine-feedback.js +146 -0
  125. package/dist/core/verification/mistake-rule-compiler.js +195 -0
  126. package/dist/core/version.js +1 -1
  127. package/dist/core/zellij/zellij-self-heal.js +9 -6
  128. package/dist/core/zellij/zellij-slot-telemetry.js +1 -1
  129. package/dist/scripts/check-feature-quality.js +2 -2
  130. package/dist/scripts/cli-output-consistency-check.js +57 -0
  131. package/dist/scripts/coding-bench-check.js +136 -0
  132. package/dist/scripts/doctor-imagegen-repair-check.js +62 -0
  133. package/dist/scripts/ensure-bin-executable.js +11 -3
  134. package/dist/scripts/harness-benchmark-check.js +104 -0
  135. package/dist/scripts/legacy-update-e2e-check.js +228 -0
  136. package/dist/scripts/mad-db-command-check.js +11 -8
  137. package/dist/scripts/mad-db-route-identity-check.js +8 -6
  138. package/dist/scripts/mad-db-safety-conflict-matrix-check.js +31 -8
  139. package/dist/scripts/mad-db-skill-policy-snapshot-check.js +4 -5
  140. package/dist/scripts/naruto-real-active-pool-runtime-check.js +26 -1
  141. package/dist/scripts/ppt-real-imagegen-wiring-check.js +5 -1
  142. package/dist/scripts/proof-root-cause-policy-check.js +2 -0
  143. package/dist/scripts/release-dag-full-coverage-check.js +13 -4
  144. package/dist/scripts/release-gate-dag-runner.js +14 -0
  145. package/dist/scripts/release-gate-existence-audit.js +2 -1
  146. package/dist/scripts/release-gate-planner.js +2 -1
  147. package/dist/scripts/release-gate-script-parity-check.js +45 -17
  148. package/dist/scripts/release-metadata-1-19-check.js +2 -1
  149. package/dist/scripts/seo-geo-feature-fixture-quality-check.js +1 -1
  150. package/dist/scripts/sks-1-11-gate-lib.js +8 -6
  151. package/dist/scripts/sks-menubar-install-check.js +57 -20
  152. package/dist/scripts/ux-review-real-loop-fixture-check.js +9 -4
  153. package/dist/scripts/ux-review-run-wires-imagegen-check.js +4 -0
  154. package/dist/scripts/zellij-slot-telemetry-runtime-check.js +1 -0
  155. package/docs/assets/sneakoscope-architecture-pipeline.jpg +0 -0
  156. package/docs/demo.tape +28 -0
  157. package/package.json +31 -24
  158. package/schemas/codex/completion-proof.schema.json +3 -2
  159. package/schemas/release/release-gate-node.schema.json +2 -1
  160. package/dist/scripts/agent-backfill-route-blackbox.js +0 -5
  161. package/dist/scripts/agent-dynamic-pool-route-blackbox.js +0 -5
  162. package/dist/scripts/agent-parallel-write-blackbox.js +0 -56
  163. package/dist/scripts/agent-patch-swarm-route-blackbox.js +0 -10
  164. package/dist/scripts/agent-route-blackbox-lib.js +0 -132
  165. package/dist/scripts/blackbox-command-import-smoke.js +0 -143
  166. package/dist/scripts/blackbox-global-shim.js +0 -77
  167. package/dist/scripts/blackbox-matrix.js +0 -70
  168. package/dist/scripts/blackbox-npx-one-shot.js +0 -69
  169. package/dist/scripts/blackbox-pack-install.js +0 -174
  170. package/dist/scripts/brand-neutrality-zero-leakage-blackbox.js +0 -4
  171. package/dist/scripts/build-once-runner-blackbox.js +0 -34
  172. package/dist/scripts/codex-0140-integration-blackbox.js +0 -13
  173. package/dist/scripts/codex-agent-type-blackbox.js +0 -4
  174. package/dist/scripts/codex-app-harness-blackbox.js +0 -4
  175. package/dist/scripts/codex-app-skill-agent-blackbox.js +0 -4
  176. package/dist/scripts/codex-hook-approval-blackbox.js +0 -4
  177. package/dist/scripts/codex-init-deep-directory-local-blackbox.js +0 -4
  178. package/dist/scripts/codex-native-feature-broker-blackbox.js +0 -4
  179. package/dist/scripts/codex-native-pattern-analysis-blackbox.js +0 -4
  180. package/dist/scripts/codex-native-read-repair-split-blackbox.js +0 -55
  181. package/dist/scripts/codex-native-reference-cache-blackbox.js +0 -41
  182. package/dist/scripts/core-skill-integrity-blackbox.js +0 -32
  183. package/dist/scripts/dfix-fast-blackbox-check.js +0 -37
  184. package/dist/scripts/dfix-parallel-write-blackbox.js +0 -48
  185. package/dist/scripts/dfix-patch-swarm-route-blackbox.js +0 -10
  186. package/dist/scripts/doctor-dirty-repair-blackbox.js +0 -22
  187. package/dist/scripts/doctor-dirty-semantic-blackbox.js +0 -8
  188. package/dist/scripts/doctor-native-capability-repair-blackbox.js +0 -39
  189. package/dist/scripts/doctor-transaction-engine-blackbox.js +0 -28
  190. package/dist/scripts/doctor-zellij-fix-blackbox.js +0 -4
  191. package/dist/scripts/doctor-zellij-no-homebrew-blackbox.js +0 -4
  192. package/dist/scripts/doctor-zellij-upgrade-blackbox.js +0 -4
  193. package/dist/scripts/gate-pack-runner-blackbox.js +0 -27
  194. package/dist/scripts/gate-pack-v2-blackbox.js +0 -18
  195. package/dist/scripts/geo-cli-blackbox-check.js +0 -18
  196. package/dist/scripts/loop-collision-blackbox.js +0 -3
  197. package/dist/scripts/loop-concurrency-oversubscription-blackbox.js +0 -3
  198. package/dist/scripts/loop-fixture-production-misuse-blackbox.js +0 -3
  199. package/dist/scripts/loop-kill-interrupt-real-blackbox.js +0 -3
  200. package/dist/scripts/loop-merge-strategy-blackbox.js +0 -3
  201. package/dist/scripts/loop-mesh-production-e2e-blackbox.js +0 -3
  202. package/dist/scripts/loop-side-effect-blackbox.js +0 -3
  203. package/dist/scripts/mad-db-operation-lifecycle-blackbox.js +0 -29
  204. package/dist/scripts/mad-sks-actual-executor-blackbox.js +0 -5
  205. package/dist/scripts/mad-zellij-headless-fallback-blackbox.js +0 -4
  206. package/dist/scripts/mad-zellij-self-heal-blackbox.js +0 -4
  207. package/dist/scripts/naruto-loop-mesh-blackbox.js +0 -3
  208. package/dist/scripts/naruto-real-parallelism-blackbox.js +0 -307
  209. package/dist/scripts/naruto-worktree-coding-blackbox.js +0 -29
  210. package/dist/scripts/parallel-runtime-real-blackbox.js +0 -44
  211. package/dist/scripts/pipeline-codex-native-doctor-mad-routing-real-blackbox.js +0 -77
  212. package/dist/scripts/pipeline-codex-native-e2e-blackbox.js +0 -13
  213. package/dist/scripts/pipeline-codex-native-image-routing-real-blackbox.js +0 -50
  214. package/dist/scripts/pipeline-codex-native-loop-routing-real-blackbox.js +0 -74
  215. package/dist/scripts/pipeline-codex-native-qa-routing-real-blackbox.js +0 -51
  216. package/dist/scripts/pipeline-codex-native-research-routing-real-blackbox.js +0 -45
  217. package/dist/scripts/pipeline-execution-profile-routing-blackbox.js +0 -4
  218. package/dist/scripts/postinstall-global-doctor-blackbox.js +0 -12
  219. package/dist/scripts/ppt-full-e2e-blackbox-check.js +0 -109
  220. package/dist/scripts/ppt-imagegen-blackbox-check.js +0 -46
  221. package/dist/scripts/qa-backfill-route-blackbox.js +0 -5
  222. package/dist/scripts/qa-patch-swarm-route-blackbox.js +0 -10
  223. package/dist/scripts/release-full-parallelism-blackbox.js +0 -41
  224. package/dist/scripts/release-triwiki-first-runner-blackbox.js +0 -48
  225. package/dist/scripts/release-wiring-3110-blackbox.js +0 -27
  226. package/dist/scripts/release-wiring-3112-blackbox.js +0 -17
  227. package/dist/scripts/release-wiring-3113-blackbox.js +0 -17
  228. package/dist/scripts/research-backfill-route-blackbox.js +0 -5
  229. package/dist/scripts/research-final-reviewer-blackbox.js +0 -70
  230. package/dist/scripts/research-stage-cycle-runtime-blackbox.js +0 -40
  231. package/dist/scripts/research-synthesis-writer-blackbox.js +0 -24
  232. package/dist/scripts/route-blackbox-realism-check.js +0 -21
  233. package/dist/scripts/scheduler-resource-claim-blackbox.js +0 -24
  234. package/dist/scripts/seo-cli-blackbox-check.js +0 -18
  235. package/dist/scripts/sks-3110-all-feature-regression-blackbox.js +0 -116
  236. package/dist/scripts/sks-3112-all-feature-regression-blackbox.js +0 -29
  237. package/dist/scripts/sks-3113-all-feature-regression-blackbox.js +0 -17
  238. package/dist/scripts/sks-400-all-feature-regression-blackbox.js +0 -21
  239. package/dist/scripts/sks-400-extreme-parallel-blackbox.js +0 -8
  240. package/dist/scripts/sks-400-five-minute-blackbox.js +0 -9
  241. package/dist/scripts/sks-400-legacy-purge-blackbox.js +0 -8
  242. package/dist/scripts/sks-401-all-feature-regression-blackbox.js +0 -46
  243. package/dist/scripts/sks-401-five-minute-actual-blackbox.js +0 -23
  244. package/dist/scripts/sks-402-all-feature-regression-blackbox.js +0 -9
  245. package/dist/scripts/sks-402-five-minute-real-blackbox.js +0 -22
  246. package/dist/scripts/sksd-warm-cache-blackbox.js +0 -12
  247. package/dist/scripts/team-backfill-route-blackbox.js +0 -5
  248. package/dist/scripts/team-parallel-write-blackbox.js +0 -55
  249. package/dist/scripts/team-patch-swarm-route-blackbox.js +0 -10
  250. package/dist/scripts/triwiki-affected-graph-blackbox.js +0 -28
  251. package/dist/scripts/triwiki-proof-bank-blackbox.js +0 -30
  252. package/dist/scripts/triwiki-proof-bank-lock-blackbox.js +0 -7
  253. package/dist/scripts/ux-patch-swarm-route-blackbox.js +0 -10
  254. package/dist/scripts/ux-review-imagegen-blackbox-check.js +0 -67
  255. package/dist/scripts/zellij-initial-main-only-blackbox.js +0 -28
  256. package/dist/scripts/zellij-pane-lock-concurrency-blackbox.js +0 -80
  257. package/dist/scripts/zellij-pane-lock-open-worker-integration-blackbox.js +0 -137
  258. package/dist/scripts/zellij-self-heal-typed-blackbox.js +0 -4
  259. package/dist/scripts/zellij-slot-telemetry-real-blackbox.js +0 -20
  260. package/dist/scripts/zellij-stacked-fallback-integration-blackbox.js +0 -81
  261. package/dist/scripts/zellij-worker-pane-real-ui-blackbox.js +0 -202
@@ -0,0 +1,111 @@
1
+ export const DASHBOARD_HTML = `<!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>SKS Dashboard</title>
7
+ <style>
8
+ :root {
9
+ --bg:#0d1117; --card:#161b22; --line:#30363d; --text:#e6edf3; --muted:#8b949e;
10
+ --run:#3fb950; --fail:#f85149; --queue:#8b949e; --verify:#58a6ff; --warn:#d29922;
11
+ }
12
+ * { box-sizing: border-box; }
13
+ body { margin:0; background:var(--bg); color:var(--text); font:14px/1.45 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace; }
14
+ header { display:grid; grid-template-columns: 1fr auto; gap:16px; align-items:center; padding:18px 22px; border-bottom:1px solid var(--line); background:#0b1016; position:sticky; top:0; z-index:2; }
15
+ h1 { margin:0; font-size:16px; font-weight:700; letter-spacing:0; }
16
+ .meta { color:var(--muted); margin-top:4px; display:flex; gap:12px; flex-wrap:wrap; }
17
+ .badge { display:inline-flex; align-items:center; min-height:28px; padding:0 10px; border:1px solid var(--line); background:var(--card); color:var(--muted); }
18
+ .badge.pass { color:var(--run); border-color:rgba(63,185,80,.55); }
19
+ .badge.fail { color:var(--fail); border-color:rgba(248,81,73,.55); }
20
+ main { display:grid; grid-template-columns:minmax(0,1fr) 360px; gap:18px; padding:18px; }
21
+ .strip { display:grid; grid-template-columns:repeat(6,minmax(92px,1fr)); gap:10px; margin-bottom:14px; }
22
+ .metric { border:1px solid var(--line); background:var(--card); padding:10px; min-height:64px; }
23
+ .metric b { display:block; font-size:20px; color:var(--text); }
24
+ .metric span { color:var(--muted); font-size:12px; }
25
+ .grid { display:grid; grid-template-columns:repeat(auto-fill,minmax(220px,1fr)); gap:10px; }
26
+ .slot { border:1px solid var(--line); border-left:4px solid var(--queue); background:var(--card); min-height:136px; padding:12px; overflow:hidden; }
27
+ .slot.running { border-left-color:var(--run); }
28
+ .slot.verifying { border-left-color:var(--verify); }
29
+ .slot.completed { border-left-color:var(--run); opacity:.86; }
30
+ .slot.failed { border-left-color:var(--fail); }
31
+ .slot h2 { margin:0 0 8px; font-size:13px; font-weight:700; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
32
+ .row { display:flex; justify-content:space-between; gap:8px; color:var(--muted); font-size:12px; min-width:0; }
33
+ .model { color:var(--verify); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; max-width:110px; }
34
+ .task { margin-top:10px; min-height:38px; color:var(--text); overflow:hidden; display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; }
35
+ .bar { height:6px; background:#0b1016; border:1px solid var(--line); margin-top:12px; overflow:hidden; }
36
+ .bar i { display:block; height:100%; width:0; background:var(--verify); transition:width .25s ease; }
37
+ aside { display:grid; gap:14px; align-content:start; }
38
+ section { border-top:1px solid var(--line); padding-top:12px; }
39
+ section h2 { margin:0 0 10px; font-size:13px; }
40
+ .feed, .gates { display:grid; gap:8px; }
41
+ .event, .gate { background:var(--card); border:1px solid var(--line); padding:9px; color:var(--muted); min-height:34px; overflow-wrap:anywhere; }
42
+ .gate.pass { color:var(--run); }
43
+ .gate.fail { color:var(--fail); }
44
+ .pulse { display:inline-block; width:8px; height:8px; border-radius:50%; background:var(--run); margin-right:7px; animation:pulse 1.6s infinite; }
45
+ @keyframes pulse { 0%,100%{opacity:.35} 50%{opacity:1} }
46
+ @media (max-width: 900px) { main { grid-template-columns:1fr; } .strip { grid-template-columns:repeat(2,minmax(0,1fr)); } header { grid-template-columns:1fr; } }
47
+ </style>
48
+ </head>
49
+ <body>
50
+ <header>
51
+ <div>
52
+ <h1><span class="pulse"></span><span id="mission">SKS mission</span></h1>
53
+ <div class="meta"><span id="route">route</span><span id="elapsed">00:00</span><span id="updated">waiting</span></div>
54
+ </div>
55
+ <div id="gateBadge" class="badge">● proof pending</div>
56
+ </header>
57
+ <main>
58
+ <div>
59
+ <div class="strip" id="strip"></div>
60
+ <div class="grid" id="slots"></div>
61
+ </div>
62
+ <aside>
63
+ <section><h2>Events</h2><div class="feed" id="events"></div></section>
64
+ <section><h2>Gates</h2><div class="gates" id="gates"></div></section>
65
+ </aside>
66
+ </main>
67
+ <script>
68
+ const state = { started: Date.now() };
69
+ function render(payload) {
70
+ const data = payload && payload.snapshot ? payload : { snapshot: payload };
71
+ const snapshot = data.snapshot || {};
72
+ const gates = data.gates || [];
73
+ const events = data.events || [];
74
+ const slots = Object.values(snapshot.slots || {});
75
+ document.getElementById('mission').textContent = snapshot.mission_id || data.mission_id || 'latest';
76
+ document.getElementById('route').textContent = data.route || snapshot.route || 'SKS';
77
+ document.getElementById('elapsed').textContent = elapsed(state.started);
78
+ document.getElementById('updated').textContent = snapshot.updated_at || data.ts || 'waiting';
79
+ const blocked = gates.some(g => g.ok === false);
80
+ const passed = gates.length > 0 && gates.every(g => g.ok !== false);
81
+ const badge = document.getElementById('gateBadge');
82
+ badge.className = 'badge ' + (blocked ? 'fail' : passed ? 'pass' : '');
83
+ badge.textContent = blocked ? '✖ blocked' : passed ? '✔ gate passed' : '● proof pending';
84
+ const counts = snapshot.counts || {};
85
+ const spawned = slots.length;
86
+ document.getElementById('strip').innerHTML = [
87
+ ['run', counts.running || 0], ['verify', counts.verifying || 0], ['queue', counts.queued || 0],
88
+ ['done', counts.completed || 0], ['fail', counts.failed || 0], ['spawned', spawned]
89
+ ].map(([k,v]) => '<div class="metric"><b>'+v+'</b><span>'+k+'</span></div>').join('');
90
+ document.getElementById('slots').innerHTML = slots.map(slot => {
91
+ const status = String(slot.status || 'queued');
92
+ const progress = slot.progress && slot.progress.total ? Math.max(0, Math.min(100, Math.round(slot.progress.done / slot.progress.total * 100))) : (status === 'completed' ? 100 : 0);
93
+ return '<article class="slot '+status+'"><h2>'+esc(slot.slot_id || slot.worker_id || 'slot')+'</h2>'
94
+ + '<div class="row"><span>'+esc(slot.role || 'worker')+'</span><span class="model">'+esc(slot.model || slot.provider || slot.backend || slot.service_tier || '')+'</span></div>'
95
+ + '<div class="row"><span>'+esc(status)+'</span><span>'+esc(slot.latest_event_type || '')+'</span></div>'
96
+ + '<div class="task">'+esc(slot.task_title || slot.current_file || slot.log_tail || '')+'</div>'
97
+ + '<div class="bar"><i style="width:'+progress+'%"></i></div></article>';
98
+ }).join('') || '<div class="event">No live slots yet.</div>';
99
+ document.getElementById('events').innerHTML = events.slice(-30).reverse().map(e => '<div class="event">'+esc((e.ts || '')+' '+(e.type || e.event_type || e.event || 'event'))+'</div>').join('') || '<div class="event">No events yet.</div>';
100
+ document.getElementById('gates').innerHTML = gates.map(g => '<div class="gate '+(g.ok===false?'fail':'pass')+'">'+esc((g.ok===false?'✖ ':'✔ ')+(g.id || g.file || 'gate')+(g.missing&&g.missing.length?' missing '+g.missing.join(', '):''))+'</div>').join('') || '<div class="gate">No gate snapshot yet.</div>';
101
+ }
102
+ function esc(v) { return String(v == null ? '' : v).replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c])); }
103
+ function elapsed(ms) { const s = Math.max(0, Math.floor((Date.now()-ms)/1000)); return String(Math.floor(s/60)).padStart(2,'0')+':'+String(s%60).padStart(2,'0'); }
104
+ fetch('/api/state').then(r => r.json()).then(render).catch(()=>{});
105
+ const source = new EventSource('/events' + location.search);
106
+ source.onmessage = (event) => render(JSON.parse(event.data));
107
+ setInterval(() => { document.getElementById('elapsed').textContent = elapsed(state.started); }, 1000);
108
+ </script>
109
+ </body>
110
+ </html>`;
111
+ //# sourceMappingURL=dashboard-html.js.map
@@ -1,9 +1,13 @@
1
1
  import fsp from 'node:fs/promises';
2
+ import os from 'node:os';
2
3
  import path from 'node:path';
3
- import { ensureDir, exists, globalSksRoot, nowIso, packageRoot, PACKAGE_VERSION, projectRoot, readJson, runProcess, sha256, which, writeJsonAtomic, writeReceiptRotated } from '../fsx.js';
4
+ import { ensureDir, exists, globalSksRoot, nowIso, packageRoot, PACKAGE_VERSION, projectRoot, readJson, readText, runProcess, sha256, which, writeJsonAtomic, writeReceiptRotated, writeTextAtomic } from '../fsx.js';
4
5
  import { MANAGED_ASSET_VERSION } from '../managed-assets/managed-assets-manifest.js';
5
6
  import { enforceRetention } from '../retention.js';
6
7
  import { COMMANDS } from '../../cli/command-registry.js';
8
+ import { reconcileSkills } from '../init/skills.js';
9
+ import { codexHookTrustDoctor } from '../codex-hooks/codex-hook-trust-doctor.js';
10
+ import { writeCodexConfigGuarded } from '../codex/codex-config-guard.js';
7
11
  export const UPDATE_MIGRATION_SCHEMA = 'sks.project-migration-receipt.v2';
8
12
  export const INSTALLATION_EPOCH_SCHEMA = 'sks.installation-epoch.v1';
9
13
  export function installationEpochPath() {
@@ -85,15 +89,19 @@ export async function writeProjectUpdateMigrationReceipt(input) {
85
89
  const receiptPath = projectUpdateMigrationReceiptPath(input.root);
86
90
  const epoch = await ensureInstallationEpoch(input.source);
87
91
  const retentionCleanup = await runUpdateRetentionCleanup(input.root, input.source);
88
- const requiredBlockers = input.blockers || [];
89
- const optionalWarnings = input.warnings || [];
92
+ const legacyStages = await runLegacyMigrationStages(input.root, { fromVersion: input.fromVersion || null });
93
+ const stageBlockers = legacyStages.flatMap((stage) => stage.blockers.map((blocker) => `${stage.id}:${blocker}`));
94
+ const stageWarnings = legacyStages.flatMap((stage) => stage.warnings.map((warning) => `${stage.id}:${warning}`));
95
+ const requiredBlockers = [...(input.blockers || []), ...stageBlockers];
96
+ const optionalWarnings = [...(input.warnings || []), ...stageWarnings];
90
97
  const receipt = {
91
98
  schema: UPDATE_MIGRATION_SCHEMA,
92
- status: input.status || 'current',
99
+ status: input.status || (requiredBlockers.length ? 'blocked' : 'current'),
93
100
  sks_version: PACKAGE_VERSION,
94
101
  root: input.root,
95
102
  source: input.source,
96
103
  generated_at: nowIso(),
104
+ from_version: input.fromVersion || null,
97
105
  project_root_hash: projectRootHash(input.root),
98
106
  installation_epoch_sha256: installationEpochSha256(epoch),
99
107
  project_semantic_hash: await projectSemanticHash(input.root),
@@ -101,7 +109,8 @@ export async function writeProjectUpdateMigrationReceipt(input) {
101
109
  installation_epoch_path: installationEpochPath(),
102
110
  doctor: input.doctor || null,
103
111
  retention_cleanup: retentionCleanup,
104
- update_stages: input.updateStages || [],
112
+ update_stages: [...(input.updateStages || []), ...legacyStages],
113
+ legacy_migration_stages: legacyStages,
105
114
  required_blockers: requiredBlockers,
106
115
  optional_warnings: optionalWarnings,
107
116
  blockers: requiredBlockers,
@@ -175,6 +184,278 @@ export async function runUpdateRetentionCleanup(root, source = 'update-migration
175
184
  };
176
185
  }
177
186
  }
187
+ const LEGACY_MIGRATION_STAGES = [
188
+ {
189
+ id: 'legacy-team-artifacts',
190
+ min_from_version: '0.0.0',
191
+ run: runLegacyTeamArtifactsStage
192
+ },
193
+ {
194
+ id: 'session-state-split',
195
+ min_from_version: '0.0.0',
196
+ run: runSessionStateSplitStage
197
+ },
198
+ {
199
+ id: 'skills-reconcile',
200
+ min_from_version: '0.0.0',
201
+ run: runSkillsReconcileStage
202
+ },
203
+ {
204
+ id: 'menubar-retarget',
205
+ min_from_version: '0.0.0',
206
+ run: runMenubarRetargetStage
207
+ },
208
+ {
209
+ id: 'config-fastmode-normalize',
210
+ min_from_version: '0.0.0',
211
+ run: runConfigFastModeNormalizeStage
212
+ },
213
+ {
214
+ id: 'hook-trust-refresh',
215
+ min_from_version: '0.0.0',
216
+ run: runHookTrustRefreshStage
217
+ },
218
+ {
219
+ id: 'receipt-rotation',
220
+ min_from_version: '0.0.0',
221
+ run: runReceiptRotationStage
222
+ }
223
+ ];
224
+ async function runLegacyMigrationStages(root, opts = {}) {
225
+ const fromVersion = opts.fromVersion || null;
226
+ const runs = [];
227
+ for (const stage of LEGACY_MIGRATION_STAGES) {
228
+ if (!legacyStageApplies(fromVersion, stage.min_from_version)) {
229
+ runs.push({
230
+ schema: 'sks.legacy-update-migration-stage.v1',
231
+ id: stage.id,
232
+ ok: true,
233
+ status: 'skipped',
234
+ min_from_version: stage.min_from_version,
235
+ from_version: fromVersion,
236
+ actions: ['skipped_by_from_version'],
237
+ blockers: [],
238
+ warnings: []
239
+ });
240
+ continue;
241
+ }
242
+ try {
243
+ const result = await stage.run(root, fromVersion);
244
+ runs.push({
245
+ schema: 'sks.legacy-update-migration-stage.v1',
246
+ id: stage.id,
247
+ min_from_version: stage.min_from_version,
248
+ from_version: fromVersion,
249
+ ...result
250
+ });
251
+ }
252
+ catch (err) {
253
+ runs.push({
254
+ schema: 'sks.legacy-update-migration-stage.v1',
255
+ id: stage.id,
256
+ ok: false,
257
+ status: 'failed',
258
+ min_from_version: stage.min_from_version,
259
+ from_version: fromVersion,
260
+ actions: [],
261
+ blockers: [err?.message || String(err)],
262
+ warnings: []
263
+ });
264
+ }
265
+ }
266
+ return runs;
267
+ }
268
+ async function runLegacyTeamArtifactsStage(root) {
269
+ const candidates = [
270
+ path.join(root, '.sneakoscope', 'team'),
271
+ path.join(root, '.sneakoscope', 'team-dashboard-state.json'),
272
+ path.join(root, '.sneakoscope', 'work-order-ledger.json')
273
+ ];
274
+ const present = [];
275
+ for (const candidate of candidates) {
276
+ if (await exists(candidate))
277
+ present.push(path.relative(root, candidate));
278
+ }
279
+ await writeJsonAtomic(path.join(root, '.sneakoscope', 'update', 'legacy-team-artifacts.json'), {
280
+ schema: 'sks.legacy-team-artifacts-migration.v1',
281
+ generated_at: nowIso(),
282
+ present
283
+ });
284
+ return {
285
+ ok: true,
286
+ status: 'ok',
287
+ actions: present.length ? ['recorded_legacy_team_artifacts'] : ['no_legacy_team_artifacts'],
288
+ blockers: [],
289
+ warnings: [],
290
+ detail: { present }
291
+ };
292
+ }
293
+ async function runSessionStateSplitStage(root) {
294
+ const legacyCurrent = path.join(root, '.sneakoscope', 'current.json');
295
+ const stateCurrent = path.join(root, '.sneakoscope', 'state', 'current.json');
296
+ const sessionsDir = path.join(root, '.sneakoscope', 'state', 'sessions');
297
+ await ensureDir(sessionsDir);
298
+ const actions = [];
299
+ let current = await readJson(stateCurrent, null).catch(() => null);
300
+ const legacy = await readJson(legacyCurrent, null).catch(() => null);
301
+ if (!current && legacy) {
302
+ current = legacy;
303
+ await writeJsonAtomic(stateCurrent, current);
304
+ actions.push('copied_legacy_current_json_to_state_current');
305
+ }
306
+ const missionId = typeof current?.mission_id === 'string' ? current.mission_id : typeof current?.mission === 'string' ? current.mission : null;
307
+ if (missionId) {
308
+ const sessionPath = path.join(sessionsDir, `${safeFileName(missionId)}.json`);
309
+ if (!(await exists(sessionPath))) {
310
+ await writeJsonAtomic(sessionPath, { ...current, migrated_from: path.relative(root, stateCurrent), migrated_at: nowIso() });
311
+ actions.push('wrote_state_session_alias');
312
+ }
313
+ }
314
+ if (!actions.length)
315
+ actions.push('session_state_current');
316
+ return { ok: true, status: 'ok', actions, blockers: [], warnings: [], detail: { legacy_present: Boolean(legacy), mission_id: missionId } };
317
+ }
318
+ async function runSkillsReconcileStage(root) {
319
+ const global = await reconcileSkills({ targetDir: path.join(os.homedir(), '.agents', 'skills'), scope: 'global', fix: true })
320
+ .catch((err) => ({ ok: false, error: err?.message || String(err) }));
321
+ const project = await reconcileSkills({ targetDir: path.join(root, '.agents', 'skills'), scope: 'project', fix: true })
322
+ .catch((err) => ({ ok: false, error: err?.message || String(err) }));
323
+ const blockers = [
324
+ ...(global.ok === false || global.error ? [`global:${global.error || 'failed'}`] : []),
325
+ ...(project.ok === false || project.error ? [`project:${project.error || 'failed'}`] : [])
326
+ ];
327
+ return {
328
+ ok: blockers.length === 0,
329
+ status: blockers.length ? 'failed' : 'ok',
330
+ actions: ['reconciled_global_skills', 'reconciled_project_skills'],
331
+ blockers,
332
+ warnings: [],
333
+ detail: {
334
+ global_installed: Array.isArray(global.installed) ? global.installed.length : null,
335
+ project_removed: Array.isArray(project.removed) ? project.removed.length : null
336
+ }
337
+ };
338
+ }
339
+ async function runMenubarRetargetStage(root) {
340
+ const actionScript = path.join(os.homedir(), '.codex', 'sks-menubar', 'sks-menubar-action.sh');
341
+ const text = await readText(actionScript, null);
342
+ if (typeof text !== 'string')
343
+ return { ok: true, status: 'ok', actions: ['menubar_action_script_absent'], blockers: [], warnings: [] };
344
+ const desired = path.join(packageRoot(), 'dist', 'bin', 'sks.js');
345
+ const line = `SKS_ENTRY=${JSON.stringify(desired)}`;
346
+ const next = /^\s*SKS_ENTRY\s*=.*$/m.test(text)
347
+ ? text.replace(/^\s*SKS_ENTRY\s*=.*$/m, line)
348
+ : `${line}\n${text}`;
349
+ if (next !== text) {
350
+ await writeTextAtomic(actionScript, next);
351
+ return { ok: true, status: 'ok', actions: ['retargeted_menubar_action_script'], blockers: [], warnings: [], detail: { action_script: actionScript } };
352
+ }
353
+ return { ok: true, status: 'ok', actions: ['menubar_action_script_current'], blockers: [], warnings: [], detail: { action_script: actionScript } };
354
+ }
355
+ async function runConfigFastModeNormalizeStage() {
356
+ const configPath = path.join(os.homedir(), '.codex', 'config.toml');
357
+ const text = await readText(configPath, null);
358
+ if (typeof text !== 'string')
359
+ return { ok: true, status: 'ok', actions: ['codex_config_absent'], blockers: [], warnings: [] };
360
+ const table = text.match(/(^|\n)\[user\.fast_mode\][\s\S]*?(?=\n\[|$)/);
361
+ const misplaced = table?.[0]?.match(/^\s*default_profile\s*=\s*"([^"]+)"\s*$/m)?.[1] || null;
362
+ const hasTopLevel = /^\s*default_profile\s*=\s*"[^"]+"\s*$/m.test(text.slice(0, Math.max(0, text.search(/^\s*\[/m))));
363
+ if (!misplaced)
364
+ return { ok: true, status: 'ok', actions: ['fastmode_default_profile_current'], blockers: [], warnings: [] };
365
+ let next = text.replace(/(^|\n)(\[user\.fast_mode\][\s\S]*?)(?=\n\[|$)/, (_match, prefix, block) => {
366
+ const cleaned = String(block).split(/\r?\n/).filter((line) => !/^\s*default_profile\s*=/.test(line)).join('\n').trimEnd();
367
+ return `${prefix}${cleaned}`;
368
+ });
369
+ if (!hasTopLevel)
370
+ next = insertTopLevelTomlKey(next, `default_profile = ${JSON.stringify(misplaced)}`);
371
+ let guardResult = null;
372
+ if (next !== text) {
373
+ guardResult = await writeCodexConfigGuarded({
374
+ configPath,
375
+ before: text,
376
+ mutate: () => `${next.trim()}\n`,
377
+ cause: 'project-update-fastmode-default-profile',
378
+ backupTag: 'project-update-fastmode-default-profile',
379
+ preserveFastUiKeys: true
380
+ });
381
+ if (!guardResult.ok) {
382
+ return {
383
+ ok: false,
384
+ status: 'failed',
385
+ actions: ['normalize_fastmode_default_profile_blocked'],
386
+ blockers: [`codex_config_guard:${guardResult.status}`],
387
+ warnings: [],
388
+ detail: { config_path: configPath, default_profile: misplaced, guard: guardResult }
389
+ };
390
+ }
391
+ }
392
+ return { ok: true, status: 'ok', actions: ['normalized_fastmode_default_profile'], blockers: [], warnings: [], detail: { config_path: configPath, default_profile: misplaced, guard: guardResult } };
393
+ }
394
+ async function runHookTrustRefreshStage(root) {
395
+ const result = await codexHookTrustDoctor(root, { fix: true, managed: true, actual: true });
396
+ const blockers = result.ok === false ? (result.blockers || ['hook_trust_refresh_failed']) : [];
397
+ return {
398
+ ok: blockers.length === 0,
399
+ status: blockers.length ? 'failed' : 'ok',
400
+ actions: ['refreshed_hook_trust'],
401
+ blockers,
402
+ warnings: result.warnings || [],
403
+ detail: { entries: result.current_hash_count ?? null }
404
+ };
405
+ }
406
+ async function runReceiptRotationStage(root) {
407
+ const receiptPath = projectUpdateMigrationReceiptPath(root);
408
+ const dir = path.dirname(receiptPath);
409
+ const base = path.basename(receiptPath);
410
+ const rows = await fsp.readdir(dir, { withFileTypes: true }).catch(() => []);
411
+ const rotated = await Promise.all(rows
412
+ .filter((entry) => entry.isFile() && entry.name.startsWith(`${base}.`) && entry.name.endsWith('.json'))
413
+ .map(async (entry) => {
414
+ const file = path.join(dir, entry.name);
415
+ return { file, stat: await fsp.stat(file).catch(() => null) };
416
+ }));
417
+ const removable = rotated
418
+ .filter((row) => Boolean(row.stat))
419
+ .sort((a, b) => b.stat.mtimeMs - a.stat.mtimeMs)
420
+ .slice(5);
421
+ await Promise.all(removable.map((row) => fsp.rm(row.file, { force: true }).catch(() => undefined)));
422
+ return {
423
+ ok: true,
424
+ status: 'ok',
425
+ actions: removable.length ? ['rotated_old_update_receipts'] : ['receipt_rotation_current'],
426
+ blockers: [],
427
+ warnings: [],
428
+ detail: { removed: removable.length }
429
+ };
430
+ }
431
+ function legacyStageApplies(fromVersion, minFromVersion) {
432
+ if (!fromVersion)
433
+ return true;
434
+ if (compareVersionLike(fromVersion, PACKAGE_VERSION) > 0)
435
+ return false;
436
+ return compareVersionLike(fromVersion, minFromVersion) >= 0;
437
+ }
438
+ function compareVersionLike(a, b) {
439
+ const pa = String(a || '').split(/[.-]/).map((value) => Number.parseInt(value, 10) || 0);
440
+ const pb = String(b || '').split(/[.-]/).map((value) => Number.parseInt(value, 10) || 0);
441
+ for (let index = 0; index < Math.max(pa.length, pb.length, 3); index += 1) {
442
+ if ((pa[index] || 0) > (pb[index] || 0))
443
+ return 1;
444
+ if ((pa[index] || 0) < (pb[index] || 0))
445
+ return -1;
446
+ }
447
+ return 0;
448
+ }
449
+ function safeFileName(value) {
450
+ return String(value || 'session').replace(/[^A-Za-z0-9_.-]+/g, '-').replace(/^-+|-+$/g, '') || 'session';
451
+ }
452
+ function insertTopLevelTomlKey(text, line) {
453
+ const raw = String(text || '').trimEnd();
454
+ const firstTable = raw.search(/^\s*\[/m);
455
+ if (firstTable < 0)
456
+ return `${line}\n${raw}`.trim() + '\n';
457
+ return `${raw.slice(0, firstTable).trimEnd()}\n${line}\n\n${raw.slice(firstTable).trimStart()}`.trim() + '\n';
458
+ }
178
459
  export async function ensureCurrentMigrationBeforeCommand(input) {
179
460
  const env = input.env || process.env;
180
461
  const command = input.command;
@@ -215,7 +496,8 @@ export async function ensureCurrentMigrationBeforeCommand(input) {
215
496
  };
216
497
  return withUpdateMigrationLock(root, empty, async () => {
217
498
  const reportFile = path.join(root, '.sneakoscope', 'update', 'doctor-migration.json');
218
- const doctor = await runPackageLocalDoctor({
499
+ const baseTimeoutMs = migrationDoctorTimeoutMs(env);
500
+ let doctor = await runPackageLocalDoctor({
219
501
  root,
220
502
  args: ['doctor', '--fix', '--yes', '--profile', 'migration', '--machine-only', '--report-file', reportFile],
221
503
  env: {
@@ -223,29 +505,51 @@ export async function ensureCurrentMigrationBeforeCommand(input) {
223
505
  SKS_UPDATE_MIGRATION_GATE_DISABLED: '1',
224
506
  SKS_DISABLE_UPDATE_CHECK: '1'
225
507
  },
226
- timeoutMs: 30_000,
508
+ timeoutMs: baseTimeoutMs,
227
509
  maxOutputBytes: 32 * 1024
228
510
  });
511
+ const timeoutWarnings = [];
512
+ if (!doctor.ok && doctor.timedOut) {
513
+ timeoutWarnings.push(`doctor_migration_timeout_retry:timeout_ms=${baseTimeoutMs}`);
514
+ doctor = await runPackageLocalDoctor({
515
+ root,
516
+ args: ['doctor', '--fix', '--yes', '--profile', 'migration', '--machine-only', '--report-file', reportFile],
517
+ env: {
518
+ ...env,
519
+ SKS_UPDATE_MIGRATION_GATE_DISABLED: '1',
520
+ SKS_DISABLE_UPDATE_CHECK: '1',
521
+ SKS_MIGRATION_DOCTOR_RETRY: '1'
522
+ },
523
+ timeoutMs: baseTimeoutMs * 2,
524
+ maxOutputBytes: 32 * 1024
525
+ });
526
+ }
229
527
  if (!doctor.ok) {
230
- const requiredBlockers = doctor.required_blockers.length ? doctor.required_blockers : ['doctor_migration_profile_failed'];
528
+ const blocker = doctor.timedOut ? 'doctor_migration_timeout' : 'doctor_migration_failed';
529
+ const requiredBlockers = [blocker, ...(doctor.required_blockers.length ? doctor.required_blockers : [])];
530
+ const warnings = [
531
+ ...timeoutWarnings,
532
+ ...doctor.optional_warnings,
533
+ ...(doctor.timedOut ? ['doctor_migration_timeout_may_be_network_or_first_compile_slow_run_sks_doctor_fix_yes_for_live_progress'] : [])
534
+ ];
231
535
  const blocked = await writeProjectUpdateMigrationReceipt({
232
536
  root,
233
537
  source: 'first-command-gate',
234
538
  status: 'blocked',
235
539
  doctor,
236
540
  blockers: requiredBlockers,
237
- warnings: doctor.optional_warnings
541
+ warnings
238
542
  });
239
- return { ...empty, ok: false, status: 'blocked', receipt: blocked, doctor, failed_stage_id: 'doctor:migration-profile', blockers: requiredBlockers, warnings: doctor.optional_warnings };
543
+ return { ...empty, ok: false, status: 'blocked', receipt: blocked, doctor, failed_stage_id: 'doctor:migration-profile', blockers: requiredBlockers, warnings };
240
544
  }
241
545
  const current = await writeProjectUpdateMigrationReceipt({
242
546
  root,
243
547
  source: 'first-command-gate',
244
548
  doctor,
245
549
  blockers: [],
246
- warnings: doctor.optional_warnings
550
+ warnings: [...timeoutWarnings, ...doctor.optional_warnings]
247
551
  });
248
- return { ...empty, ok: true, status: 'repaired', receipt: current, doctor, failed_stage_id: null, blockers: [], warnings: [] };
552
+ return { ...empty, ok: true, status: 'repaired', receipt: current, doctor, failed_stage_id: null, blockers: [], warnings: [...timeoutWarnings, ...doctor.optional_warnings] };
249
553
  }, recheck ? { recheck } : {});
250
554
  }
251
555
  export async function runPostinstallGlobalDoctorAndMarkPending(input = {}) {
@@ -267,7 +571,7 @@ export async function runPostinstallGlobalDoctorAndMarkPending(input = {}) {
267
571
  SKS_DISABLE_UPDATE_CHECK: '1',
268
572
  SKS_POSTINSTALL_NO_BOOTSTRAP: '1'
269
573
  },
270
- timeoutMs: 15_000,
574
+ timeoutMs: migrationDoctorTimeoutMs(env),
271
575
  maxOutputBytes: 32 * 1024
272
576
  });
273
577
  const pending = await writePendingUpdateMigration({
@@ -288,10 +592,18 @@ function commandSkipsMigrationGate(command) {
288
592
  const entry = COMMANDS[command];
289
593
  return entry?.skipMigrationGate === true || entry?.readonly === true;
290
594
  }
595
+ function migrationDoctorTimeoutMs(env) {
596
+ const override = Number.parseInt(env.SKS_MIGRATION_DOCTOR_TIMEOUT_MS || '', 10);
597
+ return Number.isFinite(override) && override > 0 ? override : 180_000;
598
+ }
291
599
  export async function runPackageLocalDoctor(input = {}) {
292
600
  const entrypoint = input.entrypoint || path.join(packageRoot(), 'dist', 'bin', 'sks.js');
293
601
  const cwd = input.root || globalSksRoot();
294
602
  const args = input.args || ['doctor', '--json'];
603
+ const env = input.env || process.env;
604
+ const testRun = testPackageLocalDoctorRun({ entrypoint, cwd, args, env });
605
+ if (testRun)
606
+ return testRun;
295
607
  if (!(await exists(entrypoint))) {
296
608
  return {
297
609
  schema: 'sks.package-local-doctor-run.v1',
@@ -306,6 +618,8 @@ export async function runPackageLocalDoctor(input = {}) {
306
618
  optional_warnings: [],
307
619
  stdout_tail: '',
308
620
  stderr_tail: '',
621
+ timedOut: false,
622
+ timed_out: false,
309
623
  error: `missing package-local sks entrypoint: ${entrypoint}`
310
624
  };
311
625
  }
@@ -313,7 +627,7 @@ export async function runPackageLocalDoctor(input = {}) {
313
627
  cwd,
314
628
  env: {
315
629
  ...process.env,
316
- ...(input.env || {}),
630
+ ...env,
317
631
  SKS_UPDATE_MIGRATION_GATE_DISABLED: '1',
318
632
  SKS_DISABLE_UPDATE_CHECK: '1'
319
633
  },
@@ -322,7 +636,8 @@ export async function runPackageLocalDoctor(input = {}) {
322
636
  }).catch((err) => ({
323
637
  code: 1,
324
638
  stdout: '',
325
- stderr: err?.message || String(err)
639
+ stderr: err?.message || String(err),
640
+ timedOut: false
326
641
  }));
327
642
  const reportFile = reportFileFromArgs(args);
328
643
  const parsed = reportFile
@@ -345,9 +660,65 @@ export async function runPackageLocalDoctor(input = {}) {
345
660
  optional_warnings: optionalWarnings,
346
661
  stdout_tail: tail(result.stdout || ''),
347
662
  stderr_tail: tail(result.stderr || ''),
663
+ timedOut: result.timedOut === true,
664
+ timed_out: result.timedOut === true,
348
665
  error: ok ? null : tail(result.stderr || result.stdout || requiredBlockers.join(', ') || 'doctor failed')
349
666
  };
350
667
  }
668
+ function testPackageLocalDoctorRun(input) {
669
+ if (input.env.SKS_TEST_DOCTOR_TIMEOUT_ONCE === '1' && input.env.SKS_MIGRATION_DOCTOR_RETRY !== '1') {
670
+ return mockPackageLocalDoctorRun(input, {
671
+ ok: false,
672
+ timedOut: true,
673
+ exitCode: 124,
674
+ blockers: ['test_doctor_timeout_once']
675
+ });
676
+ }
677
+ if (input.env.SKS_TEST_DOCTOR_TIMEOUT_ONCE === '1' && input.env.SKS_MIGRATION_DOCTOR_RETRY === '1') {
678
+ return mockPackageLocalDoctorRun(input, {
679
+ ok: true,
680
+ timedOut: false,
681
+ exitCode: 0,
682
+ warnings: ['test_doctor_retry_succeeded']
683
+ });
684
+ }
685
+ if (input.env.SKS_TEST_DOCTOR_FAIL === '1') {
686
+ return mockPackageLocalDoctorRun(input, {
687
+ ok: false,
688
+ timedOut: false,
689
+ exitCode: 1,
690
+ blockers: ['test_doctor_failed']
691
+ });
692
+ }
693
+ if (input.env.SKS_TEST_DOCTOR_OK === '1') {
694
+ return mockPackageLocalDoctorRun(input, {
695
+ ok: true,
696
+ timedOut: false,
697
+ exitCode: 0,
698
+ warnings: ['test_doctor_ok']
699
+ });
700
+ }
701
+ return null;
702
+ }
703
+ function mockPackageLocalDoctorRun(input, result) {
704
+ return {
705
+ schema: 'sks.package-local-doctor-run.v1',
706
+ ok: result.ok,
707
+ status: result.ok ? 'ok' : 'failed',
708
+ entrypoint: input.entrypoint,
709
+ cwd: input.cwd,
710
+ args: input.args,
711
+ exit_code: result.exitCode,
712
+ parsed_ok: result.ok,
713
+ required_blockers: result.ok ? [] : result.blockers || [],
714
+ optional_warnings: result.warnings || [],
715
+ stdout_tail: result.ok ? '{"ok":true}' : '',
716
+ stderr_tail: result.ok ? '' : (result.blockers || ['doctor failed']).join(', '),
717
+ timedOut: result.timedOut,
718
+ timed_out: result.timedOut,
719
+ error: result.ok ? null : (result.blockers || ['doctor failed']).join(', ')
720
+ };
721
+ }
351
722
  export async function resolveInstalledSksEntrypoint(input = {}) {
352
723
  const packageName = input.packageName || 'sneakoscope';
353
724
  const candidates = [