sneakoscope 3.1.10 → 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 CHANGED
@@ -35,15 +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.10** is a release-ready hardening pass for release wiring parity, immutable core skills, duplicate skill prevention, native capability postchecks, and protected secret rollback.
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.10:
40
+ What changed in 3.1.11:
41
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.
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.
47
47
 
48
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.
49
49
 
@@ -76,7 +76,7 @@ dependencies = [
76
76
 
77
77
  [[package]]
78
78
  name = "sks-core"
79
- version = "3.1.10"
79
+ version = "3.1.11"
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.11"
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.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.10",
5
- "source_digest": "57a7f5ee1f3ac797f46e3dfd59365b1e147e4637a8ddd18fde2cbee802fec0ac",
6
- "source_file_count": 2609,
7
- "built_at_source_time": 1781550268084
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
@@ -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.11';
3
3
  const args = process.argv.slice(2);
4
4
  try {
5
5
  if (args[0] === '--agent' && args[1] === 'worker') {
@@ -24,6 +24,8 @@ 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';
@@ -118,6 +120,19 @@ async function runDoctor(args = [], root, doctorFix) {
118
120
  requireActualCodex: flag(args, '--fix') || flag(args, '--require-actual-codex'),
119
121
  codexBin: codexBin || undefined
120
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
+ }));
121
136
  const codexDoctorBefore = flag(args, '--fix') ? await runCodexDoctorBridge({ codexBin: codexBin || null, cwd: root, required: flag(args, '--require-actual-codex') }).catch(() => null) : null;
122
137
  const configRepair = flag(args, '--fix') ? await repairCodexConfigEperm(root, { fix: true, ...configProbeOpts }) : null;
123
138
  const migrationJournal = flag(args, '--fix')
@@ -126,6 +141,7 @@ async function runDoctor(args = [], root, doctorFix) {
126
141
  const codexConfig = configRepair?.after || await inspectCodexConfigReadability(root, configProbeOpts);
127
142
  const codexDoctor = await runCodexDoctorBridge({ codexBin: codexBin || null, cwd: root, required: flag(args, '--require-actual-codex') });
128
143
  const codexDoctorDiff = compareCodexDoctorBridge(codexDoctorBefore, codexDoctor);
144
+ codexStartupRepair = mergeObservedCodexStartupWarnings(codexStartupRepair, codexDoctor);
129
145
  const codex = codexBin
130
146
  ? { bin: codexBin, version: 'fixture-or-explicit', available: true }
131
147
  : await getCodexInfo().catch(() => ({ bin: null, version: null, available: false }));
@@ -212,6 +228,18 @@ async function runDoctor(args = [], root, doctorFix) {
212
228
  blockers: [err?.message || String(err)],
213
229
  warnings: []
214
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
+ }));
215
243
  const zellij = await checkZellijCapability({ root, require: process.env.SKS_REQUIRE_ZELLIJ === '1' });
216
244
  const localModel = await readLocalModelConfig().catch(() => null);
217
245
  const permissionProfiles = await inventoryCodexPermissionProfiles(root, { writeReport: true });
@@ -291,6 +319,8 @@ async function runDoctor(args = [], root, doctorFix) {
291
319
  codex_doctor: codexDoctor,
292
320
  require_codex_doctor: flag(args, '--fix') || flag(args, '--require-actual-codex'),
293
321
  zellij,
322
+ context7_repair: context7Repair,
323
+ codex_startup_repair: codexStartupRepair,
294
324
  local_model: localModel,
295
325
  agent_role_config: agentRoleConfigRepair,
296
326
  repair: configRepair,
@@ -304,6 +334,7 @@ async function runDoctor(args = [], root, doctorFix) {
304
334
  ...(codexConfig.operator_actions || []),
305
335
  ...(configRepair?.operator_actions || []),
306
336
  ...(zellijRepair && !zellijRepair.ok && zellijRepair.command ? [`Run: ${zellijRepair.command}`] : []),
337
+ ...(codexStartupRepair.manual_actions || []),
307
338
  ...(pluginPolicy?.doctor_warnings || [])
308
339
  ]
309
340
  });
@@ -311,7 +342,7 @@ async function runDoctor(args = [], root, doctorFix) {
311
342
  const runtimeReadiness = buildRuntimeReadiness(zellijReadiness, codexNativeFeatureMatrix);
312
343
  const result = {
313
344
  schema: 'sks.doctor-status.v1',
314
- ok: ready.ready && (!sksUpdate || sksUpdate.ok !== false) && commandAliasCleanup.ok !== false,
345
+ ok: ready.ready && (!sksUpdate || sksUpdate.ok !== false) && commandAliasCleanup.ok !== false && codexStartupRepair.ok !== false,
315
346
  root,
316
347
  node: { ok: Number(process.versions.node.split('.')[0]) >= 20, version: process.version },
317
348
  codex,
@@ -325,6 +356,8 @@ async function runDoctor(args = [], root, doctorFix) {
325
356
  codex_doctor_diff: codexDoctorDiff,
326
357
  zellij,
327
358
  zellij_repair: zellijRepair,
359
+ context7_repair: context7Repair,
360
+ codex_startup_repair: codexStartupRepair,
328
361
  local_model: localModel,
329
362
  agent_role_config: agentRoleConfigRepair,
330
363
  zellij_readiness: zellijReadiness,
@@ -348,7 +381,7 @@ async function runDoctor(args = [], root, doctorFix) {
348
381
  ready,
349
382
  sneakoscope: { ok: await exists(`${root}/.sneakoscope`) },
350
383
  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 }
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 }
352
385
  };
353
386
  if (flag(args, '--json')) {
354
387
  printJson(result);
@@ -375,6 +408,21 @@ async function runDoctor(args = [], root, doctorFix) {
375
408
  const zellijRepairLine = doctorZellijRepairConsoleLine(zellijRepair);
376
409
  if (zellijRepairLine)
377
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}`);
378
426
  console.log(` codex doctor: ${codexDoctor.available ? (codexDoctor.exit_code === 0 ? 'ok' : 'warning') : 'unavailable'}`);
379
427
  console.log(`Rust acc.: ${rust.mode || (rust.available ? 'rust_accelerated' : 'js_fallback')} ${rust.version || rust.status || ''}`);
380
428
  console.log(`Codex App: ${ready.codex_app_ready ? 'ok' : 'optional_missing'}`);
@@ -721,4 +769,30 @@ function readOption(args = [], name, fallback = null) {
721
769
  const index = args.indexOf(name);
722
770
  return index >= 0 && args[index + 1] ? args[index + 1] : fallback;
723
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
+ }
724
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 && !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,269 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import os from 'node:os';
4
+ import { REQUIRED_CODEX_MODEL } from '../codex-model-guard.js';
5
+ import { ensureDir, exists, nowIso, readText, writeJsonAtomic, writeTextAtomic } from '../fsx.js';
6
+ export const DOCTOR_CODEX_STARTUP_REPAIR_SCHEMA = 'sks.doctor-codex-startup-repair.v1';
7
+ const AGENT_ROLE_FILES = new Map([
8
+ ['analysis_scout', { file: 'analysis-scout.toml', description: 'Read-only SKS scout.', sandbox: 'read-only', nicknames: ['Scout', 'Mapper'] }],
9
+ ['native_agent', { file: 'native-agent-intake.toml', description: 'Read-only SKS analysis agent.', sandbox: 'read-only', nicknames: ['Analysis', 'Mapper'] }],
10
+ ['team_consensus', { file: 'team-consensus.toml', description: 'SKS planning/debate agent.', sandbox: 'read-only', nicknames: ['Consensus', 'Atlas'] }],
11
+ ['implementation_worker', { file: 'implementation-worker.toml', description: 'SKS bounded implementation worker.', sandbox: 'workspace-write', nicknames: ['Builder', 'Mason'] }],
12
+ ['db_safety_reviewer', { file: 'db-safety-reviewer.toml', description: 'Read-only DB safety reviewer.', sandbox: 'read-only', nicknames: ['Sentinel', 'Ledger'] }],
13
+ ['qa_reviewer', { file: 'qa-reviewer.toml', description: 'Read-only QA reviewer.', sandbox: 'read-only', nicknames: ['Verifier', 'Reviewer'] }]
14
+ ]);
15
+ const DIRECTIVE_ROLE_FILES = [
16
+ 'sks-explorer.toml',
17
+ 'sks-planner.toml',
18
+ 'sks-implementer.toml',
19
+ 'sks-checker.toml',
20
+ 'sks-release-verifier.toml',
21
+ 'sks-zellij-ui-verifier.toml',
22
+ 'sks-codex-probe-verifier.toml'
23
+ ];
24
+ export async function runDoctorCodexStartupRepair(input) {
25
+ const root = path.resolve(input.root || process.cwd());
26
+ const codexHome = input.codexHome || process.env.CODEX_HOME || path.join(process.env.HOME || os.homedir(), '.codex');
27
+ const roleFiles = input.fix
28
+ ? await repairAgentRoleFiles(root, codexHome)
29
+ : await inspectAgentRoleFiles(root, codexHome);
30
+ const configs = [];
31
+ for (const candidate of [
32
+ { scope: 'project', path: path.join(root, '.codex', 'config.toml'), agentDir: path.join(root, '.codex', 'agents') },
33
+ { scope: 'global', path: path.join(codexHome, 'config.toml'), agentDir: path.join(codexHome, 'agents') }
34
+ ]) {
35
+ configs.push(await inspectOrRepairConfig(candidate, input.fix));
36
+ }
37
+ const blockers = [...roleFiles.blockers, ...configs.flatMap((entry) => entry.blockers.map((item) => `${entry.scope}:${item}`))];
38
+ const warnings = configs.flatMap((entry) => entry.warnings.map((item) => `${entry.scope}:${item}`));
39
+ const actions = [
40
+ ...roleFiles.sanitized.map((file) => `removed unsupported message_role_prefix from ${file}`),
41
+ ...roleFiles.created.map((file) => `created missing SKS agent role config ${file}`),
42
+ ...configs.flatMap((entry) => [
43
+ ...entry.agent_config_files_repaired.map((file) => `${entry.scope} agent config_file now points at ${file}`),
44
+ ...entry.stale_mcp_blocks_removed.map((server) => `${entry.scope} stale MCP block removed: ${server}`)
45
+ ])
46
+ ];
47
+ const manualActions = [
48
+ ...configs.flatMap((entry) => entry.blockers
49
+ .filter((item) => item.includes('codex_apps_token_expired'))
50
+ .map(() => 'Codex Apps MCP token is expired; sign in to Codex App/CLI again so the connector can mint a fresh token.')),
51
+ ...configs.flatMap((entry) => entry.blockers
52
+ .filter((item) => item.includes('supabase_access_token_missing'))
53
+ .map(() => 'Supabase MCP uses SUPABASE_ACCESS_TOKEN but the variable is unset; export the token or migrate that server to a read-only remote URL.'))
54
+ ];
55
+ const reportPath = path.join(root, '.sneakoscope', 'reports', 'doctor-codex-startup-repair.json');
56
+ const report = {
57
+ schema: DOCTOR_CODEX_STARTUP_REPAIR_SCHEMA,
58
+ ok: blockers.length === 0,
59
+ generated_at: nowIso(),
60
+ fix: input.fix === true,
61
+ configs,
62
+ agent_role_files: roleFiles,
63
+ actions,
64
+ manual_actions: [...new Set(manualActions)],
65
+ blockers,
66
+ warnings,
67
+ report_path: reportPath
68
+ };
69
+ await writeJsonAtomic(reportPath, report);
70
+ return report;
71
+ }
72
+ async function inspectOrRepairConfig(candidate, fix) {
73
+ const text = await readText(candidate.path, null);
74
+ if (text == null) {
75
+ return {
76
+ scope: candidate.scope,
77
+ path: candidate.path,
78
+ present: false,
79
+ changed: false,
80
+ backup_path: null,
81
+ agent_config_files_repaired: [],
82
+ stale_mcp_blocks_removed: [],
83
+ optional_mcp_blocks_ignored: [],
84
+ blockers: [],
85
+ warnings: candidate.scope === 'global' ? ['codex_home_config_missing_optional'] : []
86
+ };
87
+ }
88
+ let next = text;
89
+ const agentConfigFilesRepaired = [];
90
+ const staleMcpBlocksRemoved = [];
91
+ const optionalMcpBlocksIgnored = [];
92
+ const blockers = [];
93
+ const warnings = [];
94
+ for (const [tableName, role] of AGENT_ROLE_FILES) {
95
+ const target = path.join(candidate.agentDir, role.file);
96
+ const table = tomlBlock(next, `agents.${tableName}`);
97
+ if (!table)
98
+ continue;
99
+ const current = stringValue(table.text, 'config_file');
100
+ const targetExists = await exists(target);
101
+ const currentValid = Boolean(current && path.isAbsolute(current) && await exists(current));
102
+ if (currentValid && current === target)
103
+ continue;
104
+ warnings.push(`agent_config_file_stale:${tableName}`);
105
+ if (!fix)
106
+ continue;
107
+ if (!targetExists) {
108
+ await ensureDir(path.dirname(target));
109
+ await writeTextAtomic(target, roleConfigToml(tableName, role.description, role.sandbox));
110
+ }
111
+ next = replaceOrInsertKey(next, table, 'config_file', `"${escapeToml(target)}"`);
112
+ agentConfigFilesRepaired.push(target);
113
+ }
114
+ for (const server of ['node_repl']) {
115
+ const table = tomlBlock(next, `mcp_servers.${server}`);
116
+ if (!table)
117
+ continue;
118
+ const command = stringValue(table.text, 'command');
119
+ if (!command || await commandExists(command))
120
+ continue;
121
+ warnings.push(`stale_mcp_command_missing:${server}`);
122
+ if (fix) {
123
+ next = removeTomlBlock(next, table);
124
+ staleMcpBlocksRemoved.push(server);
125
+ }
126
+ }
127
+ for (const server of ['supabase_sauron']) {
128
+ if (tomlBlock(next, `mcp_servers.${server}`))
129
+ optionalMcpBlocksIgnored.push(server);
130
+ }
131
+ const supabase = tomlBlock(next, 'mcp_servers.supabase');
132
+ if (supabase && /\bSUPABASE_ACCESS_TOKEN\b/.test(supabase.text) && !process.env.SUPABASE_ACCESS_TOKEN) {
133
+ blockers.push('supabase_access_token_missing');
134
+ }
135
+ const codexApps = tomlBlock(next, 'mcp_servers.codex_apps');
136
+ if (codexApps && /token_expired|expired/i.test(codexApps.text))
137
+ blockers.push('codex_apps_token_expired');
138
+ const changed = next !== text;
139
+ const backupPath = changed && fix ? await backupConfig(candidate.path, text, 'startup') : null;
140
+ if (changed && fix)
141
+ await writeTextAtomic(candidate.path, next.replace(/\n{3,}/g, '\n\n').replace(/\s*$/, '\n'));
142
+ return {
143
+ scope: candidate.scope,
144
+ path: candidate.path,
145
+ present: true,
146
+ changed,
147
+ backup_path: backupPath,
148
+ agent_config_files_repaired: agentConfigFilesRepaired,
149
+ stale_mcp_blocks_removed: staleMcpBlocksRemoved,
150
+ optional_mcp_blocks_ignored: optionalMcpBlocksIgnored,
151
+ blockers,
152
+ warnings
153
+ };
154
+ }
155
+ async function inspectAgentRoleFiles(root, codexHome) {
156
+ const dirs = [path.join(root, '.codex', 'agents'), path.join(codexHome, 'agents')];
157
+ const sanitized = [];
158
+ for (const dir of dirs) {
159
+ for (const file of DIRECTIVE_ROLE_FILES) {
160
+ const full = path.join(dir, file);
161
+ const text = await readText(full, null);
162
+ if (typeof text === 'string' && /\bmessage_role_prefix\s*=/.test(text) && /SKS managed 3\.1\./.test(text))
163
+ sanitized.push(full);
164
+ }
165
+ }
166
+ return { sanitized, created: [], blockers: [] };
167
+ }
168
+ async function repairAgentRoleFiles(root, codexHome) {
169
+ const dirs = [path.join(root, '.codex', 'agents'), path.join(codexHome, 'agents')];
170
+ const sanitized = [];
171
+ const created = [];
172
+ const blockers = [];
173
+ for (const dir of dirs) {
174
+ for (const [name, role] of AGENT_ROLE_FILES) {
175
+ const file = path.join(dir, role.file);
176
+ const text = await readText(file, null);
177
+ if (text == null) {
178
+ await ensureDir(dir);
179
+ await writeTextAtomic(file, roleConfigToml(name, role.description, role.sandbox));
180
+ created.push(file);
181
+ }
182
+ }
183
+ for (const file of DIRECTIVE_ROLE_FILES) {
184
+ const full = path.join(dir, file);
185
+ const text = await readText(full, null);
186
+ if (typeof text !== 'string')
187
+ continue;
188
+ if (!/\bmessage_role_prefix\s*=/.test(text) || !/SKS managed 3\.1\./.test(text))
189
+ continue;
190
+ const next = text.split('\n').filter((line) => !/^\s*message_role_prefix\s*=/.test(line)).join('\n');
191
+ await backupConfig(full, text, 'role');
192
+ await writeTextAtomic(full, next.replace(/\s*$/, '\n'));
193
+ sanitized.push(full);
194
+ }
195
+ }
196
+ return { sanitized, created, blockers };
197
+ }
198
+ function tomlBlock(text, table) {
199
+ const header = new RegExp(`(^|\\n)\\s*\\[${escapeRegExp(table)}\\]\\s*(?:#.*)?(?:\\n|$)`, 'g');
200
+ const match = header.exec(text);
201
+ if (!match)
202
+ return null;
203
+ const start = match.index + (match[1] ? 1 : 0);
204
+ const rest = text.slice(header.lastIndex);
205
+ const nextHeader = rest.search(/\n\s*\[[^\]]+\]\s*(?:#.*)?(?:\n|$)/);
206
+ const end = nextHeader >= 0 ? header.lastIndex + nextHeader : text.length;
207
+ return { start, end, text: text.slice(start, end) };
208
+ }
209
+ function removeTomlBlock(text, block) {
210
+ return `${text.slice(0, block.start).trimEnd()}${block.start > 0 ? '\n\n' : ''}${text.slice(block.end).replace(/^\n+/, '')}`;
211
+ }
212
+ function replaceOrInsertKey(text, block, key, encodedValue) {
213
+ const lines = block.text.replace(/\s*$/, '').split('\n');
214
+ const re = new RegExp(`^\\s*${escapeRegExp(key)}\\s*=`);
215
+ const index = lines.findIndex((line) => re.test(line));
216
+ if (index >= 0)
217
+ lines[index] = `${key} = ${encodedValue}`;
218
+ else
219
+ lines.push(`${key} = ${encodedValue}`);
220
+ const replacement = `${lines.join('\n')}\n`;
221
+ return `${text.slice(0, block.start)}${replacement}${text.slice(block.end)}`;
222
+ }
223
+ function stringValue(text, key) {
224
+ const match = text.match(new RegExp(`^\\s*${escapeRegExp(key)}\\s*=\\s*"([^"]*)"`, 'm'));
225
+ return match && typeof match[1] === 'string' ? match[1] : null;
226
+ }
227
+ async function commandExists(command) {
228
+ if (command.includes(path.sep))
229
+ return exists(command);
230
+ const paths = String(process.env.PATH || '').split(path.delimiter).filter(Boolean);
231
+ for (const dir of paths)
232
+ if (await exists(path.join(dir, command)))
233
+ return true;
234
+ return false;
235
+ }
236
+ async function backupConfig(configPath, text, label) {
237
+ try {
238
+ const backupPath = `${configPath}.sks-${label}-${Date.now().toString(36)}.bak`;
239
+ await ensureDir(path.dirname(backupPath));
240
+ await writeTextAtomic(backupPath, text);
241
+ return backupPath;
242
+ }
243
+ catch {
244
+ return null;
245
+ }
246
+ }
247
+ function roleConfigToml(name, description, sandbox) {
248
+ return [
249
+ `name = "${name}"`,
250
+ `description = "${description}"`,
251
+ `model = "${REQUIRED_CODEX_MODEL}"`,
252
+ 'model_reasoning_effort = "medium"',
253
+ `sandbox_mode = "${sandbox}"`,
254
+ 'approval_policy = "never"',
255
+ 'developer_instructions = """',
256
+ `You are the SKS ${name} role.`,
257
+ sandbox === 'read-only' ? 'Do not edit files.' : 'Only edit the bounded files assigned by the parent orchestrator.',
258
+ 'Return concise source-backed findings and LIVE_EVENT lines when applicable.',
259
+ '"""',
260
+ ''
261
+ ].join('\n');
262
+ }
263
+ function escapeToml(value) {
264
+ return value.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
265
+ }
266
+ function escapeRegExp(value) {
267
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
268
+ }
269
+ //# sourceMappingURL=doctor-codex-startup-repair.js.map
@@ -0,0 +1,116 @@
1
+ import path from 'node:path';
2
+ import os from 'node:os';
3
+ import { ensureDir, nowIso, readText, writeJsonAtomic, writeTextAtomic } from '../fsx.js';
4
+ export const DOCTOR_CONTEXT7_REPAIR_SCHEMA = 'sks.doctor-context7-repair.v1';
5
+ const CONTEXT7_REMOTE_URL = 'https://mcp.context7.com/mcp';
6
+ export async function runDoctorContext7Repair(input) {
7
+ const root = path.resolve(input.root || process.cwd());
8
+ const codexHome = input.codexHome || process.env.CODEX_HOME || path.join(process.env.HOME || os.homedir(), '.codex');
9
+ const candidates = [
10
+ { scope: 'project', path: path.join(root, '.codex', 'config.toml') },
11
+ { scope: 'global', path: path.join(codexHome, 'config.toml') }
12
+ ];
13
+ const configs = [];
14
+ for (const candidate of candidates)
15
+ configs.push(await inspectOrRepairContext7Config(candidate, input.fix));
16
+ const blockers = configs.flatMap((entry) => entry.blockers.map((blocker) => `${entry.scope}:${blocker}`));
17
+ const warnings = configs.flatMap((entry) => entry.warnings.map((warning) => `${entry.scope}:${warning}`));
18
+ const actions = configs
19
+ .filter((entry) => entry.changed || entry.status === 'local_stdio_detected')
20
+ .map((entry) => entry.changed
21
+ ? `${entry.scope} Context7 MCP migrated to remote transport`
22
+ : `${entry.scope} Context7 MCP local stdio detected; rerun sks doctor --fix to migrate`);
23
+ const reportPath = path.join(root, '.sneakoscope', 'reports', 'doctor-context7-repair.json');
24
+ const result = {
25
+ schema: DOCTOR_CONTEXT7_REPAIR_SCHEMA,
26
+ ok: blockers.length === 0,
27
+ generated_at: nowIso(),
28
+ fix: input.fix === true,
29
+ preferred_transport: 'remote',
30
+ configs,
31
+ actions,
32
+ blockers,
33
+ warnings,
34
+ report_path: reportPath
35
+ };
36
+ await writeJsonAtomic(reportPath, result);
37
+ return result;
38
+ }
39
+ async function inspectOrRepairContext7Config(candidate, fix) {
40
+ const text = await readText(candidate.path, null);
41
+ if (text == null) {
42
+ return baseConfig(candidate, {
43
+ present: false,
44
+ status: 'missing',
45
+ warnings: candidate.scope === 'global' ? ['context7_global_config_missing_optional'] : []
46
+ });
47
+ }
48
+ const block = context7Block(text);
49
+ if (!block)
50
+ return baseConfig(candidate, { present: true, status: 'missing' });
51
+ if (/\burl\s*=\s*["']https:\/\/mcp\.context7\.com\/mcp["']/.test(block.text)) {
52
+ return baseConfig(candidate, { present: true, status: 'already_remote' });
53
+ }
54
+ const localStdio = /@upstash\/context7-mcp|context7-mcp|command\s*=\s*["']npx(?:\s|["'])/i.test(block.text);
55
+ if (!localStdio) {
56
+ return baseConfig(candidate, {
57
+ present: true,
58
+ status: 'blocked',
59
+ blockers: ['context7_custom_config_preserved'],
60
+ warnings: ['custom_context7_config_not_rewritten']
61
+ });
62
+ }
63
+ if (!fix) {
64
+ return baseConfig(candidate, {
65
+ present: true,
66
+ status: 'local_stdio_detected',
67
+ warnings: ['local_stdio_context7_can_block_interactive_codex_launch']
68
+ });
69
+ }
70
+ const remoteBlock = `[mcp_servers.context7]\nurl = "${CONTEXT7_REMOTE_URL}"\n`;
71
+ const next = `${text.slice(0, block.start).trimEnd()}${block.start > 0 ? '\n\n' : ''}${remoteBlock}${text.slice(block.end).replace(/^\n+/, '\n')}`.replace(/\s*$/, '\n');
72
+ const backupPath = await backupConfig(candidate.path, text);
73
+ await writeTextAtomic(candidate.path, next);
74
+ return baseConfig(candidate, {
75
+ present: true,
76
+ status: 'repaired_to_remote',
77
+ changed: true,
78
+ backup_path: backupPath,
79
+ warnings: ['local_stdio_context7_replaced_with_remote_mcp']
80
+ });
81
+ }
82
+ function context7Block(text) {
83
+ const header = /(^|\n)\s*\[mcp_servers\.context7\]\s*(?:#.*)?(?:\n|$)/g;
84
+ const match = header.exec(text);
85
+ if (!match)
86
+ return null;
87
+ const start = match.index + (match[1] ? 1 : 0);
88
+ const rest = text.slice(header.lastIndex);
89
+ const nextHeader = rest.search(/\n\s*\[(?!mcp_servers\.context7(?:\.|\]))[^\]]+\]\s*(?:#.*)?(?:\n|$)/);
90
+ const end = nextHeader >= 0 ? header.lastIndex + nextHeader : text.length;
91
+ return { start, end, text: text.slice(start, end) };
92
+ }
93
+ async function backupConfig(configPath, text) {
94
+ try {
95
+ const backupPath = `${configPath}.sks-context7-${Date.now().toString(36)}.bak`;
96
+ await ensureDir(path.dirname(backupPath));
97
+ await writeTextAtomic(backupPath, text);
98
+ return backupPath;
99
+ }
100
+ catch {
101
+ return null;
102
+ }
103
+ }
104
+ function baseConfig(candidate, patch) {
105
+ return {
106
+ scope: candidate.scope,
107
+ path: candidate.path,
108
+ present: patch.present === true,
109
+ status: patch.status || 'missing',
110
+ changed: patch.changed === true,
111
+ backup_path: patch.backup_path || null,
112
+ warnings: patch.warnings || [],
113
+ blockers: patch.blockers || []
114
+ };
115
+ }
116
+ //# sourceMappingURL=doctor-context7-repair.js.map
package/dist/core/fsx.js CHANGED
@@ -5,7 +5,7 @@ import os from 'node:os';
5
5
  import crypto from 'node:crypto';
6
6
  import { spawn } from 'node:child_process';
7
7
  import { fileURLToPath } from 'node:url';
8
- export const PACKAGE_VERSION = '3.1.10';
8
+ export const PACKAGE_VERSION = '3.1.11';
9
9
  export const DEFAULT_PROCESS_TAIL_BYTES = 256 * 1024;
10
10
  export const DEFAULT_PROCESS_TIMEOUT_MS = 30 * 60 * 1000;
11
11
  export function nowIso() {
@@ -1,2 +1,2 @@
1
- export const PACKAGE_VERSION = '3.1.10';
1
+ export const PACKAGE_VERSION = '3.1.11';
2
2
  //# sourceMappingURL=version.js.map
@@ -2,7 +2,7 @@ import path from 'node:path';
2
2
  import { nowIso, writeJsonAtomic } from '../fsx.js';
3
3
  import { compareVersionLike, parseZellijVersionText, runZellij } from './zellij-command.js';
4
4
  export const ZELLIJ_CAPABILITY_SCHEMA = 'sks.zellij-capability.v1';
5
- export const ZELLIJ_MIN_VERSION = '0.41.0';
5
+ export const ZELLIJ_MIN_VERSION = '0.43.0';
6
6
  export const ZELLIJ_STACKED_PANE_CAPABILITY_SCHEMA = 'sks.zellij-stacked-pane-capability.v1';
7
7
  export const ZELLIJ_STACKED_PANE_MIN_VERSION = '0.43.0';
8
8
  export function zellijSupportsStackedPanes(version) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "sneakoscope",
3
3
  "displayName": "ㅅㅋㅅ",
4
- "version": "3.1.10",
4
+ "version": "3.1.11",
5
5
  "description": "Sneakoscope Codex: fast proof-first Codex trust layer with image-based Voxel TriWiki.",
6
6
  "type": "module",
7
7
  "homepage": "https://github.com/mandarange/Sneakoscope-Codex#readme",
@@ -72,6 +72,8 @@
72
72
  "codex:config-eperm-fixture": "node ./dist/scripts/codex-config-eperm-fixture.js",
73
73
  "doctor:fix-proves-codex-read": "node ./dist/scripts/doctor-fix-proves-codex-read-check.js",
74
74
  "doctor:fix-recovers-corrupted-config": "node ./dist/scripts/doctor-fix-recovers-corrupted-config-check.js",
75
+ "doctor:context7-repair": "node ./dist/scripts/doctor-context7-repair-check.js",
76
+ "doctor:codex-startup-repair": "node ./dist/scripts/doctor-codex-startup-repair-check.js",
75
77
  "install:update-preserves-config": "node ./dist/scripts/install-update-preserves-config-check.js",
76
78
  "codex-lb:config-toml-safety": "node ./dist/scripts/codex-lb-config-toml-safety-check.js",
77
79
  "codex-app:ui-preservation": "node ./dist/scripts/codex-app-ui-preservation-check.js",