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 +7 -7
- 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 +76 -2
- package/dist/core/codex-app/codex-agent-role-sync.js +6 -6
- 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/version.js +1 -1
- package/dist/core/zellij/zellij-capability.js +1 -1
- package/package.json +3 -1
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.
|
|
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
|
-
-
|
|
46
|
-
- **
|
|
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
|
|
|
@@ -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,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 && !
|
|
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)
|
|
@@ -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.
|
|
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() {
|
package/dist/core/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const PACKAGE_VERSION = '3.1.
|
|
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.
|
|
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.
|
|
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",
|