sneakoscope 4.8.7 → 5.1.2

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 (288) hide show
  1. package/README.md +51 -726
  2. package/bench/tasks/t01-off-by-one/repo/package.json +9 -0
  3. package/bench/tasks/t01-off-by-one/repo/src/pagination.js +5 -0
  4. package/bench/tasks/t01-off-by-one/repo/test.js +5 -0
  5. package/bench/tasks/t01-off-by-one/task.json +13 -0
  6. package/bench/tasks/t02-signature-cochange/repo/package.json +9 -0
  7. package/bench/tasks/t02-signature-cochange/repo/src/admin.js +5 -0
  8. package/bench/tasks/t02-signature-cochange/repo/src/audit.js +5 -0
  9. package/bench/tasks/t02-signature-cochange/repo/src/labels.js +3 -0
  10. package/bench/tasks/t02-signature-cochange/repo/src/profile.js +5 -0
  11. package/bench/tasks/t02-signature-cochange/repo/test.js +9 -0
  12. package/bench/tasks/t02-signature-cochange/task.json +12 -0
  13. package/bench/tasks/t03-type-puzzle/repo/package.json +9 -0
  14. package/bench/tasks/t03-type-puzzle/repo/src/config.js +4 -0
  15. package/bench/tasks/t03-type-puzzle/repo/test.js +6 -0
  16. package/bench/tasks/t03-type-puzzle/task.json +13 -0
  17. package/bench/tasks/t04-refactor-preserve/repo/package.json +9 -0
  18. package/bench/tasks/t04-refactor-preserve/repo/src/cart.js +8 -0
  19. package/bench/tasks/t04-refactor-preserve/repo/test.js +4 -0
  20. package/bench/tasks/t04-refactor-preserve/task.json +12 -0
  21. package/bench/tasks/t05-performance/repo/package.json +9 -0
  22. package/bench/tasks/t05-performance/repo/src/pairs.js +9 -0
  23. package/bench/tasks/t05-performance/repo/test.js +11 -0
  24. package/bench/tasks/t05-performance/task.json +12 -0
  25. package/bench/tasks/t06-mistake-rule/repo/package.json +9 -0
  26. package/bench/tasks/t06-mistake-rule/repo/src/loader.js +6 -0
  27. package/bench/tasks/t06-mistake-rule/repo/test.js +7 -0
  28. package/bench/tasks/t06-mistake-rule/task.json +12 -0
  29. package/config/bench-baseline.json +7 -0
  30. package/crates/sks-core/Cargo.lock +1 -1
  31. package/crates/sks-core/Cargo.toml +1 -1
  32. package/crates/sks-core/src/main.rs +1 -1
  33. package/dist/bin/install.js +35 -0
  34. package/dist/bin/sks.js +1 -1
  35. package/dist/cli/cli-theme.js +51 -0
  36. package/dist/cli/command-registry.js +15 -4
  37. package/dist/cli/help-fast.js +18 -8
  38. package/dist/cli/insane-search-command.js +36 -8
  39. package/dist/cli/install-helpers.js +32 -0
  40. package/dist/commands/codex-lb.js +2 -0
  41. package/dist/commands/doctor.js +62 -6
  42. package/dist/commands/proof.js +6 -3
  43. package/dist/config/skills-manifest.json +82 -54
  44. package/dist/core/agents/agent-conflict-graph.js +5 -0
  45. package/dist/core/agents/agent-lease.js +5 -0
  46. package/dist/core/agents/agent-ledger-schemas.js +1 -0
  47. package/dist/core/agents/agent-orchestrator.js +268 -62
  48. package/dist/core/agents/agent-output-validator.js +14 -3
  49. package/dist/core/agents/agent-patch-queue-store.js +95 -2
  50. package/dist/core/agents/agent-patch-queue.js +6 -3
  51. package/dist/core/agents/agent-patch-schema.js +13 -0
  52. package/dist/core/agents/agent-proof-evidence.js +6 -1
  53. package/dist/core/agents/agent-runner-fake.js +40 -0
  54. package/dist/core/agents/agent-worker-pipeline.js +36 -2
  55. package/dist/core/agents/codex-exec-worker-adapter.js +2 -0
  56. package/dist/core/agents/native-worker-backend-router.js +58 -1
  57. package/dist/core/codex-app/sks-menubar.js +18 -5
  58. package/dist/core/codex-app.js +1 -0
  59. package/dist/core/codex-control/codex-sdk-config-policy.js +2 -1
  60. package/dist/core/codex-control/codex-sdk-env-policy.js +1 -1
  61. package/dist/core/codex-control/schemas/agent-worker-result.schema.js +12 -3
  62. package/dist/core/codex-hooks/codex-hook-managed-install.js +5 -0
  63. package/dist/core/codex-hooks/codex-hook-state-writer.js +70 -11
  64. package/dist/core/codex-hooks/codex-hook-trust-doctor.js +2 -2
  65. package/dist/core/codex-lb/codex-lb-env.js +20 -1
  66. package/dist/core/commands/basic-cli.js +21 -2
  67. package/dist/core/commands/check-command.js +37 -5
  68. package/dist/core/commands/computer-use-command.js +62 -7
  69. package/dist/core/commands/db-command.js +31 -5
  70. package/dist/core/commands/fast-mode-command.js +3 -0
  71. package/dist/core/commands/gate-result-contract.js +43 -0
  72. package/dist/core/commands/gates-command.js +6 -1
  73. package/dist/core/commands/gc-command.js +29 -2
  74. package/dist/core/commands/goal-command.js +9 -2
  75. package/dist/core/commands/gx-command.js +79 -7
  76. package/dist/core/commands/image-ux-review-command.js +188 -13
  77. package/dist/core/commands/mad-db-command.js +85 -176
  78. package/dist/core/commands/mad-sks-command.js +230 -29
  79. package/dist/core/commands/menubar-command.js +22 -1
  80. package/dist/core/commands/naruto-command.js +64 -10
  81. package/dist/core/commands/plan-command.js +76 -0
  82. package/dist/core/commands/ppt-command.js +159 -24
  83. package/dist/core/commands/qa-loop-command.js +6 -2
  84. package/dist/core/commands/release-command.js +55 -2
  85. package/dist/core/commands/research-command.js +1 -1
  86. package/dist/core/commands/review-command.js +217 -0
  87. package/dist/core/commands/route-success-helpers.js +59 -0
  88. package/dist/core/commands/run-command.js +19 -7
  89. package/dist/core/commands/seo-command.js +49 -1
  90. package/dist/core/commands/ui-command.js +161 -0
  91. package/dist/core/commands/uninstall-command.js +15 -1
  92. package/dist/core/db-safety.js +2 -2
  93. package/dist/core/doctor/doctor-transaction.js +8 -0
  94. package/dist/core/doctor/doctor-zellij-repair.js +1 -0
  95. package/dist/core/doctor/imagegen-repair.js +161 -0
  96. package/dist/core/feature-fixture-runner.js +2 -2
  97. package/dist/core/feature-fixtures.js +58 -37
  98. package/dist/core/feature-registry.js +102 -2
  99. package/dist/core/fsx.js +39 -6
  100. package/dist/core/image-ux-review/imagegen-adapter.js +48 -11
  101. package/dist/core/image-ux-review.js +16 -0
  102. package/dist/core/imagegen/imagegen-capability.js +11 -5
  103. package/dist/core/imagegen/require-imagegen.js +57 -0
  104. package/dist/core/init/skills.js +10 -5
  105. package/dist/core/mad-db/mad-db-coordinator.js +94 -18
  106. package/dist/core/mad-db/mad-db-policy.js +12 -10
  107. package/dist/core/mad-sks/executors/executor-base.js +8 -2
  108. package/dist/core/mad-sks/executors/index.js +4 -0
  109. package/dist/core/mad-sks/executors/sql-plane-executor.js +194 -0
  110. package/dist/core/naruto/naruto-active-pool.js +15 -4
  111. package/dist/core/naruto/naruto-real-worker-child.js +10 -0
  112. package/dist/core/naruto/naruto-role-policy.js +3 -0
  113. package/dist/core/naruto/naruto-task-hints.js +10 -0
  114. package/dist/core/naruto/naruto-work-graph.js +8 -2
  115. package/dist/core/naruto/naruto-work-item.js +6 -0
  116. package/dist/core/naruto/solution-tournament.js +101 -0
  117. package/dist/core/permission-gates.js +30 -0
  118. package/dist/core/pipeline-internals/runtime-core.js +11 -3
  119. package/dist/core/pipeline-internals/runtime-gates.js +27 -0
  120. package/dist/core/ppt-review/index.js +6 -1
  121. package/dist/core/ppt-review/slide-imagegen-review.js +75 -10
  122. package/dist/core/ppt.js +70 -2
  123. package/dist/core/proof/auto-finalize.js +70 -11
  124. package/dist/core/proof/proof-schema.js +2 -0
  125. package/dist/core/proof/route-adapter.js +5 -1
  126. package/dist/core/proof/route-finalizer.js +8 -5
  127. package/dist/core/proof/selftest-proof-fixtures.js +18 -5
  128. package/dist/core/proof/validation.js +2 -0
  129. package/dist/core/provider/model-router.js +53 -0
  130. package/dist/core/release/release-gate-node.js +3 -0
  131. package/dist/core/release/release-gate-resource-governor.js +1 -0
  132. package/dist/core/routes/constants.js +1 -1
  133. package/dist/core/routes.js +65 -18
  134. package/dist/core/safety/mutation-guard.js +5 -1
  135. package/dist/core/stop-gate/gate-evaluator.js +102 -0
  136. package/dist/core/stop-gate/stop-gate-check.js +16 -1
  137. package/dist/core/stop-gate/stop-gate-writer.js +4 -0
  138. package/dist/core/triwiki/agents-md-projector.js +184 -0
  139. package/dist/core/triwiki-wrongness/wrongness-schema.js +3 -1
  140. package/dist/core/trust-kernel/trust-kernel-schema.js +1 -0
  141. package/dist/core/trust-kernel/trust-report.js +1 -1
  142. package/dist/core/trust-kernel/trust-status.js +2 -0
  143. package/dist/core/ui/dashboard-html.js +111 -0
  144. package/dist/core/update/update-migration-state.js +366 -15
  145. package/dist/core/update-check.js +178 -81
  146. package/dist/core/verification/diff-quality.js +100 -0
  147. package/dist/core/verification/impact-scan.js +164 -0
  148. package/dist/core/verification/machine-feedback.js +146 -0
  149. package/dist/core/verification/mistake-rule-compiler.js +195 -0
  150. package/dist/core/version.js +1 -1
  151. package/dist/core/zellij/zellij-self-heal.js +9 -6
  152. package/dist/scripts/check-feature-quality.js +2 -2
  153. package/dist/scripts/cli-output-consistency-check.js +57 -0
  154. package/dist/scripts/coding-bench-check.js +136 -0
  155. package/dist/scripts/doctor-imagegen-repair-check.js +62 -0
  156. package/dist/scripts/ensure-bin-executable.js +11 -3
  157. package/dist/scripts/harness-benchmark-check.js +104 -0
  158. package/dist/scripts/legacy-update-e2e-check.js +228 -0
  159. package/dist/scripts/mad-db-command-check.js +11 -8
  160. package/dist/scripts/mad-db-route-identity-check.js +8 -6
  161. package/dist/scripts/mad-db-safety-conflict-matrix-check.js +31 -8
  162. package/dist/scripts/mad-db-skill-policy-snapshot-check.js +4 -5
  163. package/dist/scripts/ppt-real-imagegen-wiring-check.js +5 -1
  164. package/dist/scripts/proof-root-cause-policy-check.js +2 -0
  165. package/dist/scripts/release-dag-full-coverage-check.js +13 -4
  166. package/dist/scripts/release-gate-dag-runner.js +14 -0
  167. package/dist/scripts/release-gate-existence-audit.js +2 -1
  168. package/dist/scripts/release-gate-planner.js +2 -1
  169. package/dist/scripts/release-gate-script-parity-check.js +1 -1
  170. package/dist/scripts/release-metadata-1-19-check.js +2 -1
  171. package/dist/scripts/seo-geo-feature-fixture-quality-check.js +1 -1
  172. package/dist/scripts/sks-1-11-gate-lib.js +8 -6
  173. package/dist/scripts/ux-review-real-loop-fixture-check.js +9 -4
  174. package/dist/scripts/ux-review-run-wires-imagegen-check.js +4 -0
  175. package/docs/assets/sneakoscope-architecture-pipeline.jpg +0 -0
  176. package/docs/demo.tape +28 -0
  177. package/package.json +67 -3
  178. package/schemas/codex/completion-proof.schema.json +3 -2
  179. package/schemas/release/release-gate-node.schema.json +2 -1
  180. package/dist/scripts/agent-backfill-route-blackbox.js +0 -5
  181. package/dist/scripts/agent-dynamic-pool-route-blackbox.js +0 -5
  182. package/dist/scripts/agent-parallel-write-blackbox.js +0 -56
  183. package/dist/scripts/agent-patch-swarm-route-blackbox.js +0 -10
  184. package/dist/scripts/agent-route-blackbox-lib.js +0 -132
  185. package/dist/scripts/blackbox-command-import-smoke.js +0 -143
  186. package/dist/scripts/blackbox-global-shim.js +0 -77
  187. package/dist/scripts/blackbox-matrix.js +0 -70
  188. package/dist/scripts/blackbox-npx-one-shot.js +0 -69
  189. package/dist/scripts/blackbox-pack-install.js +0 -174
  190. package/dist/scripts/brand-neutrality-zero-leakage-blackbox.js +0 -4
  191. package/dist/scripts/build-once-runner-blackbox.js +0 -34
  192. package/dist/scripts/codex-0140-integration-blackbox.js +0 -13
  193. package/dist/scripts/codex-agent-type-blackbox.js +0 -4
  194. package/dist/scripts/codex-app-harness-blackbox.js +0 -4
  195. package/dist/scripts/codex-app-skill-agent-blackbox.js +0 -4
  196. package/dist/scripts/codex-hook-approval-blackbox.js +0 -4
  197. package/dist/scripts/codex-init-deep-directory-local-blackbox.js +0 -4
  198. package/dist/scripts/codex-native-feature-broker-blackbox.js +0 -4
  199. package/dist/scripts/codex-native-pattern-analysis-blackbox.js +0 -4
  200. package/dist/scripts/codex-native-read-repair-split-blackbox.js +0 -55
  201. package/dist/scripts/codex-native-reference-cache-blackbox.js +0 -41
  202. package/dist/scripts/core-skill-integrity-blackbox.js +0 -32
  203. package/dist/scripts/dfix-fast-blackbox-check.js +0 -37
  204. package/dist/scripts/dfix-parallel-write-blackbox.js +0 -48
  205. package/dist/scripts/dfix-patch-swarm-route-blackbox.js +0 -10
  206. package/dist/scripts/doctor-context7-mcp-repair-blackbox.js +0 -16
  207. package/dist/scripts/doctor-dirty-repair-blackbox.js +0 -22
  208. package/dist/scripts/doctor-dirty-semantic-blackbox.js +0 -8
  209. package/dist/scripts/doctor-fix-production-blackbox.js +0 -26
  210. package/dist/scripts/doctor-native-capability-repair-blackbox.js +0 -39
  211. package/dist/scripts/doctor-startup-config-repair-blackbox.js +0 -13
  212. package/dist/scripts/doctor-supabase-mcp-repair-blackbox.js +0 -14
  213. package/dist/scripts/doctor-transaction-engine-blackbox.js +0 -28
  214. package/dist/scripts/doctor-zellij-fix-blackbox.js +0 -4
  215. package/dist/scripts/doctor-zellij-no-homebrew-blackbox.js +0 -4
  216. package/dist/scripts/doctor-zellij-upgrade-blackbox.js +0 -4
  217. package/dist/scripts/gate-pack-runner-blackbox.js +0 -27
  218. package/dist/scripts/gate-pack-v2-blackbox.js +0 -18
  219. package/dist/scripts/geo-cli-blackbox-check.js +0 -18
  220. package/dist/scripts/loop-collision-blackbox.js +0 -3
  221. package/dist/scripts/loop-concurrency-oversubscription-blackbox.js +0 -3
  222. package/dist/scripts/loop-fixture-production-misuse-blackbox.js +0 -3
  223. package/dist/scripts/loop-kill-interrupt-real-blackbox.js +0 -3
  224. package/dist/scripts/loop-merge-strategy-blackbox.js +0 -3
  225. package/dist/scripts/loop-mesh-production-e2e-blackbox.js +0 -3
  226. package/dist/scripts/loop-side-effect-blackbox.js +0 -3
  227. package/dist/scripts/mad-db-operation-lifecycle-blackbox.js +0 -29
  228. package/dist/scripts/mad-sks-actual-executor-blackbox.js +0 -5
  229. package/dist/scripts/mad-zellij-headless-fallback-blackbox.js +0 -4
  230. package/dist/scripts/mad-zellij-self-heal-blackbox.js +0 -4
  231. package/dist/scripts/naruto-loop-mesh-blackbox.js +0 -3
  232. package/dist/scripts/naruto-real-parallelism-blackbox.js +0 -307
  233. package/dist/scripts/naruto-worktree-coding-blackbox.js +0 -29
  234. package/dist/scripts/parallel-runtime-real-blackbox.js +0 -44
  235. package/dist/scripts/pipeline-codex-native-doctor-mad-routing-real-blackbox.js +0 -77
  236. package/dist/scripts/pipeline-codex-native-e2e-blackbox.js +0 -13
  237. package/dist/scripts/pipeline-codex-native-image-routing-real-blackbox.js +0 -50
  238. package/dist/scripts/pipeline-codex-native-loop-routing-real-blackbox.js +0 -74
  239. package/dist/scripts/pipeline-codex-native-qa-routing-real-blackbox.js +0 -51
  240. package/dist/scripts/pipeline-codex-native-research-routing-real-blackbox.js +0 -45
  241. package/dist/scripts/pipeline-execution-profile-routing-blackbox.js +0 -4
  242. package/dist/scripts/postinstall-global-doctor-blackbox.js +0 -12
  243. package/dist/scripts/ppt-full-e2e-blackbox-check.js +0 -109
  244. package/dist/scripts/ppt-imagegen-blackbox-check.js +0 -46
  245. package/dist/scripts/project-skill-dedupe-blackbox.js +0 -36
  246. package/dist/scripts/qa-backfill-route-blackbox.js +0 -5
  247. package/dist/scripts/qa-patch-swarm-route-blackbox.js +0 -10
  248. package/dist/scripts/release-full-parallelism-blackbox.js +0 -41
  249. package/dist/scripts/release-triwiki-first-runner-blackbox.js +0 -48
  250. package/dist/scripts/release-wiring-3110-blackbox.js +0 -27
  251. package/dist/scripts/release-wiring-3112-blackbox.js +0 -17
  252. package/dist/scripts/release-wiring-3113-blackbox.js +0 -17
  253. package/dist/scripts/research-backfill-route-blackbox.js +0 -5
  254. package/dist/scripts/research-final-reviewer-blackbox.js +0 -70
  255. package/dist/scripts/research-stage-cycle-runtime-blackbox.js +0 -40
  256. package/dist/scripts/research-synthesis-writer-blackbox.js +0 -24
  257. package/dist/scripts/route-blackbox-realism-check.js +0 -21
  258. package/dist/scripts/scheduler-resource-claim-blackbox.js +0 -24
  259. package/dist/scripts/seo-cli-blackbox-check.js +0 -18
  260. package/dist/scripts/sks-3110-all-feature-regression-blackbox.js +0 -116
  261. package/dist/scripts/sks-3112-all-feature-regression-blackbox.js +0 -29
  262. package/dist/scripts/sks-3113-all-feature-regression-blackbox.js +0 -17
  263. package/dist/scripts/sks-400-all-feature-regression-blackbox.js +0 -21
  264. package/dist/scripts/sks-400-extreme-parallel-blackbox.js +0 -8
  265. package/dist/scripts/sks-400-five-minute-blackbox.js +0 -9
  266. package/dist/scripts/sks-400-legacy-purge-blackbox.js +0 -8
  267. package/dist/scripts/sks-401-all-feature-regression-blackbox.js +0 -46
  268. package/dist/scripts/sks-401-five-minute-actual-blackbox.js +0 -23
  269. package/dist/scripts/sks-402-all-feature-regression-blackbox.js +0 -9
  270. package/dist/scripts/sks-402-five-minute-real-blackbox.js +0 -22
  271. package/dist/scripts/sksd-warm-cache-blackbox.js +0 -12
  272. package/dist/scripts/supabase-secret-preservation-blackbox.js +0 -29
  273. package/dist/scripts/team-backfill-route-blackbox.js +0 -5
  274. package/dist/scripts/team-parallel-write-blackbox.js +0 -55
  275. package/dist/scripts/team-patch-swarm-route-blackbox.js +0 -10
  276. package/dist/scripts/triwiki-affected-graph-blackbox.js +0 -28
  277. package/dist/scripts/triwiki-proof-bank-blackbox.js +0 -30
  278. package/dist/scripts/triwiki-proof-bank-lock-blackbox.js +0 -7
  279. package/dist/scripts/update-preserves-supabase-keys-blackbox.js +0 -27
  280. package/dist/scripts/ux-patch-swarm-route-blackbox.js +0 -10
  281. package/dist/scripts/ux-review-imagegen-blackbox-check.js +0 -67
  282. package/dist/scripts/zellij-initial-main-only-blackbox.js +0 -28
  283. package/dist/scripts/zellij-pane-lock-concurrency-blackbox.js +0 -80
  284. package/dist/scripts/zellij-pane-lock-open-worker-integration-blackbox.js +0 -137
  285. package/dist/scripts/zellij-self-heal-typed-blackbox.js +0 -4
  286. package/dist/scripts/zellij-slot-telemetry-real-blackbox.js +0 -20
  287. package/dist/scripts/zellij-stacked-fallback-integration-blackbox.js +0 -81
  288. 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,12 @@
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';
7
10
  export const UPDATE_MIGRATION_SCHEMA = 'sks.project-migration-receipt.v2';
8
11
  export const INSTALLATION_EPOCH_SCHEMA = 'sks.installation-epoch.v1';
9
12
  export function installationEpochPath() {
@@ -85,15 +88,19 @@ export async function writeProjectUpdateMigrationReceipt(input) {
85
88
  const receiptPath = projectUpdateMigrationReceiptPath(input.root);
86
89
  const epoch = await ensureInstallationEpoch(input.source);
87
90
  const retentionCleanup = await runUpdateRetentionCleanup(input.root, input.source);
88
- const requiredBlockers = input.blockers || [];
89
- const optionalWarnings = input.warnings || [];
91
+ const legacyStages = await runLegacyMigrationStages(input.root, { fromVersion: input.fromVersion || null });
92
+ const stageBlockers = legacyStages.flatMap((stage) => stage.blockers.map((blocker) => `${stage.id}:${blocker}`));
93
+ const stageWarnings = legacyStages.flatMap((stage) => stage.warnings.map((warning) => `${stage.id}:${warning}`));
94
+ const requiredBlockers = [...(input.blockers || []), ...stageBlockers];
95
+ const optionalWarnings = [...(input.warnings || []), ...stageWarnings];
90
96
  const receipt = {
91
97
  schema: UPDATE_MIGRATION_SCHEMA,
92
- status: input.status || 'current',
98
+ status: input.status || (requiredBlockers.length ? 'blocked' : 'current'),
93
99
  sks_version: PACKAGE_VERSION,
94
100
  root: input.root,
95
101
  source: input.source,
96
102
  generated_at: nowIso(),
103
+ from_version: input.fromVersion || null,
97
104
  project_root_hash: projectRootHash(input.root),
98
105
  installation_epoch_sha256: installationEpochSha256(epoch),
99
106
  project_semantic_hash: await projectSemanticHash(input.root),
@@ -101,7 +108,8 @@ export async function writeProjectUpdateMigrationReceipt(input) {
101
108
  installation_epoch_path: installationEpochPath(),
102
109
  doctor: input.doctor || null,
103
110
  retention_cleanup: retentionCleanup,
104
- update_stages: input.updateStages || [],
111
+ update_stages: [...(input.updateStages || []), ...legacyStages],
112
+ legacy_migration_stages: legacyStages,
105
113
  required_blockers: requiredBlockers,
106
114
  optional_warnings: optionalWarnings,
107
115
  blockers: requiredBlockers,
@@ -175,6 +183,259 @@ export async function runUpdateRetentionCleanup(root, source = 'update-migration
175
183
  };
176
184
  }
177
185
  }
186
+ const LEGACY_MIGRATION_STAGES = [
187
+ {
188
+ id: 'legacy-team-artifacts',
189
+ min_from_version: '0.0.0',
190
+ run: runLegacyTeamArtifactsStage
191
+ },
192
+ {
193
+ id: 'session-state-split',
194
+ min_from_version: '0.0.0',
195
+ run: runSessionStateSplitStage
196
+ },
197
+ {
198
+ id: 'skills-reconcile',
199
+ min_from_version: '0.0.0',
200
+ run: runSkillsReconcileStage
201
+ },
202
+ {
203
+ id: 'menubar-retarget',
204
+ min_from_version: '0.0.0',
205
+ run: runMenubarRetargetStage
206
+ },
207
+ {
208
+ id: 'config-fastmode-normalize',
209
+ min_from_version: '0.0.0',
210
+ run: runConfigFastModeNormalizeStage
211
+ },
212
+ {
213
+ id: 'hook-trust-refresh',
214
+ min_from_version: '0.0.0',
215
+ run: runHookTrustRefreshStage
216
+ },
217
+ {
218
+ id: 'receipt-rotation',
219
+ min_from_version: '0.0.0',
220
+ run: runReceiptRotationStage
221
+ }
222
+ ];
223
+ async function runLegacyMigrationStages(root, opts = {}) {
224
+ const fromVersion = opts.fromVersion || null;
225
+ const runs = [];
226
+ for (const stage of LEGACY_MIGRATION_STAGES) {
227
+ if (!legacyStageApplies(fromVersion, stage.min_from_version)) {
228
+ runs.push({
229
+ schema: 'sks.legacy-update-migration-stage.v1',
230
+ id: stage.id,
231
+ ok: true,
232
+ status: 'skipped',
233
+ min_from_version: stage.min_from_version,
234
+ from_version: fromVersion,
235
+ actions: ['skipped_by_from_version'],
236
+ blockers: [],
237
+ warnings: []
238
+ });
239
+ continue;
240
+ }
241
+ try {
242
+ const result = await stage.run(root, fromVersion);
243
+ runs.push({
244
+ schema: 'sks.legacy-update-migration-stage.v1',
245
+ id: stage.id,
246
+ min_from_version: stage.min_from_version,
247
+ from_version: fromVersion,
248
+ ...result
249
+ });
250
+ }
251
+ catch (err) {
252
+ runs.push({
253
+ schema: 'sks.legacy-update-migration-stage.v1',
254
+ id: stage.id,
255
+ ok: false,
256
+ status: 'failed',
257
+ min_from_version: stage.min_from_version,
258
+ from_version: fromVersion,
259
+ actions: [],
260
+ blockers: [err?.message || String(err)],
261
+ warnings: []
262
+ });
263
+ }
264
+ }
265
+ return runs;
266
+ }
267
+ async function runLegacyTeamArtifactsStage(root) {
268
+ const candidates = [
269
+ path.join(root, '.sneakoscope', 'team'),
270
+ path.join(root, '.sneakoscope', 'team-dashboard-state.json'),
271
+ path.join(root, '.sneakoscope', 'work-order-ledger.json')
272
+ ];
273
+ const present = [];
274
+ for (const candidate of candidates) {
275
+ if (await exists(candidate))
276
+ present.push(path.relative(root, candidate));
277
+ }
278
+ await writeJsonAtomic(path.join(root, '.sneakoscope', 'update', 'legacy-team-artifacts.json'), {
279
+ schema: 'sks.legacy-team-artifacts-migration.v1',
280
+ generated_at: nowIso(),
281
+ present
282
+ });
283
+ return {
284
+ ok: true,
285
+ status: 'ok',
286
+ actions: present.length ? ['recorded_legacy_team_artifacts'] : ['no_legacy_team_artifacts'],
287
+ blockers: [],
288
+ warnings: [],
289
+ detail: { present }
290
+ };
291
+ }
292
+ async function runSessionStateSplitStage(root) {
293
+ const legacyCurrent = path.join(root, '.sneakoscope', 'current.json');
294
+ const stateCurrent = path.join(root, '.sneakoscope', 'state', 'current.json');
295
+ const sessionsDir = path.join(root, '.sneakoscope', 'state', 'sessions');
296
+ await ensureDir(sessionsDir);
297
+ const actions = [];
298
+ let current = await readJson(stateCurrent, null).catch(() => null);
299
+ const legacy = await readJson(legacyCurrent, null).catch(() => null);
300
+ if (!current && legacy) {
301
+ current = legacy;
302
+ await writeJsonAtomic(stateCurrent, current);
303
+ actions.push('copied_legacy_current_json_to_state_current');
304
+ }
305
+ const missionId = typeof current?.mission_id === 'string' ? current.mission_id : typeof current?.mission === 'string' ? current.mission : null;
306
+ if (missionId) {
307
+ const sessionPath = path.join(sessionsDir, `${safeFileName(missionId)}.json`);
308
+ if (!(await exists(sessionPath))) {
309
+ await writeJsonAtomic(sessionPath, { ...current, migrated_from: path.relative(root, stateCurrent), migrated_at: nowIso() });
310
+ actions.push('wrote_state_session_alias');
311
+ }
312
+ }
313
+ if (!actions.length)
314
+ actions.push('session_state_current');
315
+ return { ok: true, status: 'ok', actions, blockers: [], warnings: [], detail: { legacy_present: Boolean(legacy), mission_id: missionId } };
316
+ }
317
+ async function runSkillsReconcileStage(root) {
318
+ const global = await reconcileSkills({ targetDir: path.join(os.homedir(), '.agents', 'skills'), scope: 'global', fix: true })
319
+ .catch((err) => ({ ok: false, error: err?.message || String(err) }));
320
+ const project = await reconcileSkills({ targetDir: path.join(root, '.agents', 'skills'), scope: 'project', fix: true })
321
+ .catch((err) => ({ ok: false, error: err?.message || String(err) }));
322
+ const blockers = [
323
+ ...(global.ok === false || global.error ? [`global:${global.error || 'failed'}`] : []),
324
+ ...(project.ok === false || project.error ? [`project:${project.error || 'failed'}`] : [])
325
+ ];
326
+ return {
327
+ ok: blockers.length === 0,
328
+ status: blockers.length ? 'failed' : 'ok',
329
+ actions: ['reconciled_global_skills', 'reconciled_project_skills'],
330
+ blockers,
331
+ warnings: [],
332
+ detail: {
333
+ global_installed: Array.isArray(global.installed) ? global.installed.length : null,
334
+ project_removed: Array.isArray(project.removed) ? project.removed.length : null
335
+ }
336
+ };
337
+ }
338
+ async function runMenubarRetargetStage(root) {
339
+ const actionScript = path.join(os.homedir(), '.codex', 'sks-menubar', 'sks-menubar-action.sh');
340
+ const text = await readText(actionScript, null);
341
+ if (typeof text !== 'string')
342
+ return { ok: true, status: 'ok', actions: ['menubar_action_script_absent'], blockers: [], warnings: [] };
343
+ const desired = path.join(packageRoot(), 'dist', 'bin', 'sks.js');
344
+ const line = `SKS_ENTRY=${JSON.stringify(desired)}`;
345
+ const next = /^\s*SKS_ENTRY\s*=.*$/m.test(text)
346
+ ? text.replace(/^\s*SKS_ENTRY\s*=.*$/m, line)
347
+ : `${line}\n${text}`;
348
+ if (next !== text) {
349
+ await writeTextAtomic(actionScript, next);
350
+ return { ok: true, status: 'ok', actions: ['retargeted_menubar_action_script'], blockers: [], warnings: [], detail: { action_script: actionScript } };
351
+ }
352
+ return { ok: true, status: 'ok', actions: ['menubar_action_script_current'], blockers: [], warnings: [], detail: { action_script: actionScript } };
353
+ }
354
+ async function runConfigFastModeNormalizeStage() {
355
+ const configPath = path.join(os.homedir(), '.codex', 'config.toml');
356
+ const text = await readText(configPath, null);
357
+ if (typeof text !== 'string')
358
+ return { ok: true, status: 'ok', actions: ['codex_config_absent'], blockers: [], warnings: [] };
359
+ const table = text.match(/(^|\n)\[user\.fast_mode\][\s\S]*?(?=\n\[|$)/);
360
+ const misplaced = table?.[0]?.match(/^\s*default_profile\s*=\s*"([^"]+)"\s*$/m)?.[1] || null;
361
+ const hasTopLevel = /^\s*default_profile\s*=\s*"[^"]+"\s*$/m.test(text.slice(0, Math.max(0, text.search(/^\s*\[/m))));
362
+ if (!misplaced)
363
+ return { ok: true, status: 'ok', actions: ['fastmode_default_profile_current'], blockers: [], warnings: [] };
364
+ let next = text.replace(/(^|\n)(\[user\.fast_mode\][\s\S]*?)(?=\n\[|$)/, (_match, prefix, block) => {
365
+ const cleaned = String(block).split(/\r?\n/).filter((line) => !/^\s*default_profile\s*=/.test(line)).join('\n').trimEnd();
366
+ return `${prefix}${cleaned}`;
367
+ });
368
+ if (!hasTopLevel)
369
+ next = insertTopLevelTomlKey(next, `default_profile = ${JSON.stringify(misplaced)}`);
370
+ if (next !== text)
371
+ await writeTextAtomic(configPath, `${next.trim()}\n`);
372
+ return { ok: true, status: 'ok', actions: ['normalized_fastmode_default_profile'], blockers: [], warnings: [], detail: { config_path: configPath, default_profile: misplaced } };
373
+ }
374
+ async function runHookTrustRefreshStage(root) {
375
+ const result = await codexHookTrustDoctor(root, { fix: true, managed: true, actual: true });
376
+ const blockers = result.ok === false ? (result.blockers || ['hook_trust_refresh_failed']) : [];
377
+ return {
378
+ ok: blockers.length === 0,
379
+ status: blockers.length ? 'failed' : 'ok',
380
+ actions: ['refreshed_hook_trust'],
381
+ blockers,
382
+ warnings: result.warnings || [],
383
+ detail: { entries: result.current_hash_count ?? null }
384
+ };
385
+ }
386
+ async function runReceiptRotationStage(root) {
387
+ const receiptPath = projectUpdateMigrationReceiptPath(root);
388
+ const dir = path.dirname(receiptPath);
389
+ const base = path.basename(receiptPath);
390
+ const rows = await fsp.readdir(dir, { withFileTypes: true }).catch(() => []);
391
+ const rotated = await Promise.all(rows
392
+ .filter((entry) => entry.isFile() && entry.name.startsWith(`${base}.`) && entry.name.endsWith('.json'))
393
+ .map(async (entry) => {
394
+ const file = path.join(dir, entry.name);
395
+ return { file, stat: await fsp.stat(file).catch(() => null) };
396
+ }));
397
+ const removable = rotated
398
+ .filter((row) => Boolean(row.stat))
399
+ .sort((a, b) => b.stat.mtimeMs - a.stat.mtimeMs)
400
+ .slice(5);
401
+ await Promise.all(removable.map((row) => fsp.rm(row.file, { force: true }).catch(() => undefined)));
402
+ return {
403
+ ok: true,
404
+ status: 'ok',
405
+ actions: removable.length ? ['rotated_old_update_receipts'] : ['receipt_rotation_current'],
406
+ blockers: [],
407
+ warnings: [],
408
+ detail: { removed: removable.length }
409
+ };
410
+ }
411
+ function legacyStageApplies(fromVersion, minFromVersion) {
412
+ if (!fromVersion)
413
+ return true;
414
+ if (compareVersionLike(fromVersion, PACKAGE_VERSION) > 0)
415
+ return false;
416
+ return compareVersionLike(fromVersion, minFromVersion) >= 0;
417
+ }
418
+ function compareVersionLike(a, b) {
419
+ const pa = String(a || '').split(/[.-]/).map((value) => Number.parseInt(value, 10) || 0);
420
+ const pb = String(b || '').split(/[.-]/).map((value) => Number.parseInt(value, 10) || 0);
421
+ for (let index = 0; index < Math.max(pa.length, pb.length, 3); index += 1) {
422
+ if ((pa[index] || 0) > (pb[index] || 0))
423
+ return 1;
424
+ if ((pa[index] || 0) < (pb[index] || 0))
425
+ return -1;
426
+ }
427
+ return 0;
428
+ }
429
+ function safeFileName(value) {
430
+ return String(value || 'session').replace(/[^A-Za-z0-9_.-]+/g, '-').replace(/^-+|-+$/g, '') || 'session';
431
+ }
432
+ function insertTopLevelTomlKey(text, line) {
433
+ const raw = String(text || '').trimEnd();
434
+ const firstTable = raw.search(/^\s*\[/m);
435
+ if (firstTable < 0)
436
+ return `${line}\n${raw}`.trim() + '\n';
437
+ return `${raw.slice(0, firstTable).trimEnd()}\n${line}\n\n${raw.slice(firstTable).trimStart()}`.trim() + '\n';
438
+ }
178
439
  export async function ensureCurrentMigrationBeforeCommand(input) {
179
440
  const env = input.env || process.env;
180
441
  const command = input.command;
@@ -215,7 +476,8 @@ export async function ensureCurrentMigrationBeforeCommand(input) {
215
476
  };
216
477
  return withUpdateMigrationLock(root, empty, async () => {
217
478
  const reportFile = path.join(root, '.sneakoscope', 'update', 'doctor-migration.json');
218
- const doctor = await runPackageLocalDoctor({
479
+ const baseTimeoutMs = migrationDoctorTimeoutMs(env);
480
+ let doctor = await runPackageLocalDoctor({
219
481
  root,
220
482
  args: ['doctor', '--fix', '--yes', '--profile', 'migration', '--machine-only', '--report-file', reportFile],
221
483
  env: {
@@ -223,29 +485,51 @@ export async function ensureCurrentMigrationBeforeCommand(input) {
223
485
  SKS_UPDATE_MIGRATION_GATE_DISABLED: '1',
224
486
  SKS_DISABLE_UPDATE_CHECK: '1'
225
487
  },
226
- timeoutMs: 30_000,
488
+ timeoutMs: baseTimeoutMs,
227
489
  maxOutputBytes: 32 * 1024
228
490
  });
491
+ const timeoutWarnings = [];
492
+ if (!doctor.ok && doctor.timedOut) {
493
+ timeoutWarnings.push(`doctor_migration_timeout_retry:timeout_ms=${baseTimeoutMs}`);
494
+ doctor = await runPackageLocalDoctor({
495
+ root,
496
+ args: ['doctor', '--fix', '--yes', '--profile', 'migration', '--machine-only', '--report-file', reportFile],
497
+ env: {
498
+ ...env,
499
+ SKS_UPDATE_MIGRATION_GATE_DISABLED: '1',
500
+ SKS_DISABLE_UPDATE_CHECK: '1',
501
+ SKS_MIGRATION_DOCTOR_RETRY: '1'
502
+ },
503
+ timeoutMs: baseTimeoutMs * 2,
504
+ maxOutputBytes: 32 * 1024
505
+ });
506
+ }
229
507
  if (!doctor.ok) {
230
- const requiredBlockers = doctor.required_blockers.length ? doctor.required_blockers : ['doctor_migration_profile_failed'];
508
+ const blocker = doctor.timedOut ? 'doctor_migration_timeout' : 'doctor_migration_failed';
509
+ const requiredBlockers = [blocker, ...(doctor.required_blockers.length ? doctor.required_blockers : [])];
510
+ const warnings = [
511
+ ...timeoutWarnings,
512
+ ...doctor.optional_warnings,
513
+ ...(doctor.timedOut ? ['doctor_migration_timeout_may_be_network_or_first_compile_slow_run_sks_doctor_fix_yes_for_live_progress'] : [])
514
+ ];
231
515
  const blocked = await writeProjectUpdateMigrationReceipt({
232
516
  root,
233
517
  source: 'first-command-gate',
234
518
  status: 'blocked',
235
519
  doctor,
236
520
  blockers: requiredBlockers,
237
- warnings: doctor.optional_warnings
521
+ warnings
238
522
  });
239
- return { ...empty, ok: false, status: 'blocked', receipt: blocked, doctor, failed_stage_id: 'doctor:migration-profile', blockers: requiredBlockers, warnings: doctor.optional_warnings };
523
+ return { ...empty, ok: false, status: 'blocked', receipt: blocked, doctor, failed_stage_id: 'doctor:migration-profile', blockers: requiredBlockers, warnings };
240
524
  }
241
525
  const current = await writeProjectUpdateMigrationReceipt({
242
526
  root,
243
527
  source: 'first-command-gate',
244
528
  doctor,
245
529
  blockers: [],
246
- warnings: doctor.optional_warnings
530
+ warnings: [...timeoutWarnings, ...doctor.optional_warnings]
247
531
  });
248
- return { ...empty, ok: true, status: 'repaired', receipt: current, doctor, failed_stage_id: null, blockers: [], warnings: [] };
532
+ return { ...empty, ok: true, status: 'repaired', receipt: current, doctor, failed_stage_id: null, blockers: [], warnings: [...timeoutWarnings, ...doctor.optional_warnings] };
249
533
  }, recheck ? { recheck } : {});
250
534
  }
251
535
  export async function runPostinstallGlobalDoctorAndMarkPending(input = {}) {
@@ -267,7 +551,7 @@ export async function runPostinstallGlobalDoctorAndMarkPending(input = {}) {
267
551
  SKS_DISABLE_UPDATE_CHECK: '1',
268
552
  SKS_POSTINSTALL_NO_BOOTSTRAP: '1'
269
553
  },
270
- timeoutMs: 15_000,
554
+ timeoutMs: migrationDoctorTimeoutMs(env),
271
555
  maxOutputBytes: 32 * 1024
272
556
  });
273
557
  const pending = await writePendingUpdateMigration({
@@ -288,10 +572,18 @@ function commandSkipsMigrationGate(command) {
288
572
  const entry = COMMANDS[command];
289
573
  return entry?.skipMigrationGate === true || entry?.readonly === true;
290
574
  }
575
+ function migrationDoctorTimeoutMs(env) {
576
+ const override = Number.parseInt(env.SKS_MIGRATION_DOCTOR_TIMEOUT_MS || '', 10);
577
+ return Number.isFinite(override) && override > 0 ? override : 180_000;
578
+ }
291
579
  export async function runPackageLocalDoctor(input = {}) {
292
580
  const entrypoint = input.entrypoint || path.join(packageRoot(), 'dist', 'bin', 'sks.js');
293
581
  const cwd = input.root || globalSksRoot();
294
582
  const args = input.args || ['doctor', '--json'];
583
+ const env = input.env || process.env;
584
+ const testRun = testPackageLocalDoctorRun({ entrypoint, cwd, args, env });
585
+ if (testRun)
586
+ return testRun;
295
587
  if (!(await exists(entrypoint))) {
296
588
  return {
297
589
  schema: 'sks.package-local-doctor-run.v1',
@@ -306,6 +598,8 @@ export async function runPackageLocalDoctor(input = {}) {
306
598
  optional_warnings: [],
307
599
  stdout_tail: '',
308
600
  stderr_tail: '',
601
+ timedOut: false,
602
+ timed_out: false,
309
603
  error: `missing package-local sks entrypoint: ${entrypoint}`
310
604
  };
311
605
  }
@@ -313,7 +607,7 @@ export async function runPackageLocalDoctor(input = {}) {
313
607
  cwd,
314
608
  env: {
315
609
  ...process.env,
316
- ...(input.env || {}),
610
+ ...env,
317
611
  SKS_UPDATE_MIGRATION_GATE_DISABLED: '1',
318
612
  SKS_DISABLE_UPDATE_CHECK: '1'
319
613
  },
@@ -322,7 +616,8 @@ export async function runPackageLocalDoctor(input = {}) {
322
616
  }).catch((err) => ({
323
617
  code: 1,
324
618
  stdout: '',
325
- stderr: err?.message || String(err)
619
+ stderr: err?.message || String(err),
620
+ timedOut: false
326
621
  }));
327
622
  const reportFile = reportFileFromArgs(args);
328
623
  const parsed = reportFile
@@ -345,9 +640,65 @@ export async function runPackageLocalDoctor(input = {}) {
345
640
  optional_warnings: optionalWarnings,
346
641
  stdout_tail: tail(result.stdout || ''),
347
642
  stderr_tail: tail(result.stderr || ''),
643
+ timedOut: result.timedOut === true,
644
+ timed_out: result.timedOut === true,
348
645
  error: ok ? null : tail(result.stderr || result.stdout || requiredBlockers.join(', ') || 'doctor failed')
349
646
  };
350
647
  }
648
+ function testPackageLocalDoctorRun(input) {
649
+ if (input.env.SKS_TEST_DOCTOR_TIMEOUT_ONCE === '1' && input.env.SKS_MIGRATION_DOCTOR_RETRY !== '1') {
650
+ return mockPackageLocalDoctorRun(input, {
651
+ ok: false,
652
+ timedOut: true,
653
+ exitCode: 124,
654
+ blockers: ['test_doctor_timeout_once']
655
+ });
656
+ }
657
+ if (input.env.SKS_TEST_DOCTOR_TIMEOUT_ONCE === '1' && input.env.SKS_MIGRATION_DOCTOR_RETRY === '1') {
658
+ return mockPackageLocalDoctorRun(input, {
659
+ ok: true,
660
+ timedOut: false,
661
+ exitCode: 0,
662
+ warnings: ['test_doctor_retry_succeeded']
663
+ });
664
+ }
665
+ if (input.env.SKS_TEST_DOCTOR_FAIL === '1') {
666
+ return mockPackageLocalDoctorRun(input, {
667
+ ok: false,
668
+ timedOut: false,
669
+ exitCode: 1,
670
+ blockers: ['test_doctor_failed']
671
+ });
672
+ }
673
+ if (input.env.SKS_TEST_DOCTOR_OK === '1') {
674
+ return mockPackageLocalDoctorRun(input, {
675
+ ok: true,
676
+ timedOut: false,
677
+ exitCode: 0,
678
+ warnings: ['test_doctor_ok']
679
+ });
680
+ }
681
+ return null;
682
+ }
683
+ function mockPackageLocalDoctorRun(input, result) {
684
+ return {
685
+ schema: 'sks.package-local-doctor-run.v1',
686
+ ok: result.ok,
687
+ status: result.ok ? 'ok' : 'failed',
688
+ entrypoint: input.entrypoint,
689
+ cwd: input.cwd,
690
+ args: input.args,
691
+ exit_code: result.exitCode,
692
+ parsed_ok: result.ok,
693
+ required_blockers: result.ok ? [] : result.blockers || [],
694
+ optional_warnings: result.warnings || [],
695
+ stdout_tail: result.ok ? '{"ok":true}' : '',
696
+ stderr_tail: result.ok ? '' : (result.blockers || ['doctor failed']).join(', '),
697
+ timedOut: result.timedOut,
698
+ timed_out: result.timedOut,
699
+ error: result.ok ? null : (result.blockers || ['doctor failed']).join(', ')
700
+ };
701
+ }
351
702
  export async function resolveInstalledSksEntrypoint(input = {}) {
352
703
  const packageName = input.packageName || 'sneakoscope';
353
704
  const candidates = [