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,146 @@
1
+ import path from 'node:path';
2
+ import { exists, listFilesRecursive, readJson, runProcess } from '../fsx.js';
3
+ import { runCompiledRules } from './mistake-rule-compiler.js';
4
+ export async function runMachineFeedback(root, changedFiles, opts = {}) {
5
+ const t0 = Date.now();
6
+ const timeoutMs = Math.max(5_000, opts.timeoutMs ?? 60_000);
7
+ const [typecheck, lint, tests] = await Promise.all([
8
+ runTypecheck(root, changedFiles, timeoutMs),
9
+ runLint(root, changedFiles, timeoutMs),
10
+ runSelectedTests(root, changedFiles, timeoutMs)
11
+ ]);
12
+ return {
13
+ schema: 'sks.machine-feedback.v1',
14
+ ok: typecheck.ok && lint.ok && tests.ok,
15
+ typecheck,
16
+ lint,
17
+ tests,
18
+ duration_ms: Date.now() - t0
19
+ };
20
+ }
21
+ export async function selectTests(root, changedFiles) {
22
+ const out = new Set();
23
+ for (const file of changedFiles.map(normalizePath).filter(Boolean)) {
24
+ const base = path.basename(file).replace(/\.(?:[cm]?[jt]sx?)$/, '');
25
+ for (const cand of await globTests(root, base))
26
+ out.add(cand);
27
+ for (const cand of await testsImporting(root, file))
28
+ out.add(cand);
29
+ }
30
+ return [...out].sort().slice(0, 20);
31
+ }
32
+ async function runTypecheck(root, changedFiles, timeoutMs) {
33
+ if (!changedFiles.some((file) => /\.(?:tsx?|mts|cts)$/.test(file)))
34
+ return { ok: true, errors: [], skipped_reason: 'no_ts_files_changed' };
35
+ const pkg = await packageJson(root);
36
+ const script = scriptNamed(pkg, 'typecheck');
37
+ const tsconfig = await exists(path.join(root, 'tsconfig.json'));
38
+ if (!script && !tsconfig)
39
+ return { ok: true, errors: [], skipped_reason: 'tool_not_found' };
40
+ const command = script ? ['npm', ['run', 'typecheck', '--silent']] : ['npx', ['tsc', '--noEmit', '-p', 'tsconfig.json']];
41
+ const result = await runProcess(command[0], command[1], { cwd: root, timeoutMs, maxOutputBytes: 512 * 1024 });
42
+ if (result.timedOut)
43
+ return { ok: true, errors: [], skipped_reason: 'timeout' };
44
+ return {
45
+ ok: result.code === 0,
46
+ errors: result.code === 0 ? [] : summarizeErrors(result.stderr || result.stdout)
47
+ };
48
+ }
49
+ async function runLint(root, changedFiles, timeoutMs) {
50
+ const rules = await runCompiledRules(root, changedFiles).catch((err) => ({
51
+ ok: false,
52
+ violations: [{ rule_id: 'rule-runner', severity: 'error', file: '', line: 0, description: err instanceof Error ? err.message : String(err), good_example: '' }],
53
+ rule_count: 0
54
+ }));
55
+ const ruleErrors = rules.violations.map((violation) => `${violation.file}${violation.line ? `:${violation.line}` : ''} ${violation.rule_id}: ${violation.description}${violation.good_example ? ` | good: ${singleLine(violation.good_example)}` : ''}`);
56
+ const pkg = await packageJson(root);
57
+ const lintScript = scriptNamed(pkg, 'lint');
58
+ if (!lintScript) {
59
+ return {
60
+ ok: rules.ok,
61
+ errors: ruleErrors.slice(0, 20),
62
+ ...(rules.rule_count ? {} : { skipped_reason: 'skipped_reason' in rules ? rules.skipped_reason || 'tool_not_found' : 'tool_not_found' })
63
+ };
64
+ }
65
+ const args = ['run', 'lint', '--silent'];
66
+ const result = await runProcess('npm', args, { cwd: root, timeoutMs, maxOutputBytes: 512 * 1024 });
67
+ if (result.timedOut)
68
+ return { ok: rules.ok, errors: ruleErrors.slice(0, 20), skipped_reason: 'timeout' };
69
+ return {
70
+ ok: result.code === 0 && rules.ok,
71
+ errors: [...(result.code === 0 ? [] : summarizeErrors(result.stderr || result.stdout)), ...ruleErrors].slice(0, 20)
72
+ };
73
+ }
74
+ async function runSelectedTests(root, changedFiles, timeoutMs) {
75
+ const selected = await selectTests(root, changedFiles);
76
+ if (!selected.length)
77
+ return { ok: true, selected: [], failed: [], skipped_reason: 'no_related_tests' };
78
+ const failed = [];
79
+ const runnable = selected.filter((file) => /\.(?:mjs|cjs|js)$/.test(file)).slice(0, 10);
80
+ if (!runnable.length) {
81
+ const pkg = await packageJson(root);
82
+ if (!scriptNamed(pkg, 'test'))
83
+ return { ok: true, selected, failed: [], skipped_reason: 'no_directly_runnable_tests' };
84
+ const result = await runProcess('npm', ['test', '--silent'], { cwd: root, timeoutMs, maxOutputBytes: 512 * 1024 });
85
+ if (result.timedOut)
86
+ return { ok: true, selected, failed: [], skipped_reason: 'timeout' };
87
+ return { ok: result.code === 0, selected, failed: result.code === 0 ? [] : summarizeErrors(result.stderr || result.stdout).slice(0, 10) };
88
+ }
89
+ await Promise.all(runnable.map(async (file) => {
90
+ const result = await runProcess(process.execPath, [file], { cwd: root, timeoutMs: Math.max(5_000, Math.floor(timeoutMs / runnable.length)), maxOutputBytes: 256 * 1024 });
91
+ if (result.timedOut)
92
+ return;
93
+ if (result.code !== 0)
94
+ failed.push(`${file}: ${summarizeErrors(result.stderr || result.stdout).join(' | ')}`);
95
+ }));
96
+ return { ok: failed.length === 0, selected, failed: failed.slice(0, 10) };
97
+ }
98
+ async function globTests(root, base) {
99
+ const files = await listFilesRecursive(root, { ignore: ['.git', 'node_modules', 'dist', '.sneakoscope/tmp'], maxFiles: 30_000 });
100
+ return files
101
+ .map((file) => normalizePath(path.relative(root, file)))
102
+ .filter((file) => isTestFile(file))
103
+ .filter((file) => {
104
+ const name = path.basename(file);
105
+ return name.startsWith(`${base}.test.`)
106
+ || name.startsWith(`${base}.spec.`)
107
+ || file.includes(`/__tests__/${base}`);
108
+ });
109
+ }
110
+ async function testsImporting(root, changedFile) {
111
+ const files = await listFilesRecursive(root, { ignore: ['.git', 'node_modules', 'dist', '.sneakoscope/tmp'], maxFiles: 30_000 });
112
+ const stem = normalizePath(changedFile).replace(/\.(?:[cm]?[jt]sx?)$/, '');
113
+ const out = [];
114
+ await Promise.all(files.map(async (abs) => {
115
+ const rel = normalizePath(path.relative(root, abs));
116
+ if (!isTestFile(rel))
117
+ return;
118
+ const text = await import('node:fs/promises').then((fs) => fs.readFile(abs, 'utf8')).catch(() => '');
119
+ if (String(text).includes(changedFile) || String(text).includes(stem) || String(text).includes(`../${stem}`))
120
+ out.push(rel);
121
+ }));
122
+ return out;
123
+ }
124
+ function isTestFile(file) {
125
+ return /(?:^|\/)__tests__\//.test(file) || /\.(?:test|spec)\.(?:[cm]?[jt]sx?)$/.test(file);
126
+ }
127
+ async function packageJson(root) {
128
+ return readJson(path.join(root, 'package.json'), {}).catch(() => ({}));
129
+ }
130
+ function scriptNamed(pkg, name) {
131
+ const script = pkg?.scripts?.[name];
132
+ return typeof script === 'string' && script.trim() ? script : null;
133
+ }
134
+ function summarizeErrors(text) {
135
+ return String(text || '').split(/\r?\n/)
136
+ .map((line) => line.trim())
137
+ .filter(Boolean)
138
+ .slice(0, 20);
139
+ }
140
+ function normalizePath(value) {
141
+ return String(value || '').replace(/\\/g, '/').replace(/^\.\/+/, '');
142
+ }
143
+ function singleLine(value) {
144
+ return String(value || '').replace(/\s+/g, ' ').trim().slice(0, 240);
145
+ }
146
+ //# sourceMappingURL=machine-feedback.js.map
@@ -0,0 +1,195 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { ensureDir, listFilesRecursive, nowIso, readJson, readText, runProcess, which, writeJsonAtomic } from '../fsx.js';
4
+ import { readWrongnessLedger, writeWrongnessLedger } from '../triwiki-wrongness/wrongness-ledger.js';
5
+ export async function compileMistakeRules(root) {
6
+ const ledger = await readWrongnessLedger(root);
7
+ const compiled = [];
8
+ const skipped = [];
9
+ const records = Array.isArray(ledger.records) ? ledger.records : [];
10
+ let changed = false;
11
+ for (const record of records) {
12
+ if (record.rule_compiled === true)
13
+ continue;
14
+ const rule = synthesizeRule(record);
15
+ if (rule && await validateRule(root, rule)) {
16
+ await writeRule(root, rule);
17
+ record.rule_compiled = true;
18
+ record.compiled_rule_id = rule.id;
19
+ record.updated_at = nowIso();
20
+ compiled.push(rule);
21
+ changed = true;
22
+ }
23
+ else {
24
+ skipped.push(String(record.id || 'unknown'));
25
+ }
26
+ }
27
+ if (changed)
28
+ await writeWrongnessLedger(root, { ...ledger, records });
29
+ await writeJsonAtomic(path.join(root, '.sneakoscope', 'rules', 'compiled-index.json'), {
30
+ schema: 'sks.mistake-rule-index.v1',
31
+ generated_at: nowIso(),
32
+ compiled: compiled.map((rule) => rule.id),
33
+ skipped
34
+ });
35
+ return { compiled, skipped };
36
+ }
37
+ export async function runCompiledRules(root, changedFiles) {
38
+ const rules = await readCompiledRules(root);
39
+ if (!rules.length)
40
+ return { ok: true, violations: [], rule_count: 0, skipped_reason: 'no_rules' };
41
+ const files = changedFiles.length ? changedFiles : (await listFilesRecursive(root, { ignore: ['.git', 'node_modules', 'dist'], maxFiles: 2000 })).map((file) => path.relative(root, file));
42
+ const astGrep = await which('ast-grep');
43
+ const violations = [];
44
+ for (const rule of rules) {
45
+ if (rule.detector.kind === 'ast-grep') {
46
+ if (!astGrep)
47
+ continue;
48
+ violations.push(...await runAstGrepRule(root, rule, files));
49
+ }
50
+ else {
51
+ violations.push(...await runRegexRule(root, rule, files));
52
+ }
53
+ }
54
+ return {
55
+ ok: !violations.some((violation) => violation.severity === 'error'),
56
+ violations,
57
+ rule_count: rules.length
58
+ };
59
+ }
60
+ async function readCompiledRules(root) {
61
+ const dir = path.join(root, '.sneakoscope', 'rules');
62
+ const entries = await fs.readdir(dir, { withFileTypes: true }).catch(() => []);
63
+ const rules = [];
64
+ for (const entry of entries) {
65
+ if (!entry.isFile() || !entry.name.endsWith('.rule.json'))
66
+ continue;
67
+ const rule = await readJson(path.join(dir, entry.name), null);
68
+ if (rule?.schema === 'sks.mistake-rule.v1')
69
+ rules.push(rule);
70
+ }
71
+ return rules;
72
+ }
73
+ function synthesizeRule(record) {
74
+ const id = `wl-${String(record?.id || '').replace(/[^A-Za-z0-9_-]+/g, '-').slice(0, 96)}`;
75
+ const text = [
76
+ record?.avoidance_rule?.text,
77
+ record?.claim?.text,
78
+ record?.corrective_action?.summary,
79
+ record?.detected_by?.detail
80
+ ].map((part) => String(part || '')).join('\n');
81
+ const bad = String(record?.examples?.bad || record?.example_bad || record?.bad || '');
82
+ const good = String(record?.examples?.good || record?.example_good || record?.good || '');
83
+ const explicitPattern = String(record?.detector?.pattern || record?.regex || record?.forbidden_pattern || '');
84
+ const nPlusOne = /n\+1|query.*loop|loop.*query|쿼리.*반복/i.test(text);
85
+ const emptyCatch = /empty\s+catch|catch\s*\{\s*\}|빈\s*catch/i.test(text);
86
+ const pattern = explicitPattern
87
+ || (nPlusOne ? String.raw `for\s*\([^)]*\)\s*\{[\s\S]{0,500}\b(?:query|findMany|findUnique|select|fetch)\s*\(` : '')
88
+ || (emptyCatch ? String.raw `catch\s*\([^)]*\)\s*\{\s*\}` : '');
89
+ if (!pattern)
90
+ return null;
91
+ return {
92
+ schema: 'sks.mistake-rule.v1',
93
+ id,
94
+ source: 'wrongness-ledger',
95
+ description: String(record?.avoidance_rule?.text || record?.claim?.text || record?.wrongness_kind || 'Compiled wrongness rule').slice(0, 500),
96
+ detector: { kind: 'regex', pattern, file_glob: String(record?.detector?.file_glob || '**/*.{ts,tsx,js,mjs,cjs}') },
97
+ severity: record?.severity === 'low' ? 'warn' : 'error',
98
+ examples: {
99
+ bad: bad || exampleForPattern(pattern, true),
100
+ good: good || exampleForPattern(pattern, false)
101
+ }
102
+ };
103
+ }
104
+ async function validateRule(root, rule) {
105
+ if (rule.detector.kind === 'ast-grep') {
106
+ if (!(await which('ast-grep')))
107
+ return false;
108
+ const tmp = path.join(root, '.sneakoscope', 'tmp', 'rule-validate', rule.id);
109
+ await ensureDir(tmp);
110
+ const bad = path.join(tmp, `bad.${rule.detector.lang || 'ts'}`);
111
+ const good = path.join(tmp, `good.${rule.detector.lang || 'ts'}`);
112
+ await fs.writeFile(bad, rule.examples.bad, 'utf8');
113
+ await fs.writeFile(good, rule.examples.good, 'utf8');
114
+ const badRun = await runProcess('ast-grep', ['run', '-p', rule.detector.pattern, bad], { cwd: root, timeoutMs: 10_000, maxOutputBytes: 64 * 1024 });
115
+ const goodRun = await runProcess('ast-grep', ['run', '-p', rule.detector.pattern, good], { cwd: root, timeoutMs: 10_000, maxOutputBytes: 64 * 1024 });
116
+ return Boolean(badRun.stdout.trim()) && !goodRun.stdout.trim();
117
+ }
118
+ try {
119
+ const re = new RegExp(rule.detector.pattern, 'm');
120
+ return re.test(rule.examples.bad) && !re.test(rule.examples.good);
121
+ }
122
+ catch {
123
+ return false;
124
+ }
125
+ }
126
+ async function writeRule(root, rule) {
127
+ await writeJsonAtomic(path.join(root, '.sneakoscope', 'rules', `${rule.id}.rule.json`), rule);
128
+ }
129
+ async function runRegexRule(root, rule, files) {
130
+ if (rule.detector.kind !== 'regex')
131
+ return [];
132
+ const detector = rule.detector;
133
+ let re;
134
+ try {
135
+ re = new RegExp(detector.pattern, 'gm');
136
+ }
137
+ catch {
138
+ return [];
139
+ }
140
+ const out = [];
141
+ for (const file of files.filter((candidate) => fileGlobMatches(candidate, detector.file_glob))) {
142
+ const text = await readText(path.join(root, file), '').catch(() => '');
143
+ re.lastIndex = 0;
144
+ for (const match of String(text).matchAll(re)) {
145
+ out.push(ruleViolation(rule, file, lineForIndex(String(text), match.index || 0)));
146
+ if (out.length >= 100)
147
+ return out;
148
+ }
149
+ }
150
+ return out;
151
+ }
152
+ async function runAstGrepRule(root, rule, files) {
153
+ const out = [];
154
+ for (const file of files) {
155
+ const run = await runProcess('ast-grep', ['run', '-p', rule.detector.pattern, path.join(root, file)], {
156
+ cwd: root,
157
+ timeoutMs: 10_000,
158
+ maxOutputBytes: 64 * 1024
159
+ }).catch(() => null);
160
+ if (!run?.stdout.trim())
161
+ continue;
162
+ out.push(ruleViolation(rule, file, 1));
163
+ }
164
+ return out;
165
+ }
166
+ function ruleViolation(rule, file, line) {
167
+ return {
168
+ rule_id: rule.id,
169
+ severity: rule.severity,
170
+ file,
171
+ line,
172
+ description: rule.description,
173
+ good_example: rule.examples.good
174
+ };
175
+ }
176
+ function fileGlobMatches(file, glob) {
177
+ const normalized = file.replace(/\\/g, '/');
178
+ if (glob.includes('{ts,tsx,js,mjs,cjs}'))
179
+ return /\.(?:tsx?|mjs|cjs|js)$/.test(normalized);
180
+ if (glob === '**/*')
181
+ return true;
182
+ const suffix = glob.replace(/^\*\*\/*/, '').replace(/^\*/, '');
183
+ return suffix ? normalized.endsWith(suffix) : true;
184
+ }
185
+ function lineForIndex(text, index) {
186
+ return text.slice(0, index).split(/\r?\n/).length;
187
+ }
188
+ function exampleForPattern(pattern, bad) {
189
+ if (/catch/.test(pattern))
190
+ return bad ? 'try { run(); } catch (err) {}' : 'try { run(); } catch (err) { throw err; }';
191
+ return bad
192
+ ? 'for (const id of ids) { await db.user.findUnique({ where: { id } }); }'
193
+ : 'await db.user.findMany({ where: { id: { in: ids } } });';
194
+ }
195
+ //# sourceMappingURL=mistake-rule-compiler.js.map
@@ -1,2 +1,2 @@
1
- export const PACKAGE_VERSION = '4.8.7';
1
+ export const PACKAGE_VERSION = '5.1.2';
2
2
  //# sourceMappingURL=version.js.map
@@ -1,6 +1,7 @@
1
1
  import fs from 'node:fs/promises';
2
2
  import path from 'node:path';
3
3
  import { ensureDir, nowIso, runProcess, writeJsonAtomic } from '../fsx.js';
4
+ import { withHeartbeat } from '../../cli/cli-theme.js';
4
5
  import { createRequestedScopeContract } from '../safety/requested-scope-contract.js';
5
6
  import { guardContextForRoute, guardedPackageInstall } from '../safety/mutation-guard.js';
6
7
  import { mutationLedgerPath } from '../safety/mutation-ledger.js';
@@ -80,7 +81,7 @@ export async function repairZellijForSks(input) {
80
81
  : manualResult(root, input, env, before, latest, brew, policy.blockers[0] || 'homebrew_missing');
81
82
  }
82
83
  homebrewInstallAttempted = true;
83
- const homebrewRun = await runHomebrewInstall(root, env);
84
+ const homebrewRun = await runHomebrewInstall(root, env, input.quiet === true);
84
85
  if (homebrewRun.code !== 0) {
85
86
  return persistSelfHeal(root, input.missionDir, {
86
87
  schema: 'sks.zellij-self-heal.v1',
@@ -112,7 +113,7 @@ export async function repairZellijForSks(input) {
112
113
  : install ? 'brew-install-zellij'
113
114
  : 'brew-upgrade-zellij';
114
115
  const command = `brew ${brewArgs.join(' ')}`;
115
- const run = await runZellijBrew(root, env, brewBin, brewArgs, command);
116
+ const run = await runZellijBrew(root, env, brewBin, brewArgs, command, input.quiet === true);
116
117
  if (run.code !== 0 && /already installed|already up-to-date/i.test(`${run.stdout}\n${run.stderr}`)) {
117
118
  const after = await capabilitySnapshot(root, env, 'after-noop');
118
119
  return persistSelfHeal(root, input.missionDir, {
@@ -250,7 +251,7 @@ async function findBrew(env) {
250
251
  return { present: true, bin: 'brew' };
251
252
  return { present: false, bin: null };
252
253
  }
253
- async function runZellijBrew(root, env, brewBin, args, command) {
254
+ async function runZellijBrew(root, env, brewBin, args, command, quiet = false) {
254
255
  if (env.SKS_ZELLIJ_SELF_HEAL_FAKE_RUN === '1') {
255
256
  await appendFakeBrewLog(env, args);
256
257
  return { code: Number(env.SKS_ZELLIJ_SELF_HEAL_FAKE_RUN_CODE || 0), stdout: 'fake brew ok', stderr: '' };
@@ -261,7 +262,7 @@ async function runZellijBrew(root, env, brewBin, args, command) {
261
262
  projectRoot: root,
262
263
  overrides: { package_install: true, zellij_install: true }
263
264
  });
264
- return guardedPackageInstall(guardContextForRoute(root, contract, command), 'zellij', {
265
+ const work = guardedPackageInstall(guardContextForRoute(root, contract, command), 'zellij', {
265
266
  confirmed: true,
266
267
  command: brewBin,
267
268
  args,
@@ -269,8 +270,9 @@ async function runZellijBrew(root, env, brewBin, args, command) {
269
270
  timeoutMs: 180000,
270
271
  maxOutputBytes: 256 * 1024
271
272
  }).catch((err) => ({ code: 1, stdout: '', stderr: errorMessage(err) }));
273
+ return quiet ? await work : await withHeartbeat('brew zellij repair', work, { warnAfterMs: 30_000 });
272
274
  }
273
- async function runHomebrewInstall(root, env) {
275
+ async function runHomebrewInstall(root, env, quiet = false) {
274
276
  if (env.SKS_ZELLIJ_SELF_HEAL_FAKE_RUN === '1') {
275
277
  await appendFakeBrewLog(env, ['install-homebrew']);
276
278
  return { code: Number(env.SKS_ZELLIJ_SELF_HEAL_FAKE_HOMEBREW_CODE || 0), stdout: 'fake homebrew install ok', stderr: '' };
@@ -281,7 +283,7 @@ async function runHomebrewInstall(root, env) {
281
283
  projectRoot: root,
282
284
  overrides: { package_install: true, zellij_install: true }
283
285
  });
284
- return guardedPackageInstall(guardContextForRoute(root, contract, HOMEBREW_INSTALL_COMMAND), 'homebrew', {
286
+ const work = guardedPackageInstall(guardContextForRoute(root, contract, HOMEBREW_INSTALL_COMMAND), 'homebrew', {
285
287
  confirmed: true,
286
288
  command: '/bin/bash',
287
289
  args: ['-c', 'curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh | /bin/bash'],
@@ -289,6 +291,7 @@ async function runHomebrewInstall(root, env) {
289
291
  timeoutMs: 600000,
290
292
  maxOutputBytes: 256 * 1024
291
293
  }).catch((err) => ({ code: 1, stdout: '', stderr: errorMessage(err) }));
294
+ return quiet ? await work : await withHeartbeat('Homebrew install', work, { warnAfterMs: 60_000 });
292
295
  }
293
296
  async function appendFakeBrewLog(env, args) {
294
297
  if (!env.SKS_FAKE_BREW_LOG)
@@ -40,8 +40,8 @@ function releaseQualityBlockers(counts = {}) {
40
40
  const blockers = [];
41
41
  if (Number(counts.runtime_verified || 0) < 22)
42
42
  blockers.push(`runtime_verified_below_release_target:${counts.runtime_verified || 0}<22`);
43
- if (Number(counts.runtime_mock_verified || 0) < 45)
44
- blockers.push(`runtime_mock_verified_below_release_target:${counts.runtime_mock_verified || 0}<45`);
43
+ if (Number(counts.wiring_only || 0) > 30)
44
+ blockers.push(`wiring_only_above_release_target:${counts.wiring_only || 0}>30`);
45
45
  if (Number(counts.integration_optional || 0) > 6)
46
46
  blockers.push(`integration_optional_above_release_target:${counts.integration_optional || 0}>6`);
47
47
  if (Number(counts.static_contract || 0) > 45)
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env node
2
+ import fs from 'node:fs';
3
+ import { spawnSync } from 'node:child_process';
4
+ import { assertGate, emitGate, root } from './sks-1-18-gate-lib.js';
5
+ import { PACKAGE_VERSION } from '../core/version.js';
6
+ const executed = [
7
+ ['ui', '--once'],
8
+ ['review'],
9
+ ['fast-mode', 'status'],
10
+ ['menubar', 'status'],
11
+ ['uninstall', '--dry-run', '--yes'],
12
+ ['naruto', 'help']
13
+ ];
14
+ const staticFiles = [
15
+ 'src/commands/doctor.ts',
16
+ 'src/core/commands/basic-cli.ts',
17
+ 'src/core/commands/naruto-command.ts',
18
+ 'src/core/commands/review-command.ts',
19
+ 'src/core/commands/ui-command.ts',
20
+ 'src/core/commands/fast-mode-command.ts',
21
+ 'src/core/commands/menubar-command.ts',
22
+ 'src/core/commands/uninstall-command.ts'
23
+ ];
24
+ const runs = executed.map((args) => run(args));
25
+ const staticMissing = staticFiles.filter((file) => !fs.readFileSync(file, 'utf8').includes('.banner('));
26
+ const badFirstLines = runs.filter((row) => !row.first_line.startsWith(`SKS ${PACKAGE_VERSION} ·`));
27
+ const missingVocabulary = runs.filter((row) => !/[✔▲✖]/.test(row.stdout));
28
+ const report = {
29
+ schema: 'sks.cli-output-consistency.v1',
30
+ ok: badFirstLines.length === 0 && missingVocabulary.length === 0 && staticMissing.length === 0,
31
+ executed: runs,
32
+ static_files_checked: staticFiles,
33
+ bad_first_lines: badFirstLines,
34
+ missing_status_vocabulary: missingVocabulary,
35
+ static_missing_banner: staticMissing
36
+ };
37
+ assertGate(report.ok, 'CLI output consistency check failed', report);
38
+ emitGate('cli:output-consistency', report);
39
+ function run(args) {
40
+ const result = spawnSync(process.execPath, ['./dist/bin/sks.js', ...args], {
41
+ cwd: root,
42
+ encoding: 'utf8',
43
+ env: { ...process.env, NO_COLOR: '1', SKS_UNINSTALL_SKIP_TMP_SWEEP: '1' },
44
+ timeout: 120000,
45
+ maxBuffer: 1024 * 1024
46
+ });
47
+ const stdout = String(result.stdout || '');
48
+ const stderr = String(result.stderr || '');
49
+ return {
50
+ args,
51
+ code: result.status,
52
+ first_line: stdout.split(/\r?\n/).find(Boolean) || '',
53
+ stdout: stdout.slice(0, 4000),
54
+ stderr: stderr.slice(0, 1000)
55
+ };
56
+ }
57
+ //# sourceMappingURL=cli-output-consistency-check.js.map
@@ -0,0 +1,136 @@
1
+ #!/usr/bin/env node
2
+ import fs from 'node:fs/promises';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+ import { ensureDir, nowIso, readJson, runProcess, writeJsonAtomic } from '../core/fsx.js';
7
+ const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..');
8
+ const tasksRoot = path.join(root, 'bench', 'tasks');
9
+ const reportPath = path.join(root, '.sneakoscope', 'reports', 'bench-report.json');
10
+ const baselinePath = path.join(root, 'config', 'bench-baseline.json');
11
+ const baseline = await readJson(baselinePath, { schema: 'sks.coding-bench-baseline.v1', pass_rate: 0, min_pass_rate: 0 });
12
+ const tasks = await loadTasks();
13
+ const backend = backendAvailable();
14
+ const perTask = [];
15
+ for (const task of tasks) {
16
+ if (!backend.ok) {
17
+ perTask.push({
18
+ id: task.id,
19
+ kind: task.kind,
20
+ ok: true,
21
+ status: 'skipped',
22
+ skipped_reason: backend.reason
23
+ });
24
+ continue;
25
+ }
26
+ perTask.push(await runTask(task));
27
+ }
28
+ const executed = perTask.filter((task) => task.status !== 'skipped');
29
+ const pass = executed.filter((task) => task.ok).length;
30
+ const fail = executed.filter((task) => !task.ok).length;
31
+ const skipped = perTask.length - executed.length;
32
+ const passRate = executed.length ? pass / executed.length : 0;
33
+ const baselineRate = Number(baseline.pass_rate ?? baseline.min_pass_rate ?? 0);
34
+ const ok = fail === 0 && passRate >= baselineRate;
35
+ const report = {
36
+ schema: 'sks.coding-bench-report.v1',
37
+ generated_at: nowIso(),
38
+ ok,
39
+ pass,
40
+ fail,
41
+ skipped,
42
+ total: perTask.length,
43
+ pass_rate: passRate,
44
+ baseline: {
45
+ path: path.relative(root, baselinePath),
46
+ pass_rate: baselineRate,
47
+ baseline_version: baseline.baseline_version || null
48
+ },
49
+ backend,
50
+ per_task: perTask
51
+ };
52
+ await ensureDir(path.dirname(reportPath));
53
+ await writeJsonAtomic(reportPath, report);
54
+ console.log(JSON.stringify(report, null, 2));
55
+ if (!ok)
56
+ process.exit(1);
57
+ async function loadTasks() {
58
+ const dirs = (await fs.readdir(tasksRoot, { withFileTypes: true }).catch(() => []))
59
+ .filter((entry) => entry.isDirectory())
60
+ .map((entry) => entry.name)
61
+ .sort();
62
+ const rows = [];
63
+ for (const dir of dirs) {
64
+ const taskPath = path.join(tasksRoot, dir, 'task.json');
65
+ const task = await readJson(taskPath, null);
66
+ if (task?.schema === 'sks.bench-task.v1')
67
+ rows.push({ ...task, dir, task_path: taskPath, repo_path: path.join(tasksRoot, dir, 'repo') });
68
+ }
69
+ return rows;
70
+ }
71
+ function backendAvailable() {
72
+ if (process.env.SKS_RUN_CODING_BENCH_REAL !== '1')
73
+ return { ok: false, reason: 'no_backend' };
74
+ if (process.env.OPENAI_API_KEY || process.env.SKS_CODEX_LB_READY === '1' || process.env.SKS_CODING_BENCH_ALLOW_NO_KEY === '1')
75
+ return { ok: true, reason: null };
76
+ return { ok: false, reason: 'no_backend' };
77
+ }
78
+ async function runTask(task) {
79
+ const scratch = await fs.mkdtemp(path.join(os.tmpdir(), `sks-coding-bench-${task.id}-`));
80
+ await fs.cp(task.repo_path, scratch, { recursive: true });
81
+ await initGit(scratch);
82
+ const sks = path.join(root, 'dist', 'bin', 'sks.js');
83
+ const run = await runProcess(process.execPath, [sks, 'naruto', 'run', task.prompt, '--json', '--no-open-zellij', '--apply-patches', '--tournament', task.kind === 'refactor' ? '3' : '0'], {
84
+ cwd: scratch,
85
+ timeoutMs: 10 * 60_000,
86
+ maxOutputBytes: 512 * 1024,
87
+ env: { ...process.env, SKS_CODING_BENCH_TASK_ID: task.id }
88
+ });
89
+ const grade = await gradeTask(task, scratch);
90
+ return {
91
+ id: task.id,
92
+ kind: task.kind,
93
+ ok: run.code === 0 && grade.ok,
94
+ status: run.code === 0 && grade.ok ? 'passed' : 'failed',
95
+ command_exit: run.code,
96
+ grade,
97
+ scratch
98
+ };
99
+ }
100
+ async function initGit(cwd) {
101
+ await runProcess('git', ['init'], { cwd, timeoutMs: 15_000, maxOutputBytes: 64 * 1024 }).catch(() => null);
102
+ await runProcess('git', ['config', 'user.email', 'bench@sneakoscope.local'], { cwd, timeoutMs: 15_000, maxOutputBytes: 64 * 1024 }).catch(() => null);
103
+ await runProcess('git', ['config', 'user.name', 'Sneakoscope Bench'], { cwd, timeoutMs: 15_000, maxOutputBytes: 64 * 1024 }).catch(() => null);
104
+ await runProcess('git', ['add', '.'], { cwd, timeoutMs: 15_000, maxOutputBytes: 64 * 1024 }).catch(() => null);
105
+ await runProcess('git', ['commit', '-m', 'bench baseline'], { cwd, timeoutMs: 15_000, maxOutputBytes: 64 * 1024 }).catch(() => null);
106
+ }
107
+ async function gradeTask(task, cwd) {
108
+ const success = task.success || {};
109
+ const test = success.command ? await runProcess('sh', ['-lc', String(success.command)], { cwd, timeoutMs: 120_000, maxOutputBytes: 256 * 1024 }) : { code: 0, stdout: '', stderr: '' };
110
+ const diff = await runProcess('git', ['diff', '--numstat', 'HEAD'], { cwd, timeoutMs: 15_000, maxOutputBytes: 128 * 1024 }).catch(() => ({ stdout: '' }));
111
+ const changed = await runProcess('git', ['diff', '--name-only', 'HEAD'], { cwd, timeoutMs: 15_000, maxOutputBytes: 128 * 1024 }).catch(() => ({ stdout: '' }));
112
+ const patch = await runProcess('git', ['diff', 'HEAD'], { cwd, timeoutMs: 15_000, maxOutputBytes: 512 * 1024 }).catch(() => ({ stdout: '' }));
113
+ const diffLines = String(diff.stdout || '').split(/\r?\n/).filter(Boolean).reduce((sum, line) => {
114
+ const parts = line.split(/\s+/).map((value) => Number(value));
115
+ const added = parts[0] ?? 0;
116
+ const removed = parts[1] ?? 0;
117
+ return sum + (Number.isFinite(added) ? added : 0) + (Number.isFinite(removed) ? removed : 0);
118
+ }, 0);
119
+ const changedFiles = String(changed.stdout || '').split(/\r?\n/).filter(Boolean);
120
+ const forbidden = (success.forbidden_patterns || []).filter((pattern) => new RegExp(pattern, 'm').test(String(patch.stdout || '')));
121
+ const missingNewTest = success.must_contain_new_test === true && !changedFiles.some((file) => /\.(test|spec)\.[cm]?[jt]s$|test\.[cm]?js$|__tests__\//.test(file));
122
+ const tooLarge = Number(success.max_diff_lines || Infinity) < diffLines;
123
+ const ok = (success.must_pass !== true || test.code === 0) && !missingNewTest && !tooLarge && forbidden.length === 0;
124
+ return {
125
+ ok,
126
+ test_exit: test.code,
127
+ diff_lines: diffLines,
128
+ changed_files: changedFiles,
129
+ forbidden_patterns: forbidden,
130
+ missing_new_test: missingNewTest,
131
+ max_diff_lines_exceeded: tooLarge,
132
+ stdout_tail: String(test.stdout || '').slice(-4000),
133
+ stderr_tail: String(test.stderr || '').slice(-4000)
134
+ };
135
+ }
136
+ //# sourceMappingURL=coding-bench-check.js.map