sneakoscope 9.0.0 → 9.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -22,7 +22,7 @@ Proof-first orchestration for Codex CLI, ChatGPT Desktop, AI coding agents, mult
22
22
  Sneakoscope Codex (`sks`) is an open-source trust layer for Codex CLI and ChatGPT Desktop. It coordinates bounded AI coding agents, records machine-verifiable evidence, preserves project memory, and blocks release claims that are not supported by current tests or artifacts. Search visibility outcomes are measured separately; SKS does not promise rankings or traffic.
23
23
  <!-- END SKS SEARCH VISIBILITY MARKETING -->
24
24
 
25
- This README documents package **SKS 9.0.0** — its own identity, read from `package.json` and subject to release-gate verification, not advice about what to install.
25
+ This README documents package **SKS 9.0.2** — its own identity, read from `package.json` and subject to release-gate verification, not advice about what to install.
26
26
 
27
27
  Use the official latest stable SKS and Codex CLI releases. The Codex compatibility SSOT is always the **current latest stable** host; capability probes measure what that host can actually do. Product docs do not crown a fixed `0.x.y` string as SSOT (release pins and schema directories are measured artifacts for the current package, not a permanent product version claim). Menu Bar / Center induce updates to the latest stable build. Run `sks update-check` for what is installed and read the capability report for what is supported. Install SSOT is npm `sneakoscope@latest`; PATH `sks` and Menu Bar stamped generation must match that version or gates fail. It resolves managed SKS skills from the authoritative global install, preserves a runnable Naruto child slot when `max_threads=2`, and keeps Menu Bar repair transactional so stamped generations remain verifiable. Naruto uses stable opt-in multi-agent V2 when the host exposes it (Codex official multi-agent wrap-only; SKS does not reimplement a parallel runtime). Local code search is mode-separated (`sks search files|text|structure|symbol|context`); `context` is answered by the compiled TriWiki Context Graph (`context-graph.json` is exhaustive authority; `context-pack.json` and managed `AGENTS.md` are bounded projections) — see [docs/architecture/context-graph.md](https://github.com/mandarange/Sneakoscope-Codex/blob/main/docs/architecture/context-graph.md) and [docs/PRODUCT-CONTRACT.md](https://github.com/mandarange/Sneakoscope-Codex/blob/main/docs/PRODUCT-CONTRACT.md). See [CHANGELOG.md](https://github.com/mandarange/Sneakoscope-Codex/blob/main/CHANGELOG.md).
28
28
 
@@ -259,7 +259,7 @@ dependencies = [
259
259
 
260
260
  [[package]]
261
261
  name = "sks-core"
262
- version = "9.0.0"
262
+ version = "9.0.2"
263
263
  dependencies = [
264
264
  "globset",
265
265
  "grep-matcher",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "sks-core"
3
- version = "9.0.0"
3
+ version = "9.0.2"
4
4
  edition = "2021"
5
5
 
6
6
  [dependencies]
@@ -95,12 +95,16 @@ async function hookUserPromptSubmitPerfInline() {
95
95
  })}\n`);
96
96
  }
97
97
  function findProjectRootSync(fs, start) {
98
+ const home = process.env.HOME || process.env.USERPROFILE || null;
99
+ const homeDir = home ? stripTrailingSlash(home) : null;
98
100
  let dir = normalizeStart(start);
99
101
  for (;;) {
100
- if (fs.existsSync(joinPath(dir, '.sneakoscope')))
101
- return dir;
102
- if (fs.existsSync(joinPath(dir, 'AGENTS.md')) && fs.existsSync(joinPath(dir, 'package.json')))
103
- return dir;
102
+ if (dir !== homeDir) {
103
+ if (fs.existsSync(joinPath(dir, '.sneakoscope')))
104
+ return dir;
105
+ if (fs.existsSync(joinPath(dir, 'AGENTS.md')) && fs.existsSync(joinPath(dir, 'package.json')))
106
+ return dir;
107
+ }
104
108
  const parent = parentDir(dir);
105
109
  if (parent === dir)
106
110
  return null;
@@ -116,7 +116,7 @@ async function parseInvocation(args, io) {
116
116
  return { ...base, request: { operation: 'status' }, label: 'Desktop Bridge status' };
117
117
  }
118
118
  if (area === 'serve' && action === undefined) {
119
- allowOnly(parsed, ['--json'], ['--settings']);
119
+ allowOnly(parsed, ['--json', '--supervised'], ['--settings']);
120
120
  const settingsPath = parsed.values.get('--settings') || '';
121
121
  if (!path.isAbsolute(settingsPath)) {
122
122
  throw new BridgeCliError('desktop_bridge_settings_path_must_be_absolute');
@@ -0,0 +1,291 @@
1
+ import { CURRENT_CODEX_RUNTIME_CONTRACT } from '../core/codex-compat/codex-runtime-contract.js';
2
+ import { DOCTOR_CONSOLE_NOT_MEASURED, doctorDedupeStatus, doctorSkillStatus, formatCodexDoctorConsoleStatus, nativeCapabilityStatus, sksMenuBarRunningVersionConsoleLines, uniqueNativeManualActions } from './doctor-helpers.js';
3
+ export { DOCTOR_CONSOLE_NOT_MEASURED };
4
+ export function renderDoctorConsoleReport(result, extras) {
5
+ const lines = [];
6
+ const line = (text) => lines.push(text);
7
+ const skippedOr = (skipped, measured) => (skipped ? DOCTOR_CONSOLE_NOT_MEASURED : measured);
8
+ const ready = result.ready || {};
9
+ const codex = result.codex || {};
10
+ const codexConfig = result.codex_config || {};
11
+ const oauthDiagnostic = result.oauth_callback_port_diagnostic || { conflict: false, listeners: [], warnings: [] };
12
+ const context7Repair = result.context7_repair || {};
13
+ const codexStartupRepair = result.codex_startup_repair || {};
14
+ const codexConfigSyntaxRepair = result.codex_config_syntax_repair;
15
+ const rust = result.rust || {};
16
+ const codexApp = result.codex_app || {};
17
+ const runtimeReadiness = result.runtime_readiness || { notes: [], repair_actions: [] };
18
+ const codexNativeFeatureMatrix = result.codex_native_feature_matrix || {};
19
+ const repair = result.repair || {};
20
+ const doctorNativeCapabilityRepair = repair.doctor_native_capability;
21
+ const commandAliasCleanup = result.command_aliases || {};
22
+ const codexAppHarnessMatrix = result.codex_app_harness_matrix || {};
23
+ const codexAppUi = result.codex_app_ui || {};
24
+ const sksMenuBar = result.sks_menubar || {};
25
+ const providerContext = result.provider_context || {};
26
+ const imagegen = result.imagegen || {};
27
+ const imagegenRepair = result.imagegen_repair || {};
28
+ const computerUseRepair = repair.computer_use || {};
29
+ const browserUseRepair = repair.browser_use || {};
30
+ const mcpTransportCollisionRepair = repair.mcp_transport_collision;
31
+ const codexCurrentAppSection = result.codex_current_app || {};
32
+ const codexCurrentAppDoctor = codexCurrentAppSection.doctor || {};
33
+ const pluginPolicy = codexCurrentAppSection.plugin_app_template_policy;
34
+ const desktopBridge = result.desktop_bridge || {};
35
+ const permissionProfiles = result.codex_permission_profiles || {};
36
+ const configRepair = repair.codex_config;
37
+ const migrationJournal = repair.migration_journal;
38
+ const sksUpdate = repair.sks_update;
39
+ const globalSksInstallCleanup = repair.global_sks_installs;
40
+ line('SKS Doctor');
41
+ for (const warning of result.arg_warnings || [])
42
+ line(`Argument warning: ${warning}`);
43
+ for (const warning of result.official_subagent_config?.warnings || [])
44
+ line(`Official subagent warning: ${warning}`);
45
+ line(`Root: ${result.root}`);
46
+ if (extras.rootIsHome) {
47
+ line('Note: this is your home directory, not a project — project rows below describe the home folder. Run from your project: cd <your-project> && sks doctor');
48
+ }
49
+ line(`Node: ${result.node?.ok ? 'ok' : 'fail'} ${result.node?.version || ''}`);
50
+ line(`Codex: ${codex.bin ? 'ok' : 'missing'} ${codex.version || ''}`);
51
+ if (oauthDiagnostic.conflict) {
52
+ const listeners = (oauthDiagnostic.listeners || [])
53
+ .map((listener) => `${listener.command} pid ${listener.pid} ${listener.address}`)
54
+ .join(', ');
55
+ line(`OAuth callback port 1455: warning (${listeners})`);
56
+ for (const action of extras.oauthCallbackOperatorActions)
57
+ line(` action: ${action}`);
58
+ }
59
+ const actual = (codexConfig.checks || []).find((check) => check.name === 'actual_codex_cli_config_load');
60
+ line('Project config:');
61
+ line(` node read: ${ready.codex_config_readable_by_node ? 'ok' : 'failed'}`);
62
+ line(` codex cli read: ${ready.codex_config_readable_by_codex_cli ? 'ok' : (actual?.status || 'failed')}`);
63
+ line('Context7 MCP:');
64
+ line(` transport: ${context7Repair.preferred_transport || 'remote'}`);
65
+ line(` repair: ${context7Repair.ok ? 'ok' : 'blocked'}`);
66
+ for (const action of context7Repair.actions || [])
67
+ line(` - ${action}`);
68
+ for (const warning of context7Repair.warnings || [])
69
+ line(` warning: ${warning}`);
70
+ line('Codex startup config:');
71
+ line(` repair: ${codexStartupRepair.ok ? 'ok' : 'blocked'}`);
72
+ for (const action of codexStartupRepair.actions || [])
73
+ line(` - ${action}`);
74
+ for (const action of codexStartupRepair.manual_actions || [])
75
+ line(` manual: ${action}`);
76
+ for (const warning of codexStartupRepair.warnings || [])
77
+ line(` warning: ${warning}`);
78
+ if (codexConfigSyntaxRepair) {
79
+ line('Codex config syntax:');
80
+ line(` repair: ${codexConfigSyntaxRepair.ok ? 'ok' : 'blocked'}`);
81
+ for (const action of codexConfigSyntaxRepair.actions || [])
82
+ line(` - ${action}`);
83
+ for (const action of codexConfigSyntaxRepair.manual_actions || [])
84
+ line(` manual: ${action}`);
85
+ for (const warning of codexConfigSyntaxRepair.warnings || [])
86
+ line(` warning: ${warning}`);
87
+ }
88
+ line(` codex doctor: ${formatCodexDoctorConsoleStatus(result.codex_doctor)}`);
89
+ line(`Rust acc.: ${rust.mode || (rust.available ? 'rust_accelerated' : 'js_fallback')} ${rust.version || rust.status || ''}`);
90
+ line(`Codex App: ${codexApp.skipped === true ? DOCTOR_CONSOLE_NOT_MEASURED : ready.codex_app_ready ? 'ok' : 'optional_missing'}`);
91
+ const featureMatrixSkipped = codexNativeFeatureMatrix.skipped === true;
92
+ line('SKS Runtime Readiness:');
93
+ line(` Codex Native: ${skippedOr(featureMatrixSkipped, String(runtimeReadiness.codex_native))}`);
94
+ line(` Loop Mesh: ${skippedOr(featureMatrixSkipped, String(runtimeReadiness.loop_mesh))}`);
95
+ line(` QA Visual: ${skippedOr(featureMatrixSkipped, String(runtimeReadiness.qa_visual))}`);
96
+ line(` Research Sources: ${skippedOr(featureMatrixSkipped, String(runtimeReadiness.research_sources))}`);
97
+ line(` Image Follow-up: ${skippedOr(featureMatrixSkipped, String(runtimeReadiness.image_followup))}`);
98
+ if (!featureMatrixSkipped) {
99
+ for (const note of runtimeReadiness.notes || [])
100
+ line(` ${note}`);
101
+ if ((runtimeReadiness.repair_actions || []).length) {
102
+ line('Repair actions:');
103
+ for (const action of runtimeReadiness.repair_actions)
104
+ line(` - ${action}`);
105
+ }
106
+ }
107
+ const nativeCapabilitySource = doctorNativeCapabilityRepair?.native_capabilities;
108
+ const nativeCapabilitiesSkipped = nativeCapabilitySource?.skipped === true;
109
+ const nativeCapabilityRows = Array.isArray(nativeCapabilitySource?.capabilities)
110
+ ? nativeCapabilitySource.capabilities
111
+ : [];
112
+ const capabilityRow = (id, fallback) => skippedOr(nativeCapabilitiesSkipped, nativeCapabilityStatus(nativeCapabilityRows, id, fallback));
113
+ line('SKS Native Capabilities:');
114
+ line(` image generation: ${capabilityRow('image_generation', 'repair_required')}`);
115
+ line(` image follow-up edit: ${capabilityRow('image_followup_edit', 'degraded')}`);
116
+ line(` computer use: ${capabilityRow('computer_use', 'manual_required')}`);
117
+ line(` Chrome/web review: ${capabilityRow('chrome_web_review', 'manual_required')}`);
118
+ line(` app screenshot: ${capabilityRow('codex_app_screenshot', 'degraded')}`);
119
+ line(` app handoff: ${capabilityRow('app_handoff', 'unavailable')}`);
120
+ line(` image path exposure: ${capabilityRow('image_path_exposure', 'fallback')}`);
121
+ const nativeManualActions = uniqueNativeManualActions(nativeCapabilityRows);
122
+ if (nativeManualActions.length) {
123
+ line(' manual next actions:');
124
+ for (const action of nativeManualActions)
125
+ line(` - ${action}`);
126
+ }
127
+ line('SKS Skills:');
128
+ line(` core skills: ${doctorSkillStatus(doctorNativeCapabilityRepair?.core_skills)}`);
129
+ line(` duplicate project skills: ${doctorDedupeStatus(doctorNativeCapabilityRepair?.skill_dedupe)}`);
130
+ line('SKS Current Command Surface:');
131
+ line(` status: ${commandAliasCleanup.status || (commandAliasCleanup.ok ? 'clean' : 'blocked')}`);
132
+ line(` canonical commands: ${commandAliasCleanup.canonical_command_count ?? 0}`);
133
+ const managedRuntimeCleanup = commandAliasCleanup.cleanup?.managed_runtime;
134
+ if (managedRuntimeCleanup) {
135
+ line(` managed items reconciled: ${managedRuntimeCleanup.removed_managed_artifact_count ?? 0}`);
136
+ line(` user-authored collisions preserved: ${managedRuntimeCleanup.preserved_user_file_count ?? 0}`);
137
+ }
138
+ if (commandAliasCleanup.report_path)
139
+ line(` report: ${commandAliasCleanup.report_path}`);
140
+ line('Secret preservation:');
141
+ line(` Supabase keys: ${doctorNativeCapabilityRepair?.ok === false && String((doctorNativeCapabilityRepair?.blockers || []).join(' ')).includes('secret_preservation_failed') ? 'blocked' : 'preserved'}`);
142
+ line(' raw secret values: never recorded');
143
+ line(` migration journal: ${doctorNativeCapabilityRepair?.secret_preservation_guard || '.sneakoscope/reports/secret-preservation-guard.json'}`);
144
+ const harnessSkipped = codexAppHarnessMatrix.skipped === true;
145
+ const appFeatures = codexAppHarnessMatrix.app_features || {};
146
+ const sksIntegrations = codexAppHarnessMatrix.sks_integrations || {};
147
+ line('Codex App Harness:');
148
+ line(` plugins: ${skippedOr(harnessSkipped, appFeatures.plugin_json ? 'ok' : 'degraded')}`);
149
+ line(` hook approval: ${skippedOr(harnessSkipped, appFeatures.hook_approval_state_detectable ? 'ok' : 'unknown')}`);
150
+ line(` skills: ${skippedOr(harnessSkipped, sksIntegrations.dollar_skills_synced ? 'ok' : 'degraded')}`);
151
+ line(` agent roles: ${skippedOr(harnessSkipped, sksIntegrations.agent_roles_synced ? 'ok' : 'degraded')}`);
152
+ line(` native agent_type: ${skippedOr(harnessSkipped, appFeatures.agent_type_supported ? 'ok' : 'fallback message-role')}`);
153
+ line(` init-deep memory: ${skippedOr(harnessSkipped, sksIntegrations.init_deep_available ? 'available' : 'missing')}`);
154
+ line(` loop mesh app profile: ${skippedOr(harnessSkipped, sksIntegrations.loop_mesh_app_profile_available ? 'available' : 'missing')}`);
155
+ line('Codex App UI:');
156
+ line(` fast selector: ${codexAppUi.fast_selector || 'unknown'}`);
157
+ line(` provider selector: ${codexAppUi.provider_selector || 'unknown'}`);
158
+ if (Array.isArray(codexAppUi.provider_blockers) && codexAppUi.provider_blockers.length) {
159
+ line(` provider blockers: ${codexAppUi.provider_blockers.join(', ')}`);
160
+ }
161
+ if (Array.isArray(codexAppUi.provider_actions) && codexAppUi.provider_actions.length) {
162
+ line(' provider actions:');
163
+ for (const action of codexAppUi.provider_actions)
164
+ line(` - ${action}`);
165
+ }
166
+ line(` host-owned config: ${codexAppUi.host_owned_config || 'unknown'}`);
167
+ if (Array.isArray(codexAppUi.actions) && codexAppUi.actions.some((action) => action.changed)) {
168
+ line(' repaired files:');
169
+ for (const action of codexAppUi.actions.filter((entry) => entry.changed))
170
+ line(` - ${action.file}${action.backup_path ? ` (backup ${action.backup_path})` : ''}`);
171
+ }
172
+ if (codexAppUi.next_action)
173
+ line(` next action: ${codexAppUi.next_action}`);
174
+ line('SKS Menu Bar:');
175
+ line(` status: ${sksMenuBar.status || (sksMenuBar.ok ? 'ok' : 'blocked')}`);
176
+ for (const versionLine of sksMenuBarRunningVersionConsoleLines(sksMenuBar))
177
+ line(versionLine);
178
+ const menubarPhase = result.doctor_fix_transaction?.phases?.find((phase) => phase?.id === 'sks_menubar');
179
+ if (menubarPhase) {
180
+ const menubarSummary = menubarPhase.ok
181
+ ? (menubarPhase.repaired ? 'repaired' : 'verified')
182
+ : `blocked(${(menubarPhase.blockers || []).join(', ') || 'unknown'})`;
183
+ line(` menubar: ${menubarSummary}`);
184
+ }
185
+ if (sksMenuBar.app_path)
186
+ line(` app: ${sksMenuBar.app_path}`);
187
+ if (sksMenuBar.launch_agent_path)
188
+ line(` launch agent: ${sksMenuBar.launch_agent_path}`);
189
+ if (Array.isArray(sksMenuBar.blockers) && sksMenuBar.blockers.length)
190
+ line(` blockers: ${sksMenuBar.blockers.join(', ')}`);
191
+ if (Array.isArray(sksMenuBar.warnings) && sksMenuBar.warnings.length)
192
+ line(` warnings: ${sksMenuBar.warnings.join(', ')}`);
193
+ line(`Provider: ${providerContext.provider || 'unknown'} ${providerContext.service_tier || ''} (${providerContext.source || 'unknown'}, ${providerContext.confidence || 'low'})`);
194
+ const imagegenReady = imagegen.auth_readiness;
195
+ if (imagegenReady) {
196
+ const paths = imagegenReady.available_paths?.length ? imagegenReady.available_paths.join(', ') : 'none';
197
+ line(`Image Gen: auth=${imagegenReady.auth_mode} | headless_auto=${imagegenReady.headless_auto_available ? 'available' : 'unavailable'} | paths: ${paths}`);
198
+ if (!imagegenReady.headless_auto_available) {
199
+ for (const action of imagegenReady.next_actions || [])
200
+ line(` - ${action}`);
201
+ }
202
+ }
203
+ line(`Image Gen repair: ${skippedOr(imagegenRepair.skipped === true, String(extras.nativeCapabilityReadiness?.imagegen?.status))}`);
204
+ for (const action of imagegenRepair.manual_actions || [])
205
+ line(` - ${action}`);
206
+ line(`Computer Use repair: ${skippedOr(computerUseRepair.skipped === true, computerUseRepair.recovered ? 'ok' : computerUseRepair.attempted ? 'blocked' : 'not-needed')}`);
207
+ for (const action of computerUseRepair.next_actions || [])
208
+ line(` - ${action}`);
209
+ line(`Browser Use repair: ${skippedOr(browserUseRepair.skipped === true, browserUseRepair.recovered ? 'ok' : browserUseRepair.attempted ? 'blocked' : 'not-needed')}`);
210
+ for (const action of browserUseRepair.next_actions || [])
211
+ line(` - ${action}`);
212
+ if (mcpTransportCollisionRepair) {
213
+ const collisionCount = (mcpTransportCollisionRepair.servers || []).filter((server) => server.status === 'collision_resolved').length;
214
+ line(`MCP transport collision repair: ${mcpTransportCollisionRepair.ok ? 'ok' : 'blocked'}${collisionCount ? ` (${collisionCount} resolved)` : ''}`);
215
+ }
216
+ line(`Agent bridge: ${extras.agentBridgeManifestExists ? 'manifest present' : 'not set up'}${extras.agentBridgeManifestExists ? '' : ' — run `sks agent-bridge setup` to publish the manifest and register with an MCP host'}`);
217
+ const codexCurrentApp = extras.codexCurrentAppCapability?.report || {};
218
+ const currentAppSkipped = extras.codexCurrentAppCapability?.skipped === true;
219
+ line('Codex current compatibility:');
220
+ line(` target: ${CURRENT_CODEX_RUNTIME_CONTRACT.targetTag}`);
221
+ line(` runtime: ${codex.version || 'unknown'}`);
222
+ line(` multi-agent mode: ${skippedOr(featureMatrixSkipped, codexNativeFeatureMatrix.features?.multi_agent_mode?.ok ? 'verified' : 'unverified')}`);
223
+ line(` rollout budget: ${skippedOr(featureMatrixSkipped, codexNativeFeatureMatrix.features?.rollout_budget?.ok ? 'verified' : 'unverified')}`);
224
+ line(` indexed search: ${skippedOr(featureMatrixSkipped, codexNativeFeatureMatrix.features?.indexed_web_search?.ok ? 'verified' : 'unverified')}`);
225
+ line(` current time: ${skippedOr(featureMatrixSkipped, codexNativeFeatureMatrix.features?.current_time_read?.ok ? 'verified' : 'unverified')}`);
226
+ line('Current Codex app features:');
227
+ line(` /app handoff: ${skippedOr(currentAppSkipped, codexCurrentApp.supports_app_handoff ? 'ok' : 'unavailable')}`);
228
+ line(` plugin JSON: ${skippedOr(currentAppSkipped, codexCurrentApp.supports_plugin_json ? 'ok' : 'unavailable')}`);
229
+ line(` image path exposure: ${skippedOr(currentAppSkipped, codexCurrentApp.supports_image_path_exposure ? 'ok' : 'unavailable')}`);
230
+ line(` OAuth MCP pre-refresh: ${skippedOr(currentAppSkipped, codexCurrentApp.supports_oauth_mcp_prerefresh ? 'ok' : 'unavailable')}`);
231
+ const pluginInventorySkipped = extras.pluginInventory?.skipped === true;
232
+ const pluginReport = extras.pluginInventory?.report;
233
+ const plugins = pluginReport?.plugins || [];
234
+ const remoteMcpCount = plugins.flatMap((plugin) => plugin.remote_mcp_servers || []).length;
235
+ const unavailableTemplates = pluginPolicy?.unavailable_app_templates?.length || 0;
236
+ line(`Codex plugins: ${skippedOr(pluginInventorySkipped, pluginReport ? 'ok' : 'warning')}`);
237
+ line(` Remote MCP servers: ${pluginInventorySkipped ? DOCTOR_CONSOLE_NOT_MEASURED : `${remoteMcpCount} candidates`}`);
238
+ line(` Unavailable app templates: ${pluginInventorySkipped ? DOCTOR_CONSOLE_NOT_MEASURED : unavailableTemplates}`);
239
+ for (const warning of pluginPolicy?.doctor_warnings || [])
240
+ line(` warning: ${warning}`);
241
+ if (codexCurrentAppDoctor.fixed?.length)
242
+ line(` doctor --fix repaired: ${codexCurrentAppDoctor.fixed.join(', ')}`);
243
+ line(`Desktop Bridge: ${desktopBridge.ok ? 'ready' : 'blocked'} (${desktopBridge.status?.readiness?.state || 'unavailable'})`);
244
+ for (const providerId of ['codex-lb', 'openrouter']) {
245
+ const provider = desktopBridge.providers?.[providerId];
246
+ if (!provider)
247
+ continue;
248
+ line(` ${providerId}: ${provider.enabled ? 'enabled' : 'disabled'}; credential ${provider.credential?.state || 'unknown'} (${provider.credential?.source || 'none'}); endpoint ${provider.endpoint?.configured ? 'configured' : 'missing'}`);
249
+ }
250
+ for (const warning of desktopBridge.warnings || [])
251
+ line(` warning: ${warning}`);
252
+ for (const blocker of desktopBridge.blockers || [])
253
+ line(` blocker: ${blocker}`);
254
+ if (!desktopBridge.ok)
255
+ for (const action of desktopBridge.recovery_actions || [])
256
+ line(` action: ${action}`);
257
+ line(`Permissions: config profile and permission profile are tracked separately (${permissionProfiles.codex_config_profile_field}, ${permissionProfiles.codex_permission_profile_field})`);
258
+ line('Ready:');
259
+ line(` cli_ready: ${ready.cli_ready ? 'yes' : 'no'}`);
260
+ line(` mad_ready: ${ready.mad_ready ? 'yes' : 'no'}`);
261
+ line(` managed_state_current: ${ready.managed_state_current ? 'yes' : 'no'}`);
262
+ line(` core_ready: ${ready.core_ready ? 'yes' : 'no'}`);
263
+ line(` center_ready: ${ready.center_ready ? 'yes' : 'no'}${ready.center_attempted ? ' (repair attempted)' : ' (not attempted)'}`);
264
+ line(` ready: ${ready.ready ? 'yes' : 'no'}`);
265
+ if (!ready.ready) {
266
+ line('Primary blocker:');
267
+ line(` ${ready.primary_blocker || 'unknown'}`);
268
+ }
269
+ if (configRepair?.repair_actions?.length) {
270
+ line('What I fixed:');
271
+ for (const action of configRepair.repair_actions)
272
+ line(` - ${action.name}: ${action.ok ? 'ok' : 'failed'}`);
273
+ }
274
+ if (migrationJournal?.journal_path) {
275
+ line(`Migration journal: ${migrationJournal.journal_path} (${migrationJournal.event_count} events, ${migrationJournal.mutations_without_rollback} without rollback)`);
276
+ }
277
+ if (sksUpdate) {
278
+ line(`SKS update: ${sksUpdate.status}${sksUpdate.latest ? ` latest ${sksUpdate.latest}` : ''}${sksUpdate.error ? ` (${sksUpdate.error})` : ''}`);
279
+ }
280
+ if (globalSksInstallCleanup) {
281
+ line(`Global SKS installs: kept ${globalSksInstallCleanup.kept?.length ?? 0}, removed ${globalSksInstallCleanup.removed?.filter((entry) => entry.ok).length ?? 0}, source repo exempt ${globalSksInstallCleanup.candidates?.filter((entry) => entry.source_repo_exempt).length ?? 0}`);
282
+ if (globalSksInstallCleanup.npm_cache)
283
+ line(`NPM cache cleanup: ${globalSksInstallCleanup.npm_cache.status}`);
284
+ }
285
+ if (!ready.ready && ready.next_actions?.length) {
286
+ line('What still needs you:');
287
+ for (const action of ready.next_actions)
288
+ line(` - ${action}`);
289
+ }
290
+ return lines;
291
+ }
@@ -258,8 +258,11 @@ export function buildCodexAppUiDiagnosticFailure(apply, error) {
258
258
  blockers: [error instanceof Error ? error.message : String(error)]
259
259
  };
260
260
  }
261
+ export const DOCTOR_CONSOLE_NOT_MEASURED = 'not measured (run: sks doctor --full)';
261
262
  export function formatCodexDoctorConsoleStatus(report) {
262
- if (!report || report.available !== true)
263
+ if (!report)
264
+ return DOCTOR_CONSOLE_NOT_MEASURED;
265
+ if (report.available !== true)
263
266
  return 'unavailable';
264
267
  return report.disposition || (report.exit_code === 0 ? 'pass' : 'warn');
265
268
  }
@@ -1,5 +1,15 @@
1
+ import path from 'node:path';
1
2
  import { flag } from '../cli/args.js';
2
3
  import { sksMenuBarRestartDeferred } from '../core/codex-app/menubar/index.js';
4
+ export function doctorGlobalOnlySelection(input) {
5
+ if (!input.doctorFix)
6
+ return { global_only: false, reason: null };
7
+ if (flag(input.args, '--global-only'))
8
+ return { global_only: true, reason: 'explicit_flag' };
9
+ if (path.resolve(input.root) === path.resolve(input.home))
10
+ return { global_only: true, reason: 'home_is_root' };
11
+ return { global_only: false, reason: null };
12
+ }
3
13
  export function doctorProfileFromArgs(args = [], doctorFix = false) {
4
14
  const explicit = readOption(args, '--profile');
5
15
  if (isDoctorProfile(explicit))
@@ -29,7 +29,8 @@ import { sweepSksTempDirs } from '../core/retention.js';
29
29
  import { detectImagegenCapability } from '../core/imagegen/imagegen-capability.js';
30
30
  import { CURRENT_CODEX_RUNTIME_CONTRACT } from '../core/codex-compat/codex-runtime-contract.js';
31
31
  import { formatHarnessConflictReport, scanHarnessConflicts } from '../core/harness-conflicts.js';
32
- import { doctorArgWarnings as baseDoctorArgWarnings, doctorMenuBarInstallPolicy, doctorPhaseIdsForProfile, doctorProfileFromArgs, doctorProfileRequiresDesktopBridgeReadiness } from './doctor-profile.js';
32
+ import { doctorArgWarnings as baseDoctorArgWarnings, doctorGlobalOnlySelection, doctorMenuBarInstallPolicy, doctorPhaseIdsForProfile, doctorProfileFromArgs, doctorProfileRequiresDesktopBridgeReadiness } from './doctor-profile.js';
33
+ import { renderDoctorConsoleReport } from './doctor-console.js';
33
34
  import { buildCodexAppUiDiagnosticFailure, buildRuntimeReadiness, captureCodexConfigSnapshot, deferredNativeRepair, doctorDedupeStatus, doctorSkillStatus, fallbackCodexNativeFeatureMatrix, formatCodexDoctorConsoleStatus, installScopeFromArgs, isMigrationUserOwnedProjectConfigBlocker, mergeObservedCodexStartupWarnings, nativeCapabilityStatus, readOption, rebootstrapSksMenuBarLaunchdForDoctorFix, sksMenuBarRunningVersionConsoleLines, uniqueNativeManualActions, writeFixMigrationJournal, writeJsonReportFile } from './doctor-helpers.js';
34
35
  export { doctorMenuBarInstallPolicy, doctorProfileFromArgs, doctorProfileRequiresDesktopBridgeReadiness } from './doctor-profile.js';
35
36
  export { buildCodexAppUiDiagnosticFailure, buildRuntimeReadiness, formatCodexDoctorConsoleStatus, rebootstrapSksMenuBarLaunchdForDoctorFix, sksMenuBarRunningVersionConsoleLines };
@@ -134,7 +135,8 @@ export async function run(_command, args = [], deps = {}) {
134
135
  return report;
135
136
  }
136
137
  const doctorFix = flag(args, '--fix');
137
- const globalOnly = doctorFix && flag(args, '--global-only');
138
+ const homeDir = path.resolve(deps.home || process.env.HOME || os.homedir());
139
+ const globalOnly = doctorGlobalOnlySelection({ args, doctorFix, root, home: homeDir }).global_only;
138
140
  if (doctorFix) {
139
141
  const conflictScan = await scanHarnessConflicts(root);
140
142
  if (conflictScan.hard_block) {
@@ -167,9 +169,7 @@ export async function run(_command, args = [], deps = {}) {
167
169
  if (!doctorFix && flag(args, '--json') && doctorProfile === 'fast')
168
170
  return runDoctorJsonFastPath(args, root);
169
171
  if (doctorFix) {
170
- const guardRoot = globalOnly
171
- ? path.resolve(deps.home || process.env.HOME || os.homedir())
172
- : root;
172
+ const guardRoot = globalOnly ? homeDir : root;
173
173
  return withSecretPreservationGuard(guardRoot, 'doctor-fix', async () => (globalOnly
174
174
  ? runDoctorGlobalOnlyFix(args, root, deps)
175
175
  : runDoctor(args, root, doctorFix, deps)));
@@ -248,6 +248,7 @@ export async function executeDoctorGlobalOnlyFix(args = [], root, deps = {}) {
248
248
  : [])
249
249
  ].map(String).filter(Boolean))];
250
250
  const ok = blockers.length === 0;
251
+ const projectRootAliasDetected = path.resolve(root) === home;
251
252
  return {
252
253
  schema: 'sks.doctor-status.v3',
253
254
  elapsed_ms: Date.now() - startedAtMs,
@@ -258,7 +259,7 @@ export async function executeDoctorGlobalOnlyFix(args = [], root, deps = {}) {
258
259
  install_scope: 'global',
259
260
  root,
260
261
  home,
261
- project_root_alias_detected: path.resolve(root) === home,
262
+ project_root_alias_detected: projectRootAliasDetected,
262
263
  no_project_writes_performed: true,
263
264
  project_phases_skipped: [
264
265
  'project_skills_reconcile',
@@ -277,6 +278,9 @@ export async function executeDoctorGlobalOnlyFix(args = [], root, deps = {}) {
277
278
  desktop_bridge: desktopBridge,
278
279
  blockers,
279
280
  next_actions: [
281
+ ...(projectRootAliasDetected
282
+ ? ['Project checks were skipped because this directory is your home folder. Run them from your project: cd <your-project> && sks doctor --fix']
283
+ : []),
280
284
  ...(desktopBridge.ok === false ? desktopBridge.recovery_actions || [] : []),
281
285
  'Run `sks doctor --fix --json` from a specific project directory when project-scoped repair is required.'
282
286
  ]
@@ -1597,246 +1601,17 @@ async function runDoctor(args = [], root, doctorFix, deps = {}) {
1597
1601
  process.exitCode = 1;
1598
1602
  return;
1599
1603
  }
1600
- console.log('SKS Doctor');
1601
- for (const warning of argWarnings)
1602
- console.log(`Argument warning: ${warning}`);
1603
- for (const warning of officialSubagentConfig.warnings || [])
1604
- console.log(`Official subagent warning: ${warning}`);
1605
- console.log(`Root: ${root}`);
1606
- console.log(`Node: ${result.node.ok ? 'ok' : 'fail'} ${result.node.version}`);
1607
- console.log(`Codex: ${codex.bin ? 'ok' : 'missing'} ${codex.version || ''}`);
1608
- if (oauthCallbackPortDiagnostic.conflict) {
1609
- const listeners = oauthCallbackPortDiagnostic.listeners
1610
- .map((listener) => `${listener.command} pid ${listener.pid} ${listener.address}`)
1611
- .join(', ');
1612
- console.log(`OAuth callback port 1455: warning (${listeners})`);
1613
- for (const action of oauthCallbackOperatorActions)
1614
- console.log(` action: ${action}`);
1615
- }
1616
- const actual = (codexConfig.checks || []).find((check) => check.name === 'actual_codex_cli_config_load');
1617
- console.log('Project config:');
1618
- console.log(` node read: ${ready.codex_config_readable_by_node ? 'ok' : 'failed'}`);
1619
- console.log(` codex cli read: ${ready.codex_config_readable_by_codex_cli ? 'ok' : (actual?.status || 'failed')}`);
1620
- console.log('Context7 MCP:');
1621
- console.log(` transport: ${context7Repair.preferred_transport || 'remote'}`);
1622
- console.log(` repair: ${context7Repair.ok ? 'ok' : 'blocked'}`);
1623
- for (const action of context7Repair.actions || [])
1624
- console.log(` - ${action}`);
1625
- for (const warning of context7Repair.warnings || [])
1626
- console.log(` warning: ${warning}`);
1627
- console.log('Codex startup config:');
1628
- console.log(` repair: ${codexStartupRepair.ok ? 'ok' : 'blocked'}`);
1629
- for (const action of codexStartupRepair.actions || [])
1630
- console.log(` - ${action}`);
1631
- for (const action of codexStartupRepair.manual_actions || [])
1632
- console.log(` manual: ${action}`);
1633
- for (const warning of codexStartupRepair.warnings || [])
1634
- console.log(` warning: ${warning}`);
1635
- if (codexConfigSyntaxRepair) {
1636
- console.log('Codex config syntax:');
1637
- console.log(` repair: ${codexConfigSyntaxRepair.ok ? 'ok' : 'blocked'}`);
1638
- for (const action of codexConfigSyntaxRepair.actions || [])
1639
- console.log(` - ${action}`);
1640
- for (const action of codexConfigSyntaxRepair.manual_actions || [])
1641
- console.log(` manual: ${action}`);
1642
- for (const warning of codexConfigSyntaxRepair.warnings || [])
1643
- console.log(` warning: ${warning}`);
1644
- }
1645
- console.log(` codex doctor: ${formatCodexDoctorConsoleStatus(authoritativeCodexDoctor)}`);
1646
- console.log(`Rust acc.: ${rust.mode || (rust.available ? 'rust_accelerated' : 'js_fallback')} ${rust.version || rust.status || ''}`);
1647
- console.log(`Codex App: ${ready.codex_app_ready ? 'ok' : 'optional_missing'}`);
1648
- console.log('SKS Runtime Readiness:');
1649
- console.log(` Codex Native: ${runtimeReadiness.codex_native}`);
1650
- console.log(` Loop Mesh: ${runtimeReadiness.loop_mesh}`);
1651
- console.log(` QA Visual: ${runtimeReadiness.qa_visual}`);
1652
- console.log(` Research Sources: ${runtimeReadiness.research_sources}`);
1653
- console.log(` Image Follow-up: ${runtimeReadiness.image_followup}`);
1654
- for (const note of runtimeReadiness.notes)
1655
- console.log(` ${note}`);
1656
- if (runtimeReadiness.repair_actions.length) {
1657
- console.log('Repair actions:');
1658
- for (const action of runtimeReadiness.repair_actions)
1659
- console.log(` - ${action}`);
1660
- }
1661
- const nativeCapabilityRows = Array.isArray(doctorNativeCapabilityRepair?.native_capabilities?.capabilities)
1662
- ? doctorNativeCapabilityRepair.native_capabilities.capabilities
1663
- : [];
1664
- console.log('SKS Native Capabilities:');
1665
- console.log(` image generation: ${nativeCapabilityStatus(nativeCapabilityRows, 'image_generation', 'repair_required')}`);
1666
- console.log(` image follow-up edit: ${nativeCapabilityStatus(nativeCapabilityRows, 'image_followup_edit', 'degraded')}`);
1667
- console.log(` computer use: ${nativeCapabilityStatus(nativeCapabilityRows, 'computer_use', 'manual_required')}`);
1668
- console.log(` Chrome/web review: ${nativeCapabilityStatus(nativeCapabilityRows, 'chrome_web_review', 'manual_required')}`);
1669
- console.log(` app screenshot: ${nativeCapabilityStatus(nativeCapabilityRows, 'codex_app_screenshot', 'degraded')}`);
1670
- console.log(` app handoff: ${nativeCapabilityStatus(nativeCapabilityRows, 'app_handoff', 'unavailable')}`);
1671
- console.log(` image path exposure: ${nativeCapabilityStatus(nativeCapabilityRows, 'image_path_exposure', 'fallback')}`);
1672
- const nativeManualActions = uniqueNativeManualActions(nativeCapabilityRows);
1673
- if (nativeManualActions.length) {
1674
- console.log(' manual next actions:');
1675
- for (const action of nativeManualActions)
1676
- console.log(` - ${action}`);
1677
- }
1678
- console.log('SKS Skills:');
1679
- console.log(` core skills: ${doctorSkillStatus(doctorNativeCapabilityRepair?.core_skills)}`);
1680
- console.log(` duplicate project skills: ${doctorDedupeStatus(doctorNativeCapabilityRepair?.skill_dedupe)}`);
1681
- console.log('SKS Current Command Surface:');
1682
- console.log(` status: ${commandAliasCleanup.status || (commandAliasCleanup.ok ? 'clean' : 'blocked')}`);
1683
- console.log(` canonical commands: ${commandAliasCleanup.canonical_command_count ?? 0}`);
1684
- const managedRuntimeCleanup = commandAliasCleanup?.cleanup?.managed_runtime;
1685
- if (managedRuntimeCleanup) {
1686
- console.log(` managed items reconciled: ${managedRuntimeCleanup.removed_managed_artifact_count ?? 0}`);
1687
- console.log(` user-authored collisions preserved: ${managedRuntimeCleanup.preserved_user_file_count ?? 0}`);
1688
- }
1689
- if (commandAliasCleanup.report_path)
1690
- console.log(` report: ${commandAliasCleanup.report_path}`);
1691
- console.log('Secret preservation:');
1692
- console.log(` Supabase keys: ${doctorNativeCapabilityRepair?.ok === false && String((doctorNativeCapabilityRepair?.blockers || []).join(' ')).includes('secret_preservation_failed') ? 'blocked' : 'preserved'}`);
1693
- console.log(' raw secret values: never recorded');
1694
- console.log(` migration journal: ${doctorNativeCapabilityRepair?.secret_preservation_guard || '.sneakoscope/reports/secret-preservation-guard.json'}`);
1695
- console.log('Codex App Harness:');
1696
- console.log(` plugins: ${codexAppHarnessMatrix.app_features?.plugin_json ? 'ok' : 'degraded'}`);
1697
- console.log(` hook approval: ${codexAppHarnessMatrix.app_features?.hook_approval_state_detectable ? 'ok' : 'unknown'}`);
1698
- console.log(` skills: ${codexAppHarnessMatrix.sks_integrations?.dollar_skills_synced ? 'ok' : 'degraded'}`);
1699
- console.log(` agent roles: ${codexAppHarnessMatrix.sks_integrations?.agent_roles_synced ? 'ok' : 'degraded'}`);
1700
- console.log(` native agent_type: ${codexAppHarnessMatrix.app_features?.agent_type_supported ? 'ok' : 'fallback message-role'}`);
1701
- console.log(` init-deep memory: ${codexAppHarnessMatrix.sks_integrations?.init_deep_available ? 'available' : 'missing'}`);
1702
- console.log(` loop mesh app profile: ${codexAppHarnessMatrix.sks_integrations?.loop_mesh_app_profile_available ? 'available' : 'missing'}`);
1703
- const codexAppUiStatus = codexAppUi;
1704
- console.log('Codex App UI:');
1705
- console.log(` fast selector: ${codexAppUi.fast_selector || 'unknown'}`);
1706
- console.log(` provider selector: ${codexAppUi.provider_selector || 'unknown'}`);
1707
- if (Array.isArray(codexAppUiStatus.provider_blockers) && codexAppUiStatus.provider_blockers.length) {
1708
- console.log(` provider blockers: ${codexAppUiStatus.provider_blockers.join(', ')}`);
1709
- }
1710
- if (Array.isArray(codexAppUiStatus.provider_actions) && codexAppUiStatus.provider_actions.length) {
1711
- console.log(' provider actions:');
1712
- for (const action of codexAppUiStatus.provider_actions)
1713
- console.log(` - ${action}`);
1714
- }
1715
- console.log(` host-owned config: ${codexAppUi.host_owned_config || 'unknown'}`);
1716
- if (Array.isArray(codexAppUi.actions) && codexAppUi.actions.some((action) => action.changed)) {
1717
- console.log(' repaired files:');
1718
- for (const action of codexAppUi.actions.filter((entry) => entry.changed))
1719
- console.log(` - ${action.file}${action.backup_path ? ` (backup ${action.backup_path})` : ''}`);
1720
- }
1721
- if (codexAppUi.next_action)
1722
- console.log(` next action: ${codexAppUi.next_action}`);
1723
- console.log('SKS Menu Bar:');
1724
- console.log(` status: ${sksMenuBar.status || (sksMenuBar.ok ? 'ok' : 'blocked')}`);
1725
- for (const line of sksMenuBarRunningVersionConsoleLines(sksMenuBar))
1726
- console.log(line);
1727
- const menubarPhase = doctorFixTransaction?.phases?.find((phase) => phase?.id === 'sks_menubar');
1728
- if (menubarPhase) {
1729
- const menubarSummary = menubarPhase.ok
1730
- ? (menubarPhase.repaired ? 'repaired' : 'verified')
1731
- : `blocked(${(menubarPhase.blockers || []).join(', ') || 'unknown'})`;
1732
- console.log(` menubar: ${menubarSummary}`);
1733
- }
1734
- if (sksMenuBar.app_path)
1735
- console.log(` app: ${sksMenuBar.app_path}`);
1736
- if (sksMenuBar.launch_agent_path)
1737
- console.log(` launch agent: ${sksMenuBar.launch_agent_path}`);
1738
- if (Array.isArray(sksMenuBar.blockers) && sksMenuBar.blockers.length)
1739
- console.log(` blockers: ${sksMenuBar.blockers.join(', ')}`);
1740
- if (Array.isArray(sksMenuBar.warnings) && sksMenuBar.warnings.length)
1741
- console.log(` warnings: ${sksMenuBar.warnings.join(', ')}`);
1742
- console.log(`Provider: ${providerContext.provider || 'unknown'} ${providerContext.service_tier || ''} (${providerContext.source || 'unknown'}, ${providerContext.confidence || 'low'})`);
1743
- const imagegenReady = imagegen.auth_readiness;
1744
- if (imagegenReady) {
1745
- const paths = imagegenReady.available_paths?.length ? imagegenReady.available_paths.join(', ') : 'none';
1746
- console.log(`Image Gen: auth=${imagegenReady.auth_mode} | headless_auto=${imagegenReady.headless_auto_available ? 'available' : 'unavailable'} | paths: ${paths}`);
1747
- if (!imagegenReady.headless_auto_available) {
1748
- for (const action of imagegenReady.next_actions || [])
1749
- console.log(` - ${action}`);
1750
- }
1751
- }
1752
- console.log(`Image Gen repair: ${nativeCapabilityReadiness.imagegen.status}`);
1753
- for (const action of imagegenRepair.manual_actions || [])
1754
- console.log(` - ${action}`);
1755
- console.log(`Computer Use repair: ${computerUseRepair.recovered ? 'ok' : computerUseRepair.attempted ? 'blocked' : 'not-needed'}`);
1756
- for (const action of computerUseRepair.next_actions || [])
1757
- console.log(` - ${action}`);
1758
- console.log(`Browser Use repair: ${browserUseRepair.recovered ? 'ok' : browserUseRepair.attempted ? 'blocked' : 'not-needed'}`);
1759
- for (const action of browserUseRepair.next_actions || [])
1760
- console.log(` - ${action}`);
1761
- if (mcpTransportCollisionRepair) {
1762
- const collisionCount = (mcpTransportCollisionRepair.servers || []).filter((s) => s.status === 'collision_resolved').length;
1763
- console.log(`MCP transport collision repair: ${mcpTransportCollisionRepair.ok ? 'ok' : 'blocked'}${collisionCount ? ` (${collisionCount} resolved)` : ''}`);
1764
- }
1765
- {
1766
- const manifestPath = path.join(root, '.sneakoscope', 'agent-bridge', 'manifest.json');
1767
- const manifestExists = await exists(manifestPath);
1768
- console.log(`Agent bridge: ${manifestExists ? 'manifest present' : 'not set up'}${manifestExists ? '' : ' — run `sks agent-bridge setup` to publish the manifest and register with an MCP host'}`);
1769
- }
1770
- const codexCurrentApp = codexCurrentAppCapability.report || {};
1771
- console.log('Codex current compatibility:');
1772
- console.log(` target: ${CURRENT_CODEX_RUNTIME_CONTRACT.targetTag}`);
1773
- console.log(` runtime: ${codex.version || 'unknown'}`);
1774
- console.log(` multi-agent mode: ${codexNativeFeatureMatrix.features?.multi_agent_mode?.ok ? 'verified' : 'unverified'}`);
1775
- console.log(` rollout budget: ${codexNativeFeatureMatrix.features?.rollout_budget?.ok ? 'verified' : 'unverified'}`);
1776
- console.log(` indexed search: ${codexNativeFeatureMatrix.features?.indexed_web_search?.ok ? 'verified' : 'unverified'}`);
1777
- console.log(` current time: ${codexNativeFeatureMatrix.features?.current_time_read?.ok ? 'verified' : 'unverified'}`);
1778
- console.log('Current Codex app features:');
1779
- console.log(` /app handoff: ${codexCurrentApp.supports_app_handoff ? 'ok' : 'unavailable'}`);
1780
- console.log(` plugin JSON: ${codexCurrentApp.supports_plugin_json ? 'ok' : 'unavailable'}`);
1781
- console.log(` image path exposure: ${codexCurrentApp.supports_image_path_exposure ? 'ok' : 'unavailable'}`);
1782
- console.log(` OAuth MCP pre-refresh: ${codexCurrentApp.supports_oauth_mcp_prerefresh ? 'ok' : 'unavailable'}`);
1783
- const plugins = pluginInventory?.report?.plugins || [];
1784
- const remoteMcpCount = plugins.flatMap((plugin) => plugin.remote_mcp_servers || []).length;
1785
- const unavailableTemplates = pluginPolicy?.unavailable_app_templates?.length || 0;
1786
- console.log(`Codex plugins: ${pluginInventory?.report ? 'ok' : 'warning'}`);
1787
- console.log(` Remote MCP servers: ${remoteMcpCount} candidates`);
1788
- console.log(` Unavailable app templates: ${unavailableTemplates}`);
1789
- for (const warning of pluginPolicy?.doctor_warnings || [])
1790
- console.log(` warning: ${warning}`);
1791
- if (codexCurrentAppDoctor?.fixed?.length)
1792
- console.log(` doctor --fix repaired: ${codexCurrentAppDoctor.fixed.join(', ')}`);
1793
- console.log(`Desktop Bridge: ${desktopBridge.ok ? 'ready' : 'blocked'} (${desktopBridge.status?.readiness?.state || 'unavailable'})`);
1794
- for (const providerId of ['codex-lb', 'openrouter']) {
1795
- const provider = desktopBridge.providers?.[providerId];
1796
- if (!provider)
1797
- continue;
1798
- console.log(` ${providerId}: ${provider.enabled ? 'enabled' : 'disabled'}; credential ${provider.credential?.state || 'unknown'} (${provider.credential?.source || 'none'}); endpoint ${provider.endpoint?.configured ? 'configured' : 'missing'}`);
1799
- }
1800
- for (const warning of desktopBridge.warnings || [])
1801
- console.log(` warning: ${warning}`);
1802
- for (const blocker of desktopBridge.blockers || [])
1803
- console.log(` blocker: ${blocker}`);
1804
- if (!desktopBridge.ok)
1805
- for (const action of desktopBridge.recovery_actions || [])
1806
- console.log(` action: ${action}`);
1807
- console.log(`Permissions: config profile and permission profile are tracked separately (${permissionProfiles.codex_config_profile_field}, ${permissionProfiles.codex_permission_profile_field})`);
1808
- console.log('Ready:');
1809
- console.log(` cli_ready: ${ready.cli_ready ? 'yes' : 'no'}`);
1810
- console.log(` mad_ready: ${ready.mad_ready ? 'yes' : 'no'}`);
1811
- console.log(` managed_state_current: ${ready.managed_state_current ? 'yes' : 'no'}`);
1812
- console.log(` core_ready: ${ready.core_ready ? 'yes' : 'no'}`);
1813
- console.log(` center_ready: ${ready.center_ready ? 'yes' : 'no'}${ready.center_attempted ? ' (repair attempted)' : ' (not attempted)'}`);
1814
- console.log(` ready: ${ready.ready ? 'yes' : 'no'}`);
1815
- if (!ready.ready) {
1816
- console.log('Primary blocker:');
1817
- console.log(` ${ready.primary_blocker || 'unknown'}`);
1818
- }
1819
- if (configRepair?.repair_actions?.length) {
1820
- console.log('What I fixed:');
1821
- for (const action of configRepair.repair_actions)
1822
- console.log(` - ${action.name}: ${action.ok ? 'ok' : 'failed'}`);
1823
- }
1824
- if (migrationJournal?.journal_path) {
1825
- console.log(`Migration journal: ${migrationJournal.journal_path} (${migrationJournal.event_count} events, ${migrationJournal.mutations_without_rollback} without rollback)`);
1826
- }
1827
- if (sksUpdate) {
1828
- console.log(`SKS update: ${sksUpdate.status}${sksUpdate.latest ? ` latest ${sksUpdate.latest}` : ''}${sksUpdate.error ? ` (${sksUpdate.error})` : ''}`);
1829
- }
1830
- if (globalSksInstallCleanup) {
1831
- console.log(`Global SKS installs: kept ${globalSksInstallCleanup.kept?.length ?? 0}, removed ${globalSksInstallCleanup.removed?.filter((entry) => entry.ok).length ?? 0}, source repo exempt ${globalSksInstallCleanup.candidates?.filter((entry) => entry.source_repo_exempt).length ?? 0}`);
1832
- if (globalSksInstallCleanup.npm_cache)
1833
- console.log(`NPM cache cleanup: ${globalSksInstallCleanup.npm_cache.status}`);
1834
- }
1835
- if (!ready.ready && ready.next_actions?.length) {
1836
- console.log('What still needs you:');
1837
- for (const action of ready.next_actions)
1838
- console.log(` - ${action}`);
1839
- }
1604
+ const agentBridgeManifestExists = await exists(path.join(root, '.sneakoscope', 'agent-bridge', 'manifest.json'));
1605
+ const consoleLines = renderDoctorConsoleReport(result, {
1606
+ oauthCallbackOperatorActions,
1607
+ nativeCapabilityReadiness,
1608
+ agentBridgeManifestExists,
1609
+ codexCurrentAppCapability,
1610
+ pluginInventory,
1611
+ rootIsHome: path.resolve(root) === path.resolve(deps.home || process.env.HOME || os.homedir())
1612
+ });
1613
+ for (const consoleLine of consoleLines)
1614
+ console.log(consoleLine);
1840
1615
  if (!result.ok)
1841
1616
  process.exitCode = 1;
1842
1617
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schema": "sks.skills-manifest.v1",
3
- "package_version": "9.0.0",
3
+ "package_version": "9.0.2",
4
4
  "skills": [
5
5
  {
6
6
  "canonical_name": "sks",
@@ -252,6 +252,15 @@ export async function forwardHttp(req, res, config, prepared, authenticatedLocal
252
252
  upstream.once('response', (response) => {
253
253
  const statusCode = response.statusCode || 502;
254
254
  if (statusCode >= 400) {
255
+ logHttpRejection({
256
+ code: `bridge_upstream_status_${statusCode}`,
257
+ transport: 'http',
258
+ ...(req.method === undefined ? {} : { method: req.method }),
259
+ ...(req.url === undefined ? {} : { url: req.url }),
260
+ status: statusCode,
261
+ provider_id: provider.provider_id,
262
+ public_model: request.route.public_model,
263
+ });
255
264
  void readRedactedUpstreamError(response).then((body) => {
256
265
  responseStarted = true;
257
266
  const responseHeaders = rewriteResponseHeaders(response.headers, provider.base_url, authenticatedLocalBaseUrl);
@@ -2,6 +2,10 @@ import { PACKAGE_VERSION } from '../../version.js';
2
2
  export const DESKTOP_BRIDGE_LOG_SCHEMA = 'sks.desktop-bridge-log.v2';
3
3
  export const REJECTION_LOG_BURST = 5;
4
4
  export const REJECTION_LOG_SUMMARY_INTERVAL_MS = 60_000;
5
+ function safeCatalogId(value) {
6
+ const text = String(value ?? '').replace(/[\r\n\0]/g, '').trim().slice(0, 128);
7
+ return text.length > 0 ? text : null;
8
+ }
5
9
  function safePathname(url) {
6
10
  const raw = String(url || '').trim();
7
11
  if (!raw.startsWith('/'))
@@ -62,6 +66,8 @@ export function createDesktopBridgeRejectionLogger(options = {}) {
62
66
  windows.set(code, window);
63
67
  const pathname = safePathname(event.url);
64
68
  const method = safeMethod(event.method);
69
+ const providerId = safeCatalogId(event.provider_id);
70
+ const publicModel = safeCatalogId(event.public_model);
65
71
  emit({
66
72
  event: 'sks.desktop_bridge.rejected',
67
73
  code,
@@ -69,6 +75,8 @@ export function createDesktopBridgeRejectionLogger(options = {}) {
69
75
  ...(method ? { method } : {}),
70
76
  ...(pathname ? { pathname } : {}),
71
77
  ...(Number.isInteger(event.status) ? { status: event.status } : {}),
78
+ ...(providerId ? { provider_id: providerId } : {}),
79
+ ...(publicModel ? { public_model: publicModel } : {}),
72
80
  });
73
81
  };
74
82
  }
@@ -6,6 +6,7 @@ import { createDesktopBridgeRejectionLogger } from './rejection-log.js';
6
6
  import { assertAllowedOrigin, assertAllowedPath, assertLoopbackPeer, assertLoopbackListenHost, assertWebSocketUpgrade, prepareDesktopBridgeConfig, safeBridgeErrorCode, validatePreparedDesktopBridgeConfig, } from './security.js';
7
7
  import { createDesktopBridgePublicState, desktopBridgeListenOrigin, desktopBridgeStatePath, refreshDesktopBridgeState, removeDesktopBridgeStateIfOwned, writeDesktopBridgeState, } from './state.js';
8
8
  import { DesktopBridgeError } from './types.js';
9
+ import { PACKAGE_VERSION } from '../../version.js';
9
10
  import { DESKTOP_BRIDGE_CLIENT_PATH_PREFIX, DESKTOP_BRIDGE_DIAGNOSTIC_HEALTH_PATH, DESKTOP_BRIDGE_DIAGNOSTIC_PATH, DESKTOP_BRIDGE_DIAGNOSTIC_PROTOCOL } from './types.js';
10
11
  import { forwardWebSocket } from './websocket-forward.js';
11
12
  function authenticateDesktopBridgeClient(req, input) {
@@ -316,6 +317,27 @@ export async function startPreparedDesktopBridge(input, options = {}) {
316
317
  void refreshDesktopBridgeState(statePath, state, new Date(), freshnessMs).catch(() => undefined);
317
318
  }, Math.max(1_000, Math.floor(freshnessMs / 3))) : null;
318
319
  heartbeat?.unref();
320
+ let skewCandidate = null;
321
+ let skewFired = false;
322
+ const versionCheck = options.versionSkew ? setInterval(() => {
323
+ void options.versionSkew.readInstalledVersion().then((installed) => {
324
+ if (skewFired)
325
+ return;
326
+ if (!installed || installed === PACKAGE_VERSION) {
327
+ skewCandidate = null;
328
+ return;
329
+ }
330
+ if (skewCandidate !== installed) {
331
+ skewCandidate = installed;
332
+ return;
333
+ }
334
+ skewFired = true;
335
+ if (versionCheck)
336
+ clearInterval(versionCheck);
337
+ options.versionSkew.onSkew(installed);
338
+ }).catch(() => undefined);
339
+ }, Math.max(1_000, options.versionSkew.intervalMs ?? 60_000)) : null;
340
+ versionCheck?.unref();
319
341
  let stopped = false;
320
342
  const handle = {
321
343
  server,
@@ -328,6 +350,8 @@ export async function startPreparedDesktopBridge(input, options = {}) {
328
350
  stopped = true;
329
351
  if (heartbeat)
330
352
  clearInterval(heartbeat);
353
+ if (versionCheck)
354
+ clearInterval(versionCheck);
331
355
  await closeServer(server, sockets, () => activeRequests);
332
356
  destroyDesktopBridgeUpstreamAgents();
333
357
  if (statePath)
@@ -152,14 +152,10 @@ export async function forwardWebSocket(req, client, head, config, authenticatedL
152
152
  connected = true;
153
153
  clearTimeout(timer);
154
154
  upstream.setNoDelay(true);
155
- upstream.setTimeout(config.idleTimeoutMs, () => {
156
- logWebSocketRejection({
157
- code: 'bridge_websocket_upstream_idle_timeout',
158
- transport: 'websocket',
159
- ...(req.url === undefined ? {} : { url: req.url }),
160
- });
161
- upstream.destroy(new DesktopBridgeError('bridge_websocket_upstream_idle_timeout'));
162
- });
155
+ upstream.setKeepAlive(true, 30_000);
156
+ const clientSocket = client;
157
+ if (typeof clientSocket.setKeepAlive === 'function')
158
+ clientSocket.setKeepAlive(true, 30_000);
163
159
  const headers = buildProviderWebSocketHeaders(req.headers, { providerId: provider.provider_id, authTransport: provider.auth_transport, credential }, target.host);
164
160
  upstream.write([`${req.method || 'GET'} ${target.pathname}${target.search} HTTP/1.1`, ...serializeHeaders(headers), '', ''].join('\r\n'));
165
161
  if (head.length)
@@ -3,7 +3,7 @@ import { constants as fsConstants } from 'node:fs';
3
3
  import fsp from 'node:fs/promises';
4
4
  import os from 'node:os';
5
5
  import path from 'node:path';
6
- import { ensureDir, exists, runProcess, which, writeTextAtomic } from '../fsx.js';
6
+ import { ensureDir, exists, packageRoot, runProcess, which, writeTextAtomic } from '../fsx.js';
7
7
  import { withFileLock } from '../locks/file-lock.js';
8
8
  import { loadCodexLbEnv } from './codex-lb-env.js';
9
9
  import { resolveOpenRouterApiKey } from '../providers/openrouter/openrouter-secret-store.js';
@@ -352,7 +352,7 @@ export async function installAndStartDesktopBridgeService(options = {}) {
352
352
  return failedStatus(paths, settings, launchService(options.uid), 'settings_missing', 'desktop_bridge_sks_executable_missing');
353
353
  await prepareDesktopBridgeServicePaths(paths);
354
354
  await writeDesktopBridgeServiceSettings(paths.settings_path, { ...settings, provider_registry: runtime.config.providerRegistry, route_policy: runtime.config.routePolicy });
355
- await writeDesktopBridgeLaunchdPlist(paths.launch_agent_path, { executablePath: command.executable, arguments: [...command.arguments, 'bridge', 'serve', '--settings', paths.settings_path, '--json'], stdoutPath: paths.stdout_log_path, stderrPath: paths.stderr_log_path });
355
+ await writeDesktopBridgeLaunchdPlist(paths.launch_agent_path, { executablePath: command.executable, arguments: [...command.arguments, 'bridge', 'serve', '--settings', paths.settings_path, '--json', '--supervised'], stdoutPath: paths.stdout_log_path, stderrPath: paths.stderr_log_path });
356
356
  const run = options.run || runProcess;
357
357
  const ctl = options.launchctl || '/bin/launchctl';
358
358
  const service = launchService(options.uid);
@@ -421,11 +421,36 @@ export async function stopDesktopBridgeService(options = {}) {
421
421
  const status = await desktopBridgeServiceStatus({ ...options, home });
422
422
  return { ...status, ok: !status.running, blockers: status.running ? ['desktop_bridge_process_still_running'] : [] };
423
423
  }
424
+ function desktopBridgeIsSupervised(env = process.env, argv = process.argv) {
425
+ return env.XPC_SERVICE_NAME === DESKTOP_BRIDGE_LAUNCHD_LABEL || argv.includes('--supervised');
426
+ }
427
+ async function installedPackageVersion() {
428
+ try {
429
+ const raw = await fsp.readFile(path.join(packageRoot(), 'package.json'), 'utf8');
430
+ const version = JSON.parse(raw).version;
431
+ return typeof version === 'string' && version.length > 0 ? version : null;
432
+ }
433
+ catch {
434
+ return null;
435
+ }
436
+ }
424
437
  export async function serveDesktopBridge(options = {}) {
425
438
  let handle = null;
426
439
  try {
427
440
  const runtime = await resolveDesktopBridgeRuntimeConfig(options);
428
- handle = await startPreparedDesktopBridge(await preflightDesktopBridge(runtime.config), { statePath: runtime.paths.state_path });
441
+ const supervised = desktopBridgeIsSupervised();
442
+ handle = await startPreparedDesktopBridge(await preflightDesktopBridge(runtime.config), {
443
+ statePath: runtime.paths.state_path,
444
+ versionSkew: {
445
+ readInstalledVersion: installedPackageVersion,
446
+ onSkew: (installedVersion) => {
447
+ process.stdout.write(`${JSON.stringify({ schema: 'sks.desktop-bridge-log.v2', event: 'sks.desktop_bridge.version_skew', running: PACKAGE_VERSION, installed: installedVersion, supervised, action: supervised ? 'restarting' : 'logged_only', secret_fields_redacted: true })}\n`);
448
+ if (!supervised)
449
+ return;
450
+ void handle?.stop().catch(() => undefined).finally(() => process.exit(64));
451
+ },
452
+ },
453
+ });
429
454
  process.stdout.write(`${JSON.stringify({ schema: 'sks.desktop-bridge-log.v2', event: 'sks.desktop_bridge.started', pid: handle.state.pid, process_generation: handle.state.schema === DESKTOP_BRIDGE_STATE_SCHEMA ? handle.state.process_generation : null, provider_registry_generation: runtime.config.providerRegistry?.generation, route_policy_generation: runtime.config.routePolicy?.policy_generation, secret_fields_redacted: true })}\n`);
430
455
  await waitForShutdown(handle);
431
456
  return { schema: 'sks.desktop-bridge-serve.v1', ok: true, status: 'stopped', state: handle.state };
package/dist/core/fsx.js CHANGED
@@ -342,17 +342,23 @@ export async function findUp(start, names) {
342
342
  }
343
343
  export async function findProjectRoot(start = process.cwd()) {
344
344
  const resolved = path.resolve(start);
345
- const sine = await findUp(resolved, ['.sneakoscope', '.dcodex']);
346
- if (sine) {
347
- const rootDir = path.dirname(sine);
348
- if (rootDir !== path.parse(rootDir).root)
349
- return rootDir;
350
- }
351
- const git = await findUp(resolved, ['.git']);
352
- if (git) {
353
- const rootDir = path.dirname(git);
354
- if (rootDir !== path.parse(rootDir).root)
355
- return rootDir;
345
+ const home = path.resolve(os.homedir());
346
+ for (const markers of [['.sneakoscope', '.dcodex'], ['.git']]) {
347
+ let from = resolved;
348
+ while (true) {
349
+ const marker = await findUp(from, markers);
350
+ if (!marker)
351
+ break;
352
+ const rootDir = path.dirname(marker);
353
+ if (rootDir === path.parse(rootDir).root)
354
+ break;
355
+ if (rootDir !== home)
356
+ return rootDir;
357
+ const parent = path.dirname(rootDir);
358
+ if (parent === rootDir)
359
+ break;
360
+ from = parent;
361
+ }
356
362
  }
357
363
  return null;
358
364
  }
@@ -106,11 +106,3 @@ export function contextGraphExperimentLogPath(root) {
106
106
  export function contextPackPath(root) {
107
107
  return path.join(contextGraphDir(root), 'context-pack.json');
108
108
  }
109
- export function contextGraphArtifactPaths(root) {
110
- return [
111
- contextGraphSnapshotPath(root),
112
- contextGraphMetaPath(root),
113
- contextGraphPrevSnapshotPath(root),
114
- contextGraphEventLogPath(root)
115
- ];
116
- }
@@ -0,0 +1,46 @@
1
+ import os from 'node:os';
2
+ import path from 'node:path';
3
+ import { exists, PACKAGE_VERSION, readJson, runProcess } from '../../fsx.js';
4
+ export async function runDesktopBridgeRestageStage() {
5
+ const skip = (reason) => ({ ok: true, status: 'ok', actions: [reason], blockers: [], warnings: [] });
6
+ if (process.platform !== 'darwin')
7
+ return skip('desktop_bridge_restage_not_macos');
8
+ if (process.env.NODE_TEST_CONTEXT !== undefined)
9
+ return skip('desktop_bridge_restage_skipped_under_tests');
10
+ if (process.env.SKS_SKIP_BRIDGE_RESTAGE === '1')
11
+ return skip('desktop_bridge_restage_disabled');
12
+ const home = os.homedir();
13
+ const plist = path.join(home, 'Library', 'LaunchAgents', 'com.sneakoscope.desktop-bridge.plist');
14
+ if (!(await exists(plist)))
15
+ return skip('desktop_bridge_restage_no_launch_agent');
16
+ const state = await readJson(path.join(home, '.codex', 'sks', 'desktop-bridge-state.json'), null);
17
+ const runningVersion = typeof state?.sks_version === 'string' ? state.sks_version : null;
18
+ const pid = typeof state?.pid === 'number' && Number.isInteger(state.pid) && state.pid > 1 ? state.pid : null;
19
+ if (!pid)
20
+ return skip('desktop_bridge_restage_no_running_bridge');
21
+ try {
22
+ process.kill(pid, 0);
23
+ }
24
+ catch {
25
+ return skip('desktop_bridge_restage_no_running_bridge');
26
+ }
27
+ if (runningVersion === PACKAGE_VERSION)
28
+ return skip('desktop_bridge_restage_already_current');
29
+ const uid = typeof process.getuid === 'function' ? process.getuid() : null;
30
+ if (uid === null)
31
+ return skip('desktop_bridge_restage_no_uid');
32
+ const kick = await runProcess('/bin/launchctl', ['kickstart', '-k', `gui/${uid}/com.sneakoscope.desktop-bridge`], { timeoutMs: 10_000, maxOutputBytes: 16 * 1024 }).catch(() => null);
33
+ if (!kick || kick.code !== 0) {
34
+ return {
35
+ ok: true, status: 'ok',
36
+ actions: [],
37
+ blockers: [],
38
+ warnings: [`desktop_bridge_restage_kickstart_failed:${runningVersion || 'pre-8.6.2'}`]
39
+ };
40
+ }
41
+ return {
42
+ ok: true, status: 'ok',
43
+ actions: [`desktop_bridge_restarted:${runningVersion || 'pre-8.6.2'}:${PACKAGE_VERSION}`],
44
+ blockers: [], warnings: []
45
+ };
46
+ }
@@ -10,6 +10,7 @@ import { enforceRetention } from '../retention.js';
10
10
  import { COMMANDS } from '../../cli/command-registry.js';
11
11
  import { reconcileLegacyManagedGeneration } from '../init/legacy-generation-convergence.js';
12
12
  import { runConfigFastModeNormalizeStage } from './update-migration-state/fast-mode-config.js';
13
+ import { runDesktopBridgeRestageStage } from './update-migration-state/desktop-bridge-restage.js';
13
14
  import { runSessionStateSplitStage } from './update-migration-state/session-state-split.js';
14
15
  import { runHookTrustRefreshStage, runOtherHarnessCleanupStage } from './update-migration-state/simple-stages.js';
15
16
  import { compareSemVer } from './semver.js';
@@ -258,6 +259,11 @@ const UPDATE_MIGRATION_STAGES = [
258
259
  id: 'receipt-rotation',
259
260
  min_from_version: '0.0.0',
260
261
  run: runReceiptRotationStage
262
+ },
263
+ {
264
+ id: 'desktop-bridge-restage',
265
+ min_from_version: '0.0.0',
266
+ run: runDesktopBridgeRestageStage
261
267
  }
262
268
  ];
263
269
  async function runCurrentPublicSurfaceReconcileStage(root) {
@@ -325,7 +331,7 @@ async function runCurrentPublicSurfaceReconcileStage(root) {
325
331
  }
326
332
  };
327
333
  }
328
- async function runUpdateMigrationStages(root, opts = {}) {
334
+ export async function runUpdateMigrationStages(root, opts = {}) {
329
335
  const fromVersion = opts.fromVersion || null;
330
336
  const runs = [];
331
337
  for (const stage of UPDATE_MIGRATION_STAGES) {
@@ -1 +1 @@
1
- export const PACKAGE_VERSION = '9.0.0';
1
+ export const PACKAGE_VERSION = '9.0.2';
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "sneakoscope",
3
3
  "displayName": "ㅅㅋㅅ",
4
- "version": "9.0.0",
4
+ "version": "9.0.2",
5
5
  "description": "Sneakoscope Codex (`sks`) is an open-source trust layer for Codex CLI and ChatGPT Desktop. It coordinates bounded AI coding agents, records machine-verifiable evidence, preserves project memory, and blocks release claims that are not supported by current tests or artifacts. Search visibility outcomes are measured separately; SKS does not promise rankings or traffic.",
6
6
  "type": "module",
7
7
  "homepage": "https://github.com/mandarange/Sneakoscope-Codex#readme",