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.
Files changed (53) hide show
  1. package/README.md +28 -3
  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/agents/agent-cleanup-executor.js +75 -1
  10. package/dist/core/agents/agent-command-surface.js +29 -4
  11. package/dist/core/agents/agent-orchestrator.js +25 -6
  12. package/dist/core/agents/native-cli-session-swarm.js +1 -1
  13. package/dist/core/build/build-once-runner.js +62 -0
  14. package/dist/core/codex/codex-config-readability.js +52 -38
  15. package/dist/core/commands/check-command.js +130 -0
  16. package/dist/core/commands/daemon-command.js +14 -0
  17. package/dist/core/commands/local-model-command.js +1 -1
  18. package/dist/core/commands/mad-sks-command.js +18 -1
  19. package/dist/core/commands/naruto-command.js +14 -5
  20. package/dist/core/commands/release-command.js +52 -0
  21. package/dist/core/commands/task-command.js +15 -0
  22. package/dist/core/commands/triwiki-command.js +38 -0
  23. package/dist/core/daemon/sksd-client.js +9 -0
  24. package/dist/core/daemon/sksd.js +55 -0
  25. package/dist/core/doctor/doctor-codex-startup-repair.js +3 -2
  26. package/dist/core/doctor/doctor-dirty-planner.js +30 -0
  27. package/dist/core/doctor/doctor-transaction.js +13 -0
  28. package/dist/core/feature-fixtures.js +1 -0
  29. package/dist/core/fsx.js +1 -1
  30. package/dist/core/init.js +7 -1
  31. package/dist/core/probes/probe-memoization.js +42 -0
  32. package/dist/core/release/extreme-parallel-scheduler.js +33 -0
  33. package/dist/core/release/gate-pack-manifest.js +118 -0
  34. package/dist/core/release/gate-pack-runner.js +113 -0
  35. package/dist/core/release/release-gate-cache-v2.js +73 -16
  36. package/dist/core/release/release-gate-dag.js +81 -2
  37. package/dist/core/release/resource-class-budget.js +22 -0
  38. package/dist/core/release/sla-scheduler.js +22 -0
  39. package/dist/core/routes.js +5 -0
  40. package/dist/core/triwiki/triwiki-affected-graph.js +56 -0
  41. package/dist/core/triwiki/triwiki-cache-key.js +221 -0
  42. package/dist/core/triwiki/triwiki-gate-impact-map.js +79 -0
  43. package/dist/core/triwiki/triwiki-module-card.js +37 -0
  44. package/dist/core/triwiki/triwiki-proof-bank.js +132 -0
  45. package/dist/core/triwiki/triwiki-proof-card.js +42 -0
  46. package/dist/core/triwiki/triwiki-sla-certificate.js +30 -0
  47. package/dist/core/version.js +1 -1
  48. package/dist/core/zellij/zellij-worker-pane-manager.js +3 -2
  49. package/dist/scripts/fixtures/fake-codex-config-loader.js +12 -1
  50. package/dist/scripts/release-4000-required-gates.js +36 -0
  51. package/dist/scripts/release-gate-dag-runner.js +18 -0
  52. package/dist/scripts/release-speed-summary.js +9 -0
  53. package/package.json +43 -7
@@ -0,0 +1,56 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { DEFAULT_TRIWIKI_MODULE_CARDS, moduleIdsForPath } from './triwiki-module-card.js';
3
+ import { buildTriWikiGateImpactMap } from './triwiki-gate-impact-map.js';
4
+ export const TRIWIKI_AFFECTED_GRAPH_SCHEMA = 'sks.triwiki-affected-graph.v1';
5
+ export function computeTriWikiAffectedGraph(input) {
6
+ const cards = input.cards || DEFAULT_TRIWIKI_MODULE_CARDS;
7
+ const changedFiles = normalizeChangedFiles(input.changedFiles && input.changedFiles.length ? input.changedFiles : gitChangedFiles(input.root));
8
+ const affectedModules = new Set();
9
+ let conservativeReason = null;
10
+ for (const file of changedFiles) {
11
+ const ids = moduleIdsForPath(file, cards);
12
+ for (const id of ids)
13
+ affectedModules.add(id);
14
+ if (ids.includes('unknown'))
15
+ conservativeReason = 'unknown_changed_file';
16
+ }
17
+ if (!changedFiles.length)
18
+ affectedModules.add('release');
19
+ if (changedFiles.some((file) => file === 'package.json' || file === 'package-lock.json' || file === 'release-gates.v2.json')) {
20
+ conservativeReason = conservativeReason || 'root_release_surface_changed';
21
+ for (const card of cards)
22
+ affectedModules.add(card.module_id);
23
+ }
24
+ const impactMap = buildTriWikiGateImpactMap(input.root, cards);
25
+ const selected = impactMap.impacts.filter((impact) => impact.modules.some((moduleId) => affectedModules.has(moduleId)) || conservativeReason === 'root_release_surface_changed');
26
+ const gatePacks = new Set();
27
+ for (const impact of selected)
28
+ gatePacks.add(impact.gate_pack);
29
+ const tier = input.tier || 'affected';
30
+ return {
31
+ schema: TRIWIKI_AFFECTED_GRAPH_SCHEMA,
32
+ root: input.root,
33
+ tier,
34
+ changed_files: changedFiles,
35
+ affected_modules: [...affectedModules].sort(),
36
+ gate_packs: [...gatePacks].sort(),
37
+ gates: selected.map((impact) => impact.gate_id).sort(),
38
+ release_equivalent_within_scope: tier !== 'instant',
39
+ confidence: tier === 'release' ? 'full-release' : tier === 'instant' ? 'instant' : 'affected-release-equivalent',
40
+ conservative_reason: conservativeReason
41
+ };
42
+ }
43
+ function normalizeChangedFiles(files) {
44
+ return [...new Set(files.map((file) => file.trim().replace(/\\/g, '/')).filter(Boolean))].sort();
45
+ }
46
+ function gitChangedFiles(root) {
47
+ const result = spawnSync('git', ['status', '--porcelain'], { cwd: root, encoding: 'utf8' });
48
+ if (result.status !== 0)
49
+ return [];
50
+ return String(result.stdout || '')
51
+ .split('\n')
52
+ .map((line) => line.slice(3).trim())
53
+ .filter(Boolean)
54
+ .map((line) => line.includes(' -> ') ? line.split(' -> ').pop() || line : line);
55
+ }
56
+ //# sourceMappingURL=triwiki-affected-graph.js.map
@@ -0,0 +1,221 @@
1
+ import crypto from 'node:crypto';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ export const TRIWIKI_CACHE_KEY_SCHEMA = 'sks.triwiki-cache-key.v1';
5
+ const DEFAULT_EXCLUDED_DIRS = new Set([
6
+ '.git',
7
+ 'node_modules',
8
+ '.sneakoscope/cache',
9
+ '.sneakoscope/reports',
10
+ '.sneakoscope/missions',
11
+ '.sneakoscope/quarantine',
12
+ 'dist'
13
+ ]);
14
+ export function computeTriWikiCacheKey(input) {
15
+ const root = path.resolve(input.root);
16
+ const sourceFiles = collectInputFiles(root, input.inputs);
17
+ const implementationFiles = collectInputFiles(root, input.implementationFiles || []);
18
+ const packageLock = hashPathIfPresent(root, 'package-lock.json');
19
+ const envHash = hashJson(envFingerprint(input.envAllowlist || []));
20
+ const fixtureVersion = input.fixtureVersion || 'fixture-v1';
21
+ const toolVersion = input.toolVersion || readPackageVersion(root);
22
+ const inputHash = hashJson(sourceFiles.records);
23
+ const implementationHash = hashJson(implementationFiles.records);
24
+ const key = hashJson({
25
+ schema: TRIWIKI_CACHE_KEY_SCHEMA,
26
+ id: input.id,
27
+ input_hash: inputHash,
28
+ implementation_hash: implementationHash,
29
+ package_lock_hash: packageLock.hash,
30
+ env_allowlist_hash: envHash,
31
+ fixture_version: fixtureVersion,
32
+ tool_version: toolVersion,
33
+ salt: input.salt || ''
34
+ });
35
+ return {
36
+ schema: TRIWIKI_CACHE_KEY_SCHEMA,
37
+ id: input.id,
38
+ key,
39
+ input_hash: inputHash,
40
+ implementation_hash: implementationHash,
41
+ package_lock_hash: packageLock.hash,
42
+ env_allowlist_hash: envHash,
43
+ fixture_version: fixtureVersion,
44
+ tool_version: toolVersion,
45
+ file_count: sourceFiles.records.length,
46
+ missing_inputs: [...sourceFiles.missing, ...packageLock.missing]
47
+ };
48
+ }
49
+ export function hashText(value) {
50
+ return crypto.createHash('sha256').update(value).digest('hex');
51
+ }
52
+ export function hashJson(value) {
53
+ return hashText(JSON.stringify(value, stableJsonReplacer));
54
+ }
55
+ export function collectInputFiles(root, patterns) {
56
+ const files = new Set();
57
+ const missing = [];
58
+ for (const pattern of patterns) {
59
+ const matches = expandInputPattern(root, pattern);
60
+ if (!matches.length) {
61
+ const literal = path.resolve(root, pattern);
62
+ if (fs.existsSync(literal))
63
+ files.add(relativeUnix(root, literal));
64
+ else
65
+ missing.push(pattern);
66
+ }
67
+ else {
68
+ for (const match of matches)
69
+ files.add(match);
70
+ }
71
+ }
72
+ const records = [];
73
+ for (const rel of [...files].sort()) {
74
+ const hashed = hashPathIfPresent(root, rel);
75
+ if (hashed.missing.length)
76
+ missing.push(rel);
77
+ else if (hashed.record)
78
+ records.push(hashed.record);
79
+ }
80
+ return { records, missing: [...new Set(missing)].sort() };
81
+ }
82
+ function expandInputPattern(root, pattern) {
83
+ const normalized = normalizePattern(pattern);
84
+ if (!normalized.includes('*')) {
85
+ const absolute = path.resolve(root, normalized);
86
+ if (!fs.existsSync(absolute))
87
+ return [];
88
+ return listFiles(root, absolute);
89
+ }
90
+ const regex = globToRegex(normalized);
91
+ return listFiles(root, root).filter((rel) => regex.test(rel));
92
+ }
93
+ function normalizePattern(pattern) {
94
+ return pattern.replace(/\\/g, '/').replace(/^\.\//, '');
95
+ }
96
+ function globToRegex(pattern) {
97
+ const escaped = pattern
98
+ .split('')
99
+ .map((char) => {
100
+ if (char === '*')
101
+ return '*';
102
+ return /[\\^$+?.()|{}[\]]/.test(char) ? `\\${char}` : char;
103
+ })
104
+ .join('');
105
+ const regex = escaped
106
+ .replace(/\*\*\//g, '(?:.*/)?')
107
+ .replace(/\*\*/g, '.*')
108
+ .replace(/\*/g, '[^/]*');
109
+ return new RegExp(`^${regex}$`);
110
+ }
111
+ function listFiles(root, start) {
112
+ const out = [];
113
+ if (!fs.existsSync(start))
114
+ return out;
115
+ const stat = fs.lstatSync(start);
116
+ if (stat.isSymbolicLink() || stat.isFile())
117
+ return [relativeUnix(root, start)];
118
+ if (!stat.isDirectory())
119
+ return out;
120
+ const stack = [start];
121
+ while (stack.length) {
122
+ const dir = stack.pop();
123
+ if (!dir)
124
+ continue;
125
+ const relDir = relativeUnix(root, dir);
126
+ if (relDir && isExcluded(relDir))
127
+ continue;
128
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
129
+ const absolute = path.join(dir, entry.name);
130
+ const rel = relativeUnix(root, absolute);
131
+ if (isExcluded(rel))
132
+ continue;
133
+ if (entry.isDirectory())
134
+ stack.push(absolute);
135
+ else if (entry.isFile() || entry.isSymbolicLink())
136
+ out.push(rel);
137
+ }
138
+ }
139
+ return out.sort();
140
+ }
141
+ function isExcluded(rel) {
142
+ for (const dir of DEFAULT_EXCLUDED_DIRS) {
143
+ if (rel === dir || rel.startsWith(`${dir}/`))
144
+ return true;
145
+ }
146
+ return false;
147
+ }
148
+ function hashPathIfPresent(root, rel) {
149
+ const absolute = path.resolve(root, rel);
150
+ if (!fs.existsSync(absolute))
151
+ return { hash: 'missing', missing: [rel] };
152
+ const stat = fs.lstatSync(absolute);
153
+ const mode = stat.isSymbolicLink() ? 'symlink' : stat.isDirectory() ? 'dir' : 'file';
154
+ if (stat.isDirectory()) {
155
+ const files = listFiles(root, absolute).map((file) => hashPathIfPresent(root, file).record).filter((record) => Boolean(record));
156
+ const hash = hashJson(files);
157
+ return { hash, missing: [], record: { path: normalizePattern(rel), hash, size: files.length, mode } };
158
+ }
159
+ if (stat.isSymbolicLink()) {
160
+ const target = fs.readlinkSync(absolute);
161
+ const hash = hashJson({ rel, target, mode });
162
+ return { hash, missing: [], record: { path: normalizePattern(rel), hash, size: target.length, mode } };
163
+ }
164
+ const hash = hashFileChunked(absolute, stat.size);
165
+ return { hash, missing: [], record: { path: normalizePattern(rel), hash, size: stat.size, mode } };
166
+ }
167
+ function hashFileChunked(file, size) {
168
+ const h = crypto.createHash('sha256');
169
+ h.update(`size:${size}:`);
170
+ const chunk = 256 * 1024;
171
+ const fd = fs.openSync(file, 'r');
172
+ try {
173
+ if (size <= chunk * 3) {
174
+ h.update(fs.readFileSync(file));
175
+ }
176
+ else {
177
+ for (const offset of [0, Math.max(0, Math.floor(size / 2) - Math.floor(chunk / 2)), Math.max(0, size - chunk)]) {
178
+ const buffer = Buffer.alloc(chunk);
179
+ const read = fs.readSync(fd, buffer, 0, chunk, offset);
180
+ h.update(`@${offset}:`);
181
+ h.update(buffer.subarray(0, read));
182
+ }
183
+ }
184
+ }
185
+ finally {
186
+ fs.closeSync(fd);
187
+ }
188
+ return h.digest('hex');
189
+ }
190
+ function envFingerprint(keys) {
191
+ return [...new Set(keys)].sort().map((key) => {
192
+ const value = process.env[key];
193
+ return {
194
+ key,
195
+ present: value !== undefined,
196
+ fingerprint: value === undefined ? 'missing' : hashText(`${key}:${value}`)
197
+ };
198
+ });
199
+ }
200
+ function readPackageVersion(root) {
201
+ try {
202
+ const json = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8'));
203
+ return json.version || '0.0.0';
204
+ }
205
+ catch {
206
+ return '0.0.0';
207
+ }
208
+ }
209
+ function relativeUnix(root, absolute) {
210
+ return path.relative(root, absolute).replace(/\\/g, '/');
211
+ }
212
+ function stableJsonReplacer(_key, value) {
213
+ if (!value || typeof value !== 'object' || Array.isArray(value))
214
+ return value;
215
+ const record = value;
216
+ return Object.keys(record).sort().reduce((acc, key) => {
217
+ acc[key] = record[key];
218
+ return acc;
219
+ }, {});
220
+ }
221
+ //# sourceMappingURL=triwiki-cache-key.js.map
@@ -0,0 +1,79 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { validateReleaseGateManifest } from '../release/release-gate-node.js';
4
+ import { DEFAULT_TRIWIKI_MODULE_CARDS } from './triwiki-module-card.js';
5
+ export const TRIWIKI_GATE_IMPACT_MAP_SCHEMA = 'sks.triwiki-gate-impact-map.v1';
6
+ export function buildTriWikiGateImpactMap(root, cards = DEFAULT_TRIWIKI_MODULE_CARDS) {
7
+ const manifest = loadReleaseGateManifest(root);
8
+ const scripts = loadPackageScripts(root);
9
+ const impacts = manifest.gates.map((gate) => {
10
+ const modules = modulesForGate(gate, cards);
11
+ return {
12
+ gate_id: gate.id,
13
+ modules,
14
+ gate_pack: gatePackForGate(gate, modules, cards),
15
+ cache_inputs: gate.cache.inputs,
16
+ resources: gate.resource,
17
+ orphan: !scriptExistsForGateCommand(gate.command, scripts),
18
+ command: gate.command
19
+ };
20
+ });
21
+ return {
22
+ schema: TRIWIKI_GATE_IMPACT_MAP_SCHEMA,
23
+ root,
24
+ gate_count: impacts.length,
25
+ orphan_count: impacts.filter((impact) => impact.orphan).length,
26
+ impacts
27
+ };
28
+ }
29
+ export function loadReleaseGateManifest(root) {
30
+ const manifestPath = path.join(root, 'release-gates.v2.json');
31
+ const raw = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
32
+ const validated = validateReleaseGateManifest(raw);
33
+ if (!validated.ok || !validated.manifest)
34
+ throw new Error(`release-gates.v2.json invalid: ${validated.errors.join(',')}`);
35
+ return validated.manifest;
36
+ }
37
+ export function loadPackageScripts(root) {
38
+ const pkg = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8'));
39
+ return pkg.scripts || {};
40
+ }
41
+ export function modulesForGate(gate, cards = DEFAULT_TRIWIKI_MODULE_CARDS) {
42
+ const modules = new Set();
43
+ for (const card of cards) {
44
+ if (card.owns_gate_prefixes.some((prefix) => gate.id.startsWith(prefix)))
45
+ modules.add(card.module_id);
46
+ if (gate.cache.inputs.some((input) => card.paths.some((pattern) => inputMatches(pattern, input))))
47
+ modules.add(card.module_id);
48
+ }
49
+ if (!modules.size)
50
+ modules.add('release');
51
+ return [...modules].sort();
52
+ }
53
+ export function gatePackForGate(gate, modules, cards = DEFAULT_TRIWIKI_MODULE_CARDS) {
54
+ if (gate.id.startsWith('triwiki:'))
55
+ return 'triwiki';
56
+ if (gate.id.startsWith('gate-pack:') || gate.id.startsWith('release:'))
57
+ return 'release-parity';
58
+ if (gate.id.startsWith('doctor:'))
59
+ return 'doctor-production';
60
+ if (gate.id.startsWith('sksd:') || gate.id.startsWith('probes:'))
61
+ return 'startup-mcp';
62
+ if (gate.id.startsWith('legacy:') || gate.id.startsWith('orphan:') || gate.id.includes('zellij'))
63
+ return 'zellij';
64
+ const card = cards.find((candidate) => modules.includes(candidate.module_id) && candidate.gate_packs.length);
65
+ return card?.gate_packs[0] || 'release-parity';
66
+ }
67
+ export function scriptExistsForGateCommand(command, scripts) {
68
+ const match = command.match(/^npm run ([^ ]+)/);
69
+ if (!match)
70
+ return true;
71
+ const scriptName = match[1];
72
+ return scriptName !== undefined && Object.prototype.hasOwnProperty.call(scripts, scriptName);
73
+ }
74
+ function inputMatches(pattern, input) {
75
+ const cleanPattern = pattern.replace(/\/\*\*$/, '');
76
+ const cleanInput = input.replace(/\/\*\*.*$/, '');
77
+ return cleanInput === cleanPattern || cleanInput.startsWith(`${cleanPattern}/`);
78
+ }
79
+ //# sourceMappingURL=triwiki-gate-impact-map.js.map
@@ -0,0 +1,37 @@
1
+ export const TRIWIKI_MODULE_CARD_SCHEMA = 'sks.triwiki-module-card.v1';
2
+ export const DEFAULT_TRIWIKI_MODULE_CARDS = [
3
+ moduleCard('triwiki', ['src/core/triwiki/**', 'src/scripts/triwiki-*.ts'], ['triwiki:'], ['triwiki'], 'high'),
4
+ moduleCard('release', ['src/core/release/**', 'release-gates.v2.json', 'src/scripts/release-*.ts'], ['release:', 'gate-pack:', 'scheduler:', 'certificate:'], ['release-parity'], 'high'),
5
+ moduleCard('build', ['src/core/build/**', 'src/scripts/build-once-*.ts', 'tsconfig.json'], ['build-once:'], ['doctor-production'], 'medium'),
6
+ moduleCard('daemon', ['src/core/daemon/**', 'src/core/probes/**', 'src/scripts/sksd-*.ts', 'src/scripts/probe-*.ts'], ['sksd:', 'probes:'], ['startup-mcp'], 'medium'),
7
+ moduleCard('doctor', ['src/core/doctor/**', 'src/commands/doctor.ts', 'src/scripts/doctor-*.ts'], ['doctor:'], ['doctor-production'], 'high'),
8
+ moduleCard('legacy', ['src/scripts/legacy-*.ts', 'src/scripts/orphan-*.ts', 'src/commands/tmux.ts'], ['legacy:', 'orphan:'], ['zellij'], 'medium'),
9
+ moduleCard('cli', ['src/cli/**', 'src/core/commands/**', 'src/commands/**'], ['cli:', 'sks:'], ['native-capability'], 'high'),
10
+ moduleCard('codex', ['src/core/codex*/**', 'src/scripts/codex-*.ts'], ['codex:', 'pipeline:codex'], ['codex-0140'], 'medium'),
11
+ moduleCard('skills', ['.agents/skills/**', 'src/core/skills/**', 'src/scripts/skill-*.ts', 'src/scripts/core-skill-*.ts'], ['skill:', 'core-skill:'], ['core-skill', 'skill-dedupe'], 'medium'),
12
+ moduleCard('qa-research-image', ['src/core/qa/**', 'src/core/research/**', 'src/core/image/**', 'src/scripts/qa-*.ts', 'src/scripts/research-*.ts', 'src/scripts/image-*.ts'], ['qa-', 'research:', 'image:'], ['qa-research-image'], 'medium')
13
+ ];
14
+ export function moduleCard(moduleId, paths, ownsGatePrefixes, gatePacks, risk) {
15
+ return {
16
+ schema: TRIWIKI_MODULE_CARD_SCHEMA,
17
+ module_id: moduleId,
18
+ paths,
19
+ owns_gate_prefixes: ownsGatePrefixes,
20
+ gate_packs: gatePacks,
21
+ risk
22
+ };
23
+ }
24
+ export function moduleIdsForPath(file, cards = DEFAULT_TRIWIKI_MODULE_CARDS) {
25
+ const normalized = file.replace(/\\/g, '/');
26
+ const matches = cards.filter((card) => card.paths.some((pattern) => pathMatches(pattern, normalized))).map((card) => card.module_id);
27
+ return matches.length ? matches : ['unknown'];
28
+ }
29
+ export function pathMatches(pattern, file) {
30
+ if (pattern.endsWith('/**'))
31
+ return file === pattern.slice(0, -3) || file.startsWith(pattern.slice(0, -2));
32
+ if (!pattern.includes('*'))
33
+ return file === pattern || file.startsWith(`${pattern}/`);
34
+ const re = new RegExp(`^${pattern.replace(/[.+?^${}()|[\]\\]/g, '\\$&').replace(/\\\*\\\*/g, '.*').replace(/\\\*/g, '[^/]*')}$`);
35
+ return re.test(file);
36
+ }
37
+ //# sourceMappingURL=triwiki-module-card.js.map
@@ -0,0 +1,132 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { TRIWIKI_PROOF_CARD_SCHEMA, isReusableTriWikiProofCard } from './triwiki-proof-card.js';
4
+ export const TRIWIKI_PROOF_BANK_SCHEMA = 'sks.triwiki-proof-bank.v1';
5
+ export function triWikiProofBankDir(root) {
6
+ return path.join(root, '.sneakoscope', 'triwiki', 'proof-bank');
7
+ }
8
+ export function writeTriWikiProofCard(root, card, subjectType = pluralSubject(card.subject_type)) {
9
+ const dir = path.join(triWikiProofBankDir(root), subjectType, safeId(card.subject_id));
10
+ fs.mkdirSync(dir, { recursive: true });
11
+ const file = path.join(dir, `${safeId(card.proof_id)}.json`);
12
+ fs.writeFileSync(file, `${JSON.stringify(card, null, 2)}\n`);
13
+ return file;
14
+ }
15
+ export function readReusableTriWikiProofCard(input) {
16
+ const dir = path.join(triWikiProofBankDir(input.root), input.subjectType || 'gates', safeId(input.subjectId));
17
+ if (!fs.existsSync(dir))
18
+ return { hit: false, card: null, path: null, invalidation_reasons: ['proof_dir_missing'] };
19
+ const reasons = [];
20
+ for (const file of fs.readdirSync(dir).filter((name) => name.endsWith('.json')).sort()) {
21
+ const absolute = path.join(dir, file);
22
+ const card = readProofCard(absolute);
23
+ if (!card) {
24
+ backupCorruptProof(absolute);
25
+ reasons.push(`corrupt:${file}`);
26
+ continue;
27
+ }
28
+ if (card.cache_key !== input.cacheKey) {
29
+ reasons.push(`cache_key_mismatch:${file}`);
30
+ continue;
31
+ }
32
+ if (isReusableTriWikiProofCard(card))
33
+ return { hit: true, card, path: absolute, invalidation_reasons: [] };
34
+ reasons.push(`not_reusable:${file}`);
35
+ }
36
+ return { hit: false, card: null, path: null, invalidation_reasons: reasons.length ? reasons : ['proof_not_found'] };
37
+ }
38
+ export function markTriWikiProofInvalidated(root, subjectId, proofId, reason, subjectType = 'gates') {
39
+ const file = path.join(triWikiProofBankDir(root), subjectType, safeId(subjectId), `${safeId(proofId)}.json`);
40
+ const card = readProofCard(file);
41
+ if (!card)
42
+ return false;
43
+ const next = {
44
+ ...card,
45
+ reusable: false,
46
+ invalidation_reasons: [...new Set([...(card.invalidation_reasons || []), reason])]
47
+ };
48
+ fs.writeFileSync(file, `${JSON.stringify(next, null, 2)}\n`);
49
+ return true;
50
+ }
51
+ export function summarizeTriWikiProofBank(root) {
52
+ const base = triWikiProofBankDir(root);
53
+ let proofCount = 0;
54
+ let reusableCount = 0;
55
+ let invalidatedCount = 0;
56
+ let corruptBackups = 0;
57
+ if (fs.existsSync(base)) {
58
+ for (const file of walkJson(base)) {
59
+ if (file.includes('.corrupt-')) {
60
+ corruptBackups += 1;
61
+ continue;
62
+ }
63
+ const card = readProofCard(file);
64
+ if (!card) {
65
+ backupCorruptProof(file);
66
+ corruptBackups += 1;
67
+ continue;
68
+ }
69
+ proofCount += 1;
70
+ if (isReusableTriWikiProofCard(card))
71
+ reusableCount += 1;
72
+ if (card.reusable !== true || (card.invalidation_reasons || []).length > 0)
73
+ invalidatedCount += 1;
74
+ }
75
+ }
76
+ return {
77
+ schema: TRIWIKI_PROOF_BANK_SCHEMA,
78
+ ok: true,
79
+ root,
80
+ proof_count: proofCount,
81
+ reusable_count: reusableCount,
82
+ invalidated_count: invalidatedCount,
83
+ corrupt_backups: corruptBackups
84
+ };
85
+ }
86
+ function readProofCard(file) {
87
+ try {
88
+ if (!fs.existsSync(file))
89
+ return null;
90
+ const json = JSON.parse(fs.readFileSync(file, 'utf8'));
91
+ return json.schema === TRIWIKI_PROOF_CARD_SCHEMA ? json : null;
92
+ }
93
+ catch {
94
+ return null;
95
+ }
96
+ }
97
+ function backupCorruptProof(file) {
98
+ if (!fs.existsSync(file))
99
+ return;
100
+ const backup = `${file}.corrupt-${Date.now()}.bak`;
101
+ fs.renameSync(file, backup);
102
+ }
103
+ function walkJson(dir) {
104
+ const out = [];
105
+ const stack = [dir];
106
+ while (stack.length) {
107
+ const current = stack.pop();
108
+ if (!current)
109
+ continue;
110
+ for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
111
+ const absolute = path.join(current, entry.name);
112
+ if (entry.isDirectory())
113
+ stack.push(absolute);
114
+ else if (entry.isFile() && entry.name.endsWith('.json'))
115
+ out.push(absolute);
116
+ }
117
+ }
118
+ return out.sort();
119
+ }
120
+ function pluralSubject(value) {
121
+ if (value === 'gate')
122
+ return 'gates';
123
+ if (value === 'gate-pack')
124
+ return 'gate-packs';
125
+ if (value === 'module')
126
+ return 'modules';
127
+ return 'pipelines';
128
+ }
129
+ function safeId(value) {
130
+ return value.replace(/[^a-zA-Z0-9._-]+/g, '_');
131
+ }
132
+ //# sourceMappingURL=triwiki-proof-bank.js.map
@@ -0,0 +1,42 @@
1
+ import { hashJson } from './triwiki-cache-key.js';
2
+ export const TRIWIKI_PROOF_CARD_SCHEMA = 'sks.triwiki-proof-card.v1';
3
+ export function createTriWikiProofCard(input) {
4
+ const base = {
5
+ schema: TRIWIKI_PROOF_CARD_SCHEMA,
6
+ subject_type: input.subject_type,
7
+ subject_id: input.subject_id,
8
+ cache_key: input.cache_key,
9
+ input_hash: input.input_hash,
10
+ implementation_hash: input.implementation_hash,
11
+ tool_version: input.tool_version,
12
+ fixture_version: input.fixture_version,
13
+ result: input.result,
14
+ reusable: input.reusable,
15
+ evidence: input.evidence,
16
+ invalidation_reasons: input.invalidation_reasons || [],
17
+ expires_at: input.expires_at ?? null,
18
+ duration_ms: input.duration_ms ?? 0
19
+ };
20
+ return {
21
+ ...base,
22
+ proof_id: proofIdFor(base),
23
+ created_at: new Date().toISOString()
24
+ };
25
+ }
26
+ export function proofIdFor(value) {
27
+ return `proof-${hashJson(value).slice(0, 24)}`;
28
+ }
29
+ export function isReusableTriWikiProofCard(card, now = new Date()) {
30
+ if (card.schema !== TRIWIKI_PROOF_CARD_SCHEMA)
31
+ return false;
32
+ if (card.reusable !== true)
33
+ return false;
34
+ if (card.result !== 'passed')
35
+ return false;
36
+ if ((card.invalidation_reasons || []).length > 0)
37
+ return false;
38
+ if (card.expires_at && new Date(card.expires_at).getTime() <= now.getTime())
39
+ return false;
40
+ return true;
41
+ }
42
+ //# sourceMappingURL=triwiki-proof-card.js.map
@@ -0,0 +1,30 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ export const TRIWIKI_SLA_CERTIFICATE_SCHEMA = 'sks.triwiki-sla-certificate.v1';
4
+ export function buildTriWikiSlaCertificate(input) {
5
+ const reductionRatio = input.estimatedSequentialMs <= 0 ? 1 : input.estimatedCriticalPathMs / input.estimatedSequentialMs;
6
+ const blockers = input.blockers || [];
7
+ if (input.estimatedCriticalPathMs > input.slaMs)
8
+ blockers.push('sla_estimate_exceeds_budget');
9
+ return {
10
+ schema: TRIWIKI_SLA_CERTIFICATE_SCHEMA,
11
+ ok: blockers.length === 0,
12
+ created_at: new Date().toISOString(),
13
+ tier: input.graph.tier,
14
+ sla_ms: input.slaMs,
15
+ estimated_critical_path_ms: input.estimatedCriticalPathMs,
16
+ estimated_sequential_ms: input.estimatedSequentialMs,
17
+ reduction_ratio: Number(reductionRatio.toFixed(4)),
18
+ release_equivalent_within_scope: input.graph.release_equivalent_within_scope,
19
+ gates: input.graph.gates.length,
20
+ gate_packs: input.graph.gate_packs,
21
+ blockers
22
+ };
23
+ }
24
+ export function writeTriWikiSlaCertificate(root, certificate) {
25
+ const file = path.join(root, '.sneakoscope', 'reports', 'triwiki-sla-certificate.json');
26
+ fs.mkdirSync(path.dirname(file), { recursive: true });
27
+ fs.writeFileSync(file, `${JSON.stringify(certificate, null, 2)}\n`);
28
+ return file;
29
+ }
30
+ //# sourceMappingURL=triwiki-sla-certificate.js.map
@@ -1,2 +1,2 @@
1
- export const PACKAGE_VERSION = '3.1.13';
1
+ export const PACKAGE_VERSION = '4.0.0';
2
2
  //# sourceMappingURL=version.js.map
@@ -459,7 +459,8 @@ export async function closeWorkerPane(input) {
459
459
  const root = path.resolve(input.root);
460
460
  const success = (input.status || 'closed') === 'closed' && !(input.blockers || []).length;
461
461
  const closeSuccess = process.env.SKS_ZELLIJ_CLOSE_WORKER_PANE !== '0';
462
- const closeFailed = process.env.SKS_ZELLIJ_CLOSE_FAILED_PANE === '1' || process.env.SKS_ZELLIJ_KEEP_FAILED_PANE === '0';
462
+ const keepFailed = process.env.SKS_ZELLIJ_KEEP_FAILED_PANE === '1';
463
+ const closeFailed = !keepFailed && process.env.SKS_ZELLIJ_CLOSE_FAILED_PANE !== '0';
463
464
  const paneId = input.paneRecord.pane_id;
464
465
  const shouldClose = Boolean(paneId) && (success ? closeSuccess : closeFailed);
465
466
  const close = shouldClose
@@ -700,7 +701,7 @@ async function focusZellijPaneById(sessionName, paneId, cwd) {
700
701
  }
701
702
  return last;
702
703
  }
703
- async function closeZellijPaneById(sessionName, paneId, cwd) {
704
+ export async function closeZellijPaneById(sessionName, paneId, cwd) {
704
705
  const candidates = zellijPaneIdCandidates(paneId);
705
706
  let last = null;
706
707
  for (const candidate of candidates) {
@@ -5,8 +5,9 @@ import path from 'node:path';
5
5
  const args = process.argv.slice(2);
6
6
  const configPath = path.join(process.cwd(), '.codex', 'config.toml');
7
7
  const outputLastMessage = readOption('--output-last-message', '');
8
+ let configText = '';
8
9
  try {
9
- fs.readFileSync(configPath, 'utf8');
10
+ configText = fs.readFileSync(configPath, 'utf8');
10
11
  }
11
12
  catch (err) {
12
13
  emitConfigError(`Failed to read project config file ${configPath}: ${err.message}`);
@@ -14,6 +15,16 @@ catch (err) {
14
15
  if (process.env.SKS_FAKE_CODEX_CONFIG_EPERM === '1') {
15
16
  emitConfigError(`Failed to read project config file ${configPath}: Operation not permitted (os error 1)`);
16
17
  }
18
+ // Simulate the Codex 0.140 stdio/url merge conflict: when the project config still
19
+ // defines Context7 as a local stdio server (a `command`), Codex merges it with the
20
+ // remote `url` in the global config and rejects the result. Once the doctor migrates
21
+ // Context7 to the remote `url` transport (no `command`), the load succeeds.
22
+ if (process.env.SKS_FAKE_CODEX_CONFIG_CONTEXT7_STDIO_CONFLICT === '1') {
23
+ const context7 = configText.match(/\[mcp_servers\.context7\][^[]*/)?.[0] || '';
24
+ if (/^\s*command\s*=/m.test(context7) || /@upstash\/context7-mcp/.test(context7)) {
25
+ emitConfigError('url is not supported for stdio\nin `mcp_servers.context7`');
26
+ }
27
+ }
17
28
  if (process.env.SKS_FAKE_CODEX_CONFIG_TOML_ERROR === '1') {
18
29
  emitConfigError(`TOML parse error in project config file ${configPath}: invalid string`);
19
30
  }