sneakoscope 8.3.2 → 8.4.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 +1 -1
- package/config/architecture-map-policy.v1.json +168 -0
- package/crates/sks-core/Cargo.lock +1 -1
- package/crates/sks-core/Cargo.toml +1 -1
- package/dist/commands/bridge.js +19 -1
- package/dist/commands/doctor-profile.js +1 -1
- package/dist/commands/doctor.js +33 -1
- package/dist/config/skills-manifest.json +1 -1
- package/dist/core/align/align-route.js +8 -2
- package/dist/core/align/code-navigation-align.js +88 -32
- package/dist/core/architecture-map-pipeline.js +172 -0
- package/dist/core/architecture-map-review.js +180 -0
- package/dist/core/codex-app/codex-app-fast-ui-repair.js +2 -1
- package/dist/core/codex-app/menubar/constants.js +1 -0
- package/dist/core/codex-lb/codex-lb-env.js +25 -3
- package/dist/core/codex-lb/combined-catalog/builder.js +7 -2
- package/dist/core/codex-lb/combined-catalog/model-selection.js +71 -0
- package/dist/core/codex-lb/combined-catalog/normalize.js +20 -3
- package/dist/core/codex-lb/desktop-controller-v3/catalog.js +75 -4
- package/dist/core/codex-lb/desktop-controller-v3.js +5 -1
- package/dist/core/commands/triwiki-atlas-command.js +372 -0
- package/dist/core/commands/triwiki-command.js +12 -1
- package/dist/core/fsx.js +14 -0
- package/dist/core/git-hygiene/git-policy.js +4 -0
- package/dist/core/git-hygiene/gitignore-writer.js +2 -0
- package/dist/core/gx-renderer.js +2 -1
- package/dist/core/managed-paths.js +1 -0
- package/dist/core/perf/release-latency-slo.js +57 -11
- package/dist/core/pipeline-internals/pipeline-stage-builder.js +181 -0
- package/dist/core/pipeline-internals/runtime-core.js +13 -172
- package/dist/core/pipeline-internals/runtime-gates.js +12 -1
- package/dist/core/release/content-digest-cache.js +70 -0
- package/dist/core/release/gate-manifest.js +9 -0
- package/dist/core/release/main-push-guard.js +4 -3
- package/dist/core/release/package-dist-snapshot.js +18 -2
- package/dist/core/release/physical-release-gates.js +26 -3
- package/dist/core/release/release-authorization-snapshot.js +4 -0
- package/dist/core/release/release-gate-cache-v2.js +27 -3
- package/dist/core/release/release-gate-contract.js +2 -0
- package/dist/core/release/release-gate-dag.js +3 -0
- package/dist/core/release/release-inspection-memo.js +69 -0
- package/dist/core/release/release-pack-content-scanner.js +5 -0
- package/dist/core/release/release-pack-receipt.js +28 -18
- package/dist/core/release/release-pack-tarball.js +10 -0
- package/dist/core/release/release-upgrade-baseline.js +4 -0
- package/dist/core/routes.js +1 -1
- package/dist/core/safety/ssot-authority-inventory.js +114 -0
- package/dist/core/safety/ssot-guard.js +2 -44
- package/dist/core/triwiki/context-graph/architecture/baseline.js +81 -0
- package/dist/core/triwiki/context-graph/architecture/capsule.js +34 -0
- package/dist/core/triwiki/context-graph/architecture/contracts.js +67 -0
- package/dist/core/triwiki/context-graph/architecture/delta.js +149 -0
- package/dist/core/triwiki/context-graph/architecture/finding-factory.js +28 -0
- package/dist/core/triwiki/context-graph/architecture/findings.js +173 -0
- package/dist/core/triwiki/context-graph/architecture/fingerprint.js +75 -0
- package/dist/core/triwiki/context-graph/architecture/index.js +15 -0
- package/dist/core/triwiki/context-graph/architecture/input-bundle.js +66 -0
- package/dist/core/triwiki/context-graph/architecture/metrics.js +137 -0
- package/dist/core/triwiki/context-graph/architecture/policy.js +321 -0
- package/dist/core/triwiki/context-graph/architecture/slice.js +100 -0
- package/dist/core/triwiki/context-graph/architecture/ssot-analysis.js +59 -0
- package/dist/core/triwiki/context-graph/architecture/topology-overlay.js +106 -0
- package/dist/core/triwiki/context-graph/architecture/validation.js +145 -0
- package/dist/core/triwiki/context-graph/architecture/voxel-context.js +39 -0
- package/dist/core/triwiki/context-graph/extractors/index.js +4 -0
- package/dist/core/triwiki/context-graph/projections/index.js +2 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/architecture-delta.js +68 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/ast.js +49 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/change-impact.js +43 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/contracts.js +5 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/escape.js +45 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/ids.js +73 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/index.js +113 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/manifest.js +37 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/module-dependency.js +21 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/ownership-workstream.js +21 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/project-topology.js +13 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/public-surface.js +17 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/reduce.js +44 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/risk-domains.js +23 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/runtime-control.js +20 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/selection-ledger.js +94 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/serializer.js +81 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/ssot-provenance.js +17 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/verification-coverage.js +17 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/view-builder-common.js +1 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/view-builder.js +173 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/view-specs.js +33 -0
- package/dist/core/triwiki/context-graph/store/architecture-map-store.js +71 -0
- package/dist/core/triwiki/triwiki-proof-bank-index.js +26 -1
- package/dist/core/triwiki/triwiki-proof-bank.js +82 -2
- package/dist/core/version.js +1 -1
- package/dist/native/sks-menubar/Sources/OverviewViewController.swift +2 -2
- package/dist/native/sks-menubar/Sources/ProvidersModelExposure.swift +178 -0
- package/dist/native/sks-menubar/Sources/ProvidersViewController.swift +10 -1
- package/dist/scripts/architecture-map-check.js +299 -0
- package/dist/scripts/lib/ensure-dist-fresh.js +48 -1
- package/dist/scripts/release-metadata-check.js +2 -0
- package/package.json +6 -3
- package/release-gates.v2.json +204 -0
- package/schemas/architecture-baseline.v1.schema.json +47 -0
- package/schemas/architecture-map-manifest.v1.schema.json +40 -0
- package/schemas/architecture-map-policy.v1.schema.json +147 -0
- package/schemas/architecture-review.v1.schema.json +46 -0
- package/schemas/mermaid-projection.v1.schema.json +47 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
export const CONTENT_DIGEST_CACHE_SCHEMA = 'sks.content-digest-cache.v1';
|
|
5
|
+
export const CONTENT_DIGEST_CACHE_ENV = 'SKS_RELEASE_CONTENT_DIGEST_CACHE';
|
|
6
|
+
const processMemo = new Map();
|
|
7
|
+
export function contentDigestCacheEnabled() {
|
|
8
|
+
return process.env[CONTENT_DIGEST_CACHE_ENV] !== '0';
|
|
9
|
+
}
|
|
10
|
+
export function contentDigestCachePath(root) {
|
|
11
|
+
return path.join(root, '.sneakoscope', 'cache', 'release-digests.json');
|
|
12
|
+
}
|
|
13
|
+
export function statFingerprint(root, files) {
|
|
14
|
+
const hash = crypto.createHash('sha256');
|
|
15
|
+
for (const file of files) {
|
|
16
|
+
let stat;
|
|
17
|
+
try {
|
|
18
|
+
stat = fs.statSync(path.isAbsolute(file) ? file : path.join(root, file), { bigint: true });
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
hash.update(`${file}\0missing\0`);
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
hash.update(`${file}\0${stat.size}\0${stat.mtimeNs}\0${stat.ino}\0`);
|
|
25
|
+
}
|
|
26
|
+
return hash.digest('hex');
|
|
27
|
+
}
|
|
28
|
+
export function cachedByFingerprint(root, bucket, fingerprint, compute) {
|
|
29
|
+
if (!contentDigestCacheEnabled())
|
|
30
|
+
return compute();
|
|
31
|
+
const memoKey = `${path.resolve(root)}\0${bucket}`;
|
|
32
|
+
const memo = processMemo.get(memoKey);
|
|
33
|
+
if (memo?.fingerprint === fingerprint)
|
|
34
|
+
return structuredClone(memo.value);
|
|
35
|
+
const document = readCache(root);
|
|
36
|
+
const stored = document.entries[bucket];
|
|
37
|
+
if (stored && stored.fingerprint === fingerprint) {
|
|
38
|
+
processMemo.set(memoKey, { fingerprint, value: stored.value });
|
|
39
|
+
return structuredClone(stored.value);
|
|
40
|
+
}
|
|
41
|
+
const value = compute();
|
|
42
|
+
processMemo.set(memoKey, { fingerprint, value });
|
|
43
|
+
writeCache(root, bucket, fingerprint, value);
|
|
44
|
+
return value;
|
|
45
|
+
}
|
|
46
|
+
export function clearContentDigestProcessMemo() {
|
|
47
|
+
processMemo.clear();
|
|
48
|
+
}
|
|
49
|
+
function readCache(root) {
|
|
50
|
+
try {
|
|
51
|
+
const parsed = JSON.parse(fs.readFileSync(contentDigestCachePath(root), 'utf8'));
|
|
52
|
+
if (parsed?.schema === CONTENT_DIGEST_CACHE_SCHEMA && parsed.entries && typeof parsed.entries === 'object')
|
|
53
|
+
return parsed;
|
|
54
|
+
}
|
|
55
|
+
catch { }
|
|
56
|
+
return { schema: CONTENT_DIGEST_CACHE_SCHEMA, entries: {} };
|
|
57
|
+
}
|
|
58
|
+
function writeCache(root, bucket, fingerprint, value) {
|
|
59
|
+
const file = contentDigestCachePath(root);
|
|
60
|
+
try {
|
|
61
|
+
const document = readCache(root);
|
|
62
|
+
document.entries[bucket] = { fingerprint, value, recorded_at: new Date().toISOString() };
|
|
63
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
64
|
+
const temp = `${file}.${process.pid}.tmp`;
|
|
65
|
+
fs.writeFileSync(temp, `${JSON.stringify(document, null, 2)}\n`);
|
|
66
|
+
fs.renameSync(temp, file);
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -65,6 +65,15 @@ function costFor(id) {
|
|
|
65
65
|
export function affectedGlobsFor(id) {
|
|
66
66
|
const prefix = id.split(':')[0];
|
|
67
67
|
switch (prefix) {
|
|
68
|
+
case 'architecture-map':
|
|
69
|
+
return [
|
|
70
|
+
'src/core/triwiki/context-graph/architecture/**',
|
|
71
|
+
'src/core/triwiki/context-graph/projections/mermaid/**',
|
|
72
|
+
'src/core/triwiki/context-graph/store/architecture-map-store.ts',
|
|
73
|
+
'src/scripts/architecture-map-check.ts',
|
|
74
|
+
'config/architecture-map-policy.v1.json',
|
|
75
|
+
'package.json'
|
|
76
|
+
];
|
|
68
77
|
case 'architecture':
|
|
69
78
|
return ['src/core/safety/ssot-guard.ts', 'src/core/pipeline-internals/runtime-core.ts', 'src/core/pipeline-internals/runtime-gates.ts', 'src/core/commands/naruto-command.ts', 'src/scripts/release-parallel-check.ts', 'src/scripts/architecture-guard-check.ts', 'docs/architecture-ts-rust-boundary.md', 'package.json'];
|
|
70
79
|
case 'core-skill':
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
+
import { sameFilesystemPathSync } from '../fsx.js';
|
|
2
3
|
import { readMacosMenubarProof, validateMacosMenubarProofArtifacts } from './macos-menubar-proof.js';
|
|
3
4
|
import { fileSha256, gitOk, gitText, readJson, relative, unique } from './release-proof-io.js';
|
|
4
5
|
import { releaseOriginIdentity } from './release-origin.js';
|
|
5
6
|
import { releaseProofDir, validateLocalReleasePackBinding } from './release-pack-receipt.js';
|
|
6
7
|
import { validatePhysicalReleaseGateInspection } from './physical-release-gates.js';
|
|
7
8
|
import { validateFullReleaseStamp } from './release-stamp-proof.js';
|
|
8
|
-
import { RELEASE_UPGRADE_BASELINE_SHA256, RELEASE_UPGRADE_BASELINE_VERSION, releaseUpgradeExpectedVersion, releaseUpgradeProofFilename } from './release-upgrade-baseline.js';
|
|
9
|
+
import { RELEASE_UPGRADE_BASELINE_SHA256, RELEASE_UPGRADE_BASELINE_VERSION, isExpectedReleaseUpgradeLaunchctlCall, releaseUpgradeExpectedVersion, releaseUpgradeProofFilename } from './release-upgrade-baseline.js';
|
|
9
10
|
export const MAIN_PUSH_GUARD_SCHEMA = 'sks.release-main-push-guard.v1';
|
|
10
11
|
const RELEASE_UPGRADE_SMOKE_SCHEMA = 'sks.release-upgrade-smoke.v2';
|
|
11
12
|
const RELEASE_UPGRADE_COMMAND_STAGES = Object.freeze([
|
|
@@ -244,7 +245,7 @@ function validateReleaseUpgradeIsolationAndCommands(report, blockers) {
|
|
|
244
245
|
|| !path.isAbsolute(String(safety?.launchctl_log_path || ''))
|
|
245
246
|
|| !lexicalSubpath(String(safety?.launchctl_log_path || ''), sandbox)
|
|
246
247
|
|| !Array.isArray(safety?.launchctl_calls)
|
|
247
|
-
|| safety.launchctl_calls.some((call) =>
|
|
248
|
+
|| safety.launchctl_calls.some((call) => !isExpectedReleaseUpgradeLaunchctlCall(call))
|
|
248
249
|
|| !Array.isArray(safety?.launchctl_unexpected_calls)
|
|
249
250
|
|| safety.launchctl_unexpected_calls.length > 0)
|
|
250
251
|
blockers.push('install_safety_policy_invalid');
|
|
@@ -367,7 +368,7 @@ function validateDoctorCommandReceipt(command, stage, reportFile, isolation, tar
|
|
|
367
368
|
|| binding.matches_stdout !== true
|
|
368
369
|
|| binding.schema !== 'sks.doctor-status.v3'
|
|
369
370
|
|| binding.ok !== true
|
|
370
|
-
|| binding.root
|
|
371
|
+
|| !sameFilesystemPathSync(binding.root, isolation.workspace))
|
|
371
372
|
blockers.push(`doctor_report_binding_invalid:${stage}`);
|
|
372
373
|
}
|
|
373
374
|
else if (stage !== 'target_doctor' && binding.validation_mode === 'pinned_baseline_stdout_only') {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import crypto from 'node:crypto';
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
+
import { cachedByFingerprint, statFingerprint } from './content-digest-cache.js';
|
|
4
5
|
export function packageDistSnapshot(root, pkg) {
|
|
5
6
|
const distRoot = path.join(root, 'dist');
|
|
6
7
|
if (!fs.existsSync(distRoot))
|
|
@@ -11,6 +12,9 @@ export function packageDistSnapshot(root, pkg) {
|
|
|
11
12
|
const included = files
|
|
12
13
|
.filter((file) => packageFileIncluded(`dist/${path.relative(distRoot, file).split(path.sep).join('/')}`, entries))
|
|
13
14
|
.sort();
|
|
15
|
+
return cachedByFingerprint(root, 'package-dist-snapshot', statFingerprint(root, included), () => computePackageDistSnapshot(distRoot, included));
|
|
16
|
+
}
|
|
17
|
+
function computePackageDistSnapshot(distRoot, included) {
|
|
14
18
|
const hash = crypto.createHash('sha256');
|
|
15
19
|
for (const file of included) {
|
|
16
20
|
const rel = path.relative(distRoot, file).split(path.sep).join('/');
|
|
@@ -48,6 +52,10 @@ export function packageFilesSnapshot(root, pkg) {
|
|
|
48
52
|
}
|
|
49
53
|
}
|
|
50
54
|
const files = [...candidates].filter((file) => packageFileIncluded(file, entries)).sort();
|
|
55
|
+
const fingerprint = statFingerprint(root, [...missingEntries.map((entry) => `missing:${entry}`), ...files]);
|
|
56
|
+
return cachedByFingerprint(root, 'package-files-snapshot', fingerprint, () => computePackageFilesSnapshot(root, files, missingEntries));
|
|
57
|
+
}
|
|
58
|
+
function computePackageFilesSnapshot(root, files, missingEntries) {
|
|
51
59
|
const hash = crypto.createHash('sha256');
|
|
52
60
|
for (const entry of missingEntries.sort()) {
|
|
53
61
|
hash.update(entry);
|
|
@@ -95,6 +103,15 @@ function packageFileIncluded(file, entries) {
|
|
|
95
103
|
}
|
|
96
104
|
return included;
|
|
97
105
|
}
|
|
106
|
+
const PATTERN_REGEXP_CACHE = new Map();
|
|
107
|
+
function packagePatternRegExps(normalized) {
|
|
108
|
+
const cached = PATTERN_REGEXP_CACHE.get(normalized);
|
|
109
|
+
if (cached)
|
|
110
|
+
return cached;
|
|
111
|
+
const compiled = expandBracePatterns(normalized).map((expanded) => globPatternToRegExp(expanded));
|
|
112
|
+
PATTERN_REGEXP_CACHE.set(normalized, compiled);
|
|
113
|
+
return compiled;
|
|
114
|
+
}
|
|
98
115
|
function matchesPackagePattern(file, pattern) {
|
|
99
116
|
const rel = normalizeRel(file);
|
|
100
117
|
const normalized = normalizeRel(pattern);
|
|
@@ -102,8 +119,7 @@ function matchesPackagePattern(file, pattern) {
|
|
|
102
119
|
return false;
|
|
103
120
|
if (!hasGlob(normalized))
|
|
104
121
|
return rel === normalized || rel.startsWith(`${normalized}/`);
|
|
105
|
-
for (const
|
|
106
|
-
const re = globPatternToRegExp(expanded);
|
|
122
|
+
for (const re of packagePatternRegExps(normalized)) {
|
|
107
123
|
if (re.test(rel))
|
|
108
124
|
return true;
|
|
109
125
|
const parts = rel.split('/');
|
|
@@ -3,6 +3,7 @@ import fs from 'node:fs';
|
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { spawnSync } from 'node:child_process';
|
|
5
5
|
import { desktopBridgeReleaseSourceIdentitySha256, inspectDesktopBridgeReleaseEvidence } from './desktop-bridge-release-evidence.js';
|
|
6
|
+
import { cloneBuffer, fileIdentity, inspectionKey, memoizeReleaseInspection, toolIdentity } from './release-inspection-memo.js';
|
|
6
7
|
export const PHYSICAL_RELEASE_GATES_SCHEMA = 'sks.release-physical-gates.v2';
|
|
7
8
|
export const PHYSICAL_RELEASE_GATES_INSPECTION_SCHEMA = 'sks.release-physical-gates-inspection.v2';
|
|
8
9
|
export const PHYSICAL_RELEASE_EVIDENCE_ARTIFACT_MANIFEST_SCHEMA = 'sks.release-physical-evidence-artifact-manifest.v1';
|
|
@@ -375,6 +376,16 @@ function inspectPhysicalEvidenceArtifact(input) {
|
|
|
375
376
|
};
|
|
376
377
|
}
|
|
377
378
|
function verifyGithubArtifactAttestation(input) {
|
|
379
|
+
const key = inspectionKey(input.archivePath, fileIdentity(input.archivePath), toolIdentity('gh'), input.repository, input.trustedWorkflow, input.sourceCommit, input.runId);
|
|
380
|
+
const verdict = memoizeReleaseInspection('github-artifact-attestation', key, () => {
|
|
381
|
+
const blockers = [];
|
|
382
|
+
const invocationUri = computeGithubArtifactAttestation({ ...input, blockers });
|
|
383
|
+
return { invocation_uri: invocationUri, blockers };
|
|
384
|
+
});
|
|
385
|
+
input.blockers.push(...verdict.blockers);
|
|
386
|
+
return verdict.invocation_uri;
|
|
387
|
+
}
|
|
388
|
+
function computeGithubArtifactAttestation(input) {
|
|
378
389
|
const result = spawnSync('gh', [
|
|
379
390
|
'attestation', 'verify', input.archivePath,
|
|
380
391
|
'--repo', input.repository,
|
|
@@ -429,6 +440,15 @@ function readBoundArchive(file) {
|
|
|
429
440
|
}
|
|
430
441
|
}
|
|
431
442
|
function listArchiveFiles(archivePath, blockers) {
|
|
443
|
+
const listing = memoizeReleaseInspection('physical-archive-listing', inspectionKey(archivePath, fileIdentity(archivePath), toolIdentity('tar')), () => {
|
|
444
|
+
const own = [];
|
|
445
|
+
const files = computeArchiveFiles(archivePath, own);
|
|
446
|
+
return { entries: files ? [...files] : null, blockers: own };
|
|
447
|
+
});
|
|
448
|
+
blockers.push(...listing.blockers);
|
|
449
|
+
return listing.entries ? new Map(listing.entries) : null;
|
|
450
|
+
}
|
|
451
|
+
function computeArchiveFiles(archivePath, blockers) {
|
|
432
452
|
const result = spawnSync('tar', ['-tzf', archivePath], { encoding: 'utf8', maxBuffer: 16 * 1024 * 1024 });
|
|
433
453
|
if (result.status !== 0) {
|
|
434
454
|
blockers.push('physical_evidence_artifact_archive_unreadable');
|
|
@@ -455,12 +475,15 @@ function listArchiveFiles(archivePath, blockers) {
|
|
|
455
475
|
return files;
|
|
456
476
|
}
|
|
457
477
|
function readArchiveMember(archivePath, member, label, blockers) {
|
|
458
|
-
const
|
|
459
|
-
|
|
478
|
+
const bytes = memoizeReleaseInspection('physical-archive-member', inspectionKey(archivePath, fileIdentity(archivePath), toolIdentity('tar'), member), () => {
|
|
479
|
+
const result = spawnSync('tar', ['-xOzf', archivePath, member], { encoding: 'buffer', maxBuffer: 64 * 1024 * 1024 });
|
|
480
|
+
return result.status !== 0 || !Buffer.isBuffer(result.stdout) ? null : result.stdout;
|
|
481
|
+
}, (value) => (value ? cloneBuffer(value) : null));
|
|
482
|
+
if (!bytes) {
|
|
460
483
|
blockers.push(`${label}_unreadable`);
|
|
461
484
|
return null;
|
|
462
485
|
}
|
|
463
|
-
return
|
|
486
|
+
return bytes;
|
|
464
487
|
}
|
|
465
488
|
function safeArchivePath(value) {
|
|
466
489
|
if (!value || value.includes('\\') || value.startsWith('/') || value.endsWith('/'))
|
|
@@ -2,6 +2,7 @@ import { spawnSync } from 'node:child_process';
|
|
|
2
2
|
import crypto from 'node:crypto';
|
|
3
3
|
import fs from 'node:fs';
|
|
4
4
|
import path from 'node:path';
|
|
5
|
+
import { cachedByFingerprint, statFingerprint } from './content-digest-cache.js';
|
|
5
6
|
import { packageDistSnapshot, packageFilesSnapshot } from './package-dist-snapshot.js';
|
|
6
7
|
export const RELEASE_AUTHORIZATION_SNAPSHOT_KEYS = Object.freeze([
|
|
7
8
|
'git_commit',
|
|
@@ -44,6 +45,9 @@ function releaseGateHash(root, pkg) {
|
|
|
44
45
|
}
|
|
45
46
|
function releaseSourceSnapshot(root) {
|
|
46
47
|
const files = gitFiles(root).filter(releaseRelevant).sort();
|
|
48
|
+
return cachedByFingerprint(root, 'release-source-snapshot', statFingerprint(root, files), () => computeReleaseSourceSnapshot(root, files));
|
|
49
|
+
}
|
|
50
|
+
function computeReleaseSourceSnapshot(root, files) {
|
|
47
51
|
const hash = crypto.createHash('sha256');
|
|
48
52
|
for (const file of files) {
|
|
49
53
|
const full = path.join(root, file);
|
|
@@ -292,10 +292,14 @@ function writeReleaseCacheBridgeReport(root, row) {
|
|
|
292
292
|
parsed = existing;
|
|
293
293
|
}
|
|
294
294
|
catch { }
|
|
295
|
+
const stamped = { ...row, recorded_at: new Date().toISOString() };
|
|
295
296
|
parsed.records = [
|
|
296
|
-
...parsed.records.filter((item) => item.gate_id !== row.gate_id || item.
|
|
297
|
-
|
|
298
|
-
]
|
|
297
|
+
...parsed.records.filter((item) => item.gate_id !== row.gate_id || item.bridge !== row.bridge),
|
|
298
|
+
stamped
|
|
299
|
+
]
|
|
300
|
+
.sort((a, b) => Date.parse(String(b.recorded_at || 0)) - Date.parse(String(a.recorded_at || 0)) || 0)
|
|
301
|
+
.slice(0, releaseCacheBridgeRecordLimit())
|
|
302
|
+
.sort((a, b) => `${a.gate_id}:${a.bridge}`.localeCompare(`${b.gate_id}:${b.bridge}`));
|
|
299
303
|
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
300
304
|
fs.writeFileSync(file, `${JSON.stringify(parsed, null, 2)}\n`);
|
|
301
305
|
}
|
|
@@ -317,9 +321,29 @@ function writeCacheRecord(file, key, record) {
|
|
|
317
321
|
parsed.schema = RELEASE_GATE_CACHE_V2_SCHEMA;
|
|
318
322
|
parsed.records ||= {};
|
|
319
323
|
parsed.records[key] = record;
|
|
324
|
+
parsed.records = pruneCacheRecords(parsed.records);
|
|
320
325
|
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
321
326
|
fs.writeFileSync(file, `${JSON.stringify(parsed, null, 2)}\n`);
|
|
322
327
|
}
|
|
328
|
+
export function pruneCacheRecords(records, keep = releaseGateCacheRecordLimit()) {
|
|
329
|
+
const entries = Object.entries(records || {});
|
|
330
|
+
if (entries.length <= keep)
|
|
331
|
+
return records;
|
|
332
|
+
const ordered = entries.sort((left, right) => recordedAtMs(right[1]) - recordedAtMs(left[1]));
|
|
333
|
+
return Object.fromEntries(ordered.slice(0, keep));
|
|
334
|
+
}
|
|
335
|
+
export function releaseGateCacheRecordLimit() {
|
|
336
|
+
const configured = Number(process.env.SKS_RELEASE_GATE_CACHE_RECORD_LIMIT);
|
|
337
|
+
return Number.isFinite(configured) && configured > 0 ? Math.floor(configured) : 600;
|
|
338
|
+
}
|
|
339
|
+
export function releaseCacheBridgeRecordLimit() {
|
|
340
|
+
const configured = Number(process.env.SKS_RELEASE_CACHE_BRIDGE_RECORD_LIMIT);
|
|
341
|
+
return Number.isFinite(configured) && configured > 0 ? Math.floor(configured) : 600;
|
|
342
|
+
}
|
|
343
|
+
function recordedAtMs(record) {
|
|
344
|
+
const parsed = Date.parse(String(record?.recorded_at || ''));
|
|
345
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
346
|
+
}
|
|
323
347
|
function hashFileIfPresent(hash, file) {
|
|
324
348
|
if (fs.existsSync(file) && fs.statSync(file).isFile())
|
|
325
349
|
hash.update(fs.readFileSync(file));
|
|
@@ -2,6 +2,8 @@ import crypto from 'node:crypto';
|
|
|
2
2
|
export const RELEASE_GATE_CONTRACT_SCHEMA = 'sks.release-gate-contract.v1';
|
|
3
3
|
export const RELEASE_GATE_CONTRACT_IDS = Object.freeze([
|
|
4
4
|
'architecture:guard',
|
|
5
|
+
'architecture-map:contract',
|
|
6
|
+
'architecture-map:legacy-closure',
|
|
5
7
|
'codex:current:app-server-v2',
|
|
6
8
|
'codex:current:binary-identity',
|
|
7
9
|
'codex:current:capability',
|
|
@@ -10,6 +10,7 @@ import { RELEASE_GATE_NODE_SCHEMA, validateReleaseGateManifest } from './release
|
|
|
10
10
|
import { countReleaseGateResources, defaultReleaseGateBudget, summarizeReleaseGateBudget } from './release-gate-resource-governor.js';
|
|
11
11
|
import { selectAffectedReleaseGates } from './release-gate-affected-selector.js';
|
|
12
12
|
import { computeTriWikiAffectedGraph } from '../triwiki/triwiki-affected-graph.js';
|
|
13
|
+
import { pruneTriWikiProofBank } from '../triwiki/triwiki-proof-bank.js';
|
|
13
14
|
import { executeExtremeSchedule } from './extreme-parallel-scheduler.js';
|
|
14
15
|
import { computeResourceClassBudget } from './resource-class-budget.js';
|
|
15
16
|
import { guardedProcessKill, guardContextForRoute } from '../safety/mutation-guard.js';
|
|
@@ -106,6 +107,7 @@ export async function runReleaseGateDag(input) {
|
|
|
106
107
|
throw new Error(`release_gate_selection_missing_dependencies:${[...new Set(missingSelectedDeps)].sort().join(',')}`);
|
|
107
108
|
const runId = `rg-${new Date().toISOString().replace(/[:.]/g, '-')}-${process.pid}`;
|
|
108
109
|
const retentionBefore = await pruneOldReleaseGateRunDirs(root);
|
|
110
|
+
const proofBankRetention = pruneTriWikiProofBank(root);
|
|
109
111
|
await sweepSksTempDirs(root, { maxAgeHours: 24 }).catch(() => null);
|
|
110
112
|
const reportDir = path.join(root, '.sneakoscope', 'reports', 'release-gates', runId);
|
|
111
113
|
fs.mkdirSync(reportDir, { recursive: true });
|
|
@@ -113,6 +115,7 @@ export async function runReleaseGateDag(input) {
|
|
|
113
115
|
const affectedGraphFile = path.join(reportDir, 'affected-gate-graph.json');
|
|
114
116
|
const completionCertificateFile = path.join(reportDir, 'completion-certificate.json');
|
|
115
117
|
appendReleaseGateJsonl(timeline, { event: 'retention', phase: 'before_run', ...retentionBefore, at: new Date().toISOString() });
|
|
118
|
+
appendReleaseGateJsonl(timeline, { event: 'proof_bank_retention', phase: 'before_run', ...proofBankRetention, at: new Date().toISOString() });
|
|
116
119
|
const started = Date.now();
|
|
117
120
|
const slaMs = Math.max(1, Math.floor(Number(input.slaMs || 300000)));
|
|
118
121
|
const pending = new Map(selected.map((gate) => [gate.id, gate]));
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { SksLruCache } from '../perf/lru-cache.js';
|
|
5
|
+
export const RELEASE_INSPECTION_MEMO_ENV = 'SKS_RELEASE_INSPECTION_MEMO';
|
|
6
|
+
const MAX_ENTRIES_PER_BUCKET = 64;
|
|
7
|
+
const buckets = new Map();
|
|
8
|
+
export function releaseInspectionMemoEnabled() {
|
|
9
|
+
return process.env[RELEASE_INSPECTION_MEMO_ENV] !== '0';
|
|
10
|
+
}
|
|
11
|
+
export function fileIdentity(file) {
|
|
12
|
+
try {
|
|
13
|
+
const stat = fs.lstatSync(file, { bigint: true });
|
|
14
|
+
if (!stat.isFile() || stat.isSymbolicLink())
|
|
15
|
+
return null;
|
|
16
|
+
return `${stat.dev}:${stat.ino}:${stat.size}:${stat.mtimeNs}:${stat.ctimeNs}`;
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export function toolIdentity(command) {
|
|
23
|
+
const searchPath = String(process.env.PATH || '').split(path.delimiter).filter(Boolean);
|
|
24
|
+
for (const dir of searchPath) {
|
|
25
|
+
const candidate = path.join(dir, command);
|
|
26
|
+
let resolved;
|
|
27
|
+
try {
|
|
28
|
+
resolved = fs.realpathSync(candidate);
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
const identity = fileIdentity(resolved);
|
|
34
|
+
if (identity)
|
|
35
|
+
return `${candidate}>${resolved}#${identity}`;
|
|
36
|
+
}
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
export function inspectionKey(...parts) {
|
|
40
|
+
if (parts.some((part) => part === null || part === undefined))
|
|
41
|
+
return null;
|
|
42
|
+
return crypto.createHash('sha256').update(parts.map(String).join('\0')).digest('hex');
|
|
43
|
+
}
|
|
44
|
+
export function memoizeReleaseInspection(bucket, key, compute, clone = defaultClone) {
|
|
45
|
+
if (!key || !releaseInspectionMemoEnabled())
|
|
46
|
+
return compute();
|
|
47
|
+
let cache = buckets.get(bucket);
|
|
48
|
+
if (!cache) {
|
|
49
|
+
cache = new SksLruCache(MAX_ENTRIES_PER_BUCKET);
|
|
50
|
+
buckets.set(bucket, cache);
|
|
51
|
+
}
|
|
52
|
+
const hit = cache.get(key);
|
|
53
|
+
if (hit !== null)
|
|
54
|
+
return clone(hit);
|
|
55
|
+
const value = compute();
|
|
56
|
+
cache.set(key, value);
|
|
57
|
+
return clone(value);
|
|
58
|
+
}
|
|
59
|
+
function defaultClone(value) {
|
|
60
|
+
if (value === null || typeof value !== 'object')
|
|
61
|
+
return value;
|
|
62
|
+
return structuredClone(value);
|
|
63
|
+
}
|
|
64
|
+
export function cloneBuffer(value) {
|
|
65
|
+
return Buffer.from(value);
|
|
66
|
+
}
|
|
67
|
+
export function clearReleaseInspectionMemo() {
|
|
68
|
+
buckets.clear();
|
|
69
|
+
}
|
|
@@ -3,7 +3,12 @@ import fs from 'node:fs';
|
|
|
3
3
|
import os from 'node:os';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { spawnSync } from 'node:child_process';
|
|
6
|
+
import { fileIdentity, inspectionKey, memoizeReleaseInspection, toolIdentity } from './release-inspection-memo.js';
|
|
6
7
|
export function scanTarballTextContents(input) {
|
|
8
|
+
const key = inspectionKey(input.temp_prefix, input.tarball, fileIdentity(input.tarball), toolIdentity('tar'), String(input.max_findings), input.include_allowlisted_count === true ? 'allowlisted' : 'plain', input.patterns.map((pattern) => `${pattern.kind}=${pattern.regex.source}/${pattern.regex.flags}`).join('\0'), String(input.allow_finding || 'no_allowlist'), String(input.finding_blocker));
|
|
9
|
+
return memoizeReleaseInspection('tarball-content-scan', key, () => computeTarballTextContents(input));
|
|
10
|
+
}
|
|
11
|
+
function computeTarballTextContents(input) {
|
|
7
12
|
const temp = fs.mkdtempSync(path.join(os.tmpdir(), input.temp_prefix));
|
|
8
13
|
const findings = [];
|
|
9
14
|
const blockers = [];
|
|
@@ -6,6 +6,7 @@ import { spawnSync } from 'node:child_process';
|
|
|
6
6
|
import { tmpdir } from '../fsx.js';
|
|
7
7
|
import { DEFAULT_MAX_PACK_BYTES, DEFAULT_MAX_UNPACKED_BYTES } from './package-size-budget.js';
|
|
8
8
|
import { readCurrentNpmPackGateArtifacts } from './npm-pack-proof.js';
|
|
9
|
+
import { inspectionKey, memoizeReleaseInspection, toolIdentity } from './release-inspection-memo.js';
|
|
9
10
|
import { scanTarballTextContents } from './release-pack-content-scanner.js';
|
|
10
11
|
import { RELEASE_PACK_COMPARE_SCHEMA, RELEASE_PACK_RECEIPT_SCHEMA } from './release-pack-contract.js';
|
|
11
12
|
import { tarInventory, tarPackageJson, tarUnpackedBytes } from './release-pack-tarball.js';
|
|
@@ -298,12 +299,32 @@ export function inspectLocalReleaseSourceState(root) {
|
|
|
298
299
|
else if (String(fullStatus.stdout || '').split(/\r?\n/).some((line) => line.startsWith('?? '))) {
|
|
299
300
|
blockers.push('npm_pack_untracked_files_present');
|
|
300
301
|
}
|
|
302
|
+
const inventory = memoizeReleaseInspection('npm-pack-source-inventory', inspectionKey(path.resolve(root), head, toolIdentity('git'), toolIdentity('npm'), process.env.SKS_RELEASE_NPM_CACHE || '', String(trackedStatus.status), String(trackedStatus.stdout || ''), String(fullStatus.status), String(fullStatus.stdout || '')), () => packSourceInventory(root));
|
|
303
|
+
const trackedFiles = inventory.tracked_files ? new Set(inventory.tracked_files) : null;
|
|
304
|
+
if (!trackedFiles)
|
|
305
|
+
blockers.push('npm_pack_tracked_file_inventory_unavailable');
|
|
306
|
+
const packedFiles = inventory.packed_files;
|
|
307
|
+
if (!packedFiles)
|
|
308
|
+
blockers.push('npm_pack_dry_run_file_inventory_unavailable');
|
|
309
|
+
const packEligibleUntracked = trackedFiles && packedFiles
|
|
310
|
+
? unique(packedFiles.filter((relative) => !trackedFiles.has(relative) && !isGeneratedDistPackagePath(relative))).sort()
|
|
311
|
+
: [];
|
|
312
|
+
if (packEligibleUntracked.length > 0)
|
|
313
|
+
blockers.push('npm_pack_eligible_untracked_files_present');
|
|
314
|
+
return {
|
|
315
|
+
ok: blockers.length === 0,
|
|
316
|
+
head,
|
|
317
|
+
source_tree_sha256: sourceTreeSha256,
|
|
318
|
+
tracked_changes: trackedChanges,
|
|
319
|
+
pack_eligible_untracked: packEligibleUntracked,
|
|
320
|
+
blockers: unique(blockers)
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
function packSourceInventory(root) {
|
|
301
324
|
const trackedFilesResult = spawnSync('git', ['ls-files', '-z'], { cwd: root, encoding: 'utf8', maxBuffer: 32 * 1024 * 1024 });
|
|
302
325
|
const trackedFiles = trackedFilesResult.status === 0
|
|
303
|
-
?
|
|
326
|
+
? unique(String(trackedFilesResult.stdout || '').split('\0').filter(Boolean).map(normalizePath))
|
|
304
327
|
: null;
|
|
305
|
-
if (!trackedFiles)
|
|
306
|
-
blockers.push('npm_pack_tracked_file_inventory_unavailable');
|
|
307
328
|
const dryRun = spawnSync('npm', ['pack', '--dry-run', '--ignore-scripts', '--json'], {
|
|
308
329
|
cwd: root,
|
|
309
330
|
encoding: 'utf8',
|
|
@@ -323,21 +344,7 @@ export function inspectLocalReleaseSourceState(root) {
|
|
|
323
344
|
packedFiles = null;
|
|
324
345
|
}
|
|
325
346
|
}
|
|
326
|
-
|
|
327
|
-
blockers.push('npm_pack_dry_run_file_inventory_unavailable');
|
|
328
|
-
const packEligibleUntracked = trackedFiles && packedFiles
|
|
329
|
-
? unique(packedFiles.filter((relative) => !trackedFiles.has(relative) && !isGeneratedDistPackagePath(relative))).sort()
|
|
330
|
-
: [];
|
|
331
|
-
if (packEligibleUntracked.length > 0)
|
|
332
|
-
blockers.push('npm_pack_eligible_untracked_files_present');
|
|
333
|
-
return {
|
|
334
|
-
ok: blockers.length === 0,
|
|
335
|
-
head,
|
|
336
|
-
source_tree_sha256: sourceTreeSha256,
|
|
337
|
-
tracked_changes: trackedChanges,
|
|
338
|
-
pack_eligible_untracked: packEligibleUntracked,
|
|
339
|
-
blockers: unique(blockers)
|
|
340
|
-
};
|
|
347
|
+
return { tracked_files: trackedFiles, packed_files: packedFiles };
|
|
341
348
|
}
|
|
342
349
|
export function writeReleaseJson(file, value) {
|
|
343
350
|
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
@@ -536,6 +543,9 @@ function gitHead(root) {
|
|
|
536
543
|
return result.status === 0 ? String(result.stdout || '').trim() : '';
|
|
537
544
|
}
|
|
538
545
|
function gitTreeSha256(root, commit) {
|
|
546
|
+
return memoizeReleaseInspection('git-tree-sha256', inspectionKey(path.resolve(root), commit, toolIdentity('git')), () => computeGitTreeSha256(root, commit));
|
|
547
|
+
}
|
|
548
|
+
function computeGitTreeSha256(root, commit) {
|
|
539
549
|
if (!/^[a-f0-9]{40}$/i.test(commit))
|
|
540
550
|
return '';
|
|
541
551
|
const verified = spawnSync('git', ['rev-parse', '--verify', `${commit}^{commit}`], { cwd: root, encoding: 'utf8' });
|
|
@@ -2,7 +2,11 @@ import fs from 'node:fs';
|
|
|
2
2
|
import os from 'node:os';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { spawnSync } from 'node:child_process';
|
|
5
|
+
import { fileIdentity, inspectionKey, memoizeReleaseInspection, toolIdentity } from './release-inspection-memo.js';
|
|
5
6
|
export function tarInventory(tarball) {
|
|
7
|
+
return memoizeReleaseInspection('tar-inventory', inspectionKey(tarball, fileIdentity(tarball), toolIdentity('tar')), () => computeTarInventory(tarball));
|
|
8
|
+
}
|
|
9
|
+
function computeTarInventory(tarball) {
|
|
6
10
|
const result = spawnSync('tar', ['-tzf', tarball], { encoding: 'utf8', maxBuffer: 32 * 1024 * 1024 });
|
|
7
11
|
if (result.status !== 0)
|
|
8
12
|
return { files: [], blockers: ['tarball_inventory_command_failed'] };
|
|
@@ -28,6 +32,9 @@ export function tarInventory(tarball) {
|
|
|
28
32
|
return { files, blockers };
|
|
29
33
|
}
|
|
30
34
|
export function tarPackageJson(tarball) {
|
|
35
|
+
return memoizeReleaseInspection('tar-package-json', inspectionKey(tarball, fileIdentity(tarball), toolIdentity('tar')), () => computeTarPackageJson(tarball));
|
|
36
|
+
}
|
|
37
|
+
function computeTarPackageJson(tarball) {
|
|
31
38
|
const result = spawnSync('tar', ['-xOzf', tarball, 'package/package.json'], { encoding: 'utf8', maxBuffer: 1024 * 1024 });
|
|
32
39
|
if (result.status !== 0)
|
|
33
40
|
return null;
|
|
@@ -40,6 +47,9 @@ export function tarPackageJson(tarball) {
|
|
|
40
47
|
}
|
|
41
48
|
}
|
|
42
49
|
export function tarUnpackedBytes(tarball) {
|
|
50
|
+
return memoizeReleaseInspection('tar-unpacked-bytes', inspectionKey(tarball, fileIdentity(tarball), toolIdentity('tar')), () => computeTarUnpackedBytes(tarball));
|
|
51
|
+
}
|
|
52
|
+
function computeTarUnpackedBytes(tarball) {
|
|
43
53
|
const temp = fs.mkdtempSync(path.join(os.tmpdir(), 'sks-release-pack-unpacked-'));
|
|
44
54
|
try {
|
|
45
55
|
const result = spawnSync('tar', ['-xzf', tarball, '-C', temp], { encoding: 'utf8', maxBuffer: 32 * 1024 * 1024 });
|
|
@@ -4,6 +4,10 @@ export const RELEASE_UPGRADE_BASELINE_SHA256 = '40a9e89f3a234dfcd32507ab7deeb950
|
|
|
4
4
|
export function releaseUpgradeProofFilename(targetVersion) {
|
|
5
5
|
return `upgrade-${RELEASE_UPGRADE_BASELINE_LABEL}-to-${targetVersion}.json`;
|
|
6
6
|
}
|
|
7
|
+
export function isExpectedReleaseUpgradeLaunchctlCall(call) {
|
|
8
|
+
return /^(?:unsetenv (?:CODEX_LB_API_KEY|OPENROUTER_API_KEY)|print|bootout (?:com\.sneakoscope\.sks-menubar|com\.sneakoscope\.telegram-hub))$/
|
|
9
|
+
.test(String(call));
|
|
10
|
+
}
|
|
7
11
|
export function releaseUpgradeExpectedVersion(stateName, targetVersion) {
|
|
8
12
|
return stateName.startsWith('target_') ? targetVersion : RELEASE_UPGRADE_BASELINE_VERSION;
|
|
9
13
|
}
|
package/dist/core/routes.js
CHANGED
|
@@ -647,7 +647,7 @@ export const COMMAND_CATALOG = [
|
|
|
647
647
|
{ 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.' },
|
|
648
648
|
{ name: 'task', usage: 'sks task run [--sla 5m] [--json]', description: 'Run the normal affected-scope, release-equivalent task verification path.' },
|
|
649
649
|
{ name: 'release', usage: 'sks release affected|full|background|stage [--json]', description: 'Run affected release proof, full release proof, or background release proof explicitly, or drive the staged npm publish up to the human approval step.' },
|
|
650
|
-
{ name: 'triwiki', usage: 'sks triwiki index|affected|proof-bank [--json]', description: 'Inspect TriWiki module cards, gate impact maps, affected graphs,
|
|
650
|
+
{ name: 'triwiki', usage: 'sks triwiki index|affected|proof-bank|graph-status|graph-lint|graph-query|atlas-status|atlas-lint|atlas-list|atlas-show|atlas-why [--json]', description: 'Inspect TriWiki module cards, gate impact maps, affected graphs, proof bank status, and Architecture Map views.' },
|
|
651
651
|
{ name: 'daemon', usage: 'sks daemon status|warm|stop [--json]', description: 'Inspect or warm the local SKS daemon cache state for build/proof reuse.' },
|
|
652
652
|
{ 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.' },
|
|
653
653
|
{ name: 'plan', usage: 'sks plan "task" [--json]', description: 'Write a planning-only artifact under .sneakoscope/plans without editing code.' },
|