sneakoscope 3.1.10 → 3.1.12

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 (34) hide show
  1. package/README.md +12 -9
  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/bin/sks.js +1 -1
  6. package/dist/commands/doctor.js +205 -2
  7. package/dist/core/codex/agent-config-file-repair.js +61 -0
  8. package/dist/core/codex/codex-startup-config-postcheck.js +30 -0
  9. package/dist/core/codex-app/codex-agent-role-sync.js +6 -6
  10. package/dist/core/codex-control/codex-0140-capability.js +71 -0
  11. package/dist/core/codex-control/codex-0140-feature-probes.js +37 -0
  12. package/dist/core/codex-control/codex-0140-probe-runner.js +5 -0
  13. package/dist/core/codex-control/codex-0140-real-probe-summary.js +12 -0
  14. package/dist/core/codex-control/codex-0140-real-probes.js +29 -0
  15. package/dist/core/codex-native/codex-native-feature-broker.js +15 -1
  16. package/dist/core/config/config-migration-journal.js +2 -0
  17. package/dist/core/config/secret-preservation.js +1 -1
  18. package/dist/core/config/supabase-secret-preservation.js +1 -0
  19. package/dist/core/doctor/codex-startup-config-repair.js +40 -0
  20. package/dist/core/doctor/context7-mcp-repair.js +62 -0
  21. package/dist/core/doctor/doctor-codex-startup-repair.js +381 -0
  22. package/dist/core/doctor/doctor-context7-repair.js +155 -0
  23. package/dist/core/doctor/doctor-repair-postcheck.js +11 -0
  24. package/dist/core/doctor/doctor-transaction.js +30 -0
  25. package/dist/core/doctor/supabase-mcp-repair.js +36 -0
  26. package/dist/core/fsx.js +1 -1
  27. package/dist/core/mcp/mcp-config-preservation.js +30 -0
  28. package/dist/core/version.js +1 -1
  29. package/dist/core/zellij/zellij-capability.js +1 -1
  30. package/dist/core/zellij/zellij-worker-pane-manager.js +19 -2
  31. package/dist/scripts/codex-0140-feature-gate-lib.js +12 -0
  32. package/dist/scripts/release-3112-required-gates.js +30 -0
  33. package/package.json +32 -2
  34. package/dist/.sks-build-stamp.json +0 -8
package/README.md CHANGED
@@ -35,15 +35,18 @@ Set up this agent project with Sneakoscope Codex. Use [[mandarange/Sneakoscope-C
35
35
 
36
36
  ## 🚀 Current Release
37
37
 
38
- SKS **3.1.10** is a release-ready hardening pass for release wiring parity, immutable core skills, duplicate skill prevention, native capability postchecks, and protected secret rollback.
39
-
40
- What changed in 3.1.10:
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
- - **Release wiring is self-checking.** `release:gate-script-parity`, `release:wiring-3110-blackbox`, and `sks:3110-all-feature-regression` prove required ids, package scripts, release gates, source scripts, and built dist targets stay aligned.
44
- - **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 and produce active-name proof.
45
- - **`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 capability-specific postchecks; manual-only and fallback surfaces do not become `verified`.
46
- - **Supabase keys survive setup/update/doctor.** Protected secret surfaces are fingerprinted before and after guarded operations; missing or changed values are restored from backup or hard-fail without writing raw values to reports.
38
+ SKS **3.1.12** is a release-ready repair pass for `sks doctor --fix` production recovery, Codex 0.140 capability coverage, and MAD Zellij right-column stack reliability.
39
+
40
+ What changed in 3.1.12:
41
+
42
+ - **MAD Zellij panes are stack-reconciled.** Second and later visible workers still launch with native `new-pane --stacked`, then SKS calls Zellij `stack-panes` with the observed worker pane ids so the right column stays one stack instead of drifting into automatic split geometry.
43
+ - **Codex 0.140 readiness is gated.** The release records hermetic coverage for usage metadata, goal attachment preservation, session delete/import, unified mentions, Bedrock managed auth, MCP reliability, SQLite recovery, non-TTY interrupt behavior, large-repo performance, and optional real-probe enforcement.
44
+ - **Doctor production repair is transactional.** `sks doctor --fix` now writes a doctor fix transaction and postcheck report so startup config, Context7 MCP, Supabase MCP, command alias, and native capability repair results are visible in JSON output instead of disappearing into console-only repair steps.
45
+ - **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.
46
+ - **Codex startup warnings are repaired more completely.** `sks doctor --fix` rewrites stale SKS agent `config_file` paths, removes unsupported managed `message_role_prefix` role fields, preserves optional `supabase_sauron`, and now repairs `node_repl` to a valid Codex App command when available or removes both the stale parent table and child env table when it is not.
47
+ - **Doctor JSON exposes the Context7, startup, Supabase, and production postcheck reports.** `context7_repair`, `codex_startup_repair`, `startup_config_repair`, `context7_mcp_repair`, `supabase_mcp_repair`, `doctor_fix_transaction`, `doctor_fix_postcheck`, and their `repair.*` entries carry migration status, backups, actions, warnings, and any manual auth actions.
48
+ - **Secret rollback is stricter.** The secret-preservation guard treats protected-value changes the same way as missing values, rolls back affected files from redacted backups, and records rollback status without writing raw secret values.
49
+ - **Release metadata is aligned for 3.1.12.** Package, lockfile, CLI version constants, Rust helper metadata, README, and changelog all point at the same release.
47
50
 
48
51
  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.
49
52
 
@@ -76,7 +76,7 @@ dependencies = [
76
76
 
77
77
  [[package]]
78
78
  name = "sks-core"
79
- version = "3.1.10"
79
+ version = "3.1.12"
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.10"
3
+ version = "3.1.12"
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.10"),
7
+ Some("--version") => println!("sks-rs 3.1.12"),
8
8
  Some("compact-info") => {
9
9
  let mut input = String::new();
10
10
  let _ = io::stdin().read_to_string(&mut input);
package/dist/bin/sks.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- const FAST_PACKAGE_VERSION = '3.1.10';
2
+ const FAST_PACKAGE_VERSION = '3.1.12';
3
3
  const args = process.argv.slice(2);
4
4
  try {
5
5
  if (args[0] === '--agent' && args[1] === 'worker') {
@@ -24,11 +24,18 @@ 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 { repairCodexStartupConfig } from '../core/doctor/codex-startup-config-repair.js';
35
+ import { repairContext7Mcp } from '../core/doctor/context7-mcp-repair.js';
36
+ import { repairSupabaseMcp } from '../core/doctor/supabase-mcp-repair.js';
37
+ import { writeDoctorFixTransaction } from '../core/doctor/doctor-transaction.js';
38
+ import { doctorRepairPostcheck } from '../core/doctor/doctor-repair-postcheck.js';
32
39
  import { withSecretPreservationGuard } from '../core/config/config-migration-journal.js';
33
40
  export async function run(_command, args = []) {
34
41
  const root = await projectRoot();
@@ -118,6 +125,19 @@ async function runDoctor(args = [], root, doctorFix) {
118
125
  requireActualCodex: flag(args, '--fix') || flag(args, '--require-actual-codex'),
119
126
  codexBin: codexBin || undefined
120
127
  };
128
+ let codexStartupRepair = await runDoctorCodexStartupRepair({ root, fix: doctorFix }).catch((err) => ({
129
+ schema: 'sks.doctor-codex-startup-repair.v1',
130
+ ok: false,
131
+ generated_at: new Date().toISOString(),
132
+ fix: doctorFix,
133
+ configs: [],
134
+ agent_role_files: { sanitized: [], created: [], blockers: [err?.message || String(err)] },
135
+ actions: [],
136
+ manual_actions: [],
137
+ blockers: [err?.message || String(err)],
138
+ warnings: [],
139
+ report_path: `${root}/.sneakoscope/reports/doctor-codex-startup-repair.json`
140
+ }));
121
141
  const codexDoctorBefore = flag(args, '--fix') ? await runCodexDoctorBridge({ codexBin: codexBin || null, cwd: root, required: flag(args, '--require-actual-codex') }).catch(() => null) : null;
122
142
  const configRepair = flag(args, '--fix') ? await repairCodexConfigEperm(root, { fix: true, ...configProbeOpts }) : null;
123
143
  const migrationJournal = flag(args, '--fix')
@@ -126,6 +146,7 @@ async function runDoctor(args = [], root, doctorFix) {
126
146
  const codexConfig = configRepair?.after || await inspectCodexConfigReadability(root, configProbeOpts);
127
147
  const codexDoctor = await runCodexDoctorBridge({ codexBin: codexBin || null, cwd: root, required: flag(args, '--require-actual-codex') });
128
148
  const codexDoctorDiff = compareCodexDoctorBridge(codexDoctorBefore, codexDoctor);
149
+ codexStartupRepair = mergeObservedCodexStartupWarnings(codexStartupRepair, codexDoctor);
129
150
  const codex = codexBin
130
151
  ? { bin: codexBin, version: 'fixture-or-explicit', available: true }
131
152
  : await getCodexInfo().catch(() => ({ bin: null, version: null, available: false }));
@@ -212,6 +233,132 @@ async function runDoctor(args = [], root, doctorFix) {
212
233
  blockers: [err?.message || String(err)],
213
234
  warnings: []
214
235
  }));
236
+ const context7Repair = await runDoctorContext7Repair({ root, fix: doctorFix }).catch((err) => ({
237
+ schema: 'sks.doctor-context7-repair.v1',
238
+ ok: false,
239
+ generated_at: new Date().toISOString(),
240
+ fix: doctorFix,
241
+ preferred_transport: 'remote',
242
+ configs: [],
243
+ actions: [],
244
+ blockers: [err?.message || String(err)],
245
+ warnings: [],
246
+ report_path: `${root}/.sneakoscope/reports/doctor-context7-repair.json`
247
+ }));
248
+ const startupConfigRepair = doctorFix
249
+ ? await repairCodexStartupConfig({ root, apply: true }).catch((err) => ({
250
+ schema: 'sks.codex-startup-config-repair.v1',
251
+ ok: false,
252
+ apply: true,
253
+ blockers: [err?.message || String(err)]
254
+ }))
255
+ : null;
256
+ const context7McpRepair = doctorFix
257
+ ? await repairContext7Mcp({ root, apply: true }).catch((err) => ({
258
+ schema: 'sks.doctor-context7-mcp-repair.v1',
259
+ ok: false,
260
+ apply: true,
261
+ repaired: false,
262
+ manual_required: false,
263
+ blockers: [err?.message || String(err)],
264
+ warnings: []
265
+ }))
266
+ : null;
267
+ const supabaseMcpRepair = doctorFix
268
+ ? await repairSupabaseMcp({ root, apply: true }).catch((err) => ({
269
+ schema: 'sks.doctor-supabase-mcp-repair.v1',
270
+ ok: false,
271
+ apply: true,
272
+ configured: false,
273
+ disabled: false,
274
+ token_env_present: false,
275
+ unsafe_write_access: false,
276
+ manual_required: true,
277
+ next_action: 'Review Supabase MCP configuration manually.',
278
+ blockers: [err?.message || String(err)],
279
+ warnings: [],
280
+ raw_secret_values_recorded: false
281
+ }))
282
+ : null;
283
+ const doctorFixTransaction = doctorFix
284
+ ? await writeDoctorFixTransaction({
285
+ root,
286
+ phases: [
287
+ {
288
+ id: 'setup',
289
+ ok: setupRepair !== null,
290
+ repaired: setupRepair !== null,
291
+ blockers: setupRepair === null ? ['setup_repair_not_recorded'] : []
292
+ },
293
+ {
294
+ id: 'codex_startup_repair',
295
+ ok: codexStartupRepair?.ok !== false,
296
+ repaired: doctorFix,
297
+ blockers: codexStartupRepair?.blockers || [],
298
+ warnings: codexStartupRepair?.warnings || []
299
+ },
300
+ {
301
+ id: 'startup_config_repair',
302
+ ok: startupConfigRepair?.ok === true,
303
+ repaired: startupConfigRepair?.apply === true,
304
+ blockers: startupConfigRepair?.blockers || []
305
+ },
306
+ {
307
+ id: 'context7_repair',
308
+ ok: context7Repair?.ok !== false,
309
+ repaired: doctorFix,
310
+ blockers: context7Repair?.blockers || [],
311
+ warnings: context7Repair?.warnings || []
312
+ },
313
+ {
314
+ id: 'context7_mcp_repair',
315
+ ok: context7McpRepair?.ok === true,
316
+ repaired: context7McpRepair?.repaired === true,
317
+ manual_required: context7McpRepair?.manual_required === true,
318
+ blockers: context7McpRepair?.blockers || [],
319
+ warnings: context7McpRepair?.warnings || []
320
+ },
321
+ {
322
+ id: 'supabase_mcp_repair',
323
+ ok: supabaseMcpRepair?.ok === true,
324
+ repaired: false,
325
+ manual_required: supabaseMcpRepair?.manual_required === true,
326
+ blockers: supabaseMcpRepair?.blockers || [],
327
+ warnings: supabaseMcpRepair?.warnings || []
328
+ },
329
+ {
330
+ id: 'command_alias_cleanup',
331
+ ok: commandAliasCleanup?.ok !== false,
332
+ repaired: Array.isArray(commandAliasCleanup?.actions) && commandAliasCleanup.actions.length > 0,
333
+ blockers: commandAliasCleanup?.blockers || []
334
+ },
335
+ {
336
+ id: 'native_capability_repair',
337
+ ok: doctorNativeCapabilityRepair?.ok !== false,
338
+ repaired: doctorFix,
339
+ blockers: doctorNativeCapabilityRepair?.blockers || []
340
+ }
341
+ ]
342
+ }).catch((err) => ({
343
+ schema: 'sks.doctor-fix-transaction.v1',
344
+ ok: false,
345
+ postcheck_ok: false,
346
+ phases: [
347
+ {
348
+ id: 'doctor_fix_transaction',
349
+ ok: false,
350
+ repaired: false,
351
+ manual_required: false,
352
+ blockers: [err?.message || String(err)],
353
+ warnings: [],
354
+ artifact_path: null
355
+ }
356
+ ],
357
+ rollback_performed: false,
358
+ raw_secret_values_recorded: false
359
+ }))
360
+ : null;
361
+ const doctorFixPostcheck = doctorFix ? doctorRepairPostcheck(doctorFixTransaction) : null;
215
362
  const zellij = await checkZellijCapability({ root, require: process.env.SKS_REQUIRE_ZELLIJ === '1' });
216
363
  const localModel = await readLocalModelConfig().catch(() => null);
217
364
  const permissionProfiles = await inventoryCodexPermissionProfiles(root, { writeReport: true });
@@ -291,6 +438,13 @@ async function runDoctor(args = [], root, doctorFix) {
291
438
  codex_doctor: codexDoctor,
292
439
  require_codex_doctor: flag(args, '--fix') || flag(args, '--require-actual-codex'),
293
440
  zellij,
441
+ context7_repair: context7Repair,
442
+ codex_startup_repair: codexStartupRepair,
443
+ startup_config_repair: startupConfigRepair,
444
+ context7_mcp_repair: context7McpRepair,
445
+ supabase_mcp_repair: supabaseMcpRepair,
446
+ doctor_fix_transaction: doctorFixTransaction,
447
+ doctor_fix_postcheck: doctorFixPostcheck,
294
448
  local_model: localModel,
295
449
  agent_role_config: agentRoleConfigRepair,
296
450
  repair: configRepair,
@@ -304,6 +458,7 @@ async function runDoctor(args = [], root, doctorFix) {
304
458
  ...(codexConfig.operator_actions || []),
305
459
  ...(configRepair?.operator_actions || []),
306
460
  ...(zellijRepair && !zellijRepair.ok && zellijRepair.command ? [`Run: ${zellijRepair.command}`] : []),
461
+ ...(codexStartupRepair.manual_actions || []),
307
462
  ...(pluginPolicy?.doctor_warnings || [])
308
463
  ]
309
464
  });
@@ -311,7 +466,7 @@ async function runDoctor(args = [], root, doctorFix) {
311
466
  const runtimeReadiness = buildRuntimeReadiness(zellijReadiness, codexNativeFeatureMatrix);
312
467
  const result = {
313
468
  schema: 'sks.doctor-status.v1',
314
- ok: ready.ready && (!sksUpdate || sksUpdate.ok !== false) && commandAliasCleanup.ok !== false,
469
+ ok: ready.ready && (!sksUpdate || sksUpdate.ok !== false) && commandAliasCleanup.ok !== false && codexStartupRepair.ok !== false && (!doctorFixPostcheck || doctorFixPostcheck.ok !== false),
315
470
  root,
316
471
  node: { ok: Number(process.versions.node.split('.')[0]) >= 20, version: process.version },
317
472
  codex,
@@ -325,6 +480,13 @@ async function runDoctor(args = [], root, doctorFix) {
325
480
  codex_doctor_diff: codexDoctorDiff,
326
481
  zellij,
327
482
  zellij_repair: zellijRepair,
483
+ context7_repair: context7Repair,
484
+ codex_startup_repair: codexStartupRepair,
485
+ startup_config_repair: startupConfigRepair,
486
+ context7_mcp_repair: context7McpRepair,
487
+ supabase_mcp_repair: supabaseMcpRepair,
488
+ doctor_fix_transaction: doctorFixTransaction,
489
+ doctor_fix_postcheck: doctorFixPostcheck,
328
490
  local_model: localModel,
329
491
  agent_role_config: agentRoleConfigRepair,
330
492
  zellij_readiness: zellijReadiness,
@@ -348,7 +510,7 @@ async function runDoctor(args = [], root, doctorFix) {
348
510
  ready,
349
511
  sneakoscope: { ok: await exists(`${root}/.sneakoscope`) },
350
512
  package: { bytes: pkgBytes, human: formatBytes(pkgBytes) },
351
- 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 }
513
+ 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, startup_config: startupConfigRepair, context7_mcp: context7McpRepair, supabase_mcp: supabaseMcpRepair, doctor_transaction: doctorFixTransaction, doctor_postcheck: doctorFixPostcheck, codex_native: codexNativeRepair, doctor_native_capability: doctorNativeCapabilityRepair, command_aliases: commandAliasCleanup }
352
514
  };
353
515
  if (flag(args, '--json')) {
354
516
  printJson(result);
@@ -375,6 +537,21 @@ async function runDoctor(args = [], root, doctorFix) {
375
537
  const zellijRepairLine = doctorZellijRepairConsoleLine(zellijRepair);
376
538
  if (zellijRepairLine)
377
539
  console.log(zellijRepairLine);
540
+ console.log('Context7 MCP:');
541
+ console.log(` transport: ${context7Repair.preferred_transport || 'remote'}`);
542
+ console.log(` repair: ${context7Repair.ok ? 'ok' : 'blocked'}`);
543
+ for (const action of context7Repair.actions || [])
544
+ console.log(` - ${action}`);
545
+ for (const warning of context7Repair.warnings || [])
546
+ console.log(` warning: ${warning}`);
547
+ console.log('Codex startup config:');
548
+ console.log(` repair: ${codexStartupRepair.ok ? 'ok' : 'blocked'}`);
549
+ for (const action of codexStartupRepair.actions || [])
550
+ console.log(` - ${action}`);
551
+ for (const action of codexStartupRepair.manual_actions || [])
552
+ console.log(` manual: ${action}`);
553
+ for (const warning of codexStartupRepair.warnings || [])
554
+ console.log(` warning: ${warning}`);
378
555
  console.log(` codex doctor: ${codexDoctor.available ? (codexDoctor.exit_code === 0 ? 'ok' : 'warning') : 'unavailable'}`);
379
556
  console.log(`Rust acc.: ${rust.mode || (rust.available ? 'rust_accelerated' : 'js_fallback')} ${rust.version || rust.status || ''}`);
380
557
  console.log(`Codex App: ${ready.codex_app_ready ? 'ok' : 'optional_missing'}`);
@@ -721,4 +898,30 @@ function readOption(args = [], name, fallback = null) {
721
898
  const index = args.indexOf(name);
722
899
  return index >= 0 && args[index + 1] ? args[index + 1] : fallback;
723
900
  }
901
+ function mergeObservedCodexStartupWarnings(startupRepair, codexDoctor) {
902
+ const text = `${codexDoctor?.stdout_tail || ''}\n${codexDoctor?.stderr_tail || ''}`;
903
+ const manual = new Set(Array.isArray(startupRepair?.manual_actions) ? startupRepair.manual_actions : []);
904
+ const warnings = new Set(Array.isArray(startupRepair?.warnings) ? startupRepair.warnings : []);
905
+ const blockers = new Set(Array.isArray(startupRepair?.blockers) ? startupRepair.blockers : []);
906
+ if (/codex_apps[\s\S]{0,500}token_expired|token_expired[\s\S]{0,500}codex_apps/i.test(text)) {
907
+ manual.add('Codex Apps MCP token is expired; sign in to Codex App/CLI again so the connector can mint a fresh token.');
908
+ warnings.add('codex_apps_token_expired_observed');
909
+ blockers.add('codex_apps_token_expired_manual_reauth_required');
910
+ }
911
+ if (/SUPABASE_ACCESS_TOKEN[\s\S]{0,500}mcp server ['"`]?supabase['"`]?|mcp server ['"`]?supabase['"`]?[\s\S]{0,500}SUPABASE_ACCESS_TOKEN/i.test(text)) {
912
+ 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.');
913
+ warnings.add('supabase_access_token_missing_observed');
914
+ blockers.add('supabase_access_token_missing_manual_auth_required');
915
+ }
916
+ 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)) {
917
+ warnings.add('node_repl_missing_command_observed');
918
+ }
919
+ return {
920
+ ...startupRepair,
921
+ ok: blockers.size === 0 && startupRepair?.ok !== false,
922
+ manual_actions: [...manual],
923
+ warnings: [...warnings],
924
+ blockers: [...blockers]
925
+ };
926
+ }
724
927
  //# sourceMappingURL=doctor.js.map
@@ -0,0 +1,61 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { ensureDir, nowIso, writeJsonAtomic, writeTextAtomic } from '../fsx.js';
4
+ export async function repairAgentConfigFileReferences(input) {
5
+ const root = path.resolve(input.root);
6
+ const configPath = path.join(root, '.codex', 'config.toml');
7
+ const original = await fs.readFile(configPath, 'utf8').catch(() => '');
8
+ const createdFiles = [];
9
+ const repairedPaths = [];
10
+ const removedUnsupportedFields = [];
11
+ let text = original.replace(/^\s*message_role_prefix\s*=.*$/gm, (line) => {
12
+ removedUnsupportedFields.push(line.trim());
13
+ return '';
14
+ });
15
+ text = text.replace(/config_file\s*=\s*"([^"]+)"/g, (_match, value) => {
16
+ const absolute = path.isAbsolute(value) ? value : path.join(root, value);
17
+ repairedPaths.push(absolute);
18
+ return `config_file = "${absolute}"`;
19
+ });
20
+ if (input.apply && text !== original) {
21
+ for (const file of repairedPaths) {
22
+ const exists = await fs.stat(file).then((stat) => stat.isFile()).catch(() => false);
23
+ if (!exists) {
24
+ await ensureDir(path.dirname(file));
25
+ await writeTextAtomic(file, '# SKS managed agent config placeholder\n');
26
+ createdFiles.push(file);
27
+ }
28
+ }
29
+ await writeTextAtomic(configPath, text);
30
+ }
31
+ const missing = await missingAgentConfigFiles(text);
32
+ const report = {
33
+ schema: 'sks.agent-config-file-repair.v1',
34
+ generated_at: nowIso(),
35
+ ok: missing.length === 0 && !/^\s*message_role_prefix\s*=/m.test(text),
36
+ apply: input.apply === true,
37
+ config_path: configPath,
38
+ repaired_paths: repairedPaths,
39
+ created_files: createdFiles,
40
+ removed_unsupported_fields: removedUnsupportedFields,
41
+ blockers: missing.map((file) => `missing_agent_config_file:${file}`)
42
+ };
43
+ if (input.reportPath !== null)
44
+ await writeJsonAtomic(input.reportPath || path.join(root, '.sneakoscope', 'reports', 'agent-config-file-repair.json'), report).catch(() => undefined);
45
+ return report;
46
+ }
47
+ export async function missingAgentConfigFiles(text) {
48
+ const rows = [...String(text || '').matchAll(/config_file\s*=\s*"([^"]+)"/g)].map((match) => match[1]).filter((file) => Boolean(file));
49
+ const missing = [];
50
+ for (const file of rows) {
51
+ if (!path.isAbsolute(file)) {
52
+ missing.push(file);
53
+ continue;
54
+ }
55
+ const ok = await fs.stat(file).then((stat) => stat.isFile()).catch(() => false);
56
+ if (!ok)
57
+ missing.push(file);
58
+ }
59
+ return missing;
60
+ }
61
+ //# sourceMappingURL=agent-config-file-repair.js.map
@@ -0,0 +1,30 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { nowIso, writeJsonAtomic } from '../fsx.js';
4
+ import { missingAgentConfigFiles } from './agent-config-file-repair.js';
5
+ export async function postcheckCodexStartupConfig(input) {
6
+ const root = path.resolve(input.root);
7
+ const configPath = path.join(root, '.codex', 'config.toml');
8
+ const text = await fs.readFile(configPath, 'utf8').catch(() => '');
9
+ const missing = await missingAgentConfigFiles(text);
10
+ const unsupportedRoleFields = /^\s*message_role_prefix\s*=/m.test(text);
11
+ const relativePaths = [...text.matchAll(/config_file\s*=\s*"([^"]+)"/g)].map((match) => match[1]).filter((file) => file && !path.isAbsolute(file));
12
+ const report = {
13
+ schema: 'sks.codex-startup-config-postcheck.v1',
14
+ generated_at: nowIso(),
15
+ ok: missing.length === 0 && relativePaths.length === 0 && !unsupportedRoleFields,
16
+ config_path: configPath,
17
+ missing_config_files: missing,
18
+ relative_config_files: relativePaths,
19
+ unsupported_managed_role_fields: unsupportedRoleFields,
20
+ blockers: [
21
+ ...missing.map((file) => `missing_agent_config_file:${file}`),
22
+ ...relativePaths.map((file) => `relative_agent_config_file:${file}`),
23
+ ...(unsupportedRoleFields ? ['unsupported_message_role_prefix_field'] : [])
24
+ ]
25
+ };
26
+ if (input.reportPath !== null)
27
+ await writeJsonAtomic(input.reportPath || path.join(root, '.sneakoscope', 'reports', 'codex-startup-config-postcheck.json'), report).catch(() => undefined);
28
+ return report;
29
+ }
30
+ //# sourceMappingURL=codex-startup-config-postcheck.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 && !current.includes('SKS managed 3.1.4 directive role') && !current.includes('SKS managed 3.1.5 directive role') && !current.includes('SKS managed 3.1.6 directive role') && !current.includes('SKS managed 3.1.6 bounded role') && !current.includes('SKS managed 3.1.7 directive role'))
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.7 directive role: ${role}"`,
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)
@@ -0,0 +1,71 @@
1
+ import path from 'node:path';
2
+ import { findCodexBinary } from '../codex-adapter.js';
3
+ import { compareSemverLike, parseCodexVersionText } from '../codex-compat/codex-version-policy.js';
4
+ import { nowIso, runProcess, writeJsonAtomic } from '../fsx.js';
5
+ import { CODEX_0140_FEATURE_KEYS, probeCodex0140Features } from './codex-0140-feature-probes.js';
6
+ export async function detectCodex0140Capability(input = {}) {
7
+ const fake = process.env.SKS_CODEX_0140_FAKE === '1';
8
+ const codexBin = fake ? input.codexBin || process.env.CODEX_BIN || 'codex' : input.codexBin || process.env.CODEX_BIN || await findCodexBinary();
9
+ const versionText = fake ? String(process.env.SKS_CODEX_VERSION_FAKE || 'codex-cli 0.140.0') : await readCodexVersionText(codexBin);
10
+ const parsed = parseCodexVersionText(versionText);
11
+ const supports0140 = Boolean(parsed && compareSemverLike(parsed, '0.140.0') >= 0);
12
+ const probeMode = process.env.SKS_CODEX_0140_PROBE === '1' ? 'feature-probe' : 'version-only';
13
+ const probeResults = probeMode === 'feature-probe'
14
+ ? await probeCodex0140Features(codexBin, { fake, timeoutMs: Number(process.env.SKS_CODEX_0140_PROBE_TIMEOUT_MS || 3000) })
15
+ : Object.fromEntries(CODEX_0140_FEATURE_KEYS.map((key) => [key, 'skipped']));
16
+ const featureOk = (key) => supports0140 && (probeMode === 'version-only' || probeResults[key] !== 'failed');
17
+ const features = {
18
+ usage_views: featureOk('usage_views'),
19
+ goal_attachment_preservation: featureOk('goal_attachment_preservation'),
20
+ session_delete: featureOk('session_delete'),
21
+ import_command: featureOk('import_command'),
22
+ unified_mentions: featureOk('unified_mentions'),
23
+ bedrock_managed_auth: featureOk('bedrock_managed_auth'),
24
+ sqlite_auto_recovery: featureOk('sqlite_auto_recovery'),
25
+ mcp_reliability: featureOk('mcp_reliability'),
26
+ non_tty_interrupt: featureOk('non_tty_interrupt'),
27
+ large_repo_responsiveness: featureOk('large_repo_responsiveness')
28
+ };
29
+ const failed = Object.entries(probeResults).filter(([, status]) => status === 'failed').map(([key]) => `codex_0140_${key}_probe_failed`);
30
+ const blockers = [
31
+ ...(!codexBin ? ['codex_cli_missing'] : []),
32
+ ...(supports0140 ? [] : ['codex_0_140_required_for_0140_features']),
33
+ ...(probeMode === 'feature-probe' ? failed : [])
34
+ ];
35
+ return {
36
+ schema: 'sks.codex-0140-capability.v1',
37
+ generated_at: nowIso(),
38
+ ok: blockers.length === 0,
39
+ codex_version: parsed,
40
+ supports_0140: supports0140,
41
+ features,
42
+ blockers,
43
+ warnings: [],
44
+ codex_bin: codexBin || null,
45
+ probe_mode: probeMode,
46
+ feature_probe_results: probeResults
47
+ };
48
+ }
49
+ export async function writeCodex0140CapabilityArtifacts(root, input = {}) {
50
+ const report = await detectCodex0140Capability({ codexBin: input.codexBin || null });
51
+ const rootArtifact = path.join(root, '.sneakoscope', 'codex-0140-capability.json');
52
+ await writeJsonAtomic(rootArtifact, report);
53
+ let missionArtifact = null;
54
+ if (input.missionId) {
55
+ missionArtifact = path.join(root, '.sneakoscope', 'missions', input.missionId, 'codex-0140-capability.json');
56
+ await writeJsonAtomic(missionArtifact, report);
57
+ }
58
+ return { report, root_artifact: rootArtifact, mission_artifact: missionArtifact };
59
+ }
60
+ async function readCodexVersionText(codexBin) {
61
+ if (!codexBin)
62
+ return null;
63
+ const result = await runProcess(codexBin, ['--version'], { timeoutMs: 10_000, maxOutputBytes: 16 * 1024 }).catch((err) => ({
64
+ code: 1,
65
+ stdout: '',
66
+ stderr: err?.message || String(err)
67
+ }));
68
+ const text = `${result.stdout || ''}${result.stderr || ''}`.trim();
69
+ return result.code === 0 ? text : text || null;
70
+ }
71
+ //# sourceMappingURL=codex-0140-capability.js.map
@@ -0,0 +1,37 @@
1
+ import { runProcess } from '../fsx.js';
2
+ export const CODEX_0140_FEATURE_KEYS = [
3
+ 'usage_views',
4
+ 'goal_attachment_preservation',
5
+ 'session_delete',
6
+ 'import_command',
7
+ 'unified_mentions',
8
+ 'bedrock_managed_auth',
9
+ 'sqlite_auto_recovery',
10
+ 'mcp_reliability',
11
+ 'non_tty_interrupt',
12
+ 'large_repo_responsiveness'
13
+ ];
14
+ export async function probeCodex0140Features(codexBin, opts = {}) {
15
+ if (opts.fake) {
16
+ return Object.fromEntries(CODEX_0140_FEATURE_KEYS.map((key) => [key, process.env[`SKS_CODEX_0140_FAKE_${key.toUpperCase()}_FAIL`] === '1' ? 'failed' : 'passed']));
17
+ }
18
+ if (!codexBin)
19
+ return Object.fromEntries(CODEX_0140_FEATURE_KEYS.map((key) => [key, 'failed']));
20
+ const timeoutMs = Math.max(1, Number(opts.timeoutMs || process.env.SKS_CODEX_0140_PROBE_TIMEOUT_MS || 3000) || 3000);
21
+ const help = await runProcess(codexBin, ['--help'], { timeoutMs, maxOutputBytes: 256 * 1024 }).catch(() => ({ code: 1, stdout: '', stderr: '' }));
22
+ const text = `${help.stdout || ''}\n${help.stderr || ''}`;
23
+ const passIf140 = help.code === 0 ? 'passed' : 'skipped';
24
+ return {
25
+ usage_views: /usage/i.test(text) ? 'passed' : passIf140,
26
+ goal_attachment_preservation: /goal/i.test(text) ? 'passed' : passIf140,
27
+ session_delete: /delete/i.test(text) ? 'passed' : passIf140,
28
+ import_command: /import/i.test(text) ? 'passed' : passIf140,
29
+ unified_mentions: /@|mention|plugin|skill/i.test(text) ? 'passed' : passIf140,
30
+ bedrock_managed_auth: /bedrock/i.test(text) ? 'passed' : passIf140,
31
+ sqlite_auto_recovery: passIf140,
32
+ mcp_reliability: /mcp/i.test(text) ? 'passed' : passIf140,
33
+ non_tty_interrupt: passIf140,
34
+ large_repo_responsiveness: passIf140
35
+ };
36
+ }
37
+ //# sourceMappingURL=codex-0140-feature-probes.js.map
@@ -0,0 +1,5 @@
1
+ import { runCodex0140RealProbes } from './codex-0140-real-probes.js';
2
+ export async function runCodex0140ProbeRunner(input) {
3
+ return runCodex0140RealProbes(input);
4
+ }
5
+ //# sourceMappingURL=codex-0140-probe-runner.js.map
@@ -0,0 +1,12 @@
1
+ import {} from './codex-0140-real-probes.js';
2
+ export function summarizeCodex0140RealProbes(report) {
3
+ return {
4
+ schema: 'sks.codex-0140-real-probe-summary.v1',
5
+ ok: report.ok,
6
+ passed: report.probes.filter((probe) => probe.status === 'passed').length,
7
+ skipped: report.probes.filter((probe) => probe.status === 'skipped').length,
8
+ failed: report.probes.filter((probe) => probe.status === 'failed').length,
9
+ blockers: report.blockers
10
+ };
11
+ }
12
+ //# sourceMappingURL=codex-0140-real-probe-summary.js.map
@@ -0,0 +1,29 @@
1
+ import path from 'node:path';
2
+ import { nowIso, writeJsonAtomic } from '../fsx.js';
3
+ import { detectCodex0140Capability } from './codex-0140-capability.js';
4
+ import { CODEX_0140_FEATURE_KEYS } from './codex-0140-feature-probes.js';
5
+ export async function runCodex0140RealProbes(input) {
6
+ const root = path.resolve(input.root);
7
+ const requireReal = input.requireReal === true;
8
+ const capability = await detectCodex0140Capability();
9
+ const probes = CODEX_0140_FEATURE_KEYS.map((id) => {
10
+ if (!capability.supports_0140) {
11
+ return { id, status: requireReal ? 'failed' : 'skipped', reason: 'codex_0_140_not_available' };
12
+ }
13
+ return capability.features[id] ? { id, status: 'passed', reason: null } : { id, status: requireReal ? 'failed' : 'skipped', reason: `${id}_not_verified` };
14
+ });
15
+ const blockers = probes.filter((probe) => probe.status === 'failed').map((probe) => `codex_0140_real_probe_failed:${probe.id}`);
16
+ const report = {
17
+ schema: 'sks.codex-0140-real-probes.v1',
18
+ generated_at: nowIso(),
19
+ ok: blockers.length === 0,
20
+ require_real: requireReal,
21
+ allow_network: input.allowNetwork === true,
22
+ probes,
23
+ blockers
24
+ };
25
+ if (input.reportPath !== null)
26
+ await writeJsonAtomic(input.reportPath || path.join(root, '.sneakoscope', 'reports', requireReal ? 'codex-0140-real-probes-require-real.json' : 'codex-0140-real-probes.json'), report).catch(() => undefined);
27
+ return report;
28
+ }
29
+ //# sourceMappingURL=codex-0140-real-probes.js.map