swallowkit 2.0.0-alpha.1 → 2.0.0-alpha.10

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 (224) hide show
  1. package/README.ja.md +14 -0
  2. package/README.md +14 -0
  3. package/dist/cli/commands/add-auth.js +37 -6
  4. package/dist/cli/commands/add-auth.js.map +1 -1
  5. package/dist/cli/commands/configure-mcp.d.ts +12 -0
  6. package/dist/cli/commands/configure-mcp.d.ts.map +1 -0
  7. package/dist/cli/commands/configure-mcp.js +42 -0
  8. package/dist/cli/commands/configure-mcp.js.map +1 -0
  9. package/dist/cli/commands/dev.d.ts +2 -2
  10. package/dist/cli/commands/dev.d.ts.map +1 -1
  11. package/dist/cli/commands/dev.js +39 -27
  12. package/dist/cli/commands/dev.js.map +1 -1
  13. package/dist/cli/commands/init.d.ts +8 -2
  14. package/dist/cli/commands/init.d.ts.map +1 -1
  15. package/dist/cli/commands/init.js +67 -132
  16. package/dist/cli/commands/init.js.map +1 -1
  17. package/dist/cli/commands/migrations.d.ts.map +1 -1
  18. package/dist/cli/commands/migrations.js +25 -0
  19. package/dist/cli/commands/migrations.js.map +1 -1
  20. package/dist/cli/commands/provision.d.ts +5 -0
  21. package/dist/cli/commands/provision.d.ts.map +1 -1
  22. package/dist/cli/commands/provision.js +278 -55
  23. package/dist/cli/commands/provision.js.map +1 -1
  24. package/dist/cli/commands/scaffold.d.ts +4 -0
  25. package/dist/cli/commands/scaffold.d.ts.map +1 -1
  26. package/dist/cli/commands/scaffold.js +85 -3
  27. package/dist/cli/commands/scaffold.js.map +1 -1
  28. package/dist/cli/index.d.ts +2 -0
  29. package/dist/cli/index.d.ts.map +1 -1
  30. package/dist/cli/index.js +43 -5
  31. package/dist/cli/index.js.map +1 -1
  32. package/dist/core/operations/create-model.d.ts +2 -0
  33. package/dist/core/operations/create-model.d.ts.map +1 -1
  34. package/dist/core/operations/create-model.js +60 -8
  35. package/dist/core/operations/create-model.js.map +1 -1
  36. package/dist/core/operations/migration-generator.d.ts.map +1 -1
  37. package/dist/core/operations/migration-generator.js +12 -1
  38. package/dist/core/operations/migration-generator.js.map +1 -1
  39. package/dist/core/operations/model-migration.d.ts +22 -0
  40. package/dist/core/operations/model-migration.d.ts.map +1 -0
  41. package/dist/core/operations/model-migration.js +36 -0
  42. package/dist/core/operations/model-migration.js.map +1 -0
  43. package/dist/core/operations/provision-deployment.d.ts +52 -0
  44. package/dist/core/operations/provision-deployment.d.ts.map +1 -0
  45. package/dist/core/operations/provision-deployment.js +164 -0
  46. package/dist/core/operations/provision-deployment.js.map +1 -0
  47. package/dist/core/operations/provision-operations.d.ts +62 -6
  48. package/dist/core/operations/provision-operations.d.ts.map +1 -1
  49. package/dist/core/operations/provision-operations.js +653 -115
  50. package/dist/core/operations/provision-operations.js.map +1 -1
  51. package/dist/core/operations/scaffold-apply.d.ts +1 -0
  52. package/dist/core/operations/scaffold-apply.d.ts.map +1 -1
  53. package/dist/core/operations/scaffold-apply.js +1 -0
  54. package/dist/core/operations/scaffold-apply.js.map +1 -1
  55. package/dist/core/operations/scaffold-machine.d.ts +1 -0
  56. package/dist/core/operations/scaffold-machine.d.ts.map +1 -1
  57. package/dist/core/operations/scaffold-machine.js.map +1 -1
  58. package/dist/core/operations/scaffold-plan.d.ts +2 -0
  59. package/dist/core/operations/scaffold-plan.d.ts.map +1 -1
  60. package/dist/core/operations/scaffold-plan.js +3 -1
  61. package/dist/core/operations/scaffold-plan.js.map +1 -1
  62. package/dist/core/project/agent-instructions.d.ts.map +1 -1
  63. package/dist/core/project/agent-instructions.js +4 -1
  64. package/dist/core/project/agent-instructions.js.map +1 -1
  65. package/dist/core/project/boundaries.d.ts +5 -2
  66. package/dist/core/project/boundaries.d.ts.map +1 -1
  67. package/dist/core/project/boundaries.js +51 -20
  68. package/dist/core/project/boundaries.js.map +1 -1
  69. package/dist/core/project/capabilities.d.ts +10 -0
  70. package/dist/core/project/capabilities.d.ts.map +1 -1
  71. package/dist/core/project/capabilities.js +38 -6
  72. package/dist/core/project/capabilities.js.map +1 -1
  73. package/dist/core/project/home-page.d.ts +4 -0
  74. package/dist/core/project/home-page.d.ts.map +1 -0
  75. package/dist/core/project/home-page.js +113 -0
  76. package/dist/core/project/home-page.js.map +1 -0
  77. package/dist/core/project/mcp-config.d.ts +26 -0
  78. package/dist/core/project/mcp-config.d.ts.map +1 -0
  79. package/dist/core/project/mcp-config.js +117 -0
  80. package/dist/core/project/mcp-config.js.map +1 -0
  81. package/dist/core/project/react-project.d.ts +1 -1
  82. package/dist/core/project/react-project.d.ts.map +1 -1
  83. package/dist/core/project/react-project.js +46 -25
  84. package/dist/core/project/react-project.js.map +1 -1
  85. package/dist/core/project/state.d.ts +7 -0
  86. package/dist/core/project/state.d.ts.map +1 -1
  87. package/dist/core/project/state.js +86 -0
  88. package/dist/core/project/state.js.map +1 -1
  89. package/dist/core/project/workflows.d.ts.map +1 -1
  90. package/dist/core/project/workflows.js +53 -7
  91. package/dist/core/project/workflows.js.map +1 -1
  92. package/dist/core/scaffold/auth-generator.d.ts.map +1 -1
  93. package/dist/core/scaffold/auth-generator.js +26 -27
  94. package/dist/core/scaffold/auth-generator.js.map +1 -1
  95. package/dist/core/scaffold/model-parser.d.ts +23 -0
  96. package/dist/core/scaffold/model-parser.d.ts.map +1 -1
  97. package/dist/core/scaffold/model-parser.js +407 -137
  98. package/dist/core/scaffold/model-parser.js.map +1 -1
  99. package/dist/core/scaffold/native-schema-generator.d.ts +13 -0
  100. package/dist/core/scaffold/native-schema-generator.d.ts.map +1 -1
  101. package/dist/core/scaffold/native-schema-generator.js +294 -91
  102. package/dist/core/scaffold/native-schema-generator.js.map +1 -1
  103. package/dist/core/scaffold/openapi-generator.d.ts +5 -0
  104. package/dist/core/scaffold/openapi-generator.d.ts.map +1 -1
  105. package/dist/core/scaffold/openapi-generator.js +53 -3
  106. package/dist/core/scaffold/openapi-generator.js.map +1 -1
  107. package/dist/core/scaffold/ui-generator.d.ts +0 -3
  108. package/dist/core/scaffold/ui-generator.d.ts.map +1 -1
  109. package/dist/core/scaffold/ui-generator.js +68 -7
  110. package/dist/core/scaffold/ui-generator.js.map +1 -1
  111. package/dist/core/verify/index.d.ts +4 -3
  112. package/dist/core/verify/index.d.ts.map +1 -1
  113. package/dist/core/verify/index.js +122 -5
  114. package/dist/core/verify/index.js.map +1 -1
  115. package/dist/machine/contracts.d.ts +1 -0
  116. package/dist/machine/contracts.d.ts.map +1 -1
  117. package/dist/machine/errors.d.ts +3 -2
  118. package/dist/machine/errors.d.ts.map +1 -1
  119. package/dist/machine/errors.js +2 -1
  120. package/dist/machine/errors.js.map +1 -1
  121. package/dist/machine/index.d.ts.map +1 -1
  122. package/dist/machine/index.js +59 -28
  123. package/dist/machine/index.js.map +1 -1
  124. package/dist/mcp/index.d.ts +6 -4
  125. package/dist/mcp/index.d.ts.map +1 -1
  126. package/dist/mcp/index.js +210 -25
  127. package/dist/mcp/index.js.map +1 -1
  128. package/dist/mcp/version-selection.d.ts +55 -0
  129. package/dist/mcp/version-selection.d.ts.map +1 -0
  130. package/dist/mcp/version-selection.js +152 -0
  131. package/dist/mcp/version-selection.js.map +1 -0
  132. package/dist/utils/azure-cli.d.ts +56 -0
  133. package/dist/utils/azure-cli.d.ts.map +1 -0
  134. package/dist/utils/azure-cli.js +326 -0
  135. package/dist/utils/azure-cli.js.map +1 -0
  136. package/dist/utils/azure-diagnostics.d.ts +16 -0
  137. package/dist/utils/azure-diagnostics.d.ts.map +1 -0
  138. package/dist/utils/azure-diagnostics.js +68 -0
  139. package/dist/utils/azure-diagnostics.js.map +1 -0
  140. package/dist/utils/package-manager.js +2 -2
  141. package/dist/utils/package-manager.js.map +1 -1
  142. package/dist/utils/python-uv.d.ts +5 -7
  143. package/dist/utils/python-uv.d.ts.map +1 -1
  144. package/dist/utils/python-uv.js +50 -24
  145. package/dist/utils/python-uv.js.map +1 -1
  146. package/dist/utils/react-dev.d.ts +4 -0
  147. package/dist/utils/react-dev.d.ts.map +1 -0
  148. package/dist/utils/react-dev.js +35 -0
  149. package/dist/utils/react-dev.js.map +1 -0
  150. package/dist/utils/semver.d.ts +14 -0
  151. package/dist/utils/semver.d.ts.map +1 -0
  152. package/dist/utils/semver.js +61 -0
  153. package/dist/utils/semver.js.map +1 -0
  154. package/package.json +6 -3
  155. package/src/__tests__/__snapshots__/boundaries.test.ts.snap +154 -0
  156. package/src/__tests__/agent-instructions.test.ts +5 -0
  157. package/src/__tests__/agent-loop-phase34.test.ts +143 -8
  158. package/src/__tests__/auth.test.ts +9 -0
  159. package/src/__tests__/azure-cli.test.ts +202 -0
  160. package/src/__tests__/boundaries.test.ts +42 -0
  161. package/src/__tests__/csharp-schema.test.ts +261 -0
  162. package/src/__tests__/dev.test.ts +13 -6
  163. package/src/__tests__/fixtures/az wrapper.cmd +2 -0
  164. package/src/__tests__/fixtures/azure-cli.cjs +153 -0
  165. package/src/__tests__/fixtures/provision-runner.cjs +28 -0
  166. package/src/__tests__/generated-project-matrix.test.ts +789 -0
  167. package/src/__tests__/home-page.test.ts +50 -0
  168. package/src/__tests__/init.test.ts +116 -0
  169. package/src/__tests__/mcp-version.test.ts +262 -0
  170. package/src/__tests__/mcp.test.ts +120 -3
  171. package/src/__tests__/multi-auth.test.ts +115 -4
  172. package/src/__tests__/package-manager.test.ts +17 -2
  173. package/src/__tests__/plan-apply.test.ts +179 -0
  174. package/src/__tests__/provision-runtime.test.ts +722 -0
  175. package/src/__tests__/python-uv.test.ts +50 -14
  176. package/src/__tests__/python-venv.test.ts +48 -0
  177. package/src/__tests__/react-dev.test.ts +72 -0
  178. package/src/__tests__/react.test.ts +19 -0
  179. package/src/__tests__/schema-analysis.test.ts +329 -0
  180. package/src/cli/commands/add-auth.ts +36 -6
  181. package/src/cli/commands/configure-mcp.ts +54 -0
  182. package/src/cli/commands/dev.ts +40 -31
  183. package/src/cli/commands/init.ts +73 -138
  184. package/src/cli/commands/migrations.ts +22 -0
  185. package/src/cli/commands/provision.ts +294 -56
  186. package/src/cli/commands/scaffold.ts +105 -3
  187. package/src/cli/index.ts +40 -7
  188. package/src/core/operations/create-model.ts +51 -13
  189. package/src/core/operations/migration-generator.ts +14 -1
  190. package/src/core/operations/model-migration.ts +52 -0
  191. package/src/core/operations/provision-deployment.ts +183 -0
  192. package/src/core/operations/provision-operations.ts +662 -154
  193. package/src/core/operations/scaffold-apply.ts +2 -0
  194. package/src/core/operations/scaffold-machine.ts +1 -0
  195. package/src/core/operations/scaffold-plan.ts +5 -1
  196. package/src/core/project/agent-instructions.ts +4 -1
  197. package/src/core/project/agent-templates/AGENTS.md +89 -6
  198. package/src/core/project/agent-templates/CLAUDE.md +14 -1
  199. package/src/core/project/agent-templates/copilot-instructions.md +15 -1
  200. package/src/core/project/agent-templates/shared-models.instructions.md +11 -3
  201. package/src/core/project/boundaries.ts +62 -20
  202. package/src/core/project/capabilities.ts +49 -6
  203. package/src/core/project/home-page.ts +114 -0
  204. package/src/core/project/mcp-config.ts +84 -0
  205. package/src/core/project/react-project.ts +50 -28
  206. package/src/core/project/state.ts +70 -0
  207. package/src/core/project/workflows.ts +53 -7
  208. package/src/core/scaffold/auth-generator.ts +26 -27
  209. package/src/core/scaffold/model-parser.ts +413 -129
  210. package/src/core/scaffold/native-schema-generator.ts +319 -103
  211. package/src/core/scaffold/openapi-generator.ts +63 -5
  212. package/src/core/scaffold/ui-generator.ts +70 -8
  213. package/src/core/verify/index.ts +118 -6
  214. package/src/machine/contracts.ts +1 -0
  215. package/src/machine/errors.ts +4 -2
  216. package/src/machine/index.ts +65 -34
  217. package/src/mcp/index.ts +221 -31
  218. package/src/mcp/version-selection.ts +163 -0
  219. package/src/utils/azure-cli.ts +315 -0
  220. package/src/utils/azure-diagnostics.ts +75 -0
  221. package/src/utils/package-manager.ts +2 -2
  222. package/src/utils/python-uv.ts +53 -33
  223. package/src/utils/react-dev.ts +31 -0
  224. package/src/utils/semver.ts +59 -0
@@ -3,7 +3,7 @@ import { isSwallowKitProject } from "../core/config";
3
3
  import { createModelOperation } from "../core/operations/create-model";
4
4
  import { applyScaffoldOperation } from "../core/operations/scaffold-apply";
5
5
  import { applyAuthOperation, planAuthOperation } from "../core/operations/auth-operations";
6
- import { applyProvisionOperation, planProvisionOperation } from "../core/operations/provision-operations";
6
+ import { applyProvisionOperation, inspectProvisionOperation, planProvisionOperation } from "../core/operations/provision-operations";
7
7
  import { ProcessExitInterceptError } from "../core/operations/runtime";
8
8
  import { inspectInfra } from "../core/project/infra";
9
9
  import { runMachineScaffoldOperation } from "../core/operations/scaffold-machine";
@@ -58,6 +58,7 @@ function writeMachineError(command: string, error: unknown): void {
58
58
  ok: false,
59
59
  command,
60
60
  status: resolveMachineErrorStatus(error),
61
+ ...(error instanceof MachineCommandError && error.nextActions?.length ? { nextActions: error.nextActions } : {}),
61
62
  error: toMachineError(error),
62
63
  };
63
64
  writeMachineResponse(response);
@@ -140,6 +141,17 @@ function createMachineProgram(): Command {
140
141
  .showHelpAfterError(false);
141
142
 
142
143
  const inspect = new Command("inspect");
144
+ inspect
145
+ .command("provision")
146
+ .description("Read/wait for an original provision deployment without writes or replay")
147
+ .requiredOption("--plan <runId>", "Original provision plan/execution checkpoint ID")
148
+ .option("--wait-seconds <seconds>", "Bounded read-only wait, 0-3600 seconds (default: 0)", Number)
149
+ .action(async (options: { plan: string; waitSeconds?: number }) => {
150
+ await handleMachineAction("inspect-provision", async () => {
151
+ ensureProjectForMachine();
152
+ return inspectProvisionOperation({ planId: options.plan, waitSeconds: options.waitSeconds });
153
+ }, (result) => ({ status: result.status, nextActions: result.nextActions }));
154
+ });
143
155
  inspect
144
156
  .command("project")
145
157
  .description("Inspect SwallowKit project metadata")
@@ -274,7 +286,8 @@ function createMachineProgram(): Command {
274
286
  .option("--functions-dir <dir>", "Functions directory", "functions")
275
287
  .option("--api-dir <dir>", "BFF routes directory (Next.js: app/api; React: bff)")
276
288
  .option("--api-only", "Skip UI components; still update Functions, BFF routes, OpenAPI, and native schema assets", false)
277
- .action(async (model: string, options: { functionsDir?: string; apiDir?: string; apiOnly?: boolean }) => {
289
+ .option("--skip-infra", "Do not create or update anything under infra/ (container migration is reported as pending)", false)
290
+ .action(async (model: string, options: { functionsDir?: string; apiDir?: string; apiOnly?: boolean; skipInfra?: boolean }) => {
278
291
  await handleMachineAction("generate-scaffold", async () => {
279
292
  ensureProjectForMachine();
280
293
  return runMachineScaffoldOperation({
@@ -282,6 +295,7 @@ function createMachineProgram(): Command {
282
295
  functionsDir: options.functionsDir,
283
296
  apiDir: options.apiDir,
284
297
  apiOnly: options.apiOnly,
298
+ skipInfra: options.skipInfra,
285
299
  });
286
300
  });
287
301
  });
@@ -294,7 +308,8 @@ function createMachineProgram(): Command {
294
308
  .option("--functions-dir <dir>", "Functions directory", "functions")
295
309
  .option("--api-dir <dir>", "BFF routes directory (Next.js: app/api; React: bff)")
296
310
  .option("--api-only", "Skip UI components in the plan", false)
297
- .action(async (models: string[], options: { functionsDir?: string; apiDir?: string; apiOnly?: boolean }) => {
311
+ .option("--skip-infra", "Exclude all infra/ changes (container migration) from the plan", false)
312
+ .action(async (models: string[], options: { functionsDir?: string; apiDir?: string; apiOnly?: boolean; skipInfra?: boolean }) => {
298
313
  await handleMachineAction(
299
314
  "plan-scaffold",
300
315
  async () => {
@@ -307,6 +322,7 @@ function createMachineProgram(): Command {
307
322
  functionsDir: options.functionsDir,
308
323
  apiDir: options.apiDir,
309
324
  apiOnly: options.apiOnly,
325
+ skipInfra: options.skipInfra,
310
326
  })
311
327
  );
312
328
  }
@@ -316,12 +332,20 @@ function createMachineProgram(): Command {
316
332
  const plans = "plans" in data ? data.plans : [data];
317
333
  return {
318
334
  status: plans.some((planData) => planData.requiresApproval) ? "requires-human" : "complete",
319
- nextActions: plans.map((planData) => ({
320
- command: `swallowkit machine apply scaffold --plan ${planData.planId}${planData.requiresApproval ? " --approve" : ""}`,
321
- description: planData.requiresApproval
322
- ? `Apply the plan for ${planData.model} after reviewing the listed conflicts (requires --approve).`
323
- : `Apply the plan for ${planData.model}.`,
324
- })),
335
+ nextActions: [
336
+ ...plans.map((planData) => ({
337
+ command: `swallowkit machine apply scaffold --plan ${planData.planId}${planData.requiresApproval ? " --approve" : ""}`,
338
+ description: planData.requiresApproval
339
+ ? `Apply the plan for ${planData.model} after reviewing the listed conflicts (requires --approve).`
340
+ : `Apply the plan for ${planData.model}.`,
341
+ })),
342
+ ...plans
343
+ .filter((planData) => planData.warnings.some((warning) => warning.startsWith("infra-skipped:")))
344
+ .map((planData) => ({
345
+ command: `swallowkit migrations add-model ${planData.model}`,
346
+ description: `Later, when infrastructure changes are allowed, generate only the Cosmos DB container migration for ${planData.model}.`,
347
+ })),
348
+ ],
325
349
  };
326
350
  }
327
351
  );
@@ -360,7 +384,7 @@ function createMachineProgram(): Command {
360
384
 
361
385
  plan
362
386
  .command("provision")
363
- .description("Preflight Azure provisioning locally; use --what-if for an az what-if analysis (requires az login)")
387
+ .description("Review local infrastructure and current Azure state without writes (requires az login)")
364
388
  .requiredOption("-g, --resource-group <name>", "Resource group name")
365
389
  .requiredOption("--location <region>", "Primary location for Functions and Cosmos DB")
366
390
  .requiredOption("--swa-location <region>", "Static Web App location")
@@ -369,7 +393,8 @@ function createMachineProgram(): Command {
369
393
  .option("--adopt-existing", "Adopt an existing untagged resource group without deploying main.bicep", false)
370
394
  .option("--baseline <version>", "Baseline version for adoption", (value) => Number(value))
371
395
  .option("--reconcile", "Explicitly redeploy infra/main.bicep", false)
372
- .action(async (options: { resourceGroup: string; location: string; swaLocation: string; subscription?: string; whatIf?: boolean; adoptExisting?: boolean; baseline?: number; reconcile?: boolean }) => {
396
+ .option("--resume-deployment <name>", "Recover a matching older baseline deployment without re-submitting Bicep")
397
+ .action(async (options: { resourceGroup: string; location: string; swaLocation: string; subscription?: string; whatIf?: boolean; adoptExisting?: boolean; baseline?: number; reconcile?: boolean; resumeDeployment?: string }) => {
373
398
  await handleMachineAction(
374
399
  "plan-provision",
375
400
  async () => {
@@ -383,6 +408,7 @@ function createMachineProgram(): Command {
383
408
  adoptExisting: options.adoptExisting,
384
409
  baseline: options.baseline,
385
410
  reconcile: options.reconcile,
411
+ resumeDeployment: options.resumeDeployment,
386
412
  });
387
413
  },
388
414
  (planData) => ({
@@ -408,10 +434,11 @@ function createMachineProgram(): Command {
408
434
  .option("--functions-dir <dir>", "Functions directory")
409
435
  .option("--api-dir <dir>", "API routes directory")
410
436
  .option("--api-only", "Skip UI components; still update Functions, BFF routes, OpenAPI, and native schema assets")
437
+ .option("--skip-infra", "Do not create or update anything under infra/ (container migration is reported as pending)")
411
438
  .action(
412
439
  async (
413
440
  models: string[],
414
- options: { plan?: string; approve?: boolean; functionsDir?: string; apiDir?: string; apiOnly?: boolean }
441
+ options: { plan?: string; approve?: boolean; functionsDir?: string; apiDir?: string; apiOnly?: boolean; skipInfra?: boolean }
415
442
  ) => {
416
443
  await handleMachineAction(
417
444
  "apply-scaffold",
@@ -433,6 +460,7 @@ function createMachineProgram(): Command {
433
460
  functionsDir: options.functionsDir,
434
461
  apiDir: options.apiDir,
435
462
  apiOnly: options.apiOnly,
463
+ skipInfra: options.skipInfra,
436
464
  });
437
465
  }
438
466
  const results = [];
@@ -444,20 +472,30 @@ function createMachineProgram(): Command {
444
472
  functionsDir: options.functionsDir,
445
473
  apiDir: options.apiDir,
446
474
  apiOnly: options.apiOnly,
475
+ skipInfra: options.skipInfra,
447
476
  })
448
477
  );
449
478
  }
450
479
  return { applyType: "scaffold-batch" as const, results };
451
480
  },
452
- () => ({
453
- status: "complete",
454
- nextActions: [
455
- {
456
- command: "swallowkit machine verify project",
457
- description: "Verify the project after applying scaffold changes.",
458
- },
459
- ],
460
- })
481
+ (data) => {
482
+ const results = "results" in data ? data.results : [data];
483
+ return {
484
+ status: "complete",
485
+ nextActions: [
486
+ {
487
+ command: "swallowkit machine verify project",
488
+ description: "Verify the project after applying scaffold changes.",
489
+ },
490
+ ...results
491
+ .filter((result) => result.warnings.some((warning) => warning.startsWith("infra-skipped:")))
492
+ .map((result) => ({
493
+ command: `swallowkit migrations add-model ${result.model}`,
494
+ description: `The Cosmos DB container migration for ${result.model} is still pending; generate it when infrastructure changes are allowed.`,
495
+ })),
496
+ ],
497
+ };
498
+ }
461
499
  );
462
500
  }
463
501
  );
@@ -521,32 +559,25 @@ function createMachineProgram(): Command {
521
559
  .description("Apply an approved provisioning plan (always requires --approve; creates billable Azure resources)")
522
560
  .requiredOption("--plan <planId>", "Provision plan to apply")
523
561
  .option("--approve", "Confirm human approval of the provisioning plan", false)
524
- .action(async (options: { plan: string; approve?: boolean }) => {
562
+ .option("--wait-seconds <seconds>", "Bounded state wait, 0-3600 seconds (default: 300)", Number)
563
+ .action(async (options: { plan: string; approve?: boolean; waitSeconds?: number }) => {
525
564
  await handleMachineAction(
526
565
  "apply-provision",
527
566
  async () => {
528
567
  ensureProjectForMachine();
529
- return applyProvisionOperation({ planId: options.plan, approve: options.approve });
568
+ return applyProvisionOperation({ planId: options.plan, approve: options.approve, waitSeconds: options.waitSeconds });
530
569
  },
531
- () => ({
532
- status: "complete",
533
- nextActions: [
534
- {
535
- command: "swallowkit machine inspect infra",
536
- description: "Re-inspect infra assets after provisioning.",
537
- },
538
- ],
539
- })
570
+ (result) => ({ status: result.status, nextActions: result.nextActions })
540
571
  );
541
572
  });
542
573
 
543
574
  const verify = new Command("verify");
544
575
  verify
545
576
  .command("project")
546
- .description("Run verification checks (structure, drift, typecheck) with machine-readable evidence")
577
+ .description("Run verification checks (structure, drift, typecheck, backend-build) with machine-readable evidence")
547
578
  .option(
548
579
  "--checks <ids>",
549
- "Comma-separated check ids to run (structure,drift,typecheck,build,lint,test, plus custom ids from swallowkit.config verify.checks)"
580
+ "Comma-separated check ids to run (structure,drift,typecheck,backend-build,build,lint,test, plus custom ids from swallowkit.config verify.checks); quote CSV values in PowerShell"
550
581
  )
551
582
  .option("--compact", "Suppress info-severity findings/violations from evidence to keep agent-facing output small", false)
552
583
  .action(async (options: { checks?: string; compact?: boolean }) => {
package/src/mcp/index.ts CHANGED
@@ -1,11 +1,21 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ import { spawn } from "child_process";
3
4
  import * as path from "path";
4
5
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
6
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
7
  import * as z from "zod/v4";
7
- import type { MachineNextAction, MachineOperationStatus, MachineResponse } from "../machine/contracts";
8
+ import type { MachineErrorResponse, MachineNextAction, MachineOperationStatus, MachineResponse } from "../machine/contracts";
8
9
  import { getSwallowKitVersion } from "../version";
10
+ import { detectFromProject, getCommands } from "../utils/package-manager";
11
+ import { provisionNextActions } from "../core/operations/provision-operations";
12
+ import {
13
+ MCP_DELEGATED_FROM_ENV,
14
+ McpVersionContext,
15
+ buildPinnedUpdateNotice,
16
+ decideMcpLaunch,
17
+ resolveProjectSwallowKit,
18
+ } from "./version-selection";
9
19
 
10
20
  interface MachineSuccessPayload<TData> {
11
21
  ok: true;
@@ -15,13 +25,18 @@ interface MachineSuccessPayload<TData> {
15
25
  data: TData;
16
26
  }
17
27
 
18
- type MachineCliRunner = (args: string[]) => Promise<{ stdout: string; stderr: string; exitCode: number }>;
19
- type ToolContentResult = { content: Array<{ type: "text"; text: string }> };
28
+ type MachineCliRunner = (args: string[], signal?: AbortSignal) => Promise<{ stdout: string; stderr: string; exitCode: number }>;
29
+ type ToolContentResult = { content: Array<{ type: "text"; text: string }>; isError?: boolean };
30
+ class MachineCliError extends Error {
31
+ constructor(message: string, readonly response: MachineErrorResponse) {
32
+ super(message);
33
+ }
34
+ }
20
35
  type ToolDefinition = {
21
36
  name: string;
22
37
  description: string;
23
38
  inputSchema: z.ZodTypeAny;
24
- handler: (input: any) => Promise<ToolContentResult>;
39
+ handler: (input: any, signal?: AbortSignal) => Promise<ToolContentResult>;
25
40
  };
26
41
  type ExecaModule = typeof import("execa");
27
42
 
@@ -34,29 +49,45 @@ function resolveMachineCliEntrypoint(): string {
34
49
  return path.resolve(__dirname, "..", "cli", "index.js");
35
50
  }
36
51
 
37
- async function defaultMachineCliRunner(args: string[]): Promise<{ stdout: string; stderr: string; exitCode: number }> {
52
+ async function defaultMachineCliRunner(args: string[], signal?: AbortSignal): Promise<{ stdout: string; stderr: string; exitCode: number }> {
38
53
  const { execa } = await importModule("execa");
39
- const result = await execa(process.execPath, [resolveMachineCliEntrypoint(), "machine", ...args], {
54
+ const child = execa(process.execPath, [resolveMachineCliEntrypoint(), "machine", ...args], {
40
55
  reject: false,
41
56
  });
42
-
57
+ const abort = () => child.kill("SIGTERM", { forceKillAfterTimeout: 6_000 });
58
+ signal?.addEventListener("abort", abort, { once: true });
59
+ if (signal?.aborted) abort();
60
+ let result: Awaited<typeof child>;
61
+ try { result = await child; } finally { signal?.removeEventListener("abort", abort); }
43
62
  return {
44
63
  stdout: result.stdout,
45
64
  stderr: result.stderr,
46
- exitCode: result.exitCode ?? 0,
65
+ exitCode: result.exitCode ?? 1,
47
66
  };
48
67
  }
49
68
 
50
69
  async function executeMachineCommand<TData>(
51
70
  args: string[],
52
- runMachineCli: MachineCliRunner
71
+ runMachineCli: MachineCliRunner,
72
+ signal?: AbortSignal
53
73
  ): Promise<MachineSuccessPayload<TData>> {
54
- const result = await runMachineCli(args);
74
+ const interrupted = () => {
75
+ const runId = args[args.indexOf("--plan") + 1];
76
+ const response: MachineErrorResponse = {
77
+ ok: false, command: args.slice(0, 2).join("-"), status: "blocked",
78
+ error: { code: "provision-interrupted", message: "The local MCP wait was interrupted. Azure may continue; inspect the original checkpoint. No Azure cancellation or successful result is implied.", details: { runId, outcome: "unknown", localFailure: "cancelled" } },
79
+ nextActions: /^[a-f0-9]{12}$/.test(runId) ? provisionNextActions(runId) : [],
80
+ };
81
+ return new MachineCliError(response.error.message, response);
82
+ };
83
+ if (signal?.aborted) throw interrupted();
84
+ const result = await (signal ? runMachineCli(args, signal) : runMachineCli(args));
55
85
 
56
86
  let parsed: MachineResponse<TData>;
57
87
  try {
58
88
  parsed = JSON.parse(result.stdout) as MachineResponse<TData>;
59
89
  } catch {
90
+ if (signal?.aborted) throw interrupted();
60
91
  throw new Error(result.stderr || result.stdout || "Machine CLI returned invalid JSON.");
61
92
  }
62
93
 
@@ -64,12 +95,35 @@ async function executeMachineCommand<TData>(
64
95
  const detailsSuffix =
65
96
  parsed.error.details !== undefined ? `\ndetails: ${JSON.stringify(parsed.error.details)}` : "";
66
97
  const statusSuffix = parsed.status ? ` (status: ${parsed.status})` : "";
67
- throw new Error(`[${parsed.error.code}] ${parsed.error.message}${statusSuffix}${detailsSuffix}`);
98
+ throw new MachineCliError(`[${parsed.error.code}] ${parsed.error.message}${statusSuffix}${detailsSuffix}`, parsed);
68
99
  }
69
100
 
70
101
  return parsed;
71
102
  }
72
103
 
104
+ function defaultVersionContext(): McpVersionContext {
105
+ return {
106
+ mcpVersion: getSwallowKitVersion(),
107
+ requestedVersion: "latest",
108
+ projectVersion: null,
109
+ selection: "running-version",
110
+ delegated: false,
111
+ warnings: [],
112
+ nextActions: [],
113
+ };
114
+ }
115
+
116
+ function mergeNextActions(
117
+ first: MachineNextAction[] | undefined,
118
+ second: MachineNextAction[]
119
+ ): MachineNextAction[] {
120
+ const merged = [...(first ?? [])];
121
+ for (const action of second) {
122
+ if (!merged.some((existing) => existing.command === action.command)) merged.push(action);
123
+ }
124
+ return merged;
125
+ }
126
+
73
127
  function withOperationStatus<TData>(response: MachineSuccessPayload<TData>): Record<string, unknown> {
74
128
  const data = response.data && typeof response.data === "object"
75
129
  ? (response.data as Record<string, unknown>)
@@ -92,25 +146,41 @@ function jsonTextContent(value: unknown): { content: Array<{ type: "text"; text:
92
146
  };
93
147
  }
94
148
 
95
- function withSwallowKitMetadata<TData>(data: TData): TData & { metadata: Record<string, unknown> } {
96
- const value = data && typeof data === "object" ? data as Record<string, unknown> : { data };
149
+ function withVersionMetadata(data: unknown, context: McpVersionContext): Record<string, unknown> {
150
+ const value = data && typeof data === "object" && !Array.isArray(data)
151
+ ? data as Record<string, unknown>
152
+ : { data };
97
153
  const metadata = value.metadata && typeof value.metadata === "object"
98
154
  ? value.metadata as Record<string, unknown>
99
155
  : {};
156
+ const existingNextActions = Array.isArray(value.nextActions) ? value.nextActions as MachineNextAction[] : undefined;
157
+ const nextActions = mergeNextActions(existingNextActions, context.nextActions);
100
158
 
101
159
  return {
102
160
  ...value,
161
+ ...(nextActions.length > 0 ? { nextActions } : {}),
103
162
  metadata: {
104
163
  ...metadata,
105
- swallowkitVersion: getSwallowKitVersion(),
164
+ swallowkitVersion: context.mcpVersion,
165
+ mcp: {
166
+ version: context.mcpVersion,
167
+ requestedVersion: context.requestedVersion,
168
+ projectVersion: context.projectVersion,
169
+ selection: context.selection,
170
+ delegated: context.delegated,
171
+ ...(context.delegatedFrom ? { delegatedFrom: context.delegatedFrom } : {}),
172
+ },
173
+ ...(context.warnings.length > 0 ? { warnings: [...context.warnings] } : {}),
106
174
  },
107
- } as TData & { metadata: Record<string, unknown> };
175
+ };
108
176
  }
109
177
 
110
178
  export function buildSwallowKitToolDefinitions(
111
- runMachineCli: MachineCliRunner = defaultMachineCliRunner
179
+ runMachineCli: MachineCliRunner = defaultMachineCliRunner,
180
+ versionContext: McpVersionContext = defaultVersionContext()
112
181
  ): ToolDefinition[] {
113
- return [
182
+ const withSwallowKitMetadata = (data: unknown) => withVersionMetadata(data, versionContext);
183
+ const definitions: ToolDefinition[] = [
114
184
  {
115
185
  name: "swallowkit_inspect_project",
116
186
  description: "Return framework-owned SwallowKit project metadata.",
@@ -169,8 +239,9 @@ export function buildSwallowKitToolDefinitions(
169
239
  functionsDir: z.string().optional(),
170
240
  apiDir: z.string().optional(),
171
241
  apiOnly: z.boolean().optional(),
242
+ skipInfra: z.boolean().optional().describe("Do not create or update anything under infra/; the container migration stays pending."),
172
243
  }),
173
- handler: async ({ model, functionsDir, apiDir, apiOnly }: { model: string; functionsDir?: string; apiDir?: string; apiOnly?: boolean }) => {
244
+ handler: async ({ model, functionsDir, apiDir, apiOnly, skipInfra }: { model: string; functionsDir?: string; apiDir?: string; apiOnly?: boolean; skipInfra?: boolean }) => {
174
245
  const args = ["generate", "scaffold", model];
175
246
  if (functionsDir) {
176
247
  args.push("--functions-dir", functionsDir);
@@ -181,6 +252,9 @@ export function buildSwallowKitToolDefinitions(
181
252
  if (apiOnly) {
182
253
  args.push("--api-only");
183
254
  }
255
+ if (skipInfra) {
256
+ args.push("--skip-infra");
257
+ }
184
258
 
185
259
  const response = await executeMachineCommand(args, runMachineCli);
186
260
  return jsonTextContent(response.data);
@@ -195,14 +269,16 @@ export function buildSwallowKitToolDefinitions(
195
269
  functionsDir: z.string().optional(),
196
270
  apiDir: z.string().optional(),
197
271
  apiOnly: z.boolean().optional(),
272
+ skipInfra: z.boolean().optional().describe("Exclude all infra/ changes (container migration) from the plan."),
198
273
  }),
199
- handler: async ({ model, models, functionsDir, apiDir, apiOnly }: { model?: string; models?: string[]; functionsDir?: string; apiDir?: string; apiOnly?: boolean }) => {
274
+ handler: async ({ model, models, functionsDir, apiDir, apiOnly, skipInfra }: { model?: string; models?: string[]; functionsDir?: string; apiDir?: string; apiOnly?: boolean; skipInfra?: boolean }) => {
200
275
  const targets = models && models.length > 0 ? models : model ? [model] : [];
201
276
  if (targets.length === 0) throw new Error("Provide model or models.");
202
277
  const args = ["plan", "scaffold", ...targets];
203
278
  if (functionsDir) args.push("--functions-dir", functionsDir);
204
279
  if (apiDir) args.push("--api-dir", apiDir);
205
280
  if (apiOnly) args.push("--api-only");
281
+ if (skipInfra) args.push("--skip-infra");
206
282
 
207
283
  const response = await executeMachineCommand(args, runMachineCli);
208
284
  return jsonTextContent(withOperationStatus(response));
@@ -218,8 +294,9 @@ export function buildSwallowKitToolDefinitions(
218
294
  functionsDir: z.string().optional(),
219
295
  apiDir: z.string().optional(),
220
296
  apiOnly: z.boolean().optional(),
297
+ skipInfra: z.boolean().optional().describe("Do not create or update anything under infra/ (defaults to the plan's setting)."),
221
298
  }),
222
- handler: async ({ model, planId, approve, functionsDir, apiDir, apiOnly }: { model?: string; planId?: string; approve?: boolean; functionsDir?: string; apiDir?: string; apiOnly?: boolean }) => {
299
+ handler: async ({ model, planId, approve, functionsDir, apiDir, apiOnly, skipInfra }: { model?: string; planId?: string; approve?: boolean; functionsDir?: string; apiDir?: string; apiOnly?: boolean; skipInfra?: boolean }) => {
223
300
  const args = ["apply", "scaffold"];
224
301
  if (model) args.push(model);
225
302
  if (planId) args.push("--plan", planId);
@@ -227,6 +304,7 @@ export function buildSwallowKitToolDefinitions(
227
304
  if (functionsDir) args.push("--functions-dir", functionsDir);
228
305
  if (apiDir) args.push("--api-dir", apiDir);
229
306
  if (apiOnly) args.push("--api-only");
307
+ if (skipInfra) args.push("--skip-infra");
230
308
 
231
309
  const response = await executeMachineCommand(args, runMachineCli);
232
310
  return jsonTextContent(withOperationStatus(response));
@@ -252,7 +330,7 @@ export function buildSwallowKitToolDefinitions(
252
330
  },
253
331
  {
254
332
  name: "swallowkit_verify_project",
255
- description: "Run verification checks (structure, drift, typecheck by default; build, lint, test and custom checks from swallowkit.config verify.checks are also available) and return machine-readable evidence. summary.done=true means the project passed all checks. Set compact=true to suppress info-severity findings and keep the output small.",
333
+ description: "Run verification checks (structure, drift, typecheck, backend-build by default; build, lint, test and custom checks from swallowkit.config verify.checks are also available) and return machine-readable evidence. backend-build compiles standalone C#/TypeScript Functions or checks Python syntax; unavailable toolchains are skipped with a reason. summary.done=true means no checks failed, not that skipped checks were validated or runtime/auth flows work. Set compact=true to suppress info-severity findings and keep the output small.",
256
334
  inputSchema: z.object({
257
335
  checks: z.array(z.string()).optional(),
258
336
  compact: z.boolean().optional(),
@@ -362,14 +440,16 @@ export function buildSwallowKitToolDefinitions(
362
440
  adoptExisting: z.boolean().optional(),
363
441
  baseline: z.number().int().nonnegative().optional(),
364
442
  reconcile: z.boolean().optional(),
443
+ resumeDeployment: z.string().optional(),
365
444
  }),
366
- handler: async ({ resourceGroup, location, swaLocation, subscription, whatIf, adoptExisting, baseline, reconcile }: { resourceGroup: string; location: string; swaLocation: string; subscription?: string; whatIf?: boolean; adoptExisting?: boolean; baseline?: number; reconcile?: boolean }) => {
445
+ handler: async ({ resourceGroup, location, swaLocation, subscription, whatIf, adoptExisting, baseline, reconcile, resumeDeployment }: { resourceGroup: string; location: string; swaLocation: string; subscription?: string; whatIf?: boolean; adoptExisting?: boolean; baseline?: number; reconcile?: boolean; resumeDeployment?: string }) => {
367
446
  const args = ["plan", "provision", "--resource-group", resourceGroup, "--location", location, "--swa-location", swaLocation];
368
447
  if (subscription) args.push("--subscription", subscription);
369
448
  if (whatIf) args.push("--what-if");
370
449
  if (adoptExisting) args.push("--adopt-existing");
371
450
  if (baseline !== undefined) args.push("--baseline", String(baseline));
372
451
  if (reconcile) args.push("--reconcile");
452
+ if (resumeDeployment) args.push("--resume-deployment", resumeDeployment);
373
453
 
374
454
  const response = await executeMachineCommand(args, runMachineCli);
375
455
  return jsonTextContent(withOperationStatus(response));
@@ -377,46 +457,156 @@ export function buildSwallowKitToolDefinitions(
377
457
  },
378
458
  {
379
459
  name: "swallowkit_apply_provision",
380
- description: "Apply an approved provisioning plan. Creates billable Azure resources; approve=true is always required and must reflect explicit human consent.",
460
+ description: "Apply or resume an approved provision checkpoint with bounded state polling. Never resubmit an attempted deployment; finish tags/ledger only after Azure Succeeded. Check status/nextActions, not just ok. approve=true always requires explicit human consent.",
381
461
  inputSchema: z.object({
382
462
  planId: z.string(),
383
463
  approve: z.boolean().optional(),
464
+ waitSeconds: z.number().int().min(0).max(3600).optional(),
384
465
  }),
385
- handler: async ({ planId, approve }: { planId: string; approve?: boolean }) => {
466
+ handler: async ({ planId, approve, waitSeconds }: { planId: string; approve?: boolean; waitSeconds?: number }, signal?: AbortSignal) => {
386
467
  const args = ["apply", "provision", "--plan", planId];
387
468
  if (approve) args.push("--approve");
469
+ if (waitSeconds !== undefined) args.push("--wait-seconds", String(waitSeconds));
388
470
 
389
- const response = await executeMachineCommand(args, runMachineCli);
471
+ const response = await executeMachineCommand(args, runMachineCli, signal);
472
+ return jsonTextContent(withOperationStatus(response));
473
+ },
474
+ },
475
+ {
476
+ name: "swallowkit_inspect_provision",
477
+ description: "Read or bounded-wait for the original provision deployment. No create, cancel, delete, tag, or ledger writes. Azure Succeeded alone is partial until the original approved checkpoint finishes bookkeeping.",
478
+ inputSchema: z.object({
479
+ planId: z.string(),
480
+ waitSeconds: z.number().int().min(0).max(3600).optional(),
481
+ }),
482
+ handler: async ({ planId, waitSeconds }: { planId: string; waitSeconds?: number }, signal?: AbortSignal) => {
483
+ const args = ["inspect", "provision", "--plan", planId];
484
+ if (waitSeconds !== undefined) args.push("--wait-seconds", String(waitSeconds));
485
+ const response = await executeMachineCommand(args, runMachineCli, signal);
390
486
  return jsonTextContent(withOperationStatus(response));
391
487
  },
392
488
  },
393
489
  ];
490
+
491
+ // Every response reports which SwallowKit version answered and why it was selected.
492
+ return definitions.map((definition) => ({
493
+ ...definition,
494
+ handler: async (input: unknown, signal?: AbortSignal) => {
495
+ try {
496
+ const result = await definition.handler(input, signal);
497
+ return jsonTextContent(withSwallowKitMetadata(JSON.parse(result.content[0].text)));
498
+ } catch (error) {
499
+ // Prerequisite failures carry nextActions; keep them structured so agents can follow them.
500
+ if (error instanceof MachineCliError && error.response.nextActions?.length) {
501
+ return { ...jsonTextContent(withSwallowKitMetadata(error.response)), isError: true };
502
+ }
503
+ throw error;
504
+ }
505
+ },
506
+ }));
394
507
  }
395
508
 
396
- export function createSwallowKitMcpServer(runMachineCli: MachineCliRunner = defaultMachineCliRunner): McpServer {
397
- const version = getSwallowKitVersion();
509
+ export function createSwallowKitMcpServer(
510
+ runMachineCli: MachineCliRunner = defaultMachineCliRunner,
511
+ versionContext: McpVersionContext = defaultVersionContext()
512
+ ): McpServer {
513
+ const version = versionContext.mcpVersion;
398
514
  const server = new McpServer({
399
515
  name: "swallowkit-mcp",
400
516
  version,
401
517
  });
402
518
 
403
- for (const tool of buildSwallowKitToolDefinitions(runMachineCli)) {
519
+ for (const tool of buildSwallowKitToolDefinitions(runMachineCli, versionContext)) {
404
520
  server.registerTool(
405
521
  tool.name,
406
522
  {
407
523
  description: tool.description,
408
524
  inputSchema: tool.inputSchema,
409
525
  },
410
- tool.handler
526
+ (input, extra) => tool.handler(input, extra.signal)
411
527
  );
412
528
  }
413
529
 
414
530
  return server;
415
531
  }
416
532
 
533
+ function resolveExecCommand(cwd: string): string {
534
+ try {
535
+ return getCommands(detectFromProject(cwd)).exec;
536
+ } catch {
537
+ return "npx";
538
+ }
539
+ }
540
+
541
+ async function fetchPublishedLatest(): Promise<string | null> {
542
+ if (process.env.SWALLOWKIT_MCP_UPDATE_CHECK === "0") return null;
543
+ const registry = (process.env.npm_config_registry || "https://registry.npmjs.org").replace(/\/+$/, "");
544
+ try {
545
+ const response = await fetch(`${registry}/swallowkit/latest`, { signal: AbortSignal.timeout(3000) });
546
+ if (!response.ok) return null;
547
+ const body = await response.json() as { version?: unknown };
548
+ return typeof body.version === "string" ? body.version : null;
549
+ } catch {
550
+ return null;
551
+ }
552
+ }
553
+
554
+ function delegateToProjectMcp(entrypoint: string, fromVersion: string): void {
555
+ const child = spawn(process.execPath, [entrypoint], {
556
+ stdio: "inherit",
557
+ env: { ...process.env, [MCP_DELEGATED_FROM_ENV]: fromVersion },
558
+ });
559
+ for (const signal of ["SIGINT", "SIGTERM"] as const) {
560
+ process.on(signal, () => child.kill(signal));
561
+ }
562
+ child.on("error", (error) => {
563
+ console.error(`[swallowkit-mcp] failed to start the project-installed MCP: ${error.message}`);
564
+ process.exitCode = 1;
565
+ });
566
+ child.on("exit", (code, signal) => {
567
+ process.exitCode = code ?? (signal ? 1 : 0);
568
+ });
569
+ }
570
+
417
571
  export async function runMcpServer(): Promise<void> {
418
- console.error(`[swallowkit-mcp] version: ${getSwallowKitVersion()}`);
419
- const server = createSwallowKitMcpServer();
572
+ const selfVersion = getSwallowKitVersion();
573
+ const cwd = process.cwd();
574
+ const execCommand = resolveExecCommand(cwd);
575
+ const decision = decideMcpLaunch({
576
+ selfVersion,
577
+ selfPackageRoot: path.resolve(__dirname, "..", ".."),
578
+ env: process.env,
579
+ project: resolveProjectSwallowKit(cwd),
580
+ execCommand,
581
+ });
582
+
583
+ if (decision.action === "delegate") {
584
+ console.error(
585
+ `[swallowkit-mcp] version: ${selfVersion}; delegating to the project-installed swallowkit-mcp ${decision.toVersion} (newer than the launcher).`
586
+ );
587
+ delegateToProjectMcp(decision.entrypoint, decision.fromVersion);
588
+ return;
589
+ }
590
+
591
+ const context = decision.context;
592
+ // One write keeps the version line and its warnings together for log readers.
593
+ console.error([
594
+ `[swallowkit-mcp] version: ${selfVersion} (selection: ${context.selection}${context.delegatedFrom ? `, delegated from ${context.delegatedFrom}` : ""})`,
595
+ ...context.warnings.map((warning) => `[swallowkit-mcp] warning: ${warning}`),
596
+ ].join("\n"));
597
+
598
+ if (context.selection === "pinned") {
599
+ void fetchPublishedLatest().then((latest) => {
600
+ const notice = buildPinnedUpdateNotice(context, latest, execCommand);
601
+ if (notice) {
602
+ context.warnings.push(notice.warning);
603
+ context.nextActions.push(notice.nextAction);
604
+ console.error(`[swallowkit-mcp] warning: ${notice.warning}`);
605
+ }
606
+ });
607
+ }
608
+
609
+ const server = createSwallowKitMcpServer(defaultMachineCliRunner, context);
420
610
  const transport = new StdioServerTransport();
421
611
  await server.connect(transport);
422
612
  }