sneakoscope 3.1.9 → 3.1.11
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 +7 -6
- 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 +103 -4
- package/dist/core/codex-app/codex-agent-role-sync.js +6 -6
- package/dist/core/codex-app/codex-skill-sync.js +37 -2
- package/dist/core/codex-native/core-skill-integrity.js +6 -1
- package/dist/core/codex-native/core-skill-manifest.js +1 -1
- package/dist/core/codex-native/native-capability-postcheck.js +143 -15
- package/dist/core/codex-native/native-capability-repair-matrix.js +1 -1
- package/dist/core/codex-native/project-skill-dedupe.js +18 -3
- package/dist/core/codex-native/skill-registry-ledger.js +9 -2
- package/dist/core/commands/basic-cli.js +3 -1
- package/dist/core/commands/research-command.js +1 -1
- package/dist/core/config/managed-config-merge.js +59 -10
- package/dist/core/config/secret-preservation.js +145 -37
- package/dist/core/doctor/doctor-codex-startup-repair.js +269 -0
- package/dist/core/doctor/doctor-context7-repair.js +116 -0
- package/dist/core/fsx.js +1 -1
- package/dist/core/init.js +31 -6
- package/dist/core/version.js +1 -1
- package/dist/core/zellij/zellij-capability.js +1 -1
- package/package.json +7 -1
package/README.md
CHANGED
|
@@ -35,14 +35,15 @@ 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.11** is a release-ready repair pass for MAD Zellij stacked panes, Context7 MCP doctor recovery, and stale Codex startup config.
|
|
39
39
|
|
|
40
|
-
What changed in 3.1.
|
|
40
|
+
What changed in 3.1.11:
|
|
41
41
|
|
|
42
|
-
- **
|
|
43
|
-
- **
|
|
44
|
-
-
|
|
45
|
-
- **
|
|
42
|
+
- **MAD Zellij panes require native stacked-pane support.** `sks doctor --fix` now treats Zellij 0.43.0 as the minimum interactive runtime so `sks --mad` can use native stacked worker panes instead of fragmenting into plain splits.
|
|
43
|
+
- **Context7 stdio lockups are doctor-repairable.** `sks doctor --fix` detects local `@upstash/context7-mcp` stdio config and migrates it to the remote Context7 MCP endpoint so Codex launches do not stall at the stdio server banner.
|
|
44
|
+
- **Codex startup warnings are doctor-repairable.** `sks doctor --fix` rewrites stale SKS agent `config_file` paths to existing absolute files, removes unsupported managed `message_role_prefix` role fields, preserves optional `supabase_sauron`, and drops missing-command `node_repl` MCP blocks that would otherwise spam startup.
|
|
45
|
+
- **Doctor JSON exposes the Context7 and startup repair reports.** `context7_repair`, `codex_startup_repair`, and their `repair.*` entries carry migration status, backups, actions, warnings, and any manual auth actions.
|
|
46
|
+
- **Release metadata is aligned for 3.1.11.** Package, lockfile, CLI version constants, Rust helper metadata, README, and changelog all point at the same release.
|
|
46
47
|
|
|
47
48
|
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.
|
|
48
49
|
|
|
@@ -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.11"),
|
|
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.11",
|
|
5
|
+
"source_digest": "932de0e70bb2a5b787aef2782fa56cbc4956c6077d099d9e1067b9406fd8412a",
|
|
6
|
+
"source_file_count": 2613,
|
|
7
|
+
"built_at_source_time": 1781554790419
|
|
8
8
|
}
|
package/dist/bin/sks.js
CHANGED
package/dist/commands/doctor.js
CHANGED
|
@@ -24,13 +24,22 @@ import { runCodex0138Doctor } from '../core/doctor/codex-0138-doctor.js';
|
|
|
24
24
|
import { writeCodexPluginInventoryArtifacts, pluginAppTemplatePolicy } from '../core/codex-plugins/codex-plugin-json.js';
|
|
25
25
|
import { writeMcpPluginInventoryArtifacts } from '../core/mcp/mcp-plugin-inventory.js';
|
|
26
26
|
import { runDoctorZellijRepair, doctorZellijRepairConsoleLine } from '../core/doctor/doctor-zellij-repair.js';
|
|
27
|
+
import { runDoctorContext7Repair } from '../core/doctor/doctor-context7-repair.js';
|
|
28
|
+
import { runDoctorCodexStartupRepair } from '../core/doctor/doctor-codex-startup-repair.js';
|
|
27
29
|
import { buildCodexAppHarnessMatrix } from '../core/codex-app/codex-app-harness-matrix.js';
|
|
28
30
|
import { buildCodexNativeFeatureMatrix } from '../core/codex-native/codex-native-feature-broker.js';
|
|
29
31
|
import { repairCodexNativeManagedAssets } from '../core/codex-native/codex-native-repair-transaction.js';
|
|
30
32
|
import { runDoctorNativeCapabilityRepair } from '../core/doctor/doctor-native-capability-repair.js';
|
|
31
33
|
import { runDoctorCommandAliasCleanup } from '../core/doctor/command-alias-cleanup.js';
|
|
34
|
+
import { withSecretPreservationGuard } from '../core/config/config-migration-journal.js';
|
|
32
35
|
export async function run(_command, args = []) {
|
|
36
|
+
const root = await projectRoot();
|
|
33
37
|
const doctorFix = flag(args, '--fix');
|
|
38
|
+
if (doctorFix)
|
|
39
|
+
return withSecretPreservationGuard(root, 'doctor-fix', () => runDoctor(args, root, doctorFix));
|
|
40
|
+
return runDoctor(args, root, doctorFix);
|
|
41
|
+
}
|
|
42
|
+
async function runDoctor(args = [], root, doctorFix) {
|
|
34
43
|
let setupRepair = null;
|
|
35
44
|
const sksUpdate = doctorFix
|
|
36
45
|
? {
|
|
@@ -70,7 +79,6 @@ export async function run(_command, args = []) {
|
|
|
70
79
|
: await ensureGlobalCodexFastModeDuringInstall().catch((err) => ({ status: 'failed', error: err?.message || String(err) }))
|
|
71
80
|
};
|
|
72
81
|
}
|
|
73
|
-
const root = await projectRoot();
|
|
74
82
|
const commandAliasCleanup = await runDoctorCommandAliasCleanup({
|
|
75
83
|
root,
|
|
76
84
|
fix: doctorFix
|
|
@@ -112,6 +120,19 @@ export async function run(_command, args = []) {
|
|
|
112
120
|
requireActualCodex: flag(args, '--fix') || flag(args, '--require-actual-codex'),
|
|
113
121
|
codexBin: codexBin || undefined
|
|
114
122
|
};
|
|
123
|
+
let codexStartupRepair = await runDoctorCodexStartupRepair({ root, fix: doctorFix }).catch((err) => ({
|
|
124
|
+
schema: 'sks.doctor-codex-startup-repair.v1',
|
|
125
|
+
ok: false,
|
|
126
|
+
generated_at: new Date().toISOString(),
|
|
127
|
+
fix: doctorFix,
|
|
128
|
+
configs: [],
|
|
129
|
+
agent_role_files: { sanitized: [], created: [], blockers: [err?.message || String(err)] },
|
|
130
|
+
actions: [],
|
|
131
|
+
manual_actions: [],
|
|
132
|
+
blockers: [err?.message || String(err)],
|
|
133
|
+
warnings: [],
|
|
134
|
+
report_path: `${root}/.sneakoscope/reports/doctor-codex-startup-repair.json`
|
|
135
|
+
}));
|
|
115
136
|
const codexDoctorBefore = flag(args, '--fix') ? await runCodexDoctorBridge({ codexBin: codexBin || null, cwd: root, required: flag(args, '--require-actual-codex') }).catch(() => null) : null;
|
|
116
137
|
const configRepair = flag(args, '--fix') ? await repairCodexConfigEperm(root, { fix: true, ...configProbeOpts }) : null;
|
|
117
138
|
const migrationJournal = flag(args, '--fix')
|
|
@@ -120,6 +141,7 @@ export async function run(_command, args = []) {
|
|
|
120
141
|
const codexConfig = configRepair?.after || await inspectCodexConfigReadability(root, configProbeOpts);
|
|
121
142
|
const codexDoctor = await runCodexDoctorBridge({ codexBin: codexBin || null, cwd: root, required: flag(args, '--require-actual-codex') });
|
|
122
143
|
const codexDoctorDiff = compareCodexDoctorBridge(codexDoctorBefore, codexDoctor);
|
|
144
|
+
codexStartupRepair = mergeObservedCodexStartupWarnings(codexStartupRepair, codexDoctor);
|
|
123
145
|
const codex = codexBin
|
|
124
146
|
? { bin: codexBin, version: 'fixture-or-explicit', available: true }
|
|
125
147
|
: await getCodexInfo().catch(() => ({ bin: null, version: null, available: false }));
|
|
@@ -206,6 +228,18 @@ export async function run(_command, args = []) {
|
|
|
206
228
|
blockers: [err?.message || String(err)],
|
|
207
229
|
warnings: []
|
|
208
230
|
}));
|
|
231
|
+
const context7Repair = await runDoctorContext7Repair({ root, fix: doctorFix }).catch((err) => ({
|
|
232
|
+
schema: 'sks.doctor-context7-repair.v1',
|
|
233
|
+
ok: false,
|
|
234
|
+
generated_at: new Date().toISOString(),
|
|
235
|
+
fix: doctorFix,
|
|
236
|
+
preferred_transport: 'remote',
|
|
237
|
+
configs: [],
|
|
238
|
+
actions: [],
|
|
239
|
+
blockers: [err?.message || String(err)],
|
|
240
|
+
warnings: [],
|
|
241
|
+
report_path: `${root}/.sneakoscope/reports/doctor-context7-repair.json`
|
|
242
|
+
}));
|
|
209
243
|
const zellij = await checkZellijCapability({ root, require: process.env.SKS_REQUIRE_ZELLIJ === '1' });
|
|
210
244
|
const localModel = await readLocalModelConfig().catch(() => null);
|
|
211
245
|
const permissionProfiles = await inventoryCodexPermissionProfiles(root, { writeReport: true });
|
|
@@ -285,6 +319,8 @@ export async function run(_command, args = []) {
|
|
|
285
319
|
codex_doctor: codexDoctor,
|
|
286
320
|
require_codex_doctor: flag(args, '--fix') || flag(args, '--require-actual-codex'),
|
|
287
321
|
zellij,
|
|
322
|
+
context7_repair: context7Repair,
|
|
323
|
+
codex_startup_repair: codexStartupRepair,
|
|
288
324
|
local_model: localModel,
|
|
289
325
|
agent_role_config: agentRoleConfigRepair,
|
|
290
326
|
repair: configRepair,
|
|
@@ -298,6 +334,7 @@ export async function run(_command, args = []) {
|
|
|
298
334
|
...(codexConfig.operator_actions || []),
|
|
299
335
|
...(configRepair?.operator_actions || []),
|
|
300
336
|
...(zellijRepair && !zellijRepair.ok && zellijRepair.command ? [`Run: ${zellijRepair.command}`] : []),
|
|
337
|
+
...(codexStartupRepair.manual_actions || []),
|
|
301
338
|
...(pluginPolicy?.doctor_warnings || [])
|
|
302
339
|
]
|
|
303
340
|
});
|
|
@@ -305,7 +342,7 @@ export async function run(_command, args = []) {
|
|
|
305
342
|
const runtimeReadiness = buildRuntimeReadiness(zellijReadiness, codexNativeFeatureMatrix);
|
|
306
343
|
const result = {
|
|
307
344
|
schema: 'sks.doctor-status.v1',
|
|
308
|
-
ok: ready.ready && (!sksUpdate || sksUpdate.ok !== false) && commandAliasCleanup.ok !== false,
|
|
345
|
+
ok: ready.ready && (!sksUpdate || sksUpdate.ok !== false) && commandAliasCleanup.ok !== false && codexStartupRepair.ok !== false,
|
|
309
346
|
root,
|
|
310
347
|
node: { ok: Number(process.versions.node.split('.')[0]) >= 20, version: process.version },
|
|
311
348
|
codex,
|
|
@@ -319,6 +356,8 @@ export async function run(_command, args = []) {
|
|
|
319
356
|
codex_doctor_diff: codexDoctorDiff,
|
|
320
357
|
zellij,
|
|
321
358
|
zellij_repair: zellijRepair,
|
|
359
|
+
context7_repair: context7Repair,
|
|
360
|
+
codex_startup_repair: codexStartupRepair,
|
|
322
361
|
local_model: localModel,
|
|
323
362
|
agent_role_config: agentRoleConfigRepair,
|
|
324
363
|
zellij_readiness: zellijReadiness,
|
|
@@ -342,7 +381,7 @@ export async function run(_command, args = []) {
|
|
|
342
381
|
ready,
|
|
343
382
|
sneakoscope: { ok: await exists(`${root}/.sneakoscope`) },
|
|
344
383
|
package: { bytes: pkgBytes, human: formatBytes(pkgBytes) },
|
|
345
|
-
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, command_aliases: commandAliasCleanup }
|
|
384
|
+
repair: { sks_update: sksUpdate, setup: setupRepair, codex_config: configRepair, migration_journal: migrationJournal, global_sks_installs: globalSksInstallCleanup, agent_role_config: agentRoleConfigRepair, zellij: zellijRepair, context7: context7Repair, codex_startup: codexStartupRepair, codex_native: codexNativeRepair, doctor_native_capability: doctorNativeCapabilityRepair, command_aliases: commandAliasCleanup }
|
|
346
385
|
};
|
|
347
386
|
if (flag(args, '--json')) {
|
|
348
387
|
printJson(result);
|
|
@@ -369,6 +408,21 @@ export async function run(_command, args = []) {
|
|
|
369
408
|
const zellijRepairLine = doctorZellijRepairConsoleLine(zellijRepair);
|
|
370
409
|
if (zellijRepairLine)
|
|
371
410
|
console.log(zellijRepairLine);
|
|
411
|
+
console.log('Context7 MCP:');
|
|
412
|
+
console.log(` transport: ${context7Repair.preferred_transport || 'remote'}`);
|
|
413
|
+
console.log(` repair: ${context7Repair.ok ? 'ok' : 'blocked'}`);
|
|
414
|
+
for (const action of context7Repair.actions || [])
|
|
415
|
+
console.log(` - ${action}`);
|
|
416
|
+
for (const warning of context7Repair.warnings || [])
|
|
417
|
+
console.log(` warning: ${warning}`);
|
|
418
|
+
console.log('Codex startup config:');
|
|
419
|
+
console.log(` repair: ${codexStartupRepair.ok ? 'ok' : 'blocked'}`);
|
|
420
|
+
for (const action of codexStartupRepair.actions || [])
|
|
421
|
+
console.log(` - ${action}`);
|
|
422
|
+
for (const action of codexStartupRepair.manual_actions || [])
|
|
423
|
+
console.log(` manual: ${action}`);
|
|
424
|
+
for (const warning of codexStartupRepair.warnings || [])
|
|
425
|
+
console.log(` warning: ${warning}`);
|
|
372
426
|
console.log(` codex doctor: ${codexDoctor.available ? (codexDoctor.exit_code === 0 ? 'ok' : 'warning') : 'unavailable'}`);
|
|
373
427
|
console.log(`Rust acc.: ${rust.mode || (rust.available ? 'rust_accelerated' : 'js_fallback')} ${rust.version || rust.status || ''}`);
|
|
374
428
|
console.log(`Codex App: ${ready.codex_app_ready ? 'ok' : 'optional_missing'}`);
|
|
@@ -397,6 +451,12 @@ export async function run(_command, args = []) {
|
|
|
397
451
|
console.log(` app screenshot: ${nativeCapabilityStatus(nativeCapabilityRows, 'codex_app_screenshot', 'degraded')}`);
|
|
398
452
|
console.log(` app handoff: ${nativeCapabilityStatus(nativeCapabilityRows, 'app_handoff', 'unavailable')}`);
|
|
399
453
|
console.log(` image path exposure: ${nativeCapabilityStatus(nativeCapabilityRows, 'image_path_exposure', 'fallback')}`);
|
|
454
|
+
const nativeManualActions = uniqueNativeManualActions(nativeCapabilityRows);
|
|
455
|
+
if (nativeManualActions.length) {
|
|
456
|
+
console.log(' manual next actions:');
|
|
457
|
+
for (const action of nativeManualActions)
|
|
458
|
+
console.log(` - ${action}`);
|
|
459
|
+
}
|
|
400
460
|
console.log('SKS Skills:');
|
|
401
461
|
console.log(` core skills: ${doctorSkillStatus(doctorNativeCapabilityRepair?.core_skills)}`);
|
|
402
462
|
console.log(` duplicate project skills: ${doctorDedupeStatus(doctorNativeCapabilityRepair?.skill_dedupe)}`);
|
|
@@ -408,7 +468,7 @@ export async function run(_command, args = []) {
|
|
|
408
468
|
console.log(` report: ${commandAliasCleanup.report_path}`);
|
|
409
469
|
console.log('Secret preservation:');
|
|
410
470
|
console.log(` Supabase keys: ${doctorNativeCapabilityRepair?.ok === false && String((doctorNativeCapabilityRepair?.blockers || []).join(' ')).includes('secret_preservation_failed') ? 'blocked' : 'preserved'}`);
|
|
411
|
-
console.log(' secret values:
|
|
471
|
+
console.log(' raw secret values: never recorded');
|
|
412
472
|
console.log(` migration journal: ${doctorNativeCapabilityRepair?.secret_preservation_guard || '.sneakoscope/reports/secret-preservation-guard.json'}`);
|
|
413
473
|
console.log('Codex App Harness:');
|
|
414
474
|
console.log(` plugins: ${codexAppHarnessMatrix.app_features?.plugin_json ? 'ok' : 'degraded'}`);
|
|
@@ -540,6 +600,13 @@ function nativeCapabilityStatus(rows, id, fallback) {
|
|
|
540
600
|
return fallback;
|
|
541
601
|
if (row.after === 'verified' || row.before === 'verified')
|
|
542
602
|
return 'verified';
|
|
603
|
+
if (id === 'image_path_exposure') {
|
|
604
|
+
if (row.before === 'degraded' || row.after === 'degraded' || row.repairability === 'doctor-fix')
|
|
605
|
+
return 'fallback';
|
|
606
|
+
return fallback;
|
|
607
|
+
}
|
|
608
|
+
if (id === 'app_handoff')
|
|
609
|
+
return 'unavailable';
|
|
543
610
|
if (row.repairability === 'manual-required')
|
|
544
611
|
return 'manual_required';
|
|
545
612
|
if (row.before === 'degraded' || row.after === 'degraded')
|
|
@@ -550,6 +617,12 @@ function nativeCapabilityStatus(rows, id, fallback) {
|
|
|
550
617
|
return 'unavailable';
|
|
551
618
|
return fallback;
|
|
552
619
|
}
|
|
620
|
+
function uniqueNativeManualActions(rows) {
|
|
621
|
+
return [...new Set(rows
|
|
622
|
+
.filter((row) => row?.repairability === 'manual-required' && row?.after !== 'verified')
|
|
623
|
+
.flatMap((row) => Array.isArray(row.repair_actions) ? row.repair_actions : [])
|
|
624
|
+
.filter((action) => typeof action === 'string' && action.trim()))];
|
|
625
|
+
}
|
|
553
626
|
function doctorSkillStatus(coreSkills) {
|
|
554
627
|
if (!coreSkills)
|
|
555
628
|
return 'drift_detected';
|
|
@@ -696,4 +769,30 @@ function readOption(args = [], name, fallback = null) {
|
|
|
696
769
|
const index = args.indexOf(name);
|
|
697
770
|
return index >= 0 && args[index + 1] ? args[index + 1] : fallback;
|
|
698
771
|
}
|
|
772
|
+
function mergeObservedCodexStartupWarnings(startupRepair, codexDoctor) {
|
|
773
|
+
const text = `${codexDoctor?.stdout_tail || ''}\n${codexDoctor?.stderr_tail || ''}`;
|
|
774
|
+
const manual = new Set(Array.isArray(startupRepair?.manual_actions) ? startupRepair.manual_actions : []);
|
|
775
|
+
const warnings = new Set(Array.isArray(startupRepair?.warnings) ? startupRepair.warnings : []);
|
|
776
|
+
const blockers = new Set(Array.isArray(startupRepair?.blockers) ? startupRepair.blockers : []);
|
|
777
|
+
if (/codex_apps[\s\S]{0,500}token_expired|token_expired[\s\S]{0,500}codex_apps/i.test(text)) {
|
|
778
|
+
manual.add('Codex Apps MCP token is expired; sign in to Codex App/CLI again so the connector can mint a fresh token.');
|
|
779
|
+
warnings.add('codex_apps_token_expired_observed');
|
|
780
|
+
blockers.add('codex_apps_token_expired_manual_reauth_required');
|
|
781
|
+
}
|
|
782
|
+
if (/SUPABASE_ACCESS_TOKEN[\s\S]{0,500}mcp server ['"`]?supabase['"`]?|mcp server ['"`]?supabase['"`]?[\s\S]{0,500}SUPABASE_ACCESS_TOKEN/i.test(text)) {
|
|
783
|
+
manual.add('Supabase MCP uses SUPABASE_ACCESS_TOKEN but the variable is unset; export the token or migrate that server to a read-only remote URL.');
|
|
784
|
+
warnings.add('supabase_access_token_missing_observed');
|
|
785
|
+
blockers.add('supabase_access_token_missing_manual_auth_required');
|
|
786
|
+
}
|
|
787
|
+
if (/node_repl[\s\S]{0,500}No such file or directory|No such file or directory[\s\S]{0,500}node_repl/i.test(text)) {
|
|
788
|
+
warnings.add('node_repl_missing_command_observed');
|
|
789
|
+
}
|
|
790
|
+
return {
|
|
791
|
+
...startupRepair,
|
|
792
|
+
ok: blockers.size === 0 && startupRepair?.ok !== false,
|
|
793
|
+
manual_actions: [...manual],
|
|
794
|
+
warnings: [...warnings],
|
|
795
|
+
blockers: [...blockers]
|
|
796
|
+
};
|
|
797
|
+
}
|
|
699
798
|
//# sourceMappingURL=doctor.js.map
|
|
@@ -56,7 +56,7 @@ export async function syncCodexAgentRoles(input) {
|
|
|
56
56
|
for (const role of DIRECTIVE_ROLES) {
|
|
57
57
|
const file = path.join(targetDir, `${role}.toml`);
|
|
58
58
|
const current = await fs.readFile(file, 'utf8').catch(() => '');
|
|
59
|
-
if (current && !
|
|
59
|
+
if (current && !isSksManagedDirectiveRole(current))
|
|
60
60
|
continue;
|
|
61
61
|
await writeTextAtomic(file, roleToml(role, rolePayloads[role]));
|
|
62
62
|
created.push(file);
|
|
@@ -84,13 +84,9 @@ export async function syncCodexAgentRoles(input) {
|
|
|
84
84
|
return report;
|
|
85
85
|
}
|
|
86
86
|
function roleToml(role, payload) {
|
|
87
|
-
const strategyLine = payload?.strategy === 'agent_type'
|
|
88
|
-
? `agent_type = "${payload.agent_type || role}"`
|
|
89
|
-
: `message_role_prefix = "${escapeToml(payload?.message_role_prefix || `Role: ${role}.`)}"`;
|
|
90
87
|
return [
|
|
91
88
|
`name = "${role}"`,
|
|
92
|
-
`description = "SKS managed 3.1.
|
|
93
|
-
strategyLine,
|
|
89
|
+
`description = "SKS managed 3.1.11 directive role: ${role}"`,
|
|
94
90
|
'model_reasoning_effort = "medium"',
|
|
95
91
|
role.includes('implementer') ? 'sandbox_mode = "workspace-write"' : 'sandbox_mode = "read-only"',
|
|
96
92
|
'approval_policy = "never"',
|
|
@@ -110,6 +106,10 @@ function roleToml(role, payload) {
|
|
|
110
106
|
''
|
|
111
107
|
].join('\n');
|
|
112
108
|
}
|
|
109
|
+
function isSksManagedDirectiveRole(text) {
|
|
110
|
+
return /SKS managed 3\.1\.(?:4|5|6|7|11) (?:directive|bounded) role/.test(text)
|
|
111
|
+
|| /\bmessage_role_prefix\s*=/.test(text) && /SKS managed 3\.1\./.test(text);
|
|
112
|
+
}
|
|
113
113
|
function blockersOf(value) {
|
|
114
114
|
return Boolean(value) && typeof value === 'object' && Array.isArray(value.blockers)
|
|
115
115
|
? (value.blockers).map((item) => String(item)).filter(Boolean)
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import os from 'node:os';
|
|
4
|
-
import { ensureDir, nowIso, writeJsonAtomic } from '../fsx.js';
|
|
4
|
+
import { ensureDir, nowIso, readJson, writeJsonAtomic } from '../fsx.js';
|
|
5
5
|
import { buildSksCoreSkillManifest } from '../codex-native/core-skill-manifest.js';
|
|
6
6
|
import { syncCoreSkillsIntegrity } from '../codex-native/core-skill-integrity.js';
|
|
7
7
|
import { dedupeProjectSkills } from '../codex-native/project-skill-dedupe.js';
|
|
8
8
|
const EXTERNAL_ROUTE_RESERVED = new Set(['ulw-loop', 'ulw-plan', 'start-work']);
|
|
9
|
+
const SKILL_SYNC_LOCK_STALE_AFTER_MS = 30000;
|
|
9
10
|
export async function syncCodexSksSkills(input) {
|
|
10
11
|
const root = path.resolve(input.root);
|
|
11
12
|
const skillsRoot = input.skillsRoot || path.join(process.env.CODEX_HOME || path.join(os.homedir(), '.codex'), 'skills');
|
|
@@ -71,6 +72,8 @@ export async function withSkillSyncLock(root, fn) {
|
|
|
71
72
|
const code = err && typeof err === 'object' && 'code' in err ? String(err.code) : '';
|
|
72
73
|
if (code !== 'EEXIST')
|
|
73
74
|
throw err;
|
|
75
|
+
if (await recoverStaleSkillSyncLock(lockPath))
|
|
76
|
+
continue;
|
|
74
77
|
if (Date.now() - started > 30000)
|
|
75
78
|
throw new Error(`Timed out waiting for skill sync lock: ${lockPath}`);
|
|
76
79
|
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
@@ -80,7 +83,8 @@ export async function withSkillSyncLock(root, fn) {
|
|
|
80
83
|
await writeJsonAtomic(path.join(lockPath, 'owner.json'), {
|
|
81
84
|
schema: 'sks.skill-sync-lock.v1',
|
|
82
85
|
pid: process.pid,
|
|
83
|
-
acquired_at: nowIso()
|
|
86
|
+
acquired_at: nowIso(),
|
|
87
|
+
stale_after_ms: SKILL_SYNC_LOCK_STALE_AFTER_MS
|
|
84
88
|
}).catch(() => undefined);
|
|
85
89
|
return await fn();
|
|
86
90
|
}
|
|
@@ -88,6 +92,37 @@ export async function withSkillSyncLock(root, fn) {
|
|
|
88
92
|
await fs.rm(lockPath, { recursive: true, force: true }).catch(() => undefined);
|
|
89
93
|
}
|
|
90
94
|
}
|
|
95
|
+
async function recoverStaleSkillSyncLock(lockPath) {
|
|
96
|
+
const ownerPath = path.join(lockPath, 'owner.json');
|
|
97
|
+
const stat = await fs.stat(lockPath).catch(() => null);
|
|
98
|
+
const owner = await readJson(ownerPath, null).catch(() => null);
|
|
99
|
+
const staleAfterMs = Number(owner?.stale_after_ms || SKILL_SYNC_LOCK_STALE_AFTER_MS);
|
|
100
|
+
const acquiredAt = owner?.acquired_at ? Date.parse(owner.acquired_at) : NaN;
|
|
101
|
+
const ageMs = Number.isFinite(acquiredAt) ? Date.now() - acquiredAt : stat ? Date.now() - stat.mtimeMs : 0;
|
|
102
|
+
if (owner?.schema === 'sks.skill-sync-lock.v1' && Number.isFinite(owner.pid)) {
|
|
103
|
+
if (ageMs <= staleAfterMs || pidAlive(Number(owner.pid)))
|
|
104
|
+
return false;
|
|
105
|
+
await fs.rm(lockPath, { recursive: true, force: true }).catch(() => undefined);
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
if (stat && Date.now() - stat.mtimeMs > staleAfterMs) {
|
|
109
|
+
await fs.rm(lockPath, { recursive: true, force: true }).catch(() => undefined);
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
function pidAlive(pid) {
|
|
115
|
+
if (!Number.isFinite(pid) || pid <= 0)
|
|
116
|
+
return false;
|
|
117
|
+
try {
|
|
118
|
+
process.kill(pid, 0);
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
catch (err) {
|
|
122
|
+
const code = err && typeof err === 'object' && 'code' in err ? String(err.code) : '';
|
|
123
|
+
return code === 'EPERM';
|
|
124
|
+
}
|
|
125
|
+
}
|
|
91
126
|
async function listSkillNames(root) {
|
|
92
127
|
const rows = await fs.readdir(root, { withFileTypes: true }).catch(() => []);
|
|
93
128
|
return rows.filter((row) => row.isDirectory()).map((row) => row.name).sort();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { ensureDir, nowIso, readText, sha256, writeJsonAtomic, writeTextAtomic } from '../fsx.js';
|
|
4
|
-
import { buildSksCoreSkillManifest, isSksManagedCoreSkillContent, renderCoreSkillTemplate } from './core-skill-manifest.js';
|
|
4
|
+
import { CORE_SKILL_TEMPLATE_VERSION, buildSksCoreSkillManifest, isSksManagedCoreSkillContent, renderCoreSkillTemplate } from './core-skill-manifest.js';
|
|
5
5
|
import { canonicalSkillName } from './skill-name-canonicalizer.js';
|
|
6
6
|
export async function syncCoreSkillsIntegrity(input) {
|
|
7
7
|
const root = path.resolve(input.root);
|
|
@@ -69,7 +69,12 @@ export async function syncCoreSkillsIntegrity(input) {
|
|
|
69
69
|
root,
|
|
70
70
|
apply,
|
|
71
71
|
skills_root: skillsRoot,
|
|
72
|
+
template_version: CORE_SKILL_TEMPLATE_VERSION,
|
|
72
73
|
manifest_sha256: sha256(JSON.stringify(manifest.skills.map((skill) => [skill.canonical_name, skill.content_sha256]))),
|
|
74
|
+
drift_detected_count: rows.filter((row) => row.action === 'restore-corrupted-managed-copy' || row.action === 'skip-user-authored').length,
|
|
75
|
+
restored_count: restored.length,
|
|
76
|
+
installed_count: installed.length,
|
|
77
|
+
user_collision_count: skippedUserAuthored.length,
|
|
73
78
|
rows,
|
|
74
79
|
installed,
|
|
75
80
|
restored,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { PACKAGE_VERSION, nowIso, sha256 } from '../fsx.js';
|
|
2
2
|
import { canonicalSkillName } from './skill-name-canonicalizer.js';
|
|
3
|
-
export const CORE_SKILL_TEMPLATE_VERSION = '
|
|
3
|
+
export const CORE_SKILL_TEMPLATE_VERSION = 'sks-core-skill-template.v1';
|
|
4
4
|
export const CORE_SKILL_MANAGED_BEGIN = '<!-- BEGIN SKS IMMUTABLE CORE SKILL -->';
|
|
5
5
|
export const CORE_SKILL_MANAGED_END = '<!-- END SKS IMMUTABLE CORE SKILL -->';
|
|
6
6
|
const CORE_SKILL_DEFINITIONS = [
|
|
@@ -1,22 +1,13 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
1
2
|
import path from 'node:path';
|
|
2
|
-
import { writeJsonAtomic } from '../fsx.js';
|
|
3
|
+
import { ensureDir, readJson, writeJsonAtomic } from '../fsx.js';
|
|
3
4
|
import { buildNativeCapabilityRepairMatrix } from './native-capability-repair-matrix.js';
|
|
4
5
|
export async function postcheckNativeCapabilities(input) {
|
|
5
6
|
const root = path.resolve(input.root);
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
return { ...state, after: 'unknown', blockers: ['computer_use_os_permission_or_capability_unknown'] };
|
|
11
|
-
}
|
|
12
|
-
if (state.id === 'chrome_web_review' && process.env.SKS_CHROME_EXTENSION_READY !== '1' && input.fixture !== 'all-repairable') {
|
|
13
|
-
return { ...state, after: 'unknown', blockers: ['codex_chrome_extension_readiness_not_verified'] };
|
|
14
|
-
}
|
|
15
|
-
if (state.blockers.length === 0 || verifiedAfterRepair)
|
|
16
|
-
return { ...state, after: state.repairability === 'manual-required' ? 'unknown' : 'verified', blockers: state.repairability === 'manual-required' ? state.blockers : [] };
|
|
17
|
-
return { ...state, after: 'blocked' };
|
|
18
|
-
});
|
|
19
|
-
const blockers = capabilities.flatMap((state) => state.after === 'verified' ? [] : state.blockers);
|
|
7
|
+
const fixture = input.fixture || false;
|
|
8
|
+
const matrix = input.matrix || await buildNativeCapabilityRepairMatrix({ root, fixture, reportPath: null });
|
|
9
|
+
const capabilities = await Promise.all(matrix.capabilities.map((state) => postcheckCapability(root, state, fixture)));
|
|
10
|
+
const blockers = capabilities.flatMap((state) => state.after === 'verified' || state.after === 'degraded' ? [] : state.blockers);
|
|
20
11
|
const checked = {
|
|
21
12
|
...matrix,
|
|
22
13
|
generated_at: new Date().toISOString(),
|
|
@@ -32,4 +23,141 @@ export async function postcheckNativeCapabilities(input) {
|
|
|
32
23
|
await writeJsonAtomic(reportPath, checked).catch(() => undefined);
|
|
33
24
|
return checked;
|
|
34
25
|
}
|
|
26
|
+
async function postcheckCapability(root, state, fixture) {
|
|
27
|
+
if (state.id === 'image_generation')
|
|
28
|
+
return postcheckImageGeneration(state, fixture);
|
|
29
|
+
if (state.id === 'image_followup_edit')
|
|
30
|
+
return postcheckImageFollowupEdit(root, state);
|
|
31
|
+
if (state.id === 'computer_use')
|
|
32
|
+
return postcheckComputerUse(state, fixture);
|
|
33
|
+
if (state.id === 'chrome_web_review')
|
|
34
|
+
return postcheckChromeWebReview(state, fixture);
|
|
35
|
+
if (state.id === 'codex_app_screenshot')
|
|
36
|
+
return postcheckAppScreenshot(root, state);
|
|
37
|
+
if (state.id === 'app_handoff')
|
|
38
|
+
return postcheckAppHandoff(state, fixture);
|
|
39
|
+
if (state.id === 'image_path_exposure')
|
|
40
|
+
return postcheckImagePathExposure(root, state, fixture);
|
|
41
|
+
if (state.id === 'saved_artifact_path_contract')
|
|
42
|
+
return postcheckSavedArtifactPathContract(root, state);
|
|
43
|
+
return { ...state, after: 'blocked', blockers: [...state.blockers, `unknown_capability:${state.id}`] };
|
|
44
|
+
}
|
|
45
|
+
function postcheckImageGeneration(state, fixture) {
|
|
46
|
+
if (fixture === 'all-repairable' || state.before === 'verified')
|
|
47
|
+
return verified(state);
|
|
48
|
+
return {
|
|
49
|
+
...state,
|
|
50
|
+
after: 'unknown',
|
|
51
|
+
blockers: ['imagegen_auth_or_codex_app_builtin_missing'],
|
|
52
|
+
warnings: [...new Set([...state.warnings, 'image_generation_not_verified_without_real_capability'])]
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
async function postcheckImageFollowupEdit(root, state) {
|
|
56
|
+
const contract = await validateSavedArtifactPathContract(root);
|
|
57
|
+
if (!contract.ok)
|
|
58
|
+
return { ...state, after: 'blocked', blockers: contract.blockers };
|
|
59
|
+
const sample = path.join(contract.imageArtifacts, 'postcheck-followup-sample.txt');
|
|
60
|
+
if (!(await writeReadSample(sample)))
|
|
61
|
+
return { ...state, after: 'blocked', blockers: ['image_followup_sample_artifact_unwritable'] };
|
|
62
|
+
return verified(state);
|
|
63
|
+
}
|
|
64
|
+
function postcheckComputerUse(state, _fixture) {
|
|
65
|
+
if (process.env.SKS_COMPUTER_USE_CAPABILITY === 'verified')
|
|
66
|
+
return verified(state);
|
|
67
|
+
return {
|
|
68
|
+
...state,
|
|
69
|
+
after: 'unknown',
|
|
70
|
+
blockers: ['computer_use_os_permission_or_capability_unknown'],
|
|
71
|
+
warnings: [...new Set([...state.warnings, 'manual_os_permission_required'])]
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
function postcheckChromeWebReview(state, fixture) {
|
|
75
|
+
if (fixture === 'all-repairable' || process.env.SKS_CHROME_EXTENSION_READY === '1')
|
|
76
|
+
return verified(state);
|
|
77
|
+
return {
|
|
78
|
+
...state,
|
|
79
|
+
after: 'unknown',
|
|
80
|
+
blockers: ['codex_chrome_extension_readiness_not_verified'],
|
|
81
|
+
warnings: [...new Set([...state.warnings, 'manual_chrome_extension_setup_required'])]
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
async function postcheckAppScreenshot(root, state) {
|
|
85
|
+
const dir = path.join(root, '.sneakoscope', 'app-screenshots');
|
|
86
|
+
const registry = path.join(dir, 'screenshot-registry.json');
|
|
87
|
+
if (!(await writeReadSample(path.join(dir, 'postcheck-screenshot-sample.txt')))) {
|
|
88
|
+
return { ...state, after: 'blocked', blockers: ['app_screenshot_directory_unwritable'] };
|
|
89
|
+
}
|
|
90
|
+
await writeJsonAtomic(registry, { schema: 'sks.app-screenshot-registry.v1', generated_at: new Date().toISOString(), screenshots: [] }).catch(() => undefined);
|
|
91
|
+
const json = await readJson(registry, {}).catch(() => ({}));
|
|
92
|
+
if (json.schema !== 'sks.app-screenshot-registry.v1')
|
|
93
|
+
return { ...state, after: 'blocked', blockers: ['app_screenshot_registry_invalid'] };
|
|
94
|
+
return verified(state);
|
|
95
|
+
}
|
|
96
|
+
function postcheckAppHandoff(state, fixture) {
|
|
97
|
+
if (fixture === 'all-repairable' || state.before === 'verified')
|
|
98
|
+
return verified(state);
|
|
99
|
+
return {
|
|
100
|
+
...state,
|
|
101
|
+
after: 'unknown',
|
|
102
|
+
blockers: ['codex_app_handoff_not_verified'],
|
|
103
|
+
warnings: [...new Set([...state.warnings, 'manual_app_handoff_approval_required'])]
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
async function postcheckImagePathExposure(root, state, fixture) {
|
|
107
|
+
if (fixture === 'all-repairable' || state.before === 'verified')
|
|
108
|
+
return verified(state);
|
|
109
|
+
const contract = await validateSavedArtifactPathContract(root);
|
|
110
|
+
if (contract.ok) {
|
|
111
|
+
return {
|
|
112
|
+
...state,
|
|
113
|
+
after: 'degraded',
|
|
114
|
+
blockers: [],
|
|
115
|
+
warnings: [...new Set([...state.warnings, 'using_saved_artifact_path_contract_fallback'])]
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
return { ...state, after: 'blocked', blockers: ['image_path_exposure_missing_without_fallback_contract', ...contract.blockers] };
|
|
119
|
+
}
|
|
120
|
+
async function postcheckSavedArtifactPathContract(root, state) {
|
|
121
|
+
const contract = await validateSavedArtifactPathContract(root);
|
|
122
|
+
if (!contract.ok)
|
|
123
|
+
return { ...state, after: 'blocked', blockers: contract.blockers };
|
|
124
|
+
if (!(await writeReadSample(path.join(contract.imageArtifacts, 'postcheck-contract-image.txt'))))
|
|
125
|
+
return { ...state, after: 'blocked', blockers: ['image_artifacts_directory_unwritable'] };
|
|
126
|
+
if (!(await writeReadSample(path.join(contract.appScreenshots, 'postcheck-contract-screenshot.txt'))))
|
|
127
|
+
return { ...state, after: 'blocked', blockers: ['app_screenshots_directory_unwritable'] };
|
|
128
|
+
return verified(state);
|
|
129
|
+
}
|
|
130
|
+
function verified(state) {
|
|
131
|
+
return { ...state, after: 'verified', blockers: [] };
|
|
132
|
+
}
|
|
133
|
+
async function validateSavedArtifactPathContract(root) {
|
|
134
|
+
const contractPath = path.join(root, '.sneakoscope', 'reports', 'saved-artifact-path-contract.json');
|
|
135
|
+
const contract = await readJson(contractPath, null).catch(() => null);
|
|
136
|
+
const imageArtifacts = String(contract?.image_artifacts || path.join(root, '.sneakoscope', 'image-artifacts'));
|
|
137
|
+
const appScreenshots = String(contract?.app_screenshots || path.join(root, '.sneakoscope', 'app-screenshots'));
|
|
138
|
+
const blockers = [];
|
|
139
|
+
if (contract?.schema !== 'sks.saved-artifact-path-contract.v1')
|
|
140
|
+
blockers.push('saved_artifact_path_contract_schema_invalid');
|
|
141
|
+
for (const dir of [imageArtifacts, appScreenshots]) {
|
|
142
|
+
try {
|
|
143
|
+
await ensureDir(dir);
|
|
144
|
+
await fs.access(dir);
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
blockers.push(`directory_unwritable:${path.basename(dir)}`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return { ok: blockers.length === 0, imageArtifacts, appScreenshots, blockers };
|
|
151
|
+
}
|
|
152
|
+
async function writeReadSample(file) {
|
|
153
|
+
try {
|
|
154
|
+
await ensureDir(path.dirname(file));
|
|
155
|
+
await fs.writeFile(file, 'sks-native-capability-postcheck\n', 'utf8');
|
|
156
|
+
const text = await fs.readFile(file, 'utf8');
|
|
157
|
+
return text === 'sks-native-capability-postcheck\n';
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
35
163
|
//# sourceMappingURL=native-capability-postcheck.js.map
|
|
@@ -109,7 +109,7 @@ async function stateForCapability(root, id, imageCapability, nativeFeatureMatrix
|
|
|
109
109
|
warnings: envVerified ? [] : ['manual_os_permission_required']
|
|
110
110
|
};
|
|
111
111
|
}
|
|
112
|
-
const chromeReady = process.env.SKS_CHROME_EXTENSION_READY === '1'
|
|
112
|
+
const chromeReady = process.env.SKS_CHROME_EXTENSION_READY === '1';
|
|
113
113
|
return {
|
|
114
114
|
id,
|
|
115
115
|
before: chromeReady ? 'verified' : 'unknown',
|