sneakoscope 3.1.13 → 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.
- package/README.md +28 -3
- package/crates/sks-core/Cargo.lock +1 -1
- package/crates/sks-core/Cargo.toml +1 -1
- package/crates/sks-core/src/main.rs +1 -1
- package/dist/bin/sks.js +1 -1
- package/dist/cli/command-registry.js +6 -13
- package/dist/commands/doctor.js +29 -2
- package/dist/commands/proof.js +8 -0
- package/dist/core/agents/agent-cleanup-executor.js +75 -1
- package/dist/core/agents/agent-command-surface.js +29 -4
- package/dist/core/agents/agent-orchestrator.js +25 -6
- package/dist/core/agents/native-cli-session-swarm.js +1 -1
- package/dist/core/build/build-once-runner.js +62 -0
- package/dist/core/codex/codex-config-readability.js +52 -38
- package/dist/core/commands/check-command.js +130 -0
- package/dist/core/commands/daemon-command.js +14 -0
- package/dist/core/commands/local-model-command.js +1 -1
- package/dist/core/commands/mad-sks-command.js +18 -1
- package/dist/core/commands/naruto-command.js +14 -5
- package/dist/core/commands/release-command.js +52 -0
- package/dist/core/commands/task-command.js +15 -0
- package/dist/core/commands/triwiki-command.js +38 -0
- package/dist/core/daemon/sksd-client.js +9 -0
- package/dist/core/daemon/sksd.js +55 -0
- package/dist/core/doctor/doctor-codex-startup-repair.js +3 -2
- package/dist/core/doctor/doctor-dirty-planner.js +30 -0
- package/dist/core/doctor/doctor-transaction.js +13 -0
- package/dist/core/feature-fixtures.js +1 -0
- package/dist/core/fsx.js +1 -1
- package/dist/core/init.js +7 -1
- package/dist/core/probes/probe-memoization.js +42 -0
- package/dist/core/release/extreme-parallel-scheduler.js +33 -0
- package/dist/core/release/gate-pack-manifest.js +118 -0
- package/dist/core/release/gate-pack-runner.js +113 -0
- package/dist/core/release/release-gate-cache-v2.js +73 -16
- package/dist/core/release/release-gate-dag.js +81 -2
- package/dist/core/release/resource-class-budget.js +22 -0
- package/dist/core/release/sla-scheduler.js +22 -0
- package/dist/core/routes.js +5 -0
- package/dist/core/triwiki/triwiki-affected-graph.js +56 -0
- package/dist/core/triwiki/triwiki-cache-key.js +221 -0
- package/dist/core/triwiki/triwiki-gate-impact-map.js +79 -0
- package/dist/core/triwiki/triwiki-module-card.js +37 -0
- package/dist/core/triwiki/triwiki-proof-bank.js +132 -0
- package/dist/core/triwiki/triwiki-proof-card.js +42 -0
- package/dist/core/triwiki/triwiki-sla-certificate.js +30 -0
- package/dist/core/version.js +1 -1
- package/dist/core/zellij/zellij-worker-pane-manager.js +3 -2
- package/dist/scripts/fixtures/fake-codex-config-loader.js +12 -1
- package/dist/scripts/release-4000-required-gates.js +36 -0
- package/dist/scripts/release-gate-dag-runner.js +18 -0
- package/dist/scripts/release-speed-summary.js +9 -0
- package/package.json +43 -7
|
@@ -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
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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
|
-
|
|
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[
|
|
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
|
}
|
|
@@ -4,7 +4,7 @@ import { spawn } from 'node:child_process';
|
|
|
4
4
|
import { createReleaseGateHermeticEnv } from './release-gate-hermetic-env.js';
|
|
5
5
|
import { appendReleaseGateJsonl, writeReleaseGateJson } from './release-gate-report.js';
|
|
6
6
|
import { findReadyReleaseGateNodes, findReleaseGatesBlockedByFailedDeps, pickReadyLaunchableReleaseGates } from './release-gate-scheduler.js';
|
|
7
|
-
import { readReleaseGateCacheRecord, writeReleaseGateCacheHit } from './release-gate-cache-v2.js';
|
|
7
|
+
import { readReleaseGateCacheRecord, releaseGateProofBankFile, writeReleaseGateCacheHit } from './release-gate-cache-v2.js';
|
|
8
8
|
import { RELEASE_GATE_NODE_SCHEMA, validateReleaseGateManifest } from './release-gate-node.js';
|
|
9
9
|
import { countReleaseGateResources, defaultReleaseGateBudget, summarizeReleaseGateBudget } from './release-gate-resource-governor.js';
|
|
10
10
|
import { selectAffectedReleaseGates } from './release-gate-affected-selector.js';
|
|
@@ -38,8 +38,11 @@ export async function runReleaseGateDag(input) {
|
|
|
38
38
|
const reportDir = path.join(root, '.sneakoscope', 'reports', 'release-gates', runId);
|
|
39
39
|
fs.mkdirSync(reportDir, { recursive: true });
|
|
40
40
|
const timeline = path.join(reportDir, 'timeline.jsonl');
|
|
41
|
+
const affectedGraphFile = path.join(reportDir, 'affected-gate-graph.json');
|
|
42
|
+
const completionCertificateFile = path.join(reportDir, 'completion-certificate.json');
|
|
41
43
|
appendReleaseGateJsonl(timeline, { event: 'retention', phase: 'before_run', ...retentionBefore, at: new Date().toISOString() });
|
|
42
44
|
const started = Date.now();
|
|
45
|
+
const slaMs = Math.max(1, Math.floor(Number(input.slaMs || 300000)));
|
|
43
46
|
const pending = new Map(selected.map((gate) => [gate.id, gate]));
|
|
44
47
|
const running = new Map();
|
|
45
48
|
const completed = new Map();
|
|
@@ -54,6 +57,23 @@ export async function runReleaseGateDag(input) {
|
|
|
54
57
|
const writeSummarySnapshot = (finished = false) => {
|
|
55
58
|
const wallMs = Date.now() - started;
|
|
56
59
|
const failures = [...failed.values()].map((row) => ({ id: row.id, exit_code: row.exit_code, stderr_tail: row.stderr_tail, timed_out: row.timed_out, signal: row.signal }));
|
|
60
|
+
const affectedGraph = buildAffectedGraph({
|
|
61
|
+
selection: affected.selection,
|
|
62
|
+
selected,
|
|
63
|
+
cachedGates,
|
|
64
|
+
executedGates,
|
|
65
|
+
proofBankFile: releaseGateProofBankFile(root)
|
|
66
|
+
});
|
|
67
|
+
const completionCertificate = buildCompletionCertificate({
|
|
68
|
+
ok: failures.length === 0,
|
|
69
|
+
preset,
|
|
70
|
+
slaMs,
|
|
71
|
+
wallMs,
|
|
72
|
+
criticalPathMs: estimateCriticalPath(selected, completed),
|
|
73
|
+
affectedGraph,
|
|
74
|
+
affectedGraphFile,
|
|
75
|
+
skippedByAffected: affected.selection.mode === 'affected' ? affected.selection.skipped_gate_ids : []
|
|
76
|
+
});
|
|
57
77
|
const snapshot = {
|
|
58
78
|
schema: 'sks.release-gate-dag-run.v1',
|
|
59
79
|
ok: failures.length === 0,
|
|
@@ -83,7 +103,9 @@ export async function runReleaseGateDag(input) {
|
|
|
83
103
|
budget_snapshot: budget,
|
|
84
104
|
budget_summary: summarizeReleaseGateBudget(budget),
|
|
85
105
|
report_dir: reportDir,
|
|
86
|
-
failures
|
|
106
|
+
failures,
|
|
107
|
+
affected_graph: affectedGraph,
|
|
108
|
+
completion_certificate: completionCertificate
|
|
87
109
|
};
|
|
88
110
|
if (!finished) {
|
|
89
111
|
snapshot.in_progress = true;
|
|
@@ -91,6 +113,12 @@ export async function runReleaseGateDag(input) {
|
|
|
91
113
|
snapshot.running = running.size;
|
|
92
114
|
}
|
|
93
115
|
writeReleaseGateJson(path.join(reportDir, 'summary.json'), snapshot);
|
|
116
|
+
writeReleaseGateJson(affectedGraphFile, affectedGraph);
|
|
117
|
+
writeReleaseGateJson(completionCertificateFile, completionCertificate);
|
|
118
|
+
if (finished) {
|
|
119
|
+
writeReleaseGateJson(path.join(root, '.sneakoscope', 'reports', 'affected-gate-graph.json'), affectedGraph);
|
|
120
|
+
writeReleaseGateJson(path.join(root, '.sneakoscope', 'reports', 'completion-certificate.json'), completionCertificate);
|
|
121
|
+
}
|
|
94
122
|
return snapshot;
|
|
95
123
|
};
|
|
96
124
|
if (input.explain) {
|
|
@@ -179,6 +207,57 @@ export function selectReleaseGatePreset(manifest, preset) {
|
|
|
179
207
|
const effectivePreset = preset === 'affected' || preset === 'fast' ? 'release' : preset;
|
|
180
208
|
return manifest.gates.filter((gate) => gate.preset.includes(effectivePreset));
|
|
181
209
|
}
|
|
210
|
+
function buildAffectedGraph(input) {
|
|
211
|
+
return {
|
|
212
|
+
schema: 'sks.affected-gate-graph.v1',
|
|
213
|
+
changed_files: input.selection.changed_files,
|
|
214
|
+
affected_modules: inferAffectedModules(input.selection.changed_files),
|
|
215
|
+
affected_gates: input.selected.map((gate) => gate.id),
|
|
216
|
+
reused_proofs: [...input.cachedGates],
|
|
217
|
+
invalidated_proofs: [...input.executedGates],
|
|
218
|
+
skipped_gate_ids: input.selection.skipped_gate_ids,
|
|
219
|
+
proof_bank_file: input.proofBankFile
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
function buildCompletionCertificate(input) {
|
|
223
|
+
const affectedScope = input.preset === 'affected' || input.preset === 'fast';
|
|
224
|
+
return {
|
|
225
|
+
schema: 'sks.five-minute-completion-certificate.v1',
|
|
226
|
+
ok: input.ok,
|
|
227
|
+
tier: input.preset === 'affected' ? 'confidence' : input.preset,
|
|
228
|
+
confidence: affectedScope ? 'release-equivalent-for-affected-scope' : 'full-release-proof',
|
|
229
|
+
sla_ms: input.slaMs,
|
|
230
|
+
sla_met: input.wallMs <= input.slaMs,
|
|
231
|
+
changed_files: input.affectedGraph.changed_files,
|
|
232
|
+
affected_gates: input.affectedGraph.affected_gates.length,
|
|
233
|
+
reused_proofs: input.affectedGraph.reused_proofs.length,
|
|
234
|
+
newly_executed_gates: input.affectedGraph.invalidated_proofs.length,
|
|
235
|
+
skipped_as_valid_cache: input.affectedGraph.reused_proofs.length,
|
|
236
|
+
skipped_as_unaffected: input.skippedByAffected.length,
|
|
237
|
+
critical_path_ms: input.criticalPathMs,
|
|
238
|
+
wall_ms: input.wallMs,
|
|
239
|
+
full_release_proof: affectedScope ? 'background_or_release_before_publish_required' : 'current_run',
|
|
240
|
+
proof_bank_file: input.affectedGraph.proof_bank_file,
|
|
241
|
+
affected_graph_file: input.affectedGraphFile
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
function inferAffectedModules(files) {
|
|
245
|
+
const modules = new Set();
|
|
246
|
+
for (const file of files) {
|
|
247
|
+
const normalized = file.replace(/\\/g, '/');
|
|
248
|
+
const parts = normalized.split('/').filter(Boolean);
|
|
249
|
+
if (!parts.length)
|
|
250
|
+
continue;
|
|
251
|
+
const top = parts[0];
|
|
252
|
+
if (parts[0] === 'src' && parts.length >= 3)
|
|
253
|
+
modules.add(parts.slice(0, 3).join('/'));
|
|
254
|
+
else if (parts[0] === 'test' && parts.length >= 2)
|
|
255
|
+
modules.add(parts.slice(0, 2).join('/'));
|
|
256
|
+
else
|
|
257
|
+
modules.add(top);
|
|
258
|
+
}
|
|
259
|
+
return [...modules].sort();
|
|
260
|
+
}
|
|
182
261
|
function runGate(root, runId, reportRoot, gate) {
|
|
183
262
|
const started = Date.now();
|
|
184
263
|
const hermetic = createReleaseGateHermeticEnv({ root, runId, gate, reportRoot });
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import os from 'node:os';
|
|
2
|
+
export const RESOURCE_CLASS_BUDGET_SCHEMA = 'sks.resource-class-budget.v1';
|
|
3
|
+
export function computeResourceClassBudget(env = process.env) {
|
|
4
|
+
const cpus = Math.max(2, os.cpus().length || 2);
|
|
5
|
+
return {
|
|
6
|
+
schema: RESOURCE_CLASS_BUDGET_SCHEMA,
|
|
7
|
+
cpu_light: readEnvInt(env, 'SKS_RESOURCE_CPU_LIGHT', Math.max(2, cpus - 1)),
|
|
8
|
+
cpu_heavy: readEnvInt(env, 'SKS_RESOURCE_CPU_HEAVY', Math.max(1, Math.floor(cpus / 2))),
|
|
9
|
+
io_light: readEnvInt(env, 'SKS_RESOURCE_IO_LIGHT', 8),
|
|
10
|
+
io_heavy: readEnvInt(env, 'SKS_RESOURCE_IO_HEAVY', 2),
|
|
11
|
+
fs_read: readEnvInt(env, 'SKS_RESOURCE_FS_READ', 8),
|
|
12
|
+
network: readEnvInt(env, 'SKS_RESOURCE_NETWORK', 2),
|
|
13
|
+
remote_model_real: readEnvInt(env, 'SKS_RESOURCE_REMOTE_MODEL_REAL', 1),
|
|
14
|
+
zellij_real: readEnvInt(env, 'SKS_RESOURCE_ZELLIJ_REAL', 1),
|
|
15
|
+
secret_sensitive: readEnvInt(env, 'SKS_RESOURCE_SECRET', 1)
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
function readEnvInt(env, key, fallback) {
|
|
19
|
+
const value = Number(env[key]);
|
|
20
|
+
return Number.isFinite(value) && value > 0 ? Math.floor(value) : fallback;
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=resource-class-budget.js.map
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { computeTriWikiAffectedGraph } from '../triwiki/triwiki-affected-graph.js';
|
|
2
|
+
import { buildTriWikiSlaCertificate } from '../triwiki/triwiki-sla-certificate.js';
|
|
3
|
+
import { planExtremeParallelSchedule } from './extreme-parallel-scheduler.js';
|
|
4
|
+
export const SLA_SCHEDULER_SCHEMA = 'sks.sla-scheduler.v1';
|
|
5
|
+
export function planFiveMinuteSla(root, graph = computeTriWikiAffectedGraph({ root, tier: 'affected' }), slaMs = 300_000) {
|
|
6
|
+
const schedule = planExtremeParallelSchedule(root, graph);
|
|
7
|
+
const certificate = buildTriWikiSlaCertificate({
|
|
8
|
+
graph,
|
|
9
|
+
slaMs,
|
|
10
|
+
estimatedCriticalPathMs: schedule.critical_path_ms,
|
|
11
|
+
estimatedSequentialMs: schedule.sequential_ms,
|
|
12
|
+
blockers: [...schedule.blockers]
|
|
13
|
+
});
|
|
14
|
+
return {
|
|
15
|
+
schema: SLA_SCHEDULER_SCHEMA,
|
|
16
|
+
ok: certificate.ok,
|
|
17
|
+
graph,
|
|
18
|
+
certificate,
|
|
19
|
+
highest_confidence_subset: certificate.ok ? graph.gates : graph.gates.slice(0, Math.max(1, Math.floor(graph.gates.length / 2)))
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=sla-scheduler.js.map
|
package/dist/core/routes.js
CHANGED
|
@@ -623,6 +623,11 @@ export const COMMAND_CATALOG = [
|
|
|
623
623
|
{ name: 'update-check', usage: 'sks update-check [--json]', description: 'Check npm for the latest Sneakoscope Codex version.' },
|
|
624
624
|
{ name: 'wizard', usage: 'sks wizard', description: 'Open an interactive setup UI for install scope, setup, doctor, and verification.' },
|
|
625
625
|
{ name: 'commands', usage: 'sks commands [--json]', description: 'List every user-facing command with a short description.' },
|
|
626
|
+
{ name: 'check', usage: 'sks check --tier instant|affected|confidence|release|real-check [--sla 5m] [--changed-since auto] [--json]', description: 'Run build-once proof-bank checks: affected/confidence use incremental build and cached proof reuse; release keeps full clean proof for publish readiness.' },
|
|
627
|
+
{ name: 'task', usage: 'sks task run [--sla 5m] [--json]', description: 'Run the normal affected-scope, release-equivalent task verification path.' },
|
|
628
|
+
{ name: 'release', usage: 'sks release affected|full|background [--json]', description: 'Run affected release proof, full release proof, or background release proof explicitly.' },
|
|
629
|
+
{ name: 'triwiki', usage: 'sks triwiki index|affected|proof-bank [--json]', description: 'Inspect TriWiki module cards, gate impact maps, affected graphs, and proof bank status.' },
|
|
630
|
+
{ name: 'daemon', usage: 'sks daemon status|warm|stop [--json]', description: 'Inspect or warm the local SKS daemon cache state for build/proof reuse.' },
|
|
626
631
|
{ name: 'run', usage: 'sks run "task" [--visual|--research|--db] [--json]', description: 'Classify a plain-language task, materialize a mission, and route it through the SKS trust kernel.' },
|
|
627
632
|
{ name: 'status', usage: 'sks status [--json]', description: 'Show the active mission, route, phase, proof, trust, native agent, image voxel, DB safety, and next action.' },
|
|
628
633
|
{ name: 'usage', usage: `sks usage [${USAGE_TOPICS}]`, description: 'Print copy-ready workflows for common tasks.' },
|