sneakoscope 3.1.7 → 3.1.8
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 +9 -2
- 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/.sks-build-stamp.json +4 -4
- package/dist/bin/sks.js +1 -1
- package/dist/commands/doctor.js +71 -1
- package/dist/core/codex-app/codex-skill-sync.js +80 -154
- package/dist/core/codex-native/core-skill-integrity.js +89 -0
- package/dist/core/codex-native/core-skill-manifest.js +156 -0
- package/dist/core/codex-native/native-capability-postcheck.js +35 -0
- package/dist/core/codex-native/native-capability-repair-matrix.js +210 -0
- package/dist/core/codex-native/native-capability-repair.js +47 -0
- package/dist/core/codex-native/native-media-computer-repair.js +5 -0
- package/dist/core/codex-native/project-skill-dedupe.js +109 -0
- package/dist/core/codex-native/skill-name-canonicalizer.js +21 -0
- package/dist/core/codex-native/skill-registry-ledger.js +85 -0
- package/dist/core/commands/basic-cli.js +15 -9
- package/dist/core/config/config-migration-journal.js +27 -0
- package/dist/core/config/managed-config-merge.js +105 -0
- package/dist/core/config/secret-preservation.js +169 -0
- package/dist/core/config/supabase-secret-preservation.js +29 -0
- package/dist/core/doctor/doctor-native-capability-repair.js +48 -0
- package/dist/core/fsx.js +1 -1
- package/dist/core/init.js +5 -1
- package/dist/core/version.js +1 -1
- package/dist/scripts/sizecheck.js +8 -2
- package/dist/scripts/sks-3-1-8-check-lib.js +30 -0
- package/package.json +27 -1
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import { nowIso, readText, sha256, writeJsonAtomic } from '../fsx.js';
|
|
5
|
+
import { buildSksCoreSkillManifest, isSksManagedCoreSkillContent } from './core-skill-manifest.js';
|
|
6
|
+
import { canonicalSkillName, skillDisplayNameFromMarkdown } from './skill-name-canonicalizer.js';
|
|
7
|
+
export async function buildSkillRegistryLedger(input) {
|
|
8
|
+
const root = path.resolve(input.root);
|
|
9
|
+
const codexHome = process.env.CODEX_HOME || path.join(os.homedir(), '.codex');
|
|
10
|
+
const scanRoots = [
|
|
11
|
+
{ root: path.join(root, '.agents', 'skills'), scope: 'project', priority: 100 },
|
|
12
|
+
{ root: path.join(root, '.codex', 'skills'), scope: 'project', priority: 80 },
|
|
13
|
+
{ root: path.join(codexHome, 'skills'), scope: 'codex-home', priority: 60 },
|
|
14
|
+
...(input.extraRoots || [])
|
|
15
|
+
];
|
|
16
|
+
const manifestByName = new Map(buildSksCoreSkillManifest().skills.map((skill) => [skill.canonical_name, skill.content_sha256]));
|
|
17
|
+
const entries = [];
|
|
18
|
+
for (const scanRoot of scanRoots) {
|
|
19
|
+
const rows = await fs.readdir(scanRoot.root, { withFileTypes: true }).catch(() => []);
|
|
20
|
+
for (const row of rows) {
|
|
21
|
+
if (!row.isDirectory())
|
|
22
|
+
continue;
|
|
23
|
+
const skillPath = path.join(scanRoot.root, row.name, 'SKILL.md');
|
|
24
|
+
const text = await readText(skillPath, null);
|
|
25
|
+
if (typeof text !== 'string')
|
|
26
|
+
continue;
|
|
27
|
+
const displayName = skillDisplayNameFromMarkdown(text, row.name);
|
|
28
|
+
const canonical = canonicalSkillName(displayName || row.name);
|
|
29
|
+
const hash = sha256(text);
|
|
30
|
+
const managed = isSksManagedCoreSkillContent(text) || text.includes('BEGIN SKS MANAGED SKILL');
|
|
31
|
+
const expected = manifestByName.get(canonical);
|
|
32
|
+
const status = managed
|
|
33
|
+
? expected && expected === hash ? 'managed-current' : 'managed-drift'
|
|
34
|
+
: 'user-owned';
|
|
35
|
+
entries.push({
|
|
36
|
+
schema: 'sks.skill-registry-entry.v1',
|
|
37
|
+
root: scanRoot.root,
|
|
38
|
+
scope: scanRoot.scope,
|
|
39
|
+
canonical_name: canonical,
|
|
40
|
+
display_name: displayName || row.name,
|
|
41
|
+
path: skillPath,
|
|
42
|
+
managed_by_sks: managed,
|
|
43
|
+
content_sha256: hash,
|
|
44
|
+
active_priority: scanRoot.priority,
|
|
45
|
+
status,
|
|
46
|
+
blockers: []
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const grouped = groupByCanonical(entries);
|
|
51
|
+
const duplicates = [...grouped.entries()].filter(([, group]) => group.length > 1).map(([name]) => name).sort();
|
|
52
|
+
for (const group of grouped.values()) {
|
|
53
|
+
group.sort((a, b) => b.active_priority - a.active_priority || a.path.localeCompare(b.path));
|
|
54
|
+
group.forEach((entry, index) => {
|
|
55
|
+
if (group.length > 1 && index > 0)
|
|
56
|
+
entry.status = entry.status === 'user-owned' ? 'duplicate' : 'duplicate';
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
const blockers = duplicates.map((name) => `duplicate_skill_name:${name}`);
|
|
60
|
+
const ledger = {
|
|
61
|
+
schema: 'sks.skill-registry-ledger.v1',
|
|
62
|
+
generated_at: nowIso(),
|
|
63
|
+
ok: blockers.length === 0,
|
|
64
|
+
root,
|
|
65
|
+
entries,
|
|
66
|
+
duplicate_canonical_names: duplicates,
|
|
67
|
+
blockers
|
|
68
|
+
};
|
|
69
|
+
const reportPath = input.reportPath === null
|
|
70
|
+
? null
|
|
71
|
+
: input.reportPath || path.join(root, '.sneakoscope', 'reports', 'skill-registry-ledger.json');
|
|
72
|
+
if (reportPath)
|
|
73
|
+
await writeJsonAtomic(reportPath, ledger).catch(() => undefined);
|
|
74
|
+
return ledger;
|
|
75
|
+
}
|
|
76
|
+
export function groupByCanonical(entries) {
|
|
77
|
+
const grouped = new Map();
|
|
78
|
+
for (const entry of entries) {
|
|
79
|
+
const current = grouped.get(entry.canonical_name) || [];
|
|
80
|
+
current.push(entry);
|
|
81
|
+
grouped.set(entry.canonical_name, current);
|
|
82
|
+
}
|
|
83
|
+
return grouped;
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=skill-registry-ledger.js.map
|
|
@@ -11,6 +11,7 @@ import { hooksExplainReport } from '../../cli/feature-commands.js';
|
|
|
11
11
|
import { writeSelftestRouteProof } from '../proof/selftest-proof-fixtures.js';
|
|
12
12
|
import { createMission } from '../mission.js';
|
|
13
13
|
import { formatSksUpdateCheckText, runSksUpdateCheck, runSksUpdateNow } from '../update-check.js';
|
|
14
|
+
import { withSecretPreservationGuard } from '../config/config-migration-journal.js';
|
|
14
15
|
export async function helpCommand(args = []) {
|
|
15
16
|
const topic = args[0];
|
|
16
17
|
if (topic)
|
|
@@ -118,12 +119,13 @@ export async function updateCommand(sub = 'check', args = []) {
|
|
|
118
119
|
process.exitCode = 1;
|
|
119
120
|
return;
|
|
120
121
|
}
|
|
121
|
-
const
|
|
122
|
+
const root = await projectRoot();
|
|
123
|
+
const result = await withSecretPreservationGuard(root, 'update-now', async () => runSksUpdateNow({
|
|
122
124
|
version: valueAfter(args, '--version') || valueAfter(args, '-v'),
|
|
123
125
|
dryRun: flag(args, '--dry-run'),
|
|
124
126
|
timeoutMs: 10 * 60 * 1000,
|
|
125
127
|
maxOutputBytes: 128 * 1024
|
|
126
|
-
});
|
|
128
|
+
}));
|
|
127
129
|
if (flag(args, '--json'))
|
|
128
130
|
return printJson(result);
|
|
129
131
|
console.log(`${sksTextLogo()}\n`);
|
|
@@ -140,14 +142,18 @@ export async function updateCommand(sub = 'check', args = []) {
|
|
|
140
142
|
export async function setupCommand(args = []) {
|
|
141
143
|
const root = await projectRoot();
|
|
142
144
|
const installScope = installScopeFromArgs(args);
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
145
|
+
let res = null;
|
|
146
|
+
let cliTools = null;
|
|
147
|
+
await withSecretPreservationGuard(root, 'setup-command', async () => {
|
|
148
|
+
res = await initProject(root, {
|
|
149
|
+
force: flag(args, '--force'),
|
|
150
|
+
installScope,
|
|
151
|
+
localOnly: flag(args, '--local-only'),
|
|
152
|
+
globalCommand: 'sks'
|
|
153
|
+
});
|
|
154
|
+
const { ensureRelatedCliTools } = await import('../../cli/install-helpers.js');
|
|
155
|
+
cliTools = await ensureRelatedCliTools(args);
|
|
148
156
|
});
|
|
149
|
-
const { ensureRelatedCliTools } = await import('../../cli/install-helpers.js');
|
|
150
|
-
const cliTools = await ensureRelatedCliTools(args);
|
|
151
157
|
const result = {
|
|
152
158
|
schema: 'sks.setup.v1',
|
|
153
159
|
ok: true,
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { nowIso, readJson, writeJsonAtomic } from '../fsx.js';
|
|
3
|
+
import { captureSecretPreservationSnapshot, withSecretPreservationGuard } from './secret-preservation.js';
|
|
4
|
+
export { withSecretPreservationGuard };
|
|
5
|
+
export async function writeSecretMigrationJournal(root, operationName, filesTouched = []) {
|
|
6
|
+
const resolvedRoot = path.resolve(root);
|
|
7
|
+
const snapshot = await captureSecretPreservationSnapshot({ root: resolvedRoot });
|
|
8
|
+
const journalPath = path.join(resolvedRoot, '.sneakoscope', 'reports', 'secret-migration-journal.json');
|
|
9
|
+
const current = await readJson(journalPath, { entries: [] }).catch(() => ({ entries: [] }));
|
|
10
|
+
const entries = Array.isArray(current.entries) ? current.entries : [];
|
|
11
|
+
const entry = {
|
|
12
|
+
schema: 'sks.secret-migration-journal-entry.v1',
|
|
13
|
+
generated_at: nowIso(),
|
|
14
|
+
operation: operationName,
|
|
15
|
+
files_touched: filesTouched,
|
|
16
|
+
protected_keys_present: snapshot.fingerprints.filter((fp) => fp.present).map((fp) => ({ key: fp.key, source: fp.source })),
|
|
17
|
+
raw_values_recorded: false
|
|
18
|
+
};
|
|
19
|
+
const journal = {
|
|
20
|
+
schema: 'sks.secret-migration-journal.v1',
|
|
21
|
+
generated_at: nowIso(),
|
|
22
|
+
entries: [...entries, entry]
|
|
23
|
+
};
|
|
24
|
+
await writeJsonAtomic(journalPath, journal);
|
|
25
|
+
return { journal_path: journalPath, entry };
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=config-migration-journal.js.map
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { ensureDir, nowIso, writeJsonAtomic, writeTextAtomic } from '../fsx.js';
|
|
4
|
+
import { isProtectedSecretKey, PROTECTED_SECRET_KEYS } from './supabase-secret-preservation.js';
|
|
5
|
+
export async function writeManagedJsonConfig(file, current, managed) {
|
|
6
|
+
const next = safeMergeObject(current, managed);
|
|
7
|
+
const before = `${JSON.stringify(current, null, 2)}\n`;
|
|
8
|
+
const after = `${JSON.stringify(next, null, 2)}\n`;
|
|
9
|
+
return writeMergedText(file, before, after, 'json', protectedKeysPresent(current));
|
|
10
|
+
}
|
|
11
|
+
export async function writeManagedTomlConfig(file, currentText, managedBlocks) {
|
|
12
|
+
let next = String(currentText || '').trimEnd();
|
|
13
|
+
for (const block of managedBlocks)
|
|
14
|
+
next = upsertTomlBlockPreservingSecrets(next, block);
|
|
15
|
+
return writeMergedText(file, currentText, `${next.trim()}\n`, 'toml', protectedKeysInText(currentText));
|
|
16
|
+
}
|
|
17
|
+
export async function writeManagedEnvConfig(file, currentText, managedLines) {
|
|
18
|
+
const existingKeys = new Set(String(currentText || '').split(/\r?\n/).map((line) => line.match(/^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=/)?.[1]).filter((value) => Boolean(value)));
|
|
19
|
+
const additions = managedLines.filter((line) => {
|
|
20
|
+
const key = line.match(/^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=/)?.[1] || '';
|
|
21
|
+
return key && !existingKeys.has(key) && !isProtectedSecretKey(key);
|
|
22
|
+
});
|
|
23
|
+
const next = additions.length ? `${String(currentText || '').replace(/\s*$/, '\n')}${additions.join('\n')}\n` : String(currentText || '');
|
|
24
|
+
return writeMergedText(file, currentText, next, 'env', protectedKeysInText(currentText));
|
|
25
|
+
}
|
|
26
|
+
export function safeMergeObject(current, managed) {
|
|
27
|
+
const out = { ...current };
|
|
28
|
+
for (const [key, value] of Object.entries(managed)) {
|
|
29
|
+
if (isProtectedSecretKey(key) && current[key] != null)
|
|
30
|
+
continue;
|
|
31
|
+
if (isPlainObject(value) && isPlainObject(current[key]))
|
|
32
|
+
out[key] = safeMergeObject(current[key], value);
|
|
33
|
+
else
|
|
34
|
+
out[key] = value;
|
|
35
|
+
}
|
|
36
|
+
return out;
|
|
37
|
+
}
|
|
38
|
+
function upsertTomlBlockPreservingSecrets(text, block) {
|
|
39
|
+
const header = block.match(/^\s*\[([^\]]+)\]/)?.[1];
|
|
40
|
+
if (!header)
|
|
41
|
+
return text;
|
|
42
|
+
const lines = String(text || '').trimEnd().split('\n');
|
|
43
|
+
const start = lines.findIndex((line) => line.trim() === `[${header}]`);
|
|
44
|
+
const blockLines = block.trim().split('\n');
|
|
45
|
+
if (start === -1)
|
|
46
|
+
return [...lines.filter((line) => line.length), '', ...blockLines].join('\n');
|
|
47
|
+
let end = lines.length;
|
|
48
|
+
for (let index = start + 1; index < lines.length; index += 1) {
|
|
49
|
+
if (/^\s*\[.+\]\s*$/.test(lines[index] || '')) {
|
|
50
|
+
end = index;
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const existingSecretLines = lines.slice(start + 1, end).filter((line) => {
|
|
55
|
+
const key = line.match(/^\s*([A-Za-z0-9_.-]+)\s*=/)?.[1] || '';
|
|
56
|
+
return isProtectedSecretKey(`${header}.${key}`) || isProtectedSecretKey(key);
|
|
57
|
+
});
|
|
58
|
+
lines.splice(start, end - start, ...blockLines, ...existingSecretLines.filter((line) => !blockLines.includes(line)));
|
|
59
|
+
return lines.join('\n').replace(/\n{3,}/g, '\n\n');
|
|
60
|
+
}
|
|
61
|
+
async function writeMergedText(file, before, after, format, preserved) {
|
|
62
|
+
await ensureDir(path.dirname(file));
|
|
63
|
+
let backupPath = null;
|
|
64
|
+
if (before !== after) {
|
|
65
|
+
if (before.trim()) {
|
|
66
|
+
backupPath = `${file}.sks-managed-merge-${Date.now()}.bak`;
|
|
67
|
+
await fs.writeFile(backupPath, before, 'utf8');
|
|
68
|
+
}
|
|
69
|
+
await writeTextAtomic(file, after);
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
schema: 'sks.managed-config-merge.v1',
|
|
73
|
+
generated_at: nowIso(),
|
|
74
|
+
ok: true,
|
|
75
|
+
path: file,
|
|
76
|
+
format,
|
|
77
|
+
changed: before !== after,
|
|
78
|
+
backup_path: backupPath,
|
|
79
|
+
protected_keys_preserved: preserved,
|
|
80
|
+
blockers: []
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
function protectedKeysPresent(value) {
|
|
84
|
+
const found = [];
|
|
85
|
+
for (const key of PROTECTED_SECRET_KEYS)
|
|
86
|
+
if (lookupPath(value, key) != null)
|
|
87
|
+
found.push(String(key));
|
|
88
|
+
return found;
|
|
89
|
+
}
|
|
90
|
+
function protectedKeysInText(text) {
|
|
91
|
+
return PROTECTED_SECRET_KEYS.filter((key) => new RegExp(`(^|\\n)\\s*(?:export\\s+)?${String(key).replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\s*=`).test(text)).map(String);
|
|
92
|
+
}
|
|
93
|
+
function lookupPath(value, dotted) {
|
|
94
|
+
let current = value;
|
|
95
|
+
for (const part of dotted.split('.')) {
|
|
96
|
+
if (!current || typeof current !== 'object' || Array.isArray(current))
|
|
97
|
+
return undefined;
|
|
98
|
+
current = current[part];
|
|
99
|
+
}
|
|
100
|
+
return current;
|
|
101
|
+
}
|
|
102
|
+
function isPlainObject(value) {
|
|
103
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=managed-config-merge.js.map
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import { ensureDir, nowIso, readJson, readText, sha256, writeJsonAtomic } from '../fsx.js';
|
|
5
|
+
import { PROTECTED_SECRET_KEYS, PROTECTED_SUPABASE_ENV_KEYS } from './supabase-secret-preservation.js';
|
|
6
|
+
export async function captureSecretPreservationSnapshot(input) {
|
|
7
|
+
const root = path.resolve(input.root);
|
|
8
|
+
const sources = secretSources(root);
|
|
9
|
+
const fingerprints = [];
|
|
10
|
+
for (const source of sources) {
|
|
11
|
+
const text = await readText(source, null);
|
|
12
|
+
if (typeof text !== 'string')
|
|
13
|
+
continue;
|
|
14
|
+
if (source.endsWith('.json')) {
|
|
15
|
+
const json = await readJson(source, {}).catch(() => ({}));
|
|
16
|
+
fingerprints.push(...fingerprintsFromObject(json, source));
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
fingerprints.push(...fingerprintsFromText(text, source));
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
const snapshot = {
|
|
23
|
+
schema: 'sks.secret-preservation-snapshot.v1',
|
|
24
|
+
generated_at: nowIso(),
|
|
25
|
+
root,
|
|
26
|
+
fingerprints: dedupeFingerprints(fingerprints)
|
|
27
|
+
};
|
|
28
|
+
if (input.artifactPath)
|
|
29
|
+
await writeJsonAtomic(input.artifactPath, snapshot).catch(() => undefined);
|
|
30
|
+
return snapshot;
|
|
31
|
+
}
|
|
32
|
+
export async function withSecretPreservationGuard(root, operationName, fn) {
|
|
33
|
+
const resolvedRoot = path.resolve(root);
|
|
34
|
+
const reportDir = path.join(resolvedRoot, '.sneakoscope', 'reports');
|
|
35
|
+
await ensureDir(reportDir);
|
|
36
|
+
const beforePath = path.join(reportDir, 'secret-preservation-before.json');
|
|
37
|
+
const afterPath = path.join(reportDir, 'secret-preservation-after.json');
|
|
38
|
+
const guardPath = path.join(reportDir, 'secret-preservation-guard.json');
|
|
39
|
+
const before = await captureSecretPreservationSnapshot({ root: resolvedRoot, artifactPath: beforePath });
|
|
40
|
+
let result;
|
|
41
|
+
try {
|
|
42
|
+
result = await fn();
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
await writeJsonAtomic(guardPath, {
|
|
46
|
+
schema: 'sks.secret-preservation-guard.v1',
|
|
47
|
+
generated_at: nowIso(),
|
|
48
|
+
ok: false,
|
|
49
|
+
operation: operationName,
|
|
50
|
+
before_path: beforePath,
|
|
51
|
+
after_path: null,
|
|
52
|
+
restored_keys_count: 0,
|
|
53
|
+
missing_after: [],
|
|
54
|
+
raw_values_recorded: false,
|
|
55
|
+
operation_error: err instanceof Error ? err.message : String(err)
|
|
56
|
+
}).catch(() => undefined);
|
|
57
|
+
throw err;
|
|
58
|
+
}
|
|
59
|
+
const after = await captureSecretPreservationSnapshot({ root: resolvedRoot, artifactPath: afterPath });
|
|
60
|
+
const missing = missingProtectedSecrets(before, after);
|
|
61
|
+
const report = {
|
|
62
|
+
schema: 'sks.secret-preservation-guard.v1',
|
|
63
|
+
generated_at: nowIso(),
|
|
64
|
+
ok: missing.length === 0,
|
|
65
|
+
operation: operationName,
|
|
66
|
+
before_path: beforePath,
|
|
67
|
+
after_path: afterPath,
|
|
68
|
+
restored_keys_count: 0,
|
|
69
|
+
missing_after: missing,
|
|
70
|
+
raw_values_recorded: false
|
|
71
|
+
};
|
|
72
|
+
await writeJsonAtomic(guardPath, report).catch(() => undefined);
|
|
73
|
+
if (missing.length) {
|
|
74
|
+
throw new Error(`secret_preservation_failed:${missing.map((item) => `${item.source}:${item.key}`).join(',')}`);
|
|
75
|
+
}
|
|
76
|
+
return result;
|
|
77
|
+
}
|
|
78
|
+
export function missingProtectedSecrets(before, after) {
|
|
79
|
+
const afterMap = new Map(after.fingerprints.filter((fp) => fp.present).map((fp) => [`${fp.source}\0${fp.key}`, fp]));
|
|
80
|
+
return before.fingerprints
|
|
81
|
+
.filter((fp) => fp.present && fp.value_sha256)
|
|
82
|
+
.filter((fp) => !afterMap.has(`${fp.source}\0${fp.key}`))
|
|
83
|
+
.map((fp) => ({ key: fp.key, source: fp.source }));
|
|
84
|
+
}
|
|
85
|
+
function secretSources(root) {
|
|
86
|
+
const home = process.env.HOME || os.homedir();
|
|
87
|
+
return [
|
|
88
|
+
'.env',
|
|
89
|
+
'.env.local',
|
|
90
|
+
'.env.development',
|
|
91
|
+
'.env.production',
|
|
92
|
+
'.sneakoscope/config.json',
|
|
93
|
+
'.codex/config.toml'
|
|
94
|
+
].map((rel) => path.join(root, rel)).concat(path.join(home, '.codex', 'config.toml'));
|
|
95
|
+
}
|
|
96
|
+
function fingerprintsFromText(text, source) {
|
|
97
|
+
const rows = [];
|
|
98
|
+
for (const key of PROTECTED_SECRET_KEYS) {
|
|
99
|
+
const value = readAssignment(text, key);
|
|
100
|
+
if (!value)
|
|
101
|
+
continue;
|
|
102
|
+
rows.push(fingerprint(String(key), source, value));
|
|
103
|
+
}
|
|
104
|
+
for (const envKey of PROTECTED_SUPABASE_ENV_KEYS) {
|
|
105
|
+
const value = readAssignment(text, envKey);
|
|
106
|
+
if (value)
|
|
107
|
+
rows.push(fingerprint(envKey, source, value));
|
|
108
|
+
}
|
|
109
|
+
return rows;
|
|
110
|
+
}
|
|
111
|
+
function fingerprintsFromObject(value, source) {
|
|
112
|
+
const flat = flattenObject(value);
|
|
113
|
+
const rows = [];
|
|
114
|
+
for (const [key, raw] of Object.entries(flat)) {
|
|
115
|
+
if (!PROTECTED_SECRET_KEYS.includes(key))
|
|
116
|
+
continue;
|
|
117
|
+
rows.push(fingerprint(key, source, String(raw)));
|
|
118
|
+
}
|
|
119
|
+
return rows;
|
|
120
|
+
}
|
|
121
|
+
function fingerprint(key, source, value) {
|
|
122
|
+
return {
|
|
123
|
+
key,
|
|
124
|
+
source,
|
|
125
|
+
present: Boolean(value),
|
|
126
|
+
redacted_preview: redactPreview(value),
|
|
127
|
+
value_sha256: value ? sha256(value) : null
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
function readAssignment(text, key) {
|
|
131
|
+
const escaped = key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
132
|
+
const re = new RegExp(`^\\s*(?:export\\s+)?${escaped.replace(/\\\./g, '\\s*\\.\\s*')}\\s*=\\s*(.+?)\\s*$`, 'm');
|
|
133
|
+
const raw = String(text || '').match(re)?.[1]?.trim() || '';
|
|
134
|
+
return unquote(raw);
|
|
135
|
+
}
|
|
136
|
+
function unquote(value) {
|
|
137
|
+
const trimmed = String(value || '').trim().replace(/\s+#.*$/, '');
|
|
138
|
+
if ((trimmed.startsWith('"') && trimmed.endsWith('"')) || (trimmed.startsWith("'") && trimmed.endsWith("'")))
|
|
139
|
+
return trimmed.slice(1, -1);
|
|
140
|
+
return trimmed;
|
|
141
|
+
}
|
|
142
|
+
function redactPreview(value) {
|
|
143
|
+
const text = String(value || '');
|
|
144
|
+
if (!text)
|
|
145
|
+
return '';
|
|
146
|
+
const head = text.slice(0, Math.min(3, text.length));
|
|
147
|
+
const tail = text.length > 6 ? text.slice(-3) : '';
|
|
148
|
+
return `${head}...${tail || 'redacted'}(${text.length})`;
|
|
149
|
+
}
|
|
150
|
+
function flattenObject(value, prefix = '') {
|
|
151
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
152
|
+
return {};
|
|
153
|
+
const out = {};
|
|
154
|
+
for (const [key, child] of Object.entries(value)) {
|
|
155
|
+
const nextKey = prefix ? `${prefix}.${key}` : key;
|
|
156
|
+
if (child && typeof child === 'object' && !Array.isArray(child))
|
|
157
|
+
Object.assign(out, flattenObject(child, nextKey));
|
|
158
|
+
else if (child != null)
|
|
159
|
+
out[nextKey] = String(child);
|
|
160
|
+
}
|
|
161
|
+
return out;
|
|
162
|
+
}
|
|
163
|
+
function dedupeFingerprints(fingerprints) {
|
|
164
|
+
const byKey = new Map();
|
|
165
|
+
for (const fp of fingerprints)
|
|
166
|
+
byKey.set(`${fp.source}\0${fp.key}`, fp);
|
|
167
|
+
return [...byKey.values()].sort((a, b) => a.source.localeCompare(b.source) || a.key.localeCompare(b.key));
|
|
168
|
+
}
|
|
169
|
+
//# sourceMappingURL=secret-preservation.js.map
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export const PROTECTED_SUPABASE_ENV_KEYS = [
|
|
2
|
+
'SUPABASE_URL',
|
|
3
|
+
'SUPABASE_ANON_KEY',
|
|
4
|
+
'SUPABASE_SERVICE_ROLE_KEY',
|
|
5
|
+
'NEXT_PUBLIC_SUPABASE_URL',
|
|
6
|
+
'NEXT_PUBLIC_SUPABASE_ANON_KEY',
|
|
7
|
+
'VITE_SUPABASE_URL',
|
|
8
|
+
'VITE_SUPABASE_ANON_KEY',
|
|
9
|
+
'PUBLIC_SUPABASE_URL',
|
|
10
|
+
'PUBLIC_SUPABASE_ANON_KEY'
|
|
11
|
+
];
|
|
12
|
+
export const PROTECTED_SUPABASE_CONFIG_PATHS = [
|
|
13
|
+
'supabase.url',
|
|
14
|
+
'supabase.anon_key',
|
|
15
|
+
'supabase.service_role_key',
|
|
16
|
+
'mcp.supabase.url',
|
|
17
|
+
'mcp.supabase.token',
|
|
18
|
+
'mcp.supabase.access_token',
|
|
19
|
+
'mcp.supabase.service_role_key'
|
|
20
|
+
];
|
|
21
|
+
export const PROTECTED_SECRET_KEYS = [
|
|
22
|
+
...PROTECTED_SUPABASE_ENV_KEYS,
|
|
23
|
+
...PROTECTED_SUPABASE_CONFIG_PATHS
|
|
24
|
+
];
|
|
25
|
+
export function isProtectedSecretKey(key) {
|
|
26
|
+
const normalized = String(key || '').trim();
|
|
27
|
+
return PROTECTED_SECRET_KEYS.some((candidate) => candidate === normalized);
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=supabase-secret-preservation.js.map
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { nowIso, writeJsonAtomic } from '../fsx.js';
|
|
3
|
+
import { syncCoreSkillsIntegrity } from '../codex-native/core-skill-integrity.js';
|
|
4
|
+
import { dedupeProjectSkills } from '../codex-native/project-skill-dedupe.js';
|
|
5
|
+
import { repairNativeCapabilities } from '../codex-native/native-capability-repair.js';
|
|
6
|
+
import { withSecretPreservationGuard } from '../config/config-migration-journal.js';
|
|
7
|
+
export async function runDoctorNativeCapabilityRepair(input) {
|
|
8
|
+
const root = path.resolve(input.root);
|
|
9
|
+
const operation = async () => {
|
|
10
|
+
const coreSkills = await syncCoreSkillsIntegrity({ root, apply: input.fix });
|
|
11
|
+
const skillDedupe = await dedupeProjectSkills({
|
|
12
|
+
root,
|
|
13
|
+
fix: input.fix,
|
|
14
|
+
yes: input.yes,
|
|
15
|
+
quarantineUserDuplicates: (input.flags || []).includes('--quarantine-user-duplicate-skills')
|
|
16
|
+
});
|
|
17
|
+
const nativeCapabilities = await repairNativeCapabilities({
|
|
18
|
+
root,
|
|
19
|
+
fix: input.fix,
|
|
20
|
+
yes: input.yes,
|
|
21
|
+
allowManualInstructions: true
|
|
22
|
+
});
|
|
23
|
+
const blockers = [
|
|
24
|
+
...(coreSkills.blockers || []),
|
|
25
|
+
...(skillDedupe.blockers || []),
|
|
26
|
+
...(nativeCapabilities.blockers || [])
|
|
27
|
+
];
|
|
28
|
+
const report = {
|
|
29
|
+
schema: 'sks.doctor-native-capability-repair.v1',
|
|
30
|
+
generated_at: nowIso(),
|
|
31
|
+
ok: blockers.length === 0,
|
|
32
|
+
root,
|
|
33
|
+
fix: input.fix,
|
|
34
|
+
yes: input.yes,
|
|
35
|
+
core_skills: coreSkills,
|
|
36
|
+
skill_dedupe: skillDedupe,
|
|
37
|
+
native_capabilities: nativeCapabilities,
|
|
38
|
+
secret_preservation_guard: '.sneakoscope/reports/secret-preservation-guard.json',
|
|
39
|
+
blockers
|
|
40
|
+
};
|
|
41
|
+
await writeJsonAtomic(path.join(root, '.sneakoscope', 'reports', 'doctor-native-capability-repair.json'), report).catch(() => undefined);
|
|
42
|
+
return report;
|
|
43
|
+
};
|
|
44
|
+
if (!input.fix)
|
|
45
|
+
return operation();
|
|
46
|
+
return withSecretPreservationGuard(root, 'doctor-native-capability-repair', operation);
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=doctor-native-capability-repair.js.map
|
package/dist/core/fsx.js
CHANGED
|
@@ -5,7 +5,7 @@ import os from 'node:os';
|
|
|
5
5
|
import crypto from 'node:crypto';
|
|
6
6
|
import { spawn } from 'node:child_process';
|
|
7
7
|
import { fileURLToPath } from 'node:url';
|
|
8
|
-
export const PACKAGE_VERSION = '3.1.
|
|
8
|
+
export const PACKAGE_VERSION = '3.1.8';
|
|
9
9
|
export const DEFAULT_PROCESS_TAIL_BYTES = 256 * 1024;
|
|
10
10
|
export const DEFAULT_PROCESS_TIMEOUT_MS = 30 * 60 * 1000;
|
|
11
11
|
export function nowIso() {
|
package/dist/core/init.js
CHANGED
|
@@ -11,6 +11,7 @@ import { AWESOME_DESIGN_MD_REFERENCE, CODEX_APP_IMAGE_GENERATION_DOC_URL, CODEX_
|
|
|
11
11
|
import { SKILL_DREAM_POLICY, skillDreamPolicyText } from './skill-forge.js';
|
|
12
12
|
import { CODEX_HOOK_EVENT_STATE_KEYS } from './codex-compat/codex-hook-events.js';
|
|
13
13
|
import { codexCommandHookCurrentHash } from './codex-hooks/codex-hook-hash.js';
|
|
14
|
+
import { buildSksCoreSkillManifest, isCoreSkillName, renderCoreSkillTemplate } from './codex-native/core-skill-manifest.js';
|
|
14
15
|
const REFLECTION_MEMORY_PATH = '.sneakoscope/memory/q2_facts/post-route-reflection.md';
|
|
15
16
|
const SKS_GENERATED_GIT_PATTERNS = [
|
|
16
17
|
'.sneakoscope/missions/',
|
|
@@ -1083,9 +1084,12 @@ export async function installSkills(root) {
|
|
|
1083
1084
|
'design-ui-editor': `---\nname: design-ui-editor\ndescription: Legacy fallback UI/UX editor for existing design.md systems when Product Design plugin is unavailable.\n---\n\nUse Product Design plugin first. When falling back, read \`design.md\`, inspect relevant UI/assets/tests, consult getdesign-reference when improving the design system, apply the smallest design-system-conformant change, use imagegen for image/logo/raster assets, and verify render quality. ${productDesignPluginPolicyText()} ${CODEX_IMAGEGEN_REQUIRED_POLICY} If design.md is missing and Product Design is unavailable, use design-system-builder as fallback.\n`,
|
|
1084
1085
|
'design-artifact-expert': `---\nname: design-artifact-expert\ndescription: Legacy fallback for high-fidelity HTML/UI/prototype artifacts when Product Design plugin cannot be used.\n---\n\nUse Product Design plugin first for design/UI/prototype work. When falling back, read design.md when present, consult getdesign-reference for design-system grounding, build the usable artifact first, preserve state, verify overlap/readability/responsiveness, and use imagegen for required assets. ${productDesignPluginPolicyText()} ${CODEX_IMAGEGEN_REQUIRED_POLICY}\n`
|
|
1085
1086
|
};
|
|
1087
|
+
for (const skill of buildSksCoreSkillManifest().skills) {
|
|
1088
|
+
skills[skill.canonical_name] = renderCoreSkillTemplate(skill.canonical_name);
|
|
1089
|
+
}
|
|
1086
1090
|
for (const [name, content] of Object.entries(skills)) {
|
|
1087
1091
|
const dir = path.join(root, '.agents', 'skills', name);
|
|
1088
|
-
const skillContent = enrichSkillContent(name, content);
|
|
1092
|
+
const skillContent = isCoreSkillName(name) ? content : enrichSkillContent(name, content);
|
|
1089
1093
|
await ensureDir(dir);
|
|
1090
1094
|
await writeTextAtomic(path.join(dir, 'SKILL.md'), `${skillContent.trim()}\n`);
|
|
1091
1095
|
await writeSkillMetadata(dir, name);
|
package/dist/core/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const PACKAGE_VERSION = '3.1.
|
|
1
|
+
export const PACKAGE_VERSION = '3.1.8';
|
|
2
2
|
//# sourceMappingURL=version.js.map
|
|
@@ -7,11 +7,15 @@ import path from 'node:path';
|
|
|
7
7
|
import { fileURLToPath } from 'node:url';
|
|
8
8
|
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..');
|
|
9
9
|
const limits = {
|
|
10
|
-
packedBytes: Number(process.env.SKS_MAX_PACK_BYTES ||
|
|
11
|
-
unpackedBytes: Number(process.env.SKS_MAX_UNPACKED_BYTES ||
|
|
10
|
+
packedBytes: Number(process.env.SKS_MAX_PACK_BYTES || 1536 * 1024),
|
|
11
|
+
unpackedBytes: Number(process.env.SKS_MAX_UNPACKED_BYTES || 6 * 1024 * 1024),
|
|
12
12
|
packFiles: Number(process.env.SKS_MAX_PACK_FILES || 1200),
|
|
13
13
|
trackedFileBytes: Number(process.env.SKS_MAX_TRACKED_FILE_BYTES || 384 * 1024)
|
|
14
14
|
};
|
|
15
|
+
const trackedFileSizeAllowlist = new Set([
|
|
16
|
+
// Historical source documentation export; not included in the npm package payload.
|
|
17
|
+
'docs/sks-local-llm-mode/exports/sks-local-llm-mode-deck.pdf'
|
|
18
|
+
]);
|
|
15
19
|
const npmBin = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
16
20
|
function fail(message, detail = '') {
|
|
17
21
|
console.error(`Size check failed: ${message}`);
|
|
@@ -40,6 +44,8 @@ function checkTrackedFiles() {
|
|
|
40
44
|
const files = result.stdout.toString('utf8').split('\0').filter(Boolean);
|
|
41
45
|
const oversized = [];
|
|
42
46
|
for (const file of files) {
|
|
47
|
+
if (trackedFileSizeAllowlist.has(file))
|
|
48
|
+
continue;
|
|
43
49
|
let stat;
|
|
44
50
|
try {
|
|
45
51
|
stat = fs.statSync(path.join(root, file));
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { assertGate, emitGate } from './sks-1-18-gate-lib.js';
|
|
5
|
+
import { renderCoreSkillTemplate } from '../core/codex-native/core-skill-manifest.js';
|
|
6
|
+
export { assertGate, emitGate };
|
|
7
|
+
export async function makeTempRoot(prefix) {
|
|
8
|
+
const root = await fs.mkdtemp(path.join(os.tmpdir(), prefix));
|
|
9
|
+
await fs.mkdir(path.join(root, '.sneakoscope', 'reports'), { recursive: true });
|
|
10
|
+
await fs.mkdir(path.join(root, '.sneakoscope', 'locks'), { recursive: true });
|
|
11
|
+
return root;
|
|
12
|
+
}
|
|
13
|
+
export async function writeText(file, text) {
|
|
14
|
+
await fs.mkdir(path.dirname(file), { recursive: true });
|
|
15
|
+
await fs.writeFile(file, text, 'utf8');
|
|
16
|
+
}
|
|
17
|
+
export async function readJson(file) {
|
|
18
|
+
return JSON.parse(await fs.readFile(file, 'utf8'));
|
|
19
|
+
}
|
|
20
|
+
export async function writeManagedCoreSkill(root, relRoot, name) {
|
|
21
|
+
const file = path.join(root, relRoot, name, 'SKILL.md');
|
|
22
|
+
await writeText(file, renderCoreSkillTemplate(name));
|
|
23
|
+
return file;
|
|
24
|
+
}
|
|
25
|
+
export async function writeUserSkill(root, relRoot, name, display = name) {
|
|
26
|
+
const file = path.join(root, relRoot, name, 'SKILL.md');
|
|
27
|
+
await writeText(file, `---\nname: ${display}\ndescription: user-authored fixture\n---\n\nUser skill fixture.\n`);
|
|
28
|
+
return file;
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=sks-3-1-8-check-lib.js.map
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sneakoscope",
|
|
3
3
|
"displayName": "ㅅㅋㅅ",
|
|
4
|
-
"version": "3.1.
|
|
4
|
+
"version": "3.1.8",
|
|
5
5
|
"description": "Sneakoscope Codex: fast proof-first Codex trust layer with image-based Voxel TriWiki.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"homepage": "https://github.com/mandarange/Sneakoscope-Codex#readme",
|
|
@@ -617,6 +617,32 @@
|
|
|
617
617
|
"naruto:zellij-dynamic-right-column": "node ./dist/scripts/naruto-zellij-dynamic-right-column-check.js",
|
|
618
618
|
"git:worktree-integration-primary-runtime": "node ./dist/scripts/git-worktree-integration-primary-runtime-check.js",
|
|
619
619
|
"agent:role-config-repair": "node ./dist/scripts/agent-role-config-repair-check.js",
|
|
620
|
+
"core-skill:manifest": "node ./dist/scripts/core-skill-manifest-check.js",
|
|
621
|
+
"core-skill:immutable-sync": "node ./dist/scripts/core-skill-immutable-sync-check.js",
|
|
622
|
+
"core-skill:no-drift": "node ./dist/scripts/core-skill-no-drift-check.js",
|
|
623
|
+
"core-skill:integrity-blackbox": "node ./dist/scripts/core-skill-integrity-blackbox.js",
|
|
624
|
+
"skill:name-canonicalizer": "node ./dist/scripts/skill-name-canonicalizer-check.js",
|
|
625
|
+
"skill:registry-ledger": "node ./dist/scripts/skill-registry-ledger-check.js",
|
|
626
|
+
"skill:dedupe": "node ./dist/scripts/project-skill-dedupe-check.js",
|
|
627
|
+
"skill:sync-atomic": "node ./dist/scripts/skill-sync-atomic-check.js",
|
|
628
|
+
"skill:dedupe-blackbox": "node ./dist/scripts/project-skill-dedupe-blackbox.js",
|
|
629
|
+
"native-capability:repair-matrix": "node ./dist/scripts/native-capability-repair-matrix-check.js",
|
|
630
|
+
"native-capability:repair": "node ./dist/scripts/native-capability-repair-check.js",
|
|
631
|
+
"native-capability:postcheck": "node ./dist/scripts/native-capability-postcheck-check.js",
|
|
632
|
+
"native:image-generation-repair": "node ./dist/scripts/native-image-generation-repair-check.js",
|
|
633
|
+
"native:computer-use-repair": "node ./dist/scripts/native-computer-use-repair-check.js",
|
|
634
|
+
"native:chrome-web-review-repair": "node ./dist/scripts/native-chrome-web-review-repair-check.js",
|
|
635
|
+
"native:app-screenshot-repair": "node ./dist/scripts/native-app-screenshot-repair-check.js",
|
|
636
|
+
"doctor:native-capability-repair": "node ./dist/scripts/doctor-native-capability-repair-check.js",
|
|
637
|
+
"doctor:native-repair-output": "node ./dist/scripts/doctor-native-repair-output-check.js",
|
|
638
|
+
"doctor:native-capability-repair-blackbox": "node ./dist/scripts/doctor-native-capability-repair-blackbox.js",
|
|
639
|
+
"secret:preservation": "node ./dist/scripts/secret-preservation-check.js",
|
|
640
|
+
"config:managed-merge": "node ./dist/scripts/managed-config-merge-check.js",
|
|
641
|
+
"secret:preservation-guard": "node ./dist/scripts/secret-preservation-guard-check.js",
|
|
642
|
+
"secret:supabase-preservation-blackbox": "node ./dist/scripts/supabase-secret-preservation-blackbox.js",
|
|
643
|
+
"update:preserves-supabase-keys": "node ./dist/scripts/update-preserves-supabase-keys-blackbox.js",
|
|
644
|
+
"update:secret-preservation-guard": "node ./dist/scripts/update-secret-preservation-guard-check.js",
|
|
645
|
+
"update:secret-migration-journal": "node ./dist/scripts/update-secret-migration-journal-check.js",
|
|
620
646
|
"release:dag-full-coverage": "node ./dist/scripts/release-dag-full-coverage-check.js",
|
|
621
647
|
"release:cache-glob-hashing": "node ./dist/scripts/release-cache-glob-hashing-check.js",
|
|
622
648
|
"release:check:affected": "npm run build --silent && node ./dist/scripts/release-gate-dag-runner.js --preset affected --changed-since auto",
|