sneakoscope 3.1.14 → 4.0.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.
Files changed (45) hide show
  1. package/README.md +26 -1
  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/dist/bin/sks.js +1 -1
  6. package/dist/cli/command-registry.js +6 -13
  7. package/dist/commands/doctor.js +29 -2
  8. package/dist/commands/proof.js +8 -0
  9. package/dist/core/build/build-once-runner.js +62 -0
  10. package/dist/core/codex/codex-config-readability.js +52 -38
  11. package/dist/core/commands/check-command.js +130 -0
  12. package/dist/core/commands/daemon-command.js +14 -0
  13. package/dist/core/commands/mad-sks-command.js +18 -1
  14. package/dist/core/commands/release-command.js +52 -0
  15. package/dist/core/commands/task-command.js +15 -0
  16. package/dist/core/commands/triwiki-command.js +38 -0
  17. package/dist/core/daemon/sksd-client.js +9 -0
  18. package/dist/core/daemon/sksd.js +55 -0
  19. package/dist/core/doctor/doctor-dirty-planner.js +30 -0
  20. package/dist/core/doctor/doctor-transaction.js +13 -0
  21. package/dist/core/feature-fixtures.js +1 -0
  22. package/dist/core/fsx.js +1 -1
  23. package/dist/core/init.js +7 -1
  24. package/dist/core/probes/probe-memoization.js +42 -0
  25. package/dist/core/release/extreme-parallel-scheduler.js +33 -0
  26. package/dist/core/release/gate-pack-manifest.js +118 -0
  27. package/dist/core/release/gate-pack-runner.js +113 -0
  28. package/dist/core/release/release-gate-cache-v2.js +73 -16
  29. package/dist/core/release/release-gate-dag.js +81 -2
  30. package/dist/core/release/resource-class-budget.js +22 -0
  31. package/dist/core/release/sla-scheduler.js +22 -0
  32. package/dist/core/routes.js +5 -0
  33. package/dist/core/triwiki/triwiki-affected-graph.js +56 -0
  34. package/dist/core/triwiki/triwiki-cache-key.js +221 -0
  35. package/dist/core/triwiki/triwiki-gate-impact-map.js +79 -0
  36. package/dist/core/triwiki/triwiki-module-card.js +37 -0
  37. package/dist/core/triwiki/triwiki-proof-bank.js +132 -0
  38. package/dist/core/triwiki/triwiki-proof-card.js +42 -0
  39. package/dist/core/triwiki/triwiki-sla-certificate.js +30 -0
  40. package/dist/core/version.js +1 -1
  41. package/dist/scripts/fixtures/fake-codex-config-loader.js +12 -1
  42. package/dist/scripts/release-4000-required-gates.js +36 -0
  43. package/dist/scripts/release-gate-dag-runner.js +18 -0
  44. package/dist/scripts/release-speed-summary.js +9 -0
  45. package/package.json +43 -7
@@ -0,0 +1,38 @@
1
+ import { flag } from '../../cli/args.js';
2
+ import { printJson } from '../../cli/output.js';
3
+ import { projectRoot } from '../fsx.js';
4
+ import { computeTriWikiAffectedGraph } from '../triwiki/triwiki-affected-graph.js';
5
+ import { buildTriWikiGateImpactMap } from '../triwiki/triwiki-gate-impact-map.js';
6
+ import { DEFAULT_TRIWIKI_MODULE_CARDS } from '../triwiki/triwiki-module-card.js';
7
+ import { summarizeTriWikiProofBank } from '../triwiki/triwiki-proof-bank.js';
8
+ export async function triwikiCommand(args = []) {
9
+ const root = await projectRoot();
10
+ const sub = args[0] && !args[0].startsWith('-') ? args[0] : 'index';
11
+ const json = flag(args, '--json');
12
+ let result;
13
+ if (sub === 'index') {
14
+ result = {
15
+ schema: 'sks.triwiki-index.v1',
16
+ ok: true,
17
+ modules: DEFAULT_TRIWIKI_MODULE_CARDS,
18
+ impact_map: buildTriWikiGateImpactMap(root),
19
+ proof_bank: summarizeTriWikiProofBank(root)
20
+ };
21
+ }
22
+ else if (sub === 'affected') {
23
+ result = computeTriWikiAffectedGraph({ root, tier: 'affected' });
24
+ }
25
+ else if (sub === 'proof-bank') {
26
+ result = summarizeTriWikiProofBank(root);
27
+ }
28
+ else {
29
+ console.error('Usage: sks triwiki index|affected|proof-bank [--json]');
30
+ process.exitCode = 1;
31
+ return null;
32
+ }
33
+ if (json)
34
+ return printJson(result);
35
+ console.log(JSON.stringify(result, null, 2));
36
+ return result;
37
+ }
38
+ //# sourceMappingURL=triwiki-command.js.map
@@ -0,0 +1,9 @@
1
+ import { sksdStatus, sksdStop, sksdWarm } from './sksd.js';
2
+ export function runSksdClient(root, action) {
3
+ if (action === 'warm')
4
+ return sksdWarm(root);
5
+ if (action === 'stop')
6
+ return sksdStop(root);
7
+ return sksdStatus(root);
8
+ }
9
+ //# sourceMappingURL=sksd-client.js.map
@@ -0,0 +1,55 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ export const SKSD_STATE_SCHEMA = 'sks.sksd-state.v1';
4
+ export function sksdStatus(root) {
5
+ const state = readState(root);
6
+ return state || emptyState();
7
+ }
8
+ export function sksdWarm(root) {
9
+ const state = {
10
+ schema: SKSD_STATE_SCHEMA,
11
+ status: 'warm',
12
+ pid: process.pid,
13
+ warmed_at: new Date().toISOString(),
14
+ proof_bank_ready: true,
15
+ build_proof_ready: fs.existsSync(path.join(root, 'dist', '.sks-build-proof.json'))
16
+ };
17
+ writeState(root, state);
18
+ return state;
19
+ }
20
+ export function sksdStop(root) {
21
+ const state = { ...emptyState(), status: 'stopped' };
22
+ writeState(root, state);
23
+ return state;
24
+ }
25
+ function readState(root) {
26
+ const file = statePath(root);
27
+ try {
28
+ if (!fs.existsSync(file))
29
+ return null;
30
+ const json = JSON.parse(fs.readFileSync(file, 'utf8'));
31
+ return json.schema === SKSD_STATE_SCHEMA ? json : null;
32
+ }
33
+ catch {
34
+ return null;
35
+ }
36
+ }
37
+ function writeState(root, state) {
38
+ const file = statePath(root);
39
+ fs.mkdirSync(path.dirname(file), { recursive: true });
40
+ fs.writeFileSync(file, `${JSON.stringify(state, null, 2)}\n`);
41
+ }
42
+ function statePath(root) {
43
+ return path.join(root, '.sneakoscope', 'cache', 'sksd-state.json');
44
+ }
45
+ function emptyState() {
46
+ return {
47
+ schema: SKSD_STATE_SCHEMA,
48
+ status: 'stopped',
49
+ pid: null,
50
+ warmed_at: null,
51
+ proof_bank_ready: false,
52
+ build_proof_ready: false
53
+ };
54
+ }
55
+ //# sourceMappingURL=sksd.js.map
@@ -0,0 +1,30 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ export const DOCTOR_DIRTY_PLAN_SCHEMA = 'sks.doctor-dirty-plan.v1';
4
+ export function planDoctorDirtyRepair(root, phaseIds) {
5
+ const phases = phaseIds.map((id) => {
6
+ const marker = markerPath(root, id);
7
+ if (!fs.existsSync(marker))
8
+ return { id, status: 'dirty', reason: 'no_clean_marker' };
9
+ return { id, status: 'clean', reason: 'clean_marker_present' };
10
+ });
11
+ return {
12
+ schema: DOCTOR_DIRTY_PLAN_SCHEMA,
13
+ root,
14
+ phases,
15
+ dirty_count: phases.filter((phase) => phase.status === 'dirty').length,
16
+ clean_count: phases.filter((phase) => phase.status === 'clean').length
17
+ };
18
+ }
19
+ export function markDoctorPhaseClean(root, id) {
20
+ const file = markerPath(root, id);
21
+ fs.mkdirSync(path.dirname(file), { recursive: true });
22
+ fs.writeFileSync(file, `${new Date().toISOString()}\n`);
23
+ }
24
+ export function isDoctorPhaseClean(plan, id) {
25
+ return plan?.phases.find((phase) => phase.id === id)?.status === 'clean';
26
+ }
27
+ function markerPath(root, id) {
28
+ return path.join(root, '.sneakoscope', 'cache', 'doctor-dirty', `${id.replace(/[^a-zA-Z0-9._-]+/g, '_')}.clean`);
29
+ }
30
+ //# sourceMappingURL=doctor-dirty-planner.js.map
@@ -1,5 +1,6 @@
1
1
  import path from 'node:path';
2
2
  import { nowIso, writeJsonAtomic } from '../fsx.js';
3
+ import { isDoctorPhaseClean, markDoctorPhaseClean } from './doctor-dirty-planner.js';
3
4
  export async function runDoctorFixTransaction(input) {
4
5
  const startedAt = nowIso();
5
6
  const phases = [];
@@ -18,6 +19,16 @@ export async function runDoctorFixTransaction(input) {
18
19
  artifact_path: null,
19
20
  started_at: phaseStarted
20
21
  };
22
+ if (isDoctorPhaseClean(input.dirtyPlan, definition.id)) {
23
+ phases.push({
24
+ ...phase,
25
+ ok: true,
26
+ warnings: ['dirty_plan_skipped_clean_phase'],
27
+ completed_at: nowIso(),
28
+ duration_ms: Math.max(0, Date.now() - startedMs)
29
+ });
30
+ continue;
31
+ }
21
32
  try {
22
33
  const result = await definition.run();
23
34
  phase = normalizePhase(definition, result, phase, startedMs);
@@ -48,6 +59,8 @@ export async function runDoctorFixTransaction(input) {
48
59
  }
49
60
  phase.completed_at = phase.completed_at || nowIso();
50
61
  phase.duration_ms = phase.duration_ms ?? Math.max(0, Date.now() - startedMs);
62
+ if (phase.ok)
63
+ markDoctorPhaseClean(input.root, definition.id);
51
64
  phases.push(phase);
52
65
  }
53
66
  const writeInput = {
@@ -21,6 +21,7 @@ const FIXTURES = Object.freeze({
21
21
  'cli-hooks': fixture('mock', 'sks hooks trust-report --json', [], 'pass'),
22
22
  'cli-features': fixture('execute', 'sks features check --json', [], 'pass'),
23
23
  'cli-commands': fixture('execute', 'sks commands --json', [], 'pass'),
24
+ 'cli-check': fixture('execute', 'sks check --tier confidence --sla 5m --plan --json', [], 'pass'),
24
25
  'cli-run': fixture('execute_and_validate_artifacts', 'sks run "fixture" --mock --json', ['run-classification.json', 'completion-proof.json', 'evidence-index.json', 'route-completion-contract.json', 'trust-report.json'], 'pass'),
25
26
  'cli-status': fixture('execute', 'sks status --json', [], 'pass'),
26
27
  'cli-usage': fixture('execute', 'sks usage overview', [], 'pass'),
package/dist/core/fsx.js CHANGED
@@ -5,7 +5,7 @@ import os from 'node:os';
5
5
  import crypto from 'node:crypto';
6
6
  import { spawn } from 'node:child_process';
7
7
  import { fileURLToPath } from 'node:url';
8
- export const PACKAGE_VERSION = '3.1.14';
8
+ export const PACKAGE_VERSION = '4.0.0';
9
9
  export const DEFAULT_PROCESS_TAIL_BYTES = 256 * 1024;
10
10
  export const DEFAULT_PROCESS_TIMEOUT_MS = 30 * 60 * 1000;
11
11
  export function nowIso() {
package/dist/core/init.js CHANGED
@@ -806,7 +806,13 @@ export async function initProject(root, opts = {}) {
806
806
  // Context7 credentials may live directly in this table as args/env/headers/url
807
807
  // depending on the user's MCP client setup. Seed the default only when absent;
808
808
  // never replace an existing Context7 block during setup/update.
809
- { table: 'mcp_servers.context7', text: context7ConfigToml().trim(), preserveExisting: true },
809
+ // Seed the REMOTE (streamable HTTP `url`) transport, not local stdio: Codex
810
+ // merges the global ~/.codex/config.toml and the project config per-key, so a
811
+ // local-stdio `command` here merging with a remote `url` in the global config
812
+ // yields a stdio server that also carries a `url` — which Codex 0.140 rejects
813
+ // with `url is not supported for stdio`. Remote is also the transport the doctor
814
+ // migrates everyone to (local stdio can block interactive Codex launch).
815
+ { table: 'mcp_servers.context7', text: context7ConfigToml('remote').trim(), preserveExisting: true },
810
816
  { table: 'agents.native_agent', text: agentConfigBlock('native_agent', 'Read-only SKS analysis agent.', './agents/native-agent-intake.toml', ['Analysis', 'Mapper']) },
811
817
  { table: 'agents.team_consensus', text: agentConfigBlock('team_consensus', 'SKS planning/debate agent.', './agents/team-consensus.toml', ['Consensus', 'Atlas']) },
812
818
  { table: 'agents.implementation_worker', text: agentConfigBlock('implementation_worker', 'SKS bounded implementation worker.', './agents/implementation-worker.toml', ['Builder', 'Mason']) },
@@ -0,0 +1,42 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { hashJson } from '../triwiki/triwiki-cache-key.js';
4
+ export const PROBE_MEMOIZATION_SCHEMA = 'sks.probe-memoization.v1';
5
+ export function memoizeProbe(input) {
6
+ const key = hashJson({
7
+ probe_id: input.probeId,
8
+ version: input.version,
9
+ env: (input.envAllowlist || []).sort().map((name) => ({ name, present: process.env[name] !== undefined }))
10
+ });
11
+ const file = probeCachePath(input.root, key);
12
+ const existing = readProbeRecord(file);
13
+ if (existing && new Date(existing.expires_at).getTime() > Date.now())
14
+ return { value: existing.value, reused: true, key };
15
+ const value = input.run();
16
+ const record = {
17
+ schema: PROBE_MEMOIZATION_SCHEMA,
18
+ key,
19
+ probe_id: input.probeId,
20
+ created_at: new Date().toISOString(),
21
+ expires_at: new Date(Date.now() + input.ttlMs).toISOString(),
22
+ value
23
+ };
24
+ fs.mkdirSync(path.dirname(file), { recursive: true });
25
+ fs.writeFileSync(file, `${JSON.stringify(record, null, 2)}\n`);
26
+ return { value, reused: false, key };
27
+ }
28
+ function readProbeRecord(file) {
29
+ try {
30
+ if (!fs.existsSync(file))
31
+ return null;
32
+ const json = JSON.parse(fs.readFileSync(file, 'utf8'));
33
+ return json.schema === PROBE_MEMOIZATION_SCHEMA ? json : null;
34
+ }
35
+ catch {
36
+ return null;
37
+ }
38
+ }
39
+ function probeCachePath(root, key) {
40
+ return path.join(root, '.sneakoscope', 'cache', 'probes', `${key}.json`);
41
+ }
42
+ //# sourceMappingURL=probe-memoization.js.map
@@ -0,0 +1,33 @@
1
+ import { buildGatePackManifest } from './gate-pack-manifest.js';
2
+ import { computeResourceClassBudget } from './resource-class-budget.js';
3
+ export const EXTREME_PARALLEL_SCHEDULER_SCHEMA = 'sks.extreme-parallel-scheduler.v1';
4
+ export function planExtremeParallelSchedule(root, graph, budget = computeResourceClassBudget()) {
5
+ const manifest = buildGatePackManifest(root);
6
+ const selectedIds = new Set(graph?.gate_packs && graph.gate_packs.length ? graph.gate_packs : manifest.packs.map((pack) => pack.id));
7
+ const packs = manifest.packs.filter((pack) => selectedIds.has(pack.id)).sort((a, b) => b.estimated_ms - a.estimated_ms);
8
+ const laneCount = Math.max(1, Math.min(packs.length || 1, Math.max(4, budget.cpu_light)));
9
+ const lanes = Array.from({ length: laneCount }, () => ({ packs: [], estimated_ms: 0 }));
10
+ for (const pack of packs) {
11
+ const target = lanes.reduce((best, lane) => lane.estimated_ms < best.estimated_ms ? lane : best, lanes[0]);
12
+ target.packs.push(pack.id);
13
+ target.estimated_ms += pack.estimated_ms;
14
+ }
15
+ const batches = lanes
16
+ .filter((lane) => lane.packs.length > 0)
17
+ .map((lane, index) => ({ batch: index + 1, packs: lane.packs, estimated_ms: lane.estimated_ms }));
18
+ const sequential = packs.reduce((sum, pack) => sum + pack.estimated_ms, 0);
19
+ const critical = batches.reduce((max, batch) => Math.max(max, batch.estimated_ms), 0);
20
+ const ratio = sequential <= 0 ? 1 : critical / sequential;
21
+ const blockers = ratio <= 0.3 || packs.length <= 1 ? [] : ['critical_path_reduction_below_70_percent'];
22
+ return {
23
+ schema: EXTREME_PARALLEL_SCHEDULER_SCHEMA,
24
+ ok: blockers.length === 0,
25
+ batches,
26
+ sequential_ms: sequential,
27
+ critical_path_ms: critical,
28
+ reduction_ratio: Number(ratio.toFixed(4)),
29
+ budget,
30
+ blockers
31
+ };
32
+ }
33
+ //# sourceMappingURL=extreme-parallel-scheduler.js.map
@@ -0,0 +1,118 @@
1
+ import { buildTriWikiGateImpactMap } from '../triwiki/triwiki-gate-impact-map.js';
2
+ export const GATE_PACK_MANIFEST_SCHEMA = 'sks.gate-pack-manifest.v1';
3
+ export const REQUIRED_GATE_PACK_IDS = [
4
+ 'release-parity',
5
+ 'codex-0140',
6
+ 'doctor-production',
7
+ 'startup-mcp',
8
+ 'native-capability',
9
+ 'secret',
10
+ 'core-skill',
11
+ 'skill-dedupe',
12
+ 'zellij',
13
+ 'loop-mesh',
14
+ 'qa-research-image',
15
+ 'triwiki'
16
+ ];
17
+ export function buildGatePackManifest(root, gates) {
18
+ const impactMap = buildTriWikiGateImpactMap(root);
19
+ const byPack = new Map();
20
+ for (const pack of REQUIRED_GATE_PACK_IDS)
21
+ byPack.set(pack, []);
22
+ for (const impact of impactMap.impacts) {
23
+ const ids = byPack.get(impact.gate_pack) || [];
24
+ ids.push(impact.gate_id);
25
+ byPack.set(impact.gate_pack, ids);
26
+ }
27
+ for (const gate of gates || []) {
28
+ const pack = packForGateId(gate.id);
29
+ const ids = byPack.get(pack) || [];
30
+ if (!ids.includes(gate.id))
31
+ ids.push(gate.id);
32
+ byPack.set(pack, ids);
33
+ }
34
+ return {
35
+ schema: GATE_PACK_MANIFEST_SCHEMA,
36
+ root,
37
+ packs: REQUIRED_GATE_PACK_IDS.map((id) => ({
38
+ id,
39
+ description: descriptionForPack(id),
40
+ max_parallel: maxParallelForPack(id),
41
+ estimated_ms: estimatedMsForPack(id),
42
+ resource_classes: resourceClassesForPack(id),
43
+ gate_ids: [...new Set(byPack.get(id) || [])].sort()
44
+ }))
45
+ };
46
+ }
47
+ export function packForGateId(id) {
48
+ if (id.startsWith('triwiki:'))
49
+ return 'triwiki';
50
+ if (id.startsWith('codex:') || id.includes('0140'))
51
+ return 'codex-0140';
52
+ if (id.startsWith('doctor:'))
53
+ return 'doctor-production';
54
+ if (id.startsWith('sksd:') || id.startsWith('probes:') || id.includes('mcp'))
55
+ return 'startup-mcp';
56
+ if (id.includes('native') || id.startsWith('agent:'))
57
+ return 'native-capability';
58
+ if (id.startsWith('secret:') || id.includes('secret'))
59
+ return 'secret';
60
+ if (id.startsWith('core-skill:'))
61
+ return 'core-skill';
62
+ if (id.includes('skill-dedupe') || id.startsWith('skill:'))
63
+ return 'skill-dedupe';
64
+ if (id.includes('zellij') || id.startsWith('legacy:') || id.startsWith('orphan:'))
65
+ return 'zellij';
66
+ if (id.startsWith('loop:'))
67
+ return 'loop-mesh';
68
+ if (id.startsWith('qa-') || id.startsWith('research:') || id.startsWith('image:'))
69
+ return 'qa-research-image';
70
+ return 'release-parity';
71
+ }
72
+ function descriptionForPack(id) {
73
+ const descriptions = {
74
+ 'release-parity': 'Version, DAG, parity, and package release gates.',
75
+ 'codex-0140': 'Codex 0.140 capability and integration checks.',
76
+ 'doctor-production': 'Doctor repair and production safety checks.',
77
+ 'startup-mcp': 'Startup, MCP, daemon, and probe readiness checks.',
78
+ 'native-capability': 'Native agent and desktop capability checks.',
79
+ secret: 'Secret-preservation and redaction checks.',
80
+ 'core-skill': 'Immutable core skill checks.',
81
+ 'skill-dedupe': 'Skill duplication and inventory checks.',
82
+ zellij: 'Zellij and removed legacy runtime checks.',
83
+ 'loop-mesh': 'Loop mesh runtime checks.',
84
+ 'qa-research-image': 'QA, research, and image route checks.',
85
+ triwiki: 'TriWiki proof bank and affected graph checks.'
86
+ };
87
+ return descriptions[id] || id;
88
+ }
89
+ function maxParallelForPack(id) {
90
+ if (id === 'secret' || id === 'zellij')
91
+ return 1;
92
+ if (id === 'qa-research-image' || id === 'codex-0140')
93
+ return 2;
94
+ return 4;
95
+ }
96
+ function estimatedMsForPack(id) {
97
+ if (id === 'release-parity')
98
+ return 25_000;
99
+ if (id === 'triwiki')
100
+ return 12_000;
101
+ if (id === 'doctor-production')
102
+ return 18_000;
103
+ if (id === 'qa-research-image')
104
+ return 30_000;
105
+ return 15_000;
106
+ }
107
+ function resourceClassesForPack(id) {
108
+ if (id === 'secret')
109
+ return ['fs-read'];
110
+ if (id === 'zellij')
111
+ return ['zellij-real'];
112
+ if (id === 'qa-research-image')
113
+ return ['cpu-heavy', 'io-heavy'];
114
+ if (id === 'codex-0140')
115
+ return ['remote-model-real'];
116
+ return ['cpu-light', 'fs-read'];
117
+ }
118
+ //# sourceMappingURL=gate-pack-manifest.js.map
@@ -0,0 +1,113 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { computeTriWikiCacheKey } from '../triwiki/triwiki-cache-key.js';
5
+ import { createTriWikiProofCard } from '../triwiki/triwiki-proof-card.js';
6
+ import { readReusableTriWikiProofCard, writeTriWikiProofCard } from '../triwiki/triwiki-proof-bank.js';
7
+ import { buildGatePackManifest } from './gate-pack-manifest.js';
8
+ import { loadPackageScripts, loadReleaseGateManifest } from '../triwiki/triwiki-gate-impact-map.js';
9
+ export const GATE_PACK_RUNNER_SCHEMA = 'sks.gate-pack-runner.v1';
10
+ export function runGatePack(input) {
11
+ const manifest = buildGatePackManifest(input.root);
12
+ const pack = manifest.packs.find((candidate) => candidate.id === input.packId);
13
+ if (!pack) {
14
+ return { schema: GATE_PACK_RUNNER_SCHEMA, ok: false, root: input.root, pack_id: input.packId, mode: input.execute ? 'execute' : 'plan', reused: 0, executed: 0, failed: 0, proof_paths: [], blockers: ['pack_missing'] };
15
+ }
16
+ const gates = loadReleaseGateManifest(input.root).gates.filter((gate) => pack.gate_ids.includes(gate.id));
17
+ const scripts = loadPackageScripts(input.root);
18
+ const blockers = [];
19
+ const proofPaths = [];
20
+ let reused = 0;
21
+ let executed = 0;
22
+ let failed = 0;
23
+ for (const gate of gates) {
24
+ if (!input.execute)
25
+ continue;
26
+ const cacheKey = computeTriWikiCacheKey({
27
+ root: input.root,
28
+ id: gate.id,
29
+ inputs: gate.cache.inputs,
30
+ implementationFiles: [`src/scripts/${scriptFileForCommand(gate.command) || ''}`].filter(Boolean),
31
+ envAllowlist: ['CI', 'SKS_FAST_MODE', 'SKS_RELEASE_PRESET'],
32
+ fixtureVersion: 'sks-4.0.0'
33
+ });
34
+ const hit = readReusableTriWikiProofCard({ root: input.root, subjectId: gate.id, cacheKey: cacheKey.key });
35
+ if (hit.hit) {
36
+ reused += 1;
37
+ if (hit.path)
38
+ proofPaths.push(hit.path);
39
+ continue;
40
+ }
41
+ const scriptName = scriptNameForCommand(gate.command);
42
+ if (!scriptName || !scripts[scriptName]) {
43
+ failed += 1;
44
+ blockers.push(`script_missing:${gate.id}`);
45
+ continue;
46
+ }
47
+ const started = Date.now();
48
+ const run = spawnSync('npm', ['run', scriptName, '--silent'], {
49
+ cwd: input.root,
50
+ encoding: 'utf8',
51
+ maxBuffer: 1024 * 1024 * 10,
52
+ env: { ...process.env, ...(input.env || {}), CI: process.env.CI || 'true' }
53
+ });
54
+ executed += 1;
55
+ const passed = run.status === 0;
56
+ if (!passed) {
57
+ failed += 1;
58
+ blockers.push(`gate_failed:${gate.id}`);
59
+ }
60
+ const card = createTriWikiProofCard({
61
+ subject_type: 'gate',
62
+ subject_id: gate.id,
63
+ cache_key: cacheKey.key,
64
+ input_hash: cacheKey.input_hash,
65
+ implementation_hash: cacheKey.implementation_hash,
66
+ tool_version: cacheKey.tool_version,
67
+ fixture_version: cacheKey.fixture_version,
68
+ result: passed ? 'passed' : 'failed',
69
+ reusable: passed,
70
+ duration_ms: Math.max(0, Date.now() - started),
71
+ evidence: {
72
+ status: run.status,
73
+ stdout_tail: tail(String(run.stdout || '')),
74
+ stderr_tail: tail(String(run.stderr || ''))
75
+ },
76
+ invalidation_reasons: passed ? [] : ['gate_failed']
77
+ });
78
+ proofPaths.push(writeTriWikiProofCard(input.root, card));
79
+ }
80
+ const report = {
81
+ schema: GATE_PACK_RUNNER_SCHEMA,
82
+ ok: blockers.length === 0,
83
+ root: input.root,
84
+ pack_id: input.packId,
85
+ mode: input.execute ? 'execute' : 'plan',
86
+ reused,
87
+ executed,
88
+ failed,
89
+ proof_paths: proofPaths,
90
+ blockers
91
+ };
92
+ writeGatePackReport(input.root, report);
93
+ return report;
94
+ }
95
+ function writeGatePackReport(root, report) {
96
+ const file = path.join(root, '.sneakoscope', 'reports', 'gate-pack-runner.json');
97
+ fs.mkdirSync(path.dirname(file), { recursive: true });
98
+ fs.writeFileSync(file, `${JSON.stringify(report, null, 2)}\n`);
99
+ }
100
+ function scriptNameForCommand(command) {
101
+ const match = command.match(/^npm run ([^ ]+)/);
102
+ return match?.[1] || null;
103
+ }
104
+ function scriptFileForCommand(command) {
105
+ const script = scriptNameForCommand(command);
106
+ if (!script)
107
+ return null;
108
+ return `${script.replace(/[:]/g, '-')}${script.includes('blackbox') ? '' : '-check'}.ts`;
109
+ }
110
+ function tail(value, limit = 2000) {
111
+ return value.length > limit ? value.slice(-limit) : value;
112
+ }
113
+ //# sourceMappingURL=gate-pack-runner.js.map
@@ -2,10 +2,16 @@ import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import crypto from 'node:crypto';
4
4
  import { normalizeReleaseCacheInputForBehavior } from './release-cache-key.js';
5
+ import { computeTriWikiCacheKey } from '../triwiki/triwiki-cache-key.js';
6
+ import { createTriWikiProofCard } from '../triwiki/triwiki-proof-card.js';
7
+ import { readReusableTriWikiProofCard, writeTriWikiProofCard } from '../triwiki/triwiki-proof-bank.js';
5
8
  export const RELEASE_GATE_CACHE_V2_SCHEMA = 'sks.release-gate-cache.v2';
6
9
  export function releaseGateCacheFile(root) {
7
10
  return path.join(root, '.sneakoscope', 'reports', 'release-gates', 'cache-v2.json');
8
11
  }
12
+ export function releaseGateProofBankFile(root) {
13
+ return path.join(root, '.sneakoscope', 'proof-bank', 'gates', 'cache-v2.json');
14
+ }
9
15
  // Files whose only release-to-release difference is the version literal.
10
16
  // Hashing them version-neutrally keeps a pure `sks versioning bump` from
11
17
  // invalidating every behavior gate: bumping the version rewrites
@@ -117,11 +123,23 @@ export function readReleaseGateCacheHit(root, gate) {
117
123
  return Boolean(readReleaseGateCacheRecord(root, gate));
118
124
  }
119
125
  export function readReleaseGateCacheRecord(root, gate) {
120
- try {
121
- const parsed = JSON.parse(fs.readFileSync(releaseGateCacheFile(root), 'utf8'));
122
- const record = parsed.schema === RELEASE_GATE_CACHE_V2_SCHEMA ? parsed.records?.[releaseGateCacheKey(root, gate)] : null;
123
- if (record?.ok !== true)
124
- return null;
126
+ const key = releaseGateCacheKey(root, gate);
127
+ const proof = readReusableTriWikiProofCard({ root, subjectId: gate.id, cacheKey: key });
128
+ if (proof.hit && proof.card) {
129
+ return {
130
+ ok: true,
131
+ gate_id: gate.id,
132
+ command: gate.command,
133
+ resource: gate.resource,
134
+ preset: gate.preset,
135
+ duration_ms: Math.max(0, Math.floor(Number(proof.card.duration_ms) || 0)),
136
+ recorded_at: proof.card.created_at
137
+ };
138
+ }
139
+ for (const file of [releaseGateProofBankFile(root), releaseGateCacheFile(root)]) {
140
+ const record = readCacheRecord(file, key);
141
+ if (!record || record.ok !== true)
142
+ continue;
125
143
  return {
126
144
  ok: true,
127
145
  gate_id: String(record.gate_id || gate.id),
@@ -132,12 +150,59 @@ export function readReleaseGateCacheRecord(root, gate) {
132
150
  recorded_at: String(record.recorded_at || '')
133
151
  };
134
152
  }
153
+ return null;
154
+ }
155
+ export function writeReleaseGateCacheHit(root, gate, durationMs = 0) {
156
+ const key = releaseGateCacheKey(root, gate);
157
+ const record = {
158
+ ok: true,
159
+ gate_id: gate.id,
160
+ command: gate.command,
161
+ resource: gate.resource,
162
+ preset: gate.preset,
163
+ duration_ms: Math.max(0, Math.floor(Number(durationMs) || 0)),
164
+ recorded_at: new Date().toISOString()
165
+ };
166
+ writeCacheRecord(releaseGateCacheFile(root), key, record);
167
+ writeCacheRecord(releaseGateProofBankFile(root), key, record);
168
+ const triKey = computeTriWikiCacheKey({
169
+ root,
170
+ id: gate.id,
171
+ inputs: gate.cache.inputs,
172
+ implementationFiles: ['release-gates.v2.json', `src/scripts/${gate.id.replace(/[:]/g, '-')}-check.ts`],
173
+ envAllowlist: ['CI', 'SKS_FAST_MODE', 'SKS_RELEASE_PRESET'],
174
+ fixtureVersion: 'sks-4.0.0',
175
+ salt: key
176
+ });
177
+ writeTriWikiProofCard(root, createTriWikiProofCard({
178
+ subject_type: 'gate',
179
+ subject_id: gate.id,
180
+ cache_key: key,
181
+ input_hash: triKey.input_hash,
182
+ implementation_hash: triKey.implementation_hash,
183
+ tool_version: triKey.tool_version,
184
+ fixture_version: triKey.fixture_version,
185
+ result: 'passed',
186
+ reusable: true,
187
+ duration_ms: Math.max(0, Math.floor(Number(durationMs) || 0)),
188
+ evidence: {
189
+ command: gate.command,
190
+ cache_key_schema: 'release-cache-v2-compatible',
191
+ triwiki_key: triKey.key
192
+ },
193
+ invalidation_reasons: []
194
+ }));
195
+ }
196
+ function readCacheRecord(file, key) {
197
+ try {
198
+ const parsed = JSON.parse(fs.readFileSync(file, 'utf8'));
199
+ return parsed.schema === RELEASE_GATE_CACHE_V2_SCHEMA ? parsed.records?.[key] || null : null;
200
+ }
135
201
  catch {
136
202
  return null;
137
203
  }
138
204
  }
139
- export function writeReleaseGateCacheHit(root, gate, durationMs = 0) {
140
- const file = releaseGateCacheFile(root);
205
+ function writeCacheRecord(file, key, record) {
141
206
  let parsed = { schema: RELEASE_GATE_CACHE_V2_SCHEMA, records: {} };
142
207
  try {
143
208
  parsed = JSON.parse(fs.readFileSync(file, 'utf8'));
@@ -145,15 +210,7 @@ export function writeReleaseGateCacheHit(root, gate, durationMs = 0) {
145
210
  catch { }
146
211
  parsed.schema = RELEASE_GATE_CACHE_V2_SCHEMA;
147
212
  parsed.records ||= {};
148
- parsed.records[releaseGateCacheKey(root, gate)] = {
149
- ok: true,
150
- gate_id: gate.id,
151
- command: gate.command,
152
- resource: gate.resource,
153
- preset: gate.preset,
154
- duration_ms: Math.max(0, Math.floor(Number(durationMs) || 0)),
155
- recorded_at: new Date().toISOString()
156
- };
213
+ parsed.records[key] = record;
157
214
  fs.mkdirSync(path.dirname(file), { recursive: true });
158
215
  fs.writeFileSync(file, `${JSON.stringify(parsed, null, 2)}\n`);
159
216
  }