sneakoscope 3.1.11 → 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.
- package/README.md +9 -6
- package/crates/sks-core/Cargo.lock +1 -1
- package/crates/sks-core/Cargo.toml +1 -1
- package/crates/sks-core/src/main.rs +1 -1
- package/dist/bin/sks.js +1 -1
- package/dist/commands/doctor.js +131 -2
- package/dist/core/codex/agent-config-file-repair.js +61 -0
- package/dist/core/codex/codex-startup-config-postcheck.js +30 -0
- package/dist/core/codex-control/codex-0140-capability.js +71 -0
- package/dist/core/codex-control/codex-0140-feature-probes.js +37 -0
- package/dist/core/codex-control/codex-0140-probe-runner.js +5 -0
- package/dist/core/codex-control/codex-0140-real-probe-summary.js +12 -0
- package/dist/core/codex-control/codex-0140-real-probes.js +29 -0
- package/dist/core/codex-native/codex-native-feature-broker.js +15 -1
- package/dist/core/config/config-migration-journal.js +2 -0
- package/dist/core/config/secret-preservation.js +1 -1
- package/dist/core/config/supabase-secret-preservation.js +1 -0
- package/dist/core/doctor/codex-startup-config-repair.js +40 -0
- package/dist/core/doctor/context7-mcp-repair.js +62 -0
- package/dist/core/doctor/doctor-codex-startup-repair.js +127 -15
- package/dist/core/doctor/doctor-context7-repair.js +40 -1
- package/dist/core/doctor/doctor-repair-postcheck.js +11 -0
- package/dist/core/doctor/doctor-transaction.js +30 -0
- package/dist/core/doctor/supabase-mcp-repair.js +36 -0
- package/dist/core/fsx.js +1 -1
- package/dist/core/mcp/mcp-config-preservation.js +30 -0
- package/dist/core/version.js +1 -1
- package/dist/core/zellij/zellij-worker-pane-manager.js +19 -2
- package/dist/scripts/codex-0140-feature-gate-lib.js +12 -0
- package/dist/scripts/release-3112-required-gates.js +30 -0
- package/package.json +30 -2
- 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.
|
|
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
39
|
|
|
40
|
-
What changed in 3.1.
|
|
40
|
+
What changed in 3.1.12:
|
|
41
41
|
|
|
42
|
-
- **MAD Zellij panes
|
|
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.
|
|
43
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.
|
|
44
|
-
- **Codex startup warnings are
|
|
45
|
-
- **Doctor JSON exposes the Context7 and
|
|
46
|
-
- **
|
|
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
|
|
|
@@ -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.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
package/dist/commands/doctor.js
CHANGED
|
@@ -31,6 +31,11 @@ import { buildCodexNativeFeatureMatrix } from '../core/codex-native/codex-native
|
|
|
31
31
|
import { repairCodexNativeManagedAssets } from '../core/codex-native/codex-native-repair-transaction.js';
|
|
32
32
|
import { runDoctorNativeCapabilityRepair } from '../core/doctor/doctor-native-capability-repair.js';
|
|
33
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';
|
|
34
39
|
import { withSecretPreservationGuard } from '../core/config/config-migration-journal.js';
|
|
35
40
|
export async function run(_command, args = []) {
|
|
36
41
|
const root = await projectRoot();
|
|
@@ -240,6 +245,120 @@ async function runDoctor(args = [], root, doctorFix) {
|
|
|
240
245
|
warnings: [],
|
|
241
246
|
report_path: `${root}/.sneakoscope/reports/doctor-context7-repair.json`
|
|
242
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;
|
|
243
362
|
const zellij = await checkZellijCapability({ root, require: process.env.SKS_REQUIRE_ZELLIJ === '1' });
|
|
244
363
|
const localModel = await readLocalModelConfig().catch(() => null);
|
|
245
364
|
const permissionProfiles = await inventoryCodexPermissionProfiles(root, { writeReport: true });
|
|
@@ -321,6 +440,11 @@ async function runDoctor(args = [], root, doctorFix) {
|
|
|
321
440
|
zellij,
|
|
322
441
|
context7_repair: context7Repair,
|
|
323
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,
|
|
324
448
|
local_model: localModel,
|
|
325
449
|
agent_role_config: agentRoleConfigRepair,
|
|
326
450
|
repair: configRepair,
|
|
@@ -342,7 +466,7 @@ async function runDoctor(args = [], root, doctorFix) {
|
|
|
342
466
|
const runtimeReadiness = buildRuntimeReadiness(zellijReadiness, codexNativeFeatureMatrix);
|
|
343
467
|
const result = {
|
|
344
468
|
schema: 'sks.doctor-status.v1',
|
|
345
|
-
ok: ready.ready && (!sksUpdate || sksUpdate.ok !== false) && commandAliasCleanup.ok !== false && codexStartupRepair.ok !== false,
|
|
469
|
+
ok: ready.ready && (!sksUpdate || sksUpdate.ok !== false) && commandAliasCleanup.ok !== false && codexStartupRepair.ok !== false && (!doctorFixPostcheck || doctorFixPostcheck.ok !== false),
|
|
346
470
|
root,
|
|
347
471
|
node: { ok: Number(process.versions.node.split('.')[0]) >= 20, version: process.version },
|
|
348
472
|
codex,
|
|
@@ -358,6 +482,11 @@ async function runDoctor(args = [], root, doctorFix) {
|
|
|
358
482
|
zellij_repair: zellijRepair,
|
|
359
483
|
context7_repair: context7Repair,
|
|
360
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,
|
|
361
490
|
local_model: localModel,
|
|
362
491
|
agent_role_config: agentRoleConfigRepair,
|
|
363
492
|
zellij_readiness: zellijReadiness,
|
|
@@ -381,7 +510,7 @@ async function runDoctor(args = [], root, doctorFix) {
|
|
|
381
510
|
ready,
|
|
382
511
|
sneakoscope: { ok: await exists(`${root}/.sneakoscope`) },
|
|
383
512
|
package: { bytes: pkgBytes, human: formatBytes(pkgBytes) },
|
|
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 }
|
|
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 }
|
|
385
514
|
};
|
|
386
515
|
if (flag(args, '--json')) {
|
|
387
516
|
printJson(result);
|
|
@@ -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
|
|
@@ -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,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
|
|
@@ -6,6 +6,7 @@ import { probeCodexAgentTypeSupport } from '../codex-app/codex-agent-type-probe.
|
|
|
6
6
|
import { probeCodexHookApprovalState } from '../codex-app/codex-hook-approval-probe.js';
|
|
7
7
|
import { detectCodex0138Capability } from '../codex-control/codex-0138-capability.js';
|
|
8
8
|
import { detectCodex0139Capability } from '../codex-control/codex-0139-capability.js';
|
|
9
|
+
import { detectCodex0140Capability } from '../codex-control/codex-0140-capability.js';
|
|
9
10
|
import { buildCodexPluginInventory } from '../codex-plugins/codex-plugin-json.js';
|
|
10
11
|
import { nowIso, runProcess, writeJsonAtomic } from '../fsx.js';
|
|
11
12
|
import { buildMcpPluginServerCandidates } from '../mcp/mcp-plugin-inventory.js';
|
|
@@ -18,11 +19,12 @@ export async function buildCodexNativeFeatureMatrix(input = { root: process.cwd(
|
|
|
18
19
|
const deprecatedApplyRepairs = input.applyRepairs === true;
|
|
19
20
|
const mode = input.mode || (deprecatedApplyRepairs || input.repairManagedAssets === true ? 'repair' : 'read-only');
|
|
20
21
|
const repairManagedAssets = mode === 'repair' && (input.repairManagedAssets === true || deprecatedApplyRepairs);
|
|
21
|
-
const fixtureMode = process.env.SKS_CODEX_0138_FAKE === '1' || process.env.SKS_CODEX_0139_FAKE === '1' || process.env.SKS_CODEX_PLUGIN_JSON_FAKE === '1';
|
|
22
|
+
const fixtureMode = process.env.SKS_CODEX_0138_FAKE === '1' || process.env.SKS_CODEX_0139_FAKE === '1' || process.env.SKS_CODEX_0140_FAKE === '1' || process.env.SKS_CODEX_PLUGIN_JSON_FAKE === '1';
|
|
22
23
|
const codexBin = fixtureMode ? process.env.CODEX_BIN || 'codex' : await findCodexBinary().catch(() => null);
|
|
23
24
|
const version = codexBin ? await codexVersion(codexBin) : null;
|
|
24
25
|
const cap0138 = await detectCodex0138Capability({ codexBin }).catch((err) => ({ blockers: [messageOf(err)] }));
|
|
25
26
|
const cap0139 = await detectCodex0139Capability({ codexBin }).catch((err) => ({ blockers: [messageOf(err)] }));
|
|
27
|
+
const cap0140 = await detectCodex0140Capability({ codexBin }).catch((err) => ({ blockers: [messageOf(err)] }));
|
|
26
28
|
const app = await codexAppIntegrationStatus({ codex: { bin: codexBin, version, available: Boolean(codexBin) } }).catch((err) => ({ ok: false, blockers: [messageOf(err)] }));
|
|
27
29
|
const plugins = await buildCodexPluginInventory().catch((err) => ({
|
|
28
30
|
schema: 'sks.codex-plugin-inventory.v1',
|
|
@@ -106,6 +108,17 @@ export async function buildCodexNativeFeatureMatrix(input = { root: process.cwd(
|
|
|
106
108
|
app_handoff: boolState(booleanFeature(cap0138, 'supports_app_handoff'), 'actual-probe', '.sneakoscope/codex-0138-capability.json', blockersOf(cap0138)),
|
|
107
109
|
image_path_exposure: boolState(booleanFeature(cap0138, 'supports_image_path_exposure'), 'actual-probe', '.sneakoscope/codex-0138-capability.json', blockersOf(cap0138)),
|
|
108
110
|
code_mode_web_search: boolState(booleanFeature(cap0139, 'supports_code_mode_web_search'), 'actual-probe', '.sneakoscope/codex-0139-capability.json', blockersOf(cap0139)),
|
|
111
|
+
codex_0140: boolState(booleanFeature(cap0140, 'supports_0140'), 'actual-probe', '.sneakoscope/codex-0140-capability.json', blockersOf(cap0140)),
|
|
112
|
+
usage_views: boolState(booleanFeature(cap0140?.features || {}, 'usage_views'), 'actual-probe', '.sneakoscope/codex-0140-capability.json', blockersOf(cap0140)),
|
|
113
|
+
goal_attachment_preservation: boolState(booleanFeature(cap0140?.features || {}, 'goal_attachment_preservation'), 'actual-probe', '.sneakoscope/codex-0140-capability.json', blockersOf(cap0140)),
|
|
114
|
+
session_delete: boolState(booleanFeature(cap0140?.features || {}, 'session_delete'), 'actual-probe', '.sneakoscope/codex-0140-capability.json', blockersOf(cap0140)),
|
|
115
|
+
import_command: boolState(booleanFeature(cap0140?.features || {}, 'import_command'), 'actual-probe', '.sneakoscope/codex-0140-capability.json', blockersOf(cap0140)),
|
|
116
|
+
unified_mentions: boolState(booleanFeature(cap0140?.features || {}, 'unified_mentions'), 'actual-probe', '.sneakoscope/codex-0140-capability.json', blockersOf(cap0140)),
|
|
117
|
+
bedrock_managed_auth: boolState(booleanFeature(cap0140?.features || {}, 'bedrock_managed_auth'), 'actual-probe', '.sneakoscope/codex-0140-capability.json', blockersOf(cap0140)),
|
|
118
|
+
sqlite_auto_recovery: boolState(booleanFeature(cap0140?.features || {}, 'sqlite_auto_recovery'), 'actual-probe', '.sneakoscope/codex-0140-capability.json', blockersOf(cap0140)),
|
|
119
|
+
mcp_reliability: boolState(booleanFeature(cap0140?.features || {}, 'mcp_reliability'), 'actual-probe', '.sneakoscope/codex-0140-capability.json', blockersOf(cap0140)),
|
|
120
|
+
non_tty_interrupt: boolState(booleanFeature(cap0140?.features || {}, 'non_tty_interrupt'), 'actual-probe', '.sneakoscope/codex-0140-capability.json', blockersOf(cap0140)),
|
|
121
|
+
large_repo_responsiveness: boolState(booleanFeature(cap0140?.features || {}, 'large_repo_responsiveness'), 'actual-probe', '.sneakoscope/codex-0140-capability.json', blockersOf(cap0140)),
|
|
109
122
|
slash_command_bridge: boolState(true, 'config', '.sneakoscope/reports/codex-native-feature-matrix.json'),
|
|
110
123
|
project_memory: boolState(true, 'config', '.sneakoscope/context/AGENTS.generated.md')
|
|
111
124
|
};
|
|
@@ -118,6 +131,7 @@ export async function buildCodexNativeFeatureMatrix(input = { root: process.cwd(
|
|
|
118
131
|
probes: {
|
|
119
132
|
codex_0138: cap0138,
|
|
120
133
|
codex_0139: cap0139,
|
|
134
|
+
codex_0140: cap0140,
|
|
121
135
|
app,
|
|
122
136
|
plugin_inventory: plugins,
|
|
123
137
|
mcp_candidates: mcpCandidates,
|
|
@@ -14,6 +14,8 @@ export async function writeSecretMigrationJournal(root, operationName, filesTouc
|
|
|
14
14
|
operation: operationName,
|
|
15
15
|
files_touched: filesTouched,
|
|
16
16
|
protected_keys_present: snapshot.fingerprints.filter((fp) => fp.present).map((fp) => ({ key: fp.key, source: fp.source })),
|
|
17
|
+
secret_preservation_guard_report: '.sneakoscope/reports/secret-preservation-guard.json',
|
|
18
|
+
rollback_status_source: 'secret-preservation-guard.changed_or_missing + rollback_attempted + rollback_ok',
|
|
17
19
|
raw_values_recorded: false
|
|
18
20
|
};
|
|
19
21
|
const journal = {
|
|
@@ -80,7 +80,7 @@ export async function withSecretPreservationGuard(root, operationName, fn) {
|
|
|
80
80
|
await writeJsonAtomic(guardPath, report).catch(() => undefined);
|
|
81
81
|
if (operationError)
|
|
82
82
|
throw operationError;
|
|
83
|
-
if (
|
|
83
|
+
if (rollbackAttempted) {
|
|
84
84
|
throw new Error(`secret_preservation_restored:${changedOrMissing.map((item) => `${safeSourceForError(resolvedRoot, item.source)}:${item.key}:${item.reason}`).join(',')}`);
|
|
85
85
|
}
|
|
86
86
|
return result;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { nowIso, writeJsonAtomic } from '../fsx.js';
|
|
3
|
+
import { repairAgentRoleConfigs } from '../agents/agent-role-config.js';
|
|
4
|
+
import { repairAgentConfigFileReferences } from '../codex/agent-config-file-repair.js';
|
|
5
|
+
import { postcheckCodexStartupConfig } from '../codex/codex-startup-config-postcheck.js';
|
|
6
|
+
export async function repairCodexStartupConfig(input) {
|
|
7
|
+
const root = path.resolve(input.root);
|
|
8
|
+
const roleRepair = await repairAgentRoleConfigs({
|
|
9
|
+
root,
|
|
10
|
+
apply: input.apply === true,
|
|
11
|
+
reportPath: path.join(root, '.sneakoscope', 'reports', 'agent-role-config-repair.json')
|
|
12
|
+
});
|
|
13
|
+
const fileRepair = await repairAgentConfigFileReferences({
|
|
14
|
+
root,
|
|
15
|
+
apply: input.apply === true,
|
|
16
|
+
reportPath: path.join(root, '.sneakoscope', 'reports', 'agent-config-file-repair.json')
|
|
17
|
+
});
|
|
18
|
+
const postcheck = await postcheckCodexStartupConfig({
|
|
19
|
+
root,
|
|
20
|
+
reportPath: path.join(root, '.sneakoscope', 'reports', 'codex-startup-config-postcheck.json')
|
|
21
|
+
});
|
|
22
|
+
const report = {
|
|
23
|
+
schema: 'sks.codex-startup-config-repair.v1',
|
|
24
|
+
generated_at: nowIso(),
|
|
25
|
+
ok: roleRepair.ok && fileRepair.ok && postcheck.ok,
|
|
26
|
+
apply: input.apply === true,
|
|
27
|
+
role_repair: roleRepair,
|
|
28
|
+
config_file_repair: fileRepair,
|
|
29
|
+
postcheck,
|
|
30
|
+
blockers: [
|
|
31
|
+
...(roleRepair.blockers || []),
|
|
32
|
+
...fileRepair.blockers,
|
|
33
|
+
...postcheck.blockers
|
|
34
|
+
]
|
|
35
|
+
};
|
|
36
|
+
if (input.reportPath !== null)
|
|
37
|
+
await writeJsonAtomic(input.reportPath || path.join(root, '.sneakoscope', 'reports', 'codex-startup-config-repair.json'), report).catch(() => undefined);
|
|
38
|
+
return report;
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=codex-startup-config-repair.js.map
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { ensureDir, nowIso, writeJsonAtomic, writeTextAtomic } from '../fsx.js';
|
|
3
|
+
import { CONTEXT7_REMOTE_MCP_URL, mcpServerBlock, mcpServerExplicitlyDisabled, readProjectCodexConfig, replaceOrAppendMcpServerBlock } from '../mcp/mcp-config-preservation.js';
|
|
4
|
+
import { guardedWriteFile, guardContextForRoute } from '../safety/mutation-guard.js';
|
|
5
|
+
import { createRequestedScopeContract } from '../safety/requested-scope-contract.js';
|
|
6
|
+
export async function repairContext7Mcp(input) {
|
|
7
|
+
const root = path.resolve(input.root);
|
|
8
|
+
const config = await readProjectCodexConfig(root);
|
|
9
|
+
const beforeTransport = classifyContext7Transport(config.text);
|
|
10
|
+
let afterText = config.text;
|
|
11
|
+
let repaired = false;
|
|
12
|
+
if (beforeTransport === 'stdio') {
|
|
13
|
+
afterText = replaceOrAppendMcpServerBlock(config.text, 'context7', [
|
|
14
|
+
'[mcp_servers.context7]',
|
|
15
|
+
`url = "${CONTEXT7_REMOTE_MCP_URL}"`,
|
|
16
|
+
''
|
|
17
|
+
].join('\n'));
|
|
18
|
+
repaired = afterText !== config.text;
|
|
19
|
+
}
|
|
20
|
+
if (input.apply && repaired) {
|
|
21
|
+
await ensureDir(path.dirname(config.path));
|
|
22
|
+
const backupPath = `${config.path}.context7-mcp-repair-${Date.now().toString(36)}.bak`;
|
|
23
|
+
const contract = createRequestedScopeContract({
|
|
24
|
+
route: '$Team',
|
|
25
|
+
userRequest: 'Write a scoped project backup before doctor Context7 MCP repair.',
|
|
26
|
+
projectRoot: root
|
|
27
|
+
});
|
|
28
|
+
await guardedWriteFile(guardContextForRoute(root, contract, 'doctor Context7 MCP repair backup'), backupPath, config.text).catch(() => undefined);
|
|
29
|
+
await writeTextAtomic(config.path, afterText);
|
|
30
|
+
}
|
|
31
|
+
const after = input.apply && repaired ? await readProjectCodexConfig(root) : { text: afterText };
|
|
32
|
+
const afterTransport = classifyContext7Transport(after.text);
|
|
33
|
+
const report = {
|
|
34
|
+
schema: 'sks.doctor-context7-mcp-repair.v1',
|
|
35
|
+
generated_at: nowIso(),
|
|
36
|
+
ok: afterTransport === 'remote' || afterTransport === 'disabled' || beforeTransport === 'missing',
|
|
37
|
+
apply: input.apply === true,
|
|
38
|
+
config_path: config.path,
|
|
39
|
+
before_transport: beforeTransport,
|
|
40
|
+
after_transport: afterTransport,
|
|
41
|
+
repaired: input.apply === true && repaired,
|
|
42
|
+
manual_required: false,
|
|
43
|
+
blockers: afterTransport === 'stdio' ? ['context7_mcp_still_stdio'] : [],
|
|
44
|
+
warnings: beforeTransport === 'missing' ? ['context7_mcp_not_configured'] : []
|
|
45
|
+
};
|
|
46
|
+
if (input.reportPath !== null)
|
|
47
|
+
await writeJsonAtomic(input.reportPath || path.join(root, '.sneakoscope', 'reports', 'doctor-context7-mcp-repair.json'), report).catch(() => undefined);
|
|
48
|
+
return report;
|
|
49
|
+
}
|
|
50
|
+
export function classifyContext7Transport(text) {
|
|
51
|
+
if (mcpServerExplicitlyDisabled(text, 'context7'))
|
|
52
|
+
return 'disabled';
|
|
53
|
+
const block = mcpServerBlock(text, 'context7');
|
|
54
|
+
if (!block)
|
|
55
|
+
return 'missing';
|
|
56
|
+
if (/^\s*url\s*=/m.test(block))
|
|
57
|
+
return 'remote';
|
|
58
|
+
if (/^\s*command\s*=|stdio|npx|context7/i.test(block))
|
|
59
|
+
return 'stdio';
|
|
60
|
+
return 'unknown';
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=context7-mcp-repair.js.map
|
|
@@ -32,7 +32,7 @@ export async function runDoctorCodexStartupRepair(input) {
|
|
|
32
32
|
{ scope: 'project', path: path.join(root, '.codex', 'config.toml'), agentDir: path.join(root, '.codex', 'agents') },
|
|
33
33
|
{ scope: 'global', path: path.join(codexHome, 'config.toml'), agentDir: path.join(codexHome, 'agents') }
|
|
34
34
|
]) {
|
|
35
|
-
configs.push(await inspectOrRepairConfig(candidate, input.fix));
|
|
35
|
+
configs.push(await inspectOrRepairConfig(candidate, input.fix, input.nodeReplCommandCandidates || [], input.includeDefaultNodeReplCandidates !== false));
|
|
36
36
|
}
|
|
37
37
|
const blockers = [...roleFiles.blockers, ...configs.flatMap((entry) => entry.blockers.map((item) => `${entry.scope}:${item}`))];
|
|
38
38
|
const warnings = configs.flatMap((entry) => entry.warnings.map((item) => `${entry.scope}:${item}`));
|
|
@@ -41,6 +41,7 @@ export async function runDoctorCodexStartupRepair(input) {
|
|
|
41
41
|
...roleFiles.created.map((file) => `created missing SKS agent role config ${file}`),
|
|
42
42
|
...configs.flatMap((entry) => [
|
|
43
43
|
...entry.agent_config_files_repaired.map((file) => `${entry.scope} agent config_file now points at ${file}`),
|
|
44
|
+
...(entry.mcp_blocks_repaired || []).map((server) => `${entry.scope} MCP block repaired: ${server}`),
|
|
44
45
|
...entry.stale_mcp_blocks_removed.map((server) => `${entry.scope} stale MCP block removed: ${server}`)
|
|
45
46
|
])
|
|
46
47
|
];
|
|
@@ -69,7 +70,7 @@ export async function runDoctorCodexStartupRepair(input) {
|
|
|
69
70
|
await writeJsonAtomic(reportPath, report);
|
|
70
71
|
return report;
|
|
71
72
|
}
|
|
72
|
-
async function inspectOrRepairConfig(candidate, fix) {
|
|
73
|
+
async function inspectOrRepairConfig(candidate, fix, nodeReplCommandCandidates, includeDefaultNodeReplCandidates) {
|
|
73
74
|
const text = await readText(candidate.path, null);
|
|
74
75
|
if (text == null) {
|
|
75
76
|
return {
|
|
@@ -80,6 +81,7 @@ async function inspectOrRepairConfig(candidate, fix) {
|
|
|
80
81
|
backup_path: null,
|
|
81
82
|
agent_config_files_repaired: [],
|
|
82
83
|
stale_mcp_blocks_removed: [],
|
|
84
|
+
mcp_blocks_repaired: [],
|
|
83
85
|
optional_mcp_blocks_ignored: [],
|
|
84
86
|
blockers: [],
|
|
85
87
|
warnings: candidate.scope === 'global' ? ['codex_home_config_missing_optional'] : []
|
|
@@ -88,6 +90,7 @@ async function inspectOrRepairConfig(candidate, fix) {
|
|
|
88
90
|
let next = text;
|
|
89
91
|
const agentConfigFilesRepaired = [];
|
|
90
92
|
const staleMcpBlocksRemoved = [];
|
|
93
|
+
const mcpBlocksRepaired = [];
|
|
91
94
|
const optionalMcpBlocksIgnored = [];
|
|
92
95
|
const blockers = [];
|
|
93
96
|
const warnings = [];
|
|
@@ -111,19 +114,11 @@ async function inspectOrRepairConfig(candidate, fix) {
|
|
|
111
114
|
next = replaceOrInsertKey(next, table, 'config_file', `"${escapeToml(target)}"`);
|
|
112
115
|
agentConfigFilesRepaired.push(target);
|
|
113
116
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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
|
-
}
|
|
117
|
+
const nodeReplRepair = await inspectOrRepairNodeRepl(next, fix, nodeReplCommandCandidates, includeDefaultNodeReplCandidates);
|
|
118
|
+
next = nodeReplRepair.text;
|
|
119
|
+
warnings.push(...nodeReplRepair.warnings);
|
|
120
|
+
staleMcpBlocksRemoved.push(...nodeReplRepair.removed);
|
|
121
|
+
mcpBlocksRepaired.push(...nodeReplRepair.repaired);
|
|
127
122
|
for (const server of ['supabase_sauron']) {
|
|
128
123
|
if (tomlBlock(next, `mcp_servers.${server}`))
|
|
129
124
|
optionalMcpBlocksIgnored.push(server);
|
|
@@ -147,11 +142,60 @@ async function inspectOrRepairConfig(candidate, fix) {
|
|
|
147
142
|
backup_path: backupPath,
|
|
148
143
|
agent_config_files_repaired: agentConfigFilesRepaired,
|
|
149
144
|
stale_mcp_blocks_removed: staleMcpBlocksRemoved,
|
|
145
|
+
mcp_blocks_repaired: mcpBlocksRepaired,
|
|
150
146
|
optional_mcp_blocks_ignored: optionalMcpBlocksIgnored,
|
|
151
147
|
blockers,
|
|
152
148
|
warnings
|
|
153
149
|
};
|
|
154
150
|
}
|
|
151
|
+
async function inspectOrRepairNodeRepl(text, fix, extraCandidates, includeDefaultCandidates) {
|
|
152
|
+
const server = 'node_repl';
|
|
153
|
+
const table = tomlBlock(text, `mcp_servers.${server}`);
|
|
154
|
+
const fullTable = tomlBlockWithChildren(text, `mcp_servers.${server}`);
|
|
155
|
+
const childBlocks = tomlChildBlocks(text, `mcp_servers.${server}`);
|
|
156
|
+
if (!table && childBlocks.length === 0)
|
|
157
|
+
return { text, warnings: [], removed: [], repaired: [] };
|
|
158
|
+
const command = table ? stringValue(table.text, 'command') : null;
|
|
159
|
+
if (command && await commandExists(command)) {
|
|
160
|
+
return { text, warnings: [], removed: [], repaired: [] };
|
|
161
|
+
}
|
|
162
|
+
const warnings = [table ? `stale_mcp_command_missing:${server}` : `stale_mcp_orphan_children:${server}`];
|
|
163
|
+
if (!fix)
|
|
164
|
+
return { text, warnings, removed: [], repaired: [] };
|
|
165
|
+
const replacement = await firstExistingNodeReplCommand(text, extraCandidates, includeDefaultCandidates);
|
|
166
|
+
if (replacement) {
|
|
167
|
+
if (table) {
|
|
168
|
+
return {
|
|
169
|
+
text: replaceOrInsertKey(text, table, 'command', `"${escapeToml(replacement)}"`),
|
|
170
|
+
warnings,
|
|
171
|
+
removed: [],
|
|
172
|
+
repaired: [server]
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
if (childBlocks.length) {
|
|
176
|
+
const firstChild = childBlocks[0];
|
|
177
|
+
if (!firstChild)
|
|
178
|
+
return { text, warnings, removed: [], repaired: [] };
|
|
179
|
+
const mainBlock = `[mcp_servers.${server}]\ncommand = "${escapeToml(replacement)}"\nargs = []\n\n`;
|
|
180
|
+
return {
|
|
181
|
+
text: `${text.slice(0, firstChild.start).trimEnd()}${firstChild.start > 0 ? '\n\n' : ''}${mainBlock}${text.slice(firstChild.start).replace(/^\n+/, '')}`,
|
|
182
|
+
warnings,
|
|
183
|
+
removed: [],
|
|
184
|
+
repaired: [server]
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
const removalBlocks = [
|
|
189
|
+
...(fullTable ? [fullTable] : table ? [table] : []),
|
|
190
|
+
...childBlocks.filter((block) => !fullTable || block.start < fullTable.start || block.end > fullTable.end)
|
|
191
|
+
];
|
|
192
|
+
return {
|
|
193
|
+
text: removeBlocks(text, removalBlocks),
|
|
194
|
+
warnings,
|
|
195
|
+
removed: [server],
|
|
196
|
+
repaired: []
|
|
197
|
+
};
|
|
198
|
+
}
|
|
155
199
|
async function inspectAgentRoleFiles(root, codexHome) {
|
|
156
200
|
const dirs = [path.join(root, '.codex', 'agents'), path.join(codexHome, 'agents')];
|
|
157
201
|
const sanitized = [];
|
|
@@ -206,9 +250,38 @@ function tomlBlock(text, table) {
|
|
|
206
250
|
const end = nextHeader >= 0 ? header.lastIndex + nextHeader : text.length;
|
|
207
251
|
return { start, end, text: text.slice(start, end) };
|
|
208
252
|
}
|
|
253
|
+
function tomlBlockWithChildren(text, table) {
|
|
254
|
+
const header = new RegExp(`(^|\\n)\\s*\\[${escapeRegExp(table)}\\]\\s*(?:#.*)?(?:\\n|$)`, 'g');
|
|
255
|
+
const match = header.exec(text);
|
|
256
|
+
if (!match)
|
|
257
|
+
return null;
|
|
258
|
+
const start = match.index + (match[1] ? 1 : 0);
|
|
259
|
+
const rest = text.slice(header.lastIndex);
|
|
260
|
+
const nextHeader = rest.search(new RegExp(`\\n\\s*\\[(?!${escapeRegExp(table)}(?:\\.|\\]))[^\\]]+\\]\\s*(?:#.*)?(?:\\n|$)`));
|
|
261
|
+
const end = nextHeader >= 0 ? header.lastIndex + nextHeader : text.length;
|
|
262
|
+
return { start, end, text: text.slice(start, end) };
|
|
263
|
+
}
|
|
264
|
+
function tomlChildBlocks(text, table) {
|
|
265
|
+
const blocks = [];
|
|
266
|
+
const header = new RegExp(`(^|\\n)\\s*\\[${escapeRegExp(table)}\\.[^\\]]+\\]\\s*(?:#.*)?(?:\\n|$)`, 'g');
|
|
267
|
+
let match;
|
|
268
|
+
while ((match = header.exec(text))) {
|
|
269
|
+
const start = match.index + (match[1] ? 1 : 0);
|
|
270
|
+
const rest = text.slice(header.lastIndex);
|
|
271
|
+
const nextHeader = rest.search(new RegExp(`\\n\\s*\\[(?!${escapeRegExp(table)}\\.)[^\\]]+\\]\\s*(?:#.*)?(?:\\n|$)`));
|
|
272
|
+
const end = nextHeader >= 0 ? header.lastIndex + nextHeader : text.length;
|
|
273
|
+
blocks.push({ start, end, text: text.slice(start, end) });
|
|
274
|
+
}
|
|
275
|
+
return blocks;
|
|
276
|
+
}
|
|
209
277
|
function removeTomlBlock(text, block) {
|
|
210
278
|
return `${text.slice(0, block.start).trimEnd()}${block.start > 0 ? '\n\n' : ''}${text.slice(block.end).replace(/^\n+/, '')}`;
|
|
211
279
|
}
|
|
280
|
+
function removeBlocks(text, blocks) {
|
|
281
|
+
return [...blocks]
|
|
282
|
+
.sort((a, b) => b.start - a.start)
|
|
283
|
+
.reduce((current, block) => removeTomlBlock(current, block), text);
|
|
284
|
+
}
|
|
212
285
|
function replaceOrInsertKey(text, block, key, encodedValue) {
|
|
213
286
|
const lines = block.text.replace(/\s*$/, '').split('\n');
|
|
214
287
|
const re = new RegExp(`^\\s*${escapeRegExp(key)}\\s*=`);
|
|
@@ -233,6 +306,45 @@ async function commandExists(command) {
|
|
|
233
306
|
return true;
|
|
234
307
|
return false;
|
|
235
308
|
}
|
|
309
|
+
async function firstExistingNodeReplCommand(configText, extraCandidates, includeDefaultCandidates) {
|
|
310
|
+
const candidates = [
|
|
311
|
+
...extraCandidates,
|
|
312
|
+
...(includeDefaultCandidates ? [
|
|
313
|
+
process.env.SKS_NODE_REPL_COMMAND,
|
|
314
|
+
process.env.NODE_REPL_COMMAND,
|
|
315
|
+
...nodeReplCandidatesFromNodePaths([
|
|
316
|
+
...stringValues(configText, 'NODE_REPL_NODE_PATH'),
|
|
317
|
+
process.env.NODE_REPL_NODE_PATH
|
|
318
|
+
]),
|
|
319
|
+
'/Applications/Codex.app/Contents/Resources/cua_node/bin/node_repl',
|
|
320
|
+
'/Applications/Codex.app/Contents/Resources/node_repl'
|
|
321
|
+
] : [])
|
|
322
|
+
]
|
|
323
|
+
.map((item) => String(item || '').trim())
|
|
324
|
+
.filter(Boolean);
|
|
325
|
+
for (const candidate of [...new Set(candidates)]) {
|
|
326
|
+
if (await commandExists(candidate))
|
|
327
|
+
return candidate;
|
|
328
|
+
}
|
|
329
|
+
return null;
|
|
330
|
+
}
|
|
331
|
+
function nodeReplCandidatesFromNodePaths(values) {
|
|
332
|
+
const out = [];
|
|
333
|
+
for (const value of values) {
|
|
334
|
+
const nodePath = String(value || '').trim();
|
|
335
|
+
if (!nodePath)
|
|
336
|
+
continue;
|
|
337
|
+
const dir = path.dirname(nodePath);
|
|
338
|
+
out.push(path.join(dir, 'node_repl'));
|
|
339
|
+
const resources = path.basename(dir) === 'bin' ? path.dirname(path.dirname(dir)) : dir;
|
|
340
|
+
out.push(path.join(resources, 'cua_node', 'bin', 'node_repl'));
|
|
341
|
+
}
|
|
342
|
+
return out;
|
|
343
|
+
}
|
|
344
|
+
function stringValues(text, key) {
|
|
345
|
+
const re = new RegExp(`^\\s*${escapeRegExp(key)}\\s*=\\s*"([^"]*)"`, 'gm');
|
|
346
|
+
return [...text.matchAll(re)].map((match) => String(match[1] || '')).filter(Boolean);
|
|
347
|
+
}
|
|
236
348
|
async function backupConfig(configPath, text, label) {
|
|
237
349
|
try {
|
|
238
350
|
const backupPath = `${configPath}.sks-${label}-${Date.now().toString(36)}.bak`;
|
|
@@ -49,6 +49,26 @@ async function inspectOrRepairContext7Config(candidate, fix) {
|
|
|
49
49
|
if (!block)
|
|
50
50
|
return baseConfig(candidate, { present: true, status: 'missing' });
|
|
51
51
|
if (/\burl\s*=\s*["']https:\/\/mcp\.context7\.com\/mcp["']/.test(block.text)) {
|
|
52
|
+
const childBlocks = context7ChildBlocks(text);
|
|
53
|
+
if (childBlocks.length) {
|
|
54
|
+
if (!fix) {
|
|
55
|
+
return baseConfig(candidate, {
|
|
56
|
+
present: true,
|
|
57
|
+
status: 'remote_child_env_detected',
|
|
58
|
+
warnings: ['remote_context7_child_env_unsupported_by_streamable_http']
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
const next = removeBlocks(text, childBlocks).replace(/\s*$/, '\n');
|
|
62
|
+
const backupPath = await backupConfig(candidate.path, text);
|
|
63
|
+
await writeTextAtomic(candidate.path, next);
|
|
64
|
+
return baseConfig(candidate, {
|
|
65
|
+
present: true,
|
|
66
|
+
status: 'repaired_to_remote',
|
|
67
|
+
changed: true,
|
|
68
|
+
backup_path: backupPath,
|
|
69
|
+
warnings: ['remote_context7_child_env_removed']
|
|
70
|
+
});
|
|
71
|
+
}
|
|
52
72
|
return baseConfig(candidate, { present: true, status: 'already_remote' });
|
|
53
73
|
}
|
|
54
74
|
const localStdio = /@upstash\/context7-mcp|context7-mcp|command\s*=\s*["']npx(?:\s|["'])/i.test(block.text);
|
|
@@ -68,7 +88,8 @@ async function inspectOrRepairContext7Config(candidate, fix) {
|
|
|
68
88
|
});
|
|
69
89
|
}
|
|
70
90
|
const remoteBlock = `[mcp_servers.context7]\nurl = "${CONTEXT7_REMOTE_URL}"\n`;
|
|
71
|
-
const
|
|
91
|
+
const withRemote = `${text.slice(0, block.start).trimEnd()}${block.start > 0 ? '\n\n' : ''}${remoteBlock}${text.slice(block.end).replace(/^\n+/, '\n')}`.replace(/\s*$/, '\n');
|
|
92
|
+
const next = removeBlocks(withRemote, context7ChildBlocks(withRemote)).replace(/\s*$/, '\n');
|
|
72
93
|
const backupPath = await backupConfig(candidate.path, text);
|
|
73
94
|
await writeTextAtomic(candidate.path, next);
|
|
74
95
|
return baseConfig(candidate, {
|
|
@@ -79,6 +100,24 @@ async function inspectOrRepairContext7Config(candidate, fix) {
|
|
|
79
100
|
warnings: ['local_stdio_context7_replaced_with_remote_mcp']
|
|
80
101
|
});
|
|
81
102
|
}
|
|
103
|
+
function context7ChildBlocks(text) {
|
|
104
|
+
const blocks = [];
|
|
105
|
+
const header = /(^|\n)\s*\[mcp_servers\.context7\.[^\]]+\]\s*(?:#.*)?(?:\n|$)/g;
|
|
106
|
+
let match;
|
|
107
|
+
while ((match = header.exec(text))) {
|
|
108
|
+
const start = match.index + (match[1] ? 1 : 0);
|
|
109
|
+
const rest = text.slice(header.lastIndex);
|
|
110
|
+
const nextHeader = rest.search(/\n\s*\[[^\]]+\]\s*(?:#.*)?(?:\n|$)/);
|
|
111
|
+
const end = nextHeader >= 0 ? header.lastIndex + nextHeader : text.length;
|
|
112
|
+
blocks.push({ start, end, text: text.slice(start, end) });
|
|
113
|
+
}
|
|
114
|
+
return blocks;
|
|
115
|
+
}
|
|
116
|
+
function removeBlocks(text, blocks) {
|
|
117
|
+
return [...blocks]
|
|
118
|
+
.sort((a, b) => b.start - a.start)
|
|
119
|
+
.reduce((current, block) => `${current.slice(0, block.start).trimEnd()}${block.start > 0 ? '\n\n' : ''}${current.slice(block.end).replace(/^\n+/, '')}`, text);
|
|
120
|
+
}
|
|
82
121
|
function context7Block(text) {
|
|
83
122
|
const header = /(^|\n)\s*\[mcp_servers\.context7\]\s*(?:#.*)?(?:\n|$)/g;
|
|
84
123
|
const match = header.exec(text);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import {} from './doctor-transaction.js';
|
|
2
|
+
export function doctorRepairPostcheck(transaction) {
|
|
3
|
+
return {
|
|
4
|
+
schema: 'sks.doctor-repair-postcheck.v1',
|
|
5
|
+
ok: transaction?.postcheck_ok === true,
|
|
6
|
+
transaction_ok: transaction?.ok === true,
|
|
7
|
+
manual_required: (transaction?.phases || []).filter((phase) => phase.manual_required).map((phase) => phase.id),
|
|
8
|
+
blockers: (transaction?.phases || []).flatMap((phase) => phase.blockers)
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=doctor-repair-postcheck.js.map
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { nowIso, writeJsonAtomic } from '../fsx.js';
|
|
3
|
+
export async function writeDoctorFixTransaction(input) {
|
|
4
|
+
const root = path.resolve(input.root);
|
|
5
|
+
const phases = input.phases.map((phase) => ({
|
|
6
|
+
id: phase.id,
|
|
7
|
+
ok: phase.ok === true,
|
|
8
|
+
repaired: phase.repaired === true,
|
|
9
|
+
manual_required: phase.manual_required === true,
|
|
10
|
+
blockers: phase.blockers || [],
|
|
11
|
+
warnings: phase.warnings || [],
|
|
12
|
+
artifact_path: phase.artifact_path || null
|
|
13
|
+
}));
|
|
14
|
+
const postcheckOk = phases.every((phase) => phase.ok || phase.manual_required);
|
|
15
|
+
const report = {
|
|
16
|
+
schema: 'sks.doctor-fix-transaction.v1',
|
|
17
|
+
ok: postcheckOk,
|
|
18
|
+
root,
|
|
19
|
+
started_at: input.startedAt || nowIso(),
|
|
20
|
+
completed_at: nowIso(),
|
|
21
|
+
phases,
|
|
22
|
+
postcheck_ok: postcheckOk,
|
|
23
|
+
rollback_performed: input.rollbackPerformed === true,
|
|
24
|
+
raw_secret_values_recorded: false
|
|
25
|
+
};
|
|
26
|
+
if (input.reportPath !== null)
|
|
27
|
+
await writeJsonAtomic(input.reportPath || path.join(root, '.sneakoscope', 'reports', 'doctor-fix-transaction.json'), report).catch(() => undefined);
|
|
28
|
+
return report;
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=doctor-transaction.js.map
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { nowIso, writeJsonAtomic } from '../fsx.js';
|
|
3
|
+
import { mcpServerBlock, mcpServerExplicitlyDisabled, readProjectCodexConfig } from '../mcp/mcp-config-preservation.js';
|
|
4
|
+
export async function repairSupabaseMcp(input) {
|
|
5
|
+
const root = path.resolve(input.root);
|
|
6
|
+
const config = await readProjectCodexConfig(root);
|
|
7
|
+
const disabled = mcpServerExplicitlyDisabled(config.text, 'supabase') || mcpServerExplicitlyDisabled(config.text, 'supabase_sauron');
|
|
8
|
+
const block = mcpServerBlock(config.text, 'supabase') || '';
|
|
9
|
+
const configured = Boolean(block);
|
|
10
|
+
const tokenEnvPresent = Boolean(process.env.SUPABASE_ACCESS_TOKEN);
|
|
11
|
+
const unsafeWriteAccess = configured && !/read[_-]?only\s*=\s*true|access_mode\s*=\s*"read-only"|--read-only/.test(block) && /write|service_role|SUPABASE_ACCESS_TOKEN/.test(block);
|
|
12
|
+
const manualRequired = configured && !disabled && (!tokenEnvPresent || unsafeWriteAccess);
|
|
13
|
+
const report = {
|
|
14
|
+
schema: 'sks.doctor-supabase-mcp-repair.v1',
|
|
15
|
+
generated_at: nowIso(),
|
|
16
|
+
ok: !configured || disabled || !unsafeWriteAccess,
|
|
17
|
+
apply: input.apply === true,
|
|
18
|
+
configured,
|
|
19
|
+
disabled,
|
|
20
|
+
token_env_present: tokenEnvPresent,
|
|
21
|
+
unsafe_write_access: unsafeWriteAccess,
|
|
22
|
+
manual_required: manualRequired,
|
|
23
|
+
next_action: manualRequired
|
|
24
|
+
? tokenEnvPresent
|
|
25
|
+
? 'Set Supabase MCP to read-only or explicitly approve write-scoped MCP use.'
|
|
26
|
+
: 'Set SUPABASE_ACCESS_TOKEN only if Supabase write MCP features are required; otherwise keep Supabase MCP disabled/read-only.'
|
|
27
|
+
: null,
|
|
28
|
+
blockers: unsafeWriteAccess ? ['supabase_mcp_write_access_not_safe_by_default'] : [],
|
|
29
|
+
warnings: configured && !tokenEnvPresent ? ['supabase_access_token_unset_write_features_manual_required'] : [],
|
|
30
|
+
raw_secret_values_recorded: false
|
|
31
|
+
};
|
|
32
|
+
if (input.reportPath !== null)
|
|
33
|
+
await writeJsonAtomic(input.reportPath || path.join(root, '.sneakoscope', 'reports', 'doctor-supabase-mcp-repair.json'), report).catch(() => undefined);
|
|
34
|
+
return report;
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=supabase-mcp-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.12';
|
|
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() {
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
export const CONTEXT7_REMOTE_MCP_URL = 'https://mcp.context7.com/mcp';
|
|
4
|
+
export async function readProjectCodexConfig(root) {
|
|
5
|
+
const file = path.join(path.resolve(root), '.codex', 'config.toml');
|
|
6
|
+
const text = await fs.readFile(file, 'utf8').catch(() => '');
|
|
7
|
+
return { path: file, text };
|
|
8
|
+
}
|
|
9
|
+
export function mcpServerBlock(text, serverName) {
|
|
10
|
+
const escaped = serverName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
11
|
+
const re = new RegExp(`(^\\[mcp_servers\\.${escaped}\\]\\n[\\s\\S]*?)(?=^\\[|(?![\\s\\S]))`, 'm');
|
|
12
|
+
return String(text || '').match(re)?.[1] || null;
|
|
13
|
+
}
|
|
14
|
+
export function mcpServerExplicitlyDisabled(text, serverName) {
|
|
15
|
+
const block = mcpServerBlock(text, serverName);
|
|
16
|
+
return Boolean(block && /^\s*disabled\s*=\s*true\s*$/m.test(block));
|
|
17
|
+
}
|
|
18
|
+
export function replaceOrAppendMcpServerBlock(text, serverName, block) {
|
|
19
|
+
const escaped = serverName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
20
|
+
const normalizedBlock = block.endsWith('\n') ? block : `${block}\n`;
|
|
21
|
+
const re = new RegExp(`(^\\[mcp_servers\\.${escaped}\\]\\n[\\s\\S]*?)(?=^\\[|(?![\\s\\S]))`, 'm');
|
|
22
|
+
if (re.test(text))
|
|
23
|
+
return text.replace(re, normalizedBlock);
|
|
24
|
+
const prefix = text.trim() ? `${text.replace(/\s*$/, '\n\n')}` : '';
|
|
25
|
+
return `${prefix}${normalizedBlock}`;
|
|
26
|
+
}
|
|
27
|
+
export function redactedMcpText(text) {
|
|
28
|
+
return String(text || '').replace(/(token|access_token|api_key|secret)\s*=\s*"[^"]*"/gi, '$1 = "<redacted>"');
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=mcp-config-preservation.js.map
|
package/dist/core/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const PACKAGE_VERSION = '3.1.
|
|
1
|
+
export const PACKAGE_VERSION = '3.1.12';
|
|
2
2
|
//# sourceMappingURL=version.js.map
|
|
@@ -311,6 +311,20 @@ export async function openWorkerPane(input) {
|
|
|
311
311
|
const stdoutPaneId = launch?.ok ? extractZellijPaneIdFromOutput(launch.stdout_tail) : null;
|
|
312
312
|
const reconciledPane = stdoutPaneId ? null : launch?.ok ? await reconcileZellijWorkerPaneId(input.sessionName, paneName, path.join(root, input.resultPath), cwd) : null;
|
|
313
313
|
const paneId = stdoutPaneId || reconciledPane?.pane_id || null;
|
|
314
|
+
const stackPaneIds = [...new Set([
|
|
315
|
+
...(freshState?.visible_worker_panes || [])
|
|
316
|
+
.filter((pane) => pane.pane_id && (pane.status === 'launching' || pane.status === 'running'))
|
|
317
|
+
.sort((a, b) => Number(a.y_order || 0) - Number(b.y_order || 0))
|
|
318
|
+
.map((pane) => String(pane.pane_id)),
|
|
319
|
+
paneId || ''
|
|
320
|
+
].filter(Boolean))];
|
|
321
|
+
const stackPanes = stackRequested && paneId && stackPaneIds.length >= 2
|
|
322
|
+
? await runZellij(['--session', input.sessionName, 'action', 'stack-panes', '--', ...stackPaneIds], {
|
|
323
|
+
cwd,
|
|
324
|
+
timeoutMs: 5000,
|
|
325
|
+
optional: true
|
|
326
|
+
})
|
|
327
|
+
: null;
|
|
314
328
|
const renamePane = paneId ? await renameZellijPaneById(input.sessionName, paneId, paneName, cwd) : null;
|
|
315
329
|
const paneIdSource = stdoutPaneId
|
|
316
330
|
? 'zellij_worker_new_pane_stdout'
|
|
@@ -329,7 +343,8 @@ export async function openWorkerPane(input) {
|
|
|
329
343
|
];
|
|
330
344
|
const warnings = [
|
|
331
345
|
...(stackIntent && stackedCapability && !stackedCapability.supports_stacked_panes ? [`zellij_stacked_pane_fallback:${stackedCapability.fallback_mode}`] : []),
|
|
332
|
-
...(stackedRejectedFallback ? ['zellij_stacked_pane_rejected_fallback_down'] : [])
|
|
346
|
+
...(stackedRejectedFallback ? ['zellij_stacked_pane_rejected_fallback_down'] : []),
|
|
347
|
+
...(stackPanes && !stackPanes.ok ? stackPanes.blockers.map((blocker) => `zellij_stack_panes_reconcile_${blocker}`) : [])
|
|
333
348
|
];
|
|
334
349
|
const record = buildWorkerPaneArtifact({
|
|
335
350
|
...input,
|
|
@@ -343,7 +358,9 @@ export async function openWorkerPane(input) {
|
|
|
343
358
|
focus_degraded: focus ? focus.ok !== true : false,
|
|
344
359
|
slot_column_anchor_pane_id: slotColumnAnchorPaneId,
|
|
345
360
|
slot_column_anchor_launch: anchorLaunch,
|
|
346
|
-
rename_pane: renamePane
|
|
361
|
+
rename_pane: renamePane,
|
|
362
|
+
stack_panes: stackPanes,
|
|
363
|
+
stack_pane_ids: stackPaneIds
|
|
347
364
|
},
|
|
348
365
|
directionRequested,
|
|
349
366
|
directionApplied,
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { assertGate, emitGate } from './sks-1-18-gate-lib.js';
|
|
2
|
+
import { detectCodex0140Capability } from '../core/codex-control/codex-0140-capability.js';
|
|
3
|
+
export async function runCodex0140FeatureGate(gate, feature) {
|
|
4
|
+
process.env.SKS_CODEX_0140_FAKE = '1';
|
|
5
|
+
process.env.SKS_CODEX_VERSION_FAKE = 'codex-cli 0.140.0';
|
|
6
|
+
process.env.SKS_CODEX_0140_PROBE = '1';
|
|
7
|
+
const cap = await detectCodex0140Capability({ codexBin: 'codex' });
|
|
8
|
+
assertGate(cap.ok === true && cap.supports_0140 === true, `${gate} requires passing Codex 0.140 capability fixture`, cap);
|
|
9
|
+
assertGate(cap.features[feature] === true, `${gate} requires feature ${feature}`, cap);
|
|
10
|
+
emitGate(gate, { feature, schema: cap.schema });
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=codex-0140-feature-gate-lib.js.map
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export const REQUIRED_3112_RELEASE_IDS = [
|
|
2
|
+
'release:gate-script-parity',
|
|
3
|
+
'release:wiring-3112-blackbox',
|
|
4
|
+
'codex:0140-capability',
|
|
5
|
+
'codex:0140-feature-probes',
|
|
6
|
+
'codex:0140-usage',
|
|
7
|
+
'codex:0140-goal-attachment-preservation',
|
|
8
|
+
'codex:0140-session-delete',
|
|
9
|
+
'codex:0140-import',
|
|
10
|
+
'codex:0140-unified-mentions',
|
|
11
|
+
'codex:0140-bedrock-managed-auth',
|
|
12
|
+
'codex:0140-mcp-reliability',
|
|
13
|
+
'codex:0140-sqlite-recovery',
|
|
14
|
+
'codex:0140-non-tty-interrupt',
|
|
15
|
+
'codex:0140-large-repo-performance',
|
|
16
|
+
'pipeline:codex-0140-integration',
|
|
17
|
+
'codex:0140-integration-blackbox',
|
|
18
|
+
'doctor:fix-production-blackbox',
|
|
19
|
+
'doctor:startup-config-repair',
|
|
20
|
+
'doctor:startup-config-repair-blackbox',
|
|
21
|
+
'doctor:context7-mcp-repair',
|
|
22
|
+
'doctor:context7-mcp-repair-blackbox',
|
|
23
|
+
'doctor:supabase-mcp-repair',
|
|
24
|
+
'doctor:supabase-mcp-repair-blackbox',
|
|
25
|
+
'sks:3112-all-feature-regression'
|
|
26
|
+
];
|
|
27
|
+
export const REQUIRED_3112_REAL_CHECK_IDS = [
|
|
28
|
+
'codex:0140-real-probes:require-real'
|
|
29
|
+
];
|
|
30
|
+
//# sourceMappingURL=release-3112-required-gates.js.map
|
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.12",
|
|
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",
|
|
@@ -23,6 +23,8 @@
|
|
|
23
23
|
},
|
|
24
24
|
"files": [
|
|
25
25
|
"dist",
|
|
26
|
+
"!dist/*.json",
|
|
27
|
+
"!dist/.sks-build-stamp.json",
|
|
26
28
|
"!dist/build-manifest.json",
|
|
27
29
|
"!dist/**/*.d.ts",
|
|
28
30
|
"!dist/**/*.d.ts.map",
|
|
@@ -922,7 +924,33 @@
|
|
|
922
924
|
"doctor:codex-native-readiness-ux": "node ./dist/scripts/doctor-codex-native-readiness-ux-check.js",
|
|
923
925
|
"doctor:codex-native-repair-actions": "node ./dist/scripts/doctor-codex-native-repair-actions-check.js",
|
|
924
926
|
"codex-native:feature-broker-blackbox": "node ./dist/scripts/codex-native-feature-broker-blackbox.js",
|
|
925
|
-
"pipeline:codex-native-e2e-blackbox": "node ./dist/scripts/pipeline-codex-native-e2e-blackbox.js"
|
|
927
|
+
"pipeline:codex-native-e2e-blackbox": "node ./dist/scripts/pipeline-codex-native-e2e-blackbox.js",
|
|
928
|
+
"release:gate-script-parity": "node ./dist/scripts/release-gate-script-parity-check.js",
|
|
929
|
+
"release:wiring-3112-blackbox": "node ./dist/scripts/release-wiring-3112-blackbox.js",
|
|
930
|
+
"codex:0140-capability": "node ./dist/scripts/codex-0140-capability-check.js",
|
|
931
|
+
"codex:0140-feature-probes": "node ./dist/scripts/codex-0140-feature-probes-check.js",
|
|
932
|
+
"codex:0140-usage": "node ./dist/scripts/codex-0140-usage-check.js",
|
|
933
|
+
"codex:0140-goal-attachment-preservation": "node ./dist/scripts/codex-0140-goal-attachment-preservation-check.js",
|
|
934
|
+
"codex:0140-session-delete": "node ./dist/scripts/codex-0140-session-delete-check.js",
|
|
935
|
+
"codex:0140-import": "node ./dist/scripts/codex-0140-import-check.js",
|
|
936
|
+
"codex:0140-unified-mentions": "node ./dist/scripts/codex-0140-unified-mentions-check.js",
|
|
937
|
+
"codex:0140-bedrock-managed-auth": "node ./dist/scripts/codex-0140-bedrock-managed-auth-check.js",
|
|
938
|
+
"codex:0140-mcp-reliability": "node ./dist/scripts/codex-0140-mcp-reliability-check.js",
|
|
939
|
+
"codex:0140-sqlite-recovery": "node ./dist/scripts/codex-0140-sqlite-recovery-check.js",
|
|
940
|
+
"codex:0140-non-tty-interrupt": "node ./dist/scripts/codex-0140-non-tty-interrupt-check.js",
|
|
941
|
+
"codex:0140-large-repo-performance": "node ./dist/scripts/codex-0140-large-repo-performance-check.js",
|
|
942
|
+
"codex:0140-real-probes": "node ./dist/scripts/codex-0140-real-probes-check.js",
|
|
943
|
+
"codex:0140-real-probes:require-real": "node ./dist/scripts/codex-0140-real-probes-check.js --require-real --allow-network",
|
|
944
|
+
"pipeline:codex-0140-integration": "node ./dist/scripts/pipeline-codex-0140-integration-check.js",
|
|
945
|
+
"codex:0140-integration-blackbox": "node ./dist/scripts/codex-0140-integration-blackbox.js",
|
|
946
|
+
"doctor:fix-production-blackbox": "node ./dist/scripts/doctor-fix-production-blackbox.js",
|
|
947
|
+
"doctor:startup-config-repair": "node ./dist/scripts/doctor-startup-config-repair-check.js",
|
|
948
|
+
"doctor:startup-config-repair-blackbox": "node ./dist/scripts/doctor-startup-config-repair-blackbox.js",
|
|
949
|
+
"doctor:context7-mcp-repair": "node ./dist/scripts/doctor-context7-mcp-repair-check.js",
|
|
950
|
+
"doctor:context7-mcp-repair-blackbox": "node ./dist/scripts/doctor-context7-mcp-repair-blackbox.js",
|
|
951
|
+
"doctor:supabase-mcp-repair": "node ./dist/scripts/doctor-supabase-mcp-repair-check.js",
|
|
952
|
+
"doctor:supabase-mcp-repair-blackbox": "node ./dist/scripts/doctor-supabase-mcp-repair-blackbox.js",
|
|
953
|
+
"sks:3112-all-feature-regression": "node ./dist/scripts/sks-3112-all-feature-regression-blackbox.js"
|
|
926
954
|
},
|
|
927
955
|
"keywords": [
|
|
928
956
|
"sneakoscope",
|