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
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import os from 'node:os';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import { PACKAGE_VERSION, packageRoot, readJson, runProcess, which } from './fsx.js';
|
|
3
|
+
import { PACKAGE_VERSION, packageRoot, readJson, runProcess, throttleLines, which } from './fsx.js';
|
|
4
4
|
import { createRequestedScopeContract } from './safety/requested-scope-contract.js';
|
|
5
5
|
import { guardedPackageInstall, guardContextForRoute } from './safety/mutation-guard.js';
|
|
6
6
|
import { isUpdateMigrationReceiptCurrent, resolveInstalledSksEntrypoint, runPackageLocalDoctor, writeProjectUpdateMigrationReceipt } from './update/update-migration-state.js';
|
|
7
7
|
import { installSksMenuBar } from './codex-app/sks-menubar.js';
|
|
8
8
|
import { reconcileSkills } from './init/skills.js';
|
|
9
|
+
import { codexHookTrustDoctor } from './codex-hooks/codex-hook-trust-doctor.js';
|
|
10
|
+
import { readCodexHookActualState } from './codex-hooks/codex-hook-actual-discovery.js';
|
|
11
|
+
import { ui as cliUi, withHeartbeat } from '../cli/cli-theme.js';
|
|
9
12
|
const DEFAULT_REGISTRY = 'https://registry.npmjs.org/';
|
|
10
13
|
export async function runSksUpdateCheck(options = {}) {
|
|
11
14
|
const packageName = options.packageName || 'sneakoscope';
|
|
@@ -102,8 +105,16 @@ export async function runSksUpdateNow(options = {}) {
|
|
|
102
105
|
const globalRoot = npmBin ? await detectNpmGlobalRoot(npmBin, env, options).catch(() => null) : null;
|
|
103
106
|
const projectReceiptRoot = path.resolve(options.projectRoot || env.SKS_MUTATION_LEDGER_ROOT || process.cwd());
|
|
104
107
|
const stages = [];
|
|
108
|
+
const quiet = options.quiet === true || /^(1|true)$/i.test(String(env.SKS_UPDATE_QUIET || ''));
|
|
109
|
+
const machineOutput = quiet || options.json === true;
|
|
110
|
+
const stageStart = (id, status) => {
|
|
111
|
+
if (!machineOutput)
|
|
112
|
+
cliUi.step(`▸ ${id} - ${status}`);
|
|
113
|
+
};
|
|
105
114
|
const stage = (id, ok, status, detail = {}) => {
|
|
106
115
|
stages.push({ id, ok, status, detail });
|
|
116
|
+
if (!machineOutput)
|
|
117
|
+
cliUi.step(`${ok ? '✔' : '✖'} ${id} - ${status}`);
|
|
107
118
|
};
|
|
108
119
|
if (!npmBin) {
|
|
109
120
|
return buildUpdateNowResult({
|
|
@@ -157,45 +168,6 @@ export async function runSksUpdateNow(options = {}) {
|
|
|
157
168
|
error: check.error || 'latest version unavailable'
|
|
158
169
|
});
|
|
159
170
|
}
|
|
160
|
-
if (!requestedVersion && check.latest && !check.update_available) {
|
|
161
|
-
const receipt = await writeProjectUpdateMigrationReceipt({
|
|
162
|
-
root: projectReceiptRoot,
|
|
163
|
-
source: 'update-now-current',
|
|
164
|
-
blockers: [],
|
|
165
|
-
warnings: ['package_already_current']
|
|
166
|
-
}).catch(() => null);
|
|
167
|
-
const migrationCurrent = isUpdateMigrationReceiptCurrent(receipt);
|
|
168
|
-
stage('project_receipt', migrationCurrent, migrationCurrent ? 'current' : 'failed', { root: projectReceiptRoot });
|
|
169
|
-
const sksMenuBar = migrationCurrent
|
|
170
|
-
? await installUpdateSksMenuBar({ root: projectReceiptRoot, env, stage })
|
|
171
|
-
: null;
|
|
172
|
-
await runUpdateGlobalSkillsReconcile(stage);
|
|
173
|
-
return buildUpdateNowResult({
|
|
174
|
-
packageName,
|
|
175
|
-
from: check.current,
|
|
176
|
-
latest: check.latest,
|
|
177
|
-
requestedVersion,
|
|
178
|
-
installVersion,
|
|
179
|
-
npmBin,
|
|
180
|
-
npmArgs,
|
|
181
|
-
command,
|
|
182
|
-
cwd,
|
|
183
|
-
registry,
|
|
184
|
-
globalRoot,
|
|
185
|
-
status: 'current',
|
|
186
|
-
ok: migrationCurrent,
|
|
187
|
-
installCode: null,
|
|
188
|
-
oldVersionDoctor: null,
|
|
189
|
-
newBinary: null,
|
|
190
|
-
newVersion: check.current,
|
|
191
|
-
newVersionDoctor: null,
|
|
192
|
-
projectReceipt: receipt,
|
|
193
|
-
migrationCurrent,
|
|
194
|
-
sksMenuBar,
|
|
195
|
-
stages,
|
|
196
|
-
error: null
|
|
197
|
-
});
|
|
198
|
-
}
|
|
199
171
|
if (options.dryRun) {
|
|
200
172
|
stage('old_version_doctor_preflight', true, 'skipped_dry_run', { reason: 'dry_run_does_not_run_doctor_fix' });
|
|
201
173
|
stage('npm_install', true, 'dry_run', { command });
|
|
@@ -224,19 +196,20 @@ export async function runSksUpdateNow(options = {}) {
|
|
|
224
196
|
error: null
|
|
225
197
|
});
|
|
226
198
|
}
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
199
|
+
if (!requestedVersion && check.latest && !check.update_available) {
|
|
200
|
+
const receipt = await writeProjectUpdateMigrationReceipt({
|
|
201
|
+
root: projectReceiptRoot,
|
|
202
|
+
source: 'update-now-current',
|
|
203
|
+
fromVersion: check.current,
|
|
204
|
+
blockers: [],
|
|
205
|
+
warnings: ['package_already_current']
|
|
206
|
+
}).catch(() => null);
|
|
207
|
+
const migrationCurrent = isUpdateMigrationReceiptCurrent(receipt);
|
|
208
|
+
stage('project_receipt', migrationCurrent, migrationCurrent ? 'current' : 'failed', { root: projectReceiptRoot });
|
|
209
|
+
const sksMenuBar = migrationCurrent
|
|
210
|
+
? await installUpdateSksMenuBar({ root: projectReceiptRoot, env, stage, quiet: machineOutput })
|
|
211
|
+
: null;
|
|
212
|
+
await runUpdateGlobalSkillsReconcile(stage, { quiet: machineOutput });
|
|
240
213
|
return buildUpdateNowResult({
|
|
241
214
|
packageName,
|
|
242
215
|
from: check.current,
|
|
@@ -249,17 +222,46 @@ export async function runSksUpdateNow(options = {}) {
|
|
|
249
222
|
cwd,
|
|
250
223
|
registry,
|
|
251
224
|
globalRoot,
|
|
252
|
-
status: '
|
|
253
|
-
ok:
|
|
225
|
+
status: 'current',
|
|
226
|
+
ok: migrationCurrent,
|
|
254
227
|
installCode: null,
|
|
255
|
-
oldVersionDoctor,
|
|
228
|
+
oldVersionDoctor: null,
|
|
256
229
|
newBinary: null,
|
|
257
|
-
newVersion:
|
|
230
|
+
newVersion: check.current,
|
|
258
231
|
newVersionDoctor: null,
|
|
259
|
-
projectReceipt:
|
|
260
|
-
migrationCurrent
|
|
232
|
+
projectReceipt: receipt,
|
|
233
|
+
migrationCurrent,
|
|
234
|
+
sksMenuBar,
|
|
261
235
|
stages,
|
|
262
|
-
error:
|
|
236
|
+
error: null
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
const oldDoctorTimeoutOverride = Number.parseInt(env.SKS_UPDATE_OLD_DOCTOR_TIMEOUT_MS || '', 10);
|
|
240
|
+
const oldDoctorTimeoutMs = Number.isFinite(oldDoctorTimeoutOverride) && oldDoctorTimeoutOverride > 0
|
|
241
|
+
? oldDoctorTimeoutOverride
|
|
242
|
+
: 60_000;
|
|
243
|
+
let oldVersionDoctor = null;
|
|
244
|
+
if (env.SKS_UPDATE_SKIP_OLD_DOCTOR_PREFLIGHT === '1') {
|
|
245
|
+
stage('old_version_doctor_preflight', true, 'skipped', { reason: 'SKS_UPDATE_SKIP_OLD_DOCTOR_PREFLIGHT=1' });
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
248
|
+
stageStart('old_version_doctor_preflight', 'running migration doctor on current install');
|
|
249
|
+
oldVersionDoctor = await updateHeartbeat(machineOutput, 'old-version doctor', runPackageLocalDoctor({
|
|
250
|
+
root: projectReceiptRoot,
|
|
251
|
+
args: ['doctor', '--fix', '--yes', '--profile', 'migration', '--machine-only', '--report-file', path.join(projectReceiptRoot, '.sneakoscope', 'update', 'old-version-doctor.json')],
|
|
252
|
+
env: {
|
|
253
|
+
...env,
|
|
254
|
+
...(env.SKS_TEST_OLD_DOCTOR_FAIL === '1' ? { SKS_TEST_DOCTOR_FAIL: '1' } : {})
|
|
255
|
+
},
|
|
256
|
+
timeoutMs: oldDoctorTimeoutMs,
|
|
257
|
+
maxOutputBytes: 32 * 1024
|
|
258
|
+
}), 60_000);
|
|
259
|
+
stage('old_version_doctor_preflight', oldVersionDoctor.ok, oldVersionDoctor.ok ? oldVersionDoctor.status : 'failed_continuing', {
|
|
260
|
+
entrypoint: oldVersionDoctor.entrypoint,
|
|
261
|
+
exit_code: oldVersionDoctor.exit_code,
|
|
262
|
+
timeout_ms: oldDoctorTimeoutMs,
|
|
263
|
+
timed_out: oldVersionDoctor.timedOut,
|
|
264
|
+
note: oldVersionDoctor.ok ? null : 'legacy doctor unreliable; new-version doctor will repair after install'
|
|
263
265
|
});
|
|
264
266
|
}
|
|
265
267
|
const mutationLedgerRoot = env.SKS_MUTATION_LEDGER_ROOT || packageRoot();
|
|
@@ -269,7 +271,10 @@ export async function runSksUpdateNow(options = {}) {
|
|
|
269
271
|
projectRoot: mutationLedgerRoot,
|
|
270
272
|
overrides: { package_install: true }
|
|
271
273
|
});
|
|
272
|
-
const
|
|
274
|
+
const npmStdout = machineOutput ? undefined : throttleLines((line) => process.stderr.write(` npm | ${line}\n`), 500);
|
|
275
|
+
const npmStderr = machineOutput ? undefined : throttleLines((line) => process.stderr.write(` npm ! ${line}\n`), 500);
|
|
276
|
+
stageStart('npm_global_install', command || `npm global install ${packageName}`);
|
|
277
|
+
const installOptions = {
|
|
273
278
|
confirmed: true,
|
|
274
279
|
command: npmBin,
|
|
275
280
|
args: npmArgs,
|
|
@@ -277,21 +282,32 @@ export async function runSksUpdateNow(options = {}) {
|
|
|
277
282
|
env,
|
|
278
283
|
timeoutMs: options.timeoutMs ?? 10 * 60 * 1000,
|
|
279
284
|
maxOutputBytes: options.maxOutputBytes ?? 128 * 1024
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
+
};
|
|
286
|
+
if (npmStdout)
|
|
287
|
+
installOptions.onStdout = npmStdout;
|
|
288
|
+
if (npmStderr)
|
|
289
|
+
installOptions.onStderr = npmStderr;
|
|
290
|
+
const install = env.SKS_UPDATE_FAKE_INSTALL === '1'
|
|
291
|
+
? { code: 0, stdout: 'fake install ok', stderr: '', timedOut: false }
|
|
292
|
+
: await updateHeartbeat(machineOutput, `npm install -g ${packageName}`, guardedPackageInstall(guardContextForRoute(mutationLedgerRoot, installContract, command || `npm global install ${packageName}`), `${packageName}@${installVersion}`, installOptions), 60_000).catch((err) => ({
|
|
293
|
+
code: 1,
|
|
294
|
+
stdout: '',
|
|
295
|
+
stderr: err instanceof Error ? err.message : String(err),
|
|
296
|
+
timedOut: false
|
|
297
|
+
}));
|
|
285
298
|
const installOk = install.code === 0;
|
|
286
|
-
stage('npm_global_install', installOk, installOk ? 'installed' : 'failed', { command, code: install.code });
|
|
299
|
+
stage('npm_global_install', installOk, installOk ? env.SKS_UPDATE_FAKE_INSTALL === '1' ? 'fake_installed' : 'installed' : 'failed', { command, code: install.code });
|
|
287
300
|
let newBinary = null;
|
|
288
301
|
let newVersion = null;
|
|
289
302
|
let newVersionDoctor = null;
|
|
290
303
|
let projectReceipt = null;
|
|
291
304
|
let migrationCurrent = false;
|
|
292
305
|
let sksMenuBar = null;
|
|
306
|
+
let hookTrust = null;
|
|
293
307
|
if (installOk) {
|
|
294
|
-
newBinary =
|
|
308
|
+
newBinary = env.SKS_UPDATE_FAKE_INSTALL === '1'
|
|
309
|
+
? path.join(packageRoot(), 'dist', 'bin', 'sks.js')
|
|
310
|
+
: await resolveInstalledSksEntrypoint({ packageName, globalRoot, env });
|
|
295
311
|
stage('resolve_new_package_local_binary', Boolean(newBinary), newBinary ? 'resolved' : 'missing', { new_binary: newBinary });
|
|
296
312
|
if (newBinary) {
|
|
297
313
|
const versionProbe = await runProcess(process.execPath, [newBinary, '--version'], {
|
|
@@ -302,33 +318,52 @@ export async function runSksUpdateNow(options = {}) {
|
|
|
302
318
|
}).catch((err) => ({ code: 1, stdout: '', stderr: err?.message || String(err) }));
|
|
303
319
|
newVersion = parseVersionText(versionProbe.stdout || versionProbe.stderr || '') || null;
|
|
304
320
|
stage('new_version_probe', Boolean(newVersion), newVersion ? 'version_detected' : 'failed', { new_version: newVersion, code: versionProbe.code });
|
|
305
|
-
|
|
321
|
+
stageStart('new_version_global_doctor', 'running migration doctor on updated install');
|
|
322
|
+
newVersionDoctor = await updateHeartbeat(machineOutput, 'new-version doctor', runPackageLocalDoctor({
|
|
306
323
|
root: globalSksRootPath(),
|
|
307
324
|
entrypoint: newBinary,
|
|
308
325
|
args: ['doctor', '--fix', '--yes', '--profile', 'migration', '--machine-only', '--report-file', path.join(globalSksRootPath(), 'update', 'new-version-doctor.json')],
|
|
309
326
|
env,
|
|
310
|
-
timeoutMs:
|
|
327
|
+
timeoutMs: updateDoctorTimeoutMs(env),
|
|
311
328
|
maxOutputBytes: 32 * 1024
|
|
312
|
-
});
|
|
313
|
-
stage('new_version_global_doctor', newVersionDoctor.ok, newVersionDoctor.status, { entrypoint: newBinary, exit_code: newVersionDoctor.exit_code });
|
|
329
|
+
}), 60_000);
|
|
330
|
+
stage('new_version_global_doctor', newVersionDoctor.ok, newVersionDoctor.status, { entrypoint: newBinary, exit_code: newVersionDoctor.exit_code, timeout_ms: updateDoctorTimeoutMs(env), timed_out: newVersionDoctor.timedOut });
|
|
314
331
|
}
|
|
315
332
|
if (newVersionDoctor?.ok) {
|
|
333
|
+
hookTrust = await codexHookTrustDoctor(projectReceiptRoot, { fix: true, managed: true, actual: true })
|
|
334
|
+
.catch((err) => ({ ok: false, blockers: [`hook_trust_repair_failed:${err?.message || err}`] }));
|
|
335
|
+
stage('hook_trust_repair', hookTrust?.ok !== false, hookTrust?.ok !== false ? 'repaired' : 'failed', {
|
|
336
|
+
entries: hookTrust?.current_hash_count ?? null,
|
|
337
|
+
blockers: hookTrust?.blockers || []
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
if (newVersionDoctor?.ok && hookTrust?.ok !== false) {
|
|
316
341
|
projectReceipt = await writeProjectUpdateMigrationReceipt({
|
|
317
342
|
root: projectReceiptRoot,
|
|
318
343
|
source: 'update-now',
|
|
319
344
|
doctor: newVersionDoctor,
|
|
320
345
|
updateStages: stages,
|
|
346
|
+
fromVersion: check.current,
|
|
321
347
|
blockers: [],
|
|
322
348
|
warnings: []
|
|
323
349
|
}).catch(() => null);
|
|
324
350
|
migrationCurrent = isUpdateMigrationReceiptCurrent(projectReceipt);
|
|
325
351
|
stage('project_receipt', migrationCurrent, migrationCurrent ? 'current' : 'failed', { root: projectReceiptRoot });
|
|
326
352
|
if (migrationCurrent)
|
|
327
|
-
sksMenuBar = await installUpdateSksMenuBar({ root: projectReceiptRoot, env, stage });
|
|
328
|
-
await runUpdateGlobalSkillsReconcile(stage);
|
|
353
|
+
sksMenuBar = await installUpdateSksMenuBar({ root: projectReceiptRoot, env, stage, quiet: machineOutput });
|
|
354
|
+
await runUpdateGlobalSkillsReconcile(stage, { quiet: machineOutput });
|
|
329
355
|
}
|
|
330
356
|
}
|
|
331
|
-
const
|
|
357
|
+
const verification = await runFinalUpdateVerification({ installOk, newBinary, installVersion, env, projectReceiptRoot });
|
|
358
|
+
const verifyOk = verification.length > 0 && verification.every((item) => item.ok);
|
|
359
|
+
if (verification.length) {
|
|
360
|
+
stage('final_self_verification', verifyOk, verifyOk ? 'verified' : 'issues', {
|
|
361
|
+
failed: verification.filter((item) => !item.ok).map((item) => item.id)
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
const baseOk = installOk && Boolean(newBinary) && newVersionDoctor?.ok === true && hookTrust?.ok !== false && migrationCurrent;
|
|
365
|
+
const ok = baseOk && verifyOk;
|
|
366
|
+
const status = ok ? 'updated' : baseOk ? 'updated_with_issues' : 'failed';
|
|
332
367
|
return buildUpdateNowResult({
|
|
333
368
|
packageName,
|
|
334
369
|
from: check.current,
|
|
@@ -341,7 +376,7 @@ export async function runSksUpdateNow(options = {}) {
|
|
|
341
376
|
cwd,
|
|
342
377
|
registry,
|
|
343
378
|
globalRoot,
|
|
344
|
-
status
|
|
379
|
+
status,
|
|
345
380
|
ok,
|
|
346
381
|
installCode: install.code,
|
|
347
382
|
oldVersionDoctor,
|
|
@@ -352,15 +387,17 @@ export async function runSksUpdateNow(options = {}) {
|
|
|
352
387
|
migrationCurrent,
|
|
353
388
|
sksMenuBar,
|
|
354
389
|
stages,
|
|
355
|
-
|
|
390
|
+
verification,
|
|
391
|
+
error: ok ? null : status === 'updated_with_issues' ? verificationError(verification) : updateNowError(install, newBinary, newVersionDoctor, migrationCurrent)
|
|
356
392
|
});
|
|
357
393
|
}
|
|
358
|
-
async function runUpdateGlobalSkillsReconcile(stage) {
|
|
359
|
-
const
|
|
394
|
+
async function runUpdateGlobalSkillsReconcile(stage, opts = {}) {
|
|
395
|
+
const work = reconcileSkills({
|
|
360
396
|
targetDir: path.join(os.homedir(), '.agents', 'skills'),
|
|
361
397
|
scope: 'global',
|
|
362
398
|
fix: true
|
|
363
399
|
}).catch((err) => ({ schema: 'sks.skill-reconcile.v1', ok: false, error: err?.message || String(err) }));
|
|
400
|
+
const result = opts.quiet ? await work : await withHeartbeat('skills reconcile', work, { warnAfterMs: 30_000 });
|
|
364
401
|
const ok = result.ok !== false && !result.error;
|
|
365
402
|
stage('global_skills_reconcile', ok, ok ? 'reconciled' : 'failed', {
|
|
366
403
|
installed: Array.isArray(result.installed) ? result.installed.length : null,
|
|
@@ -534,6 +571,7 @@ function buildUpdateNowResult(input) {
|
|
|
534
571
|
migration_current: input.migrationCurrent,
|
|
535
572
|
sks_menubar: input.sksMenuBar || null,
|
|
536
573
|
stages: input.stages,
|
|
574
|
+
verification: input.verification || [],
|
|
537
575
|
error: input.error
|
|
538
576
|
};
|
|
539
577
|
}
|
|
@@ -542,11 +580,12 @@ async function installUpdateSksMenuBar(input) {
|
|
|
542
580
|
input.stage('sks_menubar', true, 'skipped', { reason: 'SKS_UPDATE_SKIP_SKS_MENUBAR=1' });
|
|
543
581
|
return null;
|
|
544
582
|
}
|
|
545
|
-
const
|
|
583
|
+
const work = installSksMenuBar({
|
|
546
584
|
root: input.root,
|
|
547
585
|
apply: true,
|
|
548
586
|
launch: true,
|
|
549
|
-
env: input.env
|
|
587
|
+
env: input.env,
|
|
588
|
+
quiet: input.quiet === true
|
|
550
589
|
}).catch((err) => ({
|
|
551
590
|
schema: 'sks.codex-app-sks-menubar.v1',
|
|
552
591
|
ok: false,
|
|
@@ -572,6 +611,7 @@ async function installUpdateSksMenuBar(input) {
|
|
|
572
611
|
blockers: [err?.message || String(err)],
|
|
573
612
|
warnings: []
|
|
574
613
|
}));
|
|
614
|
+
const result = input.quiet ? await work : await withHeartbeat('SKS menu bar install', work, { warnAfterMs: 30_000 });
|
|
575
615
|
input.stage('sks_menubar', result.ok !== false, result.status, {
|
|
576
616
|
app_path: result.app_path,
|
|
577
617
|
launch_agent_path: result.launch_agent_path,
|
|
@@ -597,6 +637,63 @@ function parseVersionText(text) {
|
|
|
597
637
|
function globalSksRootPath() {
|
|
598
638
|
return path.join(process.env.HOME || os.homedir(), '.sneakoscope-global');
|
|
599
639
|
}
|
|
640
|
+
function updateDoctorTimeoutMs(env) {
|
|
641
|
+
const override = Number.parseInt(env.SKS_UPDATE_NEW_DOCTOR_TIMEOUT_MS || env.SKS_MIGRATION_DOCTOR_TIMEOUT_MS || '', 10);
|
|
642
|
+
return Number.isFinite(override) && override > 0 ? override : 180_000;
|
|
643
|
+
}
|
|
644
|
+
async function updateHeartbeat(quiet, label, work, warnAfterMs = 60_000) {
|
|
645
|
+
return quiet ? work : withHeartbeat(label, work, { warnAfterMs });
|
|
646
|
+
}
|
|
647
|
+
async function runFinalUpdateVerification(input) {
|
|
648
|
+
if (!input.installOk || !input.newBinary || !input.installVersion)
|
|
649
|
+
return [];
|
|
650
|
+
const verification = [];
|
|
651
|
+
const versionProbe = await runProcess(process.execPath, [input.newBinary, '--version'], {
|
|
652
|
+
timeoutMs: 5000,
|
|
653
|
+
maxOutputBytes: 4096,
|
|
654
|
+
env: { ...input.env, SKS_UPDATE_MIGRATION_GATE_DISABLED: '1', SKS_DISABLE_UPDATE_CHECK: '1' }
|
|
655
|
+
}).catch((err) => ({ code: 1, stdout: '', stderr: err?.message || String(err) }));
|
|
656
|
+
const got = parseVersionText(`${versionProbe.stdout || ''}\n${versionProbe.stderr || ''}`);
|
|
657
|
+
verification.push({
|
|
658
|
+
id: 'version_match',
|
|
659
|
+
ok: got === input.installVersion,
|
|
660
|
+
detail: `expected ${input.installVersion}, got ${got || 'missing'}`,
|
|
661
|
+
remediation: 'Run: sks update now --version <expected>'
|
|
662
|
+
});
|
|
663
|
+
const hookState = await readCodexHookActualState(input.projectReceiptRoot).catch(() => null);
|
|
664
|
+
const managedEntries = (hookState?.entries || []).filter((entry) => entry.managed === true);
|
|
665
|
+
const untrusted = managedEntries.filter((entry) => entry.trust_status !== 'Trusted' && entry.trust_status !== 'Managed');
|
|
666
|
+
verification.push({
|
|
667
|
+
id: 'hooks_trusted',
|
|
668
|
+
ok: Boolean(hookState && hookState.ok !== false && managedEntries.length > 0 && untrusted.length === 0),
|
|
669
|
+
detail: untrusted.length ? untrusted.map((entry) => entry.key).slice(0, 3).join(', ') : `managed ${managedEntries.length}`,
|
|
670
|
+
remediation: 'Run: sks codex trust-doctor --fix --managed --actual'
|
|
671
|
+
});
|
|
672
|
+
const stampPath = path.join(path.dirname(input.newBinary), '..', '.sks-build-stamp.json');
|
|
673
|
+
const stamp = await readJson(stampPath, null).catch(() => null);
|
|
674
|
+
const stampVersion = stamp?.package_version || stamp?.version || null;
|
|
675
|
+
verification.push({
|
|
676
|
+
id: 'dist_stamp',
|
|
677
|
+
ok: stampVersion === input.installVersion,
|
|
678
|
+
detail: `expected ${input.installVersion}, got ${stampVersion || 'missing'}`,
|
|
679
|
+
remediation: 'Run: npm run build:incremental'
|
|
680
|
+
});
|
|
681
|
+
const home = input.env.HOME || os.homedir();
|
|
682
|
+
const skillsManifest = await readJson(path.join(home, '.agents', 'skills', '.sks-generated.json'), null).catch(() => null);
|
|
683
|
+
verification.push({
|
|
684
|
+
id: 'skills_manifest',
|
|
685
|
+
ok: skillsManifest?.version === input.installVersion,
|
|
686
|
+
detail: `expected ${input.installVersion}, got ${skillsManifest?.version || 'missing'}`,
|
|
687
|
+
remediation: 'Run: sks doctor --fix --yes'
|
|
688
|
+
});
|
|
689
|
+
return verification;
|
|
690
|
+
}
|
|
691
|
+
function verificationError(verification) {
|
|
692
|
+
const failed = verification.filter((item) => !item.ok);
|
|
693
|
+
return failed.length
|
|
694
|
+
? `update self-verification failed: ${failed.map((item) => item.id).join(', ')}`
|
|
695
|
+
: 'update self-verification did not run';
|
|
696
|
+
}
|
|
600
697
|
function updateNowError(install, newBinary, newVersionDoctor, migrationCurrent) {
|
|
601
698
|
if (install.code !== 0)
|
|
602
699
|
return `${install.stderr || install.stdout || 'npm global install failed'}`.trim();
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { findReferences, pickScanTool } from './impact-scan.js';
|
|
3
|
+
const ADDED_EXPORT_RE = /^\+\s*export\s+(?:async\s+)?(?:function|const|class|interface|type)\s+([A-Za-z_$][\w$]*)\b/;
|
|
4
|
+
export async function analyzeDiffQuality(input) {
|
|
5
|
+
const planFiles = new Set((input.plannedFiles || []).map(normalizePath).filter(Boolean));
|
|
6
|
+
const touchedFiles = new Set(input.changedFiles.map(normalizePath).filter(Boolean));
|
|
7
|
+
const ratio = touchedFiles.size / Math.max(1, planFiles.size || touchedFiles.size || 1);
|
|
8
|
+
const deadAdditions = await detectDeadAdditions(input.root, input.patchText, input.changedFiles);
|
|
9
|
+
const commentNoise = detectCommentNoise(input.patchText);
|
|
10
|
+
const guardBloat = detectGuardBloat(input.patchText);
|
|
11
|
+
const warnings = [
|
|
12
|
+
...(ratio > 2 ? [`minimality_ratio:${ratio.toFixed(2)}`] : []),
|
|
13
|
+
...(commentNoise > 0 ? [`comment_noise:${commentNoise}`] : []),
|
|
14
|
+
...(guardBloat > 0 ? [`guard_bloat:${guardBloat}`] : [])
|
|
15
|
+
];
|
|
16
|
+
const errors = deadAdditions.map((name) => `dead_addition:${name}`);
|
|
17
|
+
return {
|
|
18
|
+
schema: 'sks.diff-quality.v1',
|
|
19
|
+
minimality: {
|
|
20
|
+
plan_files: planFiles.size,
|
|
21
|
+
touched_files: touchedFiles.size,
|
|
22
|
+
ratio: Number(ratio.toFixed(3))
|
|
23
|
+
},
|
|
24
|
+
dead_additions: deadAdditions,
|
|
25
|
+
comment_noise: commentNoise,
|
|
26
|
+
guard_bloat: guardBloat,
|
|
27
|
+
warnings,
|
|
28
|
+
errors
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
function addedExports(patchText, changedFiles) {
|
|
32
|
+
const out = [];
|
|
33
|
+
let currentFile = normalizePath(changedFiles[0] || '');
|
|
34
|
+
for (const line of String(patchText || '').split(/\r?\n/)) {
|
|
35
|
+
if (line.startsWith('+++ b/')) {
|
|
36
|
+
currentFile = normalizePath(line.slice('+++ b/'.length));
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
const match = line.match(ADDED_EXPORT_RE);
|
|
40
|
+
if (match?.[1])
|
|
41
|
+
out.push({ name: match[1], file: currentFile });
|
|
42
|
+
}
|
|
43
|
+
const seen = new Set();
|
|
44
|
+
return out.filter((row) => {
|
|
45
|
+
const key = `${row.file}:${row.name}`;
|
|
46
|
+
if (seen.has(key))
|
|
47
|
+
return false;
|
|
48
|
+
seen.add(key);
|
|
49
|
+
return true;
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
async function detectDeadAdditions(root, patchText, changedFiles) {
|
|
53
|
+
const additions = addedExports(patchText, changedFiles);
|
|
54
|
+
if (!additions.length)
|
|
55
|
+
return [];
|
|
56
|
+
const tool = await pickScanTool();
|
|
57
|
+
const dead = [];
|
|
58
|
+
for (const addition of additions) {
|
|
59
|
+
const refs = await findReferences(root, addition.name, tool, { excludeFile: addition.file });
|
|
60
|
+
if (!refs.length)
|
|
61
|
+
dead.push(addition.name);
|
|
62
|
+
}
|
|
63
|
+
return dead;
|
|
64
|
+
}
|
|
65
|
+
function detectCommentNoise(patchText) {
|
|
66
|
+
const added = String(patchText || '').split(/\r?\n/).filter((line) => line.startsWith('+') && !line.startsWith('+++'));
|
|
67
|
+
let count = 0;
|
|
68
|
+
for (let i = 0; i < added.length - 1; i += 1) {
|
|
69
|
+
const comment = added[i]?.replace(/^\+\s*/, '') || '';
|
|
70
|
+
const code = added[i + 1]?.replace(/^\+\s*/, '') || '';
|
|
71
|
+
if (!/^(\/\/|\/\*|\*)/.test(comment.trim()))
|
|
72
|
+
continue;
|
|
73
|
+
if (tokenOverlap(comment, code) >= 0.7)
|
|
74
|
+
count += 1;
|
|
75
|
+
}
|
|
76
|
+
return count;
|
|
77
|
+
}
|
|
78
|
+
function detectGuardBloat(patchText) {
|
|
79
|
+
const text = String(patchText || '').split(/\r?\n/).filter((line) => line.startsWith('+') && !line.startsWith('+++')).map((line) => line.slice(1)).join('\n');
|
|
80
|
+
const matches = text.match(/catch\s*\([^)]*\)\s*\{\s*(?:console\.(?:log|warn|error)\([^)]*\);?\s*)?\}/g);
|
|
81
|
+
return matches?.length || 0;
|
|
82
|
+
}
|
|
83
|
+
function tokenOverlap(a, b) {
|
|
84
|
+
const left = tokens(a);
|
|
85
|
+
const right = tokens(b);
|
|
86
|
+
if (!left.size || !right.size)
|
|
87
|
+
return 0;
|
|
88
|
+
let overlap = 0;
|
|
89
|
+
for (const token of left)
|
|
90
|
+
if (right.has(token))
|
|
91
|
+
overlap += 1;
|
|
92
|
+
return overlap / Math.max(1, Math.min(left.size, right.size));
|
|
93
|
+
}
|
|
94
|
+
function tokens(value) {
|
|
95
|
+
return new Set(String(value || '').toLowerCase().match(/[a-z0-9_]{3,}/g) || []);
|
|
96
|
+
}
|
|
97
|
+
function normalizePath(value) {
|
|
98
|
+
return String(value || '').replace(/\\/g, '/').replace(/^\.\/+/, '');
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=diff-quality.js.map
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { listFilesRecursive, readText, runProcess, which } from '../fsx.js';
|
|
3
|
+
const DECL_RE = /^\s*export\s+(?:async\s+)?(?:function|const|class|interface|type)\s+([A-Za-z_$][\w$]*)\b/;
|
|
4
|
+
const TEXT_EXT_RE = /\.(?:[cm]?[jt]sx?|json|md|css|scss|html|yml|yaml|toml)$/i;
|
|
5
|
+
export async function scanImpact(root, changedFiles, patchText) {
|
|
6
|
+
const symbols = extractChangedExportedSymbols(patchText, changedFiles);
|
|
7
|
+
const tool = await pickScanTool();
|
|
8
|
+
const references = [];
|
|
9
|
+
for (const sym of symbols) {
|
|
10
|
+
references.push(...await findReferences(root, sym.name, tool, { excludeFile: sym.file }));
|
|
11
|
+
}
|
|
12
|
+
const patchFiles = new Set(changedFiles.map(normalizePath));
|
|
13
|
+
const cochange = [...new Set(references.map((ref) => ref.file))]
|
|
14
|
+
.filter((file) => !patchFiles.has(normalizePath(file)));
|
|
15
|
+
return {
|
|
16
|
+
schema: 'sks.impact-scan.v1',
|
|
17
|
+
changed_symbols: symbols,
|
|
18
|
+
references,
|
|
19
|
+
cochange_required: cochange,
|
|
20
|
+
tool
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
export function extractChangedExportedSymbols(patchText, changedFiles) {
|
|
24
|
+
const symbols = [];
|
|
25
|
+
let currentFile = normalizePath(changedFiles[0] || '');
|
|
26
|
+
for (const rawLine of String(patchText || '').split(/\r?\n/)) {
|
|
27
|
+
if (rawLine.startsWith('+++ b/')) {
|
|
28
|
+
currentFile = normalizePath(rawLine.slice('+++ b/'.length));
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
if (rawLine.startsWith('--- ') || !rawLine.startsWith('-'))
|
|
32
|
+
continue;
|
|
33
|
+
const line = rawLine.slice(1);
|
|
34
|
+
const match = line.match(DECL_RE);
|
|
35
|
+
if (!match?.[1])
|
|
36
|
+
continue;
|
|
37
|
+
symbols.push({ name: match[1], file: currentFile, kind: 'export' });
|
|
38
|
+
}
|
|
39
|
+
return dedupeSymbols(symbols);
|
|
40
|
+
}
|
|
41
|
+
export async function pickScanTool() {
|
|
42
|
+
if (await which('ast-grep'))
|
|
43
|
+
return 'ast-grep';
|
|
44
|
+
if (await which('rg'))
|
|
45
|
+
return 'ripgrep';
|
|
46
|
+
return 'builtin';
|
|
47
|
+
}
|
|
48
|
+
export async function findReferences(root, symbol, tool, opts = {}) {
|
|
49
|
+
const normalizedExclude = normalizePath(opts.excludeFile || '');
|
|
50
|
+
if (!symbol || !/^[A-Za-z_$][\w$]*$/.test(symbol))
|
|
51
|
+
return [];
|
|
52
|
+
if (tool === 'ast-grep') {
|
|
53
|
+
const refs = await astGrepReferences(root, symbol, normalizedExclude);
|
|
54
|
+
if (refs.length)
|
|
55
|
+
return refs;
|
|
56
|
+
}
|
|
57
|
+
if (tool === 'ast-grep' || tool === 'ripgrep') {
|
|
58
|
+
const refs = await ripgrepReferences(root, symbol, normalizedExclude);
|
|
59
|
+
if (refs.length || tool === 'ripgrep')
|
|
60
|
+
return refs;
|
|
61
|
+
}
|
|
62
|
+
return builtinReferences(root, symbol, normalizedExclude);
|
|
63
|
+
}
|
|
64
|
+
async function astGrepReferences(root, symbol, excludeFile) {
|
|
65
|
+
const result = await runProcess('ast-grep', ['run', '-p', symbol, '--json', root], {
|
|
66
|
+
cwd: root,
|
|
67
|
+
timeoutMs: 15_000,
|
|
68
|
+
maxOutputBytes: 512 * 1024
|
|
69
|
+
}).catch(() => null);
|
|
70
|
+
if (!result || result.code !== 0 || !result.stdout.trim())
|
|
71
|
+
return [];
|
|
72
|
+
const refs = [];
|
|
73
|
+
try {
|
|
74
|
+
const rows = JSON.parse(result.stdout);
|
|
75
|
+
for (const row of Array.isArray(rows) ? rows : []) {
|
|
76
|
+
const file = normalizePath(path.relative(root, String(row.file || row.path || '')));
|
|
77
|
+
if (!file || file === excludeFile || shouldIgnorePath(file))
|
|
78
|
+
continue;
|
|
79
|
+
refs.push({
|
|
80
|
+
symbol,
|
|
81
|
+
file,
|
|
82
|
+
line: Number(row.range?.start?.line || row.line || 1),
|
|
83
|
+
text: String(row.text || row.lines || '').trim().slice(0, 240)
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return [];
|
|
89
|
+
}
|
|
90
|
+
return capReferences(refs);
|
|
91
|
+
}
|
|
92
|
+
async function ripgrepReferences(root, symbol, excludeFile) {
|
|
93
|
+
const result = await runProcess('rg', ['-n', '--glob', '!node_modules', '--glob', '!.git', '--glob', '!dist', `\\b${escapeRegex(symbol)}\\b`, '.'], {
|
|
94
|
+
cwd: root,
|
|
95
|
+
timeoutMs: 15_000,
|
|
96
|
+
maxOutputBytes: 512 * 1024
|
|
97
|
+
}).catch(() => null);
|
|
98
|
+
if (!result || (result.code !== 0 && !result.stdout))
|
|
99
|
+
return [];
|
|
100
|
+
return capReferences(String(result.stdout || '').split(/\r?\n/).flatMap((line) => {
|
|
101
|
+
const match = line.match(/^(.+?):(\d+):(.*)$/);
|
|
102
|
+
if (!match)
|
|
103
|
+
return [];
|
|
104
|
+
const file = normalizePath(match[1] || '');
|
|
105
|
+
if (!file || file === excludeFile || shouldIgnorePath(file))
|
|
106
|
+
return [];
|
|
107
|
+
return [{ symbol, file, line: Number(match[2] || 1), text: String(match[3] || '').trim().slice(0, 240) }];
|
|
108
|
+
}));
|
|
109
|
+
}
|
|
110
|
+
async function builtinReferences(root, symbol, excludeFile) {
|
|
111
|
+
const files = await listFilesRecursive(root, { ignore: ['.git', 'node_modules', 'dist', '.sneakoscope/tmp', '.sneakoscope/arenas'], maxFiles: 30_000 });
|
|
112
|
+
const word = new RegExp(`\\b${escapeRegex(symbol)}\\b`);
|
|
113
|
+
const refs = [];
|
|
114
|
+
for (const abs of files) {
|
|
115
|
+
const file = normalizePath(path.relative(root, abs));
|
|
116
|
+
if (!TEXT_EXT_RE.test(file) || file === excludeFile || shouldIgnorePath(file))
|
|
117
|
+
continue;
|
|
118
|
+
const text = await readText(abs, '').catch(() => '');
|
|
119
|
+
if (!word.test(String(text)))
|
|
120
|
+
continue;
|
|
121
|
+
const lines = String(text).split(/\r?\n/);
|
|
122
|
+
let perFile = 0;
|
|
123
|
+
for (let index = 0; index < lines.length && perFile < 50; index += 1) {
|
|
124
|
+
if (!word.test(lines[index] || ''))
|
|
125
|
+
continue;
|
|
126
|
+
refs.push({ symbol, file, line: index + 1, text: String(lines[index] || '').trim().slice(0, 240) });
|
|
127
|
+
perFile += 1;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return capReferences(refs);
|
|
131
|
+
}
|
|
132
|
+
function capReferences(refs) {
|
|
133
|
+
const perFile = new Map();
|
|
134
|
+
const out = [];
|
|
135
|
+
for (const ref of refs) {
|
|
136
|
+
const key = `${ref.symbol}:${ref.file}`;
|
|
137
|
+
const count = perFile.get(key) || 0;
|
|
138
|
+
if (count >= 50)
|
|
139
|
+
continue;
|
|
140
|
+
perFile.set(key, count + 1);
|
|
141
|
+
out.push(ref);
|
|
142
|
+
}
|
|
143
|
+
return out.slice(0, 500);
|
|
144
|
+
}
|
|
145
|
+
function dedupeSymbols(symbols) {
|
|
146
|
+
const seen = new Set();
|
|
147
|
+
return symbols.filter((sym) => {
|
|
148
|
+
const key = `${sym.file}:${sym.name}:${sym.kind}`;
|
|
149
|
+
if (seen.has(key))
|
|
150
|
+
return false;
|
|
151
|
+
seen.add(key);
|
|
152
|
+
return true;
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
function shouldIgnorePath(file) {
|
|
156
|
+
return file.startsWith('node_modules/') || file.startsWith('.git/') || file.startsWith('dist/');
|
|
157
|
+
}
|
|
158
|
+
function normalizePath(value) {
|
|
159
|
+
return String(value || '').replace(/\\/g, '/').replace(/^\.\/+/, '');
|
|
160
|
+
}
|
|
161
|
+
function escapeRegex(value) {
|
|
162
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
163
|
+
}
|
|
164
|
+
//# sourceMappingURL=impact-scan.js.map
|