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.
Files changed (29) hide show
  1. package/README.md +9 -2
  2. package/crates/sks-core/Cargo.lock +1 -1
  3. package/crates/sks-core/Cargo.toml +1 -1
  4. package/crates/sks-core/src/main.rs +1 -1
  5. package/dist/.sks-build-stamp.json +4 -4
  6. package/dist/bin/sks.js +1 -1
  7. package/dist/commands/doctor.js +71 -1
  8. package/dist/core/codex-app/codex-skill-sync.js +80 -154
  9. package/dist/core/codex-native/core-skill-integrity.js +89 -0
  10. package/dist/core/codex-native/core-skill-manifest.js +156 -0
  11. package/dist/core/codex-native/native-capability-postcheck.js +35 -0
  12. package/dist/core/codex-native/native-capability-repair-matrix.js +210 -0
  13. package/dist/core/codex-native/native-capability-repair.js +47 -0
  14. package/dist/core/codex-native/native-media-computer-repair.js +5 -0
  15. package/dist/core/codex-native/project-skill-dedupe.js +109 -0
  16. package/dist/core/codex-native/skill-name-canonicalizer.js +21 -0
  17. package/dist/core/codex-native/skill-registry-ledger.js +85 -0
  18. package/dist/core/commands/basic-cli.js +15 -9
  19. package/dist/core/config/config-migration-journal.js +27 -0
  20. package/dist/core/config/managed-config-merge.js +105 -0
  21. package/dist/core/config/secret-preservation.js +169 -0
  22. package/dist/core/config/supabase-secret-preservation.js +29 -0
  23. package/dist/core/doctor/doctor-native-capability-repair.js +48 -0
  24. package/dist/core/fsx.js +1 -1
  25. package/dist/core/init.js +5 -1
  26. package/dist/core/version.js +1 -1
  27. package/dist/scripts/sizecheck.js +8 -2
  28. package/dist/scripts/sks-3-1-8-check-lib.js +30 -0
  29. 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.7** closes Codex-native runtime-proof gaps with real routing blackboxes, neutral reference cache refresh, read-only broker checks, explicit managed-asset repair transactions, and final brand-neutral artifact scans.
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 '$Loop' --capability agent-role --json
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
 
@@ -76,7 +76,7 @@ dependencies = [
76
76
 
77
77
  [[package]]
78
78
  name = "sks-core"
79
- version = "3.1.7"
79
+ version = "3.1.8"
80
80
  dependencies = [
81
81
  "serde_json",
82
82
  ]
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "sks-core"
3
- version = "3.1.7"
3
+ version = "3.1.8"
4
4
  edition = "2021"
5
5
 
6
6
  [dependencies]
@@ -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"),
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.7",
5
- "source_digest": "8927dd50bf67d1f2ce1df5744a3b21645a870f8f9182fe7b86a92affedf416aa",
6
- "source_file_count": 2558,
7
- "built_at_source_time": 1781491235171
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
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- const FAST_PACKAGE_VERSION = '3.1.7';
2
+ const FAST_PACKAGE_VERSION = '3.1.8';
3
3
  const args = process.argv.slice(2);
4
4
  try {
5
5
  if (args[0] === '--agent' && args[1] === 'worker') {
@@ -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 crypto from 'node:crypto';
5
- import { ensureDir, nowIso, writeJsonAtomic, writeTextAtomic } from '../fsx.js';
6
- const SKS_SKILLS = [
7
- '$Loop',
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
- const existing = await listSkillNames(skillsRoot);
21
- const collisions = existing.filter((name) => EXTERNAL_ROUTE_RESERVED.has(name));
22
- const desired = SKS_SKILLS.map((skill) => skillName(skill));
23
- const created = [];
24
- const skipped = [];
25
- if (input.apply === true) {
26
- await ensureDir(skillsRoot);
27
- for (const name of desired) {
28
- if (EXTERNAL_ROUTE_RESERVED.has(name)) {
29
- skipped.push(name);
30
- continue;
31
- }
32
- const dir = path.join(skillsRoot, name);
33
- const file = path.join(dir, 'SKILL.md');
34
- const content = skillContent(name);
35
- const current = await fs.readFile(file, 'utf8').catch(() => '');
36
- if (current && !current.includes('BEGIN SKS MANAGED SKILL')) {
37
- skipped.push(name);
38
- continue;
39
- }
40
- await ensureDir(dir);
41
- await writeTextAtomic(file, content);
42
- created.push(file);
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
- const report = {
46
- schema: 'sks.codex-skill-sync.v1',
47
- generated_at: nowIso(),
48
- ok: true,
49
- apply: input.apply === true,
50
- skills_root: skillsRoot,
51
- desired_skills: desired,
52
- existing_skills: existing,
53
- created,
54
- skipped,
55
- external_route_names_preserved: collisions,
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