@gajae-code/coding-agent 0.16.7 → 0.17.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 (321) hide show
  1. package/CHANGELOG.md +180 -1
  2. package/dist/types/capability/fs.d.ts +37 -4
  3. package/dist/types/capability/index.d.ts +19 -0
  4. package/dist/types/capability/types.d.ts +9 -0
  5. package/dist/types/cli/doctor/args.d.ts +36 -0
  6. package/dist/types/cli/doctor/checks.d.ts +18 -0
  7. package/dist/types/cli/doctor/config-repairs.d.ts +38 -0
  8. package/dist/types/cli/doctor/context.d.ts +89 -0
  9. package/dist/types/cli/doctor/files.d.ts +27 -0
  10. package/dist/types/cli/doctor/ids.d.ts +20 -0
  11. package/dist/types/cli/doctor/install-repairs.d.ts +94 -0
  12. package/dist/types/cli/doctor/journal.d.ts +43 -0
  13. package/dist/types/cli/doctor/managed-link.d.ts +39 -0
  14. package/dist/types/cli/doctor/native-probe.d.ts +2 -0
  15. package/dist/types/cli/doctor/permission-repairs.d.ts +25 -0
  16. package/dist/types/cli/doctor/plugin-quarantine.d.ts +77 -0
  17. package/dist/types/cli/doctor/plugin-restore.d.ts +40 -0
  18. package/dist/types/cli/doctor/plugin-targets.d.ts +11 -0
  19. package/dist/types/cli/doctor/probe-client.d.ts +9 -0
  20. package/dist/types/cli/doctor/probe-types.d.ts +18 -0
  21. package/dist/types/cli/doctor/probe.d.ts +3 -0
  22. package/dist/types/cli/doctor/projection-probe.d.ts +2 -0
  23. package/dist/types/cli/doctor/repairs.d.ts +28 -0
  24. package/dist/types/cli/doctor/report.d.ts +27 -0
  25. package/dist/types/cli/doctor/runner.d.ts +4 -0
  26. package/dist/types/cli/doctor/safety.d.ts +10 -0
  27. package/dist/types/cli/doctor/service-repairs.d.ts +8 -0
  28. package/dist/types/cli/doctor/service-targets.d.ts +41 -0
  29. package/dist/types/cli/doctor/types.d.ts +145 -0
  30. package/dist/types/cli/doctor-cli.d.ts +7 -0
  31. package/dist/types/cli/doctor-supervisor.d.ts +121 -0
  32. package/dist/types/cli/doctor-worker.d.ts +29 -0
  33. package/dist/types/cli/install-activation.d.ts +158 -0
  34. package/dist/types/cli/macos-community-app.d.ts +41 -0
  35. package/dist/types/cli/mcp-cli.d.ts +0 -11
  36. package/dist/types/cli/update-cli.d.ts +41 -4
  37. package/dist/types/cli-main.d.ts +174 -0
  38. package/dist/types/cli.d.ts +1 -171
  39. package/dist/types/commands/doctor.d.ts +7 -0
  40. package/dist/types/commands/sdk.d.ts +17 -8
  41. package/dist/types/config/autorouting-tier-map.d.ts +19 -0
  42. package/dist/types/config/file-lock.d.ts +27 -3
  43. package/dist/types/config/model-preset-registry.d.ts +1 -0
  44. package/dist/types/config/model-registry.d.ts +12 -5
  45. package/dist/types/config/model-resolver.d.ts +16 -3
  46. package/dist/types/config/models-config-schema.d.ts +2 -0
  47. package/dist/types/config/settings-schema.d.ts +1 -1
  48. package/dist/types/coordinator-mcp/question-gate-codec.d.ts +1 -1
  49. package/dist/types/coordinator-mcp/session-reaper.d.ts +15 -5
  50. package/dist/types/cursor.d.ts +9 -9
  51. package/dist/types/discovery/agents-md.d.ts +6 -1
  52. package/dist/types/discovery/helpers.d.ts +36 -7
  53. package/dist/types/discovery/index.d.ts +1 -1
  54. package/dist/types/discovery/mcp-json.d.ts +2 -0
  55. package/dist/types/extensibility/extensions/loader.d.ts +22 -1
  56. package/dist/types/extensibility/gjc-plugins/lifecycle.d.ts +14 -1
  57. package/dist/types/extensibility/gjc-plugins/paths.d.ts +2 -1
  58. package/dist/types/extensibility/gjc-plugins/types.d.ts +25 -0
  59. package/dist/types/extensibility/plugins/manager.d.ts +33 -4
  60. package/dist/types/extensibility/plugins/marketplace/cache.d.ts +6 -0
  61. package/dist/types/extensibility/plugins/marketplace/manager.d.ts +186 -0
  62. package/dist/types/extensibility/plugins/marketplace/registry.d.ts +27 -0
  63. package/dist/types/extensibility/plugins/marketplace/source-resolver.d.ts +16 -1
  64. package/dist/types/extensibility/runtime-skill-discovery.d.ts +16 -3
  65. package/dist/types/gjc-runtime/autoresearch-runtime.d.ts +6 -0
  66. package/dist/types/gjc-runtime/launch-worktree.d.ts +53 -0
  67. package/dist/types/gjc-runtime/session-layout.d.ts +8 -0
  68. package/dist/types/gjc-runtime/session-state-lock.d.ts +15 -0
  69. package/dist/types/gjc-runtime/session-state-sidecar.d.ts +13 -0
  70. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +6 -0
  71. package/dist/types/main.d.ts +19 -1
  72. package/dist/types/modes/acp/acp-agent.d.ts +40 -0
  73. package/dist/types/modes/components/agent-dashboard.d.ts +1 -0
  74. package/dist/types/modes/components/model-selector.d.ts +2 -0
  75. package/dist/types/modes/interactive-mode.d.ts +1 -1
  76. package/dist/types/runtime-mcp/config-writer.d.ts +47 -0
  77. package/dist/types/runtime-mcp/policy-json-edit.d.ts +60 -0
  78. package/dist/types/runtime-mcp/startup-policy.d.ts +4 -0
  79. package/dist/types/runtime-mcp/types.d.ts +2 -2
  80. package/dist/types/sdk/acp/adapter.d.ts +5 -1
  81. package/dist/types/sdk/broker/broker.d.ts +51 -0
  82. package/dist/types/sdk/broker/daemon-entry.d.ts +39 -0
  83. package/dist/types/sdk/broker/discovery.d.ts +38 -0
  84. package/dist/types/sdk/broker/doctor-restart.d.ts +79 -0
  85. package/dist/types/sdk/broker/endpoint-authority.d.ts +7 -0
  86. package/dist/types/sdk/broker/ensure.d.ts +14 -0
  87. package/dist/types/sdk/broker/lifecycle.d.ts +2 -0
  88. package/dist/types/sdk/broker/process-incarnation.d.ts +50 -0
  89. package/dist/types/sdk/broker/runtime.d.ts +26 -0
  90. package/dist/types/sdk/broker/session-index.d.ts +13 -4
  91. package/dist/types/sdk/broker/worktree-occupancy.d.ts +14 -0
  92. package/dist/types/sdk/bus/chat-daemon-cli.d.ts +16 -0
  93. package/dist/types/sdk/bus/chat-daemon-control.d.ts +101 -6
  94. package/dist/types/sdk/bus/chat-daemon-runtime.d.ts +16 -0
  95. package/dist/types/sdk/bus/daemon-startup-exclusion.d.ts +43 -0
  96. package/dist/types/sdk/bus/doctor-daemon-restart-client.d.ts +51 -0
  97. package/dist/types/sdk/bus/doctor-daemon-restart.d.ts +39 -0
  98. package/dist/types/sdk/bus/index.d.ts +2 -0
  99. package/dist/types/sdk/bus/notification-service.d.ts +3 -0
  100. package/dist/types/sdk/bus/telegram-daemon-cli.d.ts +11 -0
  101. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +1 -1
  102. package/dist/types/sdk/bus/telegram-daemon-control.d.ts +5 -0
  103. package/dist/types/sdk/bus/telegram-daemon-owner-registry.d.ts +1 -0
  104. package/dist/types/sdk/bus/telegram-daemon.d.ts +36 -5
  105. package/dist/types/sdk/cli/session-cli.d.ts +1 -0
  106. package/dist/types/sdk/host/session-runtime.d.ts +4 -0
  107. package/dist/types/sdk/host/surface-policy.d.ts +3 -1
  108. package/dist/types/sdk/lifecycle/service.d.ts +44 -10
  109. package/dist/types/sdk/prompt-deadline-lease.d.ts +6 -3
  110. package/dist/types/sdk/prompt-deadline-manager.d.ts +2 -2
  111. package/dist/types/sdk/prompt-failure.d.ts +47 -0
  112. package/dist/types/sdk/prompt-status.d.ts +19 -0
  113. package/dist/types/sdk/router/session-router.d.ts +3 -4
  114. package/dist/types/sdk/service-artifact-paths.d.ts +16 -0
  115. package/dist/types/sdk/session-authority.d.ts +9 -0
  116. package/dist/types/sdk/session.d.ts +3 -1
  117. package/dist/types/session/agent-session.d.ts +1 -1
  118. package/dist/types/session/blob-store.d.ts +1 -1
  119. package/dist/types/session/fallback-chain-controller.d.ts +3 -6
  120. package/dist/types/session/internal/managed-session-scope.d.ts +44 -0
  121. package/dist/types/session/primary-control-surface.d.ts +4 -0
  122. package/dist/types/session/session-manager.d.ts +6 -0
  123. package/dist/types/system-prompt.d.ts +6 -0
  124. package/dist/types/tools/ask.d.ts +25 -0
  125. package/dist/types/tools/image-gen.d.ts +0 -4
  126. package/dist/types/tools/skill-discovery.d.ts +7 -5
  127. package/dist/types/tools/ultragoal-ask-guard.d.ts +7 -4
  128. package/dist/types/utils/changelog.d.ts +0 -9
  129. package/dist/types/utils/mupdf-wasm.d.ts +1 -0
  130. package/package.json +9 -8
  131. package/scripts/compile-args.ts +8 -4
  132. package/src/capability/fs.ts +395 -17
  133. package/src/capability/index.ts +273 -10
  134. package/src/capability/types.ts +9 -0
  135. package/src/cli/customize-doctor.ts +18 -12
  136. package/src/cli/doctor/args.ts +236 -0
  137. package/src/cli/doctor/checks.ts +431 -0
  138. package/src/cli/doctor/config-repairs.ts +517 -0
  139. package/src/cli/doctor/context.ts +149 -0
  140. package/src/cli/doctor/files.ts +149 -0
  141. package/src/cli/doctor/ids.ts +115 -0
  142. package/src/cli/doctor/install-repairs.ts +335 -0
  143. package/src/cli/doctor/journal.ts +423 -0
  144. package/src/cli/doctor/managed-link.ts +514 -0
  145. package/src/cli/doctor/native-probe.ts +13 -0
  146. package/src/cli/doctor/permission-repairs.ts +242 -0
  147. package/src/cli/doctor/plugin-quarantine.ts +377 -0
  148. package/src/cli/doctor/plugin-restore.ts +59 -0
  149. package/src/cli/doctor/plugin-targets.ts +373 -0
  150. package/src/cli/doctor/probe-client.ts +195 -0
  151. package/src/cli/doctor/probe-types.ts +21 -0
  152. package/src/cli/doctor/probe.ts +42 -0
  153. package/src/cli/doctor/projection-probe.ts +249 -0
  154. package/src/cli/doctor/repairs.ts +1157 -0
  155. package/src/cli/doctor/report.ts +176 -0
  156. package/src/cli/doctor/runner.ts +342 -0
  157. package/src/cli/doctor/safety.ts +40 -0
  158. package/src/cli/doctor/service-repairs.ts +452 -0
  159. package/src/cli/doctor/service-targets.ts +274 -0
  160. package/src/cli/doctor/types.ts +164 -0
  161. package/src/cli/doctor-cli.ts +62 -0
  162. package/src/cli/doctor-supervisor.ts +571 -0
  163. package/src/cli/doctor-worker.ts +181 -0
  164. package/src/cli/install-activation.ts +666 -0
  165. package/src/cli/macos-community-app.ts +1449 -0
  166. package/src/cli/mcp-cli.ts +11 -21
  167. package/src/cli/nofile-limit.ts +5 -1
  168. package/src/cli/plugin-cli.ts +6 -1
  169. package/src/cli/update-cli.ts +362 -264
  170. package/src/cli-main.ts +517 -0
  171. package/src/cli.ts +64 -433
  172. package/src/commands/autoresearch.ts +11 -5
  173. package/src/commands/completion.ts +1 -1
  174. package/src/commands/doctor.ts +13 -0
  175. package/src/commands/sdk.ts +58 -10
  176. package/src/commands/ultragoal.ts +11 -5
  177. package/src/config/autorouting-tier-map.ts +7 -0
  178. package/src/config/file-lock.ts +736 -71
  179. package/src/config/mcp-schema.json +3 -3
  180. package/src/config/model-preset-registry.ts +1 -0
  181. package/src/config/model-registry.ts +80 -27
  182. package/src/config/model-resolver.ts +33 -4
  183. package/src/config/models-config-schema.ts +2 -0
  184. package/src/config/settings-schema.ts +6 -2
  185. package/src/coordinator-mcp/question-gate-codec.ts +1 -0
  186. package/src/coordinator-mcp/server.ts +534 -51
  187. package/src/coordinator-mcp/session-reaper.ts +64 -8
  188. package/src/cursor.ts +223 -53
  189. package/src/discovery/agents-md.ts +74 -8
  190. package/src/discovery/agents.ts +32 -7
  191. package/src/discovery/builtin.ts +184 -75
  192. package/src/discovery/claude-plugins.ts +85 -17
  193. package/src/discovery/cline.ts +38 -10
  194. package/src/discovery/cursor.ts +19 -6
  195. package/src/discovery/gemini.ts +62 -22
  196. package/src/discovery/github.ts +13 -3
  197. package/src/discovery/helpers.ts +483 -89
  198. package/src/discovery/index.ts +1 -0
  199. package/src/discovery/mcp-json.ts +36 -10
  200. package/src/discovery/opencode.ts +36 -19
  201. package/src/discovery/ssh.ts +11 -8
  202. package/src/discovery/windsurf.ts +12 -3
  203. package/src/exec/bash-executor.ts +10 -3
  204. package/src/exec/isolated-shell.ts +15 -2
  205. package/src/extensibility/extensions/loader.ts +88 -26
  206. package/src/extensibility/gjc-plugins/lifecycle.ts +189 -0
  207. package/src/extensibility/gjc-plugins/paths.ts +4 -1
  208. package/src/extensibility/gjc-plugins/types.ts +27 -0
  209. package/src/extensibility/plugins/manager.ts +200 -22
  210. package/src/extensibility/plugins/marketplace/cache.ts +55 -2
  211. package/src/extensibility/plugins/marketplace/manager.ts +1262 -162
  212. package/src/extensibility/plugins/marketplace/registry.ts +153 -0
  213. package/src/extensibility/plugins/marketplace/source-resolver.ts +116 -10
  214. package/src/extensibility/runtime-skill-discovery.ts +61 -9
  215. package/src/extensibility/skill-management.ts +7 -1
  216. package/src/gjc-runtime/autoresearch-runtime.ts +10 -1
  217. package/src/gjc-runtime/launch-worktree.test.ts +248 -0
  218. package/src/gjc-runtime/launch-worktree.ts +275 -38
  219. package/src/gjc-runtime/repository-binding.ts +26 -1
  220. package/src/gjc-runtime/session-layout.ts +10 -0
  221. package/src/gjc-runtime/session-state-lock.ts +330 -23
  222. package/src/gjc-runtime/session-state-sidecar.ts +328 -80
  223. package/src/gjc-runtime/ultragoal-runtime.ts +10 -1
  224. package/src/harness-control-plane/session-lease.ts +2 -2
  225. package/src/internal-urls/docs-index.generated.ts +28 -26
  226. package/src/main.ts +150 -29
  227. package/src/memories/index.ts +24 -2
  228. package/src/modes/acp/acp-agent.ts +522 -68
  229. package/src/modes/components/agent-dashboard.ts +3 -1
  230. package/src/modes/components/model-selector.ts +28 -19
  231. package/src/modes/components/plugin-settings.ts +2 -1
  232. package/src/modes/controllers/selector-controller.ts +15 -9
  233. package/src/modes/interactive-mode.ts +21 -3
  234. package/src/prompts/system/system-prompt.md +5 -1
  235. package/src/prompts/tools/browser.md +1 -1
  236. package/src/prompts/tools/move-session.md +4 -0
  237. package/src/prompts/tools/read.md +2 -1
  238. package/src/prompts/tools/skill-discovery.md +6 -5
  239. package/src/prompts/tools/task.md +1 -0
  240. package/src/runtime-mcp/config-writer.ts +228 -0
  241. package/src/runtime-mcp/oauth-discovery.ts +114 -14
  242. package/src/runtime-mcp/policy-json-edit.ts +129 -0
  243. package/src/runtime-mcp/startup-policy.ts +14 -0
  244. package/src/runtime-mcp/types.ts +2 -2
  245. package/src/sdk/acp/adapter.ts +27 -12
  246. package/src/sdk/broker/broker.ts +273 -44
  247. package/src/sdk/broker/daemon-entry.ts +119 -0
  248. package/src/sdk/broker/discovery.ts +173 -20
  249. package/src/sdk/broker/doctor-restart.ts +206 -0
  250. package/src/sdk/broker/endpoint-authority.ts +36 -0
  251. package/src/sdk/broker/ensure.ts +49 -14
  252. package/src/sdk/broker/lifecycle.ts +174 -42
  253. package/src/sdk/broker/process-incarnation.ts +86 -2
  254. package/src/sdk/broker/runtime.ts +145 -21
  255. package/src/sdk/broker/session-index.ts +136 -22
  256. package/src/sdk/broker/spawn-substrate.ts +4 -1
  257. package/src/sdk/broker/transport.ts +5 -0
  258. package/src/sdk/broker/worktree-occupancy.ts +21 -1
  259. package/src/sdk/bus/chat-daemon-cli.ts +63 -0
  260. package/src/sdk/bus/chat-daemon-control.ts +260 -64
  261. package/src/sdk/bus/chat-daemon-runtime.ts +141 -25
  262. package/src/sdk/bus/daemon-startup-exclusion.ts +119 -0
  263. package/src/sdk/bus/doctor-daemon-restart-client.ts +291 -0
  264. package/src/sdk/bus/doctor-daemon-restart.ts +92 -0
  265. package/src/sdk/bus/index.ts +252 -51
  266. package/src/sdk/bus/kind-aware-reconciliation.ts +76 -41
  267. package/src/sdk/bus/notification-service.ts +21 -3
  268. package/src/sdk/bus/reconciliation-store.ts +17 -11
  269. package/src/sdk/bus/telegram-daemon-cli.ts +91 -5
  270. package/src/sdk/bus/telegram-daemon-contract.ts +1 -1
  271. package/src/sdk/bus/telegram-daemon-control.ts +47 -0
  272. package/src/sdk/bus/telegram-daemon-owner-registry.ts +1 -1
  273. package/src/sdk/bus/telegram-daemon.ts +178 -13
  274. package/src/sdk/cli/session-cli.ts +100 -12
  275. package/src/sdk/host/host.ts +2 -7
  276. package/src/sdk/host/session-runtime.test.ts +329 -8
  277. package/src/sdk/host/session-runtime.ts +202 -50
  278. package/src/sdk/host/surface-policy.ts +8 -1
  279. package/src/sdk/lifecycle/service.ts +199 -22
  280. package/src/sdk/mcp/server.ts +1 -1
  281. package/src/sdk/prompt-deadline-lease.ts +12 -4
  282. package/src/sdk/prompt-deadline-manager.ts +5 -7
  283. package/src/sdk/prompt-failure.ts +148 -0
  284. package/src/sdk/prompt-status.ts +30 -1
  285. package/src/sdk/prompt-watchdog.ts +11 -3
  286. package/src/sdk/protocol/operation-inventory.generated.json +18 -0
  287. package/src/sdk/protocol/operation-registry.ts +20 -8
  288. package/src/sdk/router/session-router.ts +8 -3
  289. package/src/sdk/service-artifact-paths.ts +26 -0
  290. package/src/sdk/session-authority.ts +10 -0
  291. package/src/sdk/session.ts +361 -194
  292. package/src/session/agent-session.ts +398 -93
  293. package/src/session/blob-store.ts +9 -9
  294. package/src/session/fallback-chain-controller.ts +3 -7
  295. package/src/session/internal/managed-session-scope.ts +445 -29
  296. package/src/session/internal/managed-session-storage.ts +38 -6
  297. package/src/session/primary-control-surface.ts +12 -0
  298. package/src/session/session-manager.ts +11 -3
  299. package/src/session/session-storage.ts +1 -1
  300. package/src/session-import/codex.ts +6 -1
  301. package/src/session-import/redact.ts +7 -0
  302. package/src/skill-state/workflow-mutation-guard.ts +645 -39
  303. package/src/slash-commands/builtin-registry.ts +7 -3
  304. package/src/system-prompt.ts +8 -0
  305. package/src/task/executor.ts +36 -3
  306. package/src/task/types.ts +27 -5
  307. package/src/tools/ask.ts +114 -34
  308. package/src/tools/browser/actions.ts +2 -2
  309. package/src/tools/browser/tab-worker.ts +11 -8
  310. package/src/tools/descriptors.ts +6 -1
  311. package/src/tools/fetch.ts +7 -2
  312. package/src/tools/image-gen.ts +33 -34
  313. package/src/tools/read.ts +78 -32
  314. package/src/tools/skill-discovery.ts +17 -7
  315. package/src/tools/tool-catalog.generated.ts +8 -8
  316. package/src/tools/ultragoal-ask-guard.ts +16 -26
  317. package/src/utils/changelog.ts +7 -1
  318. package/src/utils/git.ts +3 -1
  319. package/src/utils/markit.ts +24 -2
  320. package/src/utils/mupdf-wasm.ts +26 -0
  321. package/src/web/search/providers/openai-compatible.ts +191 -14
@@ -0,0 +1,517 @@
1
+ import * as path from "node:path";
2
+ import type { NativeExactFileIdentity } from "@gajae-code/natives";
3
+ import { YAML } from "bun";
4
+ import { applyAtomicYamlPatchesWithCurrent, atomicYamlPathHash, type CasReceipt } from "../../config/atomic-yaml-patch";
5
+ import {
6
+ type RestoreMCPBooleanResult,
7
+ readMCPConfigFile,
8
+ restoreMCPBooleanExact,
9
+ updateMCPBooleanExact,
10
+ } from "../../runtime-mcp/config-writer";
11
+ import { readMcpBooleanField } from "../../runtime-mcp/policy-json-edit";
12
+ import { computeAutoloadStatus } from "../../runtime-mcp/startup-policy";
13
+ import { type DoctorFileObservation, readDoctorFile } from "./files";
14
+ import { DoctorJournal, DoctorJournalCreateError } from "./journal";
15
+ import { authorizePosixConfigPermission } from "./safety";
16
+ import type { DoctorCheck, DoctorRepair, DoctorScope } from "./types";
17
+
18
+ export interface DoctorConfigRepairRequest {
19
+ readonly filePath: string;
20
+ readonly rootPath: string;
21
+ readonly scope: DoctorScope;
22
+ readonly targetId: string;
23
+ readonly repairId: string;
24
+ readonly runId: string;
25
+ readonly mode: "dry-run" | "fix";
26
+ readonly authorization?: readonly string[];
27
+ readonly journalRoot: string;
28
+ readonly expected: {
29
+ readonly raw: string;
30
+ readonly observedValue?: unknown;
31
+ readonly identity: NativeExactFileIdentity;
32
+ };
33
+ readonly kind: "skill" | "mcp";
34
+ readonly schemaKey?: "skills.enabled" | "skills.enableSkillCommands";
35
+ readonly serverName?: string;
36
+ readonly field?: "autoload" | "enabled";
37
+ readonly value: boolean;
38
+ readonly collectAfterChecks: (input: {
39
+ readonly filePath: string;
40
+ readonly kind: "skill" | "mcp";
41
+ readonly value: boolean;
42
+ }) => Promise<readonly DoctorCheck[]>;
43
+ }
44
+
45
+ export interface DoctorConfigRepairResult {
46
+ readonly repair: DoctorRepair;
47
+ readonly receipt?: CasReceipt;
48
+ readonly before?: unknown;
49
+ readonly after?: unknown;
50
+ readonly journalPath?: string;
51
+ readonly afterChecks?: readonly DoctorCheck[];
52
+ }
53
+
54
+ function isRecord(value: unknown): value is Record<string, unknown> {
55
+ return !!value && typeof value === "object" && !Array.isArray(value);
56
+ }
57
+
58
+ function getAt(root: Record<string, unknown>, dotted: string): unknown {
59
+ let current: unknown = root;
60
+ for (const segment of dotted.split(".")) {
61
+ if (!isRecord(current)) return undefined;
62
+ current = current[segment];
63
+ }
64
+ return current;
65
+ }
66
+
67
+ function sameIdentity(left: NativeExactFileIdentity, right: NativeExactFileIdentity): boolean {
68
+ return (
69
+ left.dev === right.dev &&
70
+ left.ino === right.ino &&
71
+ left.size === right.size &&
72
+ left.mtimeNs === right.mtimeNs &&
73
+ left.parentDev === right.parentDev &&
74
+ left.parentIno === right.parentIno &&
75
+ left.nlink === right.nlink
76
+ );
77
+ }
78
+
79
+ function boundedJournalValue(value: unknown): Record<string, unknown> {
80
+ if (typeof value === "boolean") return { type: "boolean", value };
81
+ if (value === null || value === undefined) return { type: "null" };
82
+ return { type: Array.isArray(value) ? "array" : typeof value === "object" ? "object" : typeof value };
83
+ }
84
+
85
+ function repairBase(request: DoctorConfigRepairRequest, state: DoctorRepair["state"]): DoctorRepair {
86
+ return {
87
+ id: request.repairId,
88
+ targetId: request.targetId,
89
+ riskClasses: ["config-change"],
90
+ authorization: request.authorization ?? [],
91
+ readiness: [],
92
+ candidates: [],
93
+ preconditions: ["captured file and parent identity", "compare-and-swap", "independent post-read"],
94
+ state,
95
+ sideEffectStarted: false,
96
+ beforeCheckIds: [],
97
+ afterCheckIds: [],
98
+ restartRequired: true,
99
+ nonrollbackableEffects: [],
100
+ };
101
+ }
102
+
103
+ function observationError(observation: DoctorFileObservation): string | undefined {
104
+ return observation.status === "read" ? undefined : `file_${observation.status}`;
105
+ }
106
+
107
+ function selectedCheck(checks: readonly DoctorCheck[], request: DoctorConfigRepairRequest): DoctorCheck | undefined {
108
+ const field = request.kind === "skill" ? "enabled" : request.field!;
109
+ return checks.find(
110
+ check =>
111
+ check.targetId === request.targetId &&
112
+ check.execution === "completed" &&
113
+ check.evidenceLevel === "observed" &&
114
+ (check.evidence as Record<string, unknown>)[field] === request.value,
115
+ );
116
+ }
117
+
118
+ function startupBlockerCheck(request: DoctorConfigRepairRequest): DoctorCheck {
119
+ return {
120
+ id: `${request.targetId}:startup-blocker`,
121
+ targetId: request.targetId,
122
+ execution: "completed",
123
+ health: "error",
124
+ reasonCode: "mcp_startup_blocker",
125
+ evidenceLevel: "observed",
126
+ dependsOn: [],
127
+ evidence: { schemaLocation: request.field, readiness: ["unsupported"] },
128
+ remediationIds: [],
129
+ };
130
+ }
131
+
132
+ export async function applyDoctorConfigRepair(request: DoctorConfigRepairRequest): Promise<DoctorConfigRepairResult> {
133
+ const base = repairBase(request, "preparing");
134
+ if (!request.authorization?.includes("config-change")) {
135
+ return { repair: { ...base, state: "blocked", reasonCode: "authorization_missing" } };
136
+ }
137
+ if (
138
+ typeof request.value !== "boolean" ||
139
+ (request.kind === "skill" &&
140
+ (request.repairId !== "config.set-validated" ||
141
+ (request.schemaKey !== "skills.enabled" && request.schemaKey !== "skills.enableSkillCommands"))) ||
142
+ (request.kind === "mcp" &&
143
+ (request.repairId !== "mcp.set-startup-policy" ||
144
+ !request.serverName ||
145
+ (request.field !== "enabled" && request.field !== "autoload")))
146
+ )
147
+ return { repair: { ...base, state: "blocked", reasonCode: "invalid_config_request" } };
148
+ if (!request.collectAfterChecks)
149
+ return { repair: { ...base, state: "blocked", reasonCode: "after_checks_missing" } };
150
+ const resolved = path.resolve(request.filePath);
151
+ const root = path.resolve(request.rootPath);
152
+ if (resolved !== root && !resolved.startsWith(`${root}${path.sep}`)) {
153
+ return { repair: { ...base, state: "blocked", reasonCode: "target_outside_root" } };
154
+ }
155
+ if (request.mode === "dry-run") return { repair: { ...base, state: "planned" } };
156
+
157
+ let observation: DoctorFileObservation;
158
+ try {
159
+ observation = await readDoctorFile(request.filePath);
160
+ if (observation.status !== "read")
161
+ return { repair: { ...base, state: "blocked", reasonCode: observationError(observation) } };
162
+ if (
163
+ !sameIdentity(observation.exactIdentity, request.expected.identity) ||
164
+ observation.text !== request.expected.raw
165
+ ) {
166
+ return { repair: { ...base, state: "blocked", reasonCode: "identity_recheck_required" } };
167
+ }
168
+ const safe = await authorizePosixConfigPermission(request.filePath, request.expected.identity);
169
+ if (!safe.safe) return { repair: { ...base, state: "blocked", reasonCode: safe.reasonCode ?? "unsafe_target" } };
170
+ } catch (error) {
171
+ return {
172
+ repair: { ...base, state: "blocked", reasonCode: error instanceof Error ? error.name : "preflight_failed" },
173
+ };
174
+ }
175
+ let parsed: unknown;
176
+ let before: unknown;
177
+ if (request.kind === "skill") {
178
+ try {
179
+ parsed = YAML.parse(observation.text);
180
+ } catch {
181
+ return { repair: { ...base, state: "blocked", reasonCode: "malformed_config" } };
182
+ }
183
+ if (!isRecord(parsed) || (parsed.skills !== undefined && !isRecord(parsed.skills)))
184
+ return { repair: { ...base, state: "blocked", reasonCode: "unsupported_target_shape" } };
185
+ before = getAt(parsed, request.schemaKey!);
186
+ } else {
187
+ // Lossless JSON DOM read: rejects duplicate keys along the selected
188
+ // server/field path instead of silently resolving them (JSON.parse would
189
+ // pick "last one wins", hiding a real authoring bug).
190
+ const read = readMcpBooleanField(observation.text, request.serverName!, request.field!);
191
+ if (read.status === "malformed") return { repair: { ...base, state: "blocked", reasonCode: "malformed_config" } };
192
+ if (read.status === "duplicate_key")
193
+ return { repair: { ...base, state: "blocked", reasonCode: `duplicate_key_${read.path}` } };
194
+ if (read.status === "not_found")
195
+ return { repair: { ...base, state: "blocked", reasonCode: "unsupported_target_shape" } };
196
+ before = read.value;
197
+ }
198
+ if (before === request.value) {
199
+ let afterChecks: readonly DoctorCheck[];
200
+ try {
201
+ afterChecks = await request.collectAfterChecks({
202
+ filePath: request.filePath,
203
+ kind: request.kind,
204
+ value: request.value,
205
+ });
206
+ } catch {
207
+ return { repair: { ...base, state: "blocked", reasonCode: "after_checks_failed" }, before };
208
+ }
209
+ if (!selectedCheck(afterChecks, request))
210
+ return {
211
+ repair: { ...base, state: "blocked", reasonCode: "target_after_check_missing" },
212
+ before,
213
+ afterChecks,
214
+ };
215
+ let desiredStartupStateAchieved: boolean | undefined;
216
+ if (request.kind === "mcp") {
217
+ const config = await readMCPConfigFile(request.filePath);
218
+ const server = config.mcpServers?.[request.serverName!];
219
+ if (server) {
220
+ const status = computeAutoloadStatus(request.serverName!, server, new Set(config.disabledServers ?? []));
221
+ desiredStartupStateAchieved = request.value ? status === "autoload" : status !== "autoload";
222
+ }
223
+ }
224
+ const finalChecks =
225
+ desiredStartupStateAchieved === false && request.kind === "mcp"
226
+ ? [...afterChecks, startupBlockerCheck(request)]
227
+ : afterChecks;
228
+ return {
229
+ repair: {
230
+ ...base,
231
+ state: "not_needed",
232
+ reasonCode: desiredStartupStateAchieved === false ? "startup_blocker" : undefined,
233
+ outcome: { mutationVerified: true, desiredStartupStateAchieved },
234
+ },
235
+ before,
236
+ after: request.value,
237
+ afterChecks: finalChecks,
238
+ };
239
+ }
240
+
241
+ let journal: DoctorJournal;
242
+ try {
243
+ journal = await DoctorJournal.create(request.journalRoot, request.runId);
244
+ } catch (error) {
245
+ const started = error instanceof DoctorJournalCreateError ? error.sideEffectStarted : true;
246
+ return {
247
+ repair: {
248
+ ...base,
249
+ state: started ? "uncertain" : "blocked",
250
+ sideEffectStarted: started,
251
+ reasonCode: error instanceof DoctorJournalCreateError ? error.reasonCode : "journal_unavailable",
252
+ },
253
+ };
254
+ }
255
+ let receipt: CasReceipt | undefined;
256
+ let mcpAfterIdentity: NativeExactFileIdentity | undefined;
257
+ let journalClosed = false;
258
+ const closeJournal = () => {
259
+ if (journalClosed) return;
260
+ journal.close();
261
+ journalClosed = true;
262
+ };
263
+ try {
264
+ await journal.append({
265
+ repairId: request.repairId,
266
+ targetId: request.targetId,
267
+ phase: "before",
268
+ before: boundedJournalValue(before),
269
+ });
270
+ await journal.append({ repairId: request.repairId, targetId: request.targetId, phase: "applying" });
271
+ if (request.kind === "mcp") {
272
+ let result: Awaited<ReturnType<typeof updateMCPBooleanExact>>;
273
+ try {
274
+ result = await updateMCPBooleanExact(request.filePath, request.serverName!, request.field!, request.value, {
275
+ raw: request.expected.raw,
276
+ identity: request.expected.identity,
277
+ });
278
+ } catch (error) {
279
+ // A duplicate-key/malformed/not-found shape error is detected before
280
+ // updateMCPBooleanExact ever opens a temp file or replaces the
281
+ // destination, so nothing was written: report it as a non-mutating
282
+ // blocked outcome, not an uncertain rollback candidate.
283
+ const message = error instanceof Error ? error.message : "";
284
+ const duplicateMatch = message.match(/duplicate key at (.+)$/);
285
+ const preWrite =
286
+ duplicateMatch !== null || message.includes("not valid JSON") || message.includes("server not found");
287
+ if (preWrite) {
288
+ await journal.append({
289
+ repairId: request.repairId,
290
+ targetId: request.targetId,
291
+ phase: "failed",
292
+ outcome: "blocked_no_write",
293
+ });
294
+ const reasonCode = duplicateMatch
295
+ ? `duplicate_key_${duplicateMatch[1]}`
296
+ : message.includes("not valid JSON")
297
+ ? "malformed_config"
298
+ : "unsupported_target_shape";
299
+ return {
300
+ repair: { ...base, state: "blocked", sideEffectStarted: false, reasonCode },
301
+ before,
302
+ journalPath: journal.path,
303
+ };
304
+ }
305
+ throw error;
306
+ }
307
+ mcpAfterIdentity = result.afterIdentity;
308
+ if (!result.changed)
309
+ return {
310
+ repair: { ...base, state: "not_needed", sideEffectStarted: true },
311
+ before: result.beforeValue,
312
+ after: result.afterValue,
313
+ journalPath: journal.path,
314
+ };
315
+ } else {
316
+ const expectedRoot = YAML.parse(request.expected.raw);
317
+ if (!isRecord(expectedRoot)) throw new Error("malformed YAML root");
318
+ const expectedHash = atomicYamlPathHash(expectedRoot, request.schemaKey!);
319
+ // The supplied root is deliberately ignored: only a bounded no-follow
320
+ // re-read carries the exact-identity evidence this CAS is bound to.
321
+ receipt = await applyAtomicYamlPatchesWithCurrent(request.filePath, async () => {
322
+ const reread = await readDoctorFile(request.filePath);
323
+ if (
324
+ reread.status !== "read" ||
325
+ !sameIdentity(reread.exactIdentity, request.expected.identity) ||
326
+ reread.text !== request.expected.raw
327
+ )
328
+ throw new Error("config compare-and-swap conflict");
329
+ return [
330
+ {
331
+ path: request.schemaKey!,
332
+ op: "set",
333
+ value: request.value,
334
+ expected: { path: request.schemaKey!, hash: expectedHash },
335
+ },
336
+ ];
337
+ });
338
+ }
339
+ const afterRead = await readDoctorFile(request.filePath);
340
+ if (afterRead.status !== "read") throw new Error("postcheck_read_failed");
341
+ let after: unknown;
342
+ if (request.kind === "skill") {
343
+ const afterParsed = YAML.parse(afterRead.text);
344
+ after = getAt(isRecord(afterParsed) ? afterParsed : {}, request.schemaKey!);
345
+ } else {
346
+ const afterFieldRead = readMcpBooleanField(afterRead.text, request.serverName!, request.field!);
347
+ if (afterFieldRead.status !== "ok") throw new Error(`postcheck_${afterFieldRead.status}`);
348
+ after = afterFieldRead.value;
349
+ }
350
+ if (after !== request.value) throw new Error("postcheck failed");
351
+ let desiredStartupStateAchieved: boolean | undefined;
352
+ if (request.kind === "mcp") {
353
+ const config = await readMCPConfigFile(request.filePath);
354
+ const status = computeAutoloadStatus(
355
+ request.serverName!,
356
+ config.mcpServers![request.serverName!]!,
357
+ new Set(config.disabledServers ?? []),
358
+ );
359
+ desiredStartupStateAchieved = request.value ? status === "autoload" : status !== "autoload";
360
+ }
361
+ const collectedAfterChecks = await request.collectAfterChecks({
362
+ filePath: request.filePath,
363
+ kind: request.kind,
364
+ value: request.value,
365
+ });
366
+ const afterChecks =
367
+ desiredStartupStateAchieved === false && request.kind === "mcp"
368
+ ? [...collectedAfterChecks, startupBlockerCheck(request)]
369
+ : collectedAfterChecks;
370
+ if (!selectedCheck(afterChecks, request)) throw new Error("target_after_check_missing");
371
+ await journal.append({
372
+ repairId: request.repairId,
373
+ targetId: request.targetId,
374
+ phase: "verified",
375
+ after: boundedJournalValue(after),
376
+ outcome: desiredStartupStateAchieved === false ? "verified_with_blocker" : "verified",
377
+ });
378
+ try {
379
+ closeJournal();
380
+ } catch {
381
+ return {
382
+ repair: { ...base, state: "uncertain", sideEffectStarted: true, reasonCode: "journal_close_failed" },
383
+ before,
384
+ after,
385
+ journalPath: journal.path,
386
+ afterChecks,
387
+ };
388
+ }
389
+ return {
390
+ repair: {
391
+ ...base,
392
+ state: "verified",
393
+ sideEffectStarted: true,
394
+ reasonCode: desiredStartupStateAchieved === false ? "startup_blocker" : undefined,
395
+ outcome: { mutationVerified: true, desiredStartupStateAchieved },
396
+ },
397
+ receipt,
398
+ before,
399
+ after,
400
+ journalPath: journal.path,
401
+ afterChecks,
402
+ };
403
+ } catch (error) {
404
+ if (receipt) {
405
+ const restored = await receipt.restore().catch(() => ({ status: "not-restorable" as const }));
406
+ const state: DoctorRepair["state"] =
407
+ restored.status === "restored"
408
+ ? "rolled_back"
409
+ : restored.status === "conflict"
410
+ ? "rollback_conflict"
411
+ : "uncertain";
412
+ try {
413
+ await journal.append({
414
+ repairId: request.repairId,
415
+ targetId: request.targetId,
416
+ phase: state === "rolled_back" ? "failed" : "uncertain",
417
+ outcome: state,
418
+ });
419
+ } catch {
420
+ return {
421
+ repair: { ...base, state: "uncertain", sideEffectStarted: true, reasonCode: "journal_failure" },
422
+ before,
423
+ journalPath: journal.path,
424
+ };
425
+ }
426
+ return {
427
+ repair: {
428
+ ...base,
429
+ state,
430
+ sideEffectStarted: true,
431
+ reasonCode: error instanceof Error ? error.name : "repair_failed",
432
+ },
433
+ before,
434
+ journalPath: journal.path,
435
+ };
436
+ }
437
+ if (mcpAfterIdentity) {
438
+ // A thrown error here is a genuine unknown failure, distinct from the
439
+ // typed "conflict" outcome (an independent editor already resolved the
440
+ // field). Folding both into "conflict" would misreport an unresolved
441
+ // rollback as already-handled, so unknown throws map to "uncertain".
442
+ let rollback: RestoreMCPBooleanResult;
443
+ try {
444
+ rollback = await restoreMCPBooleanExact(
445
+ request.filePath,
446
+ request.serverName!,
447
+ request.field!,
448
+ request.expected.raw,
449
+ { identity: mcpAfterIdentity, currentValue: request.value },
450
+ );
451
+ } catch {
452
+ rollback = { status: "failed", reason: "unknown_rollback_error" };
453
+ }
454
+ const state: DoctorRepair["state"] =
455
+ rollback.status === "restored"
456
+ ? "rolled_back"
457
+ : rollback.status === "conflict"
458
+ ? "rollback_conflict"
459
+ : "uncertain";
460
+ try {
461
+ await journal.append({
462
+ repairId: request.repairId,
463
+ targetId: request.targetId,
464
+ phase: state === "rolled_back" ? "failed" : "uncertain",
465
+ outcome: state,
466
+ });
467
+ } catch {
468
+ return {
469
+ repair: { ...base, state: "uncertain", sideEffectStarted: true, reasonCode: "journal_failure" },
470
+ before,
471
+ journalPath: journal.path,
472
+ };
473
+ }
474
+ return {
475
+ repair: {
476
+ ...base,
477
+ state,
478
+ sideEffectStarted: true,
479
+ reasonCode: error instanceof Error ? error.name : "repair_failed",
480
+ },
481
+ before,
482
+ journalPath: journal.path,
483
+ };
484
+ }
485
+ try {
486
+ await journal.append({
487
+ repairId: request.repairId,
488
+ targetId: request.targetId,
489
+ phase: "uncertain",
490
+ outcome: "uncertain",
491
+ });
492
+ } catch {
493
+ return {
494
+ repair: { ...base, state: "uncertain", sideEffectStarted: true, reasonCode: "journal_failure" },
495
+ before,
496
+ journalPath: journal.path,
497
+ };
498
+ }
499
+ return {
500
+ repair: {
501
+ ...base,
502
+ state: "uncertain",
503
+ sideEffectStarted: true,
504
+ reasonCode: error instanceof Error ? error.name : "repair_failed",
505
+ },
506
+ before,
507
+ journalPath: journal.path,
508
+ };
509
+ } finally {
510
+ try {
511
+ closeJournal();
512
+ } catch {
513
+ // A terminal result has already been selected; preserve it while
514
+ // ensuring the worker is not allowed to throw from finally.
515
+ }
516
+ }
517
+ }
@@ -0,0 +1,149 @@
1
+ import * as path from "node:path";
2
+ import { fileURLToPath } from "node:url";
3
+ import { getAgentDir, getMCPConfigPath, getProjectAgentDir } from "@gajae-code/utils/dirs";
4
+ import type { LocalRestorePlanV1 } from "../../extensibility/gjc-plugins/types";
5
+ import type { MarketplaceRestorePlanV1 } from "../../extensibility/plugins/marketplace/manager";
6
+ import type { DoctorOptions } from "./args";
7
+ import type { DoctorFileObservation } from "./files";
8
+ import { type ResolvedRoot, resolveDoctorRoot } from "./ids";
9
+ import type { InstallRestoreDescriptorV1 } from "./install-repairs";
10
+ import type { ManagedLinkDescriptor } from "./managed-link";
11
+ import type { PluginQuarantinePlan } from "./plugin-quarantine";
12
+ import type { DoctorServiceObservation } from "./service-targets";
13
+ import type { DoctorScope } from "./types";
14
+
15
+ export interface DoctorConfigSource {
16
+ readonly scope: DoctorScope;
17
+ readonly root: ResolvedRoot;
18
+ readonly configPath: string;
19
+ readonly mcpPath: string;
20
+ }
21
+
22
+ export interface DoctorSettingTarget {
23
+ readonly kind: "config";
24
+ readonly targetId: string;
25
+ readonly source: DoctorConfigSource;
26
+ readonly schemaKey: "skills.enabled" | "skills.enableSkillCommands";
27
+ readonly observation: DoctorFileObservation;
28
+ readonly beforeValue: unknown;
29
+ }
30
+
31
+ export interface DoctorMcpTarget {
32
+ readonly kind: "mcp";
33
+ readonly targetId: string;
34
+ readonly source: DoctorConfigSource;
35
+ readonly serverName: string;
36
+ readonly field: "autoload" | "enabled";
37
+ readonly definitionIsMapping: boolean;
38
+ readonly observation: DoctorFileObservation;
39
+ readonly beforeValue: unknown;
40
+ }
41
+
42
+ export interface DoctorPermissionTarget {
43
+ readonly kind: "permission";
44
+ readonly targetId: string;
45
+ readonly source: DoctorConfigSource;
46
+ readonly observation: DoctorFileObservation;
47
+ }
48
+
49
+ export interface DoctorInstallTarget {
50
+ readonly kind: "binary" | "link";
51
+ readonly targetId: string;
52
+ readonly filePath: string;
53
+ readonly root: ResolvedRoot;
54
+ readonly installDescriptor?: InstallRestoreDescriptorV1;
55
+ readonly linkDescriptor?: ManagedLinkDescriptor;
56
+ }
57
+
58
+ export interface DoctorPluginTarget {
59
+ readonly kind: "plugin";
60
+ readonly targetId: string;
61
+ readonly family: "gjc" | "npm" | "marketplace";
62
+ readonly scope: DoctorScope;
63
+ readonly name: string;
64
+ readonly root: ResolvedRoot;
65
+ readonly quarantinePlan?: PluginQuarantinePlan;
66
+ /** Original read-only restore observation; apply is authorized against this exact plan, never a fresh one. */
67
+ readonly restorePlan?: LocalRestorePlanV1;
68
+ /** Original read-only private-marketplace restore observation; apply authorizes against this exact plan. */
69
+ readonly marketplaceRestorePlan?: MarketplaceRestorePlanV1;
70
+ /** Immutable stored-source identity a restore `--ref` must equal; absent means the source is unpinnable. */
71
+ readonly restoreRef?: string;
72
+ /** Stable artifact-tree digest the restore `--sha256` must equal; recomputed by the lifecycle from the resolved source. */
73
+ readonly restoreArtifactDigest?: string;
74
+ readonly registryPath?: string;
75
+ }
76
+
77
+ export interface DoctorServiceTarget {
78
+ readonly kind: "service" | "artifact";
79
+ readonly targetId: string;
80
+ readonly service: "broker" | "telegram" | "discord" | "slack";
81
+ readonly root: ResolvedRoot;
82
+ readonly slot?: "discovery" | "owner-lock" | "startup-marker";
83
+ readonly observation: DoctorServiceObservation;
84
+ }
85
+
86
+ export type DoctorTarget =
87
+ | DoctorSettingTarget
88
+ | DoctorMcpTarget
89
+ | DoctorPermissionTarget
90
+ | DoctorInstallTarget
91
+ | DoctorPluginTarget
92
+ | DoctorServiceTarget;
93
+
94
+ export interface DoctorContext {
95
+ readonly options: DoctorOptions;
96
+ readonly cwd: string;
97
+ readonly agentRoot: ResolvedRoot;
98
+ readonly installRoot: ResolvedRoot;
99
+ readonly sources: readonly DoctorConfigSource[];
100
+ readonly targets: Map<string, DoctorTarget>;
101
+ readonly compiled: boolean;
102
+ readonly executable: string;
103
+ readonly cliPath?: string;
104
+ readonly deadline: number;
105
+ }
106
+
107
+ /** Resolve only existing product directory conventions; this never initializes settings. */
108
+ export function createDoctorContext(options: DoctorOptions): DoctorContext {
109
+ const cwd = path.resolve(options.cwd ?? process.cwd());
110
+ const agentDirectory = getAgentDir();
111
+ const userRoot = resolveDoctorRoot("config-user", agentDirectory);
112
+ const projectRoot = resolveDoctorRoot("config-project", getProjectAgentDir(cwd));
113
+ const sources: DoctorConfigSource[] = [
114
+ {
115
+ scope: "user",
116
+ root: userRoot,
117
+ configPath: path.join(userRoot.locator, "config.yml"),
118
+ mcpPath: getMCPConfigPath("user", cwd, agentDirectory),
119
+ },
120
+ {
121
+ scope: "project",
122
+ root: projectRoot,
123
+ configPath: path.join(projectRoot.locator, "config.yml"),
124
+ mcpPath: getMCPConfigPath("project", cwd, agentDirectory),
125
+ },
126
+ ];
127
+ // A source caller cannot turn itself into a compiled installation by setting an environment flag.
128
+ const compiled = /(?:\/\$bunfs\/|\/~BUN\/|\/%7EBUN\/)/i.test(import.meta.url);
129
+ const cliPath = compiled ? undefined : fileURLToPath(new URL("../../cli.ts", import.meta.url));
130
+ const executable = compiled ? process.execPath : (cliPath ?? process.execPath);
131
+ const installDirectory = compiled
132
+ ? path.dirname(process.execPath)
133
+ : path.resolve(path.dirname(cliPath ?? process.execPath), "..");
134
+ return {
135
+ options,
136
+ cwd,
137
+ agentRoot: resolveDoctorRoot("agent", agentDirectory),
138
+ installRoot: resolveDoctorRoot("install", installDirectory),
139
+ sources: options.scope ? sources.filter(source => source.scope === options.scope) : sources,
140
+ targets: new Map(),
141
+ compiled,
142
+ executable,
143
+ cliPath,
144
+ deadline: Math.min(
145
+ options.deadline ?? Number.POSITIVE_INFINITY,
146
+ performance.now() + (options.timeoutMs ?? 15_000),
147
+ ),
148
+ };
149
+ }