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.
Files changed (44) hide show
  1. package/README.md +12 -7
  2. package/crates/sks-core/Cargo.lock +1 -1
  3. package/crates/sks-core/Cargo.toml +1 -1
  4. package/crates/sks-core/src/main.rs +1 -1
  5. package/package.json +18 -5
  6. package/src/core/commands/command-utils.mjs +1 -1
  7. package/src/core/commands/ppt-command.mjs +1 -1
  8. package/src/core/commands/scouts-command.mjs +93 -7
  9. package/src/core/feature-fixture-runner.mjs +42 -14
  10. package/src/core/feature-fixtures.mjs +85 -7
  11. package/src/core/feature-registry.mjs +27 -7
  12. package/src/core/fsx.mjs +1 -1
  13. package/src/core/pipeline/active-context.mjs +3 -0
  14. package/src/core/pipeline/pipeline-plan-writer.mjs +8 -0
  15. package/src/core/pipeline/plan-schema.mjs +4 -0
  16. package/src/core/pipeline/prompt-context.mjs +6 -0
  17. package/src/core/pipeline/route-prep.mjs +3 -0
  18. package/src/core/pipeline/scout-stage-policy.mjs +4 -0
  19. package/src/core/pipeline/stage-policy.mjs +4 -0
  20. package/src/core/pipeline/stop-gate.mjs +10 -0
  21. package/src/core/pipeline/validation.mjs +3 -0
  22. package/src/core/pipeline-internals/runtime-core.mjs +1693 -0
  23. package/src/core/pipeline-runtime.mjs +22 -0
  24. package/src/core/pipeline.mjs +34 -1693
  25. package/src/core/scouts/engines/codex-app-subagent-engine.mjs +70 -0
  26. package/src/core/scouts/engines/codex-exec-parallel-engine.mjs +66 -0
  27. package/src/core/scouts/engines/local-static-engine.mjs +10 -0
  28. package/src/core/scouts/engines/scout-engine-base.mjs +122 -0
  29. package/src/core/scouts/engines/scout-engine-detect.mjs +67 -0
  30. package/src/core/scouts/engines/scout-engine-policy.mjs +76 -0
  31. package/src/core/scouts/engines/sequential-fallback-engine.mjs +10 -0
  32. package/src/core/scouts/engines/tmux-lane-cleanup.mjs +16 -0
  33. package/src/core/scouts/engines/tmux-lane-engine.mjs +76 -0
  34. package/src/core/scouts/engines/tmux-lane-watcher.mjs +26 -0
  35. package/src/core/scouts/scout-consensus.mjs +31 -0
  36. package/src/core/scouts/scout-output-fixtures.mjs +27 -0
  37. package/src/core/scouts/scout-output-normalizer.mjs +4 -0
  38. package/src/core/scouts/scout-output-parser.mjs +266 -0
  39. package/src/core/scouts/scout-output-validator.mjs +3 -0
  40. package/src/core/scouts/scout-proof-evidence.mjs +10 -0
  41. package/src/core/scouts/scout-readonly-guard.mjs +101 -0
  42. package/src/core/scouts/scout-runner.mjs +262 -30
  43. package/src/core/scouts/scout-schema.mjs +4 -2
  44. 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.17` removes the old route command monolith, adds a read-only five-scout intake before serious route implementation, automatically seals serious route fixture paths with Completion Proof, binds visual/UI claims to Image Voxel TriWiki anchors and relations, and release-gates real command fixtures, route modularity, Rust fallback parity, and DB safety evidence.
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
  ![Sneakoscope Codex architecture and pipeline](https://raw.githubusercontent.com/mandarange/Sneakoscope-Codex/dev/docs/assets/sneakoscope-architecture-pipeline.jpg)
8
8
 
9
- ## 0.9.17 Current Release
9
+ ## 0.9.19 Current Release
10
10
 
11
- `0.9.17` completes the route modularity cleanup that started in 0.9.14. `src/core/commands/route-cli.mjs` is gone; every serious route now owns a focused command module, and release checks fail if the monolith returns or if command modules drift past their budget.
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
- - Serious route mock/fixture commands call `maybeFinalizeRoute`, so Team, QA-LOOP, Research, PPT, Image UX Review, Computer Use, DB, Wiki, and GX fixtures produce route-local `completion-proof.json` without a separate repair/finalize step.
16
- - Serious routes run or validate five read-only scouts for code surface, verification, safety/DB, visual/Voxel evidence, and simplification/integration before proof is written.
17
- - E2E route tests run actual route commands such as `sks team fixture --mock --json`, `sks qa-loop run <mission> --mock --json`, and `sks wiki image-ingest ... --json`.
18
- - Feature fixtures execute deterministic allowlisted commands and validate artifacts generated by those commands, including mission-local proof, visual ledgers, DB reports, and GX/Wiki evidence.
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)
@@ -76,7 +76,7 @@ dependencies = [
76
76
 
77
77
  [[package]]
78
78
  name = "sks-core"
79
- version = "0.9.17"
79
+ version = "0.9.19"
80
80
  dependencies = [
81
81
  "serde_json",
82
82
  ]
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "sks-core"
3
- version = "0.9.17"
3
+ version = "0.9.19"
4
4
  edition = "2021"
5
5
 
6
6
  [dependencies]
@@ -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.17"),
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.17",
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
- "scouts:selftest": "node ./bin/sks.mjs scouts run latest --mock --json",
53
- "scouts:check": "node ./bin/sks.mjs scouts validate latest --json",
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 packcheck && npm run route-modularity:check && npm run command-budget:check && npm run feature:check && npm run all-features:selftest && 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 sizecheck && npm run registry:check",
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.17 route evidence.',
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
- const { id, dir, mission, created } = await resolveOrCreateScoutMission(root, missionArg, { mock, action });
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 && !flag(actionArgs, '--strict')) {
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 --mock --json
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 latestBefore = latestMissionId(root);
16
- const execution = execute && commandArgs ? executeCommand(root, commandArgs) : null;
17
- const latestAfter = execution?.mission_id || latestMissionId(root) || latestBefore;
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(root, tempRoot, artifact, { latestMissionId: latestAfter }))
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
- ok: (!execution || execution.ok) && artifactFailures.length === 0 && !(validateArtifacts && fixture.kind === 'execute_and_validate_artifacts' && expected.length && !execution),
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(root, spec) {
60
+ function executeCommand(sourceRoot, projectRoot, spec, fixture = {}) {
56
61
  const normalized = Array.isArray(spec) ? { command: spec } : spec;
57
- const setup = normalized.setup || [];
58
- const setupResults = setup.map((args) => spawnSks(root, args));
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(root, command) : { status: 0, signal: null, ok: true, stdout_bytes: 0, stderr_bytes: 0, args: [] };
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(root, args = []) {
75
- const result = spawnSync(process.execPath, [path.join(root, 'bin', 'sks.mjs'), ...args], {
76
- cwd: root,
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-|sk-clb-|github_pat_|CODEX_ACCESS_TOKEN|OPENAI_API_KEY)/;
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('static', '$DFix tiny edit route policy', ['completion-proof.json'], 'pass'),
57
- 'route-answer': fixture('static', '$Answer answer-only route policy', [], 'pass'),
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-five-scout-intake': fixture('mock', 'sks scouts validate latest --json', ['scout-team-plan.json', 'scout-consensus.json', 'scout-handoff.md', 'scout-gate.json'], 'pass'),
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
- return FIXTURES[featureId] || fixture('static', 'sks features check --json', [], 'pass');
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
- return { kind, command, expected_artifacts, status };
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
  }