sneakoscope 4.1.0 → 4.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/README.md +11 -1
  2. package/crates/sks-core/Cargo.lock +1 -1
  3. package/crates/sks-core/Cargo.toml +1 -1
  4. package/crates/sks-core/src/main.rs +1 -1
  5. package/dist/bin/sks.js +1 -1
  6. package/dist/cli/router.js +6 -1
  7. package/dist/commands/doctor.js +272 -127
  8. package/dist/core/codex/agent-config-file-repair.js +43 -2
  9. package/dist/core/codex-app/codex-agent-role-sync.js +4 -4
  10. package/dist/core/codex-control/codex-0142-capability.js +51 -6
  11. package/dist/core/codex-control/codex-app-server-v2-client.js +2 -2
  12. package/dist/core/codex-native/codex-native-feature-broker.js +50 -0
  13. package/dist/core/codex-native/native-capability-postcheck.js +59 -16
  14. package/dist/core/codex-native/native-capability-repair-matrix.js +77 -13
  15. package/dist/core/commands/mad-sks-command.js +36 -30
  16. package/dist/core/doctor/doctor-dirty-planner.js +9 -4
  17. package/dist/core/doctor/doctor-native-capability-repair.js +42 -7
  18. package/dist/core/doctor/doctor-readiness-matrix.js +9 -5
  19. package/dist/core/doctor/doctor-repair-postcheck.js +10 -1
  20. package/dist/core/doctor/doctor-transaction.js +1 -1
  21. package/dist/core/fsx.js +1 -1
  22. package/dist/core/managed-assets/managed-assets-manifest.js +14 -4
  23. package/dist/core/providers/glm/bench/glm-benchmark-runner.js +4 -3
  24. package/dist/core/providers/glm/bench/glm-benchmark-types.js +1 -1
  25. package/dist/core/update/update-migration-state.js +265 -50
  26. package/dist/core/update-check.js +6 -6
  27. package/dist/core/version.js +1 -1
  28. package/dist/core/zellij/zellij-launcher.js +17 -5
  29. package/dist/scripts/codex-0142-manifest-check.js +1 -1
  30. package/dist/scripts/config-managed-merge-callsite-coverage-check.js +6 -0
  31. package/dist/scripts/doctor-dirty-plan-check.js +1 -1
  32. package/dist/scripts/doctor-transaction-engine-check.js +1 -0
  33. package/dist/scripts/doctor-warning-only-not-blocker-check.js +18 -1
  34. package/dist/scripts/loop-directive-check-lib.js +2 -1
  35. package/dist/scripts/mad-sks-zellij-launch-check.js +7 -1
  36. package/dist/scripts/managed-role-manifest-parity-check.js +4 -1
  37. package/dist/scripts/naruto-real-parallelism-blackbox.js +17 -4
  38. package/dist/scripts/native-capability-postcheck-check.js +1 -0
  39. package/dist/scripts/native-capability-repair-matrix-check.js +2 -0
  40. package/dist/scripts/native-chrome-web-review-repair-check.js +1 -0
  41. package/dist/scripts/native-computer-use-repair-check.js +1 -0
  42. package/dist/scripts/sks-3-1-5-directive-check-lib.js +1 -1
  43. package/dist/scripts/sks-401-all-feature-regression-blackbox.js +1 -1
  44. package/dist/scripts/update-concurrent-lock-check.js +1 -0
  45. package/dist/scripts/update-first-command-migration-check.js +4 -3
  46. package/package.json +2 -1
  47. package/schemas/update-migration.schema.json +13 -1
@@ -1,4 +1,4 @@
1
- import { projectRoot, dirSize, exists, formatBytes } from '../core/fsx.js';
1
+ import { projectRoot, exists, formatBytes } from '../core/fsx.js';
2
2
  import { flag } from '../cli/args.js';
3
3
  import { printJson } from '../cli/output.js';
4
4
  import { getCodexInfo } from '../core/codex-adapter.js';
@@ -38,6 +38,7 @@ import { runDoctorFixTransaction } from '../core/doctor/doctor-transaction.js';
38
38
  import { planDoctorDirtyRepair } from '../core/doctor/doctor-dirty-planner.js';
39
39
  import { doctorRepairPostcheck } from '../core/doctor/doctor-repair-postcheck.js';
40
40
  import { withSecretPreservationGuard } from '../core/config/config-migration-journal.js';
41
+ import { writeProjectUpdateMigrationReceipt } from '../core/update/update-migration-state.js';
41
42
  export async function run(_command, args = []) {
42
43
  const root = await projectRoot();
43
44
  const doctorFix = flag(args, '--fix');
@@ -46,6 +47,20 @@ export async function run(_command, args = []) {
46
47
  return runDoctor(args, root, doctorFix);
47
48
  }
48
49
  async function runDoctor(args = [], root, doctorFix) {
50
+ const startedAtMs = Date.now();
51
+ const doctorProfile = doctorProfileFromArgs(args, doctorFix);
52
+ const machineOnly = flag(args, '--machine-only');
53
+ const reportFile = readOption(args, '--report-file', null);
54
+ const deepDiagnostics = doctorProfile === 'full' || doctorProfile === 'capabilities';
55
+ const codexBin = readOption(args, '--codex-bin', process.env.SKS_DOCTOR_CODEX_BIN || '');
56
+ const actualCodexProbeRequested = flag(args, '--actual-codex') || flag(args, '--require-actual-codex') || Boolean(codexBin);
57
+ const actualCodexProbeEnabled = deepDiagnostics || actualCodexProbeRequested;
58
+ const requireActualCodexProbe = flag(args, '--require-actual-codex') || (deepDiagnostics && doctorFix);
59
+ const shouldEvaluateCodexAppUiRepair = deepDiagnostics || flag(args, '--repair-codex-app-ui');
60
+ const shouldRunZellijRepair = deepDiagnostics || flag(args, '--repair-zellij') || flag(args, '--install-homebrew') || process.env.SKS_REQUIRE_ZELLIJ === '1';
61
+ const nativeCapabilityDiagnosticsRequested = deepDiagnostics || flag(args, '--repair-native-capabilities');
62
+ const doctorPhaseIds = doctorPhaseIdsForProfile(doctorProfile);
63
+ const doctorDirtyPlan = doctorFix ? planDoctorDirtyRepair(root, doctorPhaseIds) : null;
49
64
  let setupRepair = null;
50
65
  const sksUpdate = doctorFix
51
66
  ? {
@@ -62,21 +77,17 @@ async function runDoctor(args = [], root, doctorFix) {
62
77
  // Snapshot config content before ANY mutation so the migration journal can
63
78
  // record real before/after hashes for the whole --fix transaction.
64
79
  migrationPreFix = await captureCodexConfigSnapshot();
65
- const { setupCommand } = await import('../core/commands/basic-cli.js');
66
80
  const installScope = installScopeFromArgs(args);
67
- // Back up the existing managed project config before --force regeneration so a
68
- // hand-edited .codex/config.toml is always recoverable (mirrors the splitter/structure
69
- // repair backup contract).
70
- const preFixBackup = await backupProjectConfigBeforeFix();
71
- const setupArgs = ['--force', '--install-scope', installScope];
72
- if (flag(args, '--local-only'))
73
- setupArgs.push('--local-only');
74
- await setupCommand(setupArgs);
75
81
  setupRepair = {
82
+ schema: 'sks.doctor-setup-phase.v2',
83
+ ok: true,
84
+ status: 'semantic_dirty_plan_only',
85
+ reason: 'setup_force_removed_from_doctor_hot_path',
86
+ profile: doctorProfile,
76
87
  install_scope: installScope,
77
- config_backup_path: preFixBackup,
88
+ config_backup_path: null,
78
89
  global_skills: installScope === 'global' && !flag(args, '--local-only')
79
- ? await ensureGlobalCodexSkillsDuringInstall({ force: true })
90
+ ? deepDiagnostics ? await ensureGlobalCodexSkillsDuringInstall({ force: true }) : { status: 'skipped', reason: 'default_doctor_no_global_skill_regeneration' }
80
91
  : { status: 'skipped', reason: 'project or local-only repair' },
81
92
  // Re-seed the Codex App Fast-mode UI table ([user.fast_mode] visible/enabled/
82
93
  // default_profile) in the global ~/.codex/config.toml so existing installs whose
@@ -84,7 +95,7 @@ async function runDoctor(args = [], root, doctorFix) {
84
95
  // backs up + parse-validates before writing, no-op when already present.
85
96
  codex_app_fast_mode: flag(args, '--local-only')
86
97
  ? { status: 'skipped', reason: 'local-only repair' }
87
- : await ensureGlobalCodexFastModeDuringInstall().catch((err) => ({ status: 'failed', error: err?.message || String(err) }))
98
+ : deepDiagnostics ? await ensureGlobalCodexFastModeDuringInstall().catch((err) => ({ status: 'failed', error: err?.message || String(err) })) : { status: 'skipped', reason: 'default_doctor_no_global_fast_mode_regeneration' }
88
99
  };
89
100
  }
90
101
  const commandAliasCleanup = await runDoctorCommandAliasCleanup({
@@ -106,9 +117,10 @@ async function runDoctor(args = [], root, doctorFix) {
106
117
  }));
107
118
  const doctorNativeCapabilityRepair = await runDoctorNativeCapabilityRepair({
108
119
  root,
109
- fix: doctorFix || flag(args, '--repair-native-capabilities'),
120
+ fix: nativeCapabilityDiagnosticsRequested && doctorFix,
110
121
  yes: flag(args, '--yes') || flag(args, '-y'),
111
- flags: args.map((arg) => String(arg))
122
+ flags: args.map((arg) => String(arg)),
123
+ skipNativeCapabilities: !nativeCapabilityDiagnosticsRequested
112
124
  }).catch((err) => ({
113
125
  schema: 'sks.doctor-native-capability-repair.v1',
114
126
  ok: false,
@@ -119,13 +131,16 @@ async function runDoctor(args = [], root, doctorFix) {
119
131
  skill_dedupe: null,
120
132
  native_capabilities: null,
121
133
  secret_preservation_guard: '.sneakoscope/reports/secret-preservation-guard.json',
134
+ core_blockers: [err?.message || String(err)],
135
+ route_blockers: {},
136
+ optional_manual_required: [],
137
+ optional_warnings: [],
122
138
  blockers: [err?.message || String(err)]
123
139
  }));
124
- const codexBin = readOption(args, '--codex-bin', process.env.SKS_DOCTOR_CODEX_BIN || '');
125
140
  const configProbeOpts = {
126
- codexProbe: flag(args, '--fix') || flag(args, '--actual-codex') || Boolean(codexBin),
127
- actualCodex: flag(args, '--fix') || flag(args, '--actual-codex') || Boolean(codexBin),
128
- requireActualCodex: flag(args, '--fix') || flag(args, '--require-actual-codex'),
141
+ codexProbe: actualCodexProbeEnabled,
142
+ actualCodex: actualCodexProbeEnabled,
143
+ requireActualCodex: requireActualCodexProbe,
129
144
  codexBin: codexBin || undefined
130
145
  };
131
146
  let codexStartupRepair = await runDoctorCodexStartupRepair({ root, fix: doctorFix }).catch((err) => ({
@@ -141,13 +156,15 @@ async function runDoctor(args = [], root, doctorFix) {
141
156
  warnings: [],
142
157
  report_path: `${root}/.sneakoscope/reports/doctor-codex-startup-repair.json`
143
158
  }));
144
- const codexDoctorBefore = flag(args, '--fix') ? await runCodexDoctorBridge({ codexBin: codexBin || null, cwd: root, required: flag(args, '--require-actual-codex') }).catch(() => null) : null;
159
+ const codexDoctorBefore = flag(args, '--fix') && deepDiagnostics ? await runCodexDoctorBridge({ codexBin: codexBin || null, cwd: root, required: flag(args, '--require-actual-codex') }).catch(() => null) : null;
145
160
  const configRepair = flag(args, '--fix') ? await repairCodexConfigEperm(root, { fix: true, ...configProbeOpts }) : null;
146
161
  const migrationJournal = flag(args, '--fix')
147
162
  ? await writeFixMigrationJournal(root, migrationPreFix, configRepair, setupRepair).catch(() => null)
148
163
  : null;
149
164
  let codexConfig = configRepair?.after || await inspectCodexConfigReadability(root, configProbeOpts);
150
- const preRepairCodexDoctor = await runCodexDoctorBridge({ codexBin: codexBin || null, cwd: root, required: flag(args, '--require-actual-codex') });
165
+ const preRepairCodexDoctor = deepDiagnostics || flag(args, '--require-actual-codex')
166
+ ? await runCodexDoctorBridge({ codexBin: codexBin || null, cwd: root, required: flag(args, '--require-actual-codex') })
167
+ : null;
151
168
  const codexDoctorDiff = compareCodexDoctorBridge(codexDoctorBefore, preRepairCodexDoctor);
152
169
  codexStartupRepair = mergeObservedCodexStartupWarnings(codexStartupRepair, preRepairCodexDoctor);
153
170
  const codex = codexBin
@@ -160,45 +177,83 @@ async function runDoctor(args = [], root, doctorFix) {
160
177
  version: null,
161
178
  error: err.message
162
179
  }));
163
- const codexApp = await codexAppIntegrationStatus({ codex }).catch((err) => ({ ok: false, error: err.message }));
180
+ const codexApp = deepDiagnostics
181
+ ? await codexAppIntegrationStatus({ codex }).catch((err) => ({ ok: false, error: err.message }))
182
+ : { ok: false, skipped: true, warnings: ['codex_app_optional_diagnostic_skipped'] };
164
183
  const codexLb = codexLbMetrics(await readCodexLbCircuit(root).catch(() => ({})));
165
- const providerContext = await resolveProviderContext({ root, route: '$Doctor', serviceTier: process.env.SKS_SERVICE_TIER || 'fast' }).catch((err) => ({
166
- schema: 'sks.provider-context.v1',
167
- generated_at: new Date().toISOString(),
168
- provider: 'unknown',
169
- auth_mode: 'unknown',
170
- route: '$Doctor',
171
- service_tier: 'unknown',
172
- source: 'unknown',
173
- confidence: 'low',
174
- conflict: false,
175
- warnings: [err?.message || String(err)],
176
- signals: {
177
- openai_api_key_present: false,
178
- codex_lb_key_present: false,
179
- codex_lb_explicit: false,
180
- codex_app_auth_present: false,
181
- model_provider: null
182
- }
183
- }));
184
- const explicitCodexAppUiRepair = flag(args, '--repair-codex-app-ui') || flag(args, '--yes');
185
- const codexAppUiPlan = await repairCodexAppFastUi(root, {
186
- apply: false,
187
- reportPath: `${root}/.sneakoscope/reports/codex-app-fast-ui-repair-plan.json`
188
- }).catch((err) => ({
189
- schema: 'sks.codex-app-fast-ui-repair.v1',
190
- ok: false,
191
- apply: false,
192
- safe_auto_apply: false,
193
- requires_confirmation: true,
194
- fast_selector: 'manual_action_required',
195
- provider_selector: 'ok',
196
- host_owned_config: 'diagnostic_failed',
197
- next_action: 'Review Codex App UI config manually.',
198
- actions: [],
199
- blockers: [err?.message || String(err)]
200
- }));
201
- const shouldApplyCodexAppUiRepair = doctorFix && (explicitCodexAppUiRepair ||
184
+ const providerContext = deepDiagnostics
185
+ ? await resolveProviderContext({ root, route: '$Doctor', serviceTier: process.env.SKS_SERVICE_TIER || 'fast' }).catch((err) => ({
186
+ schema: 'sks.provider-context.v1',
187
+ generated_at: new Date().toISOString(),
188
+ provider: 'unknown',
189
+ auth_mode: 'unknown',
190
+ route: '$Doctor',
191
+ service_tier: 'unknown',
192
+ source: 'unknown',
193
+ confidence: 'low',
194
+ conflict: false,
195
+ warnings: [err?.message || String(err)],
196
+ signals: {
197
+ openai_api_key_present: false,
198
+ codex_lb_key_present: false,
199
+ codex_lb_explicit: false,
200
+ codex_app_auth_present: false,
201
+ model_provider: null
202
+ }
203
+ }))
204
+ : {
205
+ schema: 'sks.provider-context.v1',
206
+ generated_at: new Date().toISOString(),
207
+ provider: 'unknown',
208
+ auth_mode: 'unknown',
209
+ route: '$Doctor',
210
+ service_tier: process.env.SKS_SERVICE_TIER || 'fast',
211
+ source: 'skipped',
212
+ confidence: 'low',
213
+ conflict: false,
214
+ warnings: ['provider_context_optional_diagnostic_skipped'],
215
+ signals: {
216
+ openai_api_key_present: false,
217
+ codex_lb_key_present: false,
218
+ codex_lb_explicit: false,
219
+ codex_app_auth_present: false,
220
+ model_provider: null
221
+ }
222
+ };
223
+ const explicitCodexAppUiRepair = flag(args, '--repair-codex-app-ui');
224
+ const codexAppUiPlan = shouldEvaluateCodexAppUiRepair
225
+ ? await repairCodexAppFastUi(root, {
226
+ apply: false,
227
+ reportPath: `${root}/.sneakoscope/reports/codex-app-fast-ui-repair-plan.json`
228
+ }).catch((err) => ({
229
+ schema: 'sks.codex-app-fast-ui-repair.v1',
230
+ ok: false,
231
+ apply: false,
232
+ safe_auto_apply: false,
233
+ requires_confirmation: true,
234
+ fast_selector: 'manual_action_required',
235
+ provider_selector: 'ok',
236
+ host_owned_config: 'diagnostic_failed',
237
+ next_action: 'Review Codex App UI config manually.',
238
+ actions: [],
239
+ blockers: [err?.message || String(err)]
240
+ }))
241
+ : {
242
+ schema: 'sks.codex-app-fast-ui-repair.v1',
243
+ ok: true,
244
+ apply: false,
245
+ skipped: true,
246
+ safe_auto_apply: false,
247
+ requires_confirmation: false,
248
+ fast_selector: 'skipped_optional',
249
+ provider_selector: 'skipped_optional',
250
+ host_owned_config: 'not_inspected',
251
+ next_action: 'Run `sks doctor --fix --repair-codex-app-ui` for Codex App UI repair.',
252
+ actions: [],
253
+ blockers: [],
254
+ warnings: ['codex_app_ui_repair_deferred']
255
+ };
256
+ const shouldApplyCodexAppUiRepair = shouldEvaluateCodexAppUiRepair && doctorFix && (explicitCodexAppUiRepair ||
202
257
  codexAppUiPlan.safe_auto_apply === true);
203
258
  const codexAppUi = shouldApplyCodexAppUiRepair
204
259
  ? await repairCodexAppFastUi(root, {
@@ -219,23 +274,42 @@ async function runDoctor(args = [], root, doctorFix) {
219
274
  blockers: [err?.message || String(err)]
220
275
  }))
221
276
  : codexAppUiPlan;
222
- const zellijRepair = await runDoctorZellijRepair({ root, args, doctorFix }).catch((err) => ({
223
- schema: 'sks.zellij-self-heal.v1',
224
- ok: false,
225
- requested_by: 'doctor --fix',
226
- fix_requested: doctorFix,
227
- auto_approved: flag(args, '--yes') || flag(args, '-y'),
228
- install_homebrew_allowed: false,
229
- before: { status: 'unknown', version: null, bin: null },
230
- latest_version: null,
231
- strategy: 'failed',
232
- command: 'sks doctor --fix --yes',
233
- after: { status: 'unknown', version: null, bin: null },
234
- mutation_guard_artifact: null,
235
- homebrew: { present: false, bin: null, install_attempted: false, install_allowed: false },
236
- blockers: [err?.message || String(err)],
237
- warnings: []
238
- }));
277
+ const zellijRepair = shouldRunZellijRepair
278
+ ? await runDoctorZellijRepair({ root, args, doctorFix }).catch((err) => ({
279
+ schema: 'sks.zellij-self-heal.v1',
280
+ ok: false,
281
+ requested_by: 'doctor --fix',
282
+ fix_requested: doctorFix,
283
+ auto_approved: flag(args, '--yes') || flag(args, '-y'),
284
+ install_homebrew_allowed: false,
285
+ before: { status: 'unknown', version: null, bin: null },
286
+ latest_version: null,
287
+ strategy: 'failed',
288
+ command: 'sks doctor --fix --yes',
289
+ after: { status: 'unknown', version: null, bin: null },
290
+ mutation_guard_artifact: null,
291
+ homebrew: { present: false, bin: null, install_attempted: false, install_allowed: false },
292
+ blockers: [err?.message || String(err)],
293
+ warnings: []
294
+ }))
295
+ : {
296
+ schema: 'sks.zellij-self-heal.v1',
297
+ ok: true,
298
+ skipped: true,
299
+ requested_by: 'doctor --fix',
300
+ fix_requested: doctorFix,
301
+ auto_approved: false,
302
+ install_homebrew_allowed: false,
303
+ before: { status: 'skipped', version: null, bin: null },
304
+ latest_version: null,
305
+ strategy: 'deferred',
306
+ command: 'sks doctor --fix --full --yes',
307
+ after: { status: 'skipped', version: null, bin: null },
308
+ mutation_guard_artifact: null,
309
+ homebrew: { present: false, bin: null, install_attempted: false, install_allowed: false },
310
+ blockers: [],
311
+ warnings: ['zellij_repair_deferred_to_full_doctor_or_route_gate']
312
+ };
239
313
  const context7Repair = await runDoctorContext7Repair({ root, fix: doctorFix }).catch((err) => ({
240
314
  schema: 'sks.doctor-context7-repair.v1',
241
315
  ok: false,
@@ -267,7 +341,7 @@ async function runDoctor(args = [], root, doctorFix) {
267
341
  warnings: []
268
342
  }))
269
343
  : null;
270
- const supabaseMcpRepair = doctorFix
344
+ const supabaseMcpRepair = doctorFix && doctorPhaseIds.includes('supabase_mcp_repair')
271
345
  ? await repairSupabaseMcp({ root, apply: true }).catch((err) => ({
272
346
  schema: 'sks.doctor-supabase-mcp-repair.v1',
273
347
  ok: false,
@@ -287,18 +361,6 @@ async function runDoctor(args = [], root, doctorFix) {
287
361
  raw_secret_values_recorded: false
288
362
  }))
289
363
  : null;
290
- const doctorDirtyPlan = doctorFix
291
- ? planDoctorDirtyRepair(root, [
292
- 'setup',
293
- 'codex_startup_repair',
294
- 'startup_config_repair',
295
- 'context7_repair',
296
- 'context7_mcp_repair',
297
- 'supabase_mcp_repair',
298
- 'command_alias_cleanup',
299
- 'native_capability_repair'
300
- ])
301
- : null;
302
364
  const doctorFixTransaction = doctorFix
303
365
  ? await runDoctorFixTransaction({
304
366
  root,
@@ -384,15 +446,20 @@ async function runDoctor(args = [], root, doctorFix) {
384
446
  },
385
447
  {
386
448
  id: 'native_capability_repair',
449
+ required_for_ready: false,
387
450
  run: async () => ({
388
451
  id: 'native_capability_repair',
389
452
  ok: doctorNativeCapabilityRepair?.ok !== false,
390
453
  repaired: doctorFix,
454
+ manual_required: Array.isArray(doctorNativeCapabilityRepair?.optional_manual_required) && doctorNativeCapabilityRepair.optional_manual_required.length > 0,
455
+ required_for_ready: false,
391
456
  blockers: doctorNativeCapabilityRepair?.blockers || [],
457
+ warnings: doctorNativeCapabilityRepair?.optional_warnings || doctorNativeCapabilityRepair?.warnings || [],
458
+ route_blockers: doctorNativeCapabilityRepair?.route_blockers || {},
392
459
  rollback_evidence: doctorNativeCapabilityRepair?.secret_preservation_guard || 'native_capability_repair_report'
393
460
  })
394
461
  }
395
- ]
462
+ ].filter((phase) => doctorPhaseIds.includes(phase.id))
396
463
  }).catch((err) => ({
397
464
  schema: 'sks.doctor-fix-transaction.v2',
398
465
  generated_at: new Date().toISOString(),
@@ -434,19 +501,27 @@ async function runDoctor(args = [], root, doctorFix) {
434
501
  warnings_suppressed: false,
435
502
  blockers: [err?.message || String(err)]
436
503
  }));
437
- const globalSksInstallCleanup = flag(args, '--fix') && !flag(args, '--local-only')
504
+ const globalSksInstallCleanup = flag(args, '--fix') && !flag(args, '--local-only') && deepDiagnostics
438
505
  ? await cleanDuplicateGlobalSksInstalls({ root, fix: true }).catch((err) => ({ schema: 'sks.global-sks-install-cleanup.v1', ok: false, fix: true, error: err?.message || String(err), blockers: ['global_sks_install_cleanup_exception'] }))
439
506
  : null;
440
507
  const { detectImagegenCapability } = await import('../core/imagegen/imagegen-capability.js');
441
- const imagegen = await detectImagegenCapability({ codexBin: codexBin || undefined }).catch((err) => ({ ok: false, error: err.message, auth_readiness: null }));
442
- const codex0138Capability = await writeCodex0138CapabilityArtifacts(root, { codexBin: codexBin || null }).catch((err) => ({ error: err?.message || String(err), report: null }));
443
- const codex0138Doctor = await runCodex0138Doctor(root, { fix: doctorFix }).catch((err) => ({ schema: 'sks.codex-0138-doctor.v1', ok: false, error: err?.message || String(err), blockers: ['codex_0138_doctor_exception'], warnings: [] }));
444
- const pluginInventory = await writeCodexPluginInventoryArtifacts(root).catch((err) => ({ error: err?.message || String(err), report: null, artifact: null }));
508
+ const imagegen = deepDiagnostics
509
+ ? await detectImagegenCapability({ codexBin: codexBin || undefined }).catch((err) => ({ ok: false, error: err.message, auth_readiness: null }))
510
+ : { ok: false, skipped: true, auth_readiness: null, warnings: ['imagegen_optional_diagnostic_skipped'] };
511
+ const codex0138Capability = deepDiagnostics
512
+ ? await writeCodex0138CapabilityArtifacts(root, { codexBin: codexBin || null }).catch((err) => ({ error: err?.message || String(err), report: null }))
513
+ : { skipped: true, report: null };
514
+ const codex0138Doctor = deepDiagnostics
515
+ ? await runCodex0138Doctor(root, { fix: doctorFix }).catch((err) => ({ schema: 'sks.codex-0138-doctor.v1', ok: false, error: err?.message || String(err), blockers: ['codex_0138_doctor_exception'], warnings: [] }))
516
+ : { schema: 'sks.codex-0138-doctor.v1', ok: true, skipped: true, blockers: [], warnings: ['historical_codex_0138_doctor_skipped'] };
517
+ const pluginInventory = deepDiagnostics
518
+ ? await writeCodexPluginInventoryArtifacts(root).catch((err) => ({ error: err?.message || String(err), report: null, artifact: null }))
519
+ : { skipped: true, report: null, artifact: null };
445
520
  const pluginPolicy = pluginInventory?.report ? pluginAppTemplatePolicy(pluginInventory.report) : null;
446
521
  const mcpPluginInventory = pluginInventory?.report
447
522
  ? await writeMcpPluginInventoryArtifacts(root, { inventory: pluginInventory.report }).catch((err) => ({ error: err?.message || String(err), candidates: null }))
448
523
  : null;
449
- const repairCodexNative = doctorFix;
524
+ const repairCodexNative = doctorFix && doctorPhaseIds.includes('native_capability_repair');
450
525
  const codexNativeRepair = repairCodexNative
451
526
  ? await repairCodexNativeManagedAssets({
452
527
  root,
@@ -462,31 +537,28 @@ async function runDoctor(args = [], root, doctorFix) {
462
537
  warnings: []
463
538
  }))
464
539
  : null;
465
- const codexAppHarnessMatrix = await buildCodexAppHarnessMatrix({ root, mode: 'read-only' }).catch((err) => ({
466
- schema: 'sks.codex-app-harness-matrix.v1',
467
- ok: false,
468
- codex_cli: { available: false, version: null },
469
- app_features: {},
470
- sks_integrations: {},
471
- blockers: [err?.message || String(err)],
472
- warnings: []
473
- }));
474
- const codexNativeFeatureMatrix = await buildCodexNativeFeatureMatrix({ root, mode: 'read-only' }).catch((err) => ({
475
- schema: 'sks.codex-native-feature-matrix.v1',
476
- ok: false,
477
- codex_cli: { available: Boolean(codex.bin), version: codex.version || null, bin: codex.bin || null },
478
- features: {},
479
- invocation_defaults: {
480
- loop_worker_role_strategy: 'message-role',
481
- qa_visual_review_strategy: 'blocked',
482
- research_source_strategy: 'local-files',
483
- image_followup_strategy: 'blocked',
484
- hook_evidence_policy: 'unknown-do-not-count',
485
- skill_bridge_strategy: 'cli-only'
486
- },
487
- blockers: [err?.message || String(err)],
488
- warnings: []
489
- }));
540
+ const codexAppHarnessMatrix = deepDiagnostics
541
+ ? await buildCodexAppHarnessMatrix({ root, mode: 'read-only' }).catch((err) => ({
542
+ schema: 'sks.codex-app-harness-matrix.v1',
543
+ ok: false,
544
+ codex_cli: { available: false, version: null },
545
+ app_features: {},
546
+ sks_integrations: {},
547
+ blockers: [err?.message || String(err)],
548
+ warnings: []
549
+ }))
550
+ : {
551
+ schema: 'sks.codex-app-harness-matrix.v1',
552
+ ok: true,
553
+ skipped: true,
554
+ app_features: {},
555
+ sks_integrations: {},
556
+ blockers: [],
557
+ warnings: ['codex_app_harness_optional_diagnostic_skipped']
558
+ };
559
+ const codexNativeFeatureMatrix = deepDiagnostics
560
+ ? await buildCodexNativeFeatureMatrix({ root, mode: 'read-only' }).catch((err) => fallbackCodexNativeFeatureMatrix(codex, [err?.message || String(err)]))
561
+ : fallbackCodexNativeFeatureMatrix(codex, [], ['native_feature_matrix_deferred_to_full_doctor_or_route_gate']);
490
562
  // Re-probe the Codex config AFTER the MCP transport repairs (Context7 remote
491
563
  // migration, Supabase read-only, startup config) have landed. `repairCodexConfigEperm`
492
564
  // ran its config-load probe ~before~ those repairs, so a config that those repairs
@@ -499,7 +571,7 @@ async function runDoctor(args = [], root, doctorFix) {
499
571
  if (reinspected)
500
572
  codexConfig = reinspected;
501
573
  }
502
- const postRepairCodexDoctor = doctorFix
574
+ const postRepairCodexDoctor = doctorFix && (deepDiagnostics || flag(args, '--require-actual-codex'))
503
575
  ? await runCodexDoctorBridge({ codexBin: codexBin || null, cwd: root, required: flag(args, '--fix') || flag(args, '--require-actual-codex') }).catch((err) => ({
504
576
  schema: 'sks.codex-doctor-bridge.v2',
505
577
  generated_at: new Date().toISOString(),
@@ -525,7 +597,7 @@ async function runDoctor(args = [], root, doctorFix) {
525
597
  : preRepairCodexDoctor;
526
598
  const authoritativeCodexDoctor = postRepairCodexDoctor;
527
599
  const codexDoctorAuthoritativeDiff = compareCodexDoctorBridge(codexDoctorBefore, authoritativeCodexDoctor);
528
- const pkgBytes = await dirSize(root).catch(() => 0);
600
+ const pkgBytes = 0;
529
601
  const ready = await writeDoctorReadinessMatrix(root, {
530
602
  codex,
531
603
  codex_config: codexConfig,
@@ -534,7 +606,7 @@ async function runDoctor(args = [], root, doctorFix) {
534
606
  codex_doctor: authoritativeCodexDoctor,
535
607
  pre_repair_codex_doctor: preRepairCodexDoctor,
536
608
  post_repair_codex_doctor: postRepairCodexDoctor,
537
- require_codex_doctor: flag(args, '--fix') || flag(args, '--require-actual-codex'),
609
+ require_codex_doctor: deepDiagnostics || flag(args, '--require-actual-codex'),
538
610
  zellij,
539
611
  context7_repair: context7Repair,
540
612
  codex_startup_repair: codexStartupRepair,
@@ -553,7 +625,7 @@ async function runDoctor(args = [], root, doctorFix) {
553
625
  codex_plugin_inventory: pluginInventory?.report || null,
554
626
  codex_plugin_app_template_policy: pluginPolicy,
555
627
  codex_app_harness_matrix: codexAppHarnessMatrix,
556
- require_codex_cli_config_load: flag(args, '--fix') || flag(args, '--require-actual-codex'),
628
+ require_codex_cli_config_load: requireActualCodexProbe,
557
629
  operator_actions: [
558
630
  ...(codexConfig.operator_actions || []),
559
631
  ...(configRepair?.operator_actions || []),
@@ -562,10 +634,22 @@ async function runDoctor(args = [], root, doctorFix) {
562
634
  ...(pluginPolicy?.doctor_warnings || [])
563
635
  ]
564
636
  });
637
+ if (doctorFix && ready.ready === true) {
638
+ await writeProjectUpdateMigrationReceipt({
639
+ root,
640
+ source: `doctor-${doctorProfile}`,
641
+ blockers: [],
642
+ warnings: [
643
+ ...(doctorNativeCapabilityRepair?.optional_warnings || []),
644
+ ...(doctorFixPostcheck?.optional_warnings || [])
645
+ ]
646
+ }).catch(() => undefined);
647
+ }
565
648
  const zellijReadiness = buildZellijReadiness(root, zellij, ready);
566
649
  const runtimeReadiness = buildRuntimeReadiness(zellijReadiness, codexNativeFeatureMatrix);
567
650
  const result = {
568
651
  schema: 'sks.doctor-status.v2',
652
+ elapsed_ms: Date.now() - startedAtMs,
569
653
  ok: ready.ready && (!sksUpdate || sksUpdate.ok !== false) && commandAliasCleanup.ok !== false && codexStartupRepair.ok !== false && (!doctorFixPostcheck || doctorFixPostcheck.ok !== false),
570
654
  root,
571
655
  node: { ok: Number(process.versions.node.split('.')[0]) >= 20, version: process.version },
@@ -615,6 +699,13 @@ async function runDoctor(args = [], root, doctorFix) {
615
699
  package: { bytes: pkgBytes, human: formatBytes(pkgBytes) },
616
700
  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_dirty_plan: doctorDirtyPlan, doctor_postcheck: doctorFixPostcheck, codex_native: codexNativeRepair, doctor_native_capability: doctorNativeCapabilityRepair, command_aliases: commandAliasCleanup }
617
701
  };
702
+ if (reportFile)
703
+ await writeJsonReportFile(reportFile, result);
704
+ if (machineOnly && !flag(args, '--json')) {
705
+ if (!result.ok)
706
+ process.exitCode = 1;
707
+ return;
708
+ }
618
709
  if (flag(args, '--json')) {
619
710
  printJson(result);
620
711
  if (!result.ok)
@@ -739,7 +830,7 @@ async function runDoctor(args = [], root, doctorFix) {
739
830
  console.log(` rollout budget: ${codexNativeFeatureMatrix.features?.rollout_budget?.ok ? 'verified' : 'unverified'}`);
740
831
  console.log(` indexed search: ${codexNativeFeatureMatrix.features?.indexed_web_search?.ok ? 'verified' : 'unverified'}`);
741
832
  console.log(` current time: ${codexNativeFeatureMatrix.features?.current_time_read?.ok ? 'verified' : 'unverified'}`);
742
- console.log('Historical compatibility: Codex 0.138 features');
833
+ console.log('Historical compatibility: Codex 0.138 features:');
743
834
  console.log(` /app handoff: ${codex0138.supports_app_handoff ? 'ok' : 'unavailable'}`);
744
835
  console.log(` plugin JSON: ${codex0138.supports_plugin_json ? 'ok' : 'unavailable'}`);
745
836
  console.log(` image path exposure: ${codex0138.supports_image_path_exposure ? 'ok' : 'unavailable'}`);
@@ -816,7 +907,7 @@ function buildRuntimeReadiness(zellijReadiness, matrix) {
816
907
  repairActions.push('Homebrew + Zellij: sks doctor --fix --install-homebrew --yes');
817
908
  }
818
909
  if (codexNative !== 'ok')
819
- repairActions.push('Codex Native managed assets: sks doctor --fix --yes');
910
+ repairActions.push('Codex Native managed assets: sks doctor --fix --repair-codex-native --yes');
820
911
  if (matrix?.features?.project_memory?.ok !== true)
821
912
  repairActions.push('Project memory: sks codex-native init-deep --apply --directory-local');
822
913
  return {
@@ -844,6 +935,60 @@ function buildRuntimeReadiness(zellijReadiness, matrix) {
844
935
  repair_actions: [...new Set(repairActions)]
845
936
  };
846
937
  }
938
+ function fallbackCodexNativeFeatureMatrix(codex, blockers = [], warnings = []) {
939
+ return {
940
+ schema: 'sks.codex-native-feature-matrix.v1',
941
+ ok: blockers.length === 0,
942
+ skipped: blockers.length === 0,
943
+ codex_cli: { available: Boolean(codex?.bin || codex?.available), version: codex?.version || null, bin: codex?.bin || null },
944
+ features: {},
945
+ invocation_defaults: {
946
+ loop_worker_role_strategy: 'message-role',
947
+ multi_agent_mode: 'none',
948
+ rollout_budget_strategy: 'sks-local-only',
949
+ qa_visual_review_strategy: 'route-gated',
950
+ research_source_strategy: 'local-files',
951
+ image_followup_strategy: 'artifact-path',
952
+ hook_evidence_policy: 'unknown-do-not-count',
953
+ skill_bridge_strategy: 'cli-only',
954
+ current_time_source: 'external-clock',
955
+ overload_retry_policy: 'generic'
956
+ },
957
+ blockers,
958
+ warnings
959
+ };
960
+ }
961
+ function doctorProfileFromArgs(args = [], doctorFix = false) {
962
+ const explicit = readOption(args, '--profile', null);
963
+ if (explicit === 'migration' || explicit === 'full' || explicit === 'capabilities' || explicit === 'fast' || explicit === 'fix')
964
+ return explicit;
965
+ if (flag(args, '--full'))
966
+ return 'full';
967
+ if (flag(args, '--capabilities'))
968
+ return 'capabilities';
969
+ return doctorFix ? 'fix' : 'fast';
970
+ }
971
+ function doctorPhaseIdsForProfile(profile) {
972
+ const required = [
973
+ 'codex_startup_repair',
974
+ 'startup_config_repair',
975
+ 'context7_repair',
976
+ 'context7_mcp_repair',
977
+ 'command_alias_cleanup'
978
+ ];
979
+ if (profile === 'migration')
980
+ return required;
981
+ const optional = ['supabase_mcp_repair', 'native_capability_repair'];
982
+ if (profile === 'full' || profile === 'capabilities')
983
+ return ['setup', ...required, ...optional];
984
+ return [...required, ...optional];
985
+ }
986
+ async function writeJsonReportFile(file, value) {
987
+ const fsp = await import('node:fs/promises');
988
+ const path = await import('node:path');
989
+ await fsp.mkdir(path.dirname(file), { recursive: true });
990
+ await fsp.writeFile(file, `${JSON.stringify(value, null, 2)}\n`, 'utf8');
991
+ }
847
992
  function nativeCapabilityStatus(rows, id, fallback) {
848
993
  const row = rows.find((entry) => entry?.id === id);
849
994
  if (!row)