sneakoscope 0.9.17 → 0.9.19
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 +12 -7
- 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/package.json +18 -5
- package/src/core/commands/command-utils.mjs +1 -1
- package/src/core/commands/ppt-command.mjs +1 -1
- package/src/core/commands/scouts-command.mjs +93 -7
- package/src/core/feature-fixture-runner.mjs +42 -14
- package/src/core/feature-fixtures.mjs +85 -7
- package/src/core/feature-registry.mjs +27 -7
- package/src/core/fsx.mjs +1 -1
- package/src/core/pipeline/active-context.mjs +3 -0
- package/src/core/pipeline/pipeline-plan-writer.mjs +8 -0
- package/src/core/pipeline/plan-schema.mjs +4 -0
- package/src/core/pipeline/prompt-context.mjs +6 -0
- package/src/core/pipeline/route-prep.mjs +3 -0
- package/src/core/pipeline/scout-stage-policy.mjs +4 -0
- package/src/core/pipeline/stage-policy.mjs +4 -0
- package/src/core/pipeline/stop-gate.mjs +10 -0
- package/src/core/pipeline/validation.mjs +3 -0
- package/src/core/pipeline-internals/runtime-core.mjs +1693 -0
- package/src/core/pipeline-runtime.mjs +22 -0
- package/src/core/pipeline.mjs +34 -1693
- package/src/core/scouts/engines/codex-app-subagent-engine.mjs +70 -0
- package/src/core/scouts/engines/codex-exec-parallel-engine.mjs +66 -0
- package/src/core/scouts/engines/local-static-engine.mjs +10 -0
- package/src/core/scouts/engines/scout-engine-base.mjs +122 -0
- package/src/core/scouts/engines/scout-engine-detect.mjs +67 -0
- package/src/core/scouts/engines/scout-engine-policy.mjs +76 -0
- package/src/core/scouts/engines/sequential-fallback-engine.mjs +10 -0
- package/src/core/scouts/engines/tmux-lane-cleanup.mjs +16 -0
- package/src/core/scouts/engines/tmux-lane-engine.mjs +76 -0
- package/src/core/scouts/engines/tmux-lane-watcher.mjs +26 -0
- package/src/core/scouts/scout-consensus.mjs +31 -0
- package/src/core/scouts/scout-output-fixtures.mjs +27 -0
- package/src/core/scouts/scout-output-normalizer.mjs +4 -0
- package/src/core/scouts/scout-output-parser.mjs +266 -0
- package/src/core/scouts/scout-output-validator.mjs +3 -0
- package/src/core/scouts/scout-proof-evidence.mjs +10 -0
- package/src/core/scouts/scout-readonly-guard.mjs +101 -0
- package/src/core/scouts/scout-runner.mjs +262 -30
- package/src/core/scouts/scout-schema.mjs +4 -2
- package/src/core/version.mjs +1 -1
package/README.md
CHANGED
|
@@ -2,20 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
Fast legacy-free proof-first Codex trust layer with image-based Voxel TriWiki.
|
|
4
4
|
|
|
5
|
-
Sneakoscope Codex (`sks`) is a Codex CLI/App harness that makes repeatable Codex work auditable. `0.9.
|
|
5
|
+
Sneakoscope Codex (`sks`) is a Codex CLI/App harness that makes repeatable Codex work auditable. `0.9.19` binds real Scout engine output to structured `sks.scout-result.v1` evidence, keeps `pipeline-runtime.mjs` as a small compatibility facade, and release-gates packed npm/npx/global install behavior with explicit feature quality boundaries.
|
|
6
6
|
|
|
7
7
|

|
|
8
8
|
|
|
9
|
-
## 0.9.
|
|
9
|
+
## 0.9.19 Current Release
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
0.9.19 makes SKS Scout evidence parse-bound and package-install verified. Real Codex/tmux/Codex App subagent engines must write parseable scout output before consensus can use them as primary evidence. If an engine is unavailable or output cannot be parsed, SKS records a blocked or verified-partial result instead of substituting static evidence. Packed package checks now cover temp install, npx one-shot, and global shim behavior.
|
|
12
12
|
|
|
13
13
|
Highlights:
|
|
14
14
|
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
15
|
+
- Real Scout outputs are parsed from pure JSON, fenced JSON, `SCOUT_RESULT_JSON:` markdown, or final JSON blocks into `sks.scout-result.v1`.
|
|
16
|
+
- `scout-consensus.json` records whether primary evidence came from parsed real outputs or local static fixtures.
|
|
17
|
+
- `tmux-lanes` has an opt-in session/window/watcher/cleanup path; release gates skip or block honestly when live tmux/Codex is unavailable.
|
|
18
|
+
- Codex App subagents require a local `sks.codex-app-subagents-capability.v1` descriptor; `SKS_CODEX_APP_SUBAGENTS=1` alone is not enough.
|
|
19
|
+
- `npm run release:check` includes `pipeline-runtime:check`, `feature-quality:check`, `scouts:parser-check`, and `blackbox:check`.
|
|
20
|
+
- Feature fixtures report `runtime_verified`, `runtime_mock_verified`, `integration_optional`, `static_contract`, and `missing` counts.
|
|
19
21
|
- `sks rust status|smoke --json` reports optional Rust availability, detects stale native binaries, and proves JS fallback parity when native Rust is missing or version-mismatched.
|
|
20
22
|
- `npm run release:check` includes `route-modularity:check`, `command-budget:check`, and `feature-fixtures:strict`.
|
|
21
23
|
|
|
@@ -36,6 +38,9 @@ Learn more:
|
|
|
36
38
|
- Image Voxel TriWiki: [docs/image-voxel-ledger.md](docs/image-voxel-ledger.md)
|
|
37
39
|
- Route finalization: [docs/route-finalization.md](docs/route-finalization.md)
|
|
38
40
|
- Feature fixtures: [docs/feature-fixtures.md](docs/feature-fixtures.md)
|
|
41
|
+
- Scout engines: [docs/scout-engines.md](docs/scout-engines.md)
|
|
42
|
+
- Hermetic E2E: [docs/testing-hermetic-e2e.md](docs/testing-hermetic-e2e.md)
|
|
43
|
+
- Pipeline architecture: [docs/pipeline-architecture.md](docs/pipeline-architecture.md)
|
|
39
44
|
- Rust accelerator: [docs/rust-accelerator.md](docs/rust-accelerator.md)
|
|
40
45
|
- Codex App Hooks/PAT: [docs/hooks-pat.md](docs/hooks-pat.md)
|
|
41
46
|
- codex-lb: [docs/codex-lb.md](docs/codex-lb.md)
|
|
@@ -4,7 +4,7 @@ use std::io::{self, Read, Seek, SeekFrom};
|
|
|
4
4
|
fn main() {
|
|
5
5
|
let mut args = std::env::args().skip(1);
|
|
6
6
|
match args.next().as_deref() {
|
|
7
|
-
Some("--version") => println!("sks-rs 0.9.
|
|
7
|
+
Some("--version") => println!("sks-rs 0.9.19"),
|
|
8
8
|
Some("compact-info") => {
|
|
9
9
|
let mut input = String::new();
|
|
10
10
|
let _ = io::stdin().read_to_string(&mut input);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sneakoscope",
|
|
3
3
|
"displayName": "ㅅㅋㅅ",
|
|
4
|
-
"version": "0.9.
|
|
4
|
+
"version": "0.9.19",
|
|
5
5
|
"description": "Sneakoscope Codex: fast proof-first Codex trust layer with image-based Voxel TriWiki.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"homepage": "https://github.com/mandarange/Sneakoscope-Codex#readme",
|
|
@@ -23,6 +23,9 @@
|
|
|
23
23
|
"files": [
|
|
24
24
|
"bin",
|
|
25
25
|
"src",
|
|
26
|
+
"!src/core/pipeline/route-prep-*.mjs",
|
|
27
|
+
"!src/core/pipeline/prompt-context-*.mjs",
|
|
28
|
+
"!src/core/pipeline/stop-gate-*.mjs",
|
|
26
29
|
"crates/sks-core/Cargo.lock",
|
|
27
30
|
"crates/sks-core/Cargo.toml",
|
|
28
31
|
"crates/sks-core/src",
|
|
@@ -43,24 +46,34 @@
|
|
|
43
46
|
"legacy-free:check": "node ./scripts/check-legacy-free.mjs",
|
|
44
47
|
"route-modularity:check": "node ./scripts/check-route-modularity.mjs",
|
|
45
48
|
"command-budget:check": "node ./scripts/check-command-module-budget.mjs",
|
|
49
|
+
"pipeline-budget:check": "node ./scripts/check-pipeline-budget.mjs",
|
|
50
|
+
"pipeline-runtime:check": "node ./scripts/check-pipeline-runtime.mjs",
|
|
46
51
|
"sizecheck": "node ./scripts/sizecheck.mjs",
|
|
47
52
|
"registry:check": "node ./scripts/release-registry-check.mjs",
|
|
48
53
|
"feature:check": "node ./bin/sks.mjs features check --json",
|
|
54
|
+
"feature-quality:check": "node ./scripts/check-feature-quality.mjs",
|
|
49
55
|
"all-features:selftest": "node ./bin/sks.mjs all-features selftest --mock --json",
|
|
50
56
|
"all-features:execute-fixtures": "node ./bin/sks.mjs all-features selftest --mock --execute-fixtures --strict-artifacts --json",
|
|
51
|
-
"feature-fixtures:strict": "node ./bin/sks.mjs all-features selftest --mock --execute-fixtures --strict-artifacts --json",
|
|
52
|
-
"
|
|
53
|
-
"scouts:check": "node
|
|
57
|
+
"feature-fixtures:strict": "node ./bin/sks.mjs all-features selftest --mock --execute-fixtures --strict-artifacts --hermetic --json",
|
|
58
|
+
"scout-engines:check": "node ./bin/sks.mjs scouts engines --json",
|
|
59
|
+
"scouts:parser-check": "node --test \"test/unit/scout-output-parser.test.mjs\"",
|
|
60
|
+
"scouts:selftest": "node ./bin/sks.mjs scouts run latest --engine local-static --mock --json",
|
|
61
|
+
"scouts:check": "node ./bin/sks.mjs scouts validate latest --strict --json",
|
|
54
62
|
"perf:cold-start": "node ./bin/sks.mjs perf cold-start --json",
|
|
55
63
|
"perf:gate": "node ./scripts/perf-gate.mjs",
|
|
56
64
|
"test": "node --test \"test/**/*.test.mjs\"",
|
|
57
65
|
"test:unit": "node --test \"test/unit/**/*.test.mjs\"",
|
|
58
66
|
"test:integration:mock": "node --test \"test/integration/**/*.test.mjs\"",
|
|
59
67
|
"test:e2e:mock": "node --test \"test/e2e/**/*.test.mjs\"",
|
|
68
|
+
"test:real-scouts": "node --test \"test/real/**/*.test.mjs\"",
|
|
69
|
+
"blackbox:pack-install": "node ./scripts/blackbox-pack-install.mjs",
|
|
70
|
+
"blackbox:npx": "node ./scripts/blackbox-npx-one-shot.mjs",
|
|
71
|
+
"blackbox:global-shim": "node ./scripts/blackbox-global-shim.mjs",
|
|
72
|
+
"blackbox:check": "npm run blackbox:pack-install && npm run blackbox:npx && npm run blackbox:global-shim",
|
|
60
73
|
"rust:check": "cargo check --manifest-path crates/sks-core/Cargo.toml",
|
|
61
74
|
"rust:smoke": "node ./scripts/rust-smoke.mjs",
|
|
62
75
|
"coverage": "node --experimental-test-coverage --test \"test/**/*.test.mjs\"",
|
|
63
|
-
"release:check": "npm run repo-audit && npm run changelog:check && npm run cli-entrypoint:check && npm run legacy-free:check && npm run
|
|
76
|
+
"release:check": "npm run repo-audit && npm run changelog:check && npm run cli-entrypoint:check && npm run legacy-free:check && npm run route-modularity:check && npm run command-budget:check && npm run pipeline-budget:check && npm run pipeline-runtime:check && npm run packcheck && npm run feature:check && npm run feature-quality:check && npm run all-features:selftest && npm run scout-engines:check && npm run scouts:parser-check && npm run scouts:selftest && npm run scouts:check && npm run feature-fixtures:strict && npm run selftest && npm run test:unit && npm run test:integration:mock && npm run test:e2e:mock && npm run rust:check && npm run rust:smoke && npm run perf:gate && npm run blackbox:check && npm run sizecheck && npm run registry:check",
|
|
64
77
|
"publish:dry": "npm run release:check && npm --cache /tmp/sks-npm-cache publish --dry-run --registry https://registry.npmjs.org/ --access public",
|
|
65
78
|
"publish:npm": "npm --cache /tmp/sks-npm-cache publish --registry https://registry.npmjs.org/ --access public",
|
|
66
79
|
"prepublishOnly": "npm run release:check && node ./scripts/release-registry-check.mjs --require-unpublished"
|
|
@@ -30,7 +30,7 @@ export function positionalArgs(args = []) {
|
|
|
30
30
|
'--lines', '--intent', '--changed', '--route', '--skills', '--prompt-signature',
|
|
31
31
|
'--mission-id', '--source', '--image-id', '--bbox', '--label', '--evidence',
|
|
32
32
|
'--claim-id', '--type', '--before', '--after', '--anchors', '--verification',
|
|
33
|
-
'--status', '--scouts'
|
|
33
|
+
'--status', '--scouts', '--engine'
|
|
34
34
|
]);
|
|
35
35
|
for (let i = 0; i < args.length; i += 1) {
|
|
36
36
|
const arg = String(args[i]);
|
|
@@ -81,7 +81,7 @@ function mockPptFixtureGate(gate = {}) {
|
|
|
81
81
|
|
|
82
82
|
function fixtureAnswers() {
|
|
83
83
|
return {
|
|
84
|
-
PRESENTATION_AUDIENCE_PROFILE: 'Release reviewer validating SKS 0.9.
|
|
84
|
+
PRESENTATION_AUDIENCE_PROFILE: 'Release reviewer validating SKS 0.9.18 route evidence.',
|
|
85
85
|
PRESENTATION_STP_STRATEGY: 'Segment: developer tooling. Target: Codex trust-layer maintainers. Positioning: proof-first release readiness.',
|
|
86
86
|
PRESENTATION_DELIVERY_CONTEXT: 'Mock release gate fixture.',
|
|
87
87
|
PRESENTATION_PAINPOINT_SOLUTION_MAP: [
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
-
import { exists, projectRoot, readJson, writeJsonAtomic } from '../fsx.mjs';
|
|
2
|
+
import { ensureDir, exists, projectRoot, readJson, writeJsonAtomic } from '../fsx.mjs';
|
|
3
3
|
import { createMission, loadMission, missionDir, setCurrent, stateFile } from '../mission.mjs';
|
|
4
4
|
import { routePrompt } from '../routes.mjs';
|
|
5
5
|
import { buildScoutTeamPlan, normalizeScoutPolicy, routeRequiresScoutIntake, scoutRouteLabel } from '../scouts/scout-plan.mjs';
|
|
6
6
|
import { readScoutGateStatus, readScoutResults } from '../scouts/scout-gate.mjs';
|
|
7
7
|
import { runFiveScoutIntake } from '../scouts/scout-runner.mjs';
|
|
8
8
|
import { readScoutProofEvidence } from '../scouts/scout-proof-evidence.mjs';
|
|
9
|
+
import { detectScoutEngines } from '../scouts/engines/scout-engine-detect.mjs';
|
|
10
|
+
import { selectScoutEngine } from '../scouts/engines/scout-engine-policy.mjs';
|
|
9
11
|
import { SCOUT_COUNT } from '../scouts/scout-schema.mjs';
|
|
10
12
|
import { flag, readFlagValue, resolveMissionId } from './command-utils.mjs';
|
|
11
13
|
|
|
12
|
-
const ACTIONS = new Set(['plan', 'run', 'status', 'consensus', 'handoff', 'validate', 'help', '--help', '-h']);
|
|
14
|
+
const ACTIONS = new Set(['plan', 'run', 'status', 'consensus', 'handoff', 'validate', 'engines', 'bench', 'help', '--help', '-h']);
|
|
13
15
|
|
|
14
16
|
export async function scoutsCommand(args = []) {
|
|
15
17
|
const root = await projectRoot();
|
|
@@ -18,10 +20,19 @@ export async function scoutsCommand(args = []) {
|
|
|
18
20
|
if (action === 'help' || action === '--help' || action === '-h') return scoutsHelp();
|
|
19
21
|
const json = flag(actionArgs, '--json');
|
|
20
22
|
const mock = flag(actionArgs, '--mock');
|
|
23
|
+
const strict = flag(actionArgs, '--strict');
|
|
24
|
+
const requestedEngine = readFlagValue(actionArgs, '--engine', 'auto');
|
|
25
|
+
const requireRealParallel = flag(actionArgs, '--require-real-parallel');
|
|
21
26
|
const force = flag(actionArgs, '--force-scouts') || flag(actionArgs, '--force');
|
|
22
27
|
const noScouts = flag(actionArgs, '--no-scouts');
|
|
23
28
|
const missionArg = actionArgs.find((arg) => !String(arg).startsWith('--')) || 'latest';
|
|
24
|
-
|
|
29
|
+
if (action === 'engines') {
|
|
30
|
+
const result = await detectScoutEngines(root, {});
|
|
31
|
+
if (json) return console.log(JSON.stringify(result, null, 2));
|
|
32
|
+
for (const engine of result.engines) console.log(`${engine.name}: ${engine.available ? 'available' : 'blocked'}${engine.reason ? ` (${engine.reason})` : ''}`);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const { id, dir, mission, created } = await resolveOrCreateScoutMission(root, missionArg, { mock, action, strict });
|
|
25
36
|
const context = await inferScoutContext(root, id, { route: readFlagValue(actionArgs, '--route', null), task: readFlagValue(actionArgs, '--task', null) });
|
|
26
37
|
if (action === 'plan') {
|
|
27
38
|
const parallelMode = flag(actionArgs, '--sequential') ? 'sequential_fallback' : 'parallel';
|
|
@@ -51,6 +62,8 @@ export async function scoutsCommand(args = []) {
|
|
|
51
62
|
task: context.task,
|
|
52
63
|
mode: mock ? 'mock' : 'manual',
|
|
53
64
|
parallel: !flag(actionArgs, '--sequential'),
|
|
65
|
+
engine: requestedEngine,
|
|
66
|
+
requireRealParallel,
|
|
54
67
|
mock
|
|
55
68
|
});
|
|
56
69
|
await setCurrent(root, {
|
|
@@ -75,6 +88,8 @@ export async function scoutsCommand(args = []) {
|
|
|
75
88
|
route: context.route,
|
|
76
89
|
scout_count: SCOUT_COUNT,
|
|
77
90
|
completed_scouts: results.filter((row) => row.status === 'done').length,
|
|
91
|
+
engine: gate.gate?.engine || null,
|
|
92
|
+
real_parallel: gate.gate?.real_parallel === true,
|
|
78
93
|
gate: gate.gate,
|
|
79
94
|
missing: gate.missing
|
|
80
95
|
};
|
|
@@ -112,13 +127,14 @@ export async function scoutsCommand(args = []) {
|
|
|
112
127
|
}
|
|
113
128
|
if (action === 'validate') {
|
|
114
129
|
let gate = await readScoutGateStatus(root, id);
|
|
115
|
-
if (!gate.ok && !
|
|
130
|
+
if (!gate.ok && !strict) {
|
|
116
131
|
const run = await runFiveScoutIntake(root, {
|
|
117
132
|
missionId: id,
|
|
118
133
|
route: context.route,
|
|
119
134
|
task: context.task,
|
|
120
135
|
mode: 'validate-fixture',
|
|
121
136
|
parallel: true,
|
|
137
|
+
engine: 'local-static',
|
|
122
138
|
mock: true
|
|
123
139
|
});
|
|
124
140
|
gate = { ok: run.gate?.passed === true, gate: run.gate, missing: run.gate?.blockers || [] };
|
|
@@ -136,13 +152,79 @@ export async function scoutsCommand(args = []) {
|
|
|
136
152
|
console.log(`Scout validation: ${result.ok ? 'pass' : 'blocked'}`);
|
|
137
153
|
return;
|
|
138
154
|
}
|
|
155
|
+
if (action === 'bench') {
|
|
156
|
+
const selection = await selectScoutEngine(root, {
|
|
157
|
+
requested: requestedEngine,
|
|
158
|
+
requireRealParallel,
|
|
159
|
+
missionId: id,
|
|
160
|
+
route: context.route,
|
|
161
|
+
mock
|
|
162
|
+
});
|
|
163
|
+
const parallelRun = await runFiveScoutIntake(root, {
|
|
164
|
+
missionId: id,
|
|
165
|
+
route: context.route,
|
|
166
|
+
task: context.task,
|
|
167
|
+
mode: 'bench-parallel',
|
|
168
|
+
parallel: true,
|
|
169
|
+
engine: selection.selected,
|
|
170
|
+
requireRealParallel,
|
|
171
|
+
mock
|
|
172
|
+
});
|
|
173
|
+
const sequentialRun = await runFiveScoutIntake(root, {
|
|
174
|
+
missionId: id,
|
|
175
|
+
route: context.route,
|
|
176
|
+
task: context.task,
|
|
177
|
+
mode: 'bench-sequential',
|
|
178
|
+
parallel: false,
|
|
179
|
+
engine: 'sequential-fallback',
|
|
180
|
+
mock: true
|
|
181
|
+
});
|
|
182
|
+
const sequentialMs = Number(sequentialRun.performance?.duration_ms || 0);
|
|
183
|
+
const parallelMs = Number(parallelRun.performance?.duration_ms || 0);
|
|
184
|
+
const parsedRealOutputs = Number(parallelRun.consensus?.source_policy?.counts?.parsed_scout_output || 0);
|
|
185
|
+
const speedup = selection.real_parallel && parallelMs > 0 ? Number((sequentialMs / parallelMs).toFixed(2)) : null;
|
|
186
|
+
const claimAllowed = selection.real_parallel === true
|
|
187
|
+
&& parsedRealOutputs === SCOUT_COUNT
|
|
188
|
+
&& parallelRun.performance?.claim_allowed === true
|
|
189
|
+
&& speedup > 1.1
|
|
190
|
+
&& parallelRun.gate?.read_only_guard === true
|
|
191
|
+
&& !parallelRun.gate?.blockers?.length;
|
|
192
|
+
const result = {
|
|
193
|
+
schema: 'sks.scout-benchmark.v2',
|
|
194
|
+
mission_id: id,
|
|
195
|
+
engine: selection.selected,
|
|
196
|
+
real_parallel: selection.real_parallel === true,
|
|
197
|
+
parsed_real_outputs: parsedRealOutputs,
|
|
198
|
+
sequential_ms: sequentialMs,
|
|
199
|
+
parallel_ms: parallelMs,
|
|
200
|
+
speedup,
|
|
201
|
+
claim_allowed: claimAllowed,
|
|
202
|
+
confidence: selection.real_parallel ? 'medium' : 'low',
|
|
203
|
+
read_only_guard: parallelRun.gate?.read_only_guard === true ? 'passed' : 'blocked',
|
|
204
|
+
notes: selection.real_parallel ? [] : ['mock/static benchmarks cannot claim real speedup']
|
|
205
|
+
};
|
|
206
|
+
await writeJsonAtomic(path.join(dir, 'scout-benchmark.json'), result);
|
|
207
|
+
const reportDir = path.join(root, '.sneakoscope', 'reports');
|
|
208
|
+
await ensureDir(reportDir);
|
|
209
|
+
await writeJsonAtomic(path.join(reportDir, 'scout-benchmark-summary.json'), {
|
|
210
|
+
schema: 'sks.scout-benchmark-summary.v1',
|
|
211
|
+
updated_at: new Date().toISOString(),
|
|
212
|
+
latest: result
|
|
213
|
+
});
|
|
214
|
+
if (json) return console.log(JSON.stringify(result, null, 2));
|
|
215
|
+
console.log(`Scout benchmark: ${result.claim_allowed ? 'claim allowed' : 'claim not allowed'}`);
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
139
218
|
}
|
|
140
219
|
|
|
141
220
|
async function resolveOrCreateScoutMission(root, missionArg, opts = {}) {
|
|
142
221
|
const resolved = await resolveMissionId(root, missionArg);
|
|
143
222
|
if (resolved) return { id: resolved, ...(await loadMission(root, resolved)), created: false };
|
|
223
|
+
if (opts.strict) {
|
|
224
|
+
throw new Error('No mission found for strict scout validation; strict mode never creates scout artifacts.');
|
|
225
|
+
}
|
|
144
226
|
if (!opts.mock && opts.action !== 'validate' && opts.action !== 'run' && opts.action !== 'plan') {
|
|
145
|
-
throw new Error('No mission found. Use sks scouts run latest --mock --json to create a fixture mission.');
|
|
227
|
+
throw new Error('No mission found. Use sks scouts run latest --engine local-static --mock --json to create a fixture mission.');
|
|
146
228
|
}
|
|
147
229
|
const created = await createMission(root, { mode: 'scouts', prompt: 'Five Scout fixture intake' });
|
|
148
230
|
return { id: created.id, dir: created.dir, mission: created.mission, created: true };
|
|
@@ -189,11 +271,15 @@ function scoutsHelp() {
|
|
|
189
271
|
|
|
190
272
|
Usage:
|
|
191
273
|
sks scouts plan latest --json
|
|
192
|
-
sks scouts run latest --
|
|
274
|
+
sks scouts run latest --engine auto --json
|
|
275
|
+
sks scouts run latest --engine local-static --mock --json
|
|
276
|
+
sks scouts run latest --require-real-parallel --json
|
|
193
277
|
sks scouts status latest --json
|
|
278
|
+
sks scouts engines --json
|
|
279
|
+
sks scouts bench latest --engine local-static --mock --json
|
|
194
280
|
sks scouts consensus latest --json
|
|
195
281
|
sks scouts handoff latest
|
|
196
|
-
sks scouts validate latest --json
|
|
282
|
+
sks scouts validate latest --strict --json
|
|
197
283
|
|
|
198
284
|
Alias:
|
|
199
285
|
sks scout run latest --json
|
|
@@ -12,12 +12,14 @@ export function runFeatureFixture(feature, {
|
|
|
12
12
|
} = {}) {
|
|
13
13
|
const fixture = feature.fixture || {};
|
|
14
14
|
const expected = normalizeExpectedArtifacts(fixture.expected_artifacts);
|
|
15
|
-
const
|
|
16
|
-
const
|
|
17
|
-
const
|
|
15
|
+
const useHermeticRoot = fixture.root_mode !== 'source_checkout_required' && (execute || validateArtifacts || fixture.kind === 'execute_and_validate_artifacts');
|
|
16
|
+
const projectRoot = useHermeticRoot ? prepareHermeticFixtureRoot(root, tempRoot) : root;
|
|
17
|
+
const latestBefore = latestMissionId(projectRoot);
|
|
18
|
+
const execution = execute && commandArgs ? executeCommand(root, projectRoot, commandArgs, fixture) : null;
|
|
19
|
+
const latestAfter = execution?.mission_id || latestMissionId(projectRoot) || latestBefore;
|
|
18
20
|
const shouldValidateArtifacts = validateArtifacts && (fixture.kind === 'execute_and_validate_artifacts' || execution);
|
|
19
21
|
const artifacts = shouldValidateArtifacts
|
|
20
|
-
? expected.map((artifact) => inspectExpectedArtifact(
|
|
22
|
+
? expected.map((artifact) => inspectExpectedArtifact(projectRoot, tempRoot, artifact, { latestMissionId: latestAfter }))
|
|
21
23
|
: expected.map((artifact) => ({ path: artifact.path, requested_path: artifact.path, schema: artifact.schema || inferSchema(artifact.path), exists: null, schema_ok: null, content_ok: null, skipped: 'contract_only' }));
|
|
22
24
|
const artifactFailures = shouldValidateArtifacts
|
|
23
25
|
? artifacts.filter((artifact) => !artifact.exists || !artifact.schema_ok || !artifact.content_ok).map((artifact) => `${feature.id}:${artifact.path}:${artifact.failure || 'artifact_invalid'}`)
|
|
@@ -26,17 +28,20 @@ export function runFeatureFixture(feature, {
|
|
|
26
28
|
id: feature.id,
|
|
27
29
|
kind: fixture.kind || 'static',
|
|
28
30
|
command: fixture.command || null,
|
|
29
|
-
temp_root: tempRoot,
|
|
31
|
+
temp_root: useHermeticRoot ? projectRoot : tempRoot,
|
|
32
|
+
root_mode: useHermeticRoot ? 'hermetic_temp_project' : 'source_checkout_required',
|
|
30
33
|
latest_mission_id: latestAfter,
|
|
31
34
|
executed: Boolean(execution),
|
|
32
35
|
execution,
|
|
33
36
|
expected_artifacts: artifacts,
|
|
34
37
|
artifact_schema_validated: validateArtifacts,
|
|
35
|
-
|
|
38
|
+
no_plaintext_secrets: validateNoPlaintextSecrets(projectRoot),
|
|
39
|
+
ok: (!execution || execution.ok) && artifactFailures.length === 0 && validateNoPlaintextSecrets(projectRoot) && !(validateArtifacts && fixture.kind === 'execute_and_validate_artifacts' && expected.length && !execution),
|
|
36
40
|
failures: [
|
|
37
41
|
...(!fixture.command && fixture.status === 'pass' ? [`${feature.id}:fixture_command_missing`] : []),
|
|
38
42
|
...(validateArtifacts && fixture.kind === 'execute_and_validate_artifacts' && expected.length && !execution ? [`${feature.id}:command_not_executed_for_artifact_validation`] : []),
|
|
39
43
|
...(execution && !execution.ok ? [`${feature.id}:command_exit_${execution.status}`] : []),
|
|
44
|
+
...(validateNoPlaintextSecrets(projectRoot) ? [] : [`${feature.id}:plaintext_secret_detected`]),
|
|
40
45
|
...artifactFailures
|
|
41
46
|
]
|
|
42
47
|
};
|
|
@@ -52,12 +57,15 @@ export function writeFeatureFixtureReports(root, report) {
|
|
|
52
57
|
return { json: jsonPath, md: mdPath };
|
|
53
58
|
}
|
|
54
59
|
|
|
55
|
-
function executeCommand(
|
|
60
|
+
function executeCommand(sourceRoot, projectRoot, spec, fixture = {}) {
|
|
56
61
|
const normalized = Array.isArray(spec) ? { command: spec } : spec;
|
|
57
|
-
const setup =
|
|
58
|
-
|
|
62
|
+
const setup = [
|
|
63
|
+
...(fixture.root_mode === 'source_checkout_required' ? [] : [['setup', '--local-only', '--json']]),
|
|
64
|
+
...(normalized.setup || [])
|
|
65
|
+
];
|
|
66
|
+
const setupResults = setup.map((args) => spawnSks(sourceRoot, projectRoot, args));
|
|
59
67
|
const command = normalized.command || normalized.args || [];
|
|
60
|
-
const result = command.length ? spawnSks(
|
|
68
|
+
const result = command.length ? spawnSks(sourceRoot, projectRoot, command) : { status: 0, signal: null, ok: true, stdout_bytes: 0, stderr_bytes: 0, args: [] };
|
|
61
69
|
const missionId = result.mission_id || [...setupResults].reverse().find((row) => row.mission_id)?.mission_id || null;
|
|
62
70
|
return {
|
|
63
71
|
args: command,
|
|
@@ -71,9 +79,9 @@ function executeCommand(root, spec) {
|
|
|
71
79
|
};
|
|
72
80
|
}
|
|
73
81
|
|
|
74
|
-
function spawnSks(
|
|
75
|
-
const result = spawnSync(process.execPath, [path.join(
|
|
76
|
-
cwd:
|
|
82
|
+
function spawnSks(sourceRoot, projectRoot, args = []) {
|
|
83
|
+
const result = spawnSync(process.execPath, [path.join(sourceRoot, 'bin', 'sks.mjs'), ...args], {
|
|
84
|
+
cwd: projectRoot,
|
|
77
85
|
encoding: 'utf8',
|
|
78
86
|
timeout: 30_000,
|
|
79
87
|
env: { ...process.env, CI: 'true', SKS_SKIP_NPM_FRESHNESS_CHECK: '1' }
|
|
@@ -91,6 +99,26 @@ function spawnSks(root, args = []) {
|
|
|
91
99
|
};
|
|
92
100
|
}
|
|
93
101
|
|
|
102
|
+
function prepareHermeticFixtureRoot(sourceRoot, tempRoot) {
|
|
103
|
+
fs.mkdirSync(tempRoot, { recursive: true });
|
|
104
|
+
const packageFile = path.join(tempRoot, 'package.json');
|
|
105
|
+
if (!fs.existsSync(packageFile)) {
|
|
106
|
+
fs.writeFileSync(packageFile, `${JSON.stringify({ name: 'sks-hermetic-fixture', private: true, version: '0.0.0' }, null, 2)}\n`);
|
|
107
|
+
}
|
|
108
|
+
const readme = path.join(tempRoot, 'README.md');
|
|
109
|
+
if (!fs.existsSync(readme)) fs.writeFileSync(readme, '# SKS Hermetic Fixture\n');
|
|
110
|
+
copyFixtureFile(sourceRoot, tempRoot, 'test/fixtures/images/one-by-one.png');
|
|
111
|
+
return tempRoot;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function copyFixtureFile(sourceRoot, tempRoot, rel) {
|
|
115
|
+
const src = path.join(sourceRoot, rel);
|
|
116
|
+
const dest = path.join(tempRoot, rel);
|
|
117
|
+
if (!fs.existsSync(src) || fs.existsSync(dest)) return;
|
|
118
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
119
|
+
fs.copyFileSync(src, dest);
|
|
120
|
+
}
|
|
121
|
+
|
|
94
122
|
function parseJsonOutput(text = '') {
|
|
95
123
|
const trimmed = String(text || '').trim();
|
|
96
124
|
if (!trimmed) return null;
|
|
@@ -198,7 +226,7 @@ export function validateImageVoxelArtifact(file, { requireAnchors = true, requir
|
|
|
198
226
|
}
|
|
199
227
|
|
|
200
228
|
export function validateNoPlaintextSecrets(root) {
|
|
201
|
-
const secretPattern = /(sk-proj
|
|
229
|
+
const secretPattern = /(sk-proj-[A-Za-z0-9_-]{8,}|sk-clb-[A-Za-z0-9_-]{8,}|github_pat_[A-Za-z0-9_]{8,}|(?:CODEX_ACCESS_TOKEN|OPENAI_API_KEY)\s*[:=]\s*["']?(?:sk-[A-Za-z0-9_-]{8,}|[A-Za-z0-9_-]{32,}))/;
|
|
202
230
|
const reportDir = path.join(root, '.sneakoscope');
|
|
203
231
|
if (!fs.existsSync(reportDir)) return true;
|
|
204
232
|
const stack = [reportDir];
|
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
export const FEATURE_FIXTURE_SCHEMA = 'sks.feature-fixtures.v1';
|
|
2
|
+
export const FEATURE_QUALITY_LEVELS = Object.freeze([
|
|
3
|
+
'runtime_verified',
|
|
4
|
+
'runtime_mock_verified',
|
|
5
|
+
'integration_optional',
|
|
6
|
+
'static_contract',
|
|
7
|
+
'missing'
|
|
8
|
+
]);
|
|
2
9
|
|
|
3
10
|
const FIXTURES = Object.freeze({
|
|
4
11
|
'cli-help': fixture('static', 'sks help', [], 'pass'),
|
|
@@ -31,7 +38,7 @@ const FIXTURES = Object.freeze({
|
|
|
31
38
|
'cli-hproof': fixture('mock', 'sks hproof check latest', ['completion-proof.json'], 'pass'),
|
|
32
39
|
'cli-proof-field': fixture('static', 'sks proof-field scan --json --intent fixture', [], 'pass'),
|
|
33
40
|
'cli-recallpulse': fixture('mock', 'sks recallpulse status latest --json', ['recallpulse-report.json'], 'pass'),
|
|
34
|
-
'cli-scouts': fixture('execute_and_validate_artifacts', 'sks scouts run latest --mock --json', ['scout-team-plan.json', 'scout-consensus.json', 'scout-handoff.md', 'scout-gate.json'], 'pass'),
|
|
41
|
+
'cli-scouts': fixture('execute_and_validate_artifacts', 'sks scouts run latest --engine local-static --mock --json', ['scout-team-plan.json', 'scout-consensus.json', 'scout-handoff.md', 'scout-gate.json', 'scout-engine-result.json'], 'pass'),
|
|
35
42
|
'cli-scout': fixture('mock', 'sks scout status latest --json', ['scout-gate.json'], 'pass'),
|
|
36
43
|
'cli-gx': fixture('mock', 'sks gx validate fixture', ['gx-validation.json'], 'pass'),
|
|
37
44
|
'cli-perf': fixture('static', 'sks perf cold-start --json --iterations 1', [], 'pass'),
|
|
@@ -53,8 +60,8 @@ const FIXTURES = Object.freeze({
|
|
|
53
60
|
'route-image-ux-review': fixture('execute_and_validate_artifacts', 'sks image-ux-review fixture --mock --json', ['completion-proof.json', { path: 'image-voxel-ledger.json', schema: 'sks.image-voxel-ledger.v1' }, 'image-ux-generated-review-ledger.json'], 'pass'),
|
|
54
61
|
'route-computer-use': fixture('execute_and_validate_artifacts', 'sks computer-use import-fixture --mock --json', ['computer-use-evidence-ledger.json', { path: 'image-voxel-ledger.json', schema: 'sks.image-voxel-ledger.v1' }, 'completion-proof.json'], 'pass'),
|
|
55
62
|
'route-cu': fixture('mock', '$CU mock evidence ledger', ['computer-use-evidence-ledger.json', 'image-voxel-ledger.json', 'completion-proof.json'], 'pass'),
|
|
56
|
-
'route-dfix': fixture('
|
|
57
|
-
'route-answer': fixture('
|
|
63
|
+
'route-dfix': fixture('mock', '$DFix tiny edit route policy', ['completion-proof.json'], 'pass'),
|
|
64
|
+
'route-answer': fixture('mock', '$Answer answer-only route policy', [], 'pass'),
|
|
58
65
|
'route-goal': fixture('mock', '$Goal bridge route', ['goal-workflow.json', 'completion-proof.json'], 'pass'),
|
|
59
66
|
'route-autoresearch': fixture('mock', '$AutoResearch fixture route', ['research-gate.json', 'completion-proof.json'], 'pass'),
|
|
60
67
|
'route-mad-sks': fixture('mock', '$MAD-SKS permission gate route', ['mad-sks-gate.json', 'completion-proof.json'], 'pass'),
|
|
@@ -63,25 +70,75 @@ const FIXTURES = Object.freeze({
|
|
|
63
70
|
'route-db': fixture('execute_and_validate_artifacts', 'sks db check --sql "SELECT 1" --json', ['completion-proof.json', 'db-operation-report.json'], 'pass'),
|
|
64
71
|
'route-wiki': fixture('execute_and_validate_artifacts', 'sks wiki image-ingest test/fixtures/images/one-by-one.png --json', [{ path: 'completion-proof.json', schema: 'sks.completion-proof.v1' }, { path: 'image-voxel-ledger.json', schema: 'sks.image-voxel-ledger.v1' }], 'pass'),
|
|
65
72
|
'route-gx': fixture('execute_and_validate_artifacts', 'sks gx validate fixture --mock --json', ['completion-proof.json', { path: 'image-voxel-ledger.json', schema: 'sks.image-voxel-ledger.v1' }, 'gx-validation.json'], 'pass'),
|
|
66
|
-
'route-
|
|
73
|
+
'route-sks': fixture('mock', '$SKS control-surface route', ['completion-proof.json'], 'pass'),
|
|
74
|
+
'route-help': fixture('mock', '$Help lightweight route', [], 'pass'),
|
|
75
|
+
'route-commit': fixture('mock', '$Commit git route', ['completion-proof.json'], 'pass'),
|
|
76
|
+
'route-commit-and-push': fixture('mock', '$Commit-And-Push git route', ['completion-proof.json'], 'pass'),
|
|
77
|
+
'route-five-scout-intake': fixture('mock', 'sks scouts validate latest --strict --json', ['scout-team-plan.json', 'scout-consensus.json', 'scout-handoff.md', 'scout-gate.json'], 'pass'),
|
|
67
78
|
'proof-scout-evidence': fixture('mock', 'sks team "fixture" --mock --json', ['completion-proof.json', 'scout-gate.json'], 'pass')
|
|
68
79
|
});
|
|
69
80
|
|
|
81
|
+
const STATIC_CONTRACT_FEATURES = new Set([
|
|
82
|
+
'cli-wizard',
|
|
83
|
+
'cli-bootstrap',
|
|
84
|
+
'cli-deps',
|
|
85
|
+
'cli-auth',
|
|
86
|
+
'cli-openclaw',
|
|
87
|
+
'cli-tmux',
|
|
88
|
+
'cli-mad',
|
|
89
|
+
'cli-auto-review',
|
|
90
|
+
'cli-commit',
|
|
91
|
+
'cli-commit-and-push',
|
|
92
|
+
'cli-context7',
|
|
93
|
+
'cli-all-features',
|
|
94
|
+
'cli-eval',
|
|
95
|
+
'cli-harness',
|
|
96
|
+
'cli-team',
|
|
97
|
+
'cli-reasoning',
|
|
98
|
+
'cli-profile',
|
|
99
|
+
'handler-$',
|
|
100
|
+
'handler-autoresearch',
|
|
101
|
+
'handler-autoreview',
|
|
102
|
+
'handler-computer-use',
|
|
103
|
+
'handler-cu',
|
|
104
|
+
'handler-dollars',
|
|
105
|
+
'handler-mad-sks',
|
|
106
|
+
'handler-postinstall'
|
|
107
|
+
]);
|
|
108
|
+
|
|
70
109
|
export function fixtureForFeature(featureId) {
|
|
71
|
-
|
|
110
|
+
if (FIXTURES[featureId]) return FIXTURES[featureId];
|
|
111
|
+
if (STATIC_CONTRACT_FEATURES.has(featureId)) {
|
|
112
|
+
return fixture('static', `explicit static contract fixture: ${featureId}`, [], 'pass', {
|
|
113
|
+
quality: 'static_contract',
|
|
114
|
+
root_mode: 'source_checkout_required'
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
if (String(featureId || '').startsWith('skill-')) {
|
|
118
|
+
return fixture('static', `skill contract: ${featureId}`, [], 'pass', { quality: 'static_contract', root_mode: 'source_checkout_required' });
|
|
119
|
+
}
|
|
120
|
+
return fixture('not_available', null, [], 'missing', {
|
|
121
|
+
quality: 'missing',
|
|
122
|
+
fallback_removed: true,
|
|
123
|
+
reason: 'No explicit fixture registered for this feature.'
|
|
124
|
+
});
|
|
72
125
|
}
|
|
73
126
|
|
|
74
127
|
export function fixtureSummary(features = []) {
|
|
75
128
|
const counts = {};
|
|
129
|
+
const quality_counts = Object.fromEntries(FEATURE_QUALITY_LEVELS.map((level) => [level, 0]));
|
|
76
130
|
const missing = [];
|
|
77
131
|
for (const feature of features) {
|
|
78
132
|
const status = feature.fixture?.status || 'missing';
|
|
79
133
|
counts[status] = (counts[status] || 0) + 1;
|
|
134
|
+
const quality = feature.fixture?.quality || 'missing';
|
|
135
|
+
quality_counts[quality] = (quality_counts[quality] || 0) + 1;
|
|
80
136
|
if (!feature.fixture) missing.push(feature.id);
|
|
81
137
|
}
|
|
82
138
|
return {
|
|
83
139
|
schema: FEATURE_FIXTURE_SCHEMA,
|
|
84
140
|
counts,
|
|
141
|
+
quality_counts,
|
|
85
142
|
missing,
|
|
86
143
|
ok: missing.length === 0 && !counts.missing
|
|
87
144
|
};
|
|
@@ -96,6 +153,7 @@ export function validateFeatureFixtures(features = []) {
|
|
|
96
153
|
continue;
|
|
97
154
|
}
|
|
98
155
|
if (!['contract', 'execute', 'execute_and_validate_artifacts', 'mock', 'static', 'real_optional', 'not_available'].includes(fx.kind)) blockers.push(`${feature.id}:fixture_kind`);
|
|
156
|
+
if (!FEATURE_QUALITY_LEVELS.includes(fx.quality)) blockers.push(`${feature.id}:fixture_quality`);
|
|
99
157
|
if (!['pass', 'missing', 'blocked', 'not_required'].includes(fx.status)) blockers.push(`${feature.id}:fixture_status`);
|
|
100
158
|
if ((fx.kind === 'mock' || fx.kind === 'static') && !fx.command && fx.status !== 'not_required') blockers.push(`${feature.id}:fixture_command`);
|
|
101
159
|
if (!Array.isArray(fx.expected_artifacts)) blockers.push(`${feature.id}:fixture_expected_artifacts`);
|
|
@@ -103,6 +161,26 @@ export function validateFeatureFixtures(features = []) {
|
|
|
103
161
|
return { ok: blockers.length === 0, blockers };
|
|
104
162
|
}
|
|
105
163
|
|
|
106
|
-
function fixture(kind, command, expected_artifacts, status) {
|
|
107
|
-
|
|
164
|
+
function fixture(kind, command, expected_artifacts, status, extra = {}) {
|
|
165
|
+
const quality = extra.quality || qualityForKind(kind);
|
|
166
|
+
const rootMode = extra.root_mode || (kind === 'execute_and_validate_artifacts' || kind === 'execute' || kind === 'mock' ? 'hermetic_temp_project' : 'source_checkout_required');
|
|
167
|
+
return {
|
|
168
|
+
kind,
|
|
169
|
+
quality,
|
|
170
|
+
root_mode: rootMode,
|
|
171
|
+
command,
|
|
172
|
+
expected_artifacts,
|
|
173
|
+
status,
|
|
174
|
+
explicit: true,
|
|
175
|
+
fallback_removed: true,
|
|
176
|
+
...extra
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function qualityForKind(kind) {
|
|
181
|
+
if (kind === 'execute' || kind === 'execute_and_validate_artifacts') return 'runtime_verified';
|
|
182
|
+
if (kind === 'mock') return 'runtime_mock_verified';
|
|
183
|
+
if (kind === 'real_optional') return 'integration_optional';
|
|
184
|
+
if (kind === 'not_available') return 'missing';
|
|
185
|
+
return 'static_contract';
|
|
108
186
|
}
|