@tacuchi/agent-workflow-cli 22.4.2 → 24.0.0

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 (295) hide show
  1. package/README.md +39 -3
  2. package/dist/adapters/git-cli.js +59 -17
  3. package/dist/adapters/git-cli.js.map +1 -1
  4. package/dist/adapters/node-file-system.js +55 -1
  5. package/dist/adapters/node-file-system.js.map +1 -1
  6. package/dist/adapters/postgres-readonly-tools.js +277 -0
  7. package/dist/adapters/postgres-readonly-tools.js.map +1 -0
  8. package/dist/application/artifacts-service.js +2 -2
  9. package/dist/application/artifacts-service.js.map +1 -1
  10. package/dist/application/capability/dispatcher.js +2 -2
  11. package/dist/application/capability/dispatcher.js.map +1 -1
  12. package/dist/application/capability/installed-inventory.js +2 -2
  13. package/dist/application/capability/installed-inventory.js.map +1 -1
  14. package/dist/application/capability/readiness.js +1 -1
  15. package/dist/application/capability/readiness.js.map +1 -1
  16. package/dist/application/check-branch-service.js +2 -2
  17. package/dist/application/check-branch-service.js.map +1 -1
  18. package/dist/application/checkpoint/files-touched.js +271 -0
  19. package/dist/application/checkpoint/files-touched.js.map +1 -0
  20. package/dist/application/checkpoint/markdown.js +61 -8
  21. package/dist/application/checkpoint/markdown.js.map +1 -1
  22. package/dist/application/checkpoint/state-reader.js +8 -2
  23. package/dist/application/checkpoint/state-reader.js.map +1 -1
  24. package/dist/application/checkpoint-write-service.js +16 -5
  25. package/dist/application/checkpoint-write-service.js.map +1 -1
  26. package/dist/application/claims-ledger.js +330 -0
  27. package/dist/application/claims-ledger.js.map +1 -0
  28. package/dist/application/claims-recovery.js +338 -0
  29. package/dist/application/claims-recovery.js.map +1 -0
  30. package/dist/application/database-mcp-server.js +168 -0
  31. package/dist/application/database-mcp-server.js.map +1 -0
  32. package/dist/application/database-mcp-stdio.js +137 -0
  33. package/dist/application/database-mcp-stdio.js.map +1 -0
  34. package/dist/application/database-tool-catalog.js +226 -0
  35. package/dist/application/database-tool-catalog.js.map +1 -0
  36. package/dist/application/dev-only-services.js +154 -44
  37. package/dist/application/dev-only-services.js.map +1 -1
  38. package/dist/application/dsn-reader-service.js +1 -1
  39. package/dist/application/elicitation-server.js +382 -0
  40. package/dist/application/elicitation-server.js.map +1 -0
  41. package/dist/application/elicitation-stdio.js +48 -0
  42. package/dist/application/elicitation-stdio.js.map +1 -0
  43. package/dist/application/flow/advance.js +86 -5
  44. package/dist/application/flow/advance.js.map +1 -1
  45. package/dist/application/flow/checkout-observation.js +152 -0
  46. package/dist/application/flow/checkout-observation.js.map +1 -0
  47. package/dist/application/flow/flow-service.js +72 -13
  48. package/dist/application/flow/flow-service.js.map +1 -1
  49. package/dist/application/flow/internal-actions.js +460 -1
  50. package/dist/application/flow/internal-actions.js.map +1 -1
  51. package/dist/application/flow/internal-drive.js +22 -9
  52. package/dist/application/flow/internal-drive.js.map +1 -1
  53. package/dist/application/flow/prove.js +205 -0
  54. package/dist/application/flow/prove.js.map +1 -0
  55. package/dist/application/flow/run-journey.js +15 -0
  56. package/dist/application/flow/run-journey.js.map +1 -0
  57. package/dist/application/flow/run-projection.js +15 -3
  58. package/dist/application/flow/run-projection.js.map +1 -1
  59. package/dist/application/flow/run-state-service.js +19 -4
  60. package/dist/application/flow/run-state-service.js.map +1 -1
  61. package/dist/application/flow/submit.js +250 -72
  62. package/dist/application/flow/submit.js.map +1 -1
  63. package/dist/application/generate-launch-service.js +4 -4
  64. package/dist/application/generate-launch-service.js.map +1 -1
  65. package/dist/application/git-flow-service.js +9 -0
  66. package/dist/application/git-flow-service.js.map +1 -1
  67. package/dist/application/local-proposal.js +9 -0
  68. package/dist/application/local-proposal.js.map +1 -1
  69. package/dist/application/lock-service.js +96 -28
  70. package/dist/application/lock-service.js.map +1 -1
  71. package/dist/application/logging/log-events.js +19 -3
  72. package/dist/application/logging/log-events.js.map +1 -1
  73. package/dist/application/logging/logger.js +2 -2
  74. package/dist/application/logging/logger.js.map +1 -1
  75. package/dist/application/mcp-connections-service.js +115 -41
  76. package/dist/application/mcp-connections-service.js.map +1 -1
  77. package/dist/application/mcp-doctor-service.js +98 -54
  78. package/dist/application/mcp-doctor-service.js.map +1 -1
  79. package/dist/application/mcp-entry-classification.js +60 -0
  80. package/dist/application/mcp-entry-classification.js.map +1 -0
  81. package/dist/application/mcp-host-reader.js +114 -81
  82. package/dist/application/mcp-host-reader.js.map +1 -1
  83. package/dist/application/mcp-host-receipt-service.js +399 -0
  84. package/dist/application/mcp-host-receipt-service.js.map +1 -0
  85. package/dist/application/mcp-host-receipt-store.js +122 -0
  86. package/dist/application/mcp-host-receipt-store.js.map +1 -0
  87. package/dist/application/mcp-host-receipts.js +28 -0
  88. package/dist/application/mcp-host-receipts.js.map +1 -0
  89. package/dist/application/mcp-host-writer.js +321 -126
  90. package/dist/application/mcp-host-writer.js.map +1 -1
  91. package/dist/application/mcp-launch-probe-service.js +142 -0
  92. package/dist/application/mcp-launch-probe-service.js.map +1 -0
  93. package/dist/application/mcp-migration-service.js +351 -0
  94. package/dist/application/mcp-migration-service.js.map +1 -0
  95. package/dist/application/mcp-native-host-check-service.js +154 -0
  96. package/dist/application/mcp-native-host-check-service.js.map +1 -0
  97. package/dist/application/mcp-receipt-registration-service.js +105 -0
  98. package/dist/application/mcp-receipt-registration-service.js.map +1 -0
  99. package/dist/application/mcp-receipt-removal-service.js +62 -0
  100. package/dist/application/mcp-receipt-removal-service.js.map +1 -0
  101. package/dist/application/mcp-remove-service.js +84 -21
  102. package/dist/application/mcp-remove-service.js.map +1 -1
  103. package/dist/application/mcp-scope-common.js +2 -4
  104. package/dist/application/mcp-scope-common.js.map +1 -1
  105. package/dist/application/mcp-setup-service.js +60 -21
  106. package/dist/application/mcp-setup-service.js.map +1 -1
  107. package/dist/application/mcp-stdio-probe.js +526 -0
  108. package/dist/application/mcp-stdio-probe.js.map +1 -0
  109. package/dist/application/mcp-test-connection-service.js +23 -50
  110. package/dist/application/mcp-test-connection-service.js.map +1 -1
  111. package/dist/application/merge-state-service.js +10 -16
  112. package/dist/application/merge-state-service.js.map +1 -1
  113. package/dist/application/multiroot/claude.js +34 -15
  114. package/dist/application/multiroot/claude.js.map +1 -1
  115. package/dist/application/multiroot/codex.js +9 -5
  116. package/dist/application/multiroot/codex.js.map +1 -1
  117. package/dist/application/multiroot-service.js +20 -11
  118. package/dist/application/multiroot-service.js.map +1 -1
  119. package/dist/application/parsers/project-block.js +25 -2
  120. package/dist/application/parsers/project-block.js.map +1 -1
  121. package/dist/application/paths-service.js +30 -31
  122. package/dist/application/paths-service.js.map +1 -1
  123. package/dist/application/plan-exec-batch-service.js +549 -0
  124. package/dist/application/plan-exec-batch-service.js.map +1 -0
  125. package/dist/application/plan-exec-decision-service.js +273 -0
  126. package/dist/application/plan-exec-decision-service.js.map +1 -0
  127. package/dist/application/project-md-service.js +2 -2
  128. package/dist/application/project-md-service.js.map +1 -1
  129. package/dist/application/project-md-upsert-service.js +4 -4
  130. package/dist/application/project-md-upsert-service.js.map +1 -1
  131. package/dist/application/project-tab-data.js +12 -7
  132. package/dist/application/project-tab-data.js.map +1 -1
  133. package/dist/application/release-data-service.js +2 -2
  134. package/dist/application/release-data-service.js.map +1 -1
  135. package/dist/application/resume-service.js +32 -11
  136. package/dist/application/resume-service.js.map +1 -1
  137. package/dist/application/retirement/apply.js +84 -0
  138. package/dist/application/retirement/apply.js.map +1 -1
  139. package/dist/application/retirement/attribution.js +31 -1
  140. package/dist/application/retirement/attribution.js.map +1 -1
  141. package/dist/application/retirement/graph.js +19 -1
  142. package/dist/application/retirement/graph.js.map +1 -1
  143. package/dist/application/retirement/journal.js +11 -0
  144. package/dist/application/retirement/journal.js.map +1 -1
  145. package/dist/application/retirement/prepare.js +89 -3
  146. package/dist/application/retirement/prepare.js.map +1 -1
  147. package/dist/application/retirement/preview.js +16 -0
  148. package/dist/application/retirement/preview.js.map +1 -1
  149. package/dist/application/retirement/resolve.js +65 -0
  150. package/dist/application/retirement/resolve.js.map +1 -1
  151. package/dist/application/self/host-states.js +2 -0
  152. package/dist/application/self/host-states.js.map +1 -1
  153. package/dist/application/self/install-skill.js +21 -17
  154. package/dist/application/self/install-skill.js.map +1 -1
  155. package/dist/application/self/mcp-config.js +392 -84
  156. package/dist/application/self/mcp-config.js.map +1 -1
  157. package/dist/application/self/mcp-via-state.js +62 -0
  158. package/dist/application/self/mcp-via-state.js.map +1 -0
  159. package/dist/application/self/uninstall.js +15 -5
  160. package/dist/application/self/uninstall.js.map +1 -1
  161. package/dist/application/session-close-service.js +24 -1
  162. package/dist/application/session-close-service.js.map +1 -1
  163. package/dist/application/session-create-service.js +7 -0
  164. package/dist/application/session-create-service.js.map +1 -1
  165. package/dist/application/session-resume-service.js +2 -2
  166. package/dist/application/session-resume-service.js.map +1 -1
  167. package/dist/application/sessions-service.js +2 -4
  168. package/dist/application/sessions-service.js.map +1 -1
  169. package/dist/application/skills-resolver-service.js +4 -4
  170. package/dist/application/skills-resolver-service.js.map +1 -1
  171. package/dist/application/source-boundary-policy.js +18 -7
  172. package/dist/application/source-boundary-policy.js.map +1 -1
  173. package/dist/application/source-remove-service.js +12 -1
  174. package/dist/application/source-remove-service.js.map +1 -1
  175. package/dist/application/sources-service.js +2 -2
  176. package/dist/application/sources-service.js.map +1 -1
  177. package/dist/application/status-service.js +4 -0
  178. package/dist/application/status-service.js.map +1 -1
  179. package/dist/application/visibility-doctor-service.js +1 -1
  180. package/dist/application/visibility-doctor-service.js.map +1 -1
  181. package/dist/application/workline-index-service.js +182 -27
  182. package/dist/application/workline-index-service.js.map +1 -1
  183. package/dist/application/workspace-init-service.js +89 -271
  184. package/dist/application/workspace-init-service.js.map +1 -1
  185. package/dist/application/workspace-materialization-service.js +300 -0
  186. package/dist/application/workspace-materialization-service.js.map +1 -0
  187. package/dist/application/worktree-service.js +32 -3
  188. package/dist/application/worktree-service.js.map +1 -1
  189. package/dist/cli/commands/capability.js +4 -7
  190. package/dist/cli/commands/capability.js.map +1 -1
  191. package/dist/cli/commands/claims.js +72 -0
  192. package/dist/cli/commands/claims.js.map +1 -0
  193. package/dist/cli/commands/dev-only.js +73 -25
  194. package/dist/cli/commands/dev-only.js.map +1 -1
  195. package/dist/cli/commands/flow.js +145 -42
  196. package/dist/cli/commands/flow.js.map +1 -1
  197. package/dist/cli/commands/index.js +4 -0
  198. package/dist/cli/commands/index.js.map +1 -1
  199. package/dist/cli/commands/mcp.js +752 -76
  200. package/dist/cli/commands/mcp.js.map +1 -1
  201. package/dist/cli/commands/multiroot.js +40 -7
  202. package/dist/cli/commands/multiroot.js.map +1 -1
  203. package/dist/cli/commands/resume.js +9 -3
  204. package/dist/cli/commands/resume.js.map +1 -1
  205. package/dist/cli/commands/retirement.js +9 -0
  206. package/dist/cli/commands/retirement.js.map +1 -1
  207. package/dist/cli/commands/session-create.js +1 -1
  208. package/dist/cli/commands/session-create.js.map +1 -1
  209. package/dist/cli/commands/skills.js +1 -1
  210. package/dist/cli/commands/skills.js.map +1 -1
  211. package/dist/cli/commands/status.js +72 -24
  212. package/dist/cli/commands/status.js.map +1 -1
  213. package/dist/cli/commands/tool.js +141 -0
  214. package/dist/cli/commands/tool.js.map +1 -0
  215. package/dist/cli/commands/workspace-init.js +5 -5
  216. package/dist/cli/commands/workspace-init.js.map +1 -1
  217. package/dist/cli/help-groups.js +6 -1
  218. package/dist/cli/help-groups.js.map +1 -1
  219. package/dist/cli/main.js +272 -67
  220. package/dist/cli/main.js.map +1 -1
  221. package/dist/cli/parser.js +8 -1
  222. package/dist/cli/parser.js.map +1 -1
  223. package/dist/cli/registry.js.map +1 -1
  224. package/dist/cli/render.js +15 -2
  225. package/dist/cli/render.js.map +1 -1
  226. package/dist/cli/tui/app.js +3 -2
  227. package/dist/cli/tui/app.js.map +1 -1
  228. package/dist/cli/tui/components/workspace-init-form.js +7 -7
  229. package/dist/cli/tui/components/workspace-init-form.js.map +1 -1
  230. package/dist/cli/tui/data/workflow-content.js +2 -2
  231. package/dist/cli/tui/data/workflow-content.js.map +1 -1
  232. package/dist/cli/tui/tabs/config-tab.js +4 -3
  233. package/dist/cli/tui/tabs/config-tab.js.map +1 -1
  234. package/dist/cli/tui/tabs/mcp-tab-helpers.js +72 -1
  235. package/dist/cli/tui/tabs/mcp-tab-helpers.js.map +1 -1
  236. package/dist/cli/tui/tabs/mcp-tab.js +101 -42
  237. package/dist/cli/tui/tabs/mcp-tab.js.map +1 -1
  238. package/dist/cli/tui/tabs/project-tab.js +18 -21
  239. package/dist/cli/tui/tabs/project-tab.js.map +1 -1
  240. package/dist/cli/tui/workspace-root.js +13 -0
  241. package/dist/cli/tui/workspace-root.js.map +1 -0
  242. package/dist/domain/capability/protocol.js +2 -2
  243. package/dist/domain/capability/protocol.js.map +1 -1
  244. package/dist/domain/database-tools.js +445 -0
  245. package/dist/domain/database-tools.js.map +1 -0
  246. package/dist/domain/degradation-notice.js +60 -0
  247. package/dist/domain/degradation-notice.js.map +1 -0
  248. package/dist/domain/elicitation.js +152 -0
  249. package/dist/domain/elicitation.js.map +1 -0
  250. package/dist/domain/flow/answer.js +173 -42
  251. package/dist/domain/flow/answer.js.map +1 -1
  252. package/dist/domain/flow/authority.js +184 -127
  253. package/dist/domain/flow/authority.js.map +1 -1
  254. package/dist/domain/flow/directive.js +47 -1
  255. package/dist/domain/flow/directive.js.map +1 -1
  256. package/dist/domain/flow/rules.js +7 -4
  257. package/dist/domain/flow/rules.js.map +1 -1
  258. package/dist/domain/flow/run-state.js +636 -31
  259. package/dist/domain/flow/run-state.js.map +1 -1
  260. package/dist/domain/harnesses.js +16 -0
  261. package/dist/domain/harnesses.js.map +1 -1
  262. package/dist/domain/mcp-entry.js +164 -28
  263. package/dist/domain/mcp-entry.js.map +1 -1
  264. package/dist/domain/postgres-object-search.js +320 -0
  265. package/dist/domain/postgres-object-search.js.map +1 -0
  266. package/dist/domain/redaction.js +47 -0
  267. package/dist/domain/redaction.js.map +1 -0
  268. package/dist/domain/retirement/proposal.js +1 -0
  269. package/dist/domain/retirement/proposal.js.map +1 -1
  270. package/dist/domain/structured-choice-stamp.js +32 -4
  271. package/dist/domain/structured-choice-stamp.js.map +1 -1
  272. package/dist/domain/workline-mcp-entry.js +34 -0
  273. package/dist/domain/workline-mcp-entry.js.map +1 -0
  274. package/dist/ports/postgres-tools.js +10 -0
  275. package/dist/ports/postgres-tools.js.map +1 -0
  276. package/dist/runtime/namespace-resolver.js +104 -18
  277. package/dist/runtime/namespace-resolver.js.map +1 -1
  278. package/package.json +5 -1
  279. package/skills/w/README.md +2 -2
  280. package/skills/w/SKILL.md +9 -10
  281. package/skills/w/artifacts/README.md +1 -1
  282. package/skills/w/commands/README.md +3 -3
  283. package/skills/w/commands/persist.md +3 -1
  284. package/skills/w/commands/resume.md +1 -1
  285. package/skills/w/commands/spec-new.md +2 -2
  286. package/skills/w/commands/status.md +1 -1
  287. package/skills/w/commands/workspace-init.md +9 -10
  288. package/skills/w/context/MANIFEST.json +1 -4
  289. package/skills/w/harness/HARNESS.md +1 -1
  290. package/skills/w/loops/quick-loop/LOOP.md +1 -1
  291. package/skills/w/modules/PROMPT-CONTINUITY.md +1 -1
  292. package/skills/w/modules/WORKSPACE-SCAFFOLD.md +17 -20
  293. package/skills/w/schemas/capability-descriptor.schema.json +1 -1
  294. package/dist/application/mcp-dbhub-launcher.js +0 -216
  295. package/dist/application/mcp-dbhub-launcher.js.map +0 -1
@@ -1,16 +1,33 @@
1
1
  import { existsSync, readFileSync } from "node:fs";
2
2
  import { homedir } from "node:os";
3
3
  import { resolve } from "node:path";
4
+ import { isDeepStrictEqual } from "node:util";
5
+ import { PostgresReadonlyTools } from "../../adapters/postgres-readonly-tools.js";
6
+ import { runDatabaseMcpStdio } from "../../application/database-mcp-stdio.js";
7
+ import { DatabaseToolCatalog } from "../../application/database-tool-catalog.js";
4
8
  import { runHarness } from "../../application/dev-only-services.js";
9
+ import { runElicitationStdio } from "../../application/elicitation-stdio.js";
5
10
  import { resolveMcpConnectionSelection, } from "../../application/mcp-connections-service.js";
6
- import { DbhubLauncherError, runDbhubLauncher } from "../../application/mcp-dbhub-launcher.js";
7
11
  import { runMcpDoctor } from "../../application/mcp-doctor-service.js";
8
- import { runMcpRemove } from "../../application/mcp-remove-service.js";
9
- import { runMcpSetup } from "../../application/mcp-setup-service.js";
12
+ import { classifyMcpEntry } from "../../application/mcp-entry-classification.js";
13
+ import { readMcpEntry } from "../../application/mcp-host-reader.js";
14
+ import { digestMcpReceiptDescriptor, } from "../../application/mcp-host-receipt-service.js";
15
+ import { openMcpHostReceiptService, registerPersistedMcpDescriptor, } from "../../application/mcp-host-receipts.js";
16
+ import { probePersistedMcpSetupEntries } from "../../application/mcp-launch-probe-service.js";
17
+ import { runMcpMigration, } from "../../application/mcp-migration-service.js";
18
+ import { checkNativeMcpHosts, recordNativeMcpHostChecks, } from "../../application/mcp-native-host-check-service.js";
19
+ import { registerMcpSetupReceipts } from "../../application/mcp-receipt-registration-service.js";
20
+ import { removeMcpRemoveReceipts } from "../../application/mcp-receipt-removal-service.js";
21
+ import { runMcpRemove, } from "../../application/mcp-remove-service.js";
22
+ import { runMcpSetup, } from "../../application/mcp-setup-service.js";
23
+ import { runMcpStdioProbe } from "../../application/mcp-stdio-probe.js";
10
24
  import { buildWarpPostInstallHint, formatWarpPostInstallHint, } from "../../application/mcp-warp-postinstall-hint.js";
11
25
  import { resolveWarpGlobalMcpPath, resolveWarpProjectMcpPath, } from "../../application/multiroot/warp.js";
26
+ import { PathsService } from "../../application/paths-service.js";
27
+ import { ensureWorklineMaterialized, previewWorklineMaterialization, } from "../../application/workspace-materialization-service.js";
28
+ import { DATABASE_TOOL_DESCRIPTORS } from "../../domain/database-tools.js";
12
29
  import { MCP_FILE_HOSTS, harnessById } from "../../domain/harnesses.js";
13
- import { mcpEntryNameFor } from "../../domain/mcp-entry.js";
30
+ import { buildMcpEntry, mcpEntryNameFor, } from "../../domain/mcp-entry.js";
14
31
  import { fail } from "../render.js";
15
32
  // File-writing hosts — single source in the domain (excludes oz, which has no
16
33
  // file writer). The TUI's host picker reads the same list.
@@ -18,16 +35,21 @@ const FILE_HOSTS = MCP_FILE_HOSTS;
18
35
  const HOST_VALUES = new Set([...FILE_HOSTS, "all"]);
19
36
  export const mcpCommand = {
20
37
  name: "mcp",
21
- describe: "MCP server tooling. Connections come from mcp-connections.json. Subcomandos: dbhub [--instance i] | setup/remove/doctor [--host h] [--instance i|--all-connections] [--workspace dir] [--global] [--dry-run] [--force] | warp-status.",
38
+ describe: "MCP server tooling. `serve` corre el servidor de elicitation de Workline y `serve-db` sirve las tools PostgreSQL; ambas reservan stdout para JSON-RPC. `dbhub` queda como alias deprecado de serve-db. Subcomandos: serve | serve-db [--instance i] | dbhub [--instance i] | setup/remove/doctor [--host h] [--instance i|--all-connections] [--workspace dir] [--global] [--dry-run] [--force] | migrate [--host h] [--instance i|--all-connections] [--workspace dir] [--global] [--apply --force] | warp-status.",
22
39
  async execute(args, ctx) {
23
40
  const subcommand = args.rest[0];
24
41
  if ((subcommand === "setup" ||
25
42
  subcommand === "remove" ||
26
43
  subcommand === "doctor" ||
44
+ subcommand === "migrate" ||
27
45
  subcommand === "warp-status") &&
28
46
  args.rest.length > 1) {
29
47
  return fail("INVALID_INPUT", "Los subcomandos MCP no aceptan una conexión posicional. Usá --instance <nombre> o --all-connections cuando el subcomando permite fan-out.");
30
48
  }
49
+ if (subcommand === "serve")
50
+ return runServeSub(args);
51
+ if (subcommand === "serve-db")
52
+ return runServeDbSub(args, ctx);
31
53
  if (subcommand === "dbhub")
32
54
  return runDbhubSub(args, ctx);
33
55
  if (subcommand === "setup")
@@ -36,39 +58,140 @@ export const mcpCommand = {
36
58
  return runRemoveSub(args, ctx);
37
59
  if (subcommand === "doctor")
38
60
  return runDoctorSub(args, ctx);
61
+ if (subcommand === "migrate")
62
+ return await runMigrateSub(args, ctx);
39
63
  if (subcommand === "warp-status")
40
64
  return runWarpStatusSub(args, ctx);
41
- return fail("INVALID_INPUT", "mcp requiere subcomando: dbhub [--instance <nombre>] | setup | remove | doctor | warp-status");
65
+ return fail("INVALID_INPUT", "mcp requiere subcomando: serve | serve-db [--instance <nombre>] | dbhub [--instance <nombre>] | setup | remove | doctor | migrate | warp-status");
42
66
  },
43
67
  };
68
+ /**
69
+ * El servidor propio, hablando por la entrada y la salida de ESTE proceso.
70
+ *
71
+ * No devuelve datos para renderizar y no puede: la salida estándar ya está tomada
72
+ * por el protocolo, así que cualquier cosa que el renderizador imprimiera ahí
73
+ * corrompería la sesión del cliente. Por eso `data` viaja indefinido.
74
+ */
75
+ async function runServeSub(args) {
76
+ // Qué host lo lanzó: el propio host lo dice al registrarlo, porque el servidor
77
+ // no puede detectarlo desde adentro. Sólo habilita una vía observada; no se usa
78
+ // para atribuir una negativa o cancelación posterior.
79
+ const declared = args.values.get("host");
80
+ const spec = declared === undefined ? null : harnessById(declared);
81
+ await runElicitationStdio({
82
+ input: process.stdin,
83
+ output: process.stdout,
84
+ diagnostics: process.stderr,
85
+ via: spec?.structuredChoice.mcpElicitation ?? {
86
+ available: false,
87
+ reason: "el servidor se lanzó sin declarar en qué host corre (--host)",
88
+ },
89
+ });
90
+ return { ok: true, exitCode: 0 };
91
+ }
44
92
  async function runDbhubSub(args, ctx) {
93
+ process.stderr.write("aw mcp dbhub está deprecado; usá 'aw mcp serve-db'.\n");
94
+ return await runServeDbSub(args, ctx);
95
+ }
96
+ async function runServeDbSub(args, ctx) {
45
97
  if (args.rest[1] !== undefined) {
46
- return fail("INVALID_INPUT", "mcp dbhub no acepta una conexión posicional. Usá --instance <nombre>.");
98
+ return serveBootstrapFailure("mcp serve-db no acepta una conexión posicional. Usá --instance <nombre>.");
47
99
  }
100
+ if (args.flags.has("--instance"))
101
+ return serveBootstrapFailure("--instance requiere un nombre.");
48
102
  const connections = resolveConnections(args, ctx, false);
49
103
  if (!("value" in connections))
50
- return connections;
104
+ return serveBootstrapFailure("No se pudo resolver la conexión MCP.");
51
105
  const connection = connections.value[0];
52
106
  if (connection === undefined) {
53
- return fail("NO_MCP_CONNECTIONS", "No hay conexiones MCP seleccionadas para dbhub.");
107
+ return serveBootstrapFailure("No hay conexiones MCP seleccionadas para serve-db.");
54
108
  }
55
- try {
56
- const result = await runDbhubLauncher({
57
- instance: connection.name,
58
- deps: {
59
- env: { ...process.env },
60
- paths: ctx.paths,
61
- platform: process.platform,
62
- },
63
- });
64
- return { ok: true, data: undefined, exitCode: clampExit(result.exitCode) };
109
+ const catalog = new DatabaseToolCatalog({
110
+ paths: ctx.paths,
111
+ env: ctx.env,
112
+ postgres: new PostgresReadonlyTools(),
113
+ });
114
+ const onHostLoadObserved = resolveHostLoadObservation(args, ctx, connection);
115
+ await runDatabaseMcpStdio({
116
+ input: process.stdin,
117
+ output: process.stdout,
118
+ diagnostics: process.stderr,
119
+ catalog,
120
+ connection: connection.name,
121
+ ...(onHostLoadObserved === undefined ? {} : { onHostLoadObserved }),
122
+ });
123
+ return { ok: true, data: undefined, exitCode: 0 };
124
+ }
125
+ /** A stdio server may never put a CLI envelope on its JSON-RPC stdout. */
126
+ function serveBootstrapFailure(message) {
127
+ process.stderr.write(`aw mcp serve-db: ${message}\n`);
128
+ return {
129
+ ok: false,
130
+ error: { code: "MCP_SERVER_BOOTSTRAP_FAILED", message },
131
+ exitCode: 2,
132
+ suppressOutput: true,
133
+ };
134
+ }
135
+ /**
136
+ * Only an exact persisted descriptor can clear reload_required. A local doctor
137
+ * sets AW_MCP_PROBE, so its initialized notification remains launch evidence
138
+ * and cannot impersonate a real host reload.
139
+ */
140
+ function resolveHostLoadObservation(args, ctx, connection) {
141
+ if (process.env.AW_MCP_PROBE === "1")
142
+ return undefined;
143
+ const rawHost = args.values.get("host");
144
+ const rawScope = args.values.get("scope");
145
+ if (rawHost === undefined || rawScope === undefined)
146
+ return undefined;
147
+ if (!FILE_HOSTS.includes(rawHost))
148
+ return undefined;
149
+ if (rawScope !== "workspace" && rawScope !== "global")
150
+ return undefined;
151
+ const host = rawHost;
152
+ const scope = rawScope;
153
+ if (scope !== "global")
154
+ return undefined;
155
+ const entry = buildMcpEntry(connection.name, connection.dsnVar, {
156
+ host,
157
+ scope,
158
+ namespace: ctx.paths.namespace,
159
+ });
160
+ // `process.argv` begins with Node and the entrypoint. The rest must be the
161
+ // descriptor byte-for-byte; a hand-written `serve-db` command is useful but
162
+ // is not evidence that a configured host loaded its registered descriptor.
163
+ if (process.execPath !== entry.command || !isDeepStrictEqual(process.argv.slice(1), entry.args)) {
164
+ return undefined;
65
165
  }
66
- catch (err) {
67
- if (err instanceof DbhubLauncherError) {
68
- return fail("DBHUB_LAUNCHER_FAILED", err.message);
166
+ const descriptorDigest = digestMcpReceiptDescriptor({
167
+ command: entry.command,
168
+ args: entry.args,
169
+ });
170
+ return async () => {
171
+ try {
172
+ const snapshot = readMcpEntry(host, ctx.env.homeDir(), entry.name, scope);
173
+ const classification = classifyMcpEntry(host, snapshot, entry, connection);
174
+ if (classification.state !== "current" ||
175
+ snapshot.command === undefined ||
176
+ snapshot.args === undefined ||
177
+ digestMcpReceiptDescriptor({ command: snapshot.command, args: snapshot.args }) !==
178
+ descriptorDigest) {
179
+ return;
180
+ }
181
+ await openMcpHostReceiptService(ctx.paths).observeHostLoad({
182
+ host,
183
+ scope,
184
+ connection: connection.name,
185
+ descriptorDigest,
186
+ });
69
187
  }
70
- throw err;
71
- }
188
+ catch {
189
+ // A stale/missing receipt must not contaminate MCP stdout. The host has
190
+ // already received a valid initialized lifecycle response; doctor will
191
+ // surface the missing operational evidence separately.
192
+ process.stderr.write("aw mcp serve-db: no se pudo registrar la carga observada del host\n");
193
+ }
194
+ };
72
195
  }
73
196
  async function runSetupSub(args, ctx) {
74
197
  const hosts = resolveHosts(args, ctx);
@@ -77,47 +200,155 @@ async function runSetupSub(args, ctx) {
77
200
  const connections = resolveConnections(args, ctx, true);
78
201
  if (!("value" in connections))
79
202
  return connections;
80
- const workspace = args.values.get("workspace");
81
- const scope = args.flags.has("--global") ? "global" : "workspace";
82
- const result = runMcpSetup(ctx.env, {
203
+ // All workspace-scoped MCP artifacts share Workline's resolved root. An
204
+ // explicit --workspace remains an intentional override; raw process cwd is
205
+ // only the invocation coordinate and may be a source subdirectory.
206
+ const workspace = args.values.get("workspace") ?? ctx.paths.workspaceDir();
207
+ const scopeInput = args.flags.has("--global")
208
+ ? { scope: "global" }
209
+ : { scope: "workspace", workspace };
210
+ const result = await runMcpSetupWithMaterialization(ctx, {
83
211
  hosts: hosts.value,
84
212
  connections: connections.value,
85
- scope,
86
- ...(workspace !== undefined ? { workspace } : {}),
213
+ namespace: ctx.paths.namespace,
214
+ ...scopeInput,
87
215
  dryRun: args.flags.has("--dry-run"),
88
- force: args.flags.has("--force"),
216
+ ...(args.flags.has("--force") ? { globalApproval: "explicit-cli-force" } : {}),
89
217
  });
90
218
  if ("ok" in result) {
91
219
  return fail("GLOBAL_REQUIRES_FORCE", result.hint, result, result.exitCode);
92
220
  }
93
- const hasErrors = result.errors.length > 0;
94
- const warpHints = !hasErrors
95
- ? buildWarpHintsFor(hosts.value, connections.value.map((connection) => connection.name), scope, ctx, workspace)
221
+ const hasProblems = result.errors.length > 0 || result.conflicts.length > 0;
222
+ const warpHints = !hasProblems
223
+ ? buildWarpHintsFor(hosts.value, connections.value.map((connection) => connection.name), scopeInput.scope, workspace)
96
224
  : [];
97
225
  return {
98
- ok: !hasErrors,
226
+ ok: !hasProblems,
99
227
  data: { ...result, ...(warpHints.length > 0 ? { warp_hints: warpHints } : {}) },
100
- ...(hasErrors
228
+ ...(hasProblems
101
229
  ? {
102
230
  error: {
103
231
  code: "MCP_SETUP_PARTIAL",
104
- message: `${result.errors.length} error(es) durante setup; ver data.errors`,
232
+ message: `${result.errors.length} error(es) y ${result.conflicts.length} conflicto(s) durante setup; ver data.errors y data.conflicts`,
105
233
  },
106
234
  }
107
235
  : {}),
108
- exitCode: hasErrors ? 1 : 0,
236
+ exitCode: hasProblems ? 1 : 0,
109
237
  };
110
238
  }
111
- function buildWarpHintsFor(hosts, instances, scope, ctx, workspace) {
239
+ /**
240
+ * MCP writers use host-native synchronous file APIs, so the generic guarded
241
+ * filesystem cannot observe their mutation. Preflight with the writer's own
242
+ * dry-run path, materialize only when that preflight has a real effect, then
243
+ * run the actual write. This preserves pure conflicts/skips and puts the
244
+ * canonical marker in place before the first host config is touched.
245
+ */
246
+ async function runMcpSetupWithMaterialization(ctx, input) {
247
+ if (input.scope !== "workspace") {
248
+ const result = runMcpSetup(ctx.env, input);
249
+ return "ok" in result ? result : await attachSetupReceipts(ctx, input, result);
250
+ }
251
+ const preview = runMcpSetup(ctx.env, { ...input, dryRun: true });
252
+ if ("ok" in preview)
253
+ return preview;
254
+ if (!hasPendingMcpMutation(preview.applied)) {
255
+ if (input.dryRun)
256
+ return preview;
257
+ const result = runMcpSetup(ctx.env, input);
258
+ return "ok" in result ? result : await attachSetupReceipts(ctx, input, result);
259
+ }
260
+ const materialization = await materializeMcpWorkspace(ctx, input.workspace, input.dryRun === true);
261
+ if (input.dryRun)
262
+ return { ...preview, materialization };
263
+ const result = runMcpSetup(ctx.env, input);
264
+ if ("ok" in result)
265
+ return result;
266
+ const withReceipts = await attachSetupReceipts(ctx, input, result);
267
+ return { ...withReceipts, materialization };
268
+ }
269
+ async function attachSetupReceipts(ctx, input, result) {
270
+ const receipts = await registerMcpSetupReceipts(ctx.paths, input, result);
271
+ const withReceipts = {
272
+ ...result,
273
+ errors: [...result.errors, ...receipts.errors],
274
+ ...(receipts.registered.length === 0 ? {} : { receipts: receipts.registered }),
275
+ };
276
+ const launch = await probePersistedMcpSetupEntries(ctx.paths, withReceipts, receipts.probeTargets);
277
+ const native = await checkNativeMcpHosts(receipts.probeTargets);
278
+ await recordNativeMcpHostChecks({
279
+ paths: ctx.paths,
280
+ scope: withReceipts.scope,
281
+ scopeDir: withReceipts.scope_dir,
282
+ targets: receipts.probeTargets,
283
+ checks: native.checks,
284
+ });
285
+ if (receipts.registered.length === 0 &&
286
+ receipts.errors.length === 0 &&
287
+ launch.probes.length === 0 &&
288
+ launch.errors.length === 0 &&
289
+ native.checks.length === 0 &&
290
+ native.errors.length === 0) {
291
+ return result;
292
+ }
293
+ return {
294
+ ...withReceipts,
295
+ errors: [...withReceipts.errors, ...launch.errors, ...native.errors],
296
+ ...(launch.probes.length === 0 ? {} : { launch_probes: launch.probes }),
297
+ ...(native.checks.length === 0 ? {} : { native_checks: native.checks }),
298
+ };
299
+ }
300
+ async function runMcpRemoveWithMaterialization(ctx, input) {
301
+ if (input.scope !== "workspace") {
302
+ const result = runMcpRemove(ctx.env, input);
303
+ return "ok" in result ? result : await attachRemoveReceipts(ctx, input, result);
304
+ }
305
+ const preview = runMcpRemove(ctx.env, { ...input, dryRun: true });
306
+ if ("ok" in preview)
307
+ return preview;
308
+ if (!hasPendingMcpMutation(preview.removed)) {
309
+ if (input.dryRun)
310
+ return preview;
311
+ const result = runMcpRemove(ctx.env, input);
312
+ return "ok" in result ? result : await attachRemoveReceipts(ctx, input, result);
313
+ }
314
+ const materialization = await materializeMcpWorkspace(ctx, input.workspace, input.dryRun === true);
315
+ if (input.dryRun)
316
+ return { ...preview, materialization };
317
+ const result = runMcpRemove(ctx.env, input);
318
+ if ("ok" in result)
319
+ return result;
320
+ const withReceipts = await attachRemoveReceipts(ctx, input, result);
321
+ return { ...withReceipts, materialization };
322
+ }
323
+ async function attachRemoveReceipts(ctx, input, result) {
324
+ const errors = await removeMcpRemoveReceipts(ctx.paths, input, result);
325
+ return errors.length === 0 ? result : { ...result, errors: [...result.errors, ...errors] };
326
+ }
327
+ function hasPendingMcpMutation(results) {
328
+ return results.some((result) => result.action === "dry-run");
329
+ }
330
+ async function materializeMcpWorkspace(ctx, workspace, dryRun) {
331
+ const root = resolve(workspace);
332
+ const currentRoot = resolve(ctx.paths.workspaceDir());
333
+ const paths = root === currentRoot
334
+ ? ctx.paths
335
+ : new PathsService(ctx.paths.namespace, ctx.env.homeDir(), root);
336
+ const fs = ctx.rawFs ?? ctx.fs;
337
+ return dryRun
338
+ ? await previewWorklineMaterialization(fs, paths)
339
+ : await ensureWorklineMaterialized(fs, paths);
340
+ }
341
+ function buildWarpHintsFor(hosts, instances, scope, workspace) {
112
342
  if (!hosts.includes("warp"))
113
343
  return [];
114
344
  const file = scope === "global"
115
345
  ? (resolveWarpGlobalMcpPath() ?? "~/.warp/.mcp.json")
116
- : resolveWarpProjectMcpPath(resolve(workspace ?? ctx.env.cwd()));
346
+ : resolveWarpProjectMcpPath(resolve(workspace));
117
347
  return instances.map((instance) => buildWarpPostInstallHint(mcpEntryNameFor(instance), scope, file));
118
348
  }
119
- async function runWarpStatusSub(_args, ctx) {
120
- const projectFile = resolveWarpProjectMcpPath(resolve(ctx.env.cwd()));
349
+ async function runWarpStatusSub(args, ctx) {
350
+ const workspace = args.values.get("workspace") ?? ctx.paths.workspaceDir();
351
+ const projectFile = resolveWarpProjectMcpPath(resolve(workspace));
121
352
  const globalFile = resolveWarpGlobalMcpPath() ?? `${homedir()}/.warp/.mcp.json`;
122
353
  const sources = [
123
354
  { scope: "workspace", file: projectFile },
@@ -136,7 +367,7 @@ async function runWarpStatusSub(_args, ctx) {
136
367
  reports,
137
368
  summary: anyDetected
138
369
  ? "Archivos .warp/.mcp.json detectados. Activá 'File-based MCP Servers' en Warp Settings si todavía no lo hiciste."
139
- : "No se encontró .warp/.mcp.json en cwd ni en home. Primero registrá una conexión con 'agent-workflow mcp setup --host warp'.",
370
+ : "No se encontró .warp/.mcp.json en el workspace ni en home. Primero registrá una conexión con 'agent-workflow mcp setup --host warp'.",
140
371
  },
141
372
  exitCode: 0,
142
373
  };
@@ -162,64 +393,516 @@ async function runRemoveSub(args, ctx) {
162
393
  const connections = resolveConnections(args, ctx, true);
163
394
  if (!("value" in connections))
164
395
  return connections;
165
- const workspace = args.values.get("workspace");
166
- const result = runMcpRemove(ctx.env, {
396
+ const workspace = args.values.get("workspace") ?? ctx.paths.workspaceDir();
397
+ const scopeInput = args.flags.has("--global")
398
+ ? { scope: "global" }
399
+ : { scope: "workspace", workspace };
400
+ const result = await runMcpRemoveWithMaterialization(ctx, {
167
401
  hosts: hosts.value,
168
402
  connections: connections.value,
169
- scope: args.flags.has("--global") ? "global" : "workspace",
170
- ...(workspace !== undefined ? { workspace } : {}),
403
+ namespace: ctx.paths.namespace,
404
+ ...scopeInput,
171
405
  dryRun: args.flags.has("--dry-run"),
172
- force: args.flags.has("--force"),
406
+ ...(args.flags.has("--force") ? { globalApproval: "explicit-cli-force" } : {}),
173
407
  });
174
408
  if ("ok" in result) {
175
409
  return fail("GLOBAL_REQUIRES_FORCE", result.hint, result, result.exitCode);
176
410
  }
177
- const hasErrors = result.errors.length > 0;
411
+ const hasProblems = result.errors.length > 0 || result.conflicts.length > 0;
178
412
  return {
179
- ok: !hasErrors,
413
+ ok: !hasProblems,
180
414
  data: result,
181
- ...(hasErrors
415
+ ...(hasProblems
182
416
  ? {
183
417
  error: {
184
418
  code: "MCP_REMOVE_PARTIAL",
185
- message: `${result.errors.length} error(es) durante remove; ver data.errors`,
419
+ message: `${result.errors.length} error(es) y ${result.conflicts.length} conflicto(s) durante remove; ver data.errors y data.conflicts`,
186
420
  },
187
421
  }
188
422
  : {}),
189
- exitCode: hasErrors ? 1 : 0,
423
+ exitCode: hasProblems ? 1 : 0,
190
424
  };
191
425
  }
192
426
  async function runDoctorSub(args, ctx) {
427
+ const input = resolveDoctorInput(args, ctx);
428
+ if (!("value" in input))
429
+ return input;
430
+ const data = runMcpDoctor(ctx.env, ctx.paths, {
431
+ hosts: input.value.hosts,
432
+ connections: input.value.connections,
433
+ namespace: ctx.paths.namespace,
434
+ ...input.value.scope,
435
+ });
436
+ if (input.value.probeMode !== undefined) {
437
+ await probeDoctorReports(data, input.value.probeMode, ctx, input.value.connections);
438
+ }
439
+ return doctorCommandResult(data);
440
+ }
441
+ function resolveDoctorInput(args, ctx) {
193
442
  const hosts = resolveHosts(args, ctx);
194
443
  if (!("value" in hosts))
195
444
  return hosts;
196
445
  const connections = resolveConnections(args, ctx, true);
197
446
  if (!("value" in connections))
198
447
  return connections;
199
- const workspace = args.values.get("workspace");
200
- const data = runMcpDoctor(ctx.env, ctx.paths, {
201
- hosts: hosts.value,
202
- connections: connections.value,
203
- scope: args.flags.has("--global") ? "global" : "workspace",
204
- ...(workspace !== undefined ? { workspace } : {}),
448
+ const probeMode = resolveDoctorProbeMode(args);
449
+ if (!("value" in probeMode))
450
+ return probeMode;
451
+ const workspace = args.values.get("workspace") ?? ctx.paths.workspaceDir();
452
+ return {
453
+ value: {
454
+ hosts: hosts.value,
455
+ connections: connections.value,
456
+ probeMode: probeMode.value,
457
+ scope: args.flags.has("--global") ? { scope: "global" } : { scope: "workspace", workspace },
458
+ },
459
+ };
460
+ }
461
+ async function probeDoctorReports(data, mode, ctx, connections) {
462
+ const receipts = openMcpHostReceiptService(ctx.paths);
463
+ const catalog = new DatabaseToolCatalog({
464
+ paths: ctx.paths,
465
+ env: ctx.env,
466
+ postgres: new PostgresReadonlyTools(),
205
467
  });
206
- const okCount = data.summary.ok;
468
+ for (const report of data.reports) {
469
+ const connection = connections.find((candidate) => candidate.name === report.instance);
470
+ if (connection === undefined) {
471
+ report.probe = { mode, outcome: "failed", phase: "spawn", code: "CONNECTION_NOT_FOUND" };
472
+ continue;
473
+ }
474
+ await probeDoctorReport(report, data, mode, receipts, catalog, ctx, connection);
475
+ }
476
+ }
477
+ async function probeDoctorReport(report, data, mode, receipts, catalog, ctx, connection) {
478
+ const readiness = doctorProbeReadiness(report, mode);
479
+ if (readiness !== undefined) {
480
+ report.probe = readiness;
481
+ return;
482
+ }
483
+ const snapshot = doctorProbeSnapshot(report, data, mode, ctx, connection);
484
+ if (snapshot === undefined)
485
+ return;
486
+ const probe = await runMcpStdioProbe({
487
+ descriptor: {
488
+ command: snapshot.command,
489
+ args: snapshot.args,
490
+ ...(snapshot.env === undefined ? {} : { env: snapshot.env }),
491
+ },
492
+ mode,
493
+ expectedTools: DATABASE_TOOL_DESCRIPTORS,
494
+ });
495
+ if (!probe.ok) {
496
+ report.probe = { mode: probe.mode, outcome: "failed", phase: probe.phase, code: probe.code };
497
+ await recordReceiptProbeBestEffort(receipts, report, snapshot, "failed", probe.phase);
498
+ await inspectDoctorSafety(mode, catalog, report);
499
+ return;
500
+ }
501
+ const phase = probe.mode === "data" ? "tools/call" : "tools/list";
502
+ report.probe = { mode: probe.mode, outcome: "passed", phase };
503
+ await recordReceiptProbeBestEffort(receipts, report, snapshot, "passed", phase);
504
+ await inspectDoctorSafety(mode, catalog, report);
505
+ }
506
+ async function inspectDoctorSafety(mode, catalog, report) {
507
+ if (mode !== "data")
508
+ return;
509
+ report.safety = safetyFromRoleOutcome((await catalog.inspectRole(report.instance)).inspection);
510
+ }
511
+ function doctorProbeReadiness(report, mode) {
512
+ if (report.launch_mode === "path-dependent") {
513
+ return { mode, outcome: "failed", phase: "spawn", code: "PATH_DEPENDENT_DESCRIPTOR" };
514
+ }
515
+ if (report.status !== "ok") {
516
+ return { mode, outcome: "failed", phase: "spawn", code: "CONFIGURATION_NOT_READY" };
517
+ }
518
+ return undefined;
519
+ }
520
+ function doctorProbeSnapshot(report, data, mode, ctx, connection) {
521
+ try {
522
+ const entry = buildMcpEntry(connection.name, connection.dsnVar, {
523
+ host: report.host,
524
+ scope: data.scope,
525
+ namespace: ctx.paths.namespace,
526
+ });
527
+ const snapshot = readMcpEntry(report.host, data.scope_dir, entry.name, data.scope);
528
+ if (snapshot.command !== undefined &&
529
+ snapshot.args !== undefined &&
530
+ classifyMcpEntry(report.host, snapshot, entry, connection).state === "current") {
531
+ return {
532
+ command: snapshot.command,
533
+ args: snapshot.args,
534
+ ...(snapshot.env === undefined ? {} : { env: snapshot.env }),
535
+ };
536
+ }
537
+ }
538
+ catch {
539
+ // The status remains actionable below without exposing a host config error.
540
+ }
541
+ report.probe = { mode, outcome: "failed", phase: "spawn", code: "DESCRIPTOR_UNREADABLE" };
542
+ return undefined;
543
+ }
544
+ function doctorCommandResult(data) {
207
545
  const total = data.reports.length;
208
- const allOk = okCount === total;
546
+ const affected = data.reports.filter(isDoctorReportAffected).length;
547
+ if (affected === 0)
548
+ return { ok: true, data, exitCode: 0 };
209
549
  return {
210
- ok: allOk,
550
+ ok: false,
211
551
  data,
212
- ...(allOk
213
- ? {}
214
- : {
552
+ error: {
553
+ code: "MCP_DOCTOR_DRIFT",
554
+ message: `${affected}/${total} entradas con drift, probe o seguridad pendiente (ver data.reports)`,
555
+ },
556
+ exitCode: 1,
557
+ };
558
+ }
559
+ function isDoctorReportAffected(report) {
560
+ return (report.status !== "ok" ||
561
+ report.probe?.outcome === "failed" ||
562
+ (report.safety !== undefined && report.safety.status !== "safe"));
563
+ }
564
+ function resolveDoctorProbeMode(args) {
565
+ if (args.flags.has("--probe")) {
566
+ return fail("INVALID_INPUT", "--probe requiere launch o data.", undefined, 2);
567
+ }
568
+ const value = args.values.get("probe");
569
+ if (value === undefined)
570
+ return { value: undefined };
571
+ if (value === "launch" || value === "data")
572
+ return { value };
573
+ return fail("INVALID_INPUT", "--probe acepta launch o data.", undefined, 2);
574
+ }
575
+ async function recordReceiptProbeBestEffort(service, report, descriptor, outcome, phase) {
576
+ try {
577
+ const identity = { host: report.host, scope: report.scope, connection: report.instance };
578
+ const receipt = await service.find(identity);
579
+ const descriptorDigest = digestMcpReceiptDescriptor(descriptor);
580
+ if (receipt === undefined || receipt.descriptor_digest !== descriptorDigest) {
581
+ return;
582
+ }
583
+ await service.recordLaunchProbe({ ...identity, descriptorDigest, outcome, phase });
584
+ }
585
+ catch {
586
+ // Probe output remains useful even if an optional receipt update is busy.
587
+ // The next setup/doctor exposes reload state from the last durable receipt.
588
+ }
589
+ }
590
+ export function safetyFromRoleOutcome(inspection) {
591
+ if ("success" in inspection) {
592
+ return {
593
+ status: "blocked",
594
+ superuser: false,
595
+ write_capable: false,
596
+ create_role: false,
597
+ create_database: false,
598
+ unsafe_server_role: false,
599
+ code: inspection.code,
600
+ };
601
+ }
602
+ const status = inspection.superuser
603
+ ? "blocked"
604
+ : inspection.canWrite ||
605
+ inspection.canCreateRole ||
606
+ inspection.canCreateDatabase ||
607
+ inspection.unsafeServerRole === true
608
+ ? "warning"
609
+ : "safe";
610
+ return {
611
+ status,
612
+ superuser: inspection.superuser,
613
+ write_capable: inspection.canWrite,
614
+ create_role: inspection.canCreateRole,
615
+ create_database: inspection.canCreateDatabase,
616
+ unsafe_server_role: inspection.unsafeServerRole === true,
617
+ };
618
+ }
619
+ async function runMigrateSub(args, ctx) {
620
+ const input = resolveMigrationInput(args, ctx);
621
+ if (!("value" in input))
622
+ return input;
623
+ const migration = runMcpMigration(ctx.env, {
624
+ hosts: input.value.hosts,
625
+ connections: input.value.connections,
626
+ namespace: ctx.paths.namespace,
627
+ ...input.value.scope,
628
+ ...(input.value.apply ? { apply: true } : {}),
629
+ ...(input.value.apply && input.value.scope.scope === "global"
630
+ ? { globalApproval: true }
631
+ : {}),
632
+ });
633
+ if (isMcpMigrationRefusal(migration)) {
634
+ return fail("GLOBAL_REQUIRES_FORCE", migration.hint, undefined, 2);
635
+ }
636
+ if (input.value.apply)
637
+ await finalizeMigration(ctx, migration, input.value);
638
+ return migrationCommandResult(migration, input.value.apply);
639
+ }
640
+ function resolveMigrationInput(args, ctx) {
641
+ if (args.flags.has("--dry-run") && args.flags.has("--apply")) {
642
+ return fail("INVALID_INPUT", "mcp migrate usa preview por defecto; no combines --dry-run con --apply.", undefined, 2);
643
+ }
644
+ const apply = args.flags.has("--apply");
645
+ if (apply && !args.flags.has("--force")) {
646
+ return fail("MCP_MIGRATE_REQUIRES_FORCE", "La migración escribe configuraciones de host. Revisá el preview y repetí con --apply --force.", undefined, 2);
647
+ }
648
+ const hosts = resolveHosts(args, ctx);
649
+ if (!("value" in hosts))
650
+ return hosts;
651
+ const connections = resolveConnections(args, ctx, true);
652
+ if (!("value" in connections))
653
+ return connections;
654
+ const workspace = args.values.get("workspace") ?? ctx.paths.workspaceDir();
655
+ return {
656
+ value: {
657
+ apply,
658
+ hosts: hosts.value,
659
+ connections: connections.value,
660
+ scope: args.flags.has("--global") ? { scope: "global" } : { scope: "workspace", workspace },
661
+ },
662
+ };
663
+ }
664
+ async function finalizeMigration(ctx, data, input) {
665
+ const finalization = {
666
+ receipts: [],
667
+ receiptErrors: [],
668
+ readbackErrors: [],
669
+ nativeErrors: [],
670
+ probeTargets: [],
671
+ };
672
+ const receipts = openMcpHostReceiptService(ctx.paths);
673
+ for (const item of data.items) {
674
+ await finalizeRetiredAliasReceipts(ctx, data, input, receipts, finalization, item);
675
+ await finalizeMigrationItem(ctx, data, input, receipts, finalization, item);
676
+ }
677
+ attachMigrationFinalization(data, finalization);
678
+ if (data.scope === "global")
679
+ await finalizeMigrationProbes(ctx, data, finalization);
680
+ }
681
+ async function finalizeRetiredAliasReceipts(ctx, data, input, receipts, finalization, item) {
682
+ if (data.scope !== "global" || item.retirements === undefined)
683
+ return;
684
+ const connection = input.connections.find((candidate) => candidate.name === item.instance);
685
+ if (connection === undefined)
686
+ return;
687
+ for (const retirement of item.retirements) {
688
+ if (retirement.action !== "retire-known-legacy" || retirement.readback_state !== "missing") {
689
+ continue;
690
+ }
691
+ const aliasConnection = { ...connection, name: retirement.instance };
692
+ const entry = buildMcpEntry(aliasConnection.name, aliasConnection.dsnVar, {
693
+ host: item.host,
694
+ scope: data.scope,
695
+ namespace: ctx.paths.namespace,
696
+ });
697
+ try {
698
+ const snapshot = readMcpEntry(item.host, data.scope_dir, entry.name, data.scope);
699
+ if (classifyMcpEntry(item.host, snapshot, entry, aliasConnection).state !== "missing") {
700
+ finalization.readbackErrors.push({
701
+ host: item.host,
702
+ instance: retirement.instance,
703
+ message: "La entrada MCP legacy reapareció antes de retirar su recibo; se conserva la evidencia operativa.",
704
+ });
705
+ continue;
706
+ }
707
+ await receipts.remove({
708
+ host: item.host,
709
+ scope: data.scope,
710
+ connection: retirement.instance,
711
+ });
712
+ }
713
+ catch {
714
+ finalization.receiptErrors.push({
715
+ host: item.host,
716
+ instance: retirement.instance,
717
+ message: "La entrada MCP legacy se retiró, pero no se pudo retirar su recibo operativo.",
718
+ });
719
+ }
720
+ }
721
+ }
722
+ async function finalizeMigrationItem(ctx, data, input, receipts, finalization, item) {
723
+ const write = eligibleMigrationWrite(item);
724
+ if (write === undefined)
725
+ return;
726
+ const connection = input.connections.find((candidate) => candidate.name === item.instance);
727
+ if (connection === undefined)
728
+ return;
729
+ if (write.partial !== undefined) {
730
+ finalization.readbackErrors.push({
731
+ host: item.host,
732
+ instance: connection.name,
733
+ message: write.partial.message,
734
+ });
735
+ return;
736
+ }
737
+ if (item.readback_state !== "current") {
738
+ finalization.readbackErrors.push(migrationReadbackError(item.host, connection.name));
739
+ return;
740
+ }
741
+ if (data.scope !== "global")
742
+ return;
743
+ const current = readCurrentMigrationEntry(ctx, data, item, connection);
744
+ if (current === undefined) {
745
+ finalization.readbackErrors.push(migrationReadbackError(item.host, connection.name));
746
+ return;
747
+ }
748
+ const registration = await registerMigrationReceipt(ctx, receipts, item, connection, current.descriptor);
749
+ if (registration === "skipped")
750
+ return;
751
+ if (registration === undefined) {
752
+ finalization.receiptErrors.push({
753
+ host: item.host,
754
+ instance: connection.name,
755
+ message: "No se pudo registrar el recibo MCP; revisá el estado antes de recargar el host.",
756
+ });
757
+ return;
758
+ }
759
+ finalization.receipts.push({
760
+ host: item.host,
761
+ instance: connection.name,
762
+ descriptor_digest: registration.descriptor_digest,
763
+ reload_required: true,
764
+ });
765
+ finalization.probeTargets.push({
766
+ host: item.host,
767
+ instance: connection.name,
768
+ target: item.target,
769
+ entry: current.entry,
770
+ });
771
+ }
772
+ function eligibleMigrationWrite(item) {
773
+ // A retirement can fail only after qtc-* was written and reread. That
774
+ // partial migration still changed a live descriptor and must receive the
775
+ // same revocable receipt/reload signal as a fully retired alias; the item
776
+ // remains failed overall and the command still reports recovery work.
777
+ if (item.write?.action === "written" || item.write?.action === "skipped-idempotent") {
778
+ return item.write;
779
+ }
780
+ return undefined;
781
+ }
782
+ function readCurrentMigrationEntry(ctx, data, item, connection) {
783
+ try {
784
+ const entry = buildMcpEntry(connection.name, connection.dsnVar, {
785
+ host: item.host,
786
+ scope: data.scope,
787
+ namespace: ctx.paths.namespace,
788
+ });
789
+ const snapshot = readMcpEntry(item.host, data.scope_dir, entry.name, data.scope);
790
+ if (snapshot.command === undefined ||
791
+ snapshot.args === undefined ||
792
+ classifyMcpEntry(item.host, snapshot, entry, connection).state !== "current") {
793
+ return undefined;
794
+ }
795
+ return { entry, descriptor: { command: snapshot.command, args: snapshot.args } };
796
+ }
797
+ catch {
798
+ return undefined;
799
+ }
800
+ }
801
+ async function registerMigrationReceipt(ctx, receipts, item, connection, descriptor) {
802
+ try {
803
+ const identity = { host: item.host, scope: "global", connection: connection.name };
804
+ // A qtc descriptor can be byte-identical while this migration retired a
805
+ // legacy alias from the same live host config. That is still a mutation:
806
+ // renew the receipt so reload_required cannot remain falsely cleared from
807
+ // an earlier host load that may still expose the retired alias.
808
+ if (item.write?.action === "skipped-idempotent" && item.configuration_changed !== true) {
809
+ const existing = await receipts.find(identity);
810
+ if (existing !== undefined &&
811
+ existing.descriptor_digest === digestMcpReceiptDescriptor(descriptor)) {
812
+ return "skipped";
813
+ }
814
+ }
815
+ return await registerPersistedMcpDescriptor(ctx.paths, {
816
+ host: item.host,
817
+ scope: "global",
818
+ connection: connection.name,
819
+ entry: descriptor,
820
+ });
821
+ }
822
+ catch {
823
+ return undefined;
824
+ }
825
+ }
826
+ function migrationReadbackError(host, instance) {
827
+ return {
828
+ host,
829
+ instance,
830
+ message: "La entrada MCP escrita no coincidió al leerla de vuelta; no se registró recibo ni se debe recargar el host.",
831
+ };
832
+ }
833
+ function attachMigrationFinalization(data, state) {
834
+ if (state.receipts.length > 0)
835
+ data.receipts = state.receipts;
836
+ if (state.receiptErrors.length > 0)
837
+ data.receipt_errors = state.receiptErrors;
838
+ if (state.readbackErrors.length > 0)
839
+ data.readback_errors = state.readbackErrors;
840
+ if (state.nativeErrors.length > 0)
841
+ data.native_errors = state.nativeErrors;
842
+ }
843
+ async function finalizeMigrationProbes(ctx, data, state) {
844
+ const launch = await probePersistedMcpSetupEntries(ctx.paths, {
845
+ scope: "global",
846
+ scope_dir: data.scope_dir,
847
+ dry_run: false,
848
+ applied: [],
849
+ skipped: [],
850
+ conflicts: [],
851
+ errors: [],
852
+ }, state.probeTargets);
853
+ if (launch.probes.length > 0)
854
+ data.launch_probes = launch.probes;
855
+ if (launch.errors.length > 0) {
856
+ data.probe_errors = launch.errors.map((error) => ({
857
+ host: error.host,
858
+ instance: error.instance,
859
+ message: error.message,
860
+ }));
861
+ }
862
+ const native = await checkNativeMcpHosts(state.probeTargets);
863
+ await recordNativeMcpHostChecks({
864
+ paths: ctx.paths,
865
+ scope: "global",
866
+ scopeDir: data.scope_dir,
867
+ targets: state.probeTargets,
868
+ checks: native.checks,
869
+ });
870
+ if (native.checks.length > 0)
871
+ data.native_checks = native.checks;
872
+ state.nativeErrors.push(...native.errors.map((error) => ({
873
+ host: error.host,
874
+ instance: error.instance,
875
+ message: error.message,
876
+ })));
877
+ if (state.nativeErrors.length > 0)
878
+ data.native_errors = state.nativeErrors;
879
+ }
880
+ function migrationCommandResult(data, apply) {
881
+ const blocked = data.items.filter((item) => item.action === "blocked").length;
882
+ const failedWrites = data.items.filter((item) => item.write?.action === "conflict" || item.action === "failed").length;
883
+ const receiptFailures = data.receipt_errors?.length ?? 0;
884
+ const readbackFailures = data.readback_errors?.length ?? 0;
885
+ const probeFailures = data.probe_errors?.length ?? 0;
886
+ const nativeFailures = data.native_errors?.length ?? 0;
887
+ const problems = blocked + failedWrites + receiptFailures + readbackFailures + probeFailures + nativeFailures;
888
+ const hasProblems = apply && problems > 0;
889
+ return {
890
+ ok: !hasProblems,
891
+ data,
892
+ ...(hasProblems
893
+ ? {
215
894
  error: {
216
- code: "MCP_DOCTOR_DRIFT",
217
- message: `${total - okCount}/${total} entradas con drift (ver data.reports)`,
895
+ code: "MCP_MIGRATE_PARTIAL",
896
+ message: `${problems} entrada(s) no se migraron, no dejaron recibo, no pasaron readback, no fueron launchable o no pudieron verificarse con su host.`,
218
897
  },
219
- }),
220
- exitCode: allOk ? 0 : 1,
898
+ }
899
+ : {}),
900
+ exitCode: hasProblems ? 1 : 0,
221
901
  };
222
902
  }
903
+ function isMcpMigrationRefusal(value) {
904
+ return "ok" in value && value.ok === false;
905
+ }
223
906
  /** Exported for tests: host-harness detection must cover every file-writing host. */
224
907
  export function resolveHosts(args, ctx) {
225
908
  const flag = args.values.get("host");
@@ -257,7 +940,7 @@ function resolveConnections(args, ctx, allowAll) {
257
940
  return fail("INVALID_INPUT", "--all-connections no recibe valor.");
258
941
  }
259
942
  if (allConnections && !allowAll) {
260
- return fail("MCP_CONNECTION_SELECTION_CONFLICT", "mcp dbhub ejecuta una sola conexión; usá --instance <nombre>.");
943
+ return fail("MCP_CONNECTION_SELECTION_CONFLICT", "mcp serve-db ejecuta una sola conexión; usá --instance <nombre>.");
261
944
  }
262
945
  const instance = args.values.get("instance");
263
946
  const selection = resolveMcpConnectionSelection(ctx.paths, {
@@ -268,11 +951,4 @@ function resolveConnections(args, ctx, allowAll) {
268
951
  return fail(selection.code, selection.message);
269
952
  return { value: selection.connections };
270
953
  }
271
- function clampExit(code) {
272
- if (code === 0)
273
- return 0;
274
- if (code === 2)
275
- return 2;
276
- return 1;
277
- }
278
954
  //# sourceMappingURL=mcp.js.map