sneakoscope 4.8.7 → 5.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +51 -726
- 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 +94 -9
- 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-lifecycle.js +5 -3
- 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-cli-worker.js +0 -1
- package/dist/core/agents/native-worker-backend-router.js +58 -1
- package/dist/core/codex-app/sks-menubar.js +477 -132
- 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 +18 -1
- 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 +24 -2
- 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-dirty-planner.js +51 -7
- 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 +11 -1
- 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 +386 -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/core/zellij/zellij-slot-telemetry.js +1 -1
- 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/naruto-real-active-pool-runtime-check.js +26 -1
- 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 +45 -17
- 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/sks-menubar-install-check.js +57 -20
- 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/dist/scripts/zellij-slot-telemetry-runtime-check.js +1 -0
- package/docs/assets/sneakoscope-architecture-pipeline.jpg +0 -0
- package/docs/demo.tape +28 -0
- package/package.json +31 -24
- 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-dirty-repair-blackbox.js +0 -22
- package/dist/scripts/doctor-dirty-semantic-blackbox.js +0 -8
- package/dist/scripts/doctor-native-capability-repair-blackbox.js +0 -39
- 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/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/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/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,217 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { flag } from '../../cli/args.js';
|
|
3
|
+
import { printJson } from '../../cli/output.js';
|
|
4
|
+
import { projectRoot, readText, runProcess, writeJsonAtomic, nowIso } from '../fsx.js';
|
|
5
|
+
import { ui } from '../../cli/cli-theme.js';
|
|
6
|
+
import { runCompiledRules } from '../verification/mistake-rule-compiler.js';
|
|
7
|
+
const LENSES = [
|
|
8
|
+
'review-correctness',
|
|
9
|
+
'review-security',
|
|
10
|
+
'review-lean',
|
|
11
|
+
'review-regression'
|
|
12
|
+
];
|
|
13
|
+
export async function reviewCommand(args = []) {
|
|
14
|
+
const root = path.resolve(String(readOption(args, '--root', '') || await projectRoot()));
|
|
15
|
+
const diff = await collectDiff(root, args);
|
|
16
|
+
if (!diff.files.length) {
|
|
17
|
+
const report = buildReport({ files: [], machine: { checks: [], findings: [] }, lenses: [], fix: null });
|
|
18
|
+
if (flag(args, '--json'))
|
|
19
|
+
return printJson(report);
|
|
20
|
+
ui.banner('review');
|
|
21
|
+
ui.ok('변경 없음');
|
|
22
|
+
return report;
|
|
23
|
+
}
|
|
24
|
+
const [machine, lenses] = await Promise.all([
|
|
25
|
+
runMachineChecks(root, diff),
|
|
26
|
+
runReadOnlyReviewLenses(diff)
|
|
27
|
+
]);
|
|
28
|
+
let fix = null;
|
|
29
|
+
if (flag(args, '--fix'))
|
|
30
|
+
fix = await attemptMachineFix(root, machine.findings);
|
|
31
|
+
const report = buildReport({ files: diff.files, machine, lenses, fix });
|
|
32
|
+
await writeJsonAtomic(path.join(root, '.sneakoscope', 'reports', 'review-report.json'), report);
|
|
33
|
+
if (flag(args, '--json'))
|
|
34
|
+
return printJson(report);
|
|
35
|
+
printReviewReport(report);
|
|
36
|
+
if (report.verdict === 'blocked')
|
|
37
|
+
process.exitCode = 1;
|
|
38
|
+
return report;
|
|
39
|
+
}
|
|
40
|
+
async function collectDiff(root, args) {
|
|
41
|
+
const staged = flag(args, '--staged');
|
|
42
|
+
const ref = String(readOption(args, '--diff', '') || '');
|
|
43
|
+
const nameArgs = staged ? ['diff', '--staged', '--name-only'] : ref ? ['diff', ref, '--name-only'] : ['diff', '--name-only'];
|
|
44
|
+
const diffArgs = staged ? ['diff', '--staged', '--no-ext-diff'] : ref ? ['diff', ref, '--no-ext-diff'] : ['diff', '--no-ext-diff'];
|
|
45
|
+
const names = await runProcess('git', nameArgs, { cwd: root, timeoutMs: 15000, maxOutputBytes: 256 * 1024 });
|
|
46
|
+
const body = await runProcess('git', diffArgs, { cwd: root, timeoutMs: 15000, maxOutputBytes: 2 * 1024 * 1024 });
|
|
47
|
+
const files = String(names.stdout || '').split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
48
|
+
return {
|
|
49
|
+
mode: staged ? 'staged' : ref ? `diff:${ref}` : 'worktree',
|
|
50
|
+
files,
|
|
51
|
+
text: body.stdout || '',
|
|
52
|
+
command: ['git', ...diffArgs].join(' ')
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
async function runMachineChecks(root, diff) {
|
|
56
|
+
const findings = [];
|
|
57
|
+
const checks = [];
|
|
58
|
+
findings.push(...await conflictMarkerFindings(root, diff.files));
|
|
59
|
+
findings.push(...secretPatternFindings(diff.text));
|
|
60
|
+
const mistakeRules = await runCompiledRules(root, diff.files).catch((err) => ({
|
|
61
|
+
ok: false,
|
|
62
|
+
violations: [{ rule_id: 'mistake-rule-runner', severity: 'error', file: '', line: 0, description: err instanceof Error ? err.message : String(err), good_example: '' }],
|
|
63
|
+
rule_count: 0
|
|
64
|
+
}));
|
|
65
|
+
checks.push({ command: 'sks mistake rules', ok: mistakeRules.ok });
|
|
66
|
+
for (const violation of mistakeRules.violations || []) {
|
|
67
|
+
findings.push({
|
|
68
|
+
id: `machine:mistake-rule:${violation.rule_id}:${violation.file}:${violation.line}`,
|
|
69
|
+
severity: violation.severity === 'error' ? 'blocker' : 'medium',
|
|
70
|
+
evidence: 'machine',
|
|
71
|
+
source: 'mistake-rule',
|
|
72
|
+
...(violation.file ? { file: violation.file } : {}),
|
|
73
|
+
...(violation.line ? { line: violation.line } : {}),
|
|
74
|
+
message: `${violation.description}${violation.good_example ? ` | good: ${singleLine(violation.good_example)}` : ''}`
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
if (diff.files.some((file) => /\.(ts|tsx|mts|cts)$/.test(file))) {
|
|
78
|
+
const command = 'tsc -p tsconfig.json --noEmit';
|
|
79
|
+
const result = await runProcess('npx', ['tsc', '-p', 'tsconfig.json', '--noEmit'], {
|
|
80
|
+
cwd: root,
|
|
81
|
+
timeoutMs: 120000,
|
|
82
|
+
maxOutputBytes: 512 * 1024
|
|
83
|
+
});
|
|
84
|
+
checks.push({ command, ok: result.code === 0, code: result.code });
|
|
85
|
+
if (result.code !== 0) {
|
|
86
|
+
findings.push({
|
|
87
|
+
id: 'machine:typecheck',
|
|
88
|
+
severity: 'blocker',
|
|
89
|
+
evidence: 'machine',
|
|
90
|
+
source: 'typescript',
|
|
91
|
+
message: summarizeProcessOutput(result.stderr || result.stdout || 'TypeScript check failed.'),
|
|
92
|
+
command
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return { checks, findings, summary: { check_count: checks.length, finding_count: findings.length } };
|
|
97
|
+
}
|
|
98
|
+
async function conflictMarkerFindings(root, files) {
|
|
99
|
+
const findings = [];
|
|
100
|
+
for (const file of files) {
|
|
101
|
+
const abs = path.join(root, file);
|
|
102
|
+
const text = await readText(abs, '').catch(() => '');
|
|
103
|
+
const lines = String(text || '').split(/\r?\n/);
|
|
104
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
105
|
+
if (/^(<<<<<<<|=======|>>>>>>>)(?:\s|$)/.test(lines[index] || '')) {
|
|
106
|
+
findings.push({
|
|
107
|
+
id: `machine:conflict:${file}:${index + 1}`,
|
|
108
|
+
severity: 'blocker',
|
|
109
|
+
evidence: 'machine',
|
|
110
|
+
source: 'conflict-marker-scan',
|
|
111
|
+
file,
|
|
112
|
+
line: index + 1,
|
|
113
|
+
message: 'Git conflict marker remains in a changed file.'
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return findings;
|
|
119
|
+
}
|
|
120
|
+
function secretPatternFindings(diffText) {
|
|
121
|
+
const findings = [];
|
|
122
|
+
const patterns = [
|
|
123
|
+
/(?:api[_-]?key|secret|token|password)\s*[:=]\s*['"][^'"]{12,}['"]/i,
|
|
124
|
+
/sk-[A-Za-z0-9_-]{20,}/
|
|
125
|
+
];
|
|
126
|
+
const lines = diffText.split(/\r?\n/);
|
|
127
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
128
|
+
const line = lines[index] || '';
|
|
129
|
+
if (!line.startsWith('+') || line.startsWith('+++'))
|
|
130
|
+
continue;
|
|
131
|
+
if (patterns.some((pattern) => pattern.test(line))) {
|
|
132
|
+
findings.push({
|
|
133
|
+
id: `machine:secret:${index + 1}`,
|
|
134
|
+
severity: 'blocker',
|
|
135
|
+
evidence: 'machine',
|
|
136
|
+
source: 'diff-secret-scan',
|
|
137
|
+
line: index + 1,
|
|
138
|
+
message: 'Added diff line looks like a secret or credential.'
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return findings;
|
|
143
|
+
}
|
|
144
|
+
async function runReadOnlyReviewLenses(diff) {
|
|
145
|
+
return LENSES.map((role) => ({
|
|
146
|
+
role,
|
|
147
|
+
evidence: 'llm',
|
|
148
|
+
status: 'not_run',
|
|
149
|
+
findings: [],
|
|
150
|
+
unverified: [`${role} native read-only worker not run in this local review invocation`],
|
|
151
|
+
files: diff.files.length
|
|
152
|
+
}));
|
|
153
|
+
}
|
|
154
|
+
async function attemptMachineFix(root, findings) {
|
|
155
|
+
const machine = findings.filter((finding) => finding.evidence === 'machine');
|
|
156
|
+
if (!machine.length)
|
|
157
|
+
return { attempted: false, reason: 'no_machine_findings' };
|
|
158
|
+
const pkg = JSON.parse(await readText(path.join(root, 'package.json'), '{}'));
|
|
159
|
+
if (pkg?.scripts?.lint && String(pkg.scripts.lint).includes('--fix')) {
|
|
160
|
+
const result = await runProcess('npm', ['run', 'lint', '--', '--fix'], { cwd: root, timeoutMs: 120000, maxOutputBytes: 512 * 1024 });
|
|
161
|
+
return { attempted: true, command: 'npm run lint -- --fix', ok: result.code === 0, code: result.code };
|
|
162
|
+
}
|
|
163
|
+
return { attempted: false, reason: 'no_safe_machine_fixer_available', machine_findings: machine.length };
|
|
164
|
+
}
|
|
165
|
+
function buildReport(input) {
|
|
166
|
+
const findings = dedupeFindings([...(input.machine.findings || []), ...input.lenses.flatMap((lens) => lens.findings || [])]);
|
|
167
|
+
const verdict = findings.some((finding) => finding.severity === 'blocker') ? 'blocked' : findings.length ? 'needs_attention' : 'clean';
|
|
168
|
+
return {
|
|
169
|
+
schema: 'sks.review-report.v1',
|
|
170
|
+
generated_at: nowIso(),
|
|
171
|
+
files: input.files.length,
|
|
172
|
+
machine: input.machine.summary || { check_count: 0, finding_count: 0 },
|
|
173
|
+
lenses: input.lenses,
|
|
174
|
+
findings,
|
|
175
|
+
fix: input.fix,
|
|
176
|
+
verdict
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
function dedupeFindings(findings) {
|
|
180
|
+
const seen = new Set();
|
|
181
|
+
const severityRank = { blocker: 0, high: 1, medium: 2, low: 3 };
|
|
182
|
+
return findings
|
|
183
|
+
.filter((finding) => {
|
|
184
|
+
const key = `${finding.evidence}:${finding.source}:${finding.file || ''}:${finding.line || ''}:${finding.message}`;
|
|
185
|
+
if (seen.has(key))
|
|
186
|
+
return false;
|
|
187
|
+
seen.add(key);
|
|
188
|
+
return true;
|
|
189
|
+
})
|
|
190
|
+
.sort((a, b) => a.evidence === b.evidence ? severityRank[a.severity] - severityRank[b.severity] : a.evidence === 'machine' ? -1 : 1);
|
|
191
|
+
}
|
|
192
|
+
function printReviewReport(report) {
|
|
193
|
+
ui.banner('review');
|
|
194
|
+
const ok = report.verdict === 'clean';
|
|
195
|
+
if (ok)
|
|
196
|
+
ui.ok(`clean (${report.files} files)`);
|
|
197
|
+
else
|
|
198
|
+
ui.warn(`${report.verdict} (${report.findings.length} findings across ${report.files} files)`);
|
|
199
|
+
for (const finding of report.findings.slice(0, 20)) {
|
|
200
|
+
const where = finding.file ? `${finding.file}${finding.line ? `:${finding.line}` : ''}` : finding.command || finding.source;
|
|
201
|
+
ui.kv(`${finding.evidence}/${finding.severity}`, `${where} - ${finding.message}`);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
function summarizeProcessOutput(text) {
|
|
205
|
+
return text.split(/\r?\n/).map((line) => line.trim()).filter(Boolean).slice(0, 8).join(' | ').slice(0, 900);
|
|
206
|
+
}
|
|
207
|
+
function singleLine(value) {
|
|
208
|
+
return String(value || '').replace(/\s+/g, ' ').trim().slice(0, 240);
|
|
209
|
+
}
|
|
210
|
+
function readOption(args = [], name, fallback = null) {
|
|
211
|
+
const index = args.indexOf(name);
|
|
212
|
+
if (index >= 0 && args[index + 1] && !String(args[index + 1]).startsWith('--'))
|
|
213
|
+
return args[index + 1];
|
|
214
|
+
const prefixed = args.find((arg) => String(arg).startsWith(`${name}=`));
|
|
215
|
+
return prefixed ? prefixed.slice(name.length + 1) : fallback;
|
|
216
|
+
}
|
|
217
|
+
//# sourceMappingURL=review-command.js.map
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { exists, readJson, readText } from '../fsx.js';
|
|
3
|
+
export async function context7EvidenceStatus(root, missionId = null) {
|
|
4
|
+
const candidates = [
|
|
5
|
+
missionId ? path.join(root, '.sneakoscope', 'missions', missionId, 'context7-evidence.jsonl') : null,
|
|
6
|
+
path.join(root, '.sneakoscope', 'state', 'context7-evidence.jsonl'),
|
|
7
|
+
path.join(root, '.sneakoscope', 'memory', 'q2_facts', 'stack-current-docs.md')
|
|
8
|
+
].filter(Boolean);
|
|
9
|
+
for (const file of candidates) {
|
|
10
|
+
if (!(await exists(file)))
|
|
11
|
+
continue;
|
|
12
|
+
const text = await readText(file, '');
|
|
13
|
+
if (/\S/.test(text) && /(context7|resolve[-_]?library|query[-_]?docs|get[-_]?library|official docs|vendor docs)/i.test(text)) {
|
|
14
|
+
return { ok: true, policy: 'required_satisfied', evidence: path.relative(root, file).split(path.sep).join('/') };
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return { ok: false, policy: 'required_not_satisfied', evidence: null, blocker: 'context7_policy_required_not_satisfied' };
|
|
18
|
+
}
|
|
19
|
+
export async function artifactPresence(root, dir, artifacts) {
|
|
20
|
+
const rows = await Promise.all(artifacts.map(async (artifact) => {
|
|
21
|
+
const file = path.isAbsolute(artifact) ? artifact : path.join(dir, artifact);
|
|
22
|
+
return { artifact, present: await exists(file) };
|
|
23
|
+
}));
|
|
24
|
+
const missing = rows.filter((row) => !row.present).map((row) => row.artifact);
|
|
25
|
+
return { ok: missing.length === 0, rows, missing };
|
|
26
|
+
}
|
|
27
|
+
export async function evaluateLocalGate(input) {
|
|
28
|
+
const missionDir = input.dir || (input.missionId ? path.join(input.root, '.sneakoscope', 'missions', input.missionId) : input.root);
|
|
29
|
+
const gatePath = path.isAbsolute(input.gateFile) ? input.gateFile : path.join(missionDir, input.gateFile);
|
|
30
|
+
const gate = await readJson(gatePath, null);
|
|
31
|
+
const blockers = [];
|
|
32
|
+
if (!gate)
|
|
33
|
+
blockers.push(`${path.basename(input.gateFile)}_missing`);
|
|
34
|
+
if (gate) {
|
|
35
|
+
if (!(gate.passed === true || gate.ok === true || gate.status === 'pass' || gate.status === 'passed'))
|
|
36
|
+
blockers.push('gate_not_passed');
|
|
37
|
+
if (gate.ok === false)
|
|
38
|
+
blockers.push('gate_ok_false');
|
|
39
|
+
if (Array.isArray(gate.blockers) && gate.blockers.length)
|
|
40
|
+
blockers.push(...gate.blockers.map(String));
|
|
41
|
+
if (Array.isArray(gate.missing_fields) && gate.missing_fields.length)
|
|
42
|
+
blockers.push(...gate.missing_fields.map((field) => `missing_field:${field}`));
|
|
43
|
+
for (const section of input.requiredSections || []) {
|
|
44
|
+
if (gate[section] == null)
|
|
45
|
+
blockers.push(`missing_gate_section:${section}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
const artifactCheck = await artifactPresence(input.root, missionDir, input.requiredArtifacts || []);
|
|
49
|
+
blockers.push(...artifactCheck.missing.map((artifact) => `missing_artifact:${artifact}`));
|
|
50
|
+
const uniqueBlockers = [...new Set(blockers)];
|
|
51
|
+
return {
|
|
52
|
+
ok: uniqueBlockers.length === 0,
|
|
53
|
+
gate,
|
|
54
|
+
gate_path: gatePath,
|
|
55
|
+
blockers: uniqueBlockers,
|
|
56
|
+
artifacts: artifactCheck
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=route-success-helpers.js.map
|
|
@@ -62,7 +62,15 @@ export async function runCommand(args = []) {
|
|
|
62
62
|
return finalizeMockRun(root, { id, route, prompt, args, classification, mode });
|
|
63
63
|
}
|
|
64
64
|
async function finalizeMockRun(root, { id, route, prompt, args, classification, mode, }) {
|
|
65
|
-
const gate = {
|
|
65
|
+
const gate = {
|
|
66
|
+
schema: 'sks.run-gate.v1',
|
|
67
|
+
ok: false,
|
|
68
|
+
passed: false,
|
|
69
|
+
route: route.command,
|
|
70
|
+
mock: true,
|
|
71
|
+
execution_class: 'mock_fixture',
|
|
72
|
+
blockers: ['run_mock_fixture_cannot_claim_real_completion']
|
|
73
|
+
};
|
|
66
74
|
await writeJsonAtomic(path.join(missionDir(root, id), 'run-gate.json'), gate);
|
|
67
75
|
const proof = await finalizeRoute(root, {
|
|
68
76
|
missionId: id,
|
|
@@ -76,19 +84,20 @@ async function finalizeMockRun(root, { id, route, prompt, args, classification,
|
|
|
76
84
|
command: { cmd: `sks run "${prompt}" --mock`, status: 0 },
|
|
77
85
|
});
|
|
78
86
|
const trust = await loadTrustReport(root, id);
|
|
87
|
+
const completionOk = proof.ok && proof.proof?.status !== 'mock_only' && gate.passed === true;
|
|
79
88
|
await setCurrent(root, {
|
|
80
89
|
mission_id: id,
|
|
81
90
|
mode: 'RUN',
|
|
82
91
|
route: route.id,
|
|
83
92
|
route_command: route.command,
|
|
84
|
-
phase:
|
|
93
|
+
phase: completionOk ? 'RUN_MOCK_FINALIZED' : 'RUN_MOCK_BLOCKED',
|
|
85
94
|
implementation_allowed: true,
|
|
86
95
|
completion_proof: 'completion-proof.json',
|
|
87
96
|
trust_report: 'trust-report.json',
|
|
88
97
|
});
|
|
89
98
|
const result = {
|
|
90
99
|
schema: 'sks.run.v2',
|
|
91
|
-
ok:
|
|
100
|
+
ok: completionOk,
|
|
92
101
|
mission_id: id,
|
|
93
102
|
route: route.command,
|
|
94
103
|
mode,
|
|
@@ -98,6 +107,8 @@ async function finalizeMockRun(root, { id, route, prompt, args, classification,
|
|
|
98
107
|
completion_proof: { ok: proof.ok, validation: proof.validation },
|
|
99
108
|
trust_report: trust,
|
|
100
109
|
};
|
|
110
|
+
if (!result.ok)
|
|
111
|
+
process.exitCode = 1;
|
|
101
112
|
if (flag(args, '--json')) {
|
|
102
113
|
console.log(JSON.stringify(result, null, 2));
|
|
103
114
|
return result;
|
|
@@ -235,7 +246,7 @@ async function executeRouteCommand(root, route, prompt, { auto = false } = {}) {
|
|
|
235
246
|
return routeExecutionResult(route, ['sks', ...commandArgs].join(' '), result, {
|
|
236
247
|
okStatus: 'completed',
|
|
237
248
|
trustStatus: 'verified_partial',
|
|
238
|
-
executionKind:
|
|
249
|
+
executionKind: 'live_route',
|
|
239
250
|
});
|
|
240
251
|
}
|
|
241
252
|
async function runAutoVerification(root, missionId) {
|
|
@@ -304,7 +315,7 @@ function routeExecutionResult(route, command, result, options = {}) {
|
|
|
304
315
|
schema: 'sks.run-route-execution.v1',
|
|
305
316
|
ok,
|
|
306
317
|
status: ok ? (options.okStatus || 'completed') : 'blocked',
|
|
307
|
-
execution_kind: ok ? (options.executionKind || '
|
|
318
|
+
execution_kind: ok ? (options.executionKind || 'live_route') : 'blocked',
|
|
308
319
|
route: route.command,
|
|
309
320
|
command,
|
|
310
321
|
exit_code: result.code,
|
|
@@ -314,7 +325,7 @@ function routeExecutionResult(route, command, result, options = {}) {
|
|
|
314
325
|
trust_status: ok ? options.trustStatus || 'verified_partial' : 'blocked',
|
|
315
326
|
blockers: ok ? [] : ['route_command_failed'],
|
|
316
327
|
unverified: ok
|
|
317
|
-
? options.unverified || ['sks run --execute
|
|
328
|
+
? options.unverified || ['sks run --execute ran the selected route command; route-specific gates remain authoritative for final trust.']
|
|
318
329
|
: [],
|
|
319
330
|
next_action: ok ? 'review completion proof and trust report' : 'inspect run-route-execution.json stderr_tail',
|
|
320
331
|
};
|
|
@@ -538,7 +549,7 @@ async function writeLightweightTrustReport(root, missionId, route, statusHint, p
|
|
|
538
549
|
const report = {
|
|
539
550
|
schema: TRUST_REPORT_SCHEMA,
|
|
540
551
|
...trustKernelMetadata(),
|
|
541
|
-
ok: proofOk && !['blocked', 'failed', 'not_verified'].includes(status),
|
|
552
|
+
ok: proofOk && !['blocked', 'failed', 'not_verified', 'mock_only'].includes(status),
|
|
542
553
|
mission_id: missionId,
|
|
543
554
|
route,
|
|
544
555
|
status,
|
|
@@ -551,6 +562,7 @@ async function writeLightweightTrustReport(root, missionId, route, statusHint, p
|
|
|
551
562
|
lightweight: true,
|
|
552
563
|
reason: 'safe_deterministic_run_wrapper'
|
|
553
564
|
},
|
|
565
|
+
trust_basis: 'lightweight',
|
|
554
566
|
evidence: {
|
|
555
567
|
completion_proof: `.sneakoscope/missions/${missionId}/completion-proof.json`,
|
|
556
568
|
route_contract: null,
|
|
@@ -2,7 +2,8 @@ import path from 'node:path';
|
|
|
2
2
|
import { printJson } from '../../cli/output.js';
|
|
3
3
|
import { projectRoot } from '../fsx.js';
|
|
4
4
|
import { flag, readOption } from './command-utils.js';
|
|
5
|
-
import { runSearchVisibilityApply, runSearchVisibilityAudit, runSearchVisibilityDoctor, runSearchVisibilityFixture, runSearchVisibilityPlan, runSearchVisibilityRollback, runSearchVisibilityStatus, runSearchVisibilityVerify, } from '../search-visibility/index.js';
|
|
5
|
+
import { runSearchVisibilityApply, runSearchVisibilityAudit, runSearchVisibilityDoctor, runSearchVisibilityFixture, runSearchVisibilityPlan, runSearchVisibilityRollback, runSearchVisibilityStatus, runSearchVisibilityVerify, resolveSearchVisibilityMission, } from '../search-visibility/index.js';
|
|
6
|
+
import { evaluateLocalGate } from './route-success-helpers.js';
|
|
6
7
|
export async function seoCommand(args = []) {
|
|
7
8
|
return runSearchVisibilityCommand('seo', args, 'seo');
|
|
8
9
|
}
|
|
@@ -15,6 +16,26 @@ export async function runSearchVisibilityCommand(mode, args = [], displayCommand
|
|
|
15
16
|
const rest = args.slice(1);
|
|
16
17
|
const options = await parseOptions(rest);
|
|
17
18
|
let result;
|
|
19
|
+
if (action === 'apply' && options.apply) {
|
|
20
|
+
const mission = await resolveSearchVisibilityMission(options.root, firstPositional(rest) || 'latest');
|
|
21
|
+
const blockers = [];
|
|
22
|
+
if (!mission)
|
|
23
|
+
blockers.push('mission_required_for_apply');
|
|
24
|
+
else {
|
|
25
|
+
const planGate = await evaluateLocalGate({ root: mission.root, dir: mission.dir, gateFile: mode === 'seo' ? 'seo-gate.json' : 'geo-gate.json', requiredArtifacts: ['search-visibility/mutation-plan.json'] });
|
|
26
|
+
blockers.push(...planGate.blockers.filter((blocker) => blocker !== 'gate_not_passed' && blocker !== 'gate_ok_false'));
|
|
27
|
+
}
|
|
28
|
+
if (blockers.length) {
|
|
29
|
+
result = { schema: 'sks.search-visibility.apply-command.v1', ok: false, route: mode === 'seo' ? '$SEO-GEO-OPTIMIZER' : '$SEO-GEO-OPTIMIZER', status: 'blocked', blockers };
|
|
30
|
+
process.exitCode = 1;
|
|
31
|
+
if (options.json) {
|
|
32
|
+
printJson(result);
|
|
33
|
+
return result;
|
|
34
|
+
}
|
|
35
|
+
printHuman(mode, action, result);
|
|
36
|
+
return result;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
18
39
|
if (action === 'doctor')
|
|
19
40
|
result = await runSearchVisibilityDoctor(mode, options);
|
|
20
41
|
else if (action === 'audit')
|
|
@@ -33,6 +54,7 @@ export async function runSearchVisibilityCommand(mode, args = [], displayCommand
|
|
|
33
54
|
result = await runSearchVisibilityFixture(mode, options);
|
|
34
55
|
else
|
|
35
56
|
return usage(mode, 2, displayCommand);
|
|
57
|
+
result = await applySearchVisibilityGateResult(mode, action, result, options);
|
|
36
58
|
if (isBlocked(result))
|
|
37
59
|
process.exitCode = 1;
|
|
38
60
|
if (options.json) {
|
|
@@ -42,6 +64,32 @@ export async function runSearchVisibilityCommand(mode, args = [], displayCommand
|
|
|
42
64
|
printHuman(mode, action, result);
|
|
43
65
|
return result;
|
|
44
66
|
}
|
|
67
|
+
async function applySearchVisibilityGateResult(mode, action, result, options) {
|
|
68
|
+
if (!result || typeof result !== 'object' || !result.mission_id)
|
|
69
|
+
return result;
|
|
70
|
+
const mission = await resolveSearchVisibilityMission(options.root, result.mission_id);
|
|
71
|
+
if (!mission)
|
|
72
|
+
return { ...result, ok: false, blockers: [...(result.blockers || []), 'mission_missing_for_gate_evaluation'] };
|
|
73
|
+
const requiredArtifacts = action === 'apply'
|
|
74
|
+
? ['search-visibility/mutation-plan.json', 'search-visibility/rollback-manifest.json', 'search-visibility/verification-report.json']
|
|
75
|
+
: action === 'status'
|
|
76
|
+
? []
|
|
77
|
+
: ['search-visibility/verification-report.json'];
|
|
78
|
+
const evaluated = await evaluateLocalGate({
|
|
79
|
+
root: mission.root,
|
|
80
|
+
dir: mission.dir,
|
|
81
|
+
gateFile: mode === 'seo' ? 'seo-gate.json' : 'geo-gate.json',
|
|
82
|
+
requiredArtifacts
|
|
83
|
+
});
|
|
84
|
+
const blockers = [...new Set([...(result.blockers || []), ...evaluated.blockers])];
|
|
85
|
+
return {
|
|
86
|
+
...result,
|
|
87
|
+
ok: result.ok === true && evaluated.ok === true,
|
|
88
|
+
status: result.ok === true && evaluated.ok === true ? result.status : 'blocked',
|
|
89
|
+
gate_evaluation: evaluated,
|
|
90
|
+
blockers
|
|
91
|
+
};
|
|
92
|
+
}
|
|
45
93
|
function normalizeOptimizerArgs(args) {
|
|
46
94
|
const out = [...args];
|
|
47
95
|
const first = String(out[0] || '').toLowerCase();
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import * as http from 'node:http';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { flag } from '../../cli/args.js';
|
|
4
|
+
import { printJson } from '../../cli/output.js';
|
|
5
|
+
import { ui } from '../../cli/cli-theme.js';
|
|
6
|
+
import { DASHBOARD_HTML } from '../ui/dashboard-html.js';
|
|
7
|
+
import { findLatestMission, missionDir } from '../mission.js';
|
|
8
|
+
import { nowIso, projectRoot, readJson, readText, runProcess } from '../fsx.js';
|
|
9
|
+
import { readZellijSlotTelemetrySnapshot } from '../zellij/zellij-slot-telemetry.js';
|
|
10
|
+
export async function uiCommand(args = []) {
|
|
11
|
+
const root = path.resolve(String(readOption(args, '--root', '') || await projectRoot()));
|
|
12
|
+
if (flag(args, '--once')) {
|
|
13
|
+
const state = await collectUiState(root, String(readOption(args, '--mission', 'latest') || 'latest'));
|
|
14
|
+
if (flag(args, '--json'))
|
|
15
|
+
return printJson(state);
|
|
16
|
+
ui.banner('ui');
|
|
17
|
+
ui.ok(`state ${state.mission_id || 'latest'} ${state.gates.length} gates`);
|
|
18
|
+
return state;
|
|
19
|
+
}
|
|
20
|
+
const requestedPort = Number(readOption(args, '--port', '4477'));
|
|
21
|
+
const port = Number.isFinite(requestedPort) && requestedPort > 0 ? Math.floor(requestedPort) : 4477;
|
|
22
|
+
const mission = String(readOption(args, '--mission', 'latest') || 'latest');
|
|
23
|
+
const server = http.createServer(async (req, res) => {
|
|
24
|
+
try {
|
|
25
|
+
const url = new URL(req.url || '/', 'http://127.0.0.1');
|
|
26
|
+
if (url.pathname === '/') {
|
|
27
|
+
res.writeHead(200, { 'content-type': 'text/html; charset=utf-8' });
|
|
28
|
+
res.end(DASHBOARD_HTML);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
if (url.pathname === '/events')
|
|
32
|
+
return sseStream(res, root, String(url.searchParams.get('mission') || mission));
|
|
33
|
+
if (url.pathname === '/api/state') {
|
|
34
|
+
res.writeHead(200, { 'content-type': 'application/json; charset=utf-8', 'cache-control': 'no-store' });
|
|
35
|
+
res.end(JSON.stringify(await collectUiState(root, String(url.searchParams.get('mission') || mission))));
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
res.writeHead(404);
|
|
39
|
+
res.end('not found');
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
res.writeHead(500, { 'content-type': 'application/json; charset=utf-8' });
|
|
43
|
+
res.end(JSON.stringify({ ok: false, error: sanitizeText(err?.message || String(err)) }));
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
const actualPort = await listenLocal(server, port);
|
|
47
|
+
const url = `http://127.0.0.1:${actualPort}`;
|
|
48
|
+
ui.banner('ui');
|
|
49
|
+
ui.ok(`dashboard ${url}`);
|
|
50
|
+
void runProcess('/usr/bin/open', [url], { timeoutMs: 5000, maxOutputBytes: 4096 }).catch(() => undefined);
|
|
51
|
+
return new Promise(() => undefined);
|
|
52
|
+
}
|
|
53
|
+
export async function collectUiState(root, missionInput = 'latest') {
|
|
54
|
+
const missionId = missionInput === 'latest' ? await findLatestMission(root) : missionInput;
|
|
55
|
+
const snapshot = missionId ? await readZellijSlotTelemetrySnapshot(root, missionId).catch(() => null) : null;
|
|
56
|
+
const gates = missionId ? await readLatestGateSummaries(root, missionId) : [];
|
|
57
|
+
const events = missionId ? await tailJsonl(path.join(missionDir(root, missionId), 'events.jsonl'), 30) : [];
|
|
58
|
+
const mission = missionId ? await readJson(path.join(missionDir(root, missionId), 'mission.json'), null).catch(() => null) : null;
|
|
59
|
+
return sanitizeForUi({
|
|
60
|
+
schema: 'sks.ui-state.v1',
|
|
61
|
+
ok: true,
|
|
62
|
+
ts: nowIso(),
|
|
63
|
+
mission_id: missionId,
|
|
64
|
+
route: mission?.mode || mission?.route || null,
|
|
65
|
+
snapshot,
|
|
66
|
+
gates,
|
|
67
|
+
events
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
async function sseStream(res, root, mission) {
|
|
71
|
+
res.writeHead(200, {
|
|
72
|
+
'content-type': 'text/event-stream',
|
|
73
|
+
'cache-control': 'no-cache, no-store',
|
|
74
|
+
connection: 'keep-alive'
|
|
75
|
+
});
|
|
76
|
+
const tick = async () => {
|
|
77
|
+
const state = await collectUiState(root, mission).catch((err) => ({ ok: false, ts: nowIso(), error: sanitizeText(err?.message || String(err)) }));
|
|
78
|
+
res.write(`data: ${JSON.stringify(state)}\n\n`);
|
|
79
|
+
};
|
|
80
|
+
await tick();
|
|
81
|
+
const timer = setInterval(tick, 1000);
|
|
82
|
+
res.on('close', () => clearInterval(timer));
|
|
83
|
+
}
|
|
84
|
+
async function readLatestGateSummaries(root, missionId) {
|
|
85
|
+
const dir = missionDir(root, missionId);
|
|
86
|
+
const files = ['stop-gate.json', 'naruto-gate.json', 'team-gate.json', 'qa-gate.json', 'reflection-gate.json', 'completion-proof.json'];
|
|
87
|
+
const rows = [];
|
|
88
|
+
for (const file of files) {
|
|
89
|
+
const gate = await readJson(path.join(dir, file), null).catch(() => null);
|
|
90
|
+
if (!gate)
|
|
91
|
+
continue;
|
|
92
|
+
rows.push({
|
|
93
|
+
id: file,
|
|
94
|
+
ok: gate.passed === true || gate.ok === true || gate.status === 'passed',
|
|
95
|
+
missing: Array.isArray(gate.missing) ? gate.missing : Array.isArray(gate.blockers) ? gate.blockers : [],
|
|
96
|
+
source: `.sneakoscope/missions/${missionId}/${file}`
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
return rows;
|
|
100
|
+
}
|
|
101
|
+
async function tailJsonl(file, limit) {
|
|
102
|
+
const text = await readText(file, '').catch(() => '');
|
|
103
|
+
return String(text || '').split(/\r?\n/).filter(Boolean).slice(-limit).map((line) => {
|
|
104
|
+
try {
|
|
105
|
+
return JSON.parse(line);
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
return { ts: null, type: 'raw', text: line.slice(0, 500) };
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
async function listenLocal(server, startPort) {
|
|
113
|
+
for (let offset = 0; offset < 20; offset += 1) {
|
|
114
|
+
const port = startPort + offset;
|
|
115
|
+
const result = await new Promise((resolve) => {
|
|
116
|
+
const onError = (err) => {
|
|
117
|
+
server.off('listening', onListen);
|
|
118
|
+
resolve({ ok: false, code: err.code || 'listen_failed' });
|
|
119
|
+
};
|
|
120
|
+
const onListen = () => {
|
|
121
|
+
server.off('error', onError);
|
|
122
|
+
resolve({ ok: true });
|
|
123
|
+
};
|
|
124
|
+
server.once('error', onError);
|
|
125
|
+
server.once('listening', onListen);
|
|
126
|
+
server.listen(port, '127.0.0.1');
|
|
127
|
+
});
|
|
128
|
+
if (result.ok)
|
|
129
|
+
return port;
|
|
130
|
+
if (result.code !== 'EADDRINUSE')
|
|
131
|
+
throw new Error(`dashboard listen failed: ${result.code}`);
|
|
132
|
+
}
|
|
133
|
+
throw new Error('dashboard listen failed: no available localhost port');
|
|
134
|
+
}
|
|
135
|
+
function sanitizeForUi(value) {
|
|
136
|
+
if (Array.isArray(value))
|
|
137
|
+
return value.map((item) => sanitizeForUi(item));
|
|
138
|
+
if (!value || typeof value !== 'object')
|
|
139
|
+
return typeof value === 'string' ? sanitizeText(value) : value;
|
|
140
|
+
const out = {};
|
|
141
|
+
for (const [key, child] of Object.entries(value)) {
|
|
142
|
+
if (/(?:key|token|secret|password|credential|cookie|auth)/i.test(key))
|
|
143
|
+
continue;
|
|
144
|
+
out[key] = sanitizeForUi(child);
|
|
145
|
+
}
|
|
146
|
+
return out;
|
|
147
|
+
}
|
|
148
|
+
function sanitizeText(value) {
|
|
149
|
+
return String(value || '')
|
|
150
|
+
.replace(/sk-[A-Za-z0-9_-]{12,}/g, '[redacted]')
|
|
151
|
+
.replace(/(?:api[_-]?key|secret|token|password|credential)/ig, '[redacted-field]')
|
|
152
|
+
.slice(0, 2000);
|
|
153
|
+
}
|
|
154
|
+
function readOption(args = [], name, fallback = null) {
|
|
155
|
+
const index = args.indexOf(name);
|
|
156
|
+
if (index >= 0 && args[index + 1] && !String(args[index + 1]).startsWith('--'))
|
|
157
|
+
return args[index + 1];
|
|
158
|
+
const prefixed = args.find((arg) => String(arg).startsWith(`${name}=`));
|
|
159
|
+
return prefixed ? prefixed.slice(name.length + 1) : fallback;
|
|
160
|
+
}
|
|
161
|
+
//# sourceMappingURL=ui-command.js.map
|
|
@@ -4,10 +4,12 @@ import fsp from 'node:fs/promises';
|
|
|
4
4
|
import readline from 'node:readline/promises';
|
|
5
5
|
import { stdin as input, stdout as output } from 'node:process';
|
|
6
6
|
import { ensureDir, exists, readJson, readText, runProcess, writeJsonAtomic } from '../fsx.js';
|
|
7
|
+
import { ui as cliUi } from '../../cli/cli-theme.js';
|
|
7
8
|
import { uninstallSksMenuBar } from '../codex-app/sks-menubar.js';
|
|
8
9
|
import { writeCodexConfigGuarded } from '../codex/codex-config-guard.js';
|
|
9
10
|
import { sweepSksTempDirs } from '../retention.js';
|
|
10
11
|
import { reconcileSkills } from '../init/skills.js';
|
|
12
|
+
import { removeTriwikiAgentsMdBlocks } from '../triwiki/agents-md-projector.js';
|
|
11
13
|
export async function uninstallCommand(args = []) {
|
|
12
14
|
const opts = {
|
|
13
15
|
yes: hasFlag(args, '--yes') || hasFlag(args, '-y'),
|
|
@@ -24,8 +26,11 @@ export async function uninstallCommand(args = []) {
|
|
|
24
26
|
const result = { schema: 'sks.uninstall.v1', ok: true, dry_run: true, inventory };
|
|
25
27
|
if (opts.json)
|
|
26
28
|
console.log(JSON.stringify(result, null, 2));
|
|
27
|
-
else
|
|
29
|
+
else {
|
|
30
|
+
cliUi.banner('uninstall');
|
|
31
|
+
cliUi.warn('dry run only');
|
|
28
32
|
printInventoryTable(inventory);
|
|
33
|
+
}
|
|
29
34
|
return result;
|
|
30
35
|
}
|
|
31
36
|
if (!opts.yes && !(await confirmUninstall())) {
|
|
@@ -66,6 +71,8 @@ export async function uninstallCommand(args = []) {
|
|
|
66
71
|
if (opts.json)
|
|
67
72
|
console.log(JSON.stringify(report, null, 2));
|
|
68
73
|
else {
|
|
74
|
+
cliUi.banner('uninstall');
|
|
75
|
+
report.ok ? cliUi.ok('removed configured SKS surfaces') : cliUi.fail('uninstall completed with errors');
|
|
69
76
|
console.log('SKS uninstall complete.');
|
|
70
77
|
console.log('Final package removal command: npm uninstall -g sneakoscope');
|
|
71
78
|
}
|
|
@@ -84,6 +91,7 @@ async function collectSksInventory(opts) {
|
|
|
84
91
|
['hooks-json', path.join(home, '.codex', 'hooks.json'), 'strip SKS-managed hooks', '--keep-config'],
|
|
85
92
|
['sks-home', path.join(home, '.sneakoscope'), 'remove', '--keep-data'],
|
|
86
93
|
['sks-global-home', path.join(home, '.sneakoscope-global'), 'remove', '--keep-data'],
|
|
94
|
+
['agents-md-memory-block', path.join(opts.root, 'AGENTS.md'), 'strip SKS Project Memory block', '--purge-projects'],
|
|
87
95
|
['tmp', os.tmpdir(), 'sweep sks temp dirs', 'default']
|
|
88
96
|
];
|
|
89
97
|
const out = [];
|
|
@@ -263,6 +271,12 @@ async function purgeProjectArtifacts(report, opts) {
|
|
|
263
271
|
report.errors.push({ id: 'project-skills', root, error: err?.message || String(err) });
|
|
264
272
|
});
|
|
265
273
|
await fsp.rm(path.join(root, '.codex'), { recursive: true, force: true }).catch(() => null);
|
|
274
|
+
const removedBlocks = await removeTriwikiAgentsMdBlocks(root).catch((err) => {
|
|
275
|
+
report.errors.push({ id: 'agents-md-memory-block', root, error: err?.message || String(err) });
|
|
276
|
+
return [];
|
|
277
|
+
});
|
|
278
|
+
if (removedBlocks.length)
|
|
279
|
+
report.removed.push({ id: 'agents-md-memory-block', root, result: { ok: true, files: removedBlocks.length } });
|
|
266
280
|
await fsp.rm(path.join(root, 'SNEAKOSCOPE.md'), { force: true }).catch(() => null);
|
|
267
281
|
if (!opts.keepData)
|
|
268
282
|
await fsp.rm(path.join(root, '.sneakoscope'), { recursive: true, force: true }).catch(() => null);
|