@launchsecure/launch-kit 0.0.26 → 0.0.28

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 (123) hide show
  1. package/dist/beacon/beacon.mjs +1003 -440
  2. package/dist/beacon/beacon.mjs.map +1 -1
  3. package/dist/beacon/beacon.umd.js +45 -24
  4. package/dist/beacon/beacon.umd.js.map +1 -1
  5. package/dist/beacon/types/capture/events.d.ts +20 -0
  6. package/dist/beacon/types/capture/events.d.ts.map +1 -0
  7. package/dist/beacon/types/element.d.ts +1 -0
  8. package/dist/beacon/types/element.d.ts.map +1 -1
  9. package/dist/beacon/types/index.d.ts +2 -1
  10. package/dist/beacon/types/index.d.ts.map +1 -1
  11. package/dist/beacon/types/monitor/dom.d.ts +13 -0
  12. package/dist/beacon/types/monitor/dom.d.ts.map +1 -0
  13. package/dist/beacon/types/monitor/index.d.ts +19 -0
  14. package/dist/beacon/types/monitor/index.d.ts.map +1 -0
  15. package/dist/beacon/types/monitor/network.d.ts +12 -0
  16. package/dist/beacon/types/monitor/network.d.ts.map +1 -0
  17. package/dist/beacon/types/monitor/transport.d.ts +27 -0
  18. package/dist/beacon/types/monitor/transport.d.ts.map +1 -0
  19. package/dist/beacon/types/monitor/types.d.ts +117 -0
  20. package/dist/beacon/types/monitor/types.d.ts.map +1 -0
  21. package/dist/beacon/types/types.d.ts +10 -0
  22. package/dist/beacon/types/types.d.ts.map +1 -1
  23. package/dist/beacon/types/ui/drawer.d.ts +3 -1
  24. package/dist/beacon/types/ui/drawer.d.ts.map +1 -1
  25. package/dist/beacon/types/ui/monitor-panel.d.ts +19 -0
  26. package/dist/beacon/types/ui/monitor-panel.d.ts.map +1 -0
  27. package/dist/chart-client/assets/index-CJ4mgRRF.css +1 -0
  28. package/dist/chart-client/assets/{index-Bk1hawjD.js → index-Ccy-DpI-.js} +46 -42
  29. package/dist/chart-client/index.html +2 -2
  30. package/dist/client/assets/index-DI5qSR_w.css +32 -0
  31. package/dist/client/assets/index-Dp0_okva.js +294 -0
  32. package/dist/client/index.html +2 -2
  33. package/dist/council-client/assets/index-C_-vAM9L.css +1 -0
  34. package/dist/council-client/index.html +2 -2
  35. package/dist/deck-client/assets/{_baseUniq-C2xT_eYu.js → _baseUniq-W2JQDmje.js} +1 -1
  36. package/dist/deck-client/assets/{arc-CmVL9pGd.js → arc-DIBWAId9.js} +1 -1
  37. package/dist/deck-client/assets/{architectureDiagram-Q4EWVU46-BSFgdjve.js → architectureDiagram-Q4EWVU46-CAIRMvJK.js} +1 -1
  38. package/dist/deck-client/assets/{blockDiagram-DXYQGD6D-DuLzscvP.js → blockDiagram-DXYQGD6D-BeNaNiOi.js} +1 -1
  39. package/dist/deck-client/assets/{c4Diagram-AHTNJAMY-CfCJB8eY.js → c4Diagram-AHTNJAMY-B9Ozi62h.js} +1 -1
  40. package/dist/deck-client/assets/channel-CRdozqbp.js +1 -0
  41. package/dist/deck-client/assets/{chunk-4BX2VUAB-DxmLYTWZ.js → chunk-4BX2VUAB-D7AZ47dt.js} +1 -1
  42. package/dist/deck-client/assets/{chunk-4TB4RGXK-CCnf7GFE.js → chunk-4TB4RGXK-DnVnNPcI.js} +1 -1
  43. package/dist/deck-client/assets/{chunk-55IACEB6-Db9DApcj.js → chunk-55IACEB6-UKYs-YNd.js} +1 -1
  44. package/dist/deck-client/assets/{chunk-EDXVE4YY-DmYDq8ZI.js → chunk-EDXVE4YY-D43b-SKn.js} +1 -1
  45. package/dist/deck-client/assets/{chunk-FMBD7UC4-BGhUlF20.js → chunk-FMBD7UC4-QzBAoyyW.js} +1 -1
  46. package/dist/deck-client/assets/{chunk-OYMX7WX6-CpEnicQZ.js → chunk-OYMX7WX6-Cjif4r6W.js} +1 -1
  47. package/dist/deck-client/assets/{chunk-QZHKN3VN-Doa7LKwf.js → chunk-QZHKN3VN-CqLDirEI.js} +1 -1
  48. package/dist/deck-client/assets/{chunk-YZCP3GAM-CpkIlH6V.js → chunk-YZCP3GAM-_FQvmMs4.js} +1 -1
  49. package/dist/deck-client/assets/classDiagram-6PBFFD2Q-lIZMp57W.js +1 -0
  50. package/dist/deck-client/assets/classDiagram-v2-HSJHXN6E-lIZMp57W.js +1 -0
  51. package/dist/deck-client/assets/clone-BtWeSTyJ.js +1 -0
  52. package/dist/deck-client/assets/{cose-bilkent-S5V4N54A-Bkh8Bfcb.js → cose-bilkent-S5V4N54A-rfrocesE.js} +1 -1
  53. package/dist/deck-client/assets/{dagre-KV5264BT-Bp0XpTgH.js → dagre-KV5264BT-Bv_7DJat.js} +1 -1
  54. package/dist/deck-client/assets/{diagram-5BDNPKRD-ZHiyGYPQ.js → diagram-5BDNPKRD-4F1414G5.js} +1 -1
  55. package/dist/deck-client/assets/{diagram-G4DWMVQ6-BW-Q8_H5.js → diagram-G4DWMVQ6-C4-Pszqm.js} +1 -1
  56. package/dist/deck-client/assets/{diagram-MMDJMWI5-6I3LTafu.js → diagram-MMDJMWI5-B647TIx9.js} +1 -1
  57. package/dist/deck-client/assets/{diagram-TYMM5635-CyM5YK28.js → diagram-TYMM5635-BFAqpezd.js} +1 -1
  58. package/dist/deck-client/assets/{erDiagram-SMLLAGMA-CjNxVJHk.js → erDiagram-SMLLAGMA-BfBfrJOC.js} +1 -1
  59. package/dist/deck-client/assets/{flowDiagram-DWJPFMVM-BDQHuAJR.js → flowDiagram-DWJPFMVM-DX9YAYes.js} +1 -1
  60. package/dist/deck-client/assets/{ganttDiagram-T4ZO3ILL-B7MnkpbP.js → ganttDiagram-T4ZO3ILL-DCuiy7wF.js} +1 -1
  61. package/dist/deck-client/assets/{gitGraphDiagram-UUTBAWPF-C9dZAcYD.js → gitGraphDiagram-UUTBAWPF-CGp1IXUh.js} +1 -1
  62. package/dist/deck-client/assets/{graph-CjdBnzUy.js → graph-B7g8aoxv.js} +1 -1
  63. package/dist/deck-client/assets/{index-DeIVPW63.js → index-Dg1r-WSN.js} +3 -3
  64. package/dist/deck-client/assets/index-DsIZ3LqL.css +1 -0
  65. package/dist/deck-client/assets/{infoDiagram-42DDH7IO-C7d3iRC3.js → infoDiagram-42DDH7IO-L3fahMkF.js} +1 -1
  66. package/dist/deck-client/assets/{ishikawaDiagram-UXIWVN3A-BcYGKj09.js → ishikawaDiagram-UXIWVN3A-aS_EjWBZ.js} +1 -1
  67. package/dist/deck-client/assets/{journeyDiagram-VCZTEJTY-DqFlRrOL.js → journeyDiagram-VCZTEJTY-djTSQZF9.js} +1 -1
  68. package/dist/deck-client/assets/{kanban-definition-6JOO6SKY-BJhPp1NR.js → kanban-definition-6JOO6SKY-CcTHo4CM.js} +1 -1
  69. package/dist/deck-client/assets/{layout-DIeS6GvK.js → layout-mEJiadb7.js} +1 -1
  70. package/dist/deck-client/assets/{linear-He_yJy5H.js → linear-XgTKqyRu.js} +1 -1
  71. package/dist/deck-client/assets/{min-DQ6Kx06t.js → min-Ct9jZdpd.js} +1 -1
  72. package/dist/deck-client/assets/{mindmap-definition-QFDTVHPH-sQ62L8T2.js → mindmap-definition-QFDTVHPH-BaFxCGNU.js} +1 -1
  73. package/dist/deck-client/assets/{pieDiagram-DEJITSTG-BqCWmU2K.js → pieDiagram-DEJITSTG-CIbYYjtw.js} +1 -1
  74. package/dist/deck-client/assets/{quadrantDiagram-34T5L4WZ-rQ1TJOoe.js → quadrantDiagram-34T5L4WZ-D9EtCOvh.js} +1 -1
  75. package/dist/deck-client/assets/{requirementDiagram-MS252O5E-BO2MPBOM.js → requirementDiagram-MS252O5E-xeni9eVG.js} +1 -1
  76. package/dist/deck-client/assets/{sankeyDiagram-XADWPNL6-BgsHEVex.js → sankeyDiagram-XADWPNL6-LYeknz9h.js} +1 -1
  77. package/dist/deck-client/assets/{sequenceDiagram-FGHM5R23-B3j1yMLU.js → sequenceDiagram-FGHM5R23-RDbsKFZf.js} +1 -1
  78. package/dist/deck-client/assets/{stateDiagram-FHFEXIEX-C8jFlZou.js → stateDiagram-FHFEXIEX-BH1Zjglk.js} +1 -1
  79. package/dist/deck-client/assets/stateDiagram-v2-QKLJ7IA2-BrV78NDR.js +1 -0
  80. package/dist/deck-client/assets/{timeline-definition-GMOUNBTQ-tM-qo4Zk.js → timeline-definition-GMOUNBTQ-IFXxKptt.js} +1 -1
  81. package/dist/deck-client/assets/{vennDiagram-DHZGUBPP-B0-6kOEu.js → vennDiagram-DHZGUBPP-D-sLkQs9.js} +1 -1
  82. package/dist/deck-client/assets/{wardley-RL74JXVD-HpBk07P-.js → wardley-RL74JXVD-C010F8l4.js} +1 -1
  83. package/dist/deck-client/assets/{wardleyDiagram-NUSXRM2D-BkA1NLDE.js → wardleyDiagram-NUSXRM2D-BTjjuDU3.js} +1 -1
  84. package/dist/deck-client/assets/{xychartDiagram-5P7HB3ND-CEKGSuI-.js → xychartDiagram-5P7HB3ND-AYbv92n-.js} +1 -1
  85. package/dist/deck-client/index.html +2 -2
  86. package/dist/server/beacon-monitor-entry.js +353 -0
  87. package/dist/server/chart-serve.js +3836 -3750
  88. package/dist/server/cli.js +8789 -8219
  89. package/dist/server/council-entry.js +17 -5
  90. package/dist/server/council-serve.js +8 -3
  91. package/dist/server/course-entry.js +246 -0
  92. package/dist/server/deck-mcp-entry.js +24 -12
  93. package/dist/server/deck-serve.js +11 -8
  94. package/dist/server/graph-mcp-entry.js +5005 -4865
  95. package/dist/server/init-entry.js +939 -0
  96. package/dist/server/orbit-entry.js +2435 -0
  97. package/dist/server/parse-worker-entry.js +4721 -0
  98. package/dist/server/recall-entry.js +356 -18
  99. package/package.json +11 -4
  100. package/scaffolds/ls-marketplace/.claude-plugin/marketplace.json +15 -0
  101. package/scaffolds/ls-marketplace/plugins/ls/.claude-plugin/plugin.json +28 -0
  102. package/scaffolds/ls-marketplace/plugins/ls/commands/activate-beacon.md +216 -0
  103. package/scaffolds/ls-marketplace/plugins/ls/commands/beacon-array.md +92 -0
  104. package/scaffolds/ls-marketplace/plugins/ls/commands/beacon-clear.md +68 -0
  105. package/scaffolds/ls-marketplace/plugins/ls/commands/beacon-pulse.md +80 -0
  106. package/scaffolds/ls-marketplace/plugins/ls/commands/beacon-scan.md +62 -0
  107. package/scaffolds/ls-marketplace/plugins/ls/commands/show-mcp-status.md +109 -0
  108. package/scaffolds/ls-marketplace/plugins/ls/commands/standup.md +177 -0
  109. package/scaffolds/migrate-safety/.github/workflows/backup-on-migration.yml +72 -0
  110. package/scaffolds/migrate-safety/docs/migrations-runbook.md +172 -0
  111. package/scaffolds/migrate-safety/scripts/migrate-with-backup.sh +294 -0
  112. package/scaffolds/recall-hook/scripts/ensure-recall.sh +69 -0
  113. package/dist/chart-client/assets/index-DpaGa3bY.css +0 -1
  114. package/dist/client/assets/index-Bfel4OQ5.css +0 -32
  115. package/dist/client/assets/index-eC-WuUWB.js +0 -291
  116. package/dist/council-client/assets/index-P5kMsT5a.css +0 -1
  117. package/dist/deck-client/assets/channel-B4aNO8ZB.js +0 -1
  118. package/dist/deck-client/assets/classDiagram-6PBFFD2Q-BHTI0yWz.js +0 -1
  119. package/dist/deck-client/assets/classDiagram-v2-HSJHXN6E-BHTI0yWz.js +0 -1
  120. package/dist/deck-client/assets/clone-HduFm7qU.js +0 -1
  121. package/dist/deck-client/assets/index-LKZDAS9S.css +0 -1
  122. package/dist/deck-client/assets/stateDiagram-v2-QKLJ7IA2-BoqepHW0.js +0 -1
  123. /package/dist/council-client/assets/{index-Cs_MVXHf.js → index-Dt4zWKSj.js} +0 -0
@@ -0,0 +1,2435 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __esm = (fn, res) => function __init() {
10
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
11
+ };
12
+ var __export = (target, all) => {
13
+ for (var name in all)
14
+ __defProp(target, name, { get: all[name], enumerable: true });
15
+ };
16
+ var __copyProps = (to, from, except, desc) => {
17
+ if (from && typeof from === "object" || typeof from === "function") {
18
+ for (let key of __getOwnPropNames(from))
19
+ if (!__hasOwnProp.call(to, key) && key !== except)
20
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
21
+ }
22
+ return to;
23
+ };
24
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
25
+ // If the importer is in node compatibility mode or this is not an ESM
26
+ // file that has been converted to a CommonJS file using a Babel-
27
+ // compatible transform (i.e. "__esModule" has not been set), then set
28
+ // "default" to the CommonJS "module.exports" for node compatibility.
29
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
30
+ mod
31
+ ));
32
+
33
+ // src/server/orbit/adapters/git-worktree.ts
34
+ function currentHeadRef(cwd) {
35
+ try {
36
+ return (0, import_node_child_process.execFileSync)("git", ["rev-parse", "HEAD"], {
37
+ cwd,
38
+ encoding: "utf-8"
39
+ }).trim();
40
+ } catch {
41
+ return "HEAD";
42
+ }
43
+ }
44
+ var import_node_child_process, import_node_fs, import_node_path, gitWorktreeAdapter;
45
+ var init_git_worktree = __esm({
46
+ "src/server/orbit/adapters/git-worktree.ts"() {
47
+ "use strict";
48
+ import_node_child_process = require("node:child_process");
49
+ import_node_fs = require("node:fs");
50
+ import_node_path = require("node:path");
51
+ gitWorktreeAdapter = {
52
+ id: "builtin/git-worktree",
53
+ async detect(ctx) {
54
+ try {
55
+ (0, import_node_child_process.execFileSync)("git", ["rev-parse", "--show-toplevel"], {
56
+ cwd: ctx.projectRoot,
57
+ stdio: "ignore"
58
+ });
59
+ return { ok: true };
60
+ } catch {
61
+ return { ok: false, reason: "git not available or projectRoot is not inside a git repo" };
62
+ }
63
+ },
64
+ async create(input) {
65
+ const { slug, branch, baseRef, config, ctx } = input;
66
+ const basePath = config.basePath ?? ".claude/worktrees";
67
+ const pattern = config.namingPattern ?? "{slug}";
68
+ const dirName = pattern.replace(/\{slug\}/g, slug);
69
+ const absBase = (0, import_node_path.isAbsolute)(basePath) ? basePath : (0, import_node_path.join)(ctx.projectRoot, basePath);
70
+ const targetPath = (0, import_node_path.join)(absBase, dirName);
71
+ if ((0, import_node_fs.existsSync)(targetPath)) {
72
+ throw new Error(`worktree path already exists: ${targetPath}`);
73
+ }
74
+ const effectiveBase = baseRef ?? config.baseRef ?? currentHeadRef(ctx.projectRoot);
75
+ const args = ["worktree", "add", targetPath, "-b", branch, effectiveBase];
76
+ try {
77
+ (0, import_node_child_process.execFileSync)("git", args, { cwd: ctx.projectRoot, stdio: ["ignore", "pipe", "pipe"] });
78
+ } catch (e) {
79
+ throw new Error(`git worktree add failed: ${e.message}`);
80
+ }
81
+ return {
82
+ adapter: "builtin/git-worktree",
83
+ path: targetPath,
84
+ branch
85
+ };
86
+ },
87
+ async remove(input) {
88
+ const { state, force, ctx } = input;
89
+ const args = ["worktree", "remove", state.path];
90
+ if (force) args.push("--force");
91
+ try {
92
+ (0, import_node_child_process.execFileSync)("git", args, { cwd: ctx.projectRoot, stdio: ["ignore", "pipe", "pipe"] });
93
+ } catch (e) {
94
+ throw new Error(`git worktree remove failed: ${e.message}`);
95
+ }
96
+ try {
97
+ (0, import_node_child_process.execFileSync)("git", ["branch", "-D", state.branch], {
98
+ cwd: ctx.projectRoot,
99
+ stdio: "ignore"
100
+ });
101
+ } catch {
102
+ }
103
+ }
104
+ };
105
+ }
106
+ });
107
+
108
+ // src/server/orbit/adapters/claude-code.ts
109
+ var import_node_fs2, claudeCodeAdapter;
110
+ var init_claude_code = __esm({
111
+ "src/server/orbit/adapters/claude-code.ts"() {
112
+ "use strict";
113
+ import_node_fs2 = require("node:fs");
114
+ claudeCodeAdapter = {
115
+ id: "builtin/claude-code",
116
+ async detect() {
117
+ return { ok: true };
118
+ },
119
+ async create(input) {
120
+ const { branch, config } = input;
121
+ const path = config.existingPath;
122
+ if (!path) {
123
+ throw new Error(
124
+ "builtin/claude-code adapter cannot create a worktree \u2014 it only registers paths supplied by Claude Code's WorktreeCreate hook payload. Use builtin/git-worktree for normal creation."
125
+ );
126
+ }
127
+ if (!(0, import_node_fs2.existsSync)(path)) {
128
+ throw new Error(`existingPath does not exist: ${path}`);
129
+ }
130
+ return { adapter: "builtin/claude-code", path, branch };
131
+ },
132
+ async remove(_input) {
133
+ }
134
+ };
135
+ }
136
+ });
137
+
138
+ // src/server/launch-kit-paths.ts
139
+ var LAUNCHSECURE_DIR;
140
+ var init_launch_kit_paths = __esm({
141
+ "src/server/launch-kit-paths.ts"() {
142
+ "use strict";
143
+ LAUNCHSECURE_DIR = ".launchsecure";
144
+ }
145
+ });
146
+
147
+ // src/server/orbit/registry.ts
148
+ function emptyRegistry() {
149
+ return { version: 1, worktrees: {} };
150
+ }
151
+ function ensureDir() {
152
+ (0, import_node_fs3.mkdirSync)(REGISTRY_DIR, { recursive: true });
153
+ }
154
+ function readRegistry() {
155
+ if (!(0, import_node_fs3.existsSync)(REGISTRY_PATH)) return emptyRegistry();
156
+ try {
157
+ const parsed = JSON.parse((0, import_node_fs3.readFileSync)(REGISTRY_PATH, "utf-8"));
158
+ if (parsed?.version === 1 && parsed.worktrees && typeof parsed.worktrees === "object") {
159
+ return parsed;
160
+ }
161
+ } catch {
162
+ }
163
+ return emptyRegistry();
164
+ }
165
+ function tryAcquireLock() {
166
+ ensureDir();
167
+ if ((0, import_node_fs3.existsSync)(LOCK_PATH)) {
168
+ try {
169
+ const age = Date.now() - (0, import_node_fs3.statSync)(LOCK_PATH).mtimeMs;
170
+ if (age > STALE_LOCK_MS) {
171
+ try {
172
+ (0, import_node_fs3.rmdirSync)(LOCK_PATH);
173
+ } catch {
174
+ }
175
+ }
176
+ } catch {
177
+ }
178
+ }
179
+ try {
180
+ (0, import_node_fs3.mkdirSync)(LOCK_PATH);
181
+ return true;
182
+ } catch {
183
+ return false;
184
+ }
185
+ }
186
+ function releaseLock() {
187
+ try {
188
+ (0, import_node_fs3.rmdirSync)(LOCK_PATH);
189
+ } catch {
190
+ }
191
+ }
192
+ async function withLock(fn) {
193
+ const start = Date.now();
194
+ while (!tryAcquireLock()) {
195
+ if (Date.now() - start > 3e4) {
196
+ throw new Error(`could not acquire orbit registry lock at ${LOCK_PATH}`);
197
+ }
198
+ await new Promise((r) => setTimeout(r, 100));
199
+ }
200
+ try {
201
+ return await fn();
202
+ } finally {
203
+ releaseLock();
204
+ }
205
+ }
206
+ function writeRegistryAtomic(reg) {
207
+ ensureDir();
208
+ const tmp = `${REGISTRY_PATH}.tmp.${process.pid}`;
209
+ (0, import_node_fs3.writeFileSync)(tmp, JSON.stringify(reg, null, 2) + "\n", "utf-8");
210
+ (0, import_node_fs3.renameSync)(tmp, REGISTRY_PATH);
211
+ }
212
+ async function mutateRegistry(fn) {
213
+ return withLock(() => {
214
+ const current = readRegistry();
215
+ const { next, result } = fn(current);
216
+ writeRegistryAtomic(next);
217
+ return result;
218
+ });
219
+ }
220
+ async function registerWorktree(entry) {
221
+ await mutateRegistry((reg) => {
222
+ if (reg.worktrees[entry.slug]) {
223
+ throw new Error(`worktree slug "${entry.slug}" already registered at ${reg.worktrees[entry.slug].path}`);
224
+ }
225
+ reg.worktrees[entry.slug] = entry;
226
+ return { next: reg, result: void 0 };
227
+ });
228
+ }
229
+ async function deregisterWorktree(slug) {
230
+ return mutateRegistry((reg) => {
231
+ const entry = reg.worktrees[slug] ?? null;
232
+ if (entry) delete reg.worktrees[slug];
233
+ return { next: reg, result: entry };
234
+ });
235
+ }
236
+ function lookupWorktree(slug) {
237
+ return readRegistry().worktrees[slug] ?? null;
238
+ }
239
+ function listWorktrees() {
240
+ return Object.values(readRegistry().worktrees);
241
+ }
242
+ var import_node_fs3, import_node_os, import_node_path2, REGISTRY_DIR, REGISTRY_PATH, LOCK_PATH, STALE_LOCK_MS;
243
+ var init_registry = __esm({
244
+ "src/server/orbit/registry.ts"() {
245
+ "use strict";
246
+ import_node_fs3 = require("node:fs");
247
+ import_node_os = require("node:os");
248
+ import_node_path2 = require("node:path");
249
+ init_launch_kit_paths();
250
+ REGISTRY_DIR = (0, import_node_path2.join)((0, import_node_os.homedir)(), LAUNCHSECURE_DIR, "orbit");
251
+ REGISTRY_PATH = (0, import_node_path2.join)(REGISTRY_DIR, "state.json");
252
+ LOCK_PATH = (0, import_node_path2.join)(REGISTRY_DIR, "state.json.lock");
253
+ STALE_LOCK_MS = 6e4;
254
+ }
255
+ });
256
+
257
+ // src/server/orbit/adapters/port-range.ts
258
+ function collectAllocatedBases() {
259
+ const out = /* @__PURE__ */ new Set();
260
+ for (const entry of listWorktrees()) {
261
+ for (const r of Object.values(entry.resources)) {
262
+ if (r.adapter !== "builtin/port-range") continue;
263
+ const state = r.state;
264
+ if (state && typeof state.base === "number") out.add(state.base);
265
+ }
266
+ }
267
+ return out;
268
+ }
269
+ function isPortInUse(port) {
270
+ try {
271
+ const out = (0, import_node_child_process2.execFileSync)("lsof", ["-nP", `-iTCP:${port}`, "-sTCP:LISTEN", "-t"], {
272
+ encoding: "utf-8",
273
+ stdio: ["ignore", "pipe", "ignore"],
274
+ timeout: 500
275
+ }).trim();
276
+ return out.length > 0;
277
+ } catch {
278
+ return false;
279
+ }
280
+ }
281
+ var import_node_child_process2, portRangeAdapter;
282
+ var init_port_range = __esm({
283
+ "src/server/orbit/adapters/port-range.ts"() {
284
+ "use strict";
285
+ import_node_child_process2 = require("node:child_process");
286
+ init_registry();
287
+ portRangeAdapter = {
288
+ id: "builtin/port-range",
289
+ async detect() {
290
+ return { ok: true };
291
+ },
292
+ async fork(input) {
293
+ const { config, ctx } = input;
294
+ const base = config.base ?? 3e3;
295
+ const stride = config.stride ?? 10;
296
+ const max = config.max ?? 100;
297
+ if (!config.rewrite || !Array.isArray(config.rewrite.env) || typeof config.rewrite.replace !== "string") {
298
+ throw new Error("port-range: config.rewrite.env (string[]) and config.rewrite.replace (string) are required");
299
+ }
300
+ const allocated = collectAllocatedBases();
301
+ allocated.add(base);
302
+ let chosen = null;
303
+ for (let i = 1; i <= max; i++) {
304
+ const candidate = base + i * stride;
305
+ if (allocated.has(candidate)) continue;
306
+ if (isPortInUse(candidate)) {
307
+ ctx.logger.warn(`port ${candidate} is bound by another process \u2014 skipping`);
308
+ continue;
309
+ }
310
+ chosen = candidate;
311
+ break;
312
+ }
313
+ if (chosen === null) {
314
+ throw new Error(`port-range: no free decade between ${base + stride} and ${base + max * stride}`);
315
+ }
316
+ return {
317
+ base: chosen,
318
+ stride,
319
+ replace: config.rewrite.replace,
320
+ envKeys: config.rewrite.env
321
+ };
322
+ },
323
+ async drop(_input) {
324
+ return {};
325
+ },
326
+ envRewrites(state) {
327
+ const map = {};
328
+ const fromPort = state.replace;
329
+ const toPort = `:${state.base}`;
330
+ for (const key of state.envKeys) {
331
+ map[key] = (oldValue) => {
332
+ if (typeof oldValue !== "string") return void 0;
333
+ if (!oldValue.includes(fromPort)) return void 0;
334
+ return oldValue.split(fromPort).join(toPort);
335
+ };
336
+ }
337
+ return map;
338
+ }
339
+ };
340
+ }
341
+ });
342
+
343
+ // src/server/orbit/pg-detect.ts
344
+ function resolvePgClient(versionHint) {
345
+ const reasons = [];
346
+ const dumpOnPath = which("pg_dump");
347
+ const psqlOnPath = which("psql");
348
+ if (dumpOnPath && psqlOnPath) {
349
+ return { kind: "binary", pgDump: dumpOnPath, psql: psqlOnPath, version: probeVersion(dumpOnPath) };
350
+ }
351
+ if (dumpOnPath || psqlOnPath) {
352
+ reasons.push(`PATH has only ${dumpOnPath ? "pg_dump" : "psql"} \u2014 need both`);
353
+ } else {
354
+ reasons.push("pg_dump + psql not on PATH");
355
+ }
356
+ const pgApp = findPostgresApp(versionHint);
357
+ if (pgApp) return { kind: "binary", pgDump: pgApp.pgDump, psql: pgApp.psql, version: pgApp.version };
358
+ reasons.push("no Postgres.app installation found");
359
+ const brew = findHomebrew(versionHint);
360
+ if (brew) return { kind: "binary", pgDump: brew.pgDump, psql: brew.psql, version: brew.version };
361
+ reasons.push("no Homebrew postgresql@* prefix found");
362
+ if (hasDocker()) {
363
+ const image = versionHint ? `postgres:${versionHint}` : "postgres:17";
364
+ return { kind: "docker", image };
365
+ }
366
+ reasons.push("docker not available");
367
+ return { kind: "unavailable", reasons };
368
+ }
369
+ function resolvePgDump(versionHint) {
370
+ const r = resolvePgClient(versionHint);
371
+ if (r.kind === "binary") return { kind: "binary", path: r.pgDump, version: r.version };
372
+ return r;
373
+ }
374
+ function which(bin) {
375
+ try {
376
+ const out = (0, import_node_child_process3.execFileSync)("which", [bin], { encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"] }).trim();
377
+ return out || null;
378
+ } catch {
379
+ return null;
380
+ }
381
+ }
382
+ function probeVersion(binPath) {
383
+ try {
384
+ const out = (0, import_node_child_process3.execFileSync)(binPath, ["--version"], { encoding: "utf-8" }).trim();
385
+ const m = /(\d+\.\d+|\d+)/.exec(out);
386
+ return m ? m[1] : void 0;
387
+ } catch {
388
+ return void 0;
389
+ }
390
+ }
391
+ function findPostgresApp(versionHint) {
392
+ const root = "/Applications/Postgres.app/Contents/Versions";
393
+ if (!(0, import_node_fs4.existsSync)(root)) return null;
394
+ let versions;
395
+ try {
396
+ versions = (0, import_node_fs4.readdirSync)(root).filter((v) => /^\d+(\.\d+)?$/.test(v));
397
+ } catch {
398
+ return null;
399
+ }
400
+ const tryVersion = (v) => {
401
+ const binDir = (0, import_node_path3.join)(root, v, "bin");
402
+ const pgDump = (0, import_node_path3.join)(binDir, "pg_dump");
403
+ const psql = (0, import_node_path3.join)(binDir, "psql");
404
+ if ((0, import_node_fs4.existsSync)(pgDump) && (0, import_node_fs4.existsSync)(psql)) return { pgDump, psql, version: v };
405
+ return null;
406
+ };
407
+ if (versionHint) {
408
+ const hit = versions.find((v) => v.startsWith(String(versionHint)));
409
+ if (hit) {
410
+ const r = tryVersion(hit);
411
+ if (r) return r;
412
+ }
413
+ }
414
+ versions.sort((a, b) => parseFloat(b) - parseFloat(a));
415
+ for (const v of versions) {
416
+ const r = tryVersion(v);
417
+ if (r) return r;
418
+ }
419
+ return null;
420
+ }
421
+ function findHomebrew(versionHint) {
422
+ const candidates = versionHint ? [`postgresql@${versionHint}`] : ["postgresql@17", "postgresql@16", "postgresql@15", "postgresql"];
423
+ for (const formula of candidates) {
424
+ try {
425
+ const prefix = (0, import_node_child_process3.execFileSync)("brew", ["--prefix", formula], {
426
+ encoding: "utf-8",
427
+ stdio: ["ignore", "pipe", "ignore"]
428
+ }).trim();
429
+ if (!prefix) continue;
430
+ const pgDump = (0, import_node_path3.join)(prefix, "bin", "pg_dump");
431
+ const psql = (0, import_node_path3.join)(prefix, "bin", "psql");
432
+ if ((0, import_node_fs4.existsSync)(pgDump) && (0, import_node_fs4.existsSync)(psql)) {
433
+ const m = /@(\d+)/.exec(formula);
434
+ return { pgDump, psql, version: m ? m[1] : "unknown" };
435
+ }
436
+ const alt = (0, import_node_path3.join)(prefix, "libexec", "bin", "pg_dump");
437
+ if ((0, import_node_fs4.existsSync)(alt)) {
438
+ const altPsql = (0, import_node_path3.join)((0, import_node_path3.dirname)(alt), "psql");
439
+ if ((0, import_node_fs4.existsSync)(altPsql)) {
440
+ const m = /@(\d+)/.exec(formula);
441
+ return { pgDump: alt, psql: altPsql, version: m ? m[1] : "unknown" };
442
+ }
443
+ }
444
+ } catch {
445
+ }
446
+ }
447
+ return null;
448
+ }
449
+ function hasDocker() {
450
+ try {
451
+ (0, import_node_child_process3.execFileSync)("docker", ["info"], { stdio: "ignore", timeout: 1500 });
452
+ return true;
453
+ } catch {
454
+ return false;
455
+ }
456
+ }
457
+ var import_node_child_process3, import_node_fs4, import_node_path3;
458
+ var init_pg_detect = __esm({
459
+ "src/server/orbit/pg-detect.ts"() {
460
+ "use strict";
461
+ import_node_child_process3 = require("node:child_process");
462
+ import_node_fs4 = require("node:fs");
463
+ import_node_path3 = require("node:path");
464
+ }
465
+ });
466
+
467
+ // src/server/orbit/adapters/postgres.ts
468
+ function isIdent(s) {
469
+ return /^[a-zA-Z_][a-zA-Z0-9_]{0,62}$/.test(s);
470
+ }
471
+ function q(ident) {
472
+ return `"${ident.replace(/"/g, '""')}"`;
473
+ }
474
+ function resolveEnvRef(ref) {
475
+ const m = /^\$\{([A-Za-z_][A-Za-z0-9_]*)\}$/.exec(ref);
476
+ if (m) return process.env[m[1]] ?? null;
477
+ return ref || null;
478
+ }
479
+ function composeAdminUrl(sourceUrl) {
480
+ try {
481
+ const u = new URL(sourceUrl);
482
+ u.pathname = "/postgres";
483
+ return u.toString();
484
+ } catch {
485
+ return sourceUrl;
486
+ }
487
+ }
488
+ async function terminateConnections(admin, dbName) {
489
+ await admin.query(
490
+ `SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = $1 AND pid <> pg_backend_pid()`,
491
+ [dbName]
492
+ );
493
+ }
494
+ function stringifyClientResolution(r) {
495
+ if (r.kind === "binary") return `binary:${r.pgDump}${r.version ? ` (v${r.version})` : ""}`;
496
+ if (r.kind === "docker") return `docker:${r.image}`;
497
+ return `unavailable (${r.reasons.join("; ")})`;
498
+ }
499
+ async function dumpRestoreClone(args) {
500
+ const { admin, sourceUrl, dbName, version, schemaOnly, snapshot, logger } = args;
501
+ const versionMajor = Math.floor(version / 1e4);
502
+ const client = resolvePgClient(versionMajor);
503
+ if (client.kind === "unavailable") {
504
+ throw new Error(
505
+ `orbit-pg: pg_dump + psql required for dump-restore: ${client.reasons.join("; ")}`
506
+ );
507
+ }
508
+ const sourceUrlParsed = new URL(sourceUrl);
509
+ const sourceDb = sourceUrlParsed.pathname.slice(1);
510
+ if (!sourceDb) {
511
+ throw new Error("orbit-pg: source URL has no database name in its path");
512
+ }
513
+ logger.step(`[orbit-pg] CREATE DATABASE ${q(dbName)}`);
514
+ await admin.query(`CREATE DATABASE ${q(dbName)}`);
515
+ const dstUrl = new URL(sourceUrl);
516
+ dstUrl.pathname = `/${dbName}`;
517
+ const dumpFlags = ["--no-owner", "--no-acl"];
518
+ if (schemaOnly) dumpFlags.push("--schema-only");
519
+ const pgDumpUrl = urlForShell(sourceUrl, client.kind);
520
+ const psqlUrl = urlForShell(dstUrl.toString(), client.kind);
521
+ const pgDumpCmd = client.kind === "binary" ? `${shellQuote(client.pgDump)} ${shellQuote(pgDumpUrl)} ${dumpFlags.join(" ")}` : `docker run --rm ${dockerNetworkArgs()}${shellQuote(client.image)} pg_dump ${shellQuote(pgDumpUrl)} ${dumpFlags.join(" ")}`;
522
+ const psqlCmd = client.kind === "binary" ? `${shellQuote(client.psql)} ${shellQuote(psqlUrl)}` : `docker run --rm -i ${dockerNetworkArgs()}${shellQuote(client.image)} psql ${shellQuote(psqlUrl)}`;
523
+ try {
524
+ if (snapshot) {
525
+ (0, import_node_fs5.mkdirSync)(SNAPSHOT_DIR, { recursive: true });
526
+ const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
527
+ const snapshotPath = (0, import_node_path4.join)(SNAPSHOT_DIR, `${ts}_${sourceDb}_to_${dbName}.sql.gz`);
528
+ logger.step(`[orbit-pg] pg_dump ${sourceDb} \u2192 ${snapshotPath}`);
529
+ runShell(`${pgDumpCmd} | gzip > ${shellQuote(snapshotPath)}`, "pg_dump");
530
+ logger.step(`[orbit-pg] restore ${snapshotPath} \u2192 ${dbName}${schemaOnly ? " (schema only)" : ""}`);
531
+ runShell(`gunzip -c ${shellQuote(snapshotPath)} | ${psqlCmd} >/dev/null`, "psql restore");
532
+ logger.ok(`[orbit-pg] cloned ${sourceDb} \u2192 ${dbName} (snapshot: ${snapshotPath})`);
533
+ } else {
534
+ logger.step(`[orbit-pg] pg_dump ${sourceDb} | psql ${dbName}${schemaOnly ? " (schema only)" : ""}`);
535
+ runShell(`${pgDumpCmd} | ${psqlCmd} >/dev/null`, "pg_dump|psql");
536
+ logger.ok(`[orbit-pg] cloned ${sourceDb} \u2192 ${dbName}`);
537
+ }
538
+ } catch (e) {
539
+ try {
540
+ await admin.query(`DROP DATABASE IF EXISTS ${q(dbName)}`);
541
+ } catch {
542
+ }
543
+ throw e;
544
+ }
545
+ }
546
+ function runShell(cmd, label) {
547
+ const res = (0, import_node_child_process4.spawnSync)("bash", ["-c", `set -o pipefail; ${cmd}`], {
548
+ stdio: ["ignore", "ignore", "pipe"]
549
+ });
550
+ if (res.status !== 0) {
551
+ const stderr = res.stderr?.toString().trim() ?? "no stderr";
552
+ throw new Error(`orbit-pg: ${label} failed (exit ${res.status}): ${stderr}`);
553
+ }
554
+ }
555
+ async function runBackup(state, ctx) {
556
+ (0, import_node_fs5.mkdirSync)(BACKUP_DIR, { recursive: true });
557
+ const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
558
+ const out = (0, import_node_path4.join)(BACKUP_DIR, `${ts}_${state.dbName}.sql.gz`);
559
+ const versionMajor = Math.floor(state.version / 1e4);
560
+ const resolution = resolvePgDump(versionMajor);
561
+ if (resolution.kind === "unavailable") {
562
+ throw new Error(`orbit-pg: pg_dump unavailable (${resolution.reasons.join("; ")}). Re-run with backup=false to skip.`);
563
+ }
564
+ const url = new URL(state.sourceUrl);
565
+ url.pathname = `/${state.dbName}`;
566
+ const dbUrl = urlForShell(url.toString(), resolution.kind === "docker" ? "docker" : "binary");
567
+ const cmd = resolution.kind === "binary" ? `${shellQuote(resolution.path)} ${shellQuote(dbUrl)} --no-owner --no-acl | gzip > ${shellQuote(out)}` : `docker run --rm ${dockerNetworkArgs()}${shellQuote(resolution.image)} pg_dump ${shellQuote(dbUrl)} --no-owner --no-acl | gzip > ${shellQuote(out)}`;
568
+ runShell(cmd, "pg_dump backup");
569
+ if (!(0, import_node_fs5.existsSync)(out)) throw new Error(`pg_dump produced no output file: ${out}`);
570
+ return out;
571
+ }
572
+ function shellQuote(s) {
573
+ return `'${s.replace(/'/g, "'\\''")}'`;
574
+ }
575
+ function dockerizeUrl(url) {
576
+ if (process.platform !== "darwin" && process.platform !== "win32") return url;
577
+ try {
578
+ const u = new URL(url);
579
+ if (u.hostname === "localhost" || u.hostname === "127.0.0.1") {
580
+ u.hostname = "host.docker.internal";
581
+ return u.toString();
582
+ }
583
+ return url;
584
+ } catch {
585
+ return url;
586
+ }
587
+ }
588
+ function dockerNetworkArgs() {
589
+ return process.platform === "linux" ? "--network host " : "";
590
+ }
591
+ function cleanLibpqUrl(url) {
592
+ try {
593
+ const u = new URL(url);
594
+ const PRISMA_ONLY = [
595
+ "schema",
596
+ "connection_limit",
597
+ "pool_timeout",
598
+ "pgbouncer",
599
+ "socket_timeout",
600
+ "statement_cache_size"
601
+ ];
602
+ for (const k of PRISMA_ONLY) u.searchParams.delete(k);
603
+ return u.toString();
604
+ } catch {
605
+ return url;
606
+ }
607
+ }
608
+ function urlForShell(url, kind) {
609
+ const cleaned = cleanLibpqUrl(url);
610
+ return kind === "docker" ? dockerizeUrl(cleaned) : cleaned;
611
+ }
612
+ var import_node_child_process4, import_node_fs5, import_node_os2, import_node_path4, import_pg, Client, BACKUP_DIR, SNAPSHOT_DIR, postgresAdapter;
613
+ var init_postgres = __esm({
614
+ "src/server/orbit/adapters/postgres.ts"() {
615
+ "use strict";
616
+ import_node_child_process4 = require("node:child_process");
617
+ import_node_fs5 = require("node:fs");
618
+ import_node_os2 = require("node:os");
619
+ import_node_path4 = require("node:path");
620
+ import_pg = __toESM(require("pg"));
621
+ init_launch_kit_paths();
622
+ init_pg_detect();
623
+ ({ Client } = import_pg.default);
624
+ BACKUP_DIR = (0, import_node_path4.join)((0, import_node_os2.homedir)(), LAUNCHSECURE_DIR, "orbit", "backups");
625
+ SNAPSHOT_DIR = (0, import_node_path4.join)((0, import_node_os2.homedir)(), LAUNCHSECURE_DIR, "orbit", "snapshots");
626
+ postgresAdapter = {
627
+ id: "orbit-pg",
628
+ async detect(_ctx) {
629
+ const url = resolveEnvRef("${DATABASE_URL}");
630
+ if (!url) {
631
+ return { ok: false, reason: "DATABASE_URL not set in environment" };
632
+ }
633
+ const client = new Client({ connectionString: url });
634
+ try {
635
+ await client.connect();
636
+ const role = await client.query(
637
+ `SELECT rolcreatedb AS create,
638
+ pg_has_role(current_user, 'pg_signal_backend', 'MEMBER') AS signal,
639
+ current_setting('server_version_num')::int AS version
640
+ FROM pg_roles WHERE rolname = current_user`
641
+ );
642
+ const row = role.rows[0];
643
+ if (!row) return { ok: false, reason: "current_user missing from pg_roles (unexpected)" };
644
+ const caps = {
645
+ rolcreatedb: row.create,
646
+ pg_signal_backend: row.signal,
647
+ server_version: row.version,
648
+ pg_client: stringifyClientResolution(resolvePgClient(Math.floor(row.version / 1e4)))
649
+ };
650
+ if (!row.create) {
651
+ return { ok: false, reason: "current_user lacks CREATEDB", capabilities: caps };
652
+ }
653
+ return { ok: true, capabilities: caps };
654
+ } catch (e) {
655
+ return { ok: false, reason: `connect failed: ${e.message}` };
656
+ } finally {
657
+ try {
658
+ await client.end();
659
+ } catch {
660
+ }
661
+ }
662
+ },
663
+ async fork(input) {
664
+ const { slug, config, ctx } = input;
665
+ const url = resolveEnvRef(config.source ?? "${DATABASE_URL}");
666
+ if (!url) throw new Error("orbit-pg: source URL did not resolve (env var unset?)");
667
+ const prefix = config.prefix ?? "orbit_";
668
+ const strategy = config.strategy ?? "dump-restore";
669
+ const template = config.template ?? `${prefix}template`;
670
+ const dbName = `${prefix}${slug}`;
671
+ const envKeys = config.rewrite?.env ?? ["DATABASE_URL"];
672
+ const snapshot = config.snapshot ?? true;
673
+ if (!isIdent(dbName)) throw new Error(`orbit-pg: derived dbName "${dbName}" is not a valid PG identifier`);
674
+ if (strategy === "template-clone" && !isIdent(template)) {
675
+ throw new Error(`orbit-pg: template "${template}" is not a valid PG identifier`);
676
+ }
677
+ const admin = new Client({ connectionString: composeAdminUrl(url) });
678
+ await admin.connect();
679
+ let version;
680
+ try {
681
+ version = (await admin.query(
682
+ `SELECT current_setting('server_version_num')::int AS v`
683
+ )).rows[0].v;
684
+ if (strategy === "template-clone") {
685
+ ctx.logger.step(`[orbit-pg] CREATE DATABASE ${q(dbName)} TEMPLATE ${q(template)}`);
686
+ await terminateConnections(admin, template);
687
+ const strategyClause = version >= 15e4 ? " STRATEGY = FILE_COPY" : "";
688
+ await admin.query(`CREATE DATABASE ${q(dbName)} WITH TEMPLATE ${q(template)}${strategyClause}`);
689
+ ctx.logger.ok(`[orbit-pg] created ${dbName} via template clone`);
690
+ } else {
691
+ const schemaOnly = strategy === "schema-only";
692
+ await dumpRestoreClone({
693
+ admin,
694
+ sourceUrl: url,
695
+ dbName,
696
+ version,
697
+ schemaOnly,
698
+ snapshot,
699
+ logger: ctx.logger
700
+ });
701
+ }
702
+ } finally {
703
+ try {
704
+ await admin.end();
705
+ } catch {
706
+ }
707
+ }
708
+ return { dbName, sourceUrl: url, version, strategy, envKeys };
709
+ },
710
+ async drop(input) {
711
+ const { state, backup, ctx } = input;
712
+ const adminUrl = composeAdminUrl(state.sourceUrl);
713
+ let backupPath;
714
+ if (backup) {
715
+ backupPath = await runBackup(state, ctx);
716
+ ctx.logger.ok(`[orbit-pg] backup \u2192 ${backupPath}`);
717
+ }
718
+ const admin = new Client({ connectionString: adminUrl });
719
+ await admin.connect();
720
+ try {
721
+ await terminateConnections(admin, state.dbName);
722
+ ctx.logger.step(`[orbit-pg] DROP DATABASE ${q(state.dbName)}`);
723
+ await admin.query(`DROP DATABASE IF EXISTS ${q(state.dbName)}`);
724
+ ctx.logger.ok(`[orbit-pg] dropped ${state.dbName}`);
725
+ } finally {
726
+ try {
727
+ await admin.end();
728
+ } catch {
729
+ }
730
+ }
731
+ return { backupPath };
732
+ },
733
+ envRewrites(state) {
734
+ const map = {};
735
+ for (const key of state.envKeys) {
736
+ map[key] = (oldValue) => {
737
+ if (typeof oldValue !== "string") return void 0;
738
+ try {
739
+ const u = new URL(oldValue);
740
+ u.pathname = `/${state.dbName}`;
741
+ return u.toString();
742
+ } catch {
743
+ return void 0;
744
+ }
745
+ };
746
+ }
747
+ return map;
748
+ }
749
+ };
750
+ }
751
+ });
752
+
753
+ // src/server/orbit/adapter-registry.ts
754
+ function getWorktreeAdapter(id) {
755
+ const a = WORKTREE[id];
756
+ if (!a) {
757
+ throw new Error(
758
+ `unknown worktree adapter "${id}". Known: ${Object.keys(WORKTREE).join(", ")}.`
759
+ );
760
+ }
761
+ return a;
762
+ }
763
+ function getResourceAdapter(id) {
764
+ const a = RESOURCE[id];
765
+ if (!a) {
766
+ throw new Error(
767
+ `unknown resource adapter "${id}". Known: ${Object.keys(RESOURCE).join(", ")}.`
768
+ );
769
+ }
770
+ return a;
771
+ }
772
+ var WORKTREE, RESOURCE;
773
+ var init_adapter_registry = __esm({
774
+ "src/server/orbit/adapter-registry.ts"() {
775
+ "use strict";
776
+ init_git_worktree();
777
+ init_claude_code();
778
+ init_port_range();
779
+ init_postgres();
780
+ WORKTREE = {
781
+ "builtin/git-worktree": gitWorktreeAdapter,
782
+ "builtin/claude-code": claudeCodeAdapter
783
+ };
784
+ RESOURCE = {
785
+ "builtin/port-range": portRangeAdapter,
786
+ "orbit-pg": postgresAdapter
787
+ };
788
+ }
789
+ });
790
+
791
+ // src/server/orbit/env-rewriter.ts
792
+ function copyEnvFile(srcPath, dstPath) {
793
+ if (!(0, import_node_fs6.existsSync)(srcPath)) {
794
+ throw new Error(`env source file not found: ${srcPath}`);
795
+ }
796
+ (0, import_node_fs6.copyFileSync)(srcPath, dstPath);
797
+ }
798
+ function rewriteEnvFile(filePath, rewrites) {
799
+ if (!(0, import_node_fs6.existsSync)(filePath)) {
800
+ throw new Error(`env file not found for rewrite: ${filePath}`);
801
+ }
802
+ const original = (0, import_node_fs6.readFileSync)(filePath, "utf-8");
803
+ const lines = original.split(/\r?\n/);
804
+ const result = { changed: [], unchanged: [], missing: [] };
805
+ const touched = /* @__PURE__ */ new Set();
806
+ const out = lines.map((line) => {
807
+ const m = LINE_RE.exec(line);
808
+ if (!m) return line;
809
+ const [, indent, key, sep, rawValue] = m;
810
+ if (!(key in rewrites)) return line;
811
+ touched.add(key);
812
+ const { quote, inner } = stripQuotes(rawValue);
813
+ const transform = rewrites[key];
814
+ const next2 = transform(inner, key);
815
+ if (next2 === void 0 || next2 === inner) {
816
+ result.unchanged.push(key);
817
+ return line;
818
+ }
819
+ result.changed.push(key);
820
+ return `${indent}${key}${sep}${quote}${next2}${quote}`;
821
+ });
822
+ for (const key of Object.keys(rewrites)) {
823
+ if (!touched.has(key)) result.missing.push(key);
824
+ }
825
+ const next = out.join("\n");
826
+ if (next !== original) {
827
+ const tmp = `${filePath}.tmp.${process.pid}`;
828
+ (0, import_node_fs6.writeFileSync)(tmp, next, "utf-8");
829
+ (0, import_node_fs6.renameSync)(tmp, filePath);
830
+ }
831
+ return result;
832
+ }
833
+ function stripQuotes(raw) {
834
+ const trimmed = raw.replace(/\s+#.*$/, "");
835
+ if (trimmed.length >= 2) {
836
+ const first = trimmed[0];
837
+ const last = trimmed[trimmed.length - 1];
838
+ if (first === '"' && last === '"' || first === "'" && last === "'") {
839
+ return { quote: first, inner: trimmed.slice(1, -1) };
840
+ }
841
+ }
842
+ return { quote: "", inner: trimmed };
843
+ }
844
+ var import_node_fs6, LINE_RE;
845
+ var init_env_rewriter = __esm({
846
+ "src/server/orbit/env-rewriter.ts"() {
847
+ "use strict";
848
+ import_node_fs6 = require("node:fs");
849
+ LINE_RE = /^(\s*)([A-Za-z_][A-Za-z0-9_]*)(\s*=\s*)(.*)$/;
850
+ }
851
+ });
852
+
853
+ // src/server/orbit/gates/build-lint.ts
854
+ function finalize(checkPath, ctx, blockers, artifacts) {
855
+ try {
856
+ (0, import_node_child_process5.execFileSync)("git", ["worktree", "remove", "--force", checkPath], {
857
+ cwd: ctx.projectRoot,
858
+ stdio: "ignore"
859
+ });
860
+ } catch {
861
+ try {
862
+ (0, import_node_fs7.rmSync)(checkPath, { recursive: true, force: true });
863
+ } catch {
864
+ }
865
+ }
866
+ return {
867
+ ok: blockers.length === 0,
868
+ gateId: "builtin/build-lint",
869
+ durationMs: 0,
870
+ blockers,
871
+ artifacts: Object.keys(artifacts).length > 0 ? artifacts : void 0
872
+ };
873
+ }
874
+ function detectInstallCmd(projectRoot) {
875
+ if ((0, import_node_fs7.existsSync)((0, import_node_path5.join)(projectRoot, "pnpm-lock.yaml"))) return "pnpm install --prefer-offline";
876
+ if ((0, import_node_fs7.existsSync)((0, import_node_path5.join)(projectRoot, "yarn.lock"))) return "yarn install --prefer-offline";
877
+ if ((0, import_node_fs7.existsSync)((0, import_node_path5.join)(projectRoot, "bun.lockb"))) return "bun install";
878
+ return "npm install --prefer-offline --no-audit --no-fund";
879
+ }
880
+ function detectLintCmd(worktreePath) {
881
+ return detectScriptCmd(worktreePath, "lint");
882
+ }
883
+ function detectBuildCmd(worktreePath) {
884
+ return detectScriptCmd(worktreePath, "build");
885
+ }
886
+ function detectScriptCmd(worktreePath, scriptName) {
887
+ const pkgPath = (0, import_node_path5.join)(worktreePath, "package.json");
888
+ if (!(0, import_node_fs7.existsSync)(pkgPath)) return null;
889
+ let pkg;
890
+ try {
891
+ pkg = JSON.parse((0, import_node_fs7.readFileSync)(pkgPath, "utf-8"));
892
+ } catch {
893
+ return null;
894
+ }
895
+ if (!pkg.scripts?.[scriptName]) return null;
896
+ const pm = pkg.packageManager?.split("@")[0] ?? detectPmFromLock(worktreePath);
897
+ return `${pm} run ${scriptName}`;
898
+ }
899
+ function loadOrbitEnv(orbitPath) {
900
+ const file = (0, import_node_path5.join)(orbitPath, ".env.local");
901
+ if (!(0, import_node_fs7.existsSync)(file)) return {};
902
+ const out = {};
903
+ let content;
904
+ try {
905
+ content = (0, import_node_fs7.readFileSync)(file, "utf-8");
906
+ } catch {
907
+ return {};
908
+ }
909
+ for (const line of content.split(/\r?\n/)) {
910
+ const m = /^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/.exec(line);
911
+ if (!m) continue;
912
+ let value = m[2].trim().replace(/\s+#.*$/, "");
913
+ if (value.length >= 2 && (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'"))) {
914
+ value = value.slice(1, -1);
915
+ }
916
+ out[m[1]] = value;
917
+ }
918
+ return out;
919
+ }
920
+ function detectPmFromLock(worktreePath) {
921
+ if ((0, import_node_fs7.existsSync)((0, import_node_path5.join)(worktreePath, "pnpm-lock.yaml"))) return "pnpm";
922
+ if ((0, import_node_fs7.existsSync)((0, import_node_path5.join)(worktreePath, "yarn.lock"))) return "yarn";
923
+ if ((0, import_node_fs7.existsSync)((0, import_node_path5.join)(worktreePath, "bun.lockb"))) return "bun";
924
+ return "npm";
925
+ }
926
+ function combineOutput(stdout, stderr) {
927
+ const out = (stdout ?? "").trimEnd();
928
+ const err2 = (stderr ?? "").trimEnd();
929
+ if (!out && !err2) return "";
930
+ if (!err2) return out + "\n";
931
+ if (!out) return err2 + "\n";
932
+ return `${out}
933
+
934
+ ---STDERR---
935
+ ${err2}
936
+ `;
937
+ }
938
+ function saveLog(slug, label, content) {
939
+ const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
940
+ const path = (0, import_node_path5.join)(LOG_DIR, `${ts}_${slug}_${label}.log`);
941
+ (0, import_node_fs7.writeFileSync)(path, content, "utf-8");
942
+ return path;
943
+ }
944
+ function shellQuote2(s) {
945
+ return `'${s.replace(/'/g, "'\\''")}'`;
946
+ }
947
+ var import_node_child_process5, import_node_fs7, import_node_os3, import_node_path5, LOG_DIR, buildLintGate;
948
+ var init_build_lint = __esm({
949
+ "src/server/orbit/gates/build-lint.ts"() {
950
+ "use strict";
951
+ import_node_child_process5 = require("node:child_process");
952
+ import_node_fs7 = require("node:fs");
953
+ import_node_os3 = require("node:os");
954
+ import_node_path5 = require("node:path");
955
+ init_launch_kit_paths();
956
+ LOG_DIR = (0, import_node_path5.join)((0, import_node_os3.homedir)(), LAUNCHSECURE_DIR, "orbit", "gate-logs");
957
+ buildLintGate = {
958
+ id: "builtin/build-lint",
959
+ name: "Build + lint on merged state",
960
+ async detect(ctx) {
961
+ try {
962
+ (0, import_node_child_process5.execFileSync)("git", ["--version"], { cwd: ctx.projectRoot, stdio: "ignore" });
963
+ } catch {
964
+ return { ok: false, reason: "git not available" };
965
+ }
966
+ return { ok: true };
967
+ },
968
+ async check(ctx, config) {
969
+ (0, import_node_fs7.mkdirSync)(LOG_DIR, { recursive: true });
970
+ const checkPath = (0, import_node_path5.join)(ctx.projectRoot, ".claude", "worktrees", `__orbit-check-${ctx.entry.slug}__`);
971
+ if ((0, import_node_fs7.existsSync)(checkPath)) {
972
+ try {
973
+ (0, import_node_child_process5.execFileSync)("git", ["worktree", "remove", "--force", checkPath], {
974
+ cwd: ctx.projectRoot,
975
+ stdio: "ignore"
976
+ });
977
+ } catch {
978
+ }
979
+ try {
980
+ (0, import_node_fs7.rmSync)(checkPath, { recursive: true, force: true });
981
+ } catch {
982
+ }
983
+ }
984
+ const blockers = [];
985
+ const artifacts = {};
986
+ try {
987
+ (0, import_node_child_process5.execFileSync)(
988
+ "git",
989
+ ["worktree", "add", "--detach", checkPath, ctx.entry.branch],
990
+ { cwd: ctx.projectRoot, stdio: ["ignore", "pipe", "pipe"] }
991
+ );
992
+ } catch (e) {
993
+ return {
994
+ ok: false,
995
+ gateId: "builtin/build-lint",
996
+ durationMs: 0,
997
+ blockers: [`failed to create ephemeral merge worktree: ${e.message}`]
998
+ };
999
+ }
1000
+ try {
1001
+ const mergeRes = (0, import_node_child_process5.spawnSync)(
1002
+ "git",
1003
+ ["merge", "--no-commit", "--no-ff", ctx.target],
1004
+ { cwd: checkPath, stdio: ["ignore", "pipe", "pipe"], encoding: "utf-8" }
1005
+ );
1006
+ if (mergeRes.status !== 0) {
1007
+ const stderr = (mergeRes.stderr ?? "").toString().trim();
1008
+ blockers.push(`merge into ephemeral worktree failed: ${stderr.split("\n")[0] ?? "unknown"}`);
1009
+ return finalize(checkPath, ctx, blockers, artifacts);
1010
+ }
1011
+ const orbitEnv = loadOrbitEnv(ctx.entry.path);
1012
+ const spawnEnv = { ...process.env, ...orbitEnv };
1013
+ if (!(0, import_node_fs7.existsSync)((0, import_node_path5.join)(checkPath, "node_modules"))) {
1014
+ const installCmd = config.installCmd ?? detectInstallCmd(ctx.projectRoot);
1015
+ ctx.logger.step(`[build-lint] ${installCmd}`);
1016
+ const installRes = (0, import_node_child_process5.spawnSync)("bash", ["-c", `cd ${shellQuote2(checkPath)} && ${installCmd}`], {
1017
+ stdio: ["ignore", "pipe", "pipe"],
1018
+ encoding: "utf-8",
1019
+ env: spawnEnv
1020
+ });
1021
+ if (installRes.status !== 0) {
1022
+ const log = saveLog(ctx.entry.slug, "install", combineOutput(installRes.stdout, installRes.stderr));
1023
+ blockers.push("dependency install failed in ephemeral worktree");
1024
+ artifacts.install_log = log;
1025
+ return finalize(checkPath, ctx, blockers, artifacts);
1026
+ }
1027
+ }
1028
+ if ((0, import_node_fs7.existsSync)((0, import_node_path5.join)(checkPath, "prisma", "schema.prisma"))) {
1029
+ const prismaCmd = "npx prisma generate";
1030
+ ctx.logger.step(`[build-lint] ${prismaCmd}`);
1031
+ const prismaRes = (0, import_node_child_process5.spawnSync)("bash", ["-c", `cd ${shellQuote2(checkPath)} && ${prismaCmd}`], {
1032
+ stdio: ["ignore", "pipe", "pipe"],
1033
+ encoding: "utf-8",
1034
+ env: spawnEnv
1035
+ });
1036
+ if (prismaRes.status !== 0) {
1037
+ const log = saveLog(
1038
+ ctx.entry.slug,
1039
+ "prisma-generate",
1040
+ combineOutput(prismaRes.stdout, prismaRes.stderr)
1041
+ );
1042
+ blockers.push(`prisma generate failed (exit ${prismaRes.status}) \u2014 tsc will not be reliable`);
1043
+ artifacts["prisma-generate_log"] = log;
1044
+ return finalize(checkPath, ctx, blockers, artifacts);
1045
+ }
1046
+ }
1047
+ const tscMode = config.tsc ?? true;
1048
+ if (tscMode !== false) {
1049
+ const tscCmd = typeof tscMode === "string" ? tscMode : "npx tsc --noEmit";
1050
+ ctx.logger.step(`[build-lint] ${tscCmd}`);
1051
+ const tscRes = (0, import_node_child_process5.spawnSync)("bash", ["-c", `cd ${shellQuote2(checkPath)} && ${tscCmd}`], {
1052
+ stdio: ["ignore", "pipe", "pipe"],
1053
+ encoding: "utf-8",
1054
+ env: spawnEnv
1055
+ });
1056
+ if (tscRes.status !== 0) {
1057
+ const log = saveLog(ctx.entry.slug, "tsc", combineOutput(tscRes.stdout, tscRes.stderr));
1058
+ blockers.push(`tsc reported errors (exit ${tscRes.status})`);
1059
+ artifacts.tsc_log = log;
1060
+ }
1061
+ }
1062
+ const lintMode = config.lint ?? true;
1063
+ if (lintMode !== false) {
1064
+ const lintCmd = typeof lintMode === "string" ? lintMode : detectLintCmd(checkPath);
1065
+ if (lintCmd) {
1066
+ ctx.logger.step(`[build-lint] ${lintCmd}`);
1067
+ const lintRes = (0, import_node_child_process5.spawnSync)("bash", ["-c", `cd ${shellQuote2(checkPath)} && ${lintCmd}`], {
1068
+ stdio: ["ignore", "pipe", "pipe"],
1069
+ encoding: "utf-8",
1070
+ env: spawnEnv
1071
+ });
1072
+ if (lintRes.status !== 0) {
1073
+ const log = saveLog(ctx.entry.slug, "lint", combineOutput(lintRes.stdout, lintRes.stderr));
1074
+ blockers.push(`lint reported errors (exit ${lintRes.status})`);
1075
+ artifacts.lint_log = log;
1076
+ }
1077
+ } else if (lintMode === true) {
1078
+ ctx.logger.info(`[build-lint] no scripts.lint in package.json \u2014 skipping lint step`);
1079
+ }
1080
+ }
1081
+ const buildMode = config.build ?? true;
1082
+ if (buildMode !== false) {
1083
+ const buildCmd = typeof buildMode === "string" ? buildMode : detectBuildCmd(checkPath);
1084
+ if (buildCmd) {
1085
+ ctx.logger.step(`[build-lint] ${buildCmd}`);
1086
+ const buildRes = (0, import_node_child_process5.spawnSync)("bash", ["-c", `cd ${shellQuote2(checkPath)} && ${buildCmd}`], {
1087
+ stdio: ["ignore", "pipe", "pipe"],
1088
+ encoding: "utf-8",
1089
+ env: spawnEnv
1090
+ });
1091
+ if (buildRes.status !== 0) {
1092
+ const log = saveLog(ctx.entry.slug, "build", combineOutput(buildRes.stdout, buildRes.stderr));
1093
+ blockers.push(`build reported errors (exit ${buildRes.status})`);
1094
+ artifacts.build_log = log;
1095
+ }
1096
+ } else if (buildMode === true) {
1097
+ ctx.logger.info(`[build-lint] no scripts.build in package.json \u2014 skipping build step`);
1098
+ }
1099
+ }
1100
+ return finalize(checkPath, ctx, blockers, artifacts);
1101
+ } catch (e) {
1102
+ blockers.push(`build-lint gate threw: ${e.message}`);
1103
+ return finalize(checkPath, ctx, blockers, artifacts);
1104
+ }
1105
+ }
1106
+ };
1107
+ }
1108
+ });
1109
+
1110
+ // src/server/orbit/gates/clean-tree.ts
1111
+ var import_node_child_process6, import_node_fs8, cleanTreeGate;
1112
+ var init_clean_tree = __esm({
1113
+ "src/server/orbit/gates/clean-tree.ts"() {
1114
+ "use strict";
1115
+ import_node_child_process6 = require("node:child_process");
1116
+ import_node_fs8 = require("node:fs");
1117
+ cleanTreeGate = {
1118
+ id: "builtin/clean-tree",
1119
+ name: "Orbit worktree has no uncommitted changes",
1120
+ async detect(ctx) {
1121
+ if (!(0, import_node_fs8.existsSync)(ctx.entry.path)) {
1122
+ return { ok: false, reason: `orbit worktree path missing: ${ctx.entry.path}` };
1123
+ }
1124
+ try {
1125
+ (0, import_node_child_process6.execFileSync)("git", ["rev-parse", "--is-inside-work-tree"], {
1126
+ cwd: ctx.entry.path,
1127
+ stdio: "ignore"
1128
+ });
1129
+ return { ok: true };
1130
+ } catch {
1131
+ return { ok: false, reason: `${ctx.entry.path} is not inside a git work tree` };
1132
+ }
1133
+ },
1134
+ async check(ctx, _config) {
1135
+ let output;
1136
+ try {
1137
+ output = (0, import_node_child_process6.execFileSync)("git", ["status", "--porcelain"], {
1138
+ cwd: ctx.entry.path,
1139
+ encoding: "utf-8",
1140
+ stdio: ["ignore", "pipe", "pipe"]
1141
+ });
1142
+ } catch (e) {
1143
+ return {
1144
+ ok: false,
1145
+ gateId: "builtin/clean-tree",
1146
+ durationMs: 0,
1147
+ blockers: [`git status failed in ${ctx.entry.path}: ${e.message}`]
1148
+ };
1149
+ }
1150
+ const lines = output.split("\n").filter((l) => l.length > 0);
1151
+ if (lines.length === 0) {
1152
+ return { ok: true, gateId: "builtin/clean-tree", durationMs: 0, blockers: [] };
1153
+ }
1154
+ return {
1155
+ ok: false,
1156
+ gateId: "builtin/clean-tree",
1157
+ durationMs: 0,
1158
+ blockers: [
1159
+ `orbit worktree has uncommitted changes (${lines.length} ${lines.length === 1 ? "entry" : "entries"}):`,
1160
+ ...lines.map((l) => ` ${l}`),
1161
+ `commit, stash, or remove these before merging.`
1162
+ ]
1163
+ };
1164
+ }
1165
+ };
1166
+ }
1167
+ });
1168
+
1169
+ // src/server/orbit/gates/mergeability.ts
1170
+ function refExists(cwd, ref) {
1171
+ try {
1172
+ (0, import_node_child_process7.execFileSync)("git", ["rev-parse", "--verify", ref], { cwd, stdio: "ignore" });
1173
+ return true;
1174
+ } catch {
1175
+ return false;
1176
+ }
1177
+ }
1178
+ var import_node_child_process7, mergeabilityGate;
1179
+ var init_mergeability = __esm({
1180
+ "src/server/orbit/gates/mergeability.ts"() {
1181
+ "use strict";
1182
+ import_node_child_process7 = require("node:child_process");
1183
+ mergeabilityGate = {
1184
+ id: "builtin/mergeability",
1185
+ name: "Textual mergeability (git merge-tree)",
1186
+ async detect(ctx) {
1187
+ try {
1188
+ (0, import_node_child_process7.execFileSync)("git", ["rev-parse", "--show-toplevel"], {
1189
+ cwd: ctx.projectRoot,
1190
+ stdio: "ignore"
1191
+ });
1192
+ return { ok: true };
1193
+ } catch {
1194
+ return { ok: false, reason: "git not available in projectRoot" };
1195
+ }
1196
+ },
1197
+ async check(ctx, _config) {
1198
+ const blockers = [];
1199
+ if (!refExists(ctx.projectRoot, ctx.target)) {
1200
+ blockers.push(`target ref "${ctx.target}" does not exist`);
1201
+ }
1202
+ if (!refExists(ctx.projectRoot, ctx.entry.branch)) {
1203
+ blockers.push(`orbit branch "${ctx.entry.branch}" does not exist`);
1204
+ }
1205
+ if (blockers.length > 0) {
1206
+ return { ok: false, gateId: "builtin/mergeability", durationMs: 0, blockers };
1207
+ }
1208
+ let stdout = "";
1209
+ let exitCode = 0;
1210
+ try {
1211
+ stdout = (0, import_node_child_process7.execFileSync)(
1212
+ "git",
1213
+ ["merge-tree", "--write-tree", "--messages", ctx.target, ctx.entry.branch],
1214
+ { cwd: ctx.projectRoot, encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] }
1215
+ );
1216
+ } catch (e) {
1217
+ const err2 = e;
1218
+ exitCode = err2.status ?? 1;
1219
+ stdout = err2.stdout?.toString() ?? "";
1220
+ }
1221
+ const lines = stdout.split("\n").filter((l) => l.length > 0);
1222
+ if (exitCode === 0 && lines.length <= 1) {
1223
+ return {
1224
+ ok: true,
1225
+ gateId: "builtin/mergeability",
1226
+ durationMs: 0,
1227
+ blockers: []
1228
+ };
1229
+ }
1230
+ const conflictLines = lines.slice(1);
1231
+ return {
1232
+ ok: false,
1233
+ gateId: "builtin/mergeability",
1234
+ durationMs: 0,
1235
+ blockers: conflictLines.length > 0 ? [`textual conflicts between ${ctx.entry.branch} and ${ctx.target}:`, ...conflictLines.map((l) => ` ${l}`)] : [`merge-tree reported exit ${exitCode} but produced no detail`]
1236
+ };
1237
+ }
1238
+ };
1239
+ }
1240
+ });
1241
+
1242
+ // src/server/orbit/gate-registry.ts
1243
+ function getMergeGate(id) {
1244
+ const g = GATES[id];
1245
+ if (!g) {
1246
+ throw new Error(
1247
+ `unknown merge gate "${id}". Known: ${Object.keys(GATES).join(", ")}.`
1248
+ );
1249
+ }
1250
+ return g;
1251
+ }
1252
+ var GATES;
1253
+ var init_gate_registry = __esm({
1254
+ "src/server/orbit/gate-registry.ts"() {
1255
+ "use strict";
1256
+ init_build_lint();
1257
+ init_clean_tree();
1258
+ init_mergeability();
1259
+ GATES = {
1260
+ "builtin/clean-tree": cleanTreeGate,
1261
+ "builtin/mergeability": mergeabilityGate,
1262
+ "builtin/build-lint": buildLintGate
1263
+ };
1264
+ }
1265
+ });
1266
+
1267
+ // src/server/orbit/gate-runner.ts
1268
+ async function runMergeGates(manifest, ctx, options = {}) {
1269
+ const refs = manifest.gates?.merge ?? DEFAULT_GATES;
1270
+ const skip = new Set(options.skipGates ?? []);
1271
+ if (skip.size > 0) {
1272
+ const declared = new Set(refs.map((r) => r.adapter));
1273
+ for (const id of skip) {
1274
+ if (!declared.has(id)) {
1275
+ ctx.logger.warn(
1276
+ `--skip-gate "${id}" does not match any declared gate (declared: ${[...declared].join(", ")})`
1277
+ );
1278
+ }
1279
+ }
1280
+ const blockedRequired = refs.filter((r) => r.required && skip.has(r.adapter)).map((r) => r.adapter);
1281
+ if (blockedRequired.length > 0) {
1282
+ throw new Error(
1283
+ `cannot skip required gate(s): ${blockedRequired.join(", ")}. Remove "required": true from orbit.json if you really need to bypass.`
1284
+ );
1285
+ }
1286
+ }
1287
+ const start = Date.now();
1288
+ const results = [];
1289
+ for (const ref of refs) {
1290
+ if (skip.has(ref.adapter)) {
1291
+ ctx.logger.info(`[gate] skip ${ref.adapter} (--skip-gate)`);
1292
+ continue;
1293
+ }
1294
+ const gate = getMergeGate(ref.adapter);
1295
+ const detect = await gate.detect(ctx);
1296
+ if (!detect.ok) {
1297
+ results.push({
1298
+ ok: false,
1299
+ gateId: gate.id,
1300
+ durationMs: 0,
1301
+ blockers: [`gate ${gate.id} unavailable: ${detect.reason ?? "unknown"}`]
1302
+ });
1303
+ continue;
1304
+ }
1305
+ const gateStart = Date.now();
1306
+ try {
1307
+ const result = await gate.check(ctx, ref.config ?? {});
1308
+ results.push({ ...result, gateId: gate.id, durationMs: Date.now() - gateStart });
1309
+ } catch (e) {
1310
+ results.push({
1311
+ ok: false,
1312
+ gateId: gate.id,
1313
+ durationMs: Date.now() - gateStart,
1314
+ blockers: [`gate ${gate.id} threw: ${e.message}`]
1315
+ });
1316
+ }
1317
+ }
1318
+ return {
1319
+ allPassed: results.every((r) => r.ok),
1320
+ results,
1321
+ durationMs: Date.now() - start
1322
+ };
1323
+ }
1324
+ function formatGateResults(results) {
1325
+ const lines = [];
1326
+ for (const r of results) {
1327
+ const mark = r.ok ? "\u2713" : "\u2717";
1328
+ lines.push(` ${mark} ${r.gateId} (${r.durationMs}ms)`);
1329
+ if (!r.ok) {
1330
+ for (const b of r.blockers) lines.push(` ${b}`);
1331
+ if (r.artifacts) {
1332
+ for (const [k, v] of Object.entries(r.artifacts)) lines.push(` ${k}: ${v}`);
1333
+ }
1334
+ }
1335
+ }
1336
+ return lines.join("\n");
1337
+ }
1338
+ var DEFAULT_GATES;
1339
+ var init_gate_runner = __esm({
1340
+ "src/server/orbit/gate-runner.ts"() {
1341
+ "use strict";
1342
+ init_gate_registry();
1343
+ DEFAULT_GATES = [{ adapter: "builtin/mergeability", required: true }];
1344
+ }
1345
+ });
1346
+
1347
+ // src/server/orbit/manifest.ts
1348
+ function findManifestPath(projectRoot) {
1349
+ return (0, import_node_path6.join)(projectRoot, DEFAULT_MANIFEST_FILENAME);
1350
+ }
1351
+ function manifestExists(projectRoot) {
1352
+ return (0, import_node_fs9.existsSync)(findManifestPath(projectRoot));
1353
+ }
1354
+ function loadManifest(projectRoot) {
1355
+ const path = findManifestPath(projectRoot);
1356
+ if (!(0, import_node_fs9.existsSync)(path)) {
1357
+ throw new Error(
1358
+ `orbit.json not found at ${path}. Run \`launch-orbit init\` to create one.`
1359
+ );
1360
+ }
1361
+ let raw;
1362
+ try {
1363
+ raw = JSON.parse((0, import_node_fs9.readFileSync)(path, "utf-8"));
1364
+ } catch (e) {
1365
+ throw new Error(`orbit.json is not valid JSON: ${e.message}`);
1366
+ }
1367
+ return validateManifest(raw, path);
1368
+ }
1369
+ function validateManifest(raw, sourcePath) {
1370
+ if (!isRecord(raw)) throw err("must be an object", sourcePath);
1371
+ if (raw.version !== 1) throw err(`unsupported version ${String(raw.version)} (expected 1)`, sourcePath);
1372
+ const envFile = validateEnvFile(raw.envFile, sourcePath);
1373
+ const worktree = validateWorktreeRef(raw.worktree, sourcePath);
1374
+ const resources = validateResourcesArray(raw.resources, sourcePath);
1375
+ const gates = validateGates(raw.gates, sourcePath);
1376
+ const profiles = validateProfiles(raw.profiles, resources, sourcePath);
1377
+ return {
1378
+ version: 1,
1379
+ ...envFile !== void 0 ? { envFile } : {},
1380
+ worktree,
1381
+ resources,
1382
+ ...gates !== void 0 ? { gates } : {},
1383
+ ...profiles !== void 0 ? { profiles } : {}
1384
+ };
1385
+ }
1386
+ function validateProfiles(raw, resources, sourcePath) {
1387
+ if (raw === void 0) return void 0;
1388
+ if (!isRecord(raw)) throw err("profiles must be an object if present", sourcePath);
1389
+ const known = new Set(resources.map((r) => r.name));
1390
+ const out = {};
1391
+ for (const [name, value] of Object.entries(raw)) {
1392
+ if (!name) throw err("profiles keys must be non-empty strings", sourcePath);
1393
+ if (!isRecord(value)) throw err(`profiles.${name} must be an object`, sourcePath);
1394
+ if (!Array.isArray(value.resources)) {
1395
+ throw err(`profiles.${name}.resources must be an array of resource names`, sourcePath);
1396
+ }
1397
+ const list2 = [];
1398
+ const seen = /* @__PURE__ */ new Set();
1399
+ for (let i = 0; i < value.resources.length; i++) {
1400
+ const r = value.resources[i];
1401
+ if (typeof r !== "string" || !r) {
1402
+ throw err(`profiles.${name}.resources[${i}] must be a non-empty string`, sourcePath);
1403
+ }
1404
+ if (!known.has(r)) {
1405
+ throw err(
1406
+ `profiles.${name}.resources[${i}] "${r}" does not match any resource name (known: ${[...known].join(", ") || "(none)"})`,
1407
+ sourcePath
1408
+ );
1409
+ }
1410
+ if (seen.has(r)) {
1411
+ throw err(`profiles.${name}.resources[${i}] "${r}" duplicated`, sourcePath);
1412
+ }
1413
+ seen.add(r);
1414
+ list2.push(r);
1415
+ }
1416
+ out[name] = { resources: list2 };
1417
+ }
1418
+ return Object.keys(out).length > 0 ? out : void 0;
1419
+ }
1420
+ function validateGates(raw, sourcePath) {
1421
+ if (raw === void 0) return void 0;
1422
+ if (!isRecord(raw)) throw err("gates must be an object if present", sourcePath);
1423
+ const out = {};
1424
+ if (raw.merge !== void 0) {
1425
+ if (!Array.isArray(raw.merge)) throw err("gates.merge must be an array", sourcePath);
1426
+ const seen = /* @__PURE__ */ new Set();
1427
+ const list2 = [];
1428
+ for (let i = 0; i < raw.merge.length; i++) {
1429
+ const g = raw.merge[i];
1430
+ if (!isRecord(g)) throw err(`gates.merge[${i}] must be an object`, sourcePath);
1431
+ if (typeof g.adapter !== "string" || !g.adapter) {
1432
+ throw err(`gates.merge[${i}].adapter must be a non-empty string`, sourcePath);
1433
+ }
1434
+ if (seen.has(g.adapter)) {
1435
+ throw err(`gates.merge[${i}].adapter "${g.adapter}" duplicated`, sourcePath);
1436
+ }
1437
+ seen.add(g.adapter);
1438
+ const config = g.config;
1439
+ if (config !== void 0 && !isRecord(config)) {
1440
+ throw err(`gates.merge[${i}].config must be an object if present`, sourcePath);
1441
+ }
1442
+ const required = g.required;
1443
+ if (required !== void 0 && typeof required !== "boolean") {
1444
+ throw err(`gates.merge[${i}].required must be a boolean if present`, sourcePath);
1445
+ }
1446
+ list2.push({
1447
+ adapter: g.adapter,
1448
+ config,
1449
+ ...required ? { required: true } : {}
1450
+ });
1451
+ }
1452
+ out.merge = list2;
1453
+ }
1454
+ return Object.keys(out).length > 0 ? out : void 0;
1455
+ }
1456
+ function validateEnvFile(raw, sourcePath) {
1457
+ if (raw === void 0) return void 0;
1458
+ if (typeof raw !== "string" || !raw) throw err("envFile must be a non-empty string", sourcePath);
1459
+ if (raw.includes("..") || raw.startsWith("/")) {
1460
+ throw err(`envFile must be a project-relative path (got "${raw}")`, sourcePath);
1461
+ }
1462
+ return raw;
1463
+ }
1464
+ function validateWorktreeRef(raw, sourcePath) {
1465
+ if (!isRecord(raw)) throw err("worktree must be an object", sourcePath);
1466
+ if (typeof raw.adapter !== "string" || !raw.adapter) {
1467
+ throw err("worktree.adapter must be a non-empty string", sourcePath);
1468
+ }
1469
+ const config = raw.config;
1470
+ if (config !== void 0 && !isRecord(config)) {
1471
+ throw err("worktree.config must be an object if present", sourcePath);
1472
+ }
1473
+ return { adapter: raw.adapter, config };
1474
+ }
1475
+ function validateResourcesArray(raw, sourcePath) {
1476
+ if (!Array.isArray(raw)) throw err("resources must be an array", sourcePath);
1477
+ const seen = /* @__PURE__ */ new Set();
1478
+ const out = [];
1479
+ for (let i = 0; i < raw.length; i++) {
1480
+ const r = raw[i];
1481
+ if (!isRecord(r)) throw err(`resources[${i}] must be an object`, sourcePath);
1482
+ if (typeof r.name !== "string" || !r.name) {
1483
+ throw err(`resources[${i}].name must be a non-empty string`, sourcePath);
1484
+ }
1485
+ if (seen.has(r.name)) throw err(`resources[${i}].name "${r.name}" is duplicated`, sourcePath);
1486
+ seen.add(r.name);
1487
+ if (typeof r.adapter !== "string" || !r.adapter) {
1488
+ throw err(`resources[${i}].adapter must be a non-empty string`, sourcePath);
1489
+ }
1490
+ const config = r.config;
1491
+ if (config !== void 0 && !isRecord(config)) {
1492
+ throw err(`resources[${i}].config must be an object if present`, sourcePath);
1493
+ }
1494
+ out.push({
1495
+ name: r.name,
1496
+ adapter: r.adapter,
1497
+ config
1498
+ });
1499
+ }
1500
+ return out;
1501
+ }
1502
+ function isRecord(x) {
1503
+ return typeof x === "object" && x !== null && !Array.isArray(x);
1504
+ }
1505
+ function err(msg, sourcePath) {
1506
+ return new Error(`orbit.json (${sourcePath}): ${msg}`);
1507
+ }
1508
+ var import_node_fs9, import_node_path6, DEFAULT_MANIFEST_FILENAME;
1509
+ var init_manifest = __esm({
1510
+ "src/server/orbit/manifest.ts"() {
1511
+ "use strict";
1512
+ import_node_fs9 = require("node:fs");
1513
+ import_node_path6 = require("node:path");
1514
+ DEFAULT_MANIFEST_FILENAME = "orbit.json";
1515
+ }
1516
+ });
1517
+
1518
+ // src/server/orbit/slug.ts
1519
+ function slugify(branch) {
1520
+ let s = branch.toLowerCase();
1521
+ s = s.replace(/[^a-z0-9]+/g, "_");
1522
+ s = s.replace(/^_+|_+$/g, "");
1523
+ if (/^\d/.test(s)) s = `b_${s}`;
1524
+ if (s.length > MAX_SLUG_LEN) s = s.slice(0, MAX_SLUG_LEN).replace(/_+$/, "");
1525
+ if (!s) throw new Error(`branch "${branch}" produced empty slug`);
1526
+ return s;
1527
+ }
1528
+ var MAX_SLUG_LEN;
1529
+ var init_slug = __esm({
1530
+ "src/server/orbit/slug.ts"() {
1531
+ "use strict";
1532
+ MAX_SLUG_LEN = 48;
1533
+ }
1534
+ });
1535
+
1536
+ // src/server/orbit/orchestrator.ts
1537
+ async function create(opts) {
1538
+ const start = Date.now();
1539
+ const slug = slugify(opts.branch);
1540
+ const manifest = loadManifestOrThrow(opts.projectRoot);
1541
+ const envFileName = opts.envFileName ?? manifest.envFile ?? DEFAULT_ENV_FILE;
1542
+ const ctx = {
1543
+ projectRoot: opts.projectRoot,
1544
+ manifestPath: (0, import_node_path7.join)(opts.projectRoot, "orbit.json"),
1545
+ manifest,
1546
+ logger: opts.logger,
1547
+ envFileName
1548
+ };
1549
+ if (lookupWorktree(slug)) {
1550
+ throw new Error(`worktree "${slug}" already registered. Run \`launch-orbit drop ${slug}\` first.`);
1551
+ }
1552
+ const effectiveResources = resolveProfile(manifest, opts.profile);
1553
+ if (opts.profile) {
1554
+ opts.logger.info(
1555
+ `profile "${opts.profile}" \u2192 resources: ${effectiveResources.map((r) => r.name).join(", ") || "(none)"}`
1556
+ );
1557
+ }
1558
+ const worktreeAdapter = getWorktreeAdapter(manifest.worktree.adapter);
1559
+ const wtDetect = await worktreeAdapter.detect(ctx);
1560
+ if (!wtDetect.ok) throw new Error(`worktree adapter "${worktreeAdapter.id}" unavailable: ${wtDetect.reason}`);
1561
+ const worktreeConfig = withHookPayload(manifest.worktree.config ?? {}, manifest.worktree.adapter, opts.hookPayload);
1562
+ opts.logger.step(`creating worktree via ${worktreeAdapter.id}`);
1563
+ const worktreeState = await worktreeAdapter.create({
1564
+ slug,
1565
+ branch: opts.branch,
1566
+ baseRef: opts.baseRef,
1567
+ config: worktreeConfig,
1568
+ ctx
1569
+ });
1570
+ opts.logger.ok(`worktree at ${worktreeState.path}`);
1571
+ const cleanups = [
1572
+ async () => {
1573
+ await worktreeAdapter.remove({ state: worktreeState, force: true, ctx });
1574
+ }
1575
+ ];
1576
+ const resourceStates = {};
1577
+ const rewriteFns = {};
1578
+ try {
1579
+ for (const ref of effectiveResources) {
1580
+ const adapter = getResourceAdapter(ref.adapter);
1581
+ const detect = await adapter.detect(ctx);
1582
+ if (!detect.ok) throw new Error(`resource adapter "${adapter.id}" unavailable: ${detect.reason}`);
1583
+ opts.logger.step(`fork resource "${ref.name}" via ${adapter.id}`);
1584
+ const state = await adapter.fork({ slug, config: ref.config ?? {}, ctx });
1585
+ opts.logger.ok(`resource "${ref.name}" forked`);
1586
+ resourceStates[ref.name] = { adapter: adapter.id, state };
1587
+ cleanups.push(async () => {
1588
+ try {
1589
+ await adapter.drop({ state, backup: false, ctx });
1590
+ } catch (e) {
1591
+ opts.logger.warn(`rollback: failed to drop ${ref.name}: ${e.message}`);
1592
+ }
1593
+ });
1594
+ if (adapter.envRewrites) {
1595
+ Object.assign(rewriteFns, adapter.envRewrites(state, ctx));
1596
+ }
1597
+ }
1598
+ const srcEnv = (0, import_node_path7.join)(opts.projectRoot, envFileName);
1599
+ const dstEnv = (0, import_node_path7.join)(worktreeState.path, envFileName);
1600
+ if ((0, import_node_fs10.existsSync)(srcEnv) && !(0, import_node_fs10.existsSync)(dstEnv)) {
1601
+ opts.logger.step(`copy ${envFileName} into worktree`);
1602
+ copyEnvFile(srcEnv, dstEnv);
1603
+ }
1604
+ if ((0, import_node_fs10.existsSync)(dstEnv) && Object.keys(rewriteFns).length > 0) {
1605
+ opts.logger.step(`rewrite ${envFileName} (${Object.keys(rewriteFns).join(", ")})`);
1606
+ const r = rewriteEnvFile(dstEnv, rewriteFns);
1607
+ if (r.missing.length > 0) {
1608
+ opts.logger.warn(`env keys not present in ${envFileName}: ${r.missing.join(", ")}`);
1609
+ }
1610
+ opts.logger.ok(`env rewritten (${r.changed.length} changed, ${r.unchanged.length} unchanged)`);
1611
+ }
1612
+ const entry = {
1613
+ slug,
1614
+ branch: opts.branch,
1615
+ path: worktreeState.path,
1616
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1617
+ projectRoot: opts.projectRoot,
1618
+ resources: resourceStates
1619
+ };
1620
+ await registerWorktree(entry);
1621
+ opts.logger.ok(`registered ${slug}`);
1622
+ return {
1623
+ slug,
1624
+ branch: opts.branch,
1625
+ path: worktreeState.path,
1626
+ envFile: dstEnv,
1627
+ durationMs: Date.now() - start,
1628
+ resources: Object.fromEntries(Object.entries(resourceStates).map(([k, v]) => [k, v.state])),
1629
+ nextAction: `cd ${worktreeState.path}`
1630
+ };
1631
+ } catch (err2) {
1632
+ opts.logger.error(`create failed: ${err2.message} \u2014 rolling back`);
1633
+ for (const undo of cleanups.reverse()) {
1634
+ try {
1635
+ await undo();
1636
+ } catch (e) {
1637
+ opts.logger.warn(`rollback step failed: ${e.message}`);
1638
+ }
1639
+ }
1640
+ throw err2;
1641
+ }
1642
+ }
1643
+ async function drop(opts) {
1644
+ const slug = slugify(opts.branch);
1645
+ const entry = lookupWorktree(slug);
1646
+ if (!entry) {
1647
+ return { slug, backups: {}, freed: {} };
1648
+ }
1649
+ const manifest = loadManifestOrThrow(opts.projectRoot);
1650
+ const ctx = {
1651
+ projectRoot: opts.projectRoot,
1652
+ manifestPath: (0, import_node_path7.join)(opts.projectRoot, "orbit.json"),
1653
+ manifest,
1654
+ logger: opts.logger,
1655
+ envFileName: manifest.envFile ?? DEFAULT_ENV_FILE
1656
+ };
1657
+ const backups = {};
1658
+ const freed = {};
1659
+ const ordered = [...manifest.resources].reverse();
1660
+ for (const ref of ordered) {
1661
+ const state = entry.resources[ref.name];
1662
+ if (!state) continue;
1663
+ const adapter = getResourceAdapter(state.adapter);
1664
+ opts.logger.step(`drop resource "${ref.name}" via ${adapter.id}`);
1665
+ const result = await adapter.drop({
1666
+ state: state.state,
1667
+ backup: opts.backup ?? true,
1668
+ ctx
1669
+ });
1670
+ if (result.backupPath) backups[ref.name] = result.backupPath;
1671
+ freed[ref.name] = state.state;
1672
+ }
1673
+ const worktreeAdapter = getWorktreeAdapter(manifest.worktree.adapter);
1674
+ opts.logger.step(`remove worktree via ${worktreeAdapter.id}`);
1675
+ await worktreeAdapter.remove({
1676
+ state: { path: entry.path, branch: entry.branch },
1677
+ force: true,
1678
+ ctx
1679
+ });
1680
+ await deregisterWorktree(slug);
1681
+ opts.logger.ok(`dropped ${slug}`);
1682
+ return { slug, backups, freed };
1683
+ }
1684
+ function list() {
1685
+ return listWorktrees().map((w) => ({
1686
+ slug: w.slug,
1687
+ branch: w.branch,
1688
+ path: w.path,
1689
+ age: formatAge(w.createdAt),
1690
+ resources: Object.fromEntries(Object.entries(w.resources).map(([k, v]) => [k, v.state]))
1691
+ }));
1692
+ }
1693
+ function switchTo(opts) {
1694
+ const slug = slugify(opts.branch);
1695
+ const entry = lookupWorktree(slug);
1696
+ if (!entry) throw new Error(`no orbit registered for branch "${opts.branch}" (slug ${slug})`);
1697
+ return {
1698
+ path: entry.path,
1699
+ envFile: (0, import_node_path7.join)(entry.path, DEFAULT_ENV_FILE),
1700
+ entry
1701
+ };
1702
+ }
1703
+ function doctor() {
1704
+ const issues = [];
1705
+ for (const w of listWorktrees()) {
1706
+ if (!(0, import_node_fs10.existsSync)(w.path)) {
1707
+ issues.push({ kind: "missing-worktree", slug: w.slug, detail: `path ${w.path} does not exist` });
1708
+ }
1709
+ }
1710
+ return issues;
1711
+ }
1712
+ async function checkMergeable(opts) {
1713
+ const start = Date.now();
1714
+ const slug = slugify(opts.branch);
1715
+ const entry = lookupWorktree(slug);
1716
+ if (!entry) throw new Error(`no orbit registered for branch "${opts.branch}" (slug ${slug})`);
1717
+ const manifest = loadManifestOrThrow(opts.projectRoot);
1718
+ const gateCtx = {
1719
+ entry,
1720
+ target: opts.target,
1721
+ projectRoot: opts.projectRoot,
1722
+ worktreePath: entry.path,
1723
+ manifest,
1724
+ logger: opts.logger
1725
+ };
1726
+ opts.logger.step(`running merge gates against target "${opts.target}"`);
1727
+ const agg = await runMergeGates(manifest, gateCtx, { skipGates: opts.skipGates });
1728
+ opts.logger.info(formatGateResults(agg.results));
1729
+ return {
1730
+ slug,
1731
+ branch: entry.branch,
1732
+ target: opts.target,
1733
+ passed: agg.allPassed,
1734
+ gates: agg.results,
1735
+ durationMs: Date.now() - start
1736
+ };
1737
+ }
1738
+ async function merge(opts) {
1739
+ const start = Date.now();
1740
+ const cleanup = opts.cleanup ?? "full";
1741
+ const check = await checkMergeable({
1742
+ branch: opts.branch,
1743
+ target: opts.target,
1744
+ skipGates: opts.skipGates,
1745
+ projectRoot: opts.projectRoot,
1746
+ logger: opts.logger
1747
+ });
1748
+ if (!check.passed) {
1749
+ const blockers = check.gates.flatMap((g) => g.blockers);
1750
+ opts.logger.error(`gates blocked the merge \u2014 fix the above and retry`);
1751
+ return {
1752
+ slug: check.slug,
1753
+ branch: check.branch,
1754
+ target: opts.target,
1755
+ merged: false,
1756
+ gates: check.gates,
1757
+ cleanedUp: false,
1758
+ blockedBy: blockers,
1759
+ durationMs: Date.now() - start
1760
+ };
1761
+ }
1762
+ const targetWorktree = findWorktreeFor(opts.projectRoot, opts.target);
1763
+ if (!targetWorktree) {
1764
+ return {
1765
+ slug: check.slug,
1766
+ branch: check.branch,
1767
+ target: opts.target,
1768
+ merged: false,
1769
+ gates: check.gates,
1770
+ cleanedUp: false,
1771
+ blockedBy: [`no worktree currently has "${opts.target}" checked out \u2014 check it out in main and retry`],
1772
+ durationMs: Date.now() - start
1773
+ };
1774
+ }
1775
+ opts.logger.step(`git merge --ff-only ${check.branch} (in ${targetWorktree})`);
1776
+ const mergeRes = (0, import_node_child_process8.spawnSync)(
1777
+ "git",
1778
+ ["merge", "--ff-only", check.branch],
1779
+ { cwd: targetWorktree, stdio: ["ignore", "pipe", "pipe"], encoding: "utf-8" }
1780
+ );
1781
+ if (mergeRes.status !== 0) {
1782
+ const stderr = (mergeRes.stderr ?? "").toString().trim();
1783
+ opts.logger.warn(`ff-only failed (${stderr.split("\n")[0]}); attempting non-ff merge`);
1784
+ const merge2 = (0, import_node_child_process8.spawnSync)(
1785
+ "git",
1786
+ ["merge", "--no-ff", "-m", `Merge orbit ${check.slug} into ${opts.target}`, check.branch],
1787
+ { cwd: targetWorktree, stdio: ["ignore", "pipe", "pipe"], encoding: "utf-8" }
1788
+ );
1789
+ if (merge2.status !== 0) {
1790
+ const detail = (merge2.stderr ?? merge2.stdout ?? "").toString().trim();
1791
+ return {
1792
+ slug: check.slug,
1793
+ branch: check.branch,
1794
+ target: opts.target,
1795
+ merged: false,
1796
+ gates: check.gates,
1797
+ cleanedUp: false,
1798
+ blockedBy: [`git merge failed despite passing gates: ${detail.split("\n")[0] ?? "unknown"}`],
1799
+ durationMs: Date.now() - start
1800
+ };
1801
+ }
1802
+ }
1803
+ const mergeSha = (0, import_node_child_process8.execFileSync)("git", ["rev-parse", "HEAD"], {
1804
+ cwd: targetWorktree,
1805
+ encoding: "utf-8"
1806
+ }).trim();
1807
+ opts.logger.ok(`merged ${check.branch} \u2192 ${opts.target} (${mergeSha.slice(0, 7)})`);
1808
+ let backupPath;
1809
+ let cleanedUp = false;
1810
+ if (cleanup === "full") {
1811
+ opts.logger.step(`cleanup: dropping orbit ${check.slug}`);
1812
+ const dropResult = await drop({
1813
+ branch: opts.branch,
1814
+ backup: true,
1815
+ projectRoot: opts.projectRoot,
1816
+ logger: opts.logger
1817
+ });
1818
+ backupPath = dropResult.backups.db;
1819
+ cleanedUp = true;
1820
+ opts.logger.ok(`cleaned up ${check.slug} (backup: ${backupPath ?? "n/a"})`);
1821
+ }
1822
+ return {
1823
+ slug: check.slug,
1824
+ branch: check.branch,
1825
+ target: opts.target,
1826
+ merged: true,
1827
+ mergeSha,
1828
+ gates: check.gates,
1829
+ cleanedUp,
1830
+ backupPath,
1831
+ durationMs: Date.now() - start
1832
+ };
1833
+ }
1834
+ function findWorktreeFor(projectRoot, branch) {
1835
+ try {
1836
+ const out = (0, import_node_child_process8.execFileSync)("git", ["worktree", "list", "--porcelain"], {
1837
+ cwd: projectRoot,
1838
+ encoding: "utf-8"
1839
+ });
1840
+ const blocks = out.split("\n\n").filter(Boolean);
1841
+ for (const block of blocks) {
1842
+ const lines = block.split("\n");
1843
+ const wtLine = lines.find((l) => l.startsWith("worktree "));
1844
+ const brLine = lines.find((l) => l.startsWith("branch "));
1845
+ if (!wtLine || !brLine) continue;
1846
+ const path = wtLine.slice("worktree ".length);
1847
+ const ref = brLine.slice("branch ".length);
1848
+ const name = ref.replace(/^refs\/heads\//, "");
1849
+ if (name === branch) return path;
1850
+ }
1851
+ return null;
1852
+ } catch {
1853
+ return null;
1854
+ }
1855
+ }
1856
+ function resolveProfile(manifest, profile) {
1857
+ if (!profile) return manifest.resources;
1858
+ const ref = manifest.profiles?.[profile];
1859
+ if (!ref) {
1860
+ const known = Object.keys(manifest.profiles ?? {});
1861
+ throw new Error(
1862
+ `profile "${profile}" not declared in orbit.json. Available: ${known.length > 0 ? known.join(", ") : "(none \u2014 add a `profiles` block to orbit.json)"}`
1863
+ );
1864
+ }
1865
+ const byName = new Map(manifest.resources.map((r) => [r.name, r]));
1866
+ return ref.resources.map((name) => {
1867
+ const r = byName.get(name);
1868
+ if (!r) throw new Error(`profile "${profile}" references unknown resource "${name}"`);
1869
+ return r;
1870
+ });
1871
+ }
1872
+ function loadManifestOrThrow(projectRoot) {
1873
+ if (!manifestExists(projectRoot)) {
1874
+ throw new Error(
1875
+ `orbit.json not found at ${projectRoot}/orbit.json. Run \`launch-orbit init\` to scaffold one.`
1876
+ );
1877
+ }
1878
+ return loadManifest(projectRoot);
1879
+ }
1880
+ function withHookPayload(config, adapterId, hookPayload) {
1881
+ if (adapterId !== "builtin/claude-code" || !hookPayload) return config;
1882
+ return { ...config, existingPath: hookPayload.path };
1883
+ }
1884
+ function formatAge(iso) {
1885
+ const ms = Date.now() - new Date(iso).getTime();
1886
+ const m = Math.floor(ms / 6e4);
1887
+ if (m < 60) return `${m}m`;
1888
+ const h = Math.floor(m / 60);
1889
+ if (h < 24) return `${h}h`;
1890
+ const d = Math.floor(h / 24);
1891
+ return `${d}d`;
1892
+ }
1893
+ var import_node_child_process8, import_node_fs10, import_node_path7, DEFAULT_ENV_FILE;
1894
+ var init_orchestrator = __esm({
1895
+ "src/server/orbit/orchestrator.ts"() {
1896
+ "use strict";
1897
+ import_node_child_process8 = require("node:child_process");
1898
+ import_node_fs10 = require("node:fs");
1899
+ import_node_path7 = require("node:path");
1900
+ init_adapter_registry();
1901
+ init_env_rewriter();
1902
+ init_gate_runner();
1903
+ init_manifest();
1904
+ init_registry();
1905
+ init_slug();
1906
+ DEFAULT_ENV_FILE = ".env.local";
1907
+ }
1908
+ });
1909
+
1910
+ // src/server/orbit/logger.ts
1911
+ function createCliLogger(prefix = "orbit") {
1912
+ return {
1913
+ info: (m) => process.stdout.write(`[${prefix}] ${m}
1914
+ `),
1915
+ warn: (m) => process.stderr.write(`[${prefix}] \u26A0 ${m}
1916
+ `),
1917
+ error: (m) => process.stderr.write(`[${prefix}] \u2717 ${m}
1918
+ `),
1919
+ step: (m) => process.stdout.write(` \u2192 ${m}
1920
+ `),
1921
+ ok: (m) => process.stdout.write(` \u2713 ${m}
1922
+ `)
1923
+ };
1924
+ }
1925
+ function createMcpLogger(prefix = "orbit") {
1926
+ const w = (m) => process.stderr.write(`[${prefix}] ${m}
1927
+ `);
1928
+ return {
1929
+ info: w,
1930
+ warn: (m) => w(`\u26A0 ${m}`),
1931
+ error: (m) => w(`\u2717 ${m}`),
1932
+ step: (m) => w(`\u2192 ${m}`),
1933
+ ok: (m) => w(` \u2713 ${m}`)
1934
+ };
1935
+ }
1936
+ var init_logger = __esm({
1937
+ "src/server/orbit/logger.ts"() {
1938
+ "use strict";
1939
+ }
1940
+ });
1941
+
1942
+ // src/server/orbit-mcp.ts
1943
+ var orbit_mcp_exports = {};
1944
+ __export(orbit_mcp_exports, {
1945
+ startOrbitMcpServer: () => startOrbitMcpServer
1946
+ });
1947
+ async function handleTool(name, args) {
1948
+ const projectRoot = process.cwd();
1949
+ const logger = createMcpLogger();
1950
+ switch (name) {
1951
+ case "orbit.create": {
1952
+ const branch = String(args.branch ?? "");
1953
+ const baseRef = args.baseRef;
1954
+ const profile = args.profile;
1955
+ if (!branch) return text({ error: "branch is required" });
1956
+ const result = await create({ branch, baseRef, profile, projectRoot, logger });
1957
+ return text(result);
1958
+ }
1959
+ case "orbit.list": {
1960
+ return text(list());
1961
+ }
1962
+ case "orbit.switch": {
1963
+ const branch = String(args.branch ?? "");
1964
+ if (!branch) return text({ error: "branch is required" });
1965
+ const result = switchTo({ branch, projectRoot });
1966
+ return text({
1967
+ slug: result.entry.slug,
1968
+ path: result.path,
1969
+ envFile: result.envFile,
1970
+ nextAction: `cd ${result.path}`
1971
+ });
1972
+ }
1973
+ case "orbit.drop": {
1974
+ const branch = String(args.branch ?? "");
1975
+ if (!branch) return text({ error: "branch is required" });
1976
+ const backup = args.backup === false ? false : true;
1977
+ const result = await drop({ branch, backup, projectRoot, logger });
1978
+ return text(result);
1979
+ }
1980
+ case "orbit.doctor": {
1981
+ return text({ issues: doctor() });
1982
+ }
1983
+ case "orbit.check_mergeable": {
1984
+ const branch = String(args.branch ?? "");
1985
+ const target = String(args.target ?? "");
1986
+ if (!branch) return text({ error: "branch is required" });
1987
+ if (!target) return text({ error: "target is required" });
1988
+ const skipGates = parseSkipGates(args.skipGates);
1989
+ if (skipGates instanceof Error) return text({ error: skipGates.message });
1990
+ const result = await checkMergeable({ branch, target, skipGates, projectRoot, logger });
1991
+ return text(result);
1992
+ }
1993
+ case "orbit.merge": {
1994
+ const branch = String(args.branch ?? "");
1995
+ const target = String(args.target ?? "");
1996
+ if (!branch) return text({ error: "branch is required" });
1997
+ if (!target) return text({ error: "target is required" });
1998
+ const cleanupArg = args.cleanup;
1999
+ const cleanup = cleanupArg === "none" ? "none" : cleanupArg === "full" || cleanupArg === void 0 ? "full" : void 0;
2000
+ if (cleanup === void 0) {
2001
+ return text({ error: `cleanup must be "full" or "none" (got ${JSON.stringify(cleanupArg)})` });
2002
+ }
2003
+ const skipGates = parseSkipGates(args.skipGates);
2004
+ if (skipGates instanceof Error) return text({ error: skipGates.message });
2005
+ const result = await merge({ branch, target, cleanup, skipGates, projectRoot, logger });
2006
+ return text(result);
2007
+ }
2008
+ default:
2009
+ return text({ error: `unknown tool: ${name}` });
2010
+ }
2011
+ }
2012
+ function parseSkipGates(raw) {
2013
+ if (raw === void 0 || raw === null) return void 0;
2014
+ if (!Array.isArray(raw)) return new Error("skipGates must be an array of adapter ids");
2015
+ const out = [];
2016
+ for (const v of raw) {
2017
+ if (typeof v !== "string" || !v) return new Error("skipGates entries must be non-empty strings");
2018
+ out.push(v);
2019
+ }
2020
+ return out.length > 0 ? out : void 0;
2021
+ }
2022
+ function text(payload) {
2023
+ const t = typeof payload === "string" ? payload : JSON.stringify(payload, null, 2);
2024
+ return { content: [{ type: "text", text: t }] };
2025
+ }
2026
+ function send(msg) {
2027
+ process.stdout.write(JSON.stringify(msg) + "\n");
2028
+ }
2029
+ function sendResponse(id, result) {
2030
+ send({ jsonrpc: "2.0", id, result });
2031
+ }
2032
+ function sendError(id, code, message) {
2033
+ send({ jsonrpc: "2.0", id, error: { code, message } });
2034
+ }
2035
+ async function handleMessage(parsed) {
2036
+ const id = parsed.id;
2037
+ const method = parsed.method;
2038
+ const params = parsed.params ?? {};
2039
+ switch (method) {
2040
+ case "initialize":
2041
+ sendResponse(id ?? null, {
2042
+ protocolVersion: "2024-11-05",
2043
+ capabilities: { tools: {} },
2044
+ serverInfo: SERVER_INFO
2045
+ });
2046
+ break;
2047
+ case "notifications/initialized":
2048
+ break;
2049
+ case "tools/list":
2050
+ sendResponse(id ?? null, { tools: TOOLS });
2051
+ break;
2052
+ case "tools/call": {
2053
+ const toolName = params.name;
2054
+ const toolArgs = params.arguments ?? {};
2055
+ try {
2056
+ const result = await handleTool(toolName, toolArgs);
2057
+ sendResponse(id ?? null, result);
2058
+ } catch (err2) {
2059
+ sendError(id ?? null, -32603, `Tool error: ${err2.message}`);
2060
+ }
2061
+ break;
2062
+ }
2063
+ case "ping":
2064
+ sendResponse(id ?? null, {});
2065
+ break;
2066
+ default:
2067
+ if (id !== void 0) sendError(id ?? null, -32601, `Method not found: ${method}`);
2068
+ }
2069
+ }
2070
+ function startOrbitMcpServer() {
2071
+ process.stderr.write("[launch-orbit] MCP server starting on stdio\n");
2072
+ process.stdin.setEncoding("utf-8");
2073
+ let buffer = "";
2074
+ process.stdin.on("data", (chunk) => {
2075
+ buffer += chunk;
2076
+ const lines = buffer.split("\n");
2077
+ buffer = lines.pop() || "";
2078
+ for (const line of lines) {
2079
+ const trimmed = line.trim();
2080
+ if (!trimmed) continue;
2081
+ try {
2082
+ handleMessage(JSON.parse(trimmed)).catch((err2) => {
2083
+ process.stderr.write(`[launch-orbit] message error: ${err2}
2084
+ `);
2085
+ });
2086
+ } catch (err2) {
2087
+ process.stderr.write(`[launch-orbit] parse error: ${err2}
2088
+ `);
2089
+ }
2090
+ }
2091
+ });
2092
+ process.stdin.on("end", () => {
2093
+ process.stderr.write("[launch-orbit] stdin closed, exiting\n");
2094
+ process.exit(0);
2095
+ });
2096
+ }
2097
+ var SERVER_INFO, TOOLS;
2098
+ var init_orbit_mcp = __esm({
2099
+ "src/server/orbit-mcp.ts"() {
2100
+ "use strict";
2101
+ init_orchestrator();
2102
+ init_logger();
2103
+ SERVER_INFO = {
2104
+ name: "launch-orbit",
2105
+ version: "0.0.1"
2106
+ };
2107
+ TOOLS = [
2108
+ {
2109
+ name: "orbit.create",
2110
+ description: "Create an isolated worktree environment for a branch. Forks the resources declared in orbit.json (database, ports, etc.) so the new worktree is fully independent from the main one.\n\nPass `profile` to fork only a named subset of resources from orbit.json's `profiles` block \u2014 e.g. a frontend-only task can use a `fe` profile that skips the DB clone. Without `profile`, all manifest resources are forked.\n\nReturns: { slug, branch, path, envFile, durationMs, resources, nextAction }. The `path` is the absolute worktree directory \u2014 the caller should `cd` into it for subsequent commands. `nextAction` contains the exact shell command to do so.",
2111
+ inputSchema: {
2112
+ type: "object",
2113
+ properties: {
2114
+ branch: { type: "string", description: "Git branch name to create (also the slug source)." },
2115
+ baseRef: { type: "string", description: "Optional ref to branch from (default: current HEAD)." },
2116
+ profile: {
2117
+ type: "string",
2118
+ description: "Optional profile name from orbit.json's `profiles`. Scopes which resources are forked. Omit to fork all resources."
2119
+ }
2120
+ },
2121
+ required: ["branch"]
2122
+ }
2123
+ },
2124
+ {
2125
+ name: "orbit.list",
2126
+ description: "List every registered orbit on this machine across all projects. Returns an array of { slug, branch, path, age, resources }.",
2127
+ inputSchema: { type: "object", properties: {} }
2128
+ },
2129
+ {
2130
+ name: "orbit.switch",
2131
+ description: "Look up the path + env file of an existing orbit by branch name. Does NOT change any shell \u2014 the caller is expected to `cd` into the returned path. Useful for agents that need to operate inside an already-created orbit.",
2132
+ inputSchema: {
2133
+ type: "object",
2134
+ properties: {
2135
+ branch: { type: "string", description: "Branch name to look up." }
2136
+ },
2137
+ required: ["branch"]
2138
+ }
2139
+ },
2140
+ {
2141
+ name: "orbit.drop",
2142
+ description: "Tear down an orbit: backup the database (default true), drop the database, remove the worktree, deregister. Returns { slug, backups, freed }.",
2143
+ inputSchema: {
2144
+ type: "object",
2145
+ properties: {
2146
+ branch: { type: "string", description: "Branch name to drop." },
2147
+ backup: { type: "boolean", description: "Whether to pg_dump first (default true)." }
2148
+ },
2149
+ required: ["branch"]
2150
+ }
2151
+ },
2152
+ {
2153
+ name: "orbit.doctor",
2154
+ description: "Report inconsistencies in the registry: orphan worktrees, missing paths, stale state, etc. Returns an array of issues.",
2155
+ inputSchema: { type: "object", properties: {} }
2156
+ },
2157
+ {
2158
+ name: "orbit.check_mergeable",
2159
+ description: 'Run all configured pre-merge gates against an orbit + target branch WITHOUT actually merging. Used by agents to verify before calling orbit.merge. Default gates: builtin/mergeability (textual conflict check) and any others declared in orbit.json\'s `gates.merge`.\n\nPass `skipGates: ["builtin/build-lint"]` to bypass specific gates by adapter id. Gates marked `required: true` in orbit.json reject skipping and throw \u2014 typically used on the textual conflict check.\n\nReturns: { slug, branch, target, passed, gates: [{ gateId, ok, blockers, artifacts }], durationMs }. If `passed` is false, `gates` contains per-gate detail including human-readable blocker lines and paths to artifact logs (tsc output, lint output, etc.).',
2160
+ inputSchema: {
2161
+ type: "object",
2162
+ properties: {
2163
+ branch: { type: "string", description: "The orbit's branch (slug-able)." },
2164
+ target: { type: "string", description: "Branch we'd merge INTO." },
2165
+ skipGates: {
2166
+ type: "array",
2167
+ items: { type: "string" },
2168
+ description: 'Adapter ids to skip (e.g. ["builtin/build-lint"]). Throws if any id has `required: true` in orbit.json. Warns on unknown ids without failing.'
2169
+ }
2170
+ },
2171
+ required: ["branch", "target"]
2172
+ }
2173
+ },
2174
+ {
2175
+ name: "orbit.merge",
2176
+ description: 'Run gates, and if all pass, perform the merge into the target branch. Default cleanup behavior is `full` \u2014 after a successful merge, the orbit is dropped (DB pg_dump\'d, then DROP DATABASE; worktree removed; branch deleted). Use cleanup="none" to keep the orbit around post-merge.\n\nPass `skipGates: ["builtin/build-lint"]` to bypass specific gates by adapter id. Gates marked `required: true` in orbit.json reject skipping and throw.\n\nReturns: { slug, branch, target, merged, mergeSha, gates, cleanedUp, backupPath, durationMs, blockedBy }. If merged is false, blockedBy lists what stopped the merge (gate failures or git errors).',
2177
+ inputSchema: {
2178
+ type: "object",
2179
+ properties: {
2180
+ branch: { type: "string", description: "The orbit's branch (slug-able)." },
2181
+ target: { type: "string", description: "Branch to merge INTO. Must be checked out in some worktree." },
2182
+ cleanup: {
2183
+ type: "string",
2184
+ enum: ["full", "none"],
2185
+ description: "Post-merge action. `full` drops the orbit; `none` leaves it intact."
2186
+ },
2187
+ skipGates: {
2188
+ type: "array",
2189
+ items: { type: "string" },
2190
+ description: 'Adapter ids to skip (e.g. ["builtin/build-lint"]). Throws if any id has `required: true` in orbit.json. Warns on unknown ids without failing.'
2191
+ }
2192
+ },
2193
+ required: ["branch", "target"]
2194
+ }
2195
+ }
2196
+ ];
2197
+ }
2198
+ });
2199
+
2200
+ // src/server/orbit-entry.ts
2201
+ var import_node_fs11 = require("node:fs");
2202
+ var import_node_path8 = require("node:path");
2203
+ init_orchestrator();
2204
+ init_logger();
2205
+ function parseFlags(argv) {
2206
+ const positional = [];
2207
+ const flags = { emitCd: false, noBackup: false, skipGates: [] };
2208
+ for (let i = 0; i < argv.length; i++) {
2209
+ const a = argv[i];
2210
+ if (a === "--emit-cd") flags.emitCd = true;
2211
+ else if (a === "--no-backup") flags.noBackup = true;
2212
+ else if (a === "--base-ref") flags.baseRef = argv[++i];
2213
+ else if (a === "--profile") flags.profile = argv[++i];
2214
+ else if (a === "--skip-gate") {
2215
+ const id = argv[++i];
2216
+ if (!id) die("--skip-gate requires an adapter id (e.g. --skip-gate builtin/build-lint)");
2217
+ flags.skipGates.push(id);
2218
+ } else if (a === "--cleanup") {
2219
+ const v = argv[++i];
2220
+ if (v !== "full" && v !== "none") {
2221
+ process.stderr.write(`[launch-orbit] --cleanup must be "full" or "none" (got "${v}")
2222
+ `);
2223
+ process.exit(2);
2224
+ }
2225
+ flags.cleanup = v;
2226
+ } else if (a.startsWith("--")) {
2227
+ process.stderr.write(`[launch-orbit] unknown flag: ${a}
2228
+ `);
2229
+ process.exit(2);
2230
+ } else positional.push(a);
2231
+ }
2232
+ return { positional, flags };
2233
+ }
2234
+ async function main() {
2235
+ const argv = process.argv.slice(2);
2236
+ const subcommand = argv[0];
2237
+ if (!subcommand || subcommand === "mcp") {
2238
+ const { startOrbitMcpServer: startOrbitMcpServer2 } = await Promise.resolve().then(() => (init_orbit_mcp(), orbit_mcp_exports));
2239
+ startOrbitMcpServer2();
2240
+ return;
2241
+ }
2242
+ const { positional, flags } = parseFlags(argv.slice(1));
2243
+ const projectRoot = process.cwd();
2244
+ const logger = createCliLogger();
2245
+ try {
2246
+ switch (subcommand) {
2247
+ case "init": {
2248
+ runInit(projectRoot);
2249
+ return;
2250
+ }
2251
+ case "create": {
2252
+ const branch = positional[0];
2253
+ if (!branch) die("usage: launch-orbit create <branch> [--base-ref <ref>] [--profile <name>] [--emit-cd]");
2254
+ const result = await create({
2255
+ branch,
2256
+ baseRef: flags.baseRef,
2257
+ profile: flags.profile,
2258
+ projectRoot,
2259
+ logger
2260
+ });
2261
+ if (flags.emitCd) {
2262
+ process.stdout.write(`cd ${result.path}
2263
+ `);
2264
+ } else {
2265
+ process.stdout.write(`
2266
+ ${JSON.stringify(result, null, 2)}
2267
+ `);
2268
+ }
2269
+ return;
2270
+ }
2271
+ case "list": {
2272
+ const rows = list();
2273
+ if (rows.length === 0) {
2274
+ process.stdout.write("(no orbits registered)\n");
2275
+ return;
2276
+ }
2277
+ for (const r of rows) {
2278
+ process.stdout.write(`${r.slug} ${r.branch} ${r.age} ${r.path}
2279
+ `);
2280
+ }
2281
+ return;
2282
+ }
2283
+ case "switch": {
2284
+ const branch = positional[0];
2285
+ if (!branch) die("usage: launch-orbit switch <branch> [--emit-cd]");
2286
+ const result = switchTo({ branch, projectRoot });
2287
+ if (flags.emitCd) {
2288
+ process.stdout.write(`cd ${result.path}
2289
+ `);
2290
+ } else {
2291
+ process.stdout.write(`${JSON.stringify({ path: result.path, envFile: result.envFile, nextAction: `cd ${result.path}` }, null, 2)}
2292
+ `);
2293
+ }
2294
+ return;
2295
+ }
2296
+ case "drop": {
2297
+ const branch = positional[0];
2298
+ if (!branch) die("usage: launch-orbit drop <branch> [--no-backup]");
2299
+ const result = await drop({
2300
+ branch,
2301
+ backup: !flags.noBackup,
2302
+ projectRoot,
2303
+ logger
2304
+ });
2305
+ process.stdout.write(`
2306
+ ${JSON.stringify(result, null, 2)}
2307
+ `);
2308
+ return;
2309
+ }
2310
+ case "doctor": {
2311
+ const issues = doctor();
2312
+ if (issues.length === 0) {
2313
+ process.stdout.write("\u2713 no issues\n");
2314
+ return;
2315
+ }
2316
+ for (const i of issues) {
2317
+ process.stdout.write(` \u26A0 [${i.kind}] ${i.detail}${i.slug ? ` (${i.slug})` : ""}
2318
+ `);
2319
+ }
2320
+ process.exit(1);
2321
+ }
2322
+ case "check-merge": {
2323
+ const branch = positional[0];
2324
+ const target = positional[1];
2325
+ if (!branch || !target) {
2326
+ die("usage: launch-orbit check-merge <branch> <target> [--skip-gate <id> ...]");
2327
+ }
2328
+ const result = await checkMergeable({
2329
+ branch,
2330
+ target,
2331
+ skipGates: flags.skipGates.length > 0 ? flags.skipGates : void 0,
2332
+ projectRoot,
2333
+ logger
2334
+ });
2335
+ process.stdout.write(`
2336
+ ${JSON.stringify(result, null, 2)}
2337
+ `);
2338
+ if (!result.passed) process.exit(1);
2339
+ return;
2340
+ }
2341
+ case "merge": {
2342
+ const branch = positional[0];
2343
+ const target = positional[1];
2344
+ if (!branch || !target) {
2345
+ die("usage: launch-orbit merge <branch> <target> [--cleanup full|none] [--skip-gate <id> ...]");
2346
+ }
2347
+ const result = await merge({
2348
+ branch,
2349
+ target,
2350
+ cleanup: flags.cleanup ?? "full",
2351
+ skipGates: flags.skipGates.length > 0 ? flags.skipGates : void 0,
2352
+ projectRoot,
2353
+ logger
2354
+ });
2355
+ process.stdout.write(`
2356
+ ${JSON.stringify(result, null, 2)}
2357
+ `);
2358
+ if (!result.merged) process.exit(1);
2359
+ return;
2360
+ }
2361
+ default:
2362
+ die(`unknown subcommand: ${subcommand}`);
2363
+ }
2364
+ } catch (err2) {
2365
+ logger.error(err2.message);
2366
+ process.exit(1);
2367
+ }
2368
+ }
2369
+ function runInit(projectRoot) {
2370
+ const path = (0, import_node_path8.join)(projectRoot, "orbit.json");
2371
+ if ((0, import_node_fs11.existsSync)(path)) {
2372
+ process.stderr.write(`[launch-orbit] orbit.json already exists at ${path}
2373
+ `);
2374
+ process.exit(1);
2375
+ }
2376
+ const starter = {
2377
+ $schema: "https://orbit.dev/schema/v1.json",
2378
+ version: 1,
2379
+ worktree: {
2380
+ adapter: "builtin/git-worktree",
2381
+ config: {
2382
+ basePath: ".claude/worktrees",
2383
+ namingPattern: "{slug}"
2384
+ }
2385
+ },
2386
+ resources: [
2387
+ {
2388
+ name: "db",
2389
+ adapter: "orbit-pg",
2390
+ config: {
2391
+ source: "${DATABASE_URL}",
2392
+ strategy: "dump-restore",
2393
+ prefix: "orbit_",
2394
+ snapshot: true,
2395
+ rewrite: { env: ["DATABASE_URL", "DIRECT_URL"] }
2396
+ }
2397
+ },
2398
+ {
2399
+ name: "ports",
2400
+ adapter: "builtin/port-range",
2401
+ config: {
2402
+ base: 3e3,
2403
+ stride: 10,
2404
+ rewrite: {
2405
+ env: ["APP_URL", "NEXT_PUBLIC_APP_URL", "NEXT_PUBLIC_API_URL", "GITHUB_CALLBACK_URL"],
2406
+ replace: ":3000"
2407
+ }
2408
+ }
2409
+ }
2410
+ ],
2411
+ gates: {
2412
+ merge: [
2413
+ { adapter: "builtin/clean-tree" },
2414
+ // `required: true` blocks --skip-gate on this one — silently merging a
2415
+ // textual conflict is the obvious footgun. Drop the flag if you really
2416
+ // need to bypass.
2417
+ { adapter: "builtin/mergeability", required: true },
2418
+ { adapter: "builtin/build-lint" }
2419
+ ]
2420
+ },
2421
+ profiles: {
2422
+ fe: { resources: ["ports"] },
2423
+ full: { resources: ["db", "ports"] }
2424
+ }
2425
+ };
2426
+ (0, import_node_fs11.writeFileSync)(path, JSON.stringify(starter, null, 2) + "\n", "utf-8");
2427
+ process.stdout.write(`\u2713 wrote ${path}
2428
+ `);
2429
+ }
2430
+ function die(msg) {
2431
+ process.stderr.write(`[launch-orbit] ${msg}
2432
+ `);
2433
+ process.exit(2);
2434
+ }
2435
+ void main();