@launchsecure/launch-kit 0.0.26 → 0.0.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chart-client/assets/index-CJ4mgRRF.css +1 -0
- package/dist/chart-client/assets/{index-Bk1hawjD.js → index-Ccy-DpI-.js} +46 -42
- package/dist/chart-client/index.html +2 -2
- package/dist/client/assets/index-DI5qSR_w.css +32 -0
- package/dist/client/assets/index-Dp0_okva.js +294 -0
- package/dist/client/index.html +2 -2
- package/dist/council-client/assets/index-C_-vAM9L.css +1 -0
- package/dist/council-client/index.html +2 -2
- package/dist/deck-client/assets/{_baseUniq-C2xT_eYu.js → _baseUniq-W2JQDmje.js} +1 -1
- package/dist/deck-client/assets/{arc-CmVL9pGd.js → arc-DIBWAId9.js} +1 -1
- package/dist/deck-client/assets/{architectureDiagram-Q4EWVU46-BSFgdjve.js → architectureDiagram-Q4EWVU46-CAIRMvJK.js} +1 -1
- package/dist/deck-client/assets/{blockDiagram-DXYQGD6D-DuLzscvP.js → blockDiagram-DXYQGD6D-BeNaNiOi.js} +1 -1
- package/dist/deck-client/assets/{c4Diagram-AHTNJAMY-CfCJB8eY.js → c4Diagram-AHTNJAMY-B9Ozi62h.js} +1 -1
- package/dist/deck-client/assets/channel-CRdozqbp.js +1 -0
- package/dist/deck-client/assets/{chunk-4BX2VUAB-DxmLYTWZ.js → chunk-4BX2VUAB-D7AZ47dt.js} +1 -1
- package/dist/deck-client/assets/{chunk-4TB4RGXK-CCnf7GFE.js → chunk-4TB4RGXK-DnVnNPcI.js} +1 -1
- package/dist/deck-client/assets/{chunk-55IACEB6-Db9DApcj.js → chunk-55IACEB6-UKYs-YNd.js} +1 -1
- package/dist/deck-client/assets/{chunk-EDXVE4YY-DmYDq8ZI.js → chunk-EDXVE4YY-D43b-SKn.js} +1 -1
- package/dist/deck-client/assets/{chunk-FMBD7UC4-BGhUlF20.js → chunk-FMBD7UC4-QzBAoyyW.js} +1 -1
- package/dist/deck-client/assets/{chunk-OYMX7WX6-CpEnicQZ.js → chunk-OYMX7WX6-Cjif4r6W.js} +1 -1
- package/dist/deck-client/assets/{chunk-QZHKN3VN-Doa7LKwf.js → chunk-QZHKN3VN-CqLDirEI.js} +1 -1
- package/dist/deck-client/assets/{chunk-YZCP3GAM-CpkIlH6V.js → chunk-YZCP3GAM-_FQvmMs4.js} +1 -1
- package/dist/deck-client/assets/classDiagram-6PBFFD2Q-lIZMp57W.js +1 -0
- package/dist/deck-client/assets/classDiagram-v2-HSJHXN6E-lIZMp57W.js +1 -0
- package/dist/deck-client/assets/clone-BtWeSTyJ.js +1 -0
- package/dist/deck-client/assets/{cose-bilkent-S5V4N54A-Bkh8Bfcb.js → cose-bilkent-S5V4N54A-rfrocesE.js} +1 -1
- package/dist/deck-client/assets/{dagre-KV5264BT-Bp0XpTgH.js → dagre-KV5264BT-Bv_7DJat.js} +1 -1
- package/dist/deck-client/assets/{diagram-5BDNPKRD-ZHiyGYPQ.js → diagram-5BDNPKRD-4F1414G5.js} +1 -1
- package/dist/deck-client/assets/{diagram-G4DWMVQ6-BW-Q8_H5.js → diagram-G4DWMVQ6-C4-Pszqm.js} +1 -1
- package/dist/deck-client/assets/{diagram-MMDJMWI5-6I3LTafu.js → diagram-MMDJMWI5-B647TIx9.js} +1 -1
- package/dist/deck-client/assets/{diagram-TYMM5635-CyM5YK28.js → diagram-TYMM5635-BFAqpezd.js} +1 -1
- package/dist/deck-client/assets/{erDiagram-SMLLAGMA-CjNxVJHk.js → erDiagram-SMLLAGMA-BfBfrJOC.js} +1 -1
- package/dist/deck-client/assets/{flowDiagram-DWJPFMVM-BDQHuAJR.js → flowDiagram-DWJPFMVM-DX9YAYes.js} +1 -1
- package/dist/deck-client/assets/{ganttDiagram-T4ZO3ILL-B7MnkpbP.js → ganttDiagram-T4ZO3ILL-DCuiy7wF.js} +1 -1
- package/dist/deck-client/assets/{gitGraphDiagram-UUTBAWPF-C9dZAcYD.js → gitGraphDiagram-UUTBAWPF-CGp1IXUh.js} +1 -1
- package/dist/deck-client/assets/{graph-CjdBnzUy.js → graph-B7g8aoxv.js} +1 -1
- package/dist/deck-client/assets/{index-DeIVPW63.js → index-Dg1r-WSN.js} +3 -3
- package/dist/deck-client/assets/index-DsIZ3LqL.css +1 -0
- package/dist/deck-client/assets/{infoDiagram-42DDH7IO-C7d3iRC3.js → infoDiagram-42DDH7IO-L3fahMkF.js} +1 -1
- package/dist/deck-client/assets/{ishikawaDiagram-UXIWVN3A-BcYGKj09.js → ishikawaDiagram-UXIWVN3A-aS_EjWBZ.js} +1 -1
- package/dist/deck-client/assets/{journeyDiagram-VCZTEJTY-DqFlRrOL.js → journeyDiagram-VCZTEJTY-djTSQZF9.js} +1 -1
- package/dist/deck-client/assets/{kanban-definition-6JOO6SKY-BJhPp1NR.js → kanban-definition-6JOO6SKY-CcTHo4CM.js} +1 -1
- package/dist/deck-client/assets/{layout-DIeS6GvK.js → layout-mEJiadb7.js} +1 -1
- package/dist/deck-client/assets/{linear-He_yJy5H.js → linear-XgTKqyRu.js} +1 -1
- package/dist/deck-client/assets/{min-DQ6Kx06t.js → min-Ct9jZdpd.js} +1 -1
- package/dist/deck-client/assets/{mindmap-definition-QFDTVHPH-sQ62L8T2.js → mindmap-definition-QFDTVHPH-BaFxCGNU.js} +1 -1
- package/dist/deck-client/assets/{pieDiagram-DEJITSTG-BqCWmU2K.js → pieDiagram-DEJITSTG-CIbYYjtw.js} +1 -1
- package/dist/deck-client/assets/{quadrantDiagram-34T5L4WZ-rQ1TJOoe.js → quadrantDiagram-34T5L4WZ-D9EtCOvh.js} +1 -1
- package/dist/deck-client/assets/{requirementDiagram-MS252O5E-BO2MPBOM.js → requirementDiagram-MS252O5E-xeni9eVG.js} +1 -1
- package/dist/deck-client/assets/{sankeyDiagram-XADWPNL6-BgsHEVex.js → sankeyDiagram-XADWPNL6-LYeknz9h.js} +1 -1
- package/dist/deck-client/assets/{sequenceDiagram-FGHM5R23-B3j1yMLU.js → sequenceDiagram-FGHM5R23-RDbsKFZf.js} +1 -1
- package/dist/deck-client/assets/{stateDiagram-FHFEXIEX-C8jFlZou.js → stateDiagram-FHFEXIEX-BH1Zjglk.js} +1 -1
- package/dist/deck-client/assets/stateDiagram-v2-QKLJ7IA2-BrV78NDR.js +1 -0
- package/dist/deck-client/assets/{timeline-definition-GMOUNBTQ-tM-qo4Zk.js → timeline-definition-GMOUNBTQ-IFXxKptt.js} +1 -1
- package/dist/deck-client/assets/{vennDiagram-DHZGUBPP-B0-6kOEu.js → vennDiagram-DHZGUBPP-D-sLkQs9.js} +1 -1
- package/dist/deck-client/assets/{wardley-RL74JXVD-HpBk07P-.js → wardley-RL74JXVD-C010F8l4.js} +1 -1
- package/dist/deck-client/assets/{wardleyDiagram-NUSXRM2D-BkA1NLDE.js → wardleyDiagram-NUSXRM2D-BTjjuDU3.js} +1 -1
- package/dist/deck-client/assets/{xychartDiagram-5P7HB3ND-CEKGSuI-.js → xychartDiagram-5P7HB3ND-AYbv92n-.js} +1 -1
- package/dist/deck-client/index.html +2 -2
- package/dist/server/chart-serve.js +3836 -3750
- package/dist/server/cli.js +8746 -8224
- package/dist/server/council-entry.js +17 -5
- package/dist/server/council-serve.js +8 -3
- package/dist/server/deck-mcp-entry.js +24 -12
- package/dist/server/deck-serve.js +11 -8
- package/dist/server/fb-wizard.js +0 -0
- package/dist/server/graph-mcp-entry.js +5005 -4865
- package/dist/server/init-entry.js +609 -0
- package/dist/server/orbit-entry.js +2272 -0
- package/dist/server/parse-worker-entry.js +4721 -0
- package/dist/server/recall-entry.js +356 -18
- package/package.json +29 -23
- package/scaffolds/migrate-safety/.github/workflows/backup-on-migration.yml +72 -0
- package/scaffolds/migrate-safety/docs/migrations-runbook.md +172 -0
- package/scaffolds/migrate-safety/scripts/migrate-with-backup.sh +294 -0
- package/dist/chart-client/assets/index-DpaGa3bY.css +0 -1
- package/dist/client/assets/index-Bfel4OQ5.css +0 -32
- package/dist/client/assets/index-eC-WuUWB.js +0 -291
- package/dist/council-client/assets/index-P5kMsT5a.css +0 -1
- package/dist/deck-client/assets/channel-B4aNO8ZB.js +0 -1
- package/dist/deck-client/assets/classDiagram-6PBFFD2Q-BHTI0yWz.js +0 -1
- package/dist/deck-client/assets/classDiagram-v2-HSJHXN6E-BHTI0yWz.js +0 -1
- package/dist/deck-client/assets/clone-HduFm7qU.js +0 -1
- package/dist/deck-client/assets/index-LKZDAS9S.css +0 -1
- package/dist/deck-client/assets/stateDiagram-v2-QKLJ7IA2-BoqepHW0.js +0 -1
- /package/dist/council-client/assets/{index-Cs_MVXHf.js → index-Dt4zWKSj.js} +0 -0
|
@@ -122,8 +122,8 @@ function loadConfig(workTree) {
|
|
|
122
122
|
return { config: { ...DEFAULT_CONFIG }, source: "defaults", path: configPath };
|
|
123
123
|
}
|
|
124
124
|
try {
|
|
125
|
-
const
|
|
126
|
-
const raw = JSON.parse(
|
|
125
|
+
const text2 = fs.readFileSync(configPath, "utf8");
|
|
126
|
+
const raw = JSON.parse(text2);
|
|
127
127
|
return { config: mergeConfig(raw), source: "file", path: configPath };
|
|
128
128
|
} catch (err) {
|
|
129
129
|
process.stderr.write(
|
|
@@ -259,14 +259,346 @@ var init_init = __esm({
|
|
|
259
259
|
}
|
|
260
260
|
});
|
|
261
261
|
|
|
262
|
+
// src/server/recall-mcp.ts
|
|
263
|
+
var recall_mcp_exports = {};
|
|
264
|
+
__export(recall_mcp_exports, {
|
|
265
|
+
startRecallMcpServer: () => startRecallMcpServer
|
|
266
|
+
});
|
|
267
|
+
function isPidAlive(pid) {
|
|
268
|
+
try {
|
|
269
|
+
process.kill(pid, 0);
|
|
270
|
+
return true;
|
|
271
|
+
} catch (err) {
|
|
272
|
+
const code = err.code;
|
|
273
|
+
if (code === "ESRCH") return false;
|
|
274
|
+
if (code === "EPERM") return true;
|
|
275
|
+
return false;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
function pidFilePath(recallDir) {
|
|
279
|
+
return `${recallDir}/watch.pid`;
|
|
280
|
+
}
|
|
281
|
+
function existingWatcherPid() {
|
|
282
|
+
const { recallDir } = resolveRecallPaths();
|
|
283
|
+
const pf = pidFilePath(recallDir);
|
|
284
|
+
if (!(0, import_node_fs.existsSync)(pf)) return null;
|
|
285
|
+
const pid = Number((0, import_node_fs.readFileSync)(pf, "utf-8").trim());
|
|
286
|
+
if (!Number.isFinite(pid) || pid <= 0) return null;
|
|
287
|
+
return isPidAlive(pid) ? pid : null;
|
|
288
|
+
}
|
|
289
|
+
function ensureWatcher() {
|
|
290
|
+
const { gitDir, workTree } = resolveRecallPaths();
|
|
291
|
+
if (!(0, import_node_fs.existsSync)(gitDir)) {
|
|
292
|
+
process.stderr.write(
|
|
293
|
+
`[launch-recall mcp] shadow repo missing at ${gitDir} \u2014 run \`launch-recall init\` first. MCP tools will still serve read-only queries, but nothing will be captured.
|
|
294
|
+
`
|
|
295
|
+
);
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
const existing = existingWatcherPid();
|
|
299
|
+
if (existing !== null) {
|
|
300
|
+
process.stderr.write(
|
|
301
|
+
`[launch-recall mcp] external watcher detected (pid ${existing}) \u2014 serving tools only, not spawning a sibling
|
|
302
|
+
`
|
|
303
|
+
);
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
const entry = process.argv[1];
|
|
307
|
+
if (!entry) {
|
|
308
|
+
process.stderr.write(`[launch-recall mcp] cannot resolve entry path from argv \u2014 watcher not spawned
|
|
309
|
+
`);
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
childWatcher = (0, import_node_child_process2.spawn)(process.execPath, [entry, "watch"], {
|
|
313
|
+
cwd: workTree,
|
|
314
|
+
stdio: ["ignore", "ignore", "inherit"],
|
|
315
|
+
detached: false
|
|
316
|
+
});
|
|
317
|
+
process.stderr.write(`[launch-recall mcp] spawned watcher (pid ${childWatcher.pid}) in ${workTree}
|
|
318
|
+
`);
|
|
319
|
+
childWatcher.on("exit", (code, sig) => {
|
|
320
|
+
process.stderr.write(`[launch-recall mcp] child watcher exited (code=${code} sig=${sig})
|
|
321
|
+
`);
|
|
322
|
+
childWatcher = null;
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
function killChildWatcher() {
|
|
326
|
+
if (childWatcher && !childWatcher.killed) {
|
|
327
|
+
try {
|
|
328
|
+
childWatcher.kill("SIGTERM");
|
|
329
|
+
} catch {
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
function text(payload) {
|
|
334
|
+
const t = typeof payload === "string" ? payload : JSON.stringify(payload, null, 2);
|
|
335
|
+
return { content: [{ type: "text", text: t }] };
|
|
336
|
+
}
|
|
337
|
+
async function handleTool(name, args) {
|
|
338
|
+
switch (name) {
|
|
339
|
+
case "recall.doctor":
|
|
340
|
+
return text(doctorTool());
|
|
341
|
+
case "recall.report":
|
|
342
|
+
return text(reportTool());
|
|
343
|
+
case "recall.status":
|
|
344
|
+
return text(statusTool());
|
|
345
|
+
case "recall.history": {
|
|
346
|
+
const path7 = String(args.path ?? "");
|
|
347
|
+
if (!path7) return text({ error: "path is required" });
|
|
348
|
+
const limit = typeof args.limit === "number" && args.limit > 0 ? Math.floor(args.limit) : 50;
|
|
349
|
+
return text(historyTool(path7, limit));
|
|
350
|
+
}
|
|
351
|
+
default:
|
|
352
|
+
return text({ error: `unknown tool: ${name}` });
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
function doctorTool() {
|
|
356
|
+
const { gitDir, recallDir } = resolveRecallPaths();
|
|
357
|
+
const checks = [];
|
|
358
|
+
const shadowOk = (0, import_node_fs.existsSync)(gitDir);
|
|
359
|
+
checks.push({
|
|
360
|
+
name: "shadow_repo",
|
|
361
|
+
ok: shadowOk,
|
|
362
|
+
detail: shadowOk ? gitDir : `missing \u2014 run \`launch-recall init\``
|
|
363
|
+
});
|
|
364
|
+
const pid = existingWatcherPid();
|
|
365
|
+
checks.push({
|
|
366
|
+
name: "watcher_alive",
|
|
367
|
+
ok: pid !== null,
|
|
368
|
+
detail: pid !== null ? `pid ${pid}` : `no live pid in ${pidFilePath(recallDir)}`
|
|
369
|
+
});
|
|
370
|
+
let recentOk = false;
|
|
371
|
+
let recentDetail = "no snapshots yet";
|
|
372
|
+
if (shadowOk) {
|
|
373
|
+
try {
|
|
374
|
+
const out = (0, import_node_child_process2.execFileSync)("git", [`--git-dir=${gitDir}`, "log", "-1", "--format=%aI"], {
|
|
375
|
+
encoding: "utf-8",
|
|
376
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
377
|
+
}).trim();
|
|
378
|
+
if (out) {
|
|
379
|
+
const ageHours = (Date.now() - new Date(out).getTime()) / 36e5;
|
|
380
|
+
recentOk = ageHours < 24;
|
|
381
|
+
recentDetail = `${out} (${ageHours.toFixed(1)}h ago)`;
|
|
382
|
+
}
|
|
383
|
+
} catch {
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
checks.push({ name: "recent_snapshot", ok: recentOk, detail: recentDetail });
|
|
387
|
+
return { ok: checks.every((c) => c.ok), checks };
|
|
388
|
+
}
|
|
389
|
+
function reportTool() {
|
|
390
|
+
const { gitDir, recallDir, workTree } = resolveRecallPaths();
|
|
391
|
+
if (!(0, import_node_fs.existsSync)(gitDir)) return { error: "shadow repo not initialised" };
|
|
392
|
+
let totalSnapshots = 0;
|
|
393
|
+
try {
|
|
394
|
+
const out = (0, import_node_child_process2.execFileSync)("git", [`--git-dir=${gitDir}`, "rev-list", "--count", "HEAD"], {
|
|
395
|
+
encoding: "utf-8",
|
|
396
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
397
|
+
}).trim();
|
|
398
|
+
totalSnapshots = Number(out) || 0;
|
|
399
|
+
} catch {
|
|
400
|
+
}
|
|
401
|
+
let shadowSizeBytes = 0;
|
|
402
|
+
try {
|
|
403
|
+
const out = (0, import_node_child_process2.execFileSync)("du", ["-sk", recallDir], { encoding: "utf-8" }).trim();
|
|
404
|
+
const kb = Number(out.split(/\s+/)[0]);
|
|
405
|
+
if (Number.isFinite(kb)) shadowSizeBytes = kb * 1024;
|
|
406
|
+
} catch {
|
|
407
|
+
}
|
|
408
|
+
const recentSnapshots = [];
|
|
409
|
+
let lastSnapshot = null;
|
|
410
|
+
try {
|
|
411
|
+
const out = (0, import_node_child_process2.execFileSync)(
|
|
412
|
+
"git",
|
|
413
|
+
[`--git-dir=${gitDir}`, "log", "-10", "--format=%H|%aI|%s"],
|
|
414
|
+
{ encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] }
|
|
415
|
+
);
|
|
416
|
+
for (const line of out.split("\n").filter(Boolean)) {
|
|
417
|
+
const [sha, ts, ...rest] = line.split("|");
|
|
418
|
+
recentSnapshots.push({ sha, ts, message: rest.join("|") });
|
|
419
|
+
}
|
|
420
|
+
if (recentSnapshots.length > 0) lastSnapshot = recentSnapshots[0].ts;
|
|
421
|
+
} catch {
|
|
422
|
+
}
|
|
423
|
+
let config = null;
|
|
424
|
+
const cfgPath = `${workTree}/.launch-recall.json`;
|
|
425
|
+
if ((0, import_node_fs.existsSync)(cfgPath)) {
|
|
426
|
+
try {
|
|
427
|
+
config = JSON.parse((0, import_node_fs.readFileSync)(cfgPath, "utf-8"));
|
|
428
|
+
} catch {
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
return { totalSnapshots, shadowSizeBytes, lastSnapshot, recentSnapshots, config, workTree };
|
|
432
|
+
}
|
|
433
|
+
function historyTool(path7, limit) {
|
|
434
|
+
const { gitDir, workTree } = resolveRecallPaths();
|
|
435
|
+
if (!(0, import_node_fs.existsSync)(gitDir)) return { error: "shadow repo not initialised", path: path7, snapshots: [] };
|
|
436
|
+
const rel = path7.startsWith("/") ? path7.replace(`${workTree}/`, "").replace(/^\/+/, "") : path7;
|
|
437
|
+
const snapshots = [];
|
|
438
|
+
try {
|
|
439
|
+
const out = (0, import_node_child_process2.execFileSync)(
|
|
440
|
+
"git",
|
|
441
|
+
[`--git-dir=${gitDir}`, `--work-tree=${workTree}`, "log", `-${limit}`, "--format=%H|%aI|%s", "--", rel],
|
|
442
|
+
{ encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] }
|
|
443
|
+
);
|
|
444
|
+
for (const line of out.split("\n").filter(Boolean)) {
|
|
445
|
+
const [sha, ts, ...rest] = line.split("|");
|
|
446
|
+
snapshots.push({ sha, ts, message: rest.join("|") });
|
|
447
|
+
}
|
|
448
|
+
} catch {
|
|
449
|
+
}
|
|
450
|
+
return { path: rel, snapshots };
|
|
451
|
+
}
|
|
452
|
+
function statusTool() {
|
|
453
|
+
const { gitDir, workTree } = resolveRecallPaths();
|
|
454
|
+
const pid = existingWatcherPid();
|
|
455
|
+
let lastSnapshotAt = null;
|
|
456
|
+
if ((0, import_node_fs.existsSync)(gitDir)) {
|
|
457
|
+
try {
|
|
458
|
+
lastSnapshotAt = (0, import_node_child_process2.execFileSync)("git", [`--git-dir=${gitDir}`, "log", "-1", "--format=%aI"], {
|
|
459
|
+
encoding: "utf-8",
|
|
460
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
461
|
+
}).trim() || null;
|
|
462
|
+
} catch {
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
return {
|
|
466
|
+
running: pid !== null,
|
|
467
|
+
pid,
|
|
468
|
+
lastSnapshotAt,
|
|
469
|
+
watchTree: workTree,
|
|
470
|
+
shadowRepo: gitDir
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
function send(msg) {
|
|
474
|
+
process.stdout.write(JSON.stringify(msg) + "\n");
|
|
475
|
+
}
|
|
476
|
+
function sendResponse(id, result) {
|
|
477
|
+
send({ jsonrpc: "2.0", id, result });
|
|
478
|
+
}
|
|
479
|
+
function sendError(id, code, message) {
|
|
480
|
+
send({ jsonrpc: "2.0", id, error: { code, message } });
|
|
481
|
+
}
|
|
482
|
+
async function handleMessage(parsed) {
|
|
483
|
+
const id = parsed.id;
|
|
484
|
+
const method = parsed.method;
|
|
485
|
+
const params = parsed.params ?? {};
|
|
486
|
+
switch (method) {
|
|
487
|
+
case "initialize":
|
|
488
|
+
sendResponse(id ?? null, {
|
|
489
|
+
protocolVersion: "2024-11-05",
|
|
490
|
+
capabilities: { tools: {} },
|
|
491
|
+
serverInfo: SERVER_INFO
|
|
492
|
+
});
|
|
493
|
+
break;
|
|
494
|
+
case "notifications/initialized":
|
|
495
|
+
break;
|
|
496
|
+
case "tools/list":
|
|
497
|
+
sendResponse(id ?? null, { tools: TOOLS });
|
|
498
|
+
break;
|
|
499
|
+
case "tools/call": {
|
|
500
|
+
const toolName = params.name;
|
|
501
|
+
const toolArgs = params.arguments ?? {};
|
|
502
|
+
try {
|
|
503
|
+
const result = await handleTool(toolName, toolArgs);
|
|
504
|
+
sendResponse(id ?? null, result);
|
|
505
|
+
} catch (err) {
|
|
506
|
+
sendError(id ?? null, -32603, `Tool error: ${err.message}`);
|
|
507
|
+
}
|
|
508
|
+
break;
|
|
509
|
+
}
|
|
510
|
+
case "ping":
|
|
511
|
+
sendResponse(id ?? null, {});
|
|
512
|
+
break;
|
|
513
|
+
default:
|
|
514
|
+
if (id !== void 0) sendError(id ?? null, -32601, `Method not found: ${method}`);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
function startRecallMcpServer() {
|
|
518
|
+
process.stderr.write("[launch-recall mcp] starting on stdio\n");
|
|
519
|
+
ensureWatcher();
|
|
520
|
+
const teardown = () => {
|
|
521
|
+
killChildWatcher();
|
|
522
|
+
process.exit(0);
|
|
523
|
+
};
|
|
524
|
+
process.on("SIGTERM", teardown);
|
|
525
|
+
process.on("SIGINT", teardown);
|
|
526
|
+
process.on("exit", killChildWatcher);
|
|
527
|
+
process.stdin.setEncoding("utf-8");
|
|
528
|
+
let buffer = "";
|
|
529
|
+
process.stdin.on("data", (chunk) => {
|
|
530
|
+
buffer += chunk;
|
|
531
|
+
const lines = buffer.split("\n");
|
|
532
|
+
buffer = lines.pop() || "";
|
|
533
|
+
for (const line of lines) {
|
|
534
|
+
const trimmed = line.trim();
|
|
535
|
+
if (!trimmed) continue;
|
|
536
|
+
try {
|
|
537
|
+
handleMessage(JSON.parse(trimmed)).catch((err) => {
|
|
538
|
+
process.stderr.write(`[launch-recall mcp] message error: ${err}
|
|
539
|
+
`);
|
|
540
|
+
});
|
|
541
|
+
} catch (err) {
|
|
542
|
+
process.stderr.write(`[launch-recall mcp] parse error: ${err}
|
|
543
|
+
`);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
});
|
|
547
|
+
process.stdin.on("end", () => {
|
|
548
|
+
process.stderr.write("[launch-recall mcp] stdin closed, exiting\n");
|
|
549
|
+
teardown();
|
|
550
|
+
});
|
|
551
|
+
void import_node_fs.statSync;
|
|
552
|
+
}
|
|
553
|
+
var import_node_child_process2, import_node_fs, SERVER_INFO, TOOLS, childWatcher;
|
|
554
|
+
var init_recall_mcp = __esm({
|
|
555
|
+
"src/server/recall-mcp.ts"() {
|
|
556
|
+
"use strict";
|
|
557
|
+
import_node_child_process2 = require("node:child_process");
|
|
558
|
+
import_node_fs = require("node:fs");
|
|
559
|
+
init_paths();
|
|
560
|
+
SERVER_INFO = { name: "launch-recall", version: "0.0.1" };
|
|
561
|
+
TOOLS = [
|
|
562
|
+
{
|
|
563
|
+
name: "recall.doctor",
|
|
564
|
+
description: "Health check on the launch-recall watcher and its shadow repo.\n\nReturns: { ok, checks: [{ name, ok, detail }] }. Names: shadow_repo (exists + valid), watcher_alive (pid file points at live pid), recent_snapshot (any snapshot within 24h).",
|
|
565
|
+
inputSchema: { type: "object", properties: {} }
|
|
566
|
+
},
|
|
567
|
+
{
|
|
568
|
+
name: "recall.report",
|
|
569
|
+
description: "Project-wide details on what recall has captured.\n\nReturns: { totalSnapshots, shadowSizeBytes, lastSnapshot, recentSnapshots: [{ sha, ts, message }], config: { debounce, ignore, retention } }. recentSnapshots is the last 10.",
|
|
570
|
+
inputSchema: { type: "object", properties: {} }
|
|
571
|
+
},
|
|
572
|
+
{
|
|
573
|
+
name: "recall.history",
|
|
574
|
+
description: "Snapshot history for a specific file or directory. Path is relative to the project root (or an absolute path under it).\n\nReturns: { path, snapshots: [{ sha, ts, message }] }. Empty array if the path was never captured (e.g. ignored, or simply has no churn).",
|
|
575
|
+
inputSchema: {
|
|
576
|
+
type: "object",
|
|
577
|
+
properties: {
|
|
578
|
+
path: { type: "string", description: "File or directory to query." },
|
|
579
|
+
limit: { type: "number", description: "Max snapshots to return (default 50)." }
|
|
580
|
+
},
|
|
581
|
+
required: ["path"]
|
|
582
|
+
}
|
|
583
|
+
},
|
|
584
|
+
{
|
|
585
|
+
name: "recall.status",
|
|
586
|
+
description: "Quick liveness + last-snapshot info. Cheaper than recall.doctor \u2014 no historical inspection.\n\nReturns: { running, pid, lastSnapshotAt, watchTree }.",
|
|
587
|
+
inputSchema: { type: "object", properties: {} }
|
|
588
|
+
}
|
|
589
|
+
];
|
|
590
|
+
childWatcher = null;
|
|
591
|
+
}
|
|
592
|
+
});
|
|
593
|
+
|
|
262
594
|
// src/server/recall/stop.ts
|
|
263
595
|
var stop_exports = {};
|
|
264
596
|
__export(stop_exports, {
|
|
265
|
-
pidFilePath: () =>
|
|
597
|
+
pidFilePath: () => pidFilePath2,
|
|
266
598
|
runStop: () => runStop,
|
|
267
599
|
stopWatcher: () => stopWatcher
|
|
268
600
|
});
|
|
269
|
-
function
|
|
601
|
+
function pidFilePath2(recallDir) {
|
|
270
602
|
return path4.join(recallDir, "watch.pid");
|
|
271
603
|
}
|
|
272
604
|
function isAlive(pid) {
|
|
@@ -285,7 +617,7 @@ function sleep(ms) {
|
|
|
285
617
|
}
|
|
286
618
|
async function stopWatcher(quiet = false) {
|
|
287
619
|
const { recallDir } = resolveRecallPaths();
|
|
288
|
-
const pf =
|
|
620
|
+
const pf = pidFilePath2(recallDir);
|
|
289
621
|
if (!fs3.existsSync(pf)) {
|
|
290
622
|
if (!quiet) process.stderr.write(`[launch-recall] no PID file at ${pf}
|
|
291
623
|
`);
|
|
@@ -353,7 +685,7 @@ var watch_exports = {};
|
|
|
353
685
|
__export(watch_exports, {
|
|
354
686
|
runWatch: () => runWatch
|
|
355
687
|
});
|
|
356
|
-
function
|
|
688
|
+
function isPidAlive2(pid) {
|
|
357
689
|
try {
|
|
358
690
|
process.kill(pid, 0);
|
|
359
691
|
return true;
|
|
@@ -385,10 +717,10 @@ async function runWatch(args) {
|
|
|
385
717
|
log(`not initialised \u2014 run: launch-recall init`);
|
|
386
718
|
process.exit(1);
|
|
387
719
|
}
|
|
388
|
-
const pf =
|
|
720
|
+
const pf = pidFilePath2(recallDir);
|
|
389
721
|
if (fs4.existsSync(pf)) {
|
|
390
722
|
const existing = Number(fs4.readFileSync(pf, "utf8").trim());
|
|
391
|
-
if (Number.isFinite(existing) &&
|
|
723
|
+
if (Number.isFinite(existing) && isPidAlive2(existing)) {
|
|
392
724
|
log(`watcher already running (pid ${existing}) \u2014 run \`launch-recall stop\` first`);
|
|
393
725
|
process.exit(1);
|
|
394
726
|
}
|
|
@@ -426,7 +758,7 @@ async function runWatch(args) {
|
|
|
426
758
|
return;
|
|
427
759
|
}
|
|
428
760
|
busy = true;
|
|
429
|
-
const add = (0,
|
|
761
|
+
const add = (0, import_node_child_process3.spawn)("git", addArgs, { env, stdio: "ignore" });
|
|
430
762
|
add.on("exit", (addCode) => {
|
|
431
763
|
if (addCode !== 0) {
|
|
432
764
|
busy = false;
|
|
@@ -436,7 +768,7 @@ async function runWatch(args) {
|
|
|
436
768
|
}
|
|
437
769
|
return;
|
|
438
770
|
}
|
|
439
|
-
const check = (0,
|
|
771
|
+
const check = (0, import_node_child_process3.spawn)("git", ["diff", "--cached", "--quiet"], { env, stdio: "ignore" });
|
|
440
772
|
check.on("exit", (checkCode) => {
|
|
441
773
|
if (checkCode === 0) {
|
|
442
774
|
busy = false;
|
|
@@ -446,7 +778,7 @@ async function runWatch(args) {
|
|
|
446
778
|
}
|
|
447
779
|
return;
|
|
448
780
|
}
|
|
449
|
-
const commit = (0,
|
|
781
|
+
const commit = (0, import_node_child_process3.spawn)(
|
|
450
782
|
"git",
|
|
451
783
|
["commit", "-q", "-m", `snap ${stamp()}`],
|
|
452
784
|
{ env, stdio: "ignore" }
|
|
@@ -492,13 +824,13 @@ async function runWatch(args) {
|
|
|
492
824
|
process.exit(1);
|
|
493
825
|
}
|
|
494
826
|
}
|
|
495
|
-
var fs4, fsp,
|
|
827
|
+
var fs4, fsp, import_node_child_process3;
|
|
496
828
|
var init_watch = __esm({
|
|
497
829
|
"src/server/recall/watch.ts"() {
|
|
498
830
|
"use strict";
|
|
499
831
|
fs4 = __toESM(require("node:fs"));
|
|
500
832
|
fsp = __toESM(require("node:fs/promises"));
|
|
501
|
-
|
|
833
|
+
import_node_child_process3 = require("node:child_process");
|
|
502
834
|
init_git();
|
|
503
835
|
init_paths();
|
|
504
836
|
init_config();
|
|
@@ -930,8 +1262,8 @@ function promptYesNo(question) {
|
|
|
930
1262
|
}
|
|
931
1263
|
function removeGitignoreLine(gitignorePath) {
|
|
932
1264
|
if (!fs10.existsSync(gitignorePath)) return "no-file";
|
|
933
|
-
const
|
|
934
|
-
const lines =
|
|
1265
|
+
const text2 = fs10.readFileSync(gitignorePath, "utf8");
|
|
1266
|
+
const lines = text2.split(/\r?\n/);
|
|
935
1267
|
const matches = (l) => {
|
|
936
1268
|
const t = l.trim();
|
|
937
1269
|
return t === `/${RECALL_DIR_NAME}/` || t === `${RECALL_DIR_NAME}/` || t === RECALL_DIR_NAME || t === `/${RECALL_DIR_NAME}` || t === `${RECALL_DIR_NAME}/**`;
|
|
@@ -946,15 +1278,15 @@ async function runUninstall(args) {
|
|
|
946
1278
|
const { workTree, recallDir } = resolveRecallPaths();
|
|
947
1279
|
const configPath = path6.join(workTree, CONFIG_FILENAME);
|
|
948
1280
|
const gitignorePath = path6.join(workTree, ".gitignore");
|
|
949
|
-
const pf =
|
|
1281
|
+
const pf = pidFilePath2(recallDir);
|
|
950
1282
|
const watcherRunning = fs10.existsSync(pf);
|
|
951
1283
|
const shadowPresent = fs10.existsSync(recallDir);
|
|
952
1284
|
const configPresent = fs10.existsSync(configPath);
|
|
953
1285
|
const shadowSize = shadowPresent ? dirSizeBytes(recallDir) : 0;
|
|
954
1286
|
let gitignoreHasLine = false;
|
|
955
1287
|
if (fs10.existsSync(gitignorePath)) {
|
|
956
|
-
const
|
|
957
|
-
gitignoreHasLine =
|
|
1288
|
+
const text2 = fs10.readFileSync(gitignorePath, "utf8");
|
|
1289
|
+
gitignoreHasLine = text2.split(/\r?\n/).some((l) => {
|
|
958
1290
|
const t = l.trim();
|
|
959
1291
|
return t === `/${RECALL_DIR_NAME}/` || t === `${RECALL_DIR_NAME}/` || t === RECALL_DIR_NAME || t === `/${RECALL_DIR_NAME}` || t === `${RECALL_DIR_NAME}/**`;
|
|
960
1292
|
});
|
|
@@ -1034,6 +1366,7 @@ function printUsage() {
|
|
|
1034
1366
|
" init create .recall/repo.git + gitignore entry + default config",
|
|
1035
1367
|
" watch [--debounce <ms>] foreground watcher (defaults from .launch-recall.json)",
|
|
1036
1368
|
" stop terminate the running watcher",
|
|
1369
|
+
" mcp stdio MCP server (spawns watcher as child)",
|
|
1037
1370
|
" log [path] show shadow history (passthrough to git log)",
|
|
1038
1371
|
" restore <path> [--at <ref>] extract a file from a snapshot",
|
|
1039
1372
|
" forget [--keep-last N] [--max-age 30d] [--dry-run]",
|
|
@@ -1059,6 +1392,11 @@ async function main() {
|
|
|
1059
1392
|
await runInit2(argv.slice(1));
|
|
1060
1393
|
return;
|
|
1061
1394
|
}
|
|
1395
|
+
case "mcp": {
|
|
1396
|
+
const { startRecallMcpServer: startRecallMcpServer2 } = await Promise.resolve().then(() => (init_recall_mcp(), recall_mcp_exports));
|
|
1397
|
+
startRecallMcpServer2();
|
|
1398
|
+
return;
|
|
1399
|
+
}
|
|
1062
1400
|
case "watch": {
|
|
1063
1401
|
const { runWatch: runWatch2 } = await Promise.resolve().then(() => (init_watch(), watch_exports));
|
|
1064
1402
|
await runWatch2(argv.slice(1));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@launchsecure/launch-kit",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.27",
|
|
4
4
|
"description": "LaunchSecure toolkit — launch-pod (pipeline), launch-chart (project graph MCP), launch-deck (visual playground MCP), launch-kit-beacon (feedback Web Component), launch-recall (file-watcher backup).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "LaunchSecure - AutomateWithUs",
|
|
@@ -47,20 +47,41 @@
|
|
|
47
47
|
"access": "public"
|
|
48
48
|
},
|
|
49
49
|
"bin": {
|
|
50
|
+
"launch-kit": "./dist/server/init-entry.js",
|
|
50
51
|
"launch-pod": "./dist/server/cli.js",
|
|
51
52
|
"launch-chart": "./dist/server/graph-mcp-entry.js",
|
|
52
53
|
"launch-deck": "./dist/server/deck-mcp-entry.js",
|
|
53
|
-
"launch-recall": "./dist/server/recall-entry.js"
|
|
54
|
+
"launch-recall": "./dist/server/recall-entry.js",
|
|
55
|
+
"launch-orbit": "./dist/server/orbit-entry.js"
|
|
56
|
+
},
|
|
57
|
+
"scripts": {
|
|
58
|
+
"build": "pnpm build:client && pnpm build:chart-client && pnpm build:deck-client && pnpm build:council-client && pnpm build:beacon && pnpm build:server",
|
|
59
|
+
"build:beacon": "vite build --config vite.beacon.config.ts && tsc -p tsconfig.beacon.json --emitDeclarationOnly --outDir dist/beacon/types",
|
|
60
|
+
"test:beacon": "vitest run --config vite.beacon.config.ts",
|
|
61
|
+
"test:radar": "vitest run --config vite.radar.config.ts",
|
|
62
|
+
"test:chart": "vitest run --config vite.chart.test.config.ts",
|
|
63
|
+
"build:deck-client": "vite build --config vite.deck.config.ts",
|
|
64
|
+
"build:council-client": "vite build --config vite.council.config.ts",
|
|
65
|
+
"build:client": "vite build",
|
|
66
|
+
"build:chart-client": "vite build --config vite.chart.config.ts",
|
|
67
|
+
"build:server": "esbuild src/server/cli.ts src/server/fb-wizard.ts src/server/graph-mcp-entry.ts src/server/chart-serve.ts src/server/deck-mcp-entry.ts src/server/deck-serve.ts src/server/council-entry.ts src/server/council-serve.ts src/server/recall-entry.ts src/server/init-entry.ts src/server/orbit-entry.ts src/server/parse-worker-entry.ts --bundle --platform=node --target=node18 --outdir=dist/server --external:node-pty --external:ws --external:typescript --external:web-tree-sitter --external:tree-sitter-typescript --external:cloudflared --external:pg --external:pg-native && rm -rf dist/server/public && cp -r ../claude-code-web/src/public dist/server/public && rm -rf dist/server/graph/queries && mkdir -p dist/server/graph && cp -r src/server/graph/queries dist/server/graph/queries",
|
|
68
|
+
"dev:client": "vite",
|
|
69
|
+
"dev:chart": "pnpm build:server && pnpm build:chart-client && node dist/server/graph-mcp-entry.js serve",
|
|
70
|
+
"dev:server": "pnpm build:server && node dist/server/cli.js",
|
|
71
|
+
"dev": "pnpm build:server && concurrently -k -n client,server -c cyan,magenta \"vite\" \"node dist/server/cli.js\"",
|
|
72
|
+
"prepublishOnly": "pnpm build"
|
|
54
73
|
},
|
|
55
74
|
"files": [
|
|
56
75
|
"dist",
|
|
57
|
-
"prompts"
|
|
76
|
+
"prompts",
|
|
77
|
+
"scaffolds"
|
|
58
78
|
],
|
|
59
79
|
"dependencies": {
|
|
60
80
|
"cacheable-lookup": "^7.0.0",
|
|
61
81
|
"cloudflared": "^0.7.1",
|
|
62
82
|
"html-to-image": "^1.11.13",
|
|
63
83
|
"node-pty": "^1.2.0-beta.12",
|
|
84
|
+
"pg": "^8.13.0",
|
|
64
85
|
"tree-sitter": "^0.21.1",
|
|
65
86
|
"tree-sitter-typescript": "^0.23.2",
|
|
66
87
|
"typescript": "^5.5.0",
|
|
@@ -69,7 +90,10 @@
|
|
|
69
90
|
"ws": "^8.18.0"
|
|
70
91
|
},
|
|
71
92
|
"devDependencies": {
|
|
93
|
+
"@launchsecure/claude-code-web": "workspace:*",
|
|
94
|
+
"@launchsecure/ui": "workspace:*",
|
|
72
95
|
"@types/node": "^20.0.0",
|
|
96
|
+
"@types/pg": "^8.11.10",
|
|
73
97
|
"@types/react": "^18.3.12",
|
|
74
98
|
"@types/react-dom": "^18.3.1",
|
|
75
99
|
"@types/ws": "^8.5.10",
|
|
@@ -91,24 +115,6 @@
|
|
|
91
115
|
"react-router-dom": "^6.28.0",
|
|
92
116
|
"tailwindcss": "^3.4.19",
|
|
93
117
|
"vite": "^5.4.11",
|
|
94
|
-
"vitest": "^1.6.0"
|
|
95
|
-
"@launchsecure/claude-code-web": "0.0.1",
|
|
96
|
-
"@launchsecure/ui": "0.0.1"
|
|
97
|
-
},
|
|
98
|
-
"scripts": {
|
|
99
|
-
"build": "pnpm build:client && pnpm build:chart-client && pnpm build:deck-client && pnpm build:council-client && pnpm build:beacon && pnpm build:server",
|
|
100
|
-
"build:beacon": "vite build --config vite.beacon.config.ts && tsc -p tsconfig.beacon.json --emitDeclarationOnly --outDir dist/beacon/types",
|
|
101
|
-
"test:beacon": "vitest run --config vite.beacon.config.ts",
|
|
102
|
-
"test:radar": "vitest run --config vite.radar.config.ts",
|
|
103
|
-
"test:chart": "vitest run --config vite.chart.test.config.ts",
|
|
104
|
-
"build:deck-client": "vite build --config vite.deck.config.ts",
|
|
105
|
-
"build:council-client": "vite build --config vite.council.config.ts",
|
|
106
|
-
"build:client": "vite build",
|
|
107
|
-
"build:chart-client": "vite build --config vite.chart.config.ts",
|
|
108
|
-
"build:server": "esbuild src/server/cli.ts src/server/fb-wizard.ts src/server/graph-mcp-entry.ts src/server/chart-serve.ts src/server/deck-mcp-entry.ts src/server/deck-serve.ts src/server/council-entry.ts src/server/council-serve.ts src/server/recall-entry.ts --bundle --platform=node --target=node18 --outdir=dist/server --external:node-pty --external:ws --external:typescript --external:web-tree-sitter --external:tree-sitter-typescript --external:cloudflared && rm -rf dist/server/public && cp -r ../claude-code-web/src/public dist/server/public && rm -rf dist/server/graph/queries && mkdir -p dist/server/graph && cp -r src/server/graph/queries dist/server/graph/queries",
|
|
109
|
-
"dev:client": "vite",
|
|
110
|
-
"dev:chart": "pnpm build:server && pnpm build:chart-client && node dist/server/graph-mcp-entry.js serve",
|
|
111
|
-
"dev:server": "pnpm build:server && node dist/server/cli.js",
|
|
112
|
-
"dev": "pnpm build:server && concurrently -k -n client,server -c cyan,magenta \"vite\" \"node dist/server/cli.js\""
|
|
118
|
+
"vitest": "^1.6.0"
|
|
113
119
|
}
|
|
114
|
-
}
|
|
120
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
name: Backup prod DB before migration deploys
|
|
2
|
+
|
|
3
|
+
# Triggers a logical pg_dump of prod whenever a migration file lands on master
|
|
4
|
+
# or implementation. Backup is uploaded as a workflow artifact (90-day retention)
|
|
5
|
+
# tied to the commit SHA — see docs/migrations-runbook.md for restore steps.
|
|
6
|
+
#
|
|
7
|
+
# Required secrets (configure under repo Settings → Secrets and variables → Actions):
|
|
8
|
+
# PROD_DATABASE_URL — full DATABASE_URL for the production DB.
|
|
9
|
+
# Treat this as production credential — anyone who can
|
|
10
|
+
# download the artifact effectively has prod data.
|
|
11
|
+
#
|
|
12
|
+
# Required permissions: this workflow only reads from the DB and writes an
|
|
13
|
+
# artifact; it does not push code, deploy, or modify anything.
|
|
14
|
+
|
|
15
|
+
on:
|
|
16
|
+
push:
|
|
17
|
+
branches: [master, implementation]
|
|
18
|
+
paths:
|
|
19
|
+
- 'prisma/migrations/**'
|
|
20
|
+
- 'prisma/schema.prisma'
|
|
21
|
+
workflow_dispatch: # allow manual runs for ad-hoc snapshots
|
|
22
|
+
|
|
23
|
+
permissions:
|
|
24
|
+
contents: read
|
|
25
|
+
|
|
26
|
+
jobs:
|
|
27
|
+
backup:
|
|
28
|
+
runs-on: ubuntu-latest
|
|
29
|
+
timeout-minutes: 15
|
|
30
|
+
steps:
|
|
31
|
+
- name: Checkout
|
|
32
|
+
uses: actions/checkout@v4
|
|
33
|
+
|
|
34
|
+
- name: Run wrapper in backup-only mode
|
|
35
|
+
env:
|
|
36
|
+
# The same env-var contract used for local — only the destination differs.
|
|
37
|
+
DATABASE_URL: ${{ secrets.PROD_DATABASE_URL }}
|
|
38
|
+
BACKUP_DIR: artifacts
|
|
39
|
+
# Force docker so the wrapper auto-detects the server's major version
|
|
40
|
+
# and pulls the matching postgres:<major> image. Avoids hardcoding a
|
|
41
|
+
# client version that drifts when prod Postgres is upgraded.
|
|
42
|
+
# ubuntu-latest runners ship with docker pre-installed.
|
|
43
|
+
PG_DUMP_VIA_DOCKER: '1'
|
|
44
|
+
run: |
|
|
45
|
+
if [[ -z "$DATABASE_URL" ]]; then
|
|
46
|
+
echo "::error::PROD_DATABASE_URL secret is not configured."
|
|
47
|
+
echo "Set it under repo Settings → Secrets and variables → Actions."
|
|
48
|
+
exit 1
|
|
49
|
+
fi
|
|
50
|
+
bash scripts/migrate-with-backup.sh backup-only
|
|
51
|
+
|
|
52
|
+
- name: Upload dump as artifact
|
|
53
|
+
uses: actions/upload-artifact@v4
|
|
54
|
+
with:
|
|
55
|
+
name: prod-db-backup-${{ github.sha }}
|
|
56
|
+
path: artifacts/*.sql.gz
|
|
57
|
+
retention-days: 90
|
|
58
|
+
if-no-files-found: error
|
|
59
|
+
|
|
60
|
+
- name: Summary
|
|
61
|
+
run: |
|
|
62
|
+
echo "### Backup complete" >> $GITHUB_STEP_SUMMARY
|
|
63
|
+
echo "" >> $GITHUB_STEP_SUMMARY
|
|
64
|
+
echo "Triggered by: \`${{ github.event_name }}\` on \`${{ github.ref_name }}\`" >> $GITHUB_STEP_SUMMARY
|
|
65
|
+
echo "Commit: \`${{ github.sha }}\`" >> $GITHUB_STEP_SUMMARY
|
|
66
|
+
echo "" >> $GITHUB_STEP_SUMMARY
|
|
67
|
+
echo "Download via:" >> $GITHUB_STEP_SUMMARY
|
|
68
|
+
echo '```bash' >> $GITHUB_STEP_SUMMARY
|
|
69
|
+
echo "gh run download ${{ github.run_id }} -n prod-db-backup-${{ github.sha }}" >> $GITHUB_STEP_SUMMARY
|
|
70
|
+
echo '```' >> $GITHUB_STEP_SUMMARY
|
|
71
|
+
echo "" >> $GITHUB_STEP_SUMMARY
|
|
72
|
+
echo "Restore steps: see [docs/migrations-runbook.md](../blob/${{ github.sha }}/docs/migrations-runbook.md)." >> $GITHUB_STEP_SUMMARY
|