()
| 1467 | // ── Main ─────────────────────────────────────────────────────────────────── |
| 1468 | |
| 1469 | async function main(): Promise<void> { |
| 1470 | const args = parseArgs(); |
| 1471 | |
| 1472 | if (!args.quiet) { |
| 1473 | const engine = detectEngineTier(); |
| 1474 | console.error(`[gbrain-sync] mode=${args.mode} engine=${engine.engine}`); |
| 1475 | } |
| 1476 | |
| 1477 | // Acquire lock (skip on dry-run since dry-run never writes). |
| 1478 | const needsLock = args.mode !== "dry-run"; |
| 1479 | let haveLock = false; |
| 1480 | if (needsLock) { |
| 1481 | haveLock = acquireLock(); |
| 1482 | if (!haveLock) { |
| 1483 | console.error( |
| 1484 | `[gbrain-sync] another /sync-gbrain is running (lock at ${LOCK_PATH}). ` + |
| 1485 | `If that process died, the lock auto-clears after 5 min, or remove it manually.` |
| 1486 | ); |
| 1487 | process.exit(2); |
| 1488 | } |
| 1489 | } |
| 1490 | |
| 1491 | const cleanup = () => { |
| 1492 | if (haveLock) releaseLock(); |
| 1493 | }; |
| 1494 | process.on("SIGINT", () => { cleanup(); process.exit(130); }); |
| 1495 | process.on("SIGTERM", () => { cleanup(); process.exit(143); }); |
| 1496 | |
| 1497 | let exitCode = 0; |
| 1498 | const stages: StageResult[] = []; |
| 1499 | try { |
| 1500 | const state = loadSyncState(); |
| 1501 | |
| 1502 | if (!args.noCode) { |
| 1503 | stages.push(await withErrorContext("sync:code", () => runCodeImport(args), "gstack-gbrain-sync")); |
| 1504 | } |
| 1505 | if (!args.noMemory) { |
| 1506 | stages.push(await withErrorContext("sync:memory", () => runMemoryIngest(args), "gstack-gbrain-sync")); |
| 1507 | } |
| 1508 | if (!args.noBrainSync) { |
| 1509 | stages.push(await withErrorContext("sync:brain-sync", () => runBrainSyncPush(args), "gstack-gbrain-sync")); |
| 1510 | } |
| 1511 | |
| 1512 | if (args.mode !== "dry-run") { |
| 1513 | state.last_sync = new Date().toISOString(); |
| 1514 | if (args.mode === "full") state.last_full_sync = state.last_sync; |
| 1515 | state.last_stages = stages; |
| 1516 | saveSyncState(state); |
| 1517 | } |
| 1518 | |
| 1519 | const anyError = stages.some((s) => s.ran && !s.ok); |
| 1520 | exitCode = anyError ? 1 : 0; |
| 1521 | } finally { |
| 1522 | // Release the sync lock BEFORE the dream cycle. Dream is a source-scoped |
| 1523 | // cycle that can run several minutes; holding the machine-wide lock that |
| 1524 | // long would freeze every other worktree's /sync-gbrain. Dream is guarded |
| 1525 | // by its own marker. |
| 1526 | cleanup(); |
no test coverage detected