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
package/README.md
CHANGED
|
@@ -35,7 +35,14 @@ Set up this agent project with Sneakoscope Codex. Use [[mandarange/Sneakoscope-C
|
|
|
35
35
|
|
|
36
36
|
## 🚀 Current Release
|
|
37
37
|
|
|
38
|
-
SKS **3.1.
|
|
38
|
+
SKS **3.1.8** hardens update/setup/doctor safety around four operator-facing failure modes: immutable core skills, duplicate project skills, real native capability repair status, and Supabase/secret preservation.
|
|
39
|
+
|
|
40
|
+
What changed in 3.1.8:
|
|
41
|
+
|
|
42
|
+
- **Core SKS skills are content-addressed and immutable.** The eight built-in route skills now have a manifest and no-drift gates; setup/update/doctor may install missing managed copies or restore corrupted managed copies, but they do not overwrite user-authored collisions.
|
|
43
|
+
- **Duplicate skills are detected and repaired safely.** Canonical skill names collapse variants such as `Loop`, `loop`, and `loop/SKILL.md`; SKS-managed duplicates can be quarantined automatically, while user-authored duplicates require explicit confirmation.
|
|
44
|
+
- **`sks doctor --fix` reports native capability truthfully.** Image generation, image follow-up edit paths, Computer Use, Chrome/web review, app screenshots, app handoff, and image path exposure now run through a repair matrix and postcheck instead of capability assumptions.
|
|
45
|
+
- **Supabase keys survive setup/update/doctor.** Protected secret surfaces are fingerprinted before and after guarded operations; reports store only redacted previews and hashes, never raw values.
|
|
39
46
|
|
|
40
47
|
SKS 3.0.0 was the parallel-runtime stabilization release. The whole live-swarm experience — what you actually *see* while 5, 20, or 100 workers run — was rebuilt and proven end-to-end.
|
|
41
48
|
|
|
@@ -622,7 +629,7 @@ TriWiki is intentionally sparse: `sks wiki sweep` records demote, soft-forget, a
|
|
|
622
629
|
|
|
623
630
|
```sh
|
|
624
631
|
sks codex-native status --json
|
|
625
|
-
sks codex-native invocation-plan --route
|
|
632
|
+
sks codex-native invocation-plan --route Loop --capability agent-role --json
|
|
626
633
|
sks codex-native init-deep --apply --directory-local --json
|
|
627
634
|
```
|
|
628
635
|
|
|
@@ -4,7 +4,7 @@ use std::io::{self, Read, Seek, SeekFrom};
|
|
|
4
4
|
fn main() {
|
|
5
5
|
let mut args = std::env::args().skip(1);
|
|
6
6
|
match args.next().as_deref() {
|
|
7
|
-
Some("--version") => println!("sks-rs 3.1.
|
|
7
|
+
Some("--version") => println!("sks-rs 3.1.8"),
|
|
8
8
|
Some("compact-info") => {
|
|
9
9
|
let mut input = String::new();
|
|
10
10
|
let _ = io::stdin().read_to_string(&mut input);
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema": "sks.dist-build-stamp.v1",
|
|
3
3
|
"package_name": "sneakoscope",
|
|
4
|
-
"package_version": "3.1.
|
|
5
|
-
"source_digest": "
|
|
6
|
-
"source_file_count":
|
|
7
|
-
"built_at_source_time":
|
|
4
|
+
"package_version": "3.1.8",
|
|
5
|
+
"source_digest": "94742dc3eb63f280df98aa286ac2a94a14fd140f27c89db85880e4f92c630454",
|
|
6
|
+
"source_file_count": 2602,
|
|
7
|
+
"built_at_source_time": 1781513577024
|
|
8
8
|
}
|
package/dist/bin/sks.js
CHANGED
package/dist/commands/doctor.js
CHANGED
|
@@ -27,6 +27,7 @@ import { runDoctorZellijRepair, doctorZellijRepairConsoleLine } from '../core/do
|
|
|
27
27
|
import { buildCodexAppHarnessMatrix } from '../core/codex-app/codex-app-harness-matrix.js';
|
|
28
28
|
import { buildCodexNativeFeatureMatrix } from '../core/codex-native/codex-native-feature-broker.js';
|
|
29
29
|
import { repairCodexNativeManagedAssets } from '../core/codex-native/codex-native-repair-transaction.js';
|
|
30
|
+
import { runDoctorNativeCapabilityRepair } from '../core/doctor/doctor-native-capability-repair.js';
|
|
30
31
|
export async function run(_command, args = []) {
|
|
31
32
|
const doctorFix = flag(args, '--fix');
|
|
32
33
|
let setupRepair = null;
|
|
@@ -69,6 +70,23 @@ export async function run(_command, args = []) {
|
|
|
69
70
|
};
|
|
70
71
|
}
|
|
71
72
|
const root = await projectRoot();
|
|
73
|
+
const doctorNativeCapabilityRepair = await runDoctorNativeCapabilityRepair({
|
|
74
|
+
root,
|
|
75
|
+
fix: doctorFix || flag(args, '--repair-native-capabilities'),
|
|
76
|
+
yes: flag(args, '--yes') || flag(args, '-y'),
|
|
77
|
+
flags: args.map((arg) => String(arg))
|
|
78
|
+
}).catch((err) => ({
|
|
79
|
+
schema: 'sks.doctor-native-capability-repair.v1',
|
|
80
|
+
ok: false,
|
|
81
|
+
root,
|
|
82
|
+
fix: doctorFix,
|
|
83
|
+
yes: flag(args, '--yes') || flag(args, '-y'),
|
|
84
|
+
core_skills: null,
|
|
85
|
+
skill_dedupe: null,
|
|
86
|
+
native_capabilities: null,
|
|
87
|
+
secret_preservation_guard: '.sneakoscope/reports/secret-preservation-guard.json',
|
|
88
|
+
blockers: [err?.message || String(err)]
|
|
89
|
+
}));
|
|
72
90
|
const codexBin = readOption(args, '--codex-bin', process.env.SKS_DOCTOR_CODEX_BIN || '');
|
|
73
91
|
const configProbeOpts = {
|
|
74
92
|
codexProbe: flag(args, '--fix') || flag(args, '--actual-codex') || Boolean(codexBin),
|
|
@@ -305,7 +323,7 @@ export async function run(_command, args = []) {
|
|
|
305
323
|
ready,
|
|
306
324
|
sneakoscope: { ok: await exists(`${root}/.sneakoscope`) },
|
|
307
325
|
package: { bytes: pkgBytes, human: formatBytes(pkgBytes) },
|
|
308
|
-
repair: { sks_update: sksUpdate, setup: setupRepair, codex_config: configRepair, migration_journal: migrationJournal, global_sks_installs: globalSksInstallCleanup, agent_role_config: agentRoleConfigRepair, zellij: zellijRepair, codex_native: codexNativeRepair }
|
|
326
|
+
repair: { sks_update: sksUpdate, setup: setupRepair, codex_config: configRepair, migration_journal: migrationJournal, global_sks_installs: globalSksInstallCleanup, agent_role_config: agentRoleConfigRepair, zellij: zellijRepair, codex_native: codexNativeRepair, doctor_native_capability: doctorNativeCapabilityRepair }
|
|
309
327
|
};
|
|
310
328
|
if (flag(args, '--json')) {
|
|
311
329
|
printJson(result);
|
|
@@ -349,6 +367,24 @@ export async function run(_command, args = []) {
|
|
|
349
367
|
for (const action of runtimeReadiness.repair_actions)
|
|
350
368
|
console.log(` - ${action}`);
|
|
351
369
|
}
|
|
370
|
+
const nativeCapabilityRows = Array.isArray(doctorNativeCapabilityRepair?.native_capabilities?.capabilities)
|
|
371
|
+
? doctorNativeCapabilityRepair.native_capabilities.capabilities
|
|
372
|
+
: [];
|
|
373
|
+
console.log('SKS Native Capabilities:');
|
|
374
|
+
console.log(` image generation: ${nativeCapabilityStatus(nativeCapabilityRows, 'image_generation', 'repair_required')}`);
|
|
375
|
+
console.log(` image follow-up edit: ${nativeCapabilityStatus(nativeCapabilityRows, 'image_followup_edit', 'degraded')}`);
|
|
376
|
+
console.log(` computer use: ${nativeCapabilityStatus(nativeCapabilityRows, 'computer_use', 'manual_required')}`);
|
|
377
|
+
console.log(` Chrome/web review: ${nativeCapabilityStatus(nativeCapabilityRows, 'chrome_web_review', 'manual_required')}`);
|
|
378
|
+
console.log(` app screenshot: ${nativeCapabilityStatus(nativeCapabilityRows, 'codex_app_screenshot', 'degraded')}`);
|
|
379
|
+
console.log(` app handoff: ${nativeCapabilityStatus(nativeCapabilityRows, 'app_handoff', 'unavailable')}`);
|
|
380
|
+
console.log(` image path exposure: ${nativeCapabilityStatus(nativeCapabilityRows, 'image_path_exposure', 'fallback')}`);
|
|
381
|
+
console.log('SKS Skills:');
|
|
382
|
+
console.log(` core skills: ${doctorSkillStatus(doctorNativeCapabilityRepair?.core_skills)}`);
|
|
383
|
+
console.log(` duplicate project skills: ${doctorDedupeStatus(doctorNativeCapabilityRepair?.skill_dedupe)}`);
|
|
384
|
+
console.log('Secret preservation:');
|
|
385
|
+
console.log(` Supabase keys: ${doctorNativeCapabilityRepair?.ok === false && String((doctorNativeCapabilityRepair?.blockers || []).join(' ')).includes('secret_preservation_failed') ? 'blocked' : 'preserved'}`);
|
|
386
|
+
console.log(' secret values: redacted');
|
|
387
|
+
console.log(` migration journal: ${doctorNativeCapabilityRepair?.secret_preservation_guard || '.sneakoscope/reports/secret-preservation-guard.json'}`);
|
|
352
388
|
console.log('Codex App Harness:');
|
|
353
389
|
console.log(` plugins: ${codexAppHarnessMatrix.app_features?.plugin_json ? 'ok' : 'degraded'}`);
|
|
354
390
|
console.log(` hook approval: ${codexAppHarnessMatrix.app_features?.hook_approval_state_detectable ? 'ok' : 'unknown'}`);
|
|
@@ -473,6 +509,40 @@ function buildRuntimeReadiness(zellijReadiness, matrix) {
|
|
|
473
509
|
repair_actions: [...new Set(repairActions)]
|
|
474
510
|
};
|
|
475
511
|
}
|
|
512
|
+
function nativeCapabilityStatus(rows, id, fallback) {
|
|
513
|
+
const row = rows.find((entry) => entry?.id === id);
|
|
514
|
+
if (!row)
|
|
515
|
+
return fallback;
|
|
516
|
+
if (row.after === 'verified' || row.before === 'verified')
|
|
517
|
+
return 'verified';
|
|
518
|
+
if (row.repairability === 'manual-required')
|
|
519
|
+
return 'manual_required';
|
|
520
|
+
if (row.before === 'degraded' || row.after === 'degraded')
|
|
521
|
+
return 'degraded';
|
|
522
|
+
if (row.repairability === 'doctor-fix')
|
|
523
|
+
return row.after === 'blocked' ? 'blocked' : 'repair_required';
|
|
524
|
+
if (row.repairability === 'unavailable')
|
|
525
|
+
return 'unavailable';
|
|
526
|
+
return fallback;
|
|
527
|
+
}
|
|
528
|
+
function doctorSkillStatus(coreSkills) {
|
|
529
|
+
if (!coreSkills)
|
|
530
|
+
return 'drift_detected';
|
|
531
|
+
if (Array.isArray(coreSkills.restored) && coreSkills.restored.length)
|
|
532
|
+
return 'repaired';
|
|
533
|
+
if (Array.isArray(coreSkills.blockers) && coreSkills.blockers.length)
|
|
534
|
+
return 'drift_detected';
|
|
535
|
+
return 'current';
|
|
536
|
+
}
|
|
537
|
+
function doctorDedupeStatus(skillDedupe) {
|
|
538
|
+
if (!skillDedupe)
|
|
539
|
+
return 'manual_required';
|
|
540
|
+
if (Array.isArray(skillDedupe.actions) && skillDedupe.actions.some((action) => action.action === 'quarantined'))
|
|
541
|
+
return 'repaired';
|
|
542
|
+
if (Array.isArray(skillDedupe.blockers) && skillDedupe.blockers.length)
|
|
543
|
+
return 'manual_required';
|
|
544
|
+
return 'none';
|
|
545
|
+
}
|
|
476
546
|
// Assemble the explicit Zellij readiness block for `doctor --json` from the
|
|
477
547
|
// capability probe + readiness matrix. Proof statuses are availability-derived:
|
|
478
548
|
// `verified` is reserved for a real environment run (SKS_REQUIRE_ZELLIJ=1 gates);
|
|
@@ -1,169 +1,95 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import os from 'node:os';
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
'$Naruto',
|
|
9
|
-
'$QA-LOOP',
|
|
10
|
-
'$Research',
|
|
11
|
-
'$DFix',
|
|
12
|
-
'$Image-UX-Review',
|
|
13
|
-
'$Computer-Use',
|
|
14
|
-
'$Init-Deep'
|
|
15
|
-
];
|
|
4
|
+
import { ensureDir, nowIso, writeJsonAtomic } from '../fsx.js';
|
|
5
|
+
import { buildSksCoreSkillManifest } from '../codex-native/core-skill-manifest.js';
|
|
6
|
+
import { syncCoreSkillsIntegrity } from '../codex-native/core-skill-integrity.js';
|
|
7
|
+
import { dedupeProjectSkills } from '../codex-native/project-skill-dedupe.js';
|
|
16
8
|
const EXTERNAL_ROUTE_RESERVED = new Set(['ulw-loop', 'ulw-plan', 'start-work']);
|
|
17
9
|
export async function syncCodexSksSkills(input) {
|
|
18
10
|
const root = path.resolve(input.root);
|
|
19
11
|
const skillsRoot = input.skillsRoot || path.join(process.env.CODEX_HOME || path.join(os.homedir(), '.codex'), 'skills');
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
12
|
+
return withSkillSyncLock(root, async () => {
|
|
13
|
+
const beforeExisting = await listSkillNames(skillsRoot);
|
|
14
|
+
const reserved = beforeExisting.filter((name) => EXTERNAL_ROUTE_RESERVED.has(name));
|
|
15
|
+
const manifest = buildSksCoreSkillManifest();
|
|
16
|
+
const desired = manifest.skills.map((skill) => skill.canonical_name);
|
|
17
|
+
const integrity = await syncCoreSkillsIntegrity({
|
|
18
|
+
root,
|
|
19
|
+
apply: input.apply === true,
|
|
20
|
+
skillsRoot,
|
|
21
|
+
reportPath: path.join(root, '.sneakoscope', 'reports', 'core-skill-integrity.json')
|
|
22
|
+
});
|
|
23
|
+
const dedupe = await dedupeProjectSkills({
|
|
24
|
+
root,
|
|
25
|
+
fix: input.apply === true,
|
|
26
|
+
yes: true,
|
|
27
|
+
quarantineUserDuplicates: false,
|
|
28
|
+
reportPath: path.join(root, '.sneakoscope', 'reports', 'project-skill-dedupe.json')
|
|
29
|
+
}).catch((err) => ({
|
|
30
|
+
ok: false,
|
|
31
|
+
blockers: [err instanceof Error ? err.message : String(err)],
|
|
32
|
+
unresolved_user_duplicates: [],
|
|
33
|
+
actions: []
|
|
34
|
+
}));
|
|
35
|
+
const report = {
|
|
36
|
+
schema: 'sks.codex-skill-sync.v1',
|
|
37
|
+
generated_at: nowIso(),
|
|
38
|
+
ok: integrity.ok && dedupe.ok !== false,
|
|
39
|
+
apply: input.apply === true,
|
|
40
|
+
skills_root: skillsRoot,
|
|
41
|
+
desired_skills: desired,
|
|
42
|
+
existing_skills: beforeExisting,
|
|
43
|
+
created: integrity.installed,
|
|
44
|
+
skipped: integrity.skipped_user_authored,
|
|
45
|
+
external_route_names_preserved: reserved,
|
|
46
|
+
integrity_report: '.sneakoscope/reports/core-skill-integrity.json',
|
|
47
|
+
dedupe_report: '.sneakoscope/reports/project-skill-dedupe.json',
|
|
48
|
+
interop: {
|
|
49
|
+
mode: 'coexist',
|
|
50
|
+
clobbered_external_routes: false,
|
|
51
|
+
clobbered_user_skills: false,
|
|
52
|
+
skipped_user_skills: integrity.skipped_user_authored,
|
|
53
|
+
managed_skills: desired
|
|
54
|
+
},
|
|
55
|
+
blockers: [...integrity.blockers, ...(dedupe.blockers || [])]
|
|
56
|
+
};
|
|
57
|
+
await writeJsonAtomic(path.join(root, '.sneakoscope', 'reports', 'codex-skill-sync.json'), report).catch(() => undefined);
|
|
58
|
+
return report;
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
export async function withSkillSyncLock(root, fn) {
|
|
62
|
+
const lockPath = path.join(path.resolve(root), '.sneakoscope', 'locks', 'skill-sync.lock');
|
|
63
|
+
await ensureDir(path.dirname(lockPath));
|
|
64
|
+
const started = Date.now();
|
|
65
|
+
while (true) {
|
|
66
|
+
try {
|
|
67
|
+
await fs.mkdir(lockPath);
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
catch (err) {
|
|
71
|
+
const code = err && typeof err === 'object' && 'code' in err ? String(err.code) : '';
|
|
72
|
+
if (code !== 'EEXIST')
|
|
73
|
+
throw err;
|
|
74
|
+
if (Date.now() - started > 30000)
|
|
75
|
+
throw new Error(`Timed out waiting for skill sync lock: ${lockPath}`);
|
|
76
|
+
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
43
77
|
}
|
|
44
78
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
interop: {
|
|
57
|
-
mode: 'coexist',
|
|
58
|
-
clobbered_external_routes: false,
|
|
59
|
-
clobbered_user_skills: false,
|
|
60
|
-
skipped_user_skills: skipped,
|
|
61
|
-
managed_skills: desired
|
|
62
|
-
},
|
|
63
|
-
blockers: []
|
|
64
|
-
};
|
|
65
|
-
await writeJsonAtomic(path.join(root, '.sneakoscope', 'reports', 'codex-skill-sync.json'), report).catch(() => undefined);
|
|
66
|
-
return report;
|
|
79
|
+
try {
|
|
80
|
+
await writeJsonAtomic(path.join(lockPath, 'owner.json'), {
|
|
81
|
+
schema: 'sks.skill-sync-lock.v1',
|
|
82
|
+
pid: process.pid,
|
|
83
|
+
acquired_at: nowIso()
|
|
84
|
+
}).catch(() => undefined);
|
|
85
|
+
return await fn();
|
|
86
|
+
}
|
|
87
|
+
finally {
|
|
88
|
+
await fs.rm(lockPath, { recursive: true, force: true }).catch(() => undefined);
|
|
89
|
+
}
|
|
67
90
|
}
|
|
68
91
|
async function listSkillNames(root) {
|
|
69
92
|
const rows = await fs.readdir(root, { withFileTypes: true }).catch(() => []);
|
|
70
93
|
return rows.filter((row) => row.isDirectory()).map((row) => row.name).sort();
|
|
71
94
|
}
|
|
72
|
-
function skillName(value) {
|
|
73
|
-
return value.replace(/^\$/, '').toLowerCase();
|
|
74
|
-
}
|
|
75
|
-
function skillContent(name) {
|
|
76
|
-
const profile = skillProfile(name);
|
|
77
|
-
const body = [
|
|
78
|
-
'---',
|
|
79
|
-
`name: ${name}`,
|
|
80
|
-
`description: SKS managed Codex App route bridge for ${profile.command}.`,
|
|
81
|
-
'---',
|
|
82
|
-
'',
|
|
83
|
-
'<!-- BEGIN SKS MANAGED SKILL -->',
|
|
84
|
-
`Command: ${profile.command}`,
|
|
85
|
-
`Purpose: ${profile.purpose}`,
|
|
86
|
-
`Use when: ${profile.when}`,
|
|
87
|
-
`Route: ${profile.command}`,
|
|
88
|
-
`Evidence: ${profile.evidence}`,
|
|
89
|
-
'Safety rules: keep route state bounded, preserve user and external route assets, and stop on hard blockers instead of fabricating fallback behavior.',
|
|
90
|
-
'Proof paths: write the route-local mission artifact named in Evidence before claiming completion.',
|
|
91
|
-
'Failure recovery: if a proof path cannot be produced, record the blocker and continue only when the selected SKS route has another allowed evidence path.',
|
|
92
|
-
`Fallback: ${profile.fallback}`,
|
|
93
|
-
`checksum: ${hash(`${name}:${profile.command}:${profile.evidence}:${profile.fallback}`)}`,
|
|
94
|
-
'<!-- END SKS MANAGED SKILL -->',
|
|
95
|
-
''
|
|
96
|
-
].join('\n');
|
|
97
|
-
return body;
|
|
98
|
-
}
|
|
99
|
-
function skillProfile(name) {
|
|
100
|
-
const table = {
|
|
101
|
-
loop: {
|
|
102
|
-
command: '$Loop',
|
|
103
|
-
purpose: 'compile persisted route work into bounded loop plans with continuation evidence.',
|
|
104
|
-
when: 'a mission needs stage-by-stage execution, memory hints, or resume-safe artifacts.',
|
|
105
|
-
evidence: '.sneakoscope/loops/** plus codex-app-execution-profile.json',
|
|
106
|
-
fallback: 'use message-role routing when native agent_type is not verified.'
|
|
107
|
-
},
|
|
108
|
-
naruto: {
|
|
109
|
-
command: '$Naruto',
|
|
110
|
-
purpose: 'fan out bounded native worker lanes for high-scale review or implementation.',
|
|
111
|
-
when: 'parallel lanes are explicitly selected by the route and parent integration remains owner.',
|
|
112
|
-
evidence: 'naruto work graph, worker ledgers, and execution profile payloads.',
|
|
113
|
-
fallback: 'degrade to message-role workers without dropping proof artifacts.'
|
|
114
|
-
},
|
|
115
|
-
'qa-loop': {
|
|
116
|
-
command: '$QA-LOOP',
|
|
117
|
-
purpose: 'dogfood UI/API behavior with gate artifacts and current execution profile.',
|
|
118
|
-
when: 'route completion needs human-proxy verification or app handoff checks.',
|
|
119
|
-
evidence: 'qa-loop gate/result ledgers and codex-app-execution-profile.json.',
|
|
120
|
-
fallback: 'record the unavailable surface as blocked rather than inventing visual proof.'
|
|
121
|
-
},
|
|
122
|
-
research: {
|
|
123
|
-
command: '$Research',
|
|
124
|
-
purpose: 'run evidence-bound research cycles with source routing and synthesis ledgers.',
|
|
125
|
-
when: 'the request depends on discovery, evaluation, or external-source claims.',
|
|
126
|
-
evidence: 'research plan, source ledger, cycle record, and execution profile routing.',
|
|
127
|
-
fallback: 'mark unavailable source tools explicitly and avoid unsupported live-accuracy claims.'
|
|
128
|
-
},
|
|
129
|
-
dfix: {
|
|
130
|
-
command: '$DFix',
|
|
131
|
-
purpose: 'perform tiny direct fixes without the full Team route.',
|
|
132
|
-
when: 'copy/config/docs/labels/spacing/translation/mechanical edits are truly narrow.',
|
|
133
|
-
evidence: 'focused diff plus DFix Honest check.',
|
|
134
|
-
fallback: 'route broad implementation through Team/Loop instead.'
|
|
135
|
-
},
|
|
136
|
-
'image-ux-review': {
|
|
137
|
-
command: '$Image-UX-Review',
|
|
138
|
-
purpose: 'produce generated annotated UI review images and extract issue ledgers.',
|
|
139
|
-
when: 'visual UX critique is requested from screenshots or app captures.',
|
|
140
|
-
evidence: 'source inventory, generated annotation images, extracted issue ledger.',
|
|
141
|
-
fallback: 'block if raster annotation cannot be produced.'
|
|
142
|
-
},
|
|
143
|
-
'computer-use': {
|
|
144
|
-
command: '$Computer-Use',
|
|
145
|
-
purpose: 'operate native macOS desktop apps through the fast Computer Use lane.',
|
|
146
|
-
when: 'the task depends on non-web desktop UI or OS settings.',
|
|
147
|
-
evidence: 'desktop interaction notes/screenshots where available.',
|
|
148
|
-
fallback: 'use Browser/Chrome only for web targets.'
|
|
149
|
-
},
|
|
150
|
-
'init-deep': {
|
|
151
|
-
command: '$Init-Deep',
|
|
152
|
-
purpose: 'refresh project-local memory, directory AGENTS sections, and loop memory hints.',
|
|
153
|
-
when: 'a route needs deeper local context or directory-specific instruction recall.',
|
|
154
|
-
evidence: '.sneakoscope/context/AGENTS.generated.md and managed directory AGENTS blocks.',
|
|
155
|
-
fallback: 'preserve user content and skip directories that cannot be safely updated.'
|
|
156
|
-
}
|
|
157
|
-
};
|
|
158
|
-
return table[name] || {
|
|
159
|
-
command: `$${name}`,
|
|
160
|
-
purpose: 'bridge an SKS managed Codex App route.',
|
|
161
|
-
when: 'the matching SKS route is explicitly requested.',
|
|
162
|
-
evidence: '.sneakoscope route artifacts.',
|
|
163
|
-
fallback: 'record blockers with evidence.'
|
|
164
|
-
};
|
|
165
|
-
}
|
|
166
|
-
function hash(value) {
|
|
167
|
-
return crypto.createHash('sha256').update(`sks-skill:${value}`).digest('hex').slice(0, 12);
|
|
168
|
-
}
|
|
169
95
|
//# sourceMappingURL=codex-skill-sync.js.map
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { ensureDir, nowIso, readText, sha256, writeJsonAtomic, writeTextAtomic } from '../fsx.js';
|
|
4
|
+
import { buildSksCoreSkillManifest, isSksManagedCoreSkillContent, renderCoreSkillTemplate } from './core-skill-manifest.js';
|
|
5
|
+
import { canonicalSkillName } from './skill-name-canonicalizer.js';
|
|
6
|
+
export async function syncCoreSkillsIntegrity(input) {
|
|
7
|
+
const root = path.resolve(input.root);
|
|
8
|
+
const skillsRoot = input.skillsRoot || path.join(root, '.agents', 'skills');
|
|
9
|
+
const apply = input.apply === true;
|
|
10
|
+
const manifest = buildSksCoreSkillManifest();
|
|
11
|
+
const rows = [];
|
|
12
|
+
const installed = [];
|
|
13
|
+
const restored = [];
|
|
14
|
+
const skippedUserAuthored = [];
|
|
15
|
+
const blockers = [];
|
|
16
|
+
for (const skill of manifest.skills) {
|
|
17
|
+
const skillDir = path.join(skillsRoot, skill.canonical_name);
|
|
18
|
+
const file = path.join(skillDir, 'SKILL.md');
|
|
19
|
+
const desired = renderCoreSkillTemplate(skill.canonical_name);
|
|
20
|
+
const current = await readText(file, null);
|
|
21
|
+
const beforeSha = typeof current === 'string' ? sha256(current) : null;
|
|
22
|
+
let action = 'already-current';
|
|
23
|
+
let backupPath = null;
|
|
24
|
+
let blocker = null;
|
|
25
|
+
if (current === null) {
|
|
26
|
+
action = 'install-missing-managed-copy';
|
|
27
|
+
if (apply) {
|
|
28
|
+
await ensureDir(skillDir);
|
|
29
|
+
await writeTextAtomic(file, desired);
|
|
30
|
+
installed.push(file);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
else if (beforeSha === skill.content_sha256) {
|
|
34
|
+
action = 'already-current';
|
|
35
|
+
}
|
|
36
|
+
else if (isSksManagedCoreSkillContent(current)) {
|
|
37
|
+
action = 'restore-corrupted-managed-copy';
|
|
38
|
+
if (apply) {
|
|
39
|
+
backupPath = path.join(root, '.sneakoscope', 'backups', 'core-skills', skill.canonical_name, `${Date.now()}-${process.pid}.SKILL.md.bak`);
|
|
40
|
+
await ensureDir(path.dirname(backupPath));
|
|
41
|
+
await fs.writeFile(backupPath, current, 'utf8');
|
|
42
|
+
await writeTextAtomic(file, desired);
|
|
43
|
+
restored.push(file);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
action = 'skip-user-authored';
|
|
48
|
+
blocker = `user_authored_core_skill_collision:${skill.canonical_name}`;
|
|
49
|
+
skippedUserAuthored.push(file);
|
|
50
|
+
}
|
|
51
|
+
const after = await readText(file, null);
|
|
52
|
+
const afterSha = typeof after === 'string' ? sha256(after) : null;
|
|
53
|
+
rows.push({
|
|
54
|
+
canonical_name: skill.canonical_name,
|
|
55
|
+
path: file,
|
|
56
|
+
action,
|
|
57
|
+
before_sha256: beforeSha,
|
|
58
|
+
after_sha256: afterSha,
|
|
59
|
+
backup_path: backupPath,
|
|
60
|
+
blocker
|
|
61
|
+
});
|
|
62
|
+
if (blocker)
|
|
63
|
+
blockers.push(blocker);
|
|
64
|
+
}
|
|
65
|
+
const report = {
|
|
66
|
+
schema: 'sks.core-skill-integrity.v1',
|
|
67
|
+
generated_at: nowIso(),
|
|
68
|
+
ok: blockers.length === 0,
|
|
69
|
+
root,
|
|
70
|
+
apply,
|
|
71
|
+
skills_root: skillsRoot,
|
|
72
|
+
manifest_sha256: sha256(JSON.stringify(manifest.skills.map((skill) => [skill.canonical_name, skill.content_sha256]))),
|
|
73
|
+
rows,
|
|
74
|
+
installed,
|
|
75
|
+
restored,
|
|
76
|
+
skipped_user_authored: skippedUserAuthored,
|
|
77
|
+
blockers
|
|
78
|
+
};
|
|
79
|
+
const reportPath = input.reportPath === null
|
|
80
|
+
? null
|
|
81
|
+
: input.reportPath || path.join(root, '.sneakoscope', 'reports', 'core-skill-integrity.json');
|
|
82
|
+
if (reportPath)
|
|
83
|
+
await writeJsonAtomic(reportPath, report).catch(() => undefined);
|
|
84
|
+
return report;
|
|
85
|
+
}
|
|
86
|
+
export function coreSkillPath(skillsRoot, name) {
|
|
87
|
+
return path.join(skillsRoot, canonicalSkillName(name), 'SKILL.md');
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=core-skill-integrity.js.map
|