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.
- package/README.md +51 -726
- package/bench/tasks/t01-off-by-one/repo/package.json +9 -0
- package/bench/tasks/t01-off-by-one/repo/src/pagination.js +5 -0
- package/bench/tasks/t01-off-by-one/repo/test.js +5 -0
- package/bench/tasks/t01-off-by-one/task.json +13 -0
- package/bench/tasks/t02-signature-cochange/repo/package.json +9 -0
- package/bench/tasks/t02-signature-cochange/repo/src/admin.js +5 -0
- package/bench/tasks/t02-signature-cochange/repo/src/audit.js +5 -0
- package/bench/tasks/t02-signature-cochange/repo/src/labels.js +3 -0
- package/bench/tasks/t02-signature-cochange/repo/src/profile.js +5 -0
- package/bench/tasks/t02-signature-cochange/repo/test.js +9 -0
- package/bench/tasks/t02-signature-cochange/task.json +12 -0
- package/bench/tasks/t03-type-puzzle/repo/package.json +9 -0
- package/bench/tasks/t03-type-puzzle/repo/src/config.js +4 -0
- package/bench/tasks/t03-type-puzzle/repo/test.js +6 -0
- package/bench/tasks/t03-type-puzzle/task.json +13 -0
- package/bench/tasks/t04-refactor-preserve/repo/package.json +9 -0
- package/bench/tasks/t04-refactor-preserve/repo/src/cart.js +8 -0
- package/bench/tasks/t04-refactor-preserve/repo/test.js +4 -0
- package/bench/tasks/t04-refactor-preserve/task.json +12 -0
- package/bench/tasks/t05-performance/repo/package.json +9 -0
- package/bench/tasks/t05-performance/repo/src/pairs.js +9 -0
- package/bench/tasks/t05-performance/repo/test.js +11 -0
- package/bench/tasks/t05-performance/task.json +12 -0
- package/bench/tasks/t06-mistake-rule/repo/package.json +9 -0
- package/bench/tasks/t06-mistake-rule/repo/src/loader.js +6 -0
- package/bench/tasks/t06-mistake-rule/repo/test.js +7 -0
- package/bench/tasks/t06-mistake-rule/task.json +12 -0
- package/config/bench-baseline.json +7 -0
- package/crates/sks-core/Cargo.lock +1 -1
- package/crates/sks-core/Cargo.toml +1 -1
- package/crates/sks-core/src/main.rs +1 -1
- package/dist/bin/install.js +35 -0
- package/dist/bin/sks.js +1 -1
- package/dist/cli/cli-theme.js +51 -0
- package/dist/cli/command-registry.js +15 -4
- package/dist/cli/help-fast.js +18 -8
- package/dist/cli/insane-search-command.js +36 -8
- package/dist/cli/install-helpers.js +32 -0
- package/dist/commands/codex-lb.js +2 -0
- package/dist/commands/doctor.js +62 -6
- package/dist/commands/proof.js +6 -3
- package/dist/config/skills-manifest.json +82 -54
- package/dist/core/agents/agent-conflict-graph.js +5 -0
- package/dist/core/agents/agent-lease.js +5 -0
- package/dist/core/agents/agent-ledger-schemas.js +1 -0
- package/dist/core/agents/agent-orchestrator.js +268 -62
- package/dist/core/agents/agent-output-validator.js +14 -3
- package/dist/core/agents/agent-patch-queue-store.js +95 -2
- package/dist/core/agents/agent-patch-queue.js +6 -3
- package/dist/core/agents/agent-patch-schema.js +13 -0
- package/dist/core/agents/agent-proof-evidence.js +6 -1
- package/dist/core/agents/agent-runner-fake.js +40 -0
- package/dist/core/agents/agent-worker-pipeline.js +36 -2
- package/dist/core/agents/codex-exec-worker-adapter.js +2 -0
- package/dist/core/agents/native-worker-backend-router.js +58 -1
- package/dist/core/codex-app/sks-menubar.js +18 -5
- package/dist/core/codex-app.js +1 -0
- package/dist/core/codex-control/codex-sdk-config-policy.js +2 -1
- package/dist/core/codex-control/codex-sdk-env-policy.js +1 -1
- package/dist/core/codex-control/schemas/agent-worker-result.schema.js +12 -3
- package/dist/core/codex-hooks/codex-hook-managed-install.js +5 -0
- package/dist/core/codex-hooks/codex-hook-state-writer.js +70 -11
- package/dist/core/codex-hooks/codex-hook-trust-doctor.js +2 -2
- package/dist/core/codex-lb/codex-lb-env.js +20 -1
- package/dist/core/commands/basic-cli.js +21 -2
- package/dist/core/commands/check-command.js +37 -5
- package/dist/core/commands/computer-use-command.js +62 -7
- package/dist/core/commands/db-command.js +31 -5
- package/dist/core/commands/fast-mode-command.js +3 -0
- package/dist/core/commands/gate-result-contract.js +43 -0
- package/dist/core/commands/gates-command.js +6 -1
- package/dist/core/commands/gc-command.js +29 -2
- package/dist/core/commands/goal-command.js +9 -2
- package/dist/core/commands/gx-command.js +79 -7
- package/dist/core/commands/image-ux-review-command.js +188 -13
- package/dist/core/commands/mad-db-command.js +85 -176
- package/dist/core/commands/mad-sks-command.js +230 -29
- package/dist/core/commands/menubar-command.js +22 -1
- package/dist/core/commands/naruto-command.js +64 -10
- package/dist/core/commands/plan-command.js +76 -0
- package/dist/core/commands/ppt-command.js +159 -24
- package/dist/core/commands/qa-loop-command.js +6 -2
- package/dist/core/commands/release-command.js +55 -2
- package/dist/core/commands/research-command.js +1 -1
- package/dist/core/commands/review-command.js +217 -0
- package/dist/core/commands/route-success-helpers.js +59 -0
- package/dist/core/commands/run-command.js +19 -7
- package/dist/core/commands/seo-command.js +49 -1
- package/dist/core/commands/ui-command.js +161 -0
- package/dist/core/commands/uninstall-command.js +15 -1
- package/dist/core/db-safety.js +2 -2
- package/dist/core/doctor/doctor-transaction.js +8 -0
- package/dist/core/doctor/doctor-zellij-repair.js +1 -0
- package/dist/core/doctor/imagegen-repair.js +161 -0
- package/dist/core/feature-fixture-runner.js +2 -2
- package/dist/core/feature-fixtures.js +58 -37
- package/dist/core/feature-registry.js +102 -2
- package/dist/core/fsx.js +39 -6
- package/dist/core/image-ux-review/imagegen-adapter.js +48 -11
- package/dist/core/image-ux-review.js +16 -0
- package/dist/core/imagegen/imagegen-capability.js +11 -5
- package/dist/core/imagegen/require-imagegen.js +57 -0
- package/dist/core/init/skills.js +10 -5
- package/dist/core/mad-db/mad-db-coordinator.js +94 -18
- package/dist/core/mad-db/mad-db-policy.js +12 -10
- package/dist/core/mad-sks/executors/executor-base.js +8 -2
- package/dist/core/mad-sks/executors/index.js +4 -0
- package/dist/core/mad-sks/executors/sql-plane-executor.js +194 -0
- package/dist/core/naruto/naruto-active-pool.js +15 -4
- package/dist/core/naruto/naruto-real-worker-child.js +10 -0
- package/dist/core/naruto/naruto-role-policy.js +3 -0
- package/dist/core/naruto/naruto-task-hints.js +10 -0
- package/dist/core/naruto/naruto-work-graph.js +8 -2
- package/dist/core/naruto/naruto-work-item.js +6 -0
- package/dist/core/naruto/solution-tournament.js +101 -0
- package/dist/core/permission-gates.js +30 -0
- package/dist/core/pipeline-internals/runtime-core.js +11 -3
- package/dist/core/pipeline-internals/runtime-gates.js +27 -0
- package/dist/core/ppt-review/index.js +6 -1
- package/dist/core/ppt-review/slide-imagegen-review.js +75 -10
- package/dist/core/ppt.js +70 -2
- package/dist/core/proof/auto-finalize.js +70 -11
- package/dist/core/proof/proof-schema.js +2 -0
- package/dist/core/proof/route-adapter.js +5 -1
- package/dist/core/proof/route-finalizer.js +8 -5
- package/dist/core/proof/selftest-proof-fixtures.js +18 -5
- package/dist/core/proof/validation.js +2 -0
- package/dist/core/provider/model-router.js +53 -0
- package/dist/core/release/release-gate-node.js +3 -0
- package/dist/core/release/release-gate-resource-governor.js +1 -0
- package/dist/core/routes/constants.js +1 -1
- package/dist/core/routes.js +65 -18
- package/dist/core/safety/mutation-guard.js +5 -1
- package/dist/core/stop-gate/gate-evaluator.js +102 -0
- package/dist/core/stop-gate/stop-gate-check.js +16 -1
- package/dist/core/stop-gate/stop-gate-writer.js +4 -0
- package/dist/core/triwiki/agents-md-projector.js +184 -0
- package/dist/core/triwiki-wrongness/wrongness-schema.js +3 -1
- package/dist/core/trust-kernel/trust-kernel-schema.js +1 -0
- package/dist/core/trust-kernel/trust-report.js +1 -1
- package/dist/core/trust-kernel/trust-status.js +2 -0
- package/dist/core/ui/dashboard-html.js +111 -0
- package/dist/core/update/update-migration-state.js +366 -15
- package/dist/core/update-check.js +178 -81
- package/dist/core/verification/diff-quality.js +100 -0
- package/dist/core/verification/impact-scan.js +164 -0
- package/dist/core/verification/machine-feedback.js +146 -0
- package/dist/core/verification/mistake-rule-compiler.js +195 -0
- package/dist/core/version.js +1 -1
- package/dist/core/zellij/zellij-self-heal.js +9 -6
- package/dist/scripts/check-feature-quality.js +2 -2
- package/dist/scripts/cli-output-consistency-check.js +57 -0
- package/dist/scripts/coding-bench-check.js +136 -0
- package/dist/scripts/doctor-imagegen-repair-check.js +62 -0
- package/dist/scripts/ensure-bin-executable.js +11 -3
- package/dist/scripts/harness-benchmark-check.js +104 -0
- package/dist/scripts/legacy-update-e2e-check.js +228 -0
- package/dist/scripts/mad-db-command-check.js +11 -8
- package/dist/scripts/mad-db-route-identity-check.js +8 -6
- package/dist/scripts/mad-db-safety-conflict-matrix-check.js +31 -8
- package/dist/scripts/mad-db-skill-policy-snapshot-check.js +4 -5
- package/dist/scripts/ppt-real-imagegen-wiring-check.js +5 -1
- package/dist/scripts/proof-root-cause-policy-check.js +2 -0
- package/dist/scripts/release-dag-full-coverage-check.js +13 -4
- package/dist/scripts/release-gate-dag-runner.js +14 -0
- package/dist/scripts/release-gate-existence-audit.js +2 -1
- package/dist/scripts/release-gate-planner.js +2 -1
- package/dist/scripts/release-gate-script-parity-check.js +1 -1
- package/dist/scripts/release-metadata-1-19-check.js +2 -1
- package/dist/scripts/seo-geo-feature-fixture-quality-check.js +1 -1
- package/dist/scripts/sks-1-11-gate-lib.js +8 -6
- package/dist/scripts/ux-review-real-loop-fixture-check.js +9 -4
- package/dist/scripts/ux-review-run-wires-imagegen-check.js +4 -0
- package/docs/assets/sneakoscope-architecture-pipeline.jpg +0 -0
- package/docs/demo.tape +28 -0
- package/package.json +67 -3
- package/schemas/codex/completion-proof.schema.json +3 -2
- package/schemas/release/release-gate-node.schema.json +2 -1
- package/dist/scripts/agent-backfill-route-blackbox.js +0 -5
- package/dist/scripts/agent-dynamic-pool-route-blackbox.js +0 -5
- package/dist/scripts/agent-parallel-write-blackbox.js +0 -56
- package/dist/scripts/agent-patch-swarm-route-blackbox.js +0 -10
- package/dist/scripts/agent-route-blackbox-lib.js +0 -132
- package/dist/scripts/blackbox-command-import-smoke.js +0 -143
- package/dist/scripts/blackbox-global-shim.js +0 -77
- package/dist/scripts/blackbox-matrix.js +0 -70
- package/dist/scripts/blackbox-npx-one-shot.js +0 -69
- package/dist/scripts/blackbox-pack-install.js +0 -174
- package/dist/scripts/brand-neutrality-zero-leakage-blackbox.js +0 -4
- package/dist/scripts/build-once-runner-blackbox.js +0 -34
- package/dist/scripts/codex-0140-integration-blackbox.js +0 -13
- package/dist/scripts/codex-agent-type-blackbox.js +0 -4
- package/dist/scripts/codex-app-harness-blackbox.js +0 -4
- package/dist/scripts/codex-app-skill-agent-blackbox.js +0 -4
- package/dist/scripts/codex-hook-approval-blackbox.js +0 -4
- package/dist/scripts/codex-init-deep-directory-local-blackbox.js +0 -4
- package/dist/scripts/codex-native-feature-broker-blackbox.js +0 -4
- package/dist/scripts/codex-native-pattern-analysis-blackbox.js +0 -4
- package/dist/scripts/codex-native-read-repair-split-blackbox.js +0 -55
- package/dist/scripts/codex-native-reference-cache-blackbox.js +0 -41
- package/dist/scripts/core-skill-integrity-blackbox.js +0 -32
- package/dist/scripts/dfix-fast-blackbox-check.js +0 -37
- package/dist/scripts/dfix-parallel-write-blackbox.js +0 -48
- package/dist/scripts/dfix-patch-swarm-route-blackbox.js +0 -10
- package/dist/scripts/doctor-context7-mcp-repair-blackbox.js +0 -16
- package/dist/scripts/doctor-dirty-repair-blackbox.js +0 -22
- package/dist/scripts/doctor-dirty-semantic-blackbox.js +0 -8
- package/dist/scripts/doctor-fix-production-blackbox.js +0 -26
- package/dist/scripts/doctor-native-capability-repair-blackbox.js +0 -39
- package/dist/scripts/doctor-startup-config-repair-blackbox.js +0 -13
- package/dist/scripts/doctor-supabase-mcp-repair-blackbox.js +0 -14
- package/dist/scripts/doctor-transaction-engine-blackbox.js +0 -28
- package/dist/scripts/doctor-zellij-fix-blackbox.js +0 -4
- package/dist/scripts/doctor-zellij-no-homebrew-blackbox.js +0 -4
- package/dist/scripts/doctor-zellij-upgrade-blackbox.js +0 -4
- package/dist/scripts/gate-pack-runner-blackbox.js +0 -27
- package/dist/scripts/gate-pack-v2-blackbox.js +0 -18
- package/dist/scripts/geo-cli-blackbox-check.js +0 -18
- package/dist/scripts/loop-collision-blackbox.js +0 -3
- package/dist/scripts/loop-concurrency-oversubscription-blackbox.js +0 -3
- package/dist/scripts/loop-fixture-production-misuse-blackbox.js +0 -3
- package/dist/scripts/loop-kill-interrupt-real-blackbox.js +0 -3
- package/dist/scripts/loop-merge-strategy-blackbox.js +0 -3
- package/dist/scripts/loop-mesh-production-e2e-blackbox.js +0 -3
- package/dist/scripts/loop-side-effect-blackbox.js +0 -3
- package/dist/scripts/mad-db-operation-lifecycle-blackbox.js +0 -29
- package/dist/scripts/mad-sks-actual-executor-blackbox.js +0 -5
- package/dist/scripts/mad-zellij-headless-fallback-blackbox.js +0 -4
- package/dist/scripts/mad-zellij-self-heal-blackbox.js +0 -4
- package/dist/scripts/naruto-loop-mesh-blackbox.js +0 -3
- package/dist/scripts/naruto-real-parallelism-blackbox.js +0 -307
- package/dist/scripts/naruto-worktree-coding-blackbox.js +0 -29
- package/dist/scripts/parallel-runtime-real-blackbox.js +0 -44
- package/dist/scripts/pipeline-codex-native-doctor-mad-routing-real-blackbox.js +0 -77
- package/dist/scripts/pipeline-codex-native-e2e-blackbox.js +0 -13
- package/dist/scripts/pipeline-codex-native-image-routing-real-blackbox.js +0 -50
- package/dist/scripts/pipeline-codex-native-loop-routing-real-blackbox.js +0 -74
- package/dist/scripts/pipeline-codex-native-qa-routing-real-blackbox.js +0 -51
- package/dist/scripts/pipeline-codex-native-research-routing-real-blackbox.js +0 -45
- package/dist/scripts/pipeline-execution-profile-routing-blackbox.js +0 -4
- package/dist/scripts/postinstall-global-doctor-blackbox.js +0 -12
- package/dist/scripts/ppt-full-e2e-blackbox-check.js +0 -109
- package/dist/scripts/ppt-imagegen-blackbox-check.js +0 -46
- package/dist/scripts/project-skill-dedupe-blackbox.js +0 -36
- package/dist/scripts/qa-backfill-route-blackbox.js +0 -5
- package/dist/scripts/qa-patch-swarm-route-blackbox.js +0 -10
- package/dist/scripts/release-full-parallelism-blackbox.js +0 -41
- package/dist/scripts/release-triwiki-first-runner-blackbox.js +0 -48
- package/dist/scripts/release-wiring-3110-blackbox.js +0 -27
- package/dist/scripts/release-wiring-3112-blackbox.js +0 -17
- package/dist/scripts/release-wiring-3113-blackbox.js +0 -17
- package/dist/scripts/research-backfill-route-blackbox.js +0 -5
- package/dist/scripts/research-final-reviewer-blackbox.js +0 -70
- package/dist/scripts/research-stage-cycle-runtime-blackbox.js +0 -40
- package/dist/scripts/research-synthesis-writer-blackbox.js +0 -24
- package/dist/scripts/route-blackbox-realism-check.js +0 -21
- package/dist/scripts/scheduler-resource-claim-blackbox.js +0 -24
- package/dist/scripts/seo-cli-blackbox-check.js +0 -18
- package/dist/scripts/sks-3110-all-feature-regression-blackbox.js +0 -116
- package/dist/scripts/sks-3112-all-feature-regression-blackbox.js +0 -29
- package/dist/scripts/sks-3113-all-feature-regression-blackbox.js +0 -17
- package/dist/scripts/sks-400-all-feature-regression-blackbox.js +0 -21
- package/dist/scripts/sks-400-extreme-parallel-blackbox.js +0 -8
- package/dist/scripts/sks-400-five-minute-blackbox.js +0 -9
- package/dist/scripts/sks-400-legacy-purge-blackbox.js +0 -8
- package/dist/scripts/sks-401-all-feature-regression-blackbox.js +0 -46
- package/dist/scripts/sks-401-five-minute-actual-blackbox.js +0 -23
- package/dist/scripts/sks-402-all-feature-regression-blackbox.js +0 -9
- package/dist/scripts/sks-402-five-minute-real-blackbox.js +0 -22
- package/dist/scripts/sksd-warm-cache-blackbox.js +0 -12
- package/dist/scripts/supabase-secret-preservation-blackbox.js +0 -29
- package/dist/scripts/team-backfill-route-blackbox.js +0 -5
- package/dist/scripts/team-parallel-write-blackbox.js +0 -55
- package/dist/scripts/team-patch-swarm-route-blackbox.js +0 -10
- package/dist/scripts/triwiki-affected-graph-blackbox.js +0 -28
- package/dist/scripts/triwiki-proof-bank-blackbox.js +0 -30
- package/dist/scripts/triwiki-proof-bank-lock-blackbox.js +0 -7
- package/dist/scripts/update-preserves-supabase-keys-blackbox.js +0 -27
- package/dist/scripts/ux-patch-swarm-route-blackbox.js +0 -10
- package/dist/scripts/ux-review-imagegen-blackbox-check.js +0 -67
- package/dist/scripts/zellij-initial-main-only-blackbox.js +0 -28
- package/dist/scripts/zellij-pane-lock-concurrency-blackbox.js +0 -80
- package/dist/scripts/zellij-pane-lock-open-worker-integration-blackbox.js +0 -137
- package/dist/scripts/zellij-self-heal-typed-blackbox.js +0 -4
- package/dist/scripts/zellij-slot-telemetry-real-blackbox.js +0 -20
- package/dist/scripts/zellij-stacked-fallback-integration-blackbox.js +0 -81
- 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
|
package/dist/core/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const PACKAGE_VERSION = '
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
44
|
-
blockers.push(`
|
|
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
|