syntaur 0.12.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/.claude-plugin/plugin.json +5 -1
  2. package/README.md +23 -0
  3. package/dashboard/dist/assets/{_basePickBy-XOV2F7dX.js → _basePickBy-qEDLAY_J.js} +1 -1
  4. package/dashboard/dist/assets/{_baseUniq-Dccfk3fK.js → _baseUniq-7tDs18Gn.js} +1 -1
  5. package/dashboard/dist/assets/{arc-TwJ5RF-H.js → arc-Brdw0dYW.js} +1 -1
  6. package/dashboard/dist/assets/{architectureDiagram-2XIMDMQ5-CpkVJudw.js → architectureDiagram-2XIMDMQ5-BiEdLT9m.js} +1 -1
  7. package/dashboard/dist/assets/{blockDiagram-WCTKOSBZ-Ck6VQ4Fh.js → blockDiagram-WCTKOSBZ-HC-j_hPZ.js} +1 -1
  8. package/dashboard/dist/assets/{c4Diagram-IC4MRINW-B5V7YG03.js → c4Diagram-IC4MRINW-DA_rWbl7.js} +1 -1
  9. package/dashboard/dist/assets/channel-D2-caOn6.js +1 -0
  10. package/dashboard/dist/assets/{chunk-4BX2VUAB-rBdyOxAe.js → chunk-4BX2VUAB-CF3hguyj.js} +1 -1
  11. package/dashboard/dist/assets/{chunk-55IACEB6-DKlNXjYt.js → chunk-55IACEB6-BTeCeIc_.js} +1 -1
  12. package/dashboard/dist/assets/{chunk-FMBD7UC4-Cqm0PtUI.js → chunk-FMBD7UC4-8qVvxmks.js} +1 -1
  13. package/dashboard/dist/assets/{chunk-JSJVCQXG-CQyXQcG3.js → chunk-JSJVCQXG-DCf8n7EO.js} +1 -1
  14. package/dashboard/dist/assets/{chunk-KX2RTZJC-DmqjHPpJ.js → chunk-KX2RTZJC-CcpjTmME.js} +1 -1
  15. package/dashboard/dist/assets/{chunk-NQ4KR5QH-CHXoBcYa.js → chunk-NQ4KR5QH-BnFPKo4-.js} +1 -1
  16. package/dashboard/dist/assets/{chunk-QZHKN3VN-Dd4sChBS.js → chunk-QZHKN3VN-IPMVLLv2.js} +1 -1
  17. package/dashboard/dist/assets/{chunk-WL4C6EOR-D-QLnkY8.js → chunk-WL4C6EOR-D1pQmHqX.js} +1 -1
  18. package/dashboard/dist/assets/classDiagram-VBA2DB6C-HFOb_XEt.js +1 -0
  19. package/dashboard/dist/assets/classDiagram-v2-RAHNMMFH-HFOb_XEt.js +1 -0
  20. package/dashboard/dist/assets/clone-BrYOwgIq.js +1 -0
  21. package/dashboard/dist/assets/{cose-bilkent-S5V4N54A-VZi6uV56.js → cose-bilkent-S5V4N54A-tvJjvBQr.js} +1 -1
  22. package/dashboard/dist/assets/{dagre-KLK3FWXG-CtT97Qhx.js → dagre-KLK3FWXG-qp1vt6LY.js} +1 -1
  23. package/dashboard/dist/assets/{diagram-E7M64L7V-BjYTNNHb.js → diagram-E7M64L7V-VF5eizNr.js} +1 -1
  24. package/dashboard/dist/assets/{diagram-IFDJBPK2-qj8_r6fb.js → diagram-IFDJBPK2-C3pCbJ1e.js} +1 -1
  25. package/dashboard/dist/assets/{diagram-P4PSJMXO-DlfWo7Vt.js → diagram-P4PSJMXO-D-taXZwH.js} +1 -1
  26. package/dashboard/dist/assets/{erDiagram-INFDFZHY-Ch_nApTv.js → erDiagram-INFDFZHY-QjY-NHVs.js} +1 -1
  27. package/dashboard/dist/assets/{flowDiagram-PKNHOUZH-EOVsqNyi.js → flowDiagram-PKNHOUZH-FjO8P5Tq.js} +1 -1
  28. package/dashboard/dist/assets/{ganttDiagram-A5KZAMGK-CS-lx0B-.js → ganttDiagram-A5KZAMGK-Bsn7CyK3.js} +1 -1
  29. package/dashboard/dist/assets/{gitGraphDiagram-K3NZZRJ6-Ba6RDzPy.js → gitGraphDiagram-K3NZZRJ6-BZ8DjhnF.js} +1 -1
  30. package/dashboard/dist/assets/{graph-BKaAyLk-.js → graph-AdliIYqX.js} +1 -1
  31. package/dashboard/dist/assets/{index-BTWPZaIA.css → index-BoR_oyUZ.css} +1 -1
  32. package/dashboard/dist/assets/index-RCBI4aX2.js +525 -0
  33. package/dashboard/dist/assets/{infoDiagram-LFFYTUFH-Cr20xEsd.js → infoDiagram-LFFYTUFH-BfZjLA86.js} +1 -1
  34. package/dashboard/dist/assets/{ishikawaDiagram-PHBUUO56-CuILV9ph.js → ishikawaDiagram-PHBUUO56-BSgSGMp5.js} +1 -1
  35. package/dashboard/dist/assets/{journeyDiagram-4ABVD52K-CO-l7Mpm.js → journeyDiagram-4ABVD52K-BlQ7qeMC.js} +1 -1
  36. package/dashboard/dist/assets/{kanban-definition-K7BYSVSG-CcirPw_S.js → kanban-definition-K7BYSVSG-CifO0AUu.js} +1 -1
  37. package/dashboard/dist/assets/{layout-BXvr8dMw.js → layout-B9r-gpD9.js} +1 -1
  38. package/dashboard/dist/assets/{linear-B85hgYnX.js → linear-CaaIRxVZ.js} +1 -1
  39. package/dashboard/dist/assets/{mermaid.core-DcZN9Xp2.js → mermaid.core-C8wCWYw-.js} +4 -4
  40. package/dashboard/dist/assets/{mindmap-definition-YRQLILUH-Bxi6p5_3.js → mindmap-definition-YRQLILUH-BpX5T7iT.js} +1 -1
  41. package/dashboard/dist/assets/{pieDiagram-SKSYHLDU-DOSvU0UD.js → pieDiagram-SKSYHLDU-BNGYtWrc.js} +1 -1
  42. package/dashboard/dist/assets/{quadrantDiagram-337W2JSQ-Cmo-5tW9.js → quadrantDiagram-337W2JSQ-CR4GeM9D.js} +1 -1
  43. package/dashboard/dist/assets/{requirementDiagram-Z7DCOOCP-Dw8alwuI.js → requirementDiagram-Z7DCOOCP-CXrPkMLl.js} +1 -1
  44. package/dashboard/dist/assets/{sankeyDiagram-WA2Y5GQK-ZPB-rzOg.js → sankeyDiagram-WA2Y5GQK-CIzof7Dm.js} +1 -1
  45. package/dashboard/dist/assets/{sequenceDiagram-2WXFIKYE-Ct8bkK2h.js → sequenceDiagram-2WXFIKYE-BJZLt0O2.js} +1 -1
  46. package/dashboard/dist/assets/{stateDiagram-RAJIS63D-DnGOXlmx.js → stateDiagram-RAJIS63D-x9gC6zy6.js} +1 -1
  47. package/dashboard/dist/assets/stateDiagram-v2-FVOUBMTO-CN5Dcv7u.js +1 -0
  48. package/dashboard/dist/assets/{timeline-definition-YZTLITO2-AUFmbJjr.js → timeline-definition-YZTLITO2-D_ZSQn1K.js} +1 -1
  49. package/dashboard/dist/assets/{treemap-KZPCXAKY-Dw5FDFAV.js → treemap-KZPCXAKY-LPpMEco4.js} +1 -1
  50. package/dashboard/dist/assets/{vennDiagram-LZ73GAT5-DfxicU4N.js → vennDiagram-LZ73GAT5-CRFZQpr7.js} +1 -1
  51. package/dashboard/dist/assets/{xychartDiagram-JWTSCODW--9N0V1r_.js → xychartDiagram-JWTSCODW-Bivutynw.js} +1 -1
  52. package/dashboard/dist/index.html +2 -2
  53. package/dist/dashboard/server.js +419 -84
  54. package/dist/dashboard/server.js.map +1 -1
  55. package/dist/db/leases-db.d.ts +227 -0
  56. package/dist/db/leases-db.js +679 -0
  57. package/dist/db/leases-db.js.map +1 -0
  58. package/dist/index.js +1825 -445
  59. package/dist/index.js.map +1 -1
  60. package/package.json +1 -1
  61. package/platforms/claude-code/.claude-plugin/plugin.json +5 -1
  62. package/platforms/claude-code/skills/claim-resource/SKILL.md +89 -0
  63. package/platforms/claude-code/skills/extend-resource/SKILL.md +83 -0
  64. package/platforms/claude-code/skills/list-resources/SKILL.md +74 -0
  65. package/platforms/claude-code/skills/release-resource/SKILL.md +65 -0
  66. package/platforms/claude-code/skills/syntaur-protocol/SKILL.md +1 -0
  67. package/platforms/codex/skills/claim-resource/SKILL.md +89 -0
  68. package/platforms/codex/skills/extend-resource/SKILL.md +83 -0
  69. package/platforms/codex/skills/list-resources/SKILL.md +74 -0
  70. package/platforms/codex/skills/release-resource/SKILL.md +65 -0
  71. package/platforms/codex/skills/syntaur-protocol/SKILL.md +1 -0
  72. package/skills/claim-resource/SKILL.md +89 -0
  73. package/skills/extend-resource/SKILL.md +83 -0
  74. package/skills/list-resources/SKILL.md +74 -0
  75. package/skills/release-resource/SKILL.md +65 -0
  76. package/skills/syntaur-protocol/SKILL.md +1 -0
  77. package/dashboard/dist/assets/channel-AEMTX70R.js +0 -1
  78. package/dashboard/dist/assets/classDiagram-VBA2DB6C-LzuOAGzu.js +0 -1
  79. package/dashboard/dist/assets/classDiagram-v2-RAHNMMFH-LzuOAGzu.js +0 -1
  80. package/dashboard/dist/assets/clone-Cw63QGUJ.js +0 -1
  81. package/dashboard/dist/assets/index-Dk9PBm5B.js +0 -510
  82. package/dashboard/dist/assets/stateDiagram-v2-FVOUBMTO-COGzlZxn.js +0 -1
package/dist/index.js CHANGED
@@ -5961,7 +5961,7 @@ __export(parser_exports, {
5961
5961
  });
5962
5962
  import { randomBytes } from "crypto";
5963
5963
  import { readFile as readFile13 } from "fs/promises";
5964
- import { resolve as resolve18 } from "path";
5964
+ import { resolve as resolve19 } from "path";
5965
5965
  function generateShortId() {
5966
5966
  return randomBytes(2).toString("hex");
5967
5967
  }
@@ -6199,10 +6199,10 @@ function serializeLogEntry(entry) {
6199
6199
  return lines.join("\n");
6200
6200
  }
6201
6201
  function checklistPath(todosDir2, workspace) {
6202
- return resolve18(todosDir2, `${workspace}.md`);
6202
+ return resolve19(todosDir2, `${workspace}.md`);
6203
6203
  }
6204
6204
  function logPath(todosDir2, workspace) {
6205
- return resolve18(todosDir2, `${workspace}-log.md`);
6205
+ return resolve19(todosDir2, `${workspace}-log.md`);
6206
6206
  }
6207
6207
  function archivePath(todosDir2, workspace, interval, now = /* @__PURE__ */ new Date()) {
6208
6208
  const year = now.getFullYear();
@@ -6226,7 +6226,7 @@ function archivePath(todosDir2, workspace, interval, now = /* @__PURE__ */ new D
6226
6226
  default:
6227
6227
  suffix = `${year}-${month}-${day}`;
6228
6228
  }
6229
- return resolve18(todosDir2, "archive", `${workspace}-${suffix}.md`);
6229
+ return resolve19(todosDir2, "archive", `${workspace}-${suffix}.md`);
6230
6230
  }
6231
6231
  async function readChecklist(todosDir2, workspace) {
6232
6232
  const path = checklistPath(todosDir2, workspace);
@@ -6855,7 +6855,7 @@ __export(launch_exports, {
6855
6855
  });
6856
6856
  import { spawn as spawn2 } from "child_process";
6857
6857
  import { mkdir as mkdir6, writeFile as writeFile9 } from "fs/promises";
6858
- import { isAbsolute as isAbsolute3, resolve as resolve33 } from "path";
6858
+ import { isAbsolute as isAbsolute3, resolve as resolve34 } from "path";
6859
6859
  function formatFallbackCwdWarning(opts) {
6860
6860
  const missing = [];
6861
6861
  if (!opts.worktreePath) missing.push("worktreePath");
@@ -6899,8 +6899,8 @@ async function launchAgent(options) {
6899
6899
  console.error(`Assignment not found: ${projectSlug}/${assignmentSlug}`);
6900
6900
  process.exit(1);
6901
6901
  }
6902
- const projectDir = resolve33(projectsDir2, projectSlug);
6903
- const assignmentDir = resolve33(projectDir, "assignments", assignmentSlug);
6902
+ const projectDir = resolve34(projectsDir2, projectSlug);
6903
+ const assignmentDir = resolve34(projectDir, "assignments", assignmentSlug);
6904
6904
  const resolvedFromWorkspace = cwdOverride ?? detail.workspace.worktreePath ?? (detail.workspace.repository?.startsWith("/") ? detail.workspace.repository : null);
6905
6905
  const workspaceDir = resolvedFromWorkspace ?? process.cwd();
6906
6906
  if (!cwdOverride) {
@@ -6912,7 +6912,7 @@ async function launchAgent(options) {
6912
6912
  });
6913
6913
  if (warning) console.warn(warning);
6914
6914
  }
6915
- const contextDir = resolve33(workspaceDir, ".syntaur");
6915
+ const contextDir = resolve34(workspaceDir, ".syntaur");
6916
6916
  await mkdir6(contextDir, { recursive: true });
6917
6917
  const context = {
6918
6918
  projectSlug,
@@ -6925,7 +6925,7 @@ async function launchAgent(options) {
6925
6925
  grabbedAt: (/* @__PURE__ */ new Date()).toISOString()
6926
6926
  };
6927
6927
  await writeFile9(
6928
- resolve33(contextDir, "context.json"),
6928
+ resolve34(contextDir, "context.json"),
6929
6929
  JSON.stringify(context, null, 2) + "\n"
6930
6930
  );
6931
6931
  const { argv, shellFallbackWarning } = buildAgentArgv(
@@ -7082,7 +7082,7 @@ var init_git_worktree = __esm({
7082
7082
  });
7083
7083
 
7084
7084
  // src/index.ts
7085
- import { Command as Command12 } from "commander";
7085
+ import { Command as Command13 } from "commander";
7086
7086
 
7087
7087
  // src/commands/init.ts
7088
7088
  init_paths();
@@ -7240,7 +7240,7 @@ init_create_assignment();
7240
7240
  init_config2();
7241
7241
  import { spawn } from "child_process";
7242
7242
  import { createServer as createNetServer } from "net";
7243
- import { resolve as resolve22, dirname as dirname6 } from "path";
7243
+ import { resolve as resolve23, dirname as dirname7 } from "path";
7244
7244
  import { fileURLToPath as fileURLToPath2 } from "url";
7245
7245
 
7246
7246
  // src/dashboard/server.ts
@@ -7249,7 +7249,7 @@ init_api();
7249
7249
  init_assignment_resolver();
7250
7250
  import express from "express";
7251
7251
  import { createServer } from "http";
7252
- import { resolve as resolve21 } from "path";
7252
+ import { resolve as resolve22 } from "path";
7253
7253
  import { writeFile as writeFile5, unlink as unlink4 } from "fs/promises";
7254
7254
  import { WebSocketServer, WebSocket } from "ws";
7255
7255
 
@@ -7464,8 +7464,8 @@ function rowToSession(row) {
7464
7464
  };
7465
7465
  }
7466
7466
  async function appendSession(_projectDir, session) {
7467
- const db3 = getSessionDb();
7468
- db3.prepare(`
7467
+ const db4 = getSessionDb();
7468
+ db4.prepare(`
7469
7469
  INSERT INTO sessions (session_id, project_slug, assignment_slug, agent, started, status, path, description, transcript_path)
7470
7470
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
7471
7471
  ON CONFLICT(session_id) DO UPDATE SET
@@ -7490,36 +7490,36 @@ async function appendSession(_projectDir, session) {
7490
7490
  );
7491
7491
  }
7492
7492
  async function updateSessionStatus(_projectDir, sessionId, status) {
7493
- const db3 = getSessionDb();
7493
+ const db4 = getSessionDb();
7494
7494
  const isTerminal = status === "completed" || status === "stopped";
7495
- const result = isTerminal ? db3.prepare(
7495
+ const result = isTerminal ? db4.prepare(
7496
7496
  "UPDATE sessions SET status = ?, ended = datetime('now'), updated_at = datetime('now') WHERE session_id = ?"
7497
- ).run(status, sessionId) : db3.prepare(
7497
+ ).run(status, sessionId) : db4.prepare(
7498
7498
  "UPDATE sessions SET status = ?, updated_at = datetime('now') WHERE session_id = ?"
7499
7499
  ).run(status, sessionId);
7500
7500
  return result.changes > 0;
7501
7501
  }
7502
7502
  async function listAllSessions(_projectsDir) {
7503
- const db3 = getSessionDb();
7504
- const rows = db3.prepare("SELECT * FROM sessions ORDER BY started DESC").all();
7503
+ const db4 = getSessionDb();
7504
+ const rows = db4.prepare("SELECT * FROM sessions ORDER BY started DESC").all();
7505
7505
  return rows.map(rowToSession);
7506
7506
  }
7507
7507
  async function listProjectSessions(_projectsDir, projectSlug, assignmentSlug) {
7508
- const db3 = getSessionDb();
7508
+ const db4 = getSessionDb();
7509
7509
  if (assignmentSlug) {
7510
- const rows2 = db3.prepare(
7510
+ const rows2 = db4.prepare(
7511
7511
  "SELECT * FROM sessions WHERE project_slug = ? AND assignment_slug = ? ORDER BY started DESC"
7512
7512
  ).all(projectSlug, assignmentSlug);
7513
7513
  return rows2.map(rowToSession);
7514
7514
  }
7515
- const rows = db3.prepare("SELECT * FROM sessions WHERE project_slug = ? ORDER BY started DESC").all(projectSlug);
7515
+ const rows = db4.prepare("SELECT * FROM sessions WHERE project_slug = ? ORDER BY started DESC").all(projectSlug);
7516
7516
  return rows.map(rowToSession);
7517
7517
  }
7518
7518
  async function deleteSessions(sessionIds) {
7519
7519
  if (sessionIds.length === 0) return 0;
7520
- const db3 = getSessionDb();
7520
+ const db4 = getSessionDb();
7521
7521
  const placeholders = sessionIds.map(() => "?").join(", ");
7522
- const result = db3.prepare(`DELETE FROM sessions WHERE session_id IN (${placeholders})`).run(...sessionIds);
7522
+ const result = db4.prepare(`DELETE FROM sessions WHERE session_id IN (${placeholders})`).run(...sessionIds);
7523
7523
  return result.changes;
7524
7524
  }
7525
7525
  var DONE_ASSIGNMENT_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "review"]);
@@ -7535,8 +7535,8 @@ async function readAssignmentStatus(projectDir, assignmentSlug) {
7535
7535
  );
7536
7536
  }
7537
7537
  async function reconcileActiveSessions(projectsDir2, assignmentsDir2) {
7538
- const db3 = getSessionDb();
7539
- const activeSessions = db3.prepare("SELECT * FROM sessions WHERE status = 'active' AND assignment_slug IS NOT NULL").all();
7538
+ const db4 = getSessionDb();
7539
+ const activeSessions = db4.prepare("SELECT * FROM sessions WHERE status = 'active' AND assignment_slug IS NOT NULL").all();
7540
7540
  if (activeSessions.length === 0) return 0;
7541
7541
  const assignmentStatuses = /* @__PURE__ */ new Map();
7542
7542
  const seen = /* @__PURE__ */ new Set();
@@ -7573,10 +7573,10 @@ async function reconcileActiveSessions(projectsDir2, assignmentsDir2) {
7573
7573
  return totalUpdated;
7574
7574
  }
7575
7575
  async function listSessionsByAssignment(projectSlug, assignmentSlug) {
7576
- const db3 = getSessionDb();
7577
- const rows = projectSlug === null ? db3.prepare(
7576
+ const db4 = getSessionDb();
7577
+ const rows = projectSlug === null ? db4.prepare(
7578
7578
  "SELECT * FROM sessions WHERE assignment_slug = ? AND project_slug IS NULL ORDER BY started DESC"
7579
- ).all(assignmentSlug) : db3.prepare(
7579
+ ).all(assignmentSlug) : db4.prepare(
7580
7580
  "SELECT * FROM sessions WHERE project_slug = ? AND assignment_slug = ? ORDER BY started DESC"
7581
7581
  ).all(projectSlug, assignmentSlug);
7582
7582
  return rows.map(rowToSession);
@@ -7584,9 +7584,9 @@ async function listSessionsByAssignment(projectSlug, assignmentSlug) {
7584
7584
 
7585
7585
  // src/dashboard/watcher.ts
7586
7586
  import { watch } from "chokidar";
7587
- import { relative, sep } from "path";
7587
+ import { basename as basename2, dirname as dirname4, relative, sep } from "path";
7588
7588
  function createWatcher(options) {
7589
- const { projectsDir: projectsDir2, assignmentsDir: assignmentsDir2, serversDir: serversDir2, playbooksDir: playbooksDir3, todosDir: todosDir2, onMessage, debounceMs = 300 } = options;
7589
+ const { projectsDir: projectsDir2, assignmentsDir: assignmentsDir2, serversDir: serversDir2, playbooksDir: playbooksDir3, todosDir: todosDir2, dbPath, onMessage, debounceMs = 300 } = options;
7590
7590
  const pendingEvents = /* @__PURE__ */ new Map();
7591
7591
  const projectsWatcher = watch(projectsDir2, {
7592
7592
  ignoreInitial: true,
@@ -7754,6 +7754,38 @@ function createWatcher(options) {
7754
7754
  todosWatcher.on("add", handleTodoChange2);
7755
7755
  todosWatcher.on("unlink", handleTodoChange2);
7756
7756
  }
7757
+ let leasesDbWatcher = null;
7758
+ if (dbPath) {
7759
+ let handleDbChange2 = function(filePath) {
7760
+ if (!basename2(filePath).startsWith(dbBase)) return;
7761
+ const debounceKey = "__leases-db__";
7762
+ const existing = pendingEvents.get(debounceKey);
7763
+ if (existing) clearTimeout(existing);
7764
+ pendingEvents.set(
7765
+ debounceKey,
7766
+ setTimeout(() => {
7767
+ pendingEvents.delete(debounceKey);
7768
+ const message = {
7769
+ type: "leases-updated",
7770
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
7771
+ };
7772
+ onMessage(message);
7773
+ }, debounceMs)
7774
+ );
7775
+ };
7776
+ var handleDbChange = handleDbChange2;
7777
+ const dbDir = dirname4(dbPath);
7778
+ const dbBase = basename2(dbPath);
7779
+ leasesDbWatcher = watch(dbDir, {
7780
+ ignoreInitial: true,
7781
+ persistent: true,
7782
+ depth: 0,
7783
+ ignored: /(^|[\/\\])\../
7784
+ });
7785
+ leasesDbWatcher.on("change", handleDbChange2);
7786
+ leasesDbWatcher.on("add", handleDbChange2);
7787
+ leasesDbWatcher.on("unlink", handleDbChange2);
7788
+ }
7757
7789
  return {
7758
7790
  close: async () => {
7759
7791
  pendingEvents.forEach((timeout) => {
@@ -7765,6 +7797,7 @@ function createWatcher(options) {
7765
7797
  if (serversWatcher) await serversWatcher.close();
7766
7798
  if (playbooksWatcher) await playbooksWatcher.close();
7767
7799
  if (todosWatcher) await todosWatcher.close();
7800
+ if (leasesDbWatcher) await leasesDbWatcher.close();
7768
7801
  }
7769
7802
  };
7770
7803
  }
@@ -7782,7 +7815,7 @@ init_timestamp();
7782
7815
  init_fs();
7783
7816
  init_parser();
7784
7817
  import { Router } from "express";
7785
- import { resolve as resolve15, basename as basename2 } from "path";
7818
+ import { resolve as resolve15, basename as basename3 } from "path";
7786
7819
  import { rm, readFile as readFile11, open as fsOpen } from "fs/promises";
7787
7820
 
7788
7821
  // src/dashboard/acceptance-criteria.ts
@@ -8067,7 +8100,7 @@ function createWriteRouter(projectsDir2, assignmentsDir2) {
8067
8100
  res.status(404).json({ error: `Project "${slug}" not found` });
8068
8101
  return;
8069
8102
  }
8070
- const document = await getEditableDocument(projectsDir2, "memory", basename2(projectDir), itemSlug);
8103
+ const document = await getEditableDocument(projectsDir2, "memory", basename3(projectDir), itemSlug);
8071
8104
  if (!document) {
8072
8105
  res.status(404).json({ error: "Memory not found" });
8073
8106
  return;
@@ -8086,7 +8119,7 @@ function createWriteRouter(projectsDir2, assignmentsDir2) {
8086
8119
  res.status(404).json({ error: `Project "${slug}" not found` });
8087
8120
  return;
8088
8121
  }
8089
- const document = await getEditableDocument(projectsDir2, "resource", basename2(projectDir), itemSlug);
8122
+ const document = await getEditableDocument(projectsDir2, "resource", basename3(projectDir), itemSlug);
8090
8123
  if (!document) {
8091
8124
  res.status(404).json({ error: "Resource not found" });
8092
8125
  return;
@@ -8171,7 +8204,7 @@ ${body.startsWith("\n") ? body.slice(1) : body}${body.endsWith("\n") ? "" : "\n"
8171
8204
  let content = renderItemStub(kind, {
8172
8205
  slug: requestedSlug,
8173
8206
  name,
8174
- projectSlug: basename2(projectDir),
8207
+ projectSlug: basename3(projectDir),
8175
8208
  timestamp
8176
8209
  });
8177
8210
  const customBody = typeof body.body === "string" ? body.body : "";
@@ -8188,13 +8221,13 @@ ${body.startsWith("\n") ? body.slice(1) : body}${body.endsWith("\n") ? "" : "\n"
8188
8221
  } catch (err2) {
8189
8222
  if (err2.code === "EEXIST") {
8190
8223
  res.status(409).json({
8191
- error: `${kind === "memory" ? "Memory" : "Resource"} with slug "${requestedSlug}" already exists in project "${basename2(projectDir)}".`
8224
+ error: `${kind === "memory" ? "Memory" : "Resource"} with slug "${requestedSlug}" already exists in project "${basename3(projectDir)}".`
8192
8225
  });
8193
8226
  return;
8194
8227
  }
8195
8228
  throw err2;
8196
8229
  }
8197
- res.status(201).json({ slug: requestedSlug, projectSlug: basename2(projectDir), content });
8230
+ res.status(201).json({ slug: requestedSlug, projectSlug: basename3(projectDir), content });
8198
8231
  } catch (error) {
8199
8232
  console.error(`Error creating ${kind}:`, error);
8200
8233
  res.status(500).json({ error: `Failed to create ${kind}: ${error.message}` });
@@ -8229,7 +8262,7 @@ ${body.startsWith("\n") ? body.slice(1) : body}${body.endsWith("\n") ? "" : "\n"
8229
8262
  ${nextBody}${nextBody.endsWith("\n") ? "" : "\n"}`;
8230
8263
  merged = setTopLevelField(merged, "updated", nowTimestamp());
8231
8264
  await writeFileForce(filePath, merged);
8232
- const detail = await getItemDetail(kind, basename2(projectDir), itemSlug);
8265
+ const detail = await getItemDetail(kind, basename3(projectDir), itemSlug);
8233
8266
  res.json({ [kind]: detail, content: merged });
8234
8267
  } catch (error) {
8235
8268
  console.error(`Error updating ${kind}:`, error);
@@ -9639,6 +9672,65 @@ function createServersRouter(serversDir2, projectsDir2, assignmentsDir2) {
9639
9672
  import { Router as Router3 } from "express";
9640
9673
  import { resolve as resolve16 } from "path";
9641
9674
  init_fs();
9675
+
9676
+ // src/utils/transcript.ts
9677
+ import { open } from "fs/promises";
9678
+ var MAX_LINES_SCANNED = 50;
9679
+ async function derivePathFromTranscript(transcriptPath) {
9680
+ if (!transcriptPath) return null;
9681
+ let handle;
9682
+ try {
9683
+ handle = await open(transcriptPath, "r");
9684
+ } catch {
9685
+ return null;
9686
+ }
9687
+ try {
9688
+ const stream = handle.createReadStream({ encoding: "utf-8" });
9689
+ let buffer = "";
9690
+ let scanned = 0;
9691
+ for await (const chunk of stream) {
9692
+ buffer += chunk;
9693
+ let nl = buffer.indexOf("\n");
9694
+ while (nl !== -1) {
9695
+ const line = buffer.slice(0, nl);
9696
+ buffer = buffer.slice(nl + 1);
9697
+ const cwd = extractCwd(line);
9698
+ if (cwd) {
9699
+ stream.destroy();
9700
+ return cwd;
9701
+ }
9702
+ scanned++;
9703
+ if (scanned >= MAX_LINES_SCANNED) {
9704
+ stream.destroy();
9705
+ return null;
9706
+ }
9707
+ nl = buffer.indexOf("\n");
9708
+ }
9709
+ }
9710
+ if (buffer.length > 0) {
9711
+ const cwd = extractCwd(buffer);
9712
+ if (cwd) return cwd;
9713
+ }
9714
+ return null;
9715
+ } finally {
9716
+ await handle.close().catch(() => {
9717
+ });
9718
+ }
9719
+ }
9720
+ function extractCwd(line) {
9721
+ const trimmed = line.trim();
9722
+ if (trimmed.length === 0 || trimmed[0] !== "{") return null;
9723
+ try {
9724
+ const parsed = JSON.parse(trimmed);
9725
+ if (typeof parsed.cwd === "string" && parsed.cwd.length > 0) {
9726
+ return parsed.cwd;
9727
+ }
9728
+ } catch {
9729
+ }
9730
+ return null;
9731
+ }
9732
+
9733
+ // src/dashboard/api-agent-sessions.ts
9642
9734
  function createAgentSessionsRouter(projectsDir2, broadcast, assignmentsDir2) {
9643
9735
  const router = Router3();
9644
9736
  router.get("/", async (_req, res) => {
@@ -9686,6 +9778,8 @@ function createAgentSessionsRouter(projectsDir2, broadcast, assignmentsDir2) {
9686
9778
  return;
9687
9779
  }
9688
9780
  }
9781
+ const derivedPath = await derivePathFromTranscript(transcriptPath);
9782
+ const recordedPath = derivedPath ?? path ?? "";
9689
9783
  const session = {
9690
9784
  projectSlug: projectSlug || null,
9691
9785
  assignmentSlug: assignmentSlug || null,
@@ -9693,7 +9787,7 @@ function createAgentSessionsRouter(projectsDir2, broadcast, assignmentsDir2) {
9693
9787
  sessionId,
9694
9788
  started: (/* @__PURE__ */ new Date()).toISOString(),
9695
9789
  status: "active",
9696
- path: path || "",
9790
+ path: recordedPath,
9697
9791
  description: description || null,
9698
9792
  transcriptPath: transcriptPath || null
9699
9793
  };
@@ -9744,6 +9838,692 @@ function createAgentSessionsRouter(projectsDir2, broadcast, assignmentsDir2) {
9744
9838
  return router;
9745
9839
  }
9746
9840
 
9841
+ // src/dashboard/api-leases.ts
9842
+ import { Router as Router4 } from "express";
9843
+
9844
+ // src/db/leases-db.ts
9845
+ init_paths();
9846
+ import Database2 from "better-sqlite3";
9847
+ import { randomUUID as randomUUID2 } from "crypto";
9848
+ import { resolve as resolve17 } from "path";
9849
+ var db2 = null;
9850
+ var LEASE_SCHEMA_VERSION = "1";
9851
+ var SCHEMA_SQL2 = `
9852
+ CREATE TABLE IF NOT EXISTS meta (
9853
+ key TEXT PRIMARY KEY,
9854
+ value TEXT
9855
+ );
9856
+
9857
+ CREATE TABLE IF NOT EXISTS inventories (
9858
+ slug TEXT PRIMARY KEY,
9859
+ kind TEXT NOT NULL,
9860
+ display_name TEXT,
9861
+ default_ttl_s INTEGER NOT NULL CHECK (default_ttl_s > 0),
9862
+ created_at TEXT NOT NULL
9863
+ );
9864
+
9865
+ CREATE TABLE IF NOT EXISTS inventory_members (
9866
+ inventory_slug TEXT NOT NULL,
9867
+ member_id TEXT NOT NULL,
9868
+ status TEXT NOT NULL DEFAULT 'idle'
9869
+ CHECK (status IN ('idle','leased','retired')),
9870
+ generation INTEGER NOT NULL DEFAULT 0,
9871
+ metadata_json TEXT,
9872
+ last_used_at TEXT,
9873
+ retired_at TEXT,
9874
+ PRIMARY KEY (inventory_slug, member_id),
9875
+ FOREIGN KEY (inventory_slug) REFERENCES inventories(slug)
9876
+ );
9877
+
9878
+ CREATE INDEX IF NOT EXISTS idx_members_idle
9879
+ ON inventory_members (inventory_slug, status, last_used_at);
9880
+
9881
+ CREATE TABLE IF NOT EXISTS leases (
9882
+ lease_id TEXT PRIMARY KEY,
9883
+ inventory_slug TEXT NOT NULL,
9884
+ member_id TEXT NOT NULL,
9885
+ member_gen INTEGER NOT NULL,
9886
+ state TEXT NOT NULL
9887
+ CHECK (state IN ('active','released','expired','revoked')),
9888
+ granted_at TEXT NOT NULL,
9889
+ expires_at TEXT NOT NULL,
9890
+ released_at TEXT,
9891
+ requested_for TEXT,
9892
+ FOREIGN KEY (inventory_slug, member_id)
9893
+ REFERENCES inventory_members(inventory_slug, member_id)
9894
+ );
9895
+
9896
+ CREATE INDEX IF NOT EXISTS idx_leases_gc
9897
+ ON leases (state, expires_at);
9898
+
9899
+ CREATE TABLE IF NOT EXISTS lease_events (
9900
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
9901
+ lease_id TEXT NOT NULL,
9902
+ event TEXT NOT NULL,
9903
+ at TEXT NOT NULL,
9904
+ detail_json TEXT,
9905
+ FOREIGN KEY (lease_id) REFERENCES leases(lease_id)
9906
+ );
9907
+
9908
+ CREATE INDEX IF NOT EXISTS idx_events_lease
9909
+ ON lease_events (lease_id, at);
9910
+ `;
9911
+ var NoIdleMemberError = class extends Error {
9912
+ constructor(inventorySlug) {
9913
+ super(`no idle members in '${inventorySlug}'`);
9914
+ this.inventorySlug = inventorySlug;
9915
+ this.name = "NoIdleMemberError";
9916
+ }
9917
+ };
9918
+ var StaleLeaseError = class extends Error {
9919
+ constructor(leaseId) {
9920
+ super(`lease ${leaseId} is no longer active (expired or revoked)`);
9921
+ this.leaseId = leaseId;
9922
+ this.name = "StaleLeaseError";
9923
+ }
9924
+ };
9925
+ var NotFoundError = class extends Error {
9926
+ constructor(id) {
9927
+ super(`lease ${id} not found`);
9928
+ this.id = id;
9929
+ this.name = "NotFoundError";
9930
+ }
9931
+ };
9932
+ var LeaseContentionError = class extends Error {
9933
+ constructor(inventorySlug) {
9934
+ super(`contention timeout on '${inventorySlug}'; retry`);
9935
+ this.inventorySlug = inventorySlug;
9936
+ this.name = "LeaseContentionError";
9937
+ }
9938
+ };
9939
+ var MemberInUseError = class extends Error {
9940
+ constructor(inventorySlug, memberId) {
9941
+ super(`member ${inventorySlug}/${memberId} is currently leased; release before retiring`);
9942
+ this.inventorySlug = inventorySlug;
9943
+ this.memberId = memberId;
9944
+ this.name = "MemberInUseError";
9945
+ }
9946
+ };
9947
+ var DuplicateInventoryError = class extends Error {
9948
+ constructor(slug) {
9949
+ super(`inventory '${slug}' already exists`);
9950
+ this.slug = slug;
9951
+ this.name = "DuplicateInventoryError";
9952
+ }
9953
+ };
9954
+ var DuplicateMemberError = class extends Error {
9955
+ constructor(inventorySlug, memberId) {
9956
+ super(`member ${inventorySlug}/${memberId} already exists`);
9957
+ this.inventorySlug = inventorySlug;
9958
+ this.memberId = memberId;
9959
+ this.name = "DuplicateMemberError";
9960
+ }
9961
+ };
9962
+ function nowIso() {
9963
+ return (/* @__PURE__ */ new Date()).toISOString();
9964
+ }
9965
+ function isBusyError(err2) {
9966
+ if (!err2 || typeof err2 !== "object") return false;
9967
+ const e = err2;
9968
+ return e.code === "SQLITE_BUSY" || e.code === "SQLITE_BUSY_SNAPSHOT";
9969
+ }
9970
+ function initLeasesDb(dbPath) {
9971
+ if (db2) return db2;
9972
+ const finalPath = dbPath ?? resolve17(syntaurRoot(), "syntaur.db");
9973
+ db2 = new Database2(finalPath);
9974
+ db2.pragma("journal_mode = WAL");
9975
+ db2.pragma("busy_timeout = 5000");
9976
+ db2.pragma("foreign_keys = ON");
9977
+ const database = db2;
9978
+ const runMigrations = database.transaction(() => {
9979
+ database.exec(SCHEMA_SQL2);
9980
+ database.prepare("INSERT OR IGNORE INTO meta (key, value) VALUES (?, ?)").run("lease_schema_version", LEASE_SCHEMA_VERSION);
9981
+ });
9982
+ runMigrations.exclusive();
9983
+ return db2;
9984
+ }
9985
+ function getLeasesDb() {
9986
+ if (!db2) {
9987
+ throw new Error("Leases database not initialized. Call initLeasesDb() first.");
9988
+ }
9989
+ return db2;
9990
+ }
9991
+ function closeLeasesDb() {
9992
+ if (db2) {
9993
+ db2.close();
9994
+ db2 = null;
9995
+ }
9996
+ }
9997
+ function createInventory(input4) {
9998
+ const database = getLeasesDb();
9999
+ const row = {
10000
+ slug: input4.slug,
10001
+ kind: input4.kind,
10002
+ display_name: input4.display_name ?? null,
10003
+ default_ttl_s: input4.default_ttl_s,
10004
+ created_at: nowIso()
10005
+ };
10006
+ try {
10007
+ database.prepare(
10008
+ `INSERT INTO inventories (slug, kind, display_name, default_ttl_s, created_at)
10009
+ VALUES (?, ?, ?, ?, ?)`
10010
+ ).run(row.slug, row.kind, row.display_name, row.default_ttl_s, row.created_at);
10011
+ } catch (err2) {
10012
+ if (err2 instanceof Error && err2.message.includes("UNIQUE")) {
10013
+ throw new DuplicateInventoryError(input4.slug);
10014
+ }
10015
+ throw err2;
10016
+ }
10017
+ return row;
10018
+ }
10019
+ function listInventories() {
10020
+ const database = getLeasesDb();
10021
+ return database.prepare(
10022
+ `SELECT slug, kind, display_name, default_ttl_s, created_at
10023
+ FROM inventories ORDER BY slug`
10024
+ ).all();
10025
+ }
10026
+ function getInventoryDetail(slug) {
10027
+ const database = getLeasesDb();
10028
+ const inv = database.prepare(
10029
+ `SELECT slug, kind, display_name, default_ttl_s, created_at
10030
+ FROM inventories WHERE slug = ?`
10031
+ ).get(slug);
10032
+ if (!inv) return null;
10033
+ const members = database.prepare(
10034
+ `SELECT inventory_slug, member_id, status, generation, metadata_json, last_used_at, retired_at
10035
+ FROM inventory_members WHERE inventory_slug = ?
10036
+ ORDER BY member_id`
10037
+ ).all(slug);
10038
+ const active_leases = database.prepare(
10039
+ `SELECT lease_id, inventory_slug, member_id, member_gen, state, granted_at, expires_at, released_at, requested_for
10040
+ FROM leases WHERE inventory_slug = ? AND state = 'active'
10041
+ ORDER BY granted_at`
10042
+ ).all(slug);
10043
+ return { inventory: inv, members, active_leases };
10044
+ }
10045
+ function addMember(input4) {
10046
+ const database = getLeasesDb();
10047
+ const row = {
10048
+ inventory_slug: input4.inventory_slug,
10049
+ member_id: input4.member_id,
10050
+ status: "idle",
10051
+ generation: 0,
10052
+ metadata_json: input4.metadata ? JSON.stringify(input4.metadata) : null,
10053
+ last_used_at: null,
10054
+ retired_at: null
10055
+ };
10056
+ try {
10057
+ database.prepare(
10058
+ `INSERT INTO inventory_members
10059
+ (inventory_slug, member_id, status, generation, metadata_json)
10060
+ VALUES (?, ?, 'idle', 0, ?)`
10061
+ ).run(row.inventory_slug, row.member_id, row.metadata_json);
10062
+ } catch (err2) {
10063
+ if (err2 instanceof Error && err2.message.includes("UNIQUE")) {
10064
+ throw new DuplicateMemberError(input4.inventory_slug, input4.member_id);
10065
+ }
10066
+ throw err2;
10067
+ }
10068
+ return row;
10069
+ }
10070
+ function retireMember(inventorySlug, memberId) {
10071
+ const database = getLeasesDb();
10072
+ const retire = database.transaction(() => {
10073
+ const row = database.prepare(
10074
+ `SELECT status FROM inventory_members
10075
+ WHERE inventory_slug = ? AND member_id = ?`
10076
+ ).get(inventorySlug, memberId);
10077
+ if (!row) throw new NotFoundError(`${inventorySlug}/${memberId}`);
10078
+ if (row.status === "leased") {
10079
+ throw new MemberInUseError(inventorySlug, memberId);
10080
+ }
10081
+ database.prepare(
10082
+ `UPDATE inventory_members
10083
+ SET status = 'retired', generation = generation + 1, retired_at = ?
10084
+ WHERE inventory_slug = ? AND member_id = ?`
10085
+ ).run(nowIso(), inventorySlug, memberId);
10086
+ });
10087
+ retire.immediate();
10088
+ }
10089
+ function claimLease(slug, ttl_s, requested_for) {
10090
+ const database = getLeasesDb();
10091
+ try {
10092
+ const fn = database.transaction(() => {
10093
+ const now = nowIso();
10094
+ database.prepare(
10095
+ `UPDATE leases
10096
+ SET state = 'expired'
10097
+ WHERE state = 'active'
10098
+ AND expires_at <= ?
10099
+ AND inventory_slug = ?`
10100
+ ).run(now, slug);
10101
+ database.prepare(
10102
+ `UPDATE inventory_members
10103
+ SET status = 'idle', generation = generation + 1
10104
+ WHERE inventory_slug = ?
10105
+ AND status = 'leased'
10106
+ AND (inventory_slug, member_id) IN (
10107
+ SELECT inventory_slug, member_id
10108
+ FROM leases
10109
+ WHERE inventory_slug = ?
10110
+ AND state = 'expired'
10111
+ AND released_at IS NULL
10112
+ )
10113
+ AND generation = (
10114
+ SELECT member_gen FROM leases l
10115
+ WHERE l.inventory_slug = inventory_members.inventory_slug
10116
+ AND l.member_id = inventory_members.member_id
10117
+ AND l.state = 'expired'
10118
+ AND l.released_at IS NULL
10119
+ ORDER BY l.granted_at DESC LIMIT 1
10120
+ )`
10121
+ ).run(slug, slug);
10122
+ database.prepare(
10123
+ `UPDATE leases
10124
+ SET released_at = ?
10125
+ WHERE state = 'expired'
10126
+ AND released_at IS NULL
10127
+ AND inventory_slug = ?`
10128
+ ).run(now, slug);
10129
+ const picked = database.prepare(
10130
+ `UPDATE inventory_members
10131
+ SET status = 'leased',
10132
+ generation = generation + 1,
10133
+ last_used_at = ?
10134
+ WHERE (inventory_slug, member_id) = (
10135
+ SELECT inventory_slug, member_id
10136
+ FROM inventory_members
10137
+ WHERE inventory_slug = ? AND status = 'idle'
10138
+ ORDER BY last_used_at ASC NULLS FIRST
10139
+ LIMIT 1
10140
+ )
10141
+ RETURNING member_id, generation, metadata_json`
10142
+ ).get(now, slug);
10143
+ if (!picked) {
10144
+ throw new NoIdleMemberError(slug);
10145
+ }
10146
+ const lease_id = randomUUID2();
10147
+ const expires_at = new Date(Date.now() + ttl_s * 1e3).toISOString();
10148
+ database.prepare(
10149
+ `INSERT INTO leases
10150
+ (lease_id, inventory_slug, member_id, member_gen,
10151
+ state, granted_at, expires_at, requested_for)
10152
+ VALUES (?, ?, ?, ?, 'active', ?, ?, ?)`
10153
+ ).run(
10154
+ lease_id,
10155
+ slug,
10156
+ picked.member_id,
10157
+ picked.generation,
10158
+ now,
10159
+ expires_at,
10160
+ requested_for ?? null
10161
+ );
10162
+ database.prepare(
10163
+ `INSERT INTO lease_events (lease_id, event, at, detail_json)
10164
+ VALUES (?, 'claimed', ?, ?)`
10165
+ ).run(lease_id, now, JSON.stringify({ member_id: picked.member_id }));
10166
+ return {
10167
+ lease_id,
10168
+ inventory_slug: slug,
10169
+ member_id: picked.member_id,
10170
+ member_gen: picked.generation,
10171
+ granted_at: now,
10172
+ expires_at,
10173
+ metadata: picked.metadata_json ? JSON.parse(picked.metadata_json) : null
10174
+ };
10175
+ });
10176
+ return fn.immediate();
10177
+ } catch (err2) {
10178
+ if (isBusyError(err2)) throw new LeaseContentionError(slug);
10179
+ throw err2;
10180
+ }
10181
+ }
10182
+ function releaseLease(lease_id) {
10183
+ const database = getLeasesDb();
10184
+ try {
10185
+ const fn = database.transaction(() => {
10186
+ const now = nowIso();
10187
+ const memberRes = database.prepare(
10188
+ `UPDATE inventory_members
10189
+ SET status = 'idle', generation = generation + 1
10190
+ WHERE (inventory_slug, member_id) = (
10191
+ SELECT inventory_slug, member_id FROM leases
10192
+ WHERE lease_id = ? AND state = 'active'
10193
+ )
10194
+ AND generation = (
10195
+ SELECT member_gen FROM leases
10196
+ WHERE lease_id = ? AND state = 'active'
10197
+ )`
10198
+ ).run(lease_id, lease_id);
10199
+ if (memberRes.changes === 0) {
10200
+ throw new StaleLeaseError(lease_id);
10201
+ }
10202
+ database.prepare(
10203
+ `UPDATE leases
10204
+ SET state = 'released', released_at = ?
10205
+ WHERE lease_id = ? AND state = 'active'`
10206
+ ).run(now, lease_id);
10207
+ database.prepare(
10208
+ `INSERT INTO lease_events (lease_id, event, at)
10209
+ VALUES (?, 'released', ?)`
10210
+ ).run(lease_id, now);
10211
+ });
10212
+ fn.immediate();
10213
+ } catch (err2) {
10214
+ if (isBusyError(err2)) throw new LeaseContentionError(lease_id);
10215
+ throw err2;
10216
+ }
10217
+ }
10218
+ function extendLease(lease_id, ttl_s) {
10219
+ const database = getLeasesDb();
10220
+ try {
10221
+ const fn = database.transaction(() => {
10222
+ const now = nowIso();
10223
+ const new_expires_at = new Date(Date.now() + ttl_s * 1e3).toISOString();
10224
+ const res = database.prepare(
10225
+ `UPDATE leases
10226
+ SET expires_at = ?
10227
+ WHERE lease_id = ?
10228
+ AND state = 'active'
10229
+ AND expires_at > ?
10230
+ AND member_gen = (
10231
+ SELECT generation FROM inventory_members
10232
+ WHERE inventory_slug = leases.inventory_slug
10233
+ AND member_id = leases.member_id
10234
+ )`
10235
+ ).run(new_expires_at, lease_id, now);
10236
+ if (res.changes === 0) {
10237
+ throw new StaleLeaseError(lease_id);
10238
+ }
10239
+ database.prepare(
10240
+ `INSERT INTO lease_events (lease_id, event, at, detail_json)
10241
+ VALUES (?, 'extended', ?, ?)`
10242
+ ).run(lease_id, now, JSON.stringify({ new_expires_at }));
10243
+ return { new_expires_at };
10244
+ });
10245
+ return fn.immediate();
10246
+ } catch (err2) {
10247
+ if (isBusyError(err2)) throw new LeaseContentionError(lease_id);
10248
+ throw err2;
10249
+ }
10250
+ }
10251
+ function getLease(lease_id) {
10252
+ const database = getLeasesDb();
10253
+ const row = database.prepare(
10254
+ `SELECT lease_id, inventory_slug, member_id, member_gen, state,
10255
+ granted_at, expires_at, released_at, requested_for
10256
+ FROM leases WHERE lease_id = ?`
10257
+ ).get(lease_id);
10258
+ return row ?? null;
10259
+ }
10260
+ function listLeases(filter) {
10261
+ const database = getLeasesDb();
10262
+ const where = [];
10263
+ const params2 = [];
10264
+ if (filter?.inventory) {
10265
+ where.push("inventory_slug = ?");
10266
+ params2.push(filter.inventory);
10267
+ }
10268
+ if (filter?.state) {
10269
+ where.push("state = ?");
10270
+ params2.push(filter.state);
10271
+ }
10272
+ const whereSql = where.length > 0 ? `WHERE ${where.join(" AND ")}` : "";
10273
+ return database.prepare(
10274
+ `SELECT lease_id, inventory_slug, member_id, member_gen, state,
10275
+ granted_at, expires_at, released_at, requested_for
10276
+ FROM leases ${whereSql}
10277
+ ORDER BY granted_at DESC`
10278
+ ).all(...params2);
10279
+ }
10280
+ function listMembers(inventory_slug) {
10281
+ const database = getLeasesDb();
10282
+ const inv = database.prepare("SELECT slug FROM inventories WHERE slug = ?").get(inventory_slug);
10283
+ if (!inv) throw new NotFoundError(inventory_slug);
10284
+ return database.prepare(
10285
+ `SELECT inventory_slug, member_id, status, generation, metadata_json, last_used_at, retired_at
10286
+ FROM inventory_members WHERE inventory_slug = ?
10287
+ ORDER BY member_id`
10288
+ ).all(inventory_slug);
10289
+ }
10290
+ function getLeaseEvents(lease_id, limit = 50) {
10291
+ const database = getLeasesDb();
10292
+ if (lease_id) {
10293
+ return database.prepare(
10294
+ `SELECT id, lease_id, event, at, detail_json
10295
+ FROM lease_events WHERE lease_id = ?
10296
+ ORDER BY at ASC, id ASC
10297
+ LIMIT ?`
10298
+ ).all(lease_id, limit);
10299
+ }
10300
+ return database.prepare(
10301
+ `SELECT id, lease_id, event, at, detail_json
10302
+ FROM lease_events
10303
+ ORDER BY at DESC, id DESC
10304
+ LIMIT ?`
10305
+ ).all(limit);
10306
+ }
10307
+ function gcExpiredLeases() {
10308
+ const database = getLeasesDb();
10309
+ try {
10310
+ const fn = database.transaction(() => {
10311
+ const now = nowIso();
10312
+ const expRes = database.prepare(
10313
+ `UPDATE leases SET state = 'expired'
10314
+ WHERE state = 'active' AND expires_at <= ?`
10315
+ ).run(now);
10316
+ database.prepare(
10317
+ `UPDATE inventory_members
10318
+ SET status = 'idle', generation = generation + 1
10319
+ WHERE status = 'leased'
10320
+ AND (inventory_slug, member_id) IN (
10321
+ SELECT inventory_slug, member_id FROM leases
10322
+ WHERE state = 'expired' AND released_at IS NULL
10323
+ )
10324
+ AND generation = (
10325
+ SELECT member_gen FROM leases l
10326
+ WHERE l.inventory_slug = inventory_members.inventory_slug
10327
+ AND l.member_id = inventory_members.member_id
10328
+ AND l.state = 'expired'
10329
+ AND l.released_at IS NULL
10330
+ ORDER BY l.granted_at DESC LIMIT 1
10331
+ )`
10332
+ ).run();
10333
+ database.prepare(
10334
+ `UPDATE leases SET released_at = ?
10335
+ WHERE state = 'expired' AND released_at IS NULL AND expires_at <= ?`
10336
+ ).run(now, now);
10337
+ return expRes.changes;
10338
+ });
10339
+ return fn.immediate();
10340
+ } catch (err2) {
10341
+ if (isBusyError(err2)) throw new LeaseContentionError("gc");
10342
+ throw err2;
10343
+ }
10344
+ }
10345
+ function forceReleaseLease(lease_id) {
10346
+ const database = getLeasesDb();
10347
+ try {
10348
+ const fn = database.transaction(() => {
10349
+ const now = nowIso();
10350
+ const lookup = database.prepare(
10351
+ `SELECT inventory_slug, member_id, member_gen, state
10352
+ FROM leases WHERE lease_id = ?`
10353
+ ).get(lease_id);
10354
+ if (!lookup) throw new NotFoundError(lease_id);
10355
+ if (lookup.state === "revoked") return { member_freed: false };
10356
+ const memberRes = database.prepare(
10357
+ `UPDATE inventory_members
10358
+ SET status = 'idle', generation = generation + 1
10359
+ WHERE inventory_slug = ? AND member_id = ?
10360
+ AND generation = ?`
10361
+ ).run(lookup.inventory_slug, lookup.member_id, lookup.member_gen);
10362
+ database.prepare(
10363
+ `UPDATE leases
10364
+ SET state = 'revoked', released_at = ?
10365
+ WHERE lease_id = ? AND state != 'revoked'`
10366
+ ).run(now, lease_id);
10367
+ const member_freed = memberRes.changes > 0;
10368
+ database.prepare(
10369
+ `INSERT INTO lease_events (lease_id, event, at, detail_json)
10370
+ VALUES (?, 'force_released', ?, ?)`
10371
+ ).run(lease_id, now, JSON.stringify({ member_freed }));
10372
+ return { member_freed };
10373
+ });
10374
+ return fn.immediate();
10375
+ } catch (err2) {
10376
+ if (isBusyError(err2)) throw new LeaseContentionError(lease_id);
10377
+ throw err2;
10378
+ }
10379
+ }
10380
+ function updateInventory(slug, input4) {
10381
+ if ("kind" in input4) {
10382
+ throw new Error("inventory kind is immutable");
10383
+ }
10384
+ if (input4.default_ttl_s === void 0 && input4.display_name === void 0) {
10385
+ throw new Error("nothing to update");
10386
+ }
10387
+ if (input4.default_ttl_s !== void 0 && input4.default_ttl_s <= 0) {
10388
+ throw new Error("default_ttl_s must be positive");
10389
+ }
10390
+ const database = getLeasesDb();
10391
+ const fn = database.transaction(() => {
10392
+ const existing = database.prepare("SELECT slug FROM inventories WHERE slug = ?").get(slug);
10393
+ if (!existing) throw new NotFoundError(slug);
10394
+ const sets = [];
10395
+ const params2 = [];
10396
+ if (input4.default_ttl_s !== void 0) {
10397
+ sets.push("default_ttl_s = ?");
10398
+ params2.push(input4.default_ttl_s);
10399
+ }
10400
+ if (input4.display_name !== void 0) {
10401
+ sets.push("display_name = ?");
10402
+ params2.push(input4.display_name);
10403
+ }
10404
+ params2.push(slug);
10405
+ database.prepare(`UPDATE inventories SET ${sets.join(", ")} WHERE slug = ?`).run(...params2);
10406
+ return database.prepare(
10407
+ `SELECT slug, kind, display_name, default_ttl_s, created_at
10408
+ FROM inventories WHERE slug = ?`
10409
+ ).get(slug);
10410
+ });
10411
+ try {
10412
+ return fn.immediate();
10413
+ } catch (err2) {
10414
+ if (isBusyError(err2)) throw new LeaseContentionError(slug);
10415
+ throw err2;
10416
+ }
10417
+ }
10418
+ function deleteInventory(slug, opts = {}) {
10419
+ const database = getLeasesDb();
10420
+ let revoked = 0;
10421
+ const fn = database.transaction(() => {
10422
+ const existing = database.prepare("SELECT slug FROM inventories WHERE slug = ?").get(slug);
10423
+ if (!existing) throw new NotFoundError(slug);
10424
+ const activeLeases = database.prepare(
10425
+ `SELECT lease_id FROM leases
10426
+ WHERE inventory_slug = ? AND state = 'active'`
10427
+ ).all(slug);
10428
+ if (activeLeases.length > 0 && !opts.force) {
10429
+ throw new MemberInUseError(slug, "*");
10430
+ }
10431
+ revoked = activeLeases.length;
10432
+ database.prepare(
10433
+ `DELETE FROM lease_events
10434
+ WHERE lease_id IN (SELECT lease_id FROM leases WHERE inventory_slug = ?)`
10435
+ ).run(slug);
10436
+ database.prepare("DELETE FROM leases WHERE inventory_slug = ?").run(slug);
10437
+ database.prepare("DELETE FROM inventory_members WHERE inventory_slug = ?").run(slug);
10438
+ database.prepare("DELETE FROM inventories WHERE slug = ?").run(slug);
10439
+ });
10440
+ try {
10441
+ fn.immediate();
10442
+ } catch (err2) {
10443
+ if (isBusyError(err2)) throw new LeaseContentionError(slug);
10444
+ throw err2;
10445
+ }
10446
+ return { deleted: true, revoked };
10447
+ }
10448
+ function releaseLeasesByRequestedFor(tag) {
10449
+ const database = getLeasesDb();
10450
+ const rows = database.prepare(
10451
+ `SELECT lease_id FROM leases
10452
+ WHERE state = 'active' AND requested_for = ?`
10453
+ ).all(tag);
10454
+ const released = [];
10455
+ const stale = [];
10456
+ for (const { lease_id } of rows) {
10457
+ try {
10458
+ releaseLease(lease_id);
10459
+ released.push(lease_id);
10460
+ } catch (err2) {
10461
+ if (err2 instanceof StaleLeaseError) {
10462
+ stale.push(lease_id);
10463
+ continue;
10464
+ }
10465
+ throw err2;
10466
+ }
10467
+ }
10468
+ return { released, stale };
10469
+ }
10470
+
10471
+ // src/dashboard/api-leases.ts
10472
+ function createLeasesRouter(broadcast) {
10473
+ const router = Router4();
10474
+ router.get("/", (_req, res) => {
10475
+ try {
10476
+ initLeasesDb();
10477
+ const inventories = listInventories();
10478
+ const details = inventories.map((inv) => getInventoryDetail(inv.slug)).filter((d) => d !== null);
10479
+ res.json({ inventories: details });
10480
+ } catch (error) {
10481
+ res.status(500).json({
10482
+ error: error instanceof Error ? error.message : "List failed"
10483
+ });
10484
+ }
10485
+ });
10486
+ router.get("/:slug", (req, res) => {
10487
+ try {
10488
+ initLeasesDb();
10489
+ const detail = getInventoryDetail(req.params.slug);
10490
+ if (!detail) {
10491
+ res.status(404).json({ error: "Inventory not found" });
10492
+ return;
10493
+ }
10494
+ res.json(detail);
10495
+ } catch (error) {
10496
+ res.status(500).json({
10497
+ error: error instanceof Error ? error.message : "Read failed"
10498
+ });
10499
+ }
10500
+ });
10501
+ router.post("/:slug/force-release/:lease_id", (req, res) => {
10502
+ try {
10503
+ initLeasesDb();
10504
+ const result = forceReleaseLease(req.params.lease_id);
10505
+ broadcast?.({
10506
+ type: "leases-updated",
10507
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
10508
+ });
10509
+ res.json({ ...result, lease_id: req.params.lease_id });
10510
+ } catch (error) {
10511
+ if (error instanceof NotFoundError) {
10512
+ res.status(404).json({ error: "Lease not found" });
10513
+ return;
10514
+ }
10515
+ if (error instanceof LeaseContentionError) {
10516
+ res.status(503).json({ error: "Contention timeout; retry" });
10517
+ return;
10518
+ }
10519
+ res.status(500).json({
10520
+ error: error instanceof Error ? error.message : "Force release failed"
10521
+ });
10522
+ }
10523
+ });
10524
+ return router;
10525
+ }
10526
+
9747
10527
  // src/dashboard/api-playbooks.ts
9748
10528
  init_api();
9749
10529
  init_parser();
@@ -9752,11 +10532,11 @@ init_timestamp();
9752
10532
  init_fs();
9753
10533
  init_playbook();
9754
10534
  init_playbooks();
9755
- import { Router as Router4 } from "express";
9756
- import { resolve as resolve17 } from "path";
10535
+ import { Router as Router5 } from "express";
10536
+ import { resolve as resolve18 } from "path";
9757
10537
  import { readFile as readFile12, unlink as unlink2 } from "fs/promises";
9758
10538
  function createPlaybooksRouter(playbooksDir3) {
9759
- const router = Router4();
10539
+ const router = Router5();
9760
10540
  router.get("/", async (_req, res) => {
9761
10541
  try {
9762
10542
  const playbooks = await listPlaybooks(playbooksDir3);
@@ -9823,7 +10603,7 @@ function createPlaybooksRouter(playbooksDir3) {
9823
10603
  res.status(404).json({ error: `Playbook "${req.params.slug}" not found` });
9824
10604
  return;
9825
10605
  }
9826
- const filePath = resolve17(playbooksDir3, resolved.filename);
10606
+ const filePath = resolve18(playbooksDir3, resolved.filename);
9827
10607
  const content = await readFile12(filePath, "utf-8");
9828
10608
  res.json({
9829
10609
  documentType: "playbook",
@@ -9849,7 +10629,7 @@ function createPlaybooksRouter(playbooksDir3) {
9849
10629
  return;
9850
10630
  }
9851
10631
  await ensureDir(playbooksDir3);
9852
- const filePath = resolve17(playbooksDir3, `${slug}.md`);
10632
+ const filePath = resolve18(playbooksDir3, `${slug}.md`);
9853
10633
  if (await fileExists(filePath)) {
9854
10634
  res.status(409).json({ error: `Playbook "${slug}" already exists` });
9855
10635
  return;
@@ -9873,7 +10653,7 @@ function createPlaybooksRouter(playbooksDir3) {
9873
10653
  res.status(404).json({ error: `Playbook "${req.params.slug}" not found` });
9874
10654
  return;
9875
10655
  }
9876
- const filePath = resolve17(playbooksDir3, resolved.filename);
10656
+ const filePath = resolve18(playbooksDir3, resolved.filename);
9877
10657
  await writeFileForce(filePath, content);
9878
10658
  await rebuildPlaybookManifest(playbooksDir3);
9879
10659
  res.json({ slug: resolved.slug, path: filePath });
@@ -9892,7 +10672,7 @@ function createPlaybooksRouter(playbooksDir3) {
9892
10672
  res.status(404).json({ error: `Playbook "${req.params.slug}" not found` });
9893
10673
  return;
9894
10674
  }
9895
- const filePath = resolve17(playbooksDir3, resolved.filename);
10675
+ const filePath = resolve18(playbooksDir3, resolved.filename);
9896
10676
  await unlink2(filePath);
9897
10677
  await removeFromDisabledList(resolved.slug);
9898
10678
  await rebuildPlaybookManifest(playbooksDir3);
@@ -9911,9 +10691,9 @@ init_fs_migration();
9911
10691
  init_parser2();
9912
10692
  init_fs();
9913
10693
  init_paths();
9914
- import { Router as Router5 } from "express";
10694
+ import { Router as Router6 } from "express";
9915
10695
  import { readdir as readdir9 } from "fs/promises";
9916
- import { resolve as resolvePath, dirname as dirname4 } from "path";
10696
+ import { resolve as resolvePath, dirname as dirname5 } from "path";
9917
10697
  import { rename as rename3, mkdir as mkdir2 } from "fs/promises";
9918
10698
 
9919
10699
  // src/dashboard/todos-locks.ts
@@ -9959,7 +10739,7 @@ function touchItem(item) {
9959
10739
  item.updatedAt = now;
9960
10740
  }
9961
10741
  function createTodosRouter(todosDir2, broadcast, projectsDir2) {
9962
- const router = Router5();
10742
+ const router = Router6();
9963
10743
  function broadcastUpdate() {
9964
10744
  broadcast({ type: "todos-updated", timestamp: (/* @__PURE__ */ new Date()).toISOString() });
9965
10745
  }
@@ -10089,7 +10869,7 @@ function createTodosRouter(todosDir2, broadcast, projectsDir2) {
10089
10869
  router.post("/:workspace/archive", async (req, res) => {
10090
10870
  try {
10091
10871
  const { archivePath: archivePath2 } = await Promise.resolve().then(() => (init_parser2(), parser_exports));
10092
- const { resolve: resolve62 } = await import("path");
10872
+ const { resolve: resolve63 } = await import("path");
10093
10873
  const { readFile: readFile43 } = await import("fs/promises");
10094
10874
  const { writeFileForce: writeFileForce2 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
10095
10875
  const workspace = getWorkspaceParam(req.params.workspace);
@@ -10106,7 +10886,7 @@ function createTodosRouter(todosDir2, broadcast, projectsDir2) {
10106
10886
  (e) => e.itemIds.every((id) => completedIds.has(id))
10107
10887
  );
10108
10888
  const archFile = archivePath2(todosDir2, workspace, checklist.archiveInterval);
10109
- await ensureDir(resolve62(todosDir2, "archive"));
10889
+ await ensureDir(resolve63(todosDir2, "archive"));
10110
10890
  let archContent = "";
10111
10891
  if (await fileExists(archFile)) {
10112
10892
  archContent = await readFile43(archFile, "utf-8");
@@ -10513,7 +11293,7 @@ workspace: ${workspace}
10513
11293
  if (await fileExists(newPlanDir)) {
10514
11294
  return { status: 409, error: "plan dir already exists in target" };
10515
11295
  }
10516
- await mkdir2(dirname4(newPlanDir), { recursive: true });
11296
+ await mkdir2(dirname5(newPlanDir), { recursive: true });
10517
11297
  await rename3(item.planDir, newPlanDir);
10518
11298
  item.planDir = newPlanDir;
10519
11299
  }
@@ -10589,9 +11369,9 @@ init_parser2();
10589
11369
  init_fs();
10590
11370
  init_paths();
10591
11371
  init_slug();
10592
- import { Router as Router6 } from "express";
11372
+ import { Router as Router7 } from "express";
10593
11373
  import { mkdir as mkdir3, readFile as readFile14, rename as rename4 } from "fs/promises";
10594
- import { resolve as resolve19, dirname as dirname5 } from "path";
11374
+ import { resolve as resolve20, dirname as dirname6 } from "path";
10595
11375
  var WORKSPACE_REGEX2 = /^[a-z0-9_][a-z0-9-]*$/;
10596
11376
  function touchItem2(item) {
10597
11377
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -10606,7 +11386,7 @@ function params(req) {
10606
11386
  return req.params;
10607
11387
  }
10608
11388
  async function projectExists(projectsDir2, slug) {
10609
- return fileExists(resolve19(projectsDir2, slug, "project.md"));
11389
+ return fileExists(resolve20(projectsDir2, slug, "project.md"));
10610
11390
  }
10611
11391
  async function ensureProjectTodosDir(projectsDir2, slug) {
10612
11392
  const todosDir2 = projectTodosDir(projectsDir2, slug);
@@ -10623,7 +11403,7 @@ async function ensureProjectTodosDir(projectsDir2, slug) {
10623
11403
  throw err2;
10624
11404
  }
10625
11405
  try {
10626
- await mkdir3(resolve19(todosDir2, "archive"), { recursive: false });
11406
+ await mkdir3(resolve20(todosDir2, "archive"), { recursive: false });
10627
11407
  } catch (err2) {
10628
11408
  const code = err2.code;
10629
11409
  if (code === "EEXIST") return;
@@ -10639,7 +11419,7 @@ function notFound(res, slug) {
10639
11419
  res.status(404).json({ error: `Project "${slug}" not found` });
10640
11420
  }
10641
11421
  function createProjectTodosRouter(projectsDir2, broadcast, workspaceTodosDir) {
10642
- const router = Router6({ mergeParams: true });
11422
+ const router = Router7({ mergeParams: true });
10643
11423
  function broadcastUpdate(projectSlug) {
10644
11424
  broadcast({ type: "todos-updated", projectSlug, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
10645
11425
  }
@@ -11247,18 +12027,18 @@ workspace: ${slug}
11247
12027
  if (tg.includes("/")) {
11248
12028
  const parts = tg.split("/");
11249
12029
  if (parts.length !== 2) return { error: `Invalid target.assignment "${tg}"` };
11250
- assignmentDir = resolve19(projectsDir2, parts[0], "assignments", parts[1]);
12030
+ assignmentDir = resolve20(projectsDir2, parts[0], "assignments", parts[1]);
11251
12031
  assignmentRef = `${parts[0]}/${parts[1]}`;
11252
12032
  } else if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(tg)) {
11253
- assignmentDir = resolve19(assignmentsDirFn(), tg);
12033
+ assignmentDir = resolve20(assignmentsDirFn(), tg);
11254
12034
  assignmentRef = tg;
11255
12035
  } else {
11256
12036
  return { error: `Invalid target.assignment "${tg}"` };
11257
12037
  }
11258
- const assignmentMdPath3 = resolve19(assignmentDir, "assignment.md");
12038
+ const assignmentMdPath3 = resolve20(assignmentDir, "assignment.md");
11259
12039
  if (!await fileExists(assignmentMdPath3)) return { error: `Target assignment not found: ${assignmentMdPath3}` };
11260
12040
  }
11261
- const assignmentMdPath2 = resolve19(assignmentDir, "assignment.md");
12041
+ const assignmentMdPath2 = resolve20(assignmentDir, "assignment.md");
11262
12042
  let content = await readFile14(assignmentMdPath2, "utf-8");
11263
12043
  content = appendTodosToAssignmentBody2(
11264
12044
  content,
@@ -11388,7 +12168,7 @@ workspace: ${slug}
11388
12168
  if (await fileExists(newPlanDir)) {
11389
12169
  return { status: 409, error: "plan dir already exists in target" };
11390
12170
  }
11391
- await mkdir3(dirname5(newPlanDir), { recursive: true });
12171
+ await mkdir3(dirname6(newPlanDir), { recursive: true });
11392
12172
  await rename4(item.planDir, newPlanDir);
11393
12173
  item.planDir = newPlanDir;
11394
12174
  }
@@ -11451,7 +12231,7 @@ workspace: ${slug}
11451
12231
 
11452
12232
  // src/dashboard/api-backup.ts
11453
12233
  init_config2();
11454
- import { Router as Router7 } from "express";
12234
+ import { Router as Router8 } from "express";
11455
12235
 
11456
12236
  // src/utils/github-backup.ts
11457
12237
  init_paths();
@@ -11459,8 +12239,8 @@ init_fs();
11459
12239
  init_config2();
11460
12240
  import { execFile as execFile2 } from "child_process";
11461
12241
  import { promisify as promisify2 } from "util";
11462
- import { cp, mkdtemp, rm as rm2, readFile as readFile15, writeFile as writeFile4, unlink as unlink3, stat, open, rename as rename5 } from "fs/promises";
11463
- import { resolve as resolve20, join as join2 } from "path";
12242
+ import { cp, mkdtemp, rm as rm2, readFile as readFile15, writeFile as writeFile4, unlink as unlink3, stat, open as open2, rename as rename5 } from "fs/promises";
12243
+ import { resolve as resolve21, join as join2 } from "path";
11464
12244
  import { tmpdir } from "os";
11465
12245
  var exec2 = promisify2(execFile2);
11466
12246
  var VALID_CATEGORIES = ["projects", "playbooks", "todos", "servers", "config"];
@@ -11500,7 +12280,7 @@ async function resolveCategoryPath(category) {
11500
12280
  case "servers":
11501
12281
  return { sourcePath: serversDir(), repoPath: "servers", isFile: false };
11502
12282
  case "config":
11503
- return { sourcePath: resolve20(syntaurRoot(), "config.md"), repoPath: "config.md", isFile: true };
12283
+ return { sourcePath: resolve21(syntaurRoot(), "config.md"), repoPath: "config.md", isFile: true };
11504
12284
  }
11505
12285
  }
11506
12286
  async function checkGitInstalled() {
@@ -11511,10 +12291,10 @@ async function checkGitInstalled() {
11511
12291
  }
11512
12292
  }
11513
12293
  async function acquireLock() {
11514
- const lockPath = resolve20(syntaurRoot(), LOCK_FILE_NAME);
12294
+ const lockPath = resolve21(syntaurRoot(), LOCK_FILE_NAME);
11515
12295
  await ensureDir(syntaurRoot());
11516
12296
  try {
11517
- const handle = await open(lockPath, "wx");
12297
+ const handle = await open2(lockPath, "wx");
11518
12298
  await handle.write(String(process.pid));
11519
12299
  await handle.close();
11520
12300
  return lockPath;
@@ -11558,7 +12338,7 @@ async function copyRecursive(src, dest) {
11558
12338
  await ensureDir(dest);
11559
12339
  await cp(src, dest, { recursive: true, force: true });
11560
12340
  } else {
11561
- await ensureDir(resolve20(dest, ".."));
12341
+ await ensureDir(resolve21(dest, ".."));
11562
12342
  await cp(src, dest, { force: true });
11563
12343
  }
11564
12344
  }
@@ -11606,7 +12386,7 @@ async function backupToGithub(overrides) {
11606
12386
  }
11607
12387
  if (category === "config") {
11608
12388
  const sanitized = await readSanitizedConfig(sourcePath);
11609
- await ensureDir(resolve20(destPath, ".."));
12389
+ await ensureDir(resolve21(destPath, ".."));
11610
12390
  await writeFile4(destPath, sanitized, "utf-8");
11611
12391
  } else {
11612
12392
  await copyRecursive(sourcePath, destPath);
@@ -11660,7 +12440,7 @@ async function backupToGithub(overrides) {
11660
12440
  }
11661
12441
  async function safeRestoreCategory(localPath, repoSrcPath, isFile) {
11662
12442
  if (isFile) {
11663
- await ensureDir(resolve20(localPath, ".."));
12443
+ await ensureDir(resolve21(localPath, ".."));
11664
12444
  await cp(repoSrcPath, localPath, { force: true });
11665
12445
  return;
11666
12446
  }
@@ -11761,7 +12541,7 @@ async function restoreFromGithub(overrides) {
11761
12541
  }
11762
12542
  async function getBackupStatus() {
11763
12543
  const config = await readConfig();
11764
- const lockPath = resolve20(syntaurRoot(), LOCK_FILE_NAME);
12544
+ const lockPath = resolve21(syntaurRoot(), LOCK_FILE_NAME);
11765
12545
  const locked = await fileExists(lockPath);
11766
12546
  return {
11767
12547
  repo: config.backup?.repo ?? null,
@@ -11774,7 +12554,7 @@ async function getBackupStatus() {
11774
12554
 
11775
12555
  // src/dashboard/api-backup.ts
11776
12556
  function createBackupRouter() {
11777
- const router = Router7();
12557
+ const router = Router8();
11778
12558
  router.get("/", async (_req, res) => {
11779
12559
  try {
11780
12560
  const status = await getBackupStatus();
@@ -12095,10 +12875,11 @@ function createDashboardServer(options) {
12095
12875
  migrateFromMarkdown(projectsDir2).catch((err2) => {
12096
12876
  console.error("Session migration from markdown failed:", err2);
12097
12877
  });
12878
+ initLeasesDb();
12098
12879
  (async () => {
12099
12880
  try {
12100
12881
  const configResult = await migrateLegacyConfig(
12101
- resolve21(syntaurRoot(), "config.md")
12882
+ resolve22(syntaurRoot(), "config.md")
12102
12883
  );
12103
12884
  const projectResult = await migrateLegacyProjectFiles(projectsDir2);
12104
12885
  const summary = summarizeMigration(projectResult, configResult);
@@ -12428,6 +13209,7 @@ function createDashboardServer(options) {
12428
13209
  });
12429
13210
  app.use(createWriteRouter(projectsDir2, assignmentsDir2));
12430
13211
  app.use("/api/servers", createServersRouter(serversDir2, projectsDir2, assignmentsDir2));
13212
+ app.use("/api/leases", createLeasesRouter(broadcast));
12431
13213
  app.use("/api/agent-sessions", createAgentSessionsRouter(projectsDir2, broadcast, assignmentsDir2));
12432
13214
  app.use("/api/playbooks", createPlaybooksRouter(playbooksDir3));
12433
13215
  app.get("/api/memories", async (_req, res) => {
@@ -12453,14 +13235,14 @@ function createDashboardServer(options) {
12453
13235
  app.use("/api/backup", createBackupRouter());
12454
13236
  if (serveStaticUi && dashboardDistPath) {
12455
13237
  const sendOpts = { dotfiles: "allow" };
12456
- app.use("/assets", express.static(resolve21(dashboardDistPath, "assets"), sendOpts));
13238
+ app.use("/assets", express.static(resolve22(dashboardDistPath, "assets"), sendOpts));
12457
13239
  app.use(express.static(dashboardDistPath, { ...sendOpts, index: false, fallthrough: true }));
12458
13240
  app.get("{*path}", async (req, res) => {
12459
13241
  if (req.path.startsWith("/api") || req.path === "/ws" || req.path.startsWith("/assets")) {
12460
13242
  res.status(404).json({ error: "Not Found" });
12461
13243
  return;
12462
13244
  }
12463
- const indexPath = resolve21(dashboardDistPath, "index.html");
13245
+ const indexPath = resolve22(dashboardDistPath, "index.html");
12464
13246
  if (!await fileExists(indexPath)) {
12465
13247
  res.status(503).send(
12466
13248
  'Dashboard not built. Run "npm run build:dashboard" first.'
@@ -12484,6 +13266,7 @@ function createDashboardServer(options) {
12484
13266
  serversDir: serversDir2,
12485
13267
  playbooksDir: playbooksDir3,
12486
13268
  todosDir: todosDir2,
13269
+ dbPath: resolve22(syntaurRoot(), "syntaur.db"),
12487
13270
  onMessage: broadcast
12488
13271
  });
12489
13272
  startAutodiscovery({ serversDir: serversDir2, projectsDir: projectsDir2, assignmentsDir: assignmentsDir2, excludePids: /* @__PURE__ */ new Set([process.pid]) });
@@ -12498,7 +13281,7 @@ function createDashboardServer(options) {
12498
13281
  }
12499
13282
  });
12500
13283
  server.listen(port, () => {
12501
- const portFile = resolve21(syntaurRoot(), "dashboard-port");
13284
+ const portFile = resolve22(syntaurRoot(), "dashboard-port");
12502
13285
  writeFile5(portFile, String(port), "utf-8").catch(() => {
12503
13286
  });
12504
13287
  resolvePromise();
@@ -12511,11 +13294,12 @@ function createDashboardServer(options) {
12511
13294
  await watcherHandle.close();
12512
13295
  }
12513
13296
  closeSessionDb();
13297
+ closeLeasesDb();
12514
13298
  for (const client of clients) {
12515
13299
  client.terminate();
12516
13300
  }
12517
13301
  clients.clear();
12518
- const portFile = resolve21(syntaurRoot(), "dashboard-port");
13302
+ const portFile = resolve22(syntaurRoot(), "dashboard-port");
12519
13303
  await unlink4(portFile).catch(() => {
12520
13304
  });
12521
13305
  server.closeAllConnections?.();
@@ -12595,8 +13379,8 @@ async function dashboardCommand(options) {
12595
13379
  port = availablePort;
12596
13380
  }
12597
13381
  const thisFile = fileURLToPath2(import.meta.url);
12598
- const packageRoot = resolve22(dirname6(thisFile), "..");
12599
- const dashboardDist = resolve22(packageRoot, "dashboard", "dist");
13382
+ const packageRoot = resolve23(dirname7(thisFile), "..");
13383
+ const dashboardDist = resolve23(packageRoot, "dashboard", "dist");
12600
13384
  const server = createDashboardServer({
12601
13385
  port,
12602
13386
  projectsDir: projectsDir2,
@@ -12610,8 +13394,8 @@ async function dashboardCommand(options) {
12610
13394
  await server.start();
12611
13395
  let viteProcess = null;
12612
13396
  if (mode === "dev") {
12613
- const dashboardDir = resolve22(packageRoot, "dashboard");
12614
- const viteBin = resolve22(dashboardDir, "node_modules", ".bin", "vite");
13397
+ const dashboardDir = resolve23(packageRoot, "dashboard");
13398
+ const viteBin = resolve23(dashboardDir, "node_modules", ".bin", "vite");
12615
13399
  if (!await fileExists(viteBin)) {
12616
13400
  console.error(
12617
13401
  'Vite not found. Run "npm ci --prefix dashboard" first, or use the default bundled dashboard mode.'
@@ -12686,7 +13470,7 @@ init_config2();
12686
13470
  init_slug();
12687
13471
  init_lifecycle();
12688
13472
  init_assignment_resolver();
12689
- import { resolve as resolve23 } from "path";
13473
+ import { resolve as resolve24 } from "path";
12690
13474
  async function runTransition(assignment, command, options = {}) {
12691
13475
  const config = await readConfig();
12692
13476
  const baseDir = options.dir ? expandHome(options.dir) : config.defaultProjectDir;
@@ -12697,8 +13481,8 @@ async function runTransition(assignment, command, options = {}) {
12697
13481
  if (!isValidSlug(assignment)) {
12698
13482
  throw new Error(`Invalid assignment slug "${assignment}".`);
12699
13483
  }
12700
- const projectDir = resolve23(baseDir, options.project);
12701
- const projectMdPath = resolve23(projectDir, "project.md");
13484
+ const projectDir = resolve24(baseDir, options.project);
13485
+ const projectMdPath = resolve24(projectDir, "project.md");
12702
13486
  if (!await fileExists(projectDir) || !await fileExists(projectMdPath)) {
12703
13487
  throw new Error(`Project "${options.project}" not found at ${projectDir}.`);
12704
13488
  }
@@ -12729,8 +13513,8 @@ async function runAssign(assignment, agent, options = {}) {
12729
13513
  if (!isValidSlug(assignment)) {
12730
13514
  throw new Error(`Invalid assignment slug "${assignment}".`);
12731
13515
  }
12732
- const projectDir = resolve23(baseDir, options.project);
12733
- const projectMdPath = resolve23(projectDir, "project.md");
13516
+ const projectDir = resolve24(baseDir, options.project);
13517
+ const projectMdPath = resolve24(projectDir, "project.md");
12734
13518
  if (!await fileExists(projectDir) || !await fileExists(projectMdPath)) {
12735
13519
  throw new Error(`Project "${options.project}" not found at ${projectDir}.`);
12736
13520
  }
@@ -12825,20 +13609,20 @@ import {
12825
13609
  } from "fs/promises";
12826
13610
  import { existsSync } from "fs";
12827
13611
  import { homedir as homedir2 } from "os";
12828
- import { basename as basename3, dirname as dirname8, isAbsolute as isAbsolute2, relative as relative2, resolve as resolve25 } from "path";
13612
+ import { basename as basename4, dirname as dirname9, isAbsolute as isAbsolute2, relative as relative2, resolve as resolve26 } from "path";
12829
13613
 
12830
13614
  // src/utils/package-root.ts
12831
13615
  init_fs();
12832
- import { dirname as dirname7, resolve as resolve24 } from "path";
13616
+ import { dirname as dirname8, resolve as resolve25 } from "path";
12833
13617
  import { fileURLToPath as fileURLToPath3 } from "url";
12834
13618
  async function findPackageRoot(expectedRelativePath) {
12835
- let currentDir = dirname7(fileURLToPath3(import.meta.url));
13619
+ let currentDir = dirname8(fileURLToPath3(import.meta.url));
12836
13620
  while (true) {
12837
- const candidate = resolve24(currentDir, expectedRelativePath);
13621
+ const candidate = resolve25(currentDir, expectedRelativePath);
12838
13622
  if (await fileExists(candidate)) {
12839
13623
  return currentDir;
12840
13624
  }
12841
- const parentDir = resolve24(currentDir, "..");
13625
+ const parentDir = resolve25(currentDir, "..");
12842
13626
  if (parentDir === currentDir) {
12843
13627
  throw new Error(
12844
13628
  `Could not locate package root containing ${expectedRelativePath}.`
@@ -12859,25 +13643,25 @@ function getPluginManifestRelativePath(pluginKind) {
12859
13643
  }
12860
13644
  function getDefaultPluginTargetDir(pluginKind) {
12861
13645
  const home = homedir2();
12862
- return pluginKind === "claude" ? resolve25(home, ".claude", "plugins", "syntaur") : resolve25(home, "plugins", "syntaur");
13646
+ return pluginKind === "claude" ? resolve26(home, ".claude", "plugins", "syntaur") : resolve26(home, "plugins", "syntaur");
12863
13647
  }
12864
13648
  function getDefaultMarketplacePath() {
12865
- return resolve25(homedir2(), ".agents", "plugins", "marketplace.json");
13649
+ return resolve26(homedir2(), ".agents", "plugins", "marketplace.json");
12866
13650
  }
12867
13651
  function getClaudeMarketplacesRoot() {
12868
- return resolve25(homedir2(), ".claude", "plugins", "marketplaces");
13652
+ return resolve26(homedir2(), ".claude", "plugins", "marketplaces");
12869
13653
  }
12870
13654
  function getClaudeKnownMarketplacesPath() {
12871
- return resolve25(homedir2(), ".claude", "plugins", "known_marketplaces.json");
13655
+ return resolve26(homedir2(), ".claude", "plugins", "known_marketplaces.json");
12872
13656
  }
12873
13657
  function getClaudeInstalledPluginsPath() {
12874
- return resolve25(homedir2(), ".claude", "plugins", "installed_plugins.json");
13658
+ return resolve26(homedir2(), ".claude", "plugins", "installed_plugins.json");
12875
13659
  }
12876
13660
  function getInstallMarkerPath(targetDir) {
12877
- return resolve25(targetDir, INSTALL_MARKER_FILENAME);
13661
+ return resolve26(targetDir, INSTALL_MARKER_FILENAME);
12878
13662
  }
12879
13663
  async function readPackageManifest(packageRoot) {
12880
- const raw = await readFile16(resolve25(packageRoot, "package.json"), "utf-8");
13664
+ const raw = await readFile16(resolve26(packageRoot, "package.json"), "utf-8");
12881
13665
  return JSON.parse(raw);
12882
13666
  }
12883
13667
  async function readJsonFileIfExists(pathValue) {
@@ -12893,11 +13677,11 @@ async function readJsonFileIfExists(pathValue) {
12893
13677
  }
12894
13678
  async function readClaudePluginManifest(pluginDir) {
12895
13679
  return await readJsonFileIfExists(
12896
- resolve25(pluginDir, ".claude-plugin", "plugin.json")
13680
+ resolve26(pluginDir, ".claude-plugin", "plugin.json")
12897
13681
  ) ?? {};
12898
13682
  }
12899
13683
  async function readPluginManifestName(targetDir, pluginKind) {
12900
- const manifestPath = resolve25(targetDir, getPluginManifestRelativePath(pluginKind));
13684
+ const manifestPath = resolve26(targetDir, getPluginManifestRelativePath(pluginKind));
12901
13685
  if (!await fileExists(manifestPath)) {
12902
13686
  return void 0;
12903
13687
  }
@@ -12924,7 +13708,7 @@ async function getInstallStatus(targetDir, pluginKind) {
12924
13708
  const info = await lstat(targetDir);
12925
13709
  if (info.isSymbolicLink()) {
12926
13710
  const symlinkTarget = await readlink(targetDir);
12927
- const resolvedTarget = resolve25(dirname8(targetDir), symlinkTarget);
13711
+ const resolvedTarget = resolve26(dirname9(targetDir), symlinkTarget);
12928
13712
  const manifestName2 = await readPluginManifestName(resolvedTarget, pluginKind);
12929
13713
  return {
12930
13714
  exists: true,
@@ -12961,16 +13745,16 @@ async function writeInstallMetadata(targetDir, pluginKind, installMode, packageM
12961
13745
  );
12962
13746
  }
12963
13747
  async function installCopy(paths, pluginKind) {
12964
- await ensureDir(dirname8(paths.targetDir));
13748
+ await ensureDir(dirname9(paths.targetDir));
12965
13749
  await cp2(paths.sourceDir, paths.targetDir, { recursive: true });
12966
13750
  const packageManifest = await readPackageManifest(paths.packageRoot);
12967
13751
  await writeInstallMetadata(paths.targetDir, pluginKind, "copy", packageManifest);
12968
13752
  }
12969
13753
  async function installLink(paths) {
12970
- await ensureDir(dirname8(paths.targetDir));
13754
+ await ensureDir(dirname9(paths.targetDir));
12971
13755
  await rm3(paths.targetDir, { recursive: true, force: true });
12972
- await ensureDir(dirname8(paths.targetDir));
12973
- await symlink(resolve25(paths.sourceDir), paths.targetDir, "dir");
13756
+ await ensureDir(dirname9(paths.targetDir));
13757
+ await symlink(resolve26(paths.sourceDir), paths.targetDir, "dir");
12974
13758
  }
12975
13759
  async function removeInstallMarker(targetDir) {
12976
13760
  const markerPath = getInstallMarkerPath(targetDir);
@@ -12984,13 +13768,13 @@ function normalizeAbsoluteInstallPath(pathValue, label) {
12984
13768
  if (!isAbsolute2(expanded)) {
12985
13769
  throw new Error(`${label} must be an absolute path.`);
12986
13770
  }
12987
- return resolve25(expanded);
13771
+ return resolve26(expanded);
12988
13772
  }
12989
13773
  async function resolvePluginPaths(pluginKind, targetDir) {
12990
13774
  const packageRoot = await findPackageRoot(getPluginRelativePath(pluginKind));
12991
13775
  return {
12992
13776
  packageRoot,
12993
- sourceDir: resolve25(packageRoot, getPluginRelativePath(pluginKind)),
13777
+ sourceDir: resolve26(packageRoot, getPluginRelativePath(pluginKind)),
12994
13778
  targetDir: targetDir ?? getDefaultPluginTargetDir(pluginKind)
12995
13779
  };
12996
13780
  }
@@ -13024,7 +13808,7 @@ async function readClaudeMarketplaceFile(manifestPath) {
13024
13808
  };
13025
13809
  }
13026
13810
  async function writeClaudeMarketplaceFile(manifestPath, marketplace) {
13027
- await ensureDir(dirname8(manifestPath));
13811
+ await ensureDir(dirname9(manifestPath));
13028
13812
  if (Array.isArray(marketplace.plugins)) {
13029
13813
  marketplace.plugins = [...marketplace.plugins].sort((a, b) => {
13030
13814
  const an = a?.name ?? "";
@@ -13115,8 +13899,8 @@ async function listClaudeMarketplaceCandidates() {
13115
13899
  if (!entry.isDirectory()) {
13116
13900
  continue;
13117
13901
  }
13118
- const candidateRoot = resolve25(rootDir, entry.name);
13119
- const manifestPath = resolve25(candidateRoot, ".claude-plugin", "marketplace.json");
13902
+ const candidateRoot = resolve26(rootDir, entry.name);
13903
+ const manifestPath = resolve26(candidateRoot, ".claude-plugin", "marketplace.json");
13120
13904
  if (!await fileExists(manifestPath)) {
13121
13905
  continue;
13122
13906
  }
@@ -13143,11 +13927,11 @@ async function listClaudeMarketplaceCandidates() {
13143
13927
  if (!installLocation) {
13144
13928
  continue;
13145
13929
  }
13146
- const candidateRoot = resolve25(expandHome(installLocation));
13930
+ const candidateRoot = resolve26(expandHome(installLocation));
13147
13931
  if (seen.has(candidateRoot)) {
13148
13932
  continue;
13149
13933
  }
13150
- const manifestPath = resolve25(candidateRoot, ".claude-plugin", "marketplace.json");
13934
+ const manifestPath = resolve26(candidateRoot, ".claude-plugin", "marketplace.json");
13151
13935
  if (!await fileExists(manifestPath)) {
13152
13936
  continue;
13153
13937
  }
@@ -13175,7 +13959,7 @@ async function getPreferredClaudeMarketplace() {
13175
13959
  name: candidate.name,
13176
13960
  rootDir: candidate.rootDir,
13177
13961
  manifestPath: candidate.manifestPath,
13178
- targetDir: resolve25(candidate.rootDir, "plugins", "syntaur")
13962
+ targetDir: resolve26(candidate.rootDir, "plugins", "syntaur")
13179
13963
  };
13180
13964
  }
13181
13965
  async function registerKnownClaudeMarketplace(name, rootDir) {
@@ -13207,7 +13991,7 @@ async function registerKnownClaudeMarketplace(name, rootDir) {
13207
13991
  };
13208
13992
  existing[name].lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
13209
13993
  existing[name].autoUpdate = true;
13210
- await ensureDir(dirname8(manifestPath));
13994
+ await ensureDir(dirname9(manifestPath));
13211
13995
  if (await fileExists(manifestPath)) {
13212
13996
  const prev = await readFile16(manifestPath, "utf-8");
13213
13997
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
@@ -13223,7 +14007,7 @@ async function ensureKnownClaudeMarketplaceForRoot(options) {
13223
14007
  return registerKnownClaudeMarketplace(options.name, options.rootDir);
13224
14008
  }
13225
14009
  async function setSyntaurPluginEnabled(options) {
13226
- const settingsPath = resolve25(homedir2(), ".claude", "settings.json");
14010
+ const settingsPath = resolve26(homedir2(), ".claude", "settings.json");
13227
14011
  const key = `syntaur@${options.marketplaceName}`;
13228
14012
  let parsed = {};
13229
14013
  if (await fileExists(settingsPath)) {
@@ -13243,7 +14027,7 @@ async function setSyntaurPluginEnabled(options) {
13243
14027
  }
13244
14028
  enabledPlugins[key] = options.enabled;
13245
14029
  parsed.enabledPlugins = enabledPlugins;
13246
- await ensureDir(dirname8(settingsPath));
14030
+ await ensureDir(dirname9(settingsPath));
13247
14031
  if (await fileExists(settingsPath)) {
13248
14032
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
13249
14033
  const prev = await readFile16(settingsPath, "utf-8");
@@ -13260,9 +14044,9 @@ async function ensureClaudeUserMarketplace() {
13260
14044
  if (existing) {
13261
14045
  return existing;
13262
14046
  }
13263
- const rootDir = resolve25(getClaudeMarketplacesRoot(), "user-plugins");
13264
- const manifestPath = resolve25(rootDir, ".claude-plugin", "marketplace.json");
13265
- await ensureDir(resolve25(rootDir, "plugins"));
14047
+ const rootDir = resolve26(getClaudeMarketplacesRoot(), "user-plugins");
14048
+ const manifestPath = resolve26(rootDir, ".claude-plugin", "marketplace.json");
14049
+ await ensureDir(resolve26(rootDir, "plugins"));
13266
14050
  if (!await fileExists(manifestPath)) {
13267
14051
  const scaffold = {
13268
14052
  plugins: []
@@ -13281,22 +14065,22 @@ async function ensureClaudeUserMarketplace() {
13281
14065
  name: "user-plugins",
13282
14066
  rootDir,
13283
14067
  manifestPath,
13284
- targetDir: resolve25(rootDir, "plugins", "syntaur")
14068
+ targetDir: resolve26(rootDir, "plugins", "syntaur")
13285
14069
  };
13286
14070
  }
13287
14071
  async function detectClaudeMarketplaceForTarget(targetDir) {
13288
14072
  const normalizedTargetDir = normalizeAbsoluteInstallPath(targetDir, "Claude plugin target");
13289
- const pluginsDir = dirname8(normalizedTargetDir);
13290
- if (basename3(pluginsDir) !== "plugins") {
14073
+ const pluginsDir = dirname9(normalizedTargetDir);
14074
+ if (basename4(pluginsDir) !== "plugins") {
13291
14075
  return null;
13292
14076
  }
13293
- const rootDir = dirname8(pluginsDir);
13294
- const manifestPath = resolve25(rootDir, ".claude-plugin", "marketplace.json");
14077
+ const rootDir = dirname9(pluginsDir);
14078
+ const manifestPath = resolve26(rootDir, ".claude-plugin", "marketplace.json");
13295
14079
  if (!await fileExists(manifestPath)) {
13296
14080
  return null;
13297
14081
  }
13298
14082
  const marketplace = await readClaudeMarketplaceFile(manifestPath);
13299
- const name = typeof marketplace.name === "string" && marketplace.name.trim() !== "" ? marketplace.name : basename3(rootDir);
14083
+ const name = typeof marketplace.name === "string" && marketplace.name.trim() !== "" ? marketplace.name : basename4(rootDir);
13300
14084
  return {
13301
14085
  name,
13302
14086
  rootDir,
@@ -13307,7 +14091,7 @@ async function detectClaudeMarketplaceForTarget(targetDir) {
13307
14091
  async function findManagedClaudeMarketplacePluginDir() {
13308
14092
  const marketplaces = await listClaudeMarketplaceCandidates();
13309
14093
  for (const marketplace of marketplaces) {
13310
- const targetDir = resolve25(marketplace.rootDir, "plugins", "syntaur");
14094
+ const targetDir = resolve26(marketplace.rootDir, "plugins", "syntaur");
13311
14095
  const status = await getInstallStatus(targetDir, "claude");
13312
14096
  if (status.exists && status.managed) {
13313
14097
  return targetDir;
@@ -13432,7 +14216,7 @@ async function installManagedPlugin(options) {
13432
14216
  `${paths.targetDir} already exists and is not a Syntaur-managed install. Remove it manually before installing Syntaur there.`
13433
14217
  );
13434
14218
  }
13435
- if (desiredMode === "link" && existing.exists && existing.installMode === "link" && existing.symlinkTarget === resolve25(paths.sourceDir) && !force) {
14219
+ if (desiredMode === "link" && existing.exists && existing.installMode === "link" && existing.symlinkTarget === resolve26(paths.sourceDir) && !force) {
13436
14220
  return {
13437
14221
  targetDir: paths.targetDir,
13438
14222
  sourceDir: paths.sourceDir,
@@ -13456,7 +14240,7 @@ async function installManagedPlugin(options) {
13456
14240
  };
13457
14241
  }
13458
14242
  function buildMarketplaceSourcePath(pluginTargetDir, marketplacePath) {
13459
- const relPath = relative2(dirname8(marketplacePath), pluginTargetDir).replaceAll("\\", "/");
14243
+ const relPath = relative2(dirname9(marketplacePath), pluginTargetDir).replaceAll("\\", "/");
13460
14244
  if (relPath === "") {
13461
14245
  return ".";
13462
14246
  }
@@ -13493,7 +14277,7 @@ async function readMarketplaceFile(marketplacePath) {
13493
14277
  };
13494
14278
  }
13495
14279
  async function writeMarketplaceFile(marketplacePath, marketplace) {
13496
- await ensureDir(dirname8(marketplacePath));
14280
+ await ensureDir(dirname9(marketplacePath));
13497
14281
  await writeFile6(marketplacePath, `${JSON.stringify(marketplace, null, 2)}
13498
14282
  `, "utf-8");
13499
14283
  }
@@ -13645,13 +14429,13 @@ async function recommendMarketplacePath() {
13645
14429
  return configuredOrManaged ?? getDefaultMarketplacePath();
13646
14430
  }
13647
14431
  async function isSyntaurDataInstalled() {
13648
- return fileExists(resolve25(syntaurRoot(), "config.md"));
14432
+ return fileExists(resolve26(syntaurRoot(), "config.md"));
13649
14433
  }
13650
14434
  async function removeSyntaurData() {
13651
14435
  await rm3(syntaurRoot(), { recursive: true, force: true });
13652
14436
  }
13653
14437
  async function getConfiguredProjectDir() {
13654
- if (!await fileExists(resolve25(syntaurRoot(), "config.md"))) {
14438
+ if (!await fileExists(resolve26(syntaurRoot(), "config.md"))) {
13655
14439
  return null;
13656
14440
  }
13657
14441
  return (await readConfig()).defaultProjectDir;
@@ -13721,16 +14505,16 @@ async function textPrompt(question, defaultValue) {
13721
14505
  // src/utils/install-skills.ts
13722
14506
  init_fs();
13723
14507
  import { readFile as readFile18, readdir as readdir11, mkdir as mkdir4, copyFile, rm as rm4, lstat as lstat2 } from "fs/promises";
13724
- import { resolve as resolve27, relative as relative3, join as join3 } from "path";
14508
+ import { resolve as resolve28, relative as relative3, join as join3 } from "path";
13725
14509
  import { homedir as homedir4 } from "os";
13726
14510
 
13727
14511
  // src/utils/plugin-state.ts
13728
14512
  init_fs();
13729
14513
  import { readFile as readFile17 } from "fs/promises";
13730
- import { resolve as resolve26 } from "path";
14514
+ import { resolve as resolve27 } from "path";
13731
14515
  import { homedir as homedir3 } from "os";
13732
14516
  function settingsPathFor(agent) {
13733
- if (agent === "claude") return resolve26(homedir3(), ".claude", "settings.json");
14517
+ if (agent === "claude") return resolve27(homedir3(), ".claude", "settings.json");
13734
14518
  return null;
13735
14519
  }
13736
14520
  async function readJsonOrNull(path) {
@@ -13776,16 +14560,20 @@ var KNOWN_SKILL_NAMES = [
13776
14560
  "add-memory",
13777
14561
  "list-assignments",
13778
14562
  "log-progress",
13779
- "set-workspace"
14563
+ "set-workspace",
14564
+ "claim-resource",
14565
+ "release-resource",
14566
+ "extend-resource",
14567
+ "list-resources"
13780
14568
  ];
13781
14569
  var KNOWN_SKILLS = KNOWN_SKILL_NAMES;
13782
14570
  async function getSkillsDir() {
13783
14571
  const packageRoot = await findPackageRoot("skills");
13784
- return resolve27(packageRoot, "skills");
14572
+ return resolve28(packageRoot, "skills");
13785
14573
  }
13786
14574
  function defaultSkillTargetDir(target) {
13787
- if (target === "claude") return resolve27(homedir4(), ".claude", "skills");
13788
- return resolve27(homedir4(), ".codex", "skills");
14575
+ if (target === "claude") return resolve28(homedir4(), ".claude", "skills");
14576
+ return resolve28(homedir4(), ".codex", "skills");
13789
14577
  }
13790
14578
  async function walkFiles(root) {
13791
14579
  const out = [];
@@ -13839,8 +14627,8 @@ async function skillMatches(srcDir, destDir) {
13839
14627
  }
13840
14628
  async function isSymlink(path) {
13841
14629
  try {
13842
- const stat9 = await lstat2(path);
13843
- return stat9.isSymbolicLink();
14630
+ const stat10 = await lstat2(path);
14631
+ return stat10.isSymbolicLink();
13844
14632
  } catch {
13845
14633
  return false;
13846
14634
  }
@@ -14117,7 +14905,7 @@ async function installPluginCommand(options) {
14117
14905
  init_paths();
14118
14906
  init_fs();
14119
14907
  import { readFile as readFile20, writeFile as writeFile8, copyFile as copyFile2, rm as rm5, stat as stat2, symlink as symlink2, unlink as unlink6, lstat as lstat3 } from "fs/promises";
14120
- import { resolve as resolve29, dirname as dirname10 } from "path";
14908
+ import { resolve as resolve30, dirname as dirname11 } from "path";
14121
14909
  import { homedir as homedir5 } from "os";
14122
14910
  import { fileURLToPath as fileURLToPath4 } from "url";
14123
14911
 
@@ -14125,7 +14913,7 @@ import { fileURLToPath as fileURLToPath4 } from "url";
14125
14913
  init_paths();
14126
14914
  init_fs();
14127
14915
  import { readFile as readFile19, writeFile as writeFile7 } from "fs/promises";
14128
- import { resolve as resolve28, dirname as dirname9 } from "path";
14916
+ import { resolve as resolve29, dirname as dirname10 } from "path";
14129
14917
  import { spawnSync } from "child_process";
14130
14918
  import { checkbox, input as input2, confirm } from "@inquirer/prompts";
14131
14919
  var AVAILABLE_SEGMENTS = [
@@ -14146,7 +14934,7 @@ var PRESETS = {
14146
14934
  tracker: { segments: ["git", "assignment", "external", "session"], separator: " \xB7 " }
14147
14935
  };
14148
14936
  function getConfigPath(installRoot) {
14149
- return resolve28(installRoot, "statusline.config.json");
14937
+ return resolve29(installRoot, "statusline.config.json");
14150
14938
  }
14151
14939
  async function readConfig2(path) {
14152
14940
  if (!await fileExists(path)) return null;
@@ -14246,7 +15034,7 @@ function renderPreview(config, statuslineScript, cwd) {
14246
15034
  env: {
14247
15035
  ...process.env,
14248
15036
  // Force the child to pick up the freshly-written config from install root.
14249
- HOME: dirname9(dirname9(statuslineScript))
15037
+ HOME: dirname10(dirname10(statuslineScript))
14250
15038
  }
14251
15039
  });
14252
15040
  if (res.status !== 0) return null;
@@ -14297,14 +15085,14 @@ async function configureStatuslineCommand(options = {}) {
14297
15085
  console.log("(preview mode \u2014 config NOT written)");
14298
15086
  return;
14299
15087
  }
14300
- await ensureDir(dirname9(configPath));
15088
+ await ensureDir(dirname10(configPath));
14301
15089
  await writeFile7(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
14302
15090
  console.log("Wrote statusline config:");
14303
15091
  console.log(` path: ${configPath}`);
14304
15092
  console.log(` segments: ${config.segments.join(", ")}`);
14305
15093
  console.log(` separator: ${JSON.stringify(config.separator)}`);
14306
15094
  if (config.wrap) console.log(` wrap: ${config.wrap}`);
14307
- const script = options.statuslineScript ?? resolve28(installRoot, "statusline.sh");
15095
+ const script = options.statuslineScript ?? resolve29(installRoot, "statusline.sh");
14308
15096
  if (await fileExists(script)) {
14309
15097
  console.log("");
14310
15098
  console.log("Live preview:");
@@ -14324,7 +15112,7 @@ async function configureStatuslineCommand(options = {}) {
14324
15112
  async function writeDefaultConfigIfMissing(installRoot) {
14325
15113
  const path = getConfigPath(installRoot);
14326
15114
  if (await fileExists(path)) return;
14327
- await ensureDir(dirname9(path));
15115
+ await ensureDir(dirname10(path));
14328
15116
  const defaultConfig = {
14329
15117
  segments: ["git", "assignment", "session"],
14330
15118
  separator: " \xB7 "
@@ -14334,8 +15122,8 @@ async function writeDefaultConfigIfMissing(installRoot) {
14334
15122
 
14335
15123
  // src/commands/install-statusline.ts
14336
15124
  function getPackageStatuslineSource() {
14337
- const here = dirname10(fileURLToPath4(import.meta.url));
14338
- return resolve29(here, "..", "statusline", "statusline.sh");
15125
+ const here = dirname11(fileURLToPath4(import.meta.url));
15126
+ return resolve30(here, "..", "statusline", "statusline.sh");
14339
15127
  }
14340
15128
  async function readSettingsJson(settingsPath) {
14341
15129
  if (!await fileExists(settingsPath)) return {};
@@ -14351,7 +15139,7 @@ async function readSettingsJson(settingsPath) {
14351
15139
  }
14352
15140
  }
14353
15141
  async function writeSettingsJson(settingsPath, data) {
14354
- await ensureDir(dirname10(settingsPath));
15142
+ await ensureDir(dirname11(settingsPath));
14355
15143
  await writeFile8(settingsPath, JSON.stringify(data, null, 2) + "\n", "utf-8");
14356
15144
  }
14357
15145
  async function resolveMode(mode, existingCommand, ourCommand) {
@@ -14386,7 +15174,7 @@ function extractExistingCommand(settings) {
14386
15174
  };
14387
15175
  }
14388
15176
  async function backupSettings(settingsSnapshot, backupPath) {
14389
- await ensureDir(dirname10(backupPath));
15177
+ await ensureDir(dirname11(backupPath));
14390
15178
  await writeFile8(
14391
15179
  backupPath,
14392
15180
  JSON.stringify(
@@ -14403,7 +15191,7 @@ async function backupSettings(settingsSnapshot, backupPath) {
14403
15191
  );
14404
15192
  }
14405
15193
  async function installScript(sourceScript, destScript, link) {
14406
- await ensureDir(dirname10(destScript));
15194
+ await ensureDir(dirname11(destScript));
14407
15195
  try {
14408
15196
  const s = await lstat3(destScript);
14409
15197
  if (s.isSymbolicLink() || s.isFile()) {
@@ -14419,12 +15207,12 @@ async function installScript(sourceScript, destScript, link) {
14419
15207
  }
14420
15208
  async function installStatuslineCommand(options = {}) {
14421
15209
  const mode = options.mode ?? "ask";
14422
- const settingsPath = options.settingsPath ?? resolve29(homedir5(), ".claude", "settings.json");
15210
+ const settingsPath = options.settingsPath ?? resolve30(homedir5(), ".claude", "settings.json");
14423
15211
  const installRoot = options.installRoot ?? syntaurRoot();
14424
15212
  const sourceScript = options.sourceScript ?? getPackageStatuslineSource();
14425
- const destScript = resolve29(installRoot, "statusline.sh");
14426
- const confPath = resolve29(installRoot, "statusline.conf");
14427
- const backupPath = resolve29(installRoot, "statusline.backup.json");
15213
+ const destScript = resolve30(installRoot, "statusline.sh");
15214
+ const confPath = resolve30(installRoot, "statusline.conf");
15215
+ const backupPath = resolve30(installRoot, "statusline.backup.json");
14428
15216
  if (!await fileExists(sourceScript)) {
14429
15217
  throw new Error(
14430
15218
  `Statusline source script not found at ${sourceScript}. Try re-installing syntaur (npm install -g syntaur) or pass --source-script explicitly.`
@@ -14460,7 +15248,7 @@ async function installStatuslineCommand(options = {}) {
14460
15248
  if (parsed) {
14461
15249
  wrapTarget = parsed;
14462
15250
  } else {
14463
- const wrapperPath = resolve29(installRoot, "statusline-wrapped.sh");
15251
+ const wrapperPath = resolve30(installRoot, "statusline-wrapped.sh");
14464
15252
  const wrapperBody = `#!/usr/bin/env bash
14465
15253
  # Auto-generated by syntaur install-statusline.
14466
15254
  # Executes the previously configured statusLine command.
@@ -14471,7 +15259,7 @@ exec ${existingCommand}
14471
15259
  wrapTarget = wrapperPath;
14472
15260
  }
14473
15261
  }
14474
- await ensureDir(dirname10(confPath));
15262
+ await ensureDir(dirname11(confPath));
14475
15263
  await writeFile8(
14476
15264
  confPath,
14477
15265
  wrapTarget ? `# Wrap target \u2014 the command below is invoked with the same stdin; its
@@ -14515,12 +15303,12 @@ async function chmodExec(path) {
14515
15303
  }
14516
15304
  }
14517
15305
  async function uninstallStatuslineCommand(options = {}) {
14518
- const settingsPath = options.settingsPath ?? resolve29(homedir5(), ".claude", "settings.json");
15306
+ const settingsPath = options.settingsPath ?? resolve30(homedir5(), ".claude", "settings.json");
14519
15307
  const installRoot = options.installRoot ?? syntaurRoot();
14520
- const destScript = resolve29(installRoot, "statusline.sh");
14521
- const confPath = resolve29(installRoot, "statusline.conf");
14522
- const backupPath = resolve29(installRoot, "statusline.backup.json");
14523
- const wrapperPath = resolve29(installRoot, "statusline-wrapped.sh");
15308
+ const destScript = resolve30(installRoot, "statusline.sh");
15309
+ const confPath = resolve30(installRoot, "statusline.conf");
15310
+ const backupPath = resolve30(installRoot, "statusline.backup.json");
15311
+ const wrapperPath = resolve30(installRoot, "statusline-wrapped.sh");
14524
15312
  const settings = await readSettingsJson(settingsPath);
14525
15313
  const existing = extractExistingCommand(settings);
14526
15314
  const ourCommand = `bash ${destScript}`;
@@ -14548,7 +15336,7 @@ async function uninstallStatuslineCommand(options = {}) {
14548
15336
  await writeSettingsJson(settingsPath, settings);
14549
15337
  }
14550
15338
  if (!options.keepScript) {
14551
- const configPath = resolve29(installRoot, "statusline.config.json");
15339
+ const configPath = resolve30(installRoot, "statusline.config.json");
14552
15340
  for (const path of [destScript, confPath, backupPath, wrapperPath, configPath]) {
14553
15341
  try {
14554
15342
  await rm5(path, { force: true });
@@ -14805,7 +15593,7 @@ async function setupCommand(options) {
14805
15593
  }
14806
15594
 
14807
15595
  // src/commands/uninstall.ts
14808
- import { resolve as resolve30 } from "path";
15596
+ import { resolve as resolve31 } from "path";
14809
15597
  init_paths();
14810
15598
  function expandTargets(options) {
14811
15599
  if (options.all) {
@@ -14885,7 +15673,7 @@ async function uninstallCommand(options) {
14885
15673
  const configuredProjectDir = await getConfiguredProjectDir();
14886
15674
  await removeSyntaurData();
14887
15675
  console.log(`Removed ${syntaurRoot()}`);
14888
- if (configuredProjectDir && resolve30(configuredProjectDir) !== resolve30(syntaurRoot(), "projects")) {
15676
+ if (configuredProjectDir && resolve31(configuredProjectDir) !== resolve31(syntaurRoot(), "projects")) {
14889
15677
  console.warn(
14890
15678
  `Warning: config.md pointed to an external project directory (${configuredProjectDir}). That directory was not removed automatically.`
14891
15679
  );
@@ -14904,7 +15692,7 @@ init_slug();
14904
15692
  init_cursor_rules();
14905
15693
  init_codex_agents();
14906
15694
  init_opencode_config();
14907
- import { resolve as resolve31 } from "path";
15695
+ import { resolve as resolve32 } from "path";
14908
15696
  var SUPPORTED_FRAMEWORKS = ["cursor", "codex", "opencode"];
14909
15697
  async function setupAdapterCommand(framework, options) {
14910
15698
  if (!SUPPORTED_FRAMEWORKS.includes(framework)) {
@@ -14930,19 +15718,19 @@ async function setupAdapterCommand(framework, options) {
14930
15718
  }
14931
15719
  const config = await readConfig();
14932
15720
  const baseDir = options.dir ? expandHome(options.dir) : config.defaultProjectDir;
14933
- const projectDir = resolve31(baseDir, options.project);
14934
- const assignmentDir = resolve31(
15721
+ const projectDir = resolve32(baseDir, options.project);
15722
+ const assignmentDir = resolve32(
14935
15723
  projectDir,
14936
15724
  "assignments",
14937
15725
  options.assignment
14938
15726
  );
14939
- const projectMdPath = resolve31(projectDir, "project.md");
15727
+ const projectMdPath = resolve32(projectDir, "project.md");
14940
15728
  if (!await fileExists(projectDir) || !await fileExists(projectMdPath)) {
14941
15729
  throw new Error(
14942
15730
  `Project "${options.project}" not found at ${projectDir}.`
14943
15731
  );
14944
15732
  }
14945
- const assignmentMdPath2 = resolve31(assignmentDir, "assignment.md");
15733
+ const assignmentMdPath2 = resolve32(assignmentDir, "assignment.md");
14946
15734
  if (!await fileExists(assignmentDir) || !await fileExists(assignmentMdPath2)) {
14947
15735
  throw new Error(
14948
15736
  `Assignment "${options.assignment}" not found at ${assignmentDir}.`
@@ -14970,15 +15758,15 @@ async function setupAdapterCommand(framework, options) {
14970
15758
  }
14971
15759
  }
14972
15760
  if (framework === "cursor") {
14973
- const protocolPath = resolve31(cwd, ".cursor", "rules", "syntaur-protocol.mdc");
14974
- const assignmentPath = resolve31(cwd, ".cursor", "rules", "syntaur-assignment.mdc");
15761
+ const protocolPath = resolve32(cwd, ".cursor", "rules", "syntaur-protocol.mdc");
15762
+ const assignmentPath = resolve32(cwd, ".cursor", "rules", "syntaur-assignment.mdc");
14975
15763
  await writeAdapterFile(protocolPath, renderCursorProtocol());
14976
15764
  await writeAdapterFile(assignmentPath, renderCursorAssignment(rendererParams));
14977
15765
  } else if (framework === "codex" || framework === "opencode") {
14978
- const agentsPath = resolve31(cwd, "AGENTS.md");
15766
+ const agentsPath = resolve32(cwd, "AGENTS.md");
14979
15767
  await writeAdapterFile(agentsPath, renderCodexAgents(rendererParams));
14980
15768
  if (framework === "opencode") {
14981
- const configPath = resolve31(cwd, "opencode.json");
15769
+ const configPath = resolve32(cwd, "opencode.json");
14982
15770
  await writeAdapterFile(configPath, renderOpenCodeConfig({ projectDir }));
14983
15771
  }
14984
15772
  }
@@ -15003,7 +15791,7 @@ async function setupAdapterCommand(framework, options) {
15003
15791
  init_paths();
15004
15792
  init_fs();
15005
15793
  init_config2();
15006
- import { resolve as resolve32 } from "path";
15794
+ import { resolve as resolve33 } from "path";
15007
15795
  async function trackSessionCommand(options) {
15008
15796
  if (!options.agent) {
15009
15797
  throw new Error("--agent <name> is required.");
@@ -15016,7 +15804,7 @@ async function trackSessionCommand(options) {
15016
15804
  if (options.project) {
15017
15805
  const config = await readConfig();
15018
15806
  const baseDir = options.dir ? expandHome(options.dir) : config.defaultProjectDir;
15019
- const projectDir = resolve32(baseDir, options.project);
15807
+ const projectDir = resolve33(baseDir, options.project);
15020
15808
  if (!await fileExists(projectDir)) {
15021
15809
  throw new Error(
15022
15810
  `Project "${options.project}" not found at ${projectDir}.`
@@ -15025,6 +15813,8 @@ async function trackSessionCommand(options) {
15025
15813
  }
15026
15814
  initSessionDb();
15027
15815
  const { sessionId } = options;
15816
+ const derivedPath = await derivePathFromTranscript(options.transcriptPath);
15817
+ const recordedPath = derivedPath ?? options.path ?? process.cwd();
15028
15818
  await appendSession("", {
15029
15819
  projectSlug: options.project || null,
15030
15820
  assignmentSlug: options.assignment || null,
@@ -15032,7 +15822,7 @@ async function trackSessionCommand(options) {
15032
15822
  sessionId,
15033
15823
  started: (/* @__PURE__ */ new Date()).toISOString(),
15034
15824
  status: "active",
15035
- path: options.path || process.cwd(),
15825
+ path: recordedPath,
15036
15826
  description: options.description || null,
15037
15827
  transcriptPath: options.transcriptPath ?? null
15038
15828
  });
@@ -15050,7 +15840,7 @@ init_config2();
15050
15840
  init_paths();
15051
15841
  init_fs();
15052
15842
  import { spawnSync as spawnSync2 } from "child_process";
15053
- import { resolve as resolve34, isAbsolute as isAbsolute4 } from "path";
15843
+ import { resolve as resolve35, isAbsolute as isAbsolute4 } from "path";
15054
15844
  import { readFile as readFile22 } from "fs/promises";
15055
15845
  import { select, confirm as confirm2, input as input3 } from "@inquirer/prompts";
15056
15846
  async function browseCommand(options) {
@@ -15120,7 +15910,7 @@ async function pickAgent(agents) {
15120
15910
  return picked;
15121
15911
  }
15122
15912
  async function ensureWorktree(opts) {
15123
- const assignmentPath = resolve34(
15913
+ const assignmentPath = resolve35(
15124
15914
  opts.projectsDir,
15125
15915
  opts.projectSlug,
15126
15916
  "assignments",
@@ -15201,7 +15991,7 @@ function computeWorktreeDefaults(opts) {
15201
15991
  const repository = opts.existing.repository ?? detectCurrentGitRoot();
15202
15992
  const branch = opts.projectSlug ? `syntaur/${opts.projectSlug}/${opts.assignmentSlug}` : `syntaur/${opts.assignmentSlug}`;
15203
15993
  const parentBranch = opts.existing.parentBranch ?? detectCurrentBranch() ?? "main";
15204
- const worktreeBase = repository ? resolve34(repository, ".worktrees", branch) : resolve34(
15994
+ const worktreeBase = repository ? resolve35(repository, ".worktrees", branch) : resolve35(
15205
15995
  syntaurRoot(),
15206
15996
  "worktrees",
15207
15997
  opts.projectSlug || "standalone",
@@ -15234,7 +16024,7 @@ function detectCurrentBranch() {
15234
16024
  async function runCreate(opts) {
15235
16025
  const { createWorktreeAndRecord: createWorktreeAndRecord2, GitWorktreeError: GitWorktreeError2 } = await Promise.resolve().then(() => (init_git_worktree(), git_worktree_exports));
15236
16026
  const expandedWorktree = expandHome(opts.worktreePath);
15237
- const absWorktree = isAbsolute4(expandedWorktree) ? expandedWorktree : resolve34(expandedWorktree);
16027
+ const absWorktree = isAbsolute4(expandedWorktree) ? expandedWorktree : resolve35(expandedWorktree);
15238
16028
  try {
15239
16029
  await createWorktreeAndRecord2({
15240
16030
  assignmentPath: opts.assignmentPath,
@@ -15263,7 +16053,7 @@ init_paths();
15263
16053
  init_fs();
15264
16054
  init_playbook();
15265
16055
  init_playbooks();
15266
- import { resolve as resolve35 } from "path";
16056
+ import { resolve as resolve36 } from "path";
15267
16057
  async function createPlaybookCommand(name, options) {
15268
16058
  if (!name.trim()) {
15269
16059
  throw new Error("Playbook name cannot be empty.");
@@ -15276,7 +16066,7 @@ async function createPlaybookCommand(name, options) {
15276
16066
  }
15277
16067
  const dir = playbooksDir();
15278
16068
  await ensureDir(dir);
15279
- const filePath = resolve35(dir, `${slug}.md`);
16069
+ const filePath = resolve36(dir, `${slug}.md`);
15280
16070
  if (await fileExists(filePath)) {
15281
16071
  throw new Error(
15282
16072
  `Playbook "${slug}" already exists at ${filePath}
@@ -15299,7 +16089,7 @@ init_fs();
15299
16089
  init_parser();
15300
16090
  init_config2();
15301
16091
  import { readdir as readdir12, readFile as readFile23 } from "fs/promises";
15302
- import { resolve as resolve36 } from "path";
16092
+ import { resolve as resolve37 } from "path";
15303
16093
  async function listPlaybooksCommand(options = {}) {
15304
16094
  const dir = playbooksDir();
15305
16095
  if (!await fileExists(dir)) {
@@ -15314,7 +16104,7 @@ async function listPlaybooksCommand(options = {}) {
15314
16104
  );
15315
16105
  const rows = [];
15316
16106
  for (const entry of mdFiles) {
15317
- const filePath = resolve36(dir, entry.name);
16107
+ const filePath = resolve37(dir, entry.name);
15318
16108
  const raw = await readFile23(filePath, "utf-8");
15319
16109
  const parsed = parsePlaybook(raw);
15320
16110
  const slug = parsed.slug || entry.name.replace(/\.md$/, "");
@@ -15414,7 +16204,7 @@ init_config2();
15414
16204
  init_slug();
15415
16205
  import { Command } from "commander";
15416
16206
  import { readFile as readFile24 } from "fs/promises";
15417
- import { resolve as resolve37 } from "path";
16207
+ import { resolve as resolve38 } from "path";
15418
16208
  var WORKSPACE_REGEX3 = /^[a-z0-9_][a-z0-9-]*$/;
15419
16209
  async function resolveScope(options) {
15420
16210
  const flagCount = [Boolean(options.project), Boolean(options.workspace), Boolean(options.global)].filter(Boolean).length;
@@ -15426,7 +16216,7 @@ async function resolveScope(options) {
15426
16216
  throw new Error(`Invalid project slug: "${options.project}".`);
15427
16217
  }
15428
16218
  const config = await readConfig();
15429
- const projectMd = resolve37(config.defaultProjectDir, options.project, "project.md");
16219
+ const projectMd = resolve38(config.defaultProjectDir, options.project, "project.md");
15430
16220
  if (!await fileExists(projectMd)) {
15431
16221
  throw new Error(`Project "${options.project}" not found.`);
15432
16222
  }
@@ -15745,7 +16535,7 @@ todoCommand.command("archive").description("Archive completed todos and their lo
15745
16535
  (e) => e.itemIds.every((id) => completedIds.has(id))
15746
16536
  );
15747
16537
  const archFile = archivePath(todosPath, workspace, checklist.archiveInterval);
15748
- await ensureDir(resolve37(todosPath, "archive"));
16538
+ await ensureDir(resolve38(todosPath, "archive"));
15749
16539
  let archContent = "";
15750
16540
  if (await fileExists(archFile)) {
15751
16541
  archContent = await readFile24(archFile, "utf-8");
@@ -15977,7 +16767,7 @@ todoCommand.command("plan").description("Create or open a plan directory for a t
15977
16767
  );
15978
16768
  let target;
15979
16769
  if (existingFiles.length === 0) {
15980
- target = resolve37(planDir, "plan.md");
16770
+ target = resolve38(planDir, "plan.md");
15981
16771
  } else {
15982
16772
  const versions = /* @__PURE__ */ new Set();
15983
16773
  for (const f of existingFiles) {
@@ -15987,7 +16777,7 @@ todoCommand.command("plan").description("Create or open a plan directory for a t
15987
16777
  }
15988
16778
  let n = 2;
15989
16779
  while (versions.has(n)) n++;
15990
- target = resolve37(planDir, `plan-v${n}.md`);
16780
+ target = resolve38(planDir, `plan-v${n}.md`);
15991
16781
  }
15992
16782
  if (!await fileExists(target)) {
15993
16783
  const stub = `---
@@ -16058,8 +16848,8 @@ async function moveTodo(id, options) {
16058
16848
  throw new Error(`Plan directory already exists at target: ${newPlanDir}; refusing to move.`);
16059
16849
  }
16060
16850
  const { rename: rename8, mkdir: mkdir8 } = await import("fs/promises");
16061
- const { dirname: dirname16 } = await import("path");
16062
- await mkdir8(dirname16(newPlanDir), { recursive: true });
16851
+ const { dirname: dirname17 } = await import("path");
16852
+ await mkdir8(dirname17(newPlanDir), { recursive: true });
16063
16853
  await rename8(item.planDir, newPlanDir);
16064
16854
  item.planDir = newPlanDir;
16065
16855
  }
@@ -16177,31 +16967,31 @@ backupCommand.command("config").description("Show or update backup configuration
16177
16967
  // src/commands/doctor.ts
16178
16968
  import { Command as Command3 } from "commander";
16179
16969
  import { readFile as readFile31 } from "fs/promises";
16180
- import { isAbsolute as isAbsolute7, resolve as resolve47 } from "path";
16970
+ import { isAbsolute as isAbsolute7, resolve as resolve48 } from "path";
16181
16971
 
16182
16972
  // src/utils/doctor/index.ts
16183
16973
  import { fileURLToPath as fileURLToPath6 } from "url";
16184
16974
  import { readFile as readFile30 } from "fs/promises";
16185
- import { dirname as dirname13, join as join6 } from "path";
16975
+ import { dirname as dirname14, join as join6 } from "path";
16186
16976
 
16187
16977
  // src/utils/doctor/context.ts
16188
16978
  init_config2();
16189
16979
  init_paths();
16190
16980
  init_fs();
16191
- import Database2 from "better-sqlite3";
16192
- import { resolve as resolve38 } from "path";
16981
+ import Database3 from "better-sqlite3";
16982
+ import { resolve as resolve39 } from "path";
16193
16983
  async function buildCheckContext(cwd = process.cwd()) {
16194
16984
  const config = await readConfig();
16195
16985
  const root = syntaurRoot();
16196
- const dbPath = resolve38(root, "syntaur.db");
16197
- let db3 = null;
16986
+ const dbPath = resolve39(root, "syntaur.db");
16987
+ let db4 = null;
16198
16988
  let dbError = null;
16199
16989
  if (await fileExists(dbPath)) {
16200
16990
  try {
16201
- db3 = new Database2(dbPath, { readonly: true, fileMustExist: true });
16991
+ db4 = new Database3(dbPath, { readonly: true, fileMustExist: true });
16202
16992
  } catch (err2) {
16203
16993
  dbError = err2 instanceof Error ? err2.message : String(err2);
16204
- db3 = null;
16994
+ db4 = null;
16205
16995
  }
16206
16996
  } else {
16207
16997
  dbError = `database file not found at ${dbPath}`;
@@ -16209,7 +16999,7 @@ async function buildCheckContext(cwd = process.cwd()) {
16209
16999
  return {
16210
17000
  config,
16211
17001
  syntaurRoot: root,
16212
- db: db3,
17002
+ db: db4,
16213
17003
  dbError,
16214
17004
  cwd,
16215
17005
  now: /* @__PURE__ */ new Date()
@@ -16227,10 +17017,10 @@ function closeCheckContext(ctx) {
16227
17017
  // src/utils/doctor/checks/env.ts
16228
17018
  init_fs();
16229
17019
  init_paths();
16230
- import { resolve as resolve39, isAbsolute as isAbsolute5 } from "path";
17020
+ import { resolve as resolve40, isAbsolute as isAbsolute5 } from "path";
16231
17021
  import { readFile as readFile25, stat as stat3 } from "fs/promises";
16232
17022
  import { fileURLToPath as fileURLToPath5 } from "url";
16233
- import { dirname as dirname11, join as join4 } from "path";
17023
+ import { dirname as dirname12, join as join4 } from "path";
16234
17024
  var CATEGORY = "env";
16235
17025
  var syntaurRootExists = {
16236
17026
  id: "env.syntaur-root-exists",
@@ -16268,7 +17058,7 @@ var configValid = {
16268
17058
  category: CATEGORY,
16269
17059
  title: "~/.syntaur/config.md is valid",
16270
17060
  async run(ctx) {
16271
- const configPath = resolve39(ctx.syntaurRoot, "config.md");
17061
+ const configPath = resolve40(ctx.syntaurRoot, "config.md");
16272
17062
  if (!await fileExists(configPath)) {
16273
17063
  return {
16274
17064
  id: this.id,
@@ -16561,14 +17351,14 @@ async function readLocalVersion() {
16561
17351
  async function readLocalPkg() {
16562
17352
  try {
16563
17353
  const here = fileURLToPath5(import.meta.url);
16564
- let dir = dirname11(here);
17354
+ let dir = dirname12(here);
16565
17355
  for (let i = 0; i < 6; i++) {
16566
17356
  const candidate = join4(dir, "package.json");
16567
17357
  try {
16568
17358
  const text = await readFile25(candidate, "utf-8");
16569
17359
  return JSON.parse(text);
16570
17360
  } catch {
16571
- dir = dirname11(dir);
17361
+ dir = dirname12(dir);
16572
17362
  }
16573
17363
  }
16574
17364
  return null;
@@ -16617,7 +17407,7 @@ function versionGte(a, b) {
16617
17407
 
16618
17408
  // src/utils/doctor/checks/structure.ts
16619
17409
  init_fs();
16620
- import { resolve as resolve40 } from "path";
17410
+ import { resolve as resolve41 } from "path";
16621
17411
  import { readdir as readdir13, stat as stat4 } from "fs/promises";
16622
17412
  var CATEGORY2 = "structure";
16623
17413
  var KNOWN_TOP_LEVEL = /* @__PURE__ */ new Set([
@@ -16637,7 +17427,7 @@ var projectsDir = {
16637
17427
  category: CATEGORY2,
16638
17428
  title: "projects/ directory exists",
16639
17429
  async run(ctx) {
16640
- const p = resolve40(ctx.syntaurRoot, "projects");
17430
+ const p = resolve41(ctx.syntaurRoot, "projects");
16641
17431
  if (!await fileExists(p)) {
16642
17432
  return {
16643
17433
  id: this.id,
@@ -16662,7 +17452,7 @@ var playbooksDir2 = {
16662
17452
  category: CATEGORY2,
16663
17453
  title: "playbooks/ directory exists",
16664
17454
  async run(ctx) {
16665
- const p = resolve40(ctx.syntaurRoot, "playbooks");
17455
+ const p = resolve41(ctx.syntaurRoot, "playbooks");
16666
17456
  if (!await fileExists(p)) {
16667
17457
  return {
16668
17458
  id: this.id,
@@ -16687,7 +17477,7 @@ var todosDirValid = {
16687
17477
  category: CATEGORY2,
16688
17478
  title: "todos/ directory is readable (if present)",
16689
17479
  async run(ctx) {
16690
- const p = resolve40(ctx.syntaurRoot, "todos");
17480
+ const p = resolve41(ctx.syntaurRoot, "todos");
16691
17481
  if (!await fileExists(p)) {
16692
17482
  return {
16693
17483
  id: this.id,
@@ -16718,7 +17508,7 @@ var serversDirValid = {
16718
17508
  category: CATEGORY2,
16719
17509
  title: "servers/ directory is readable (if present)",
16720
17510
  async run(ctx) {
16721
- const p = resolve40(ctx.syntaurRoot, "servers");
17511
+ const p = resolve41(ctx.syntaurRoot, "servers");
16722
17512
  if (!await fileExists(p)) {
16723
17513
  return {
16724
17514
  id: this.id,
@@ -16763,7 +17553,7 @@ var knownFilesRecognized = {
16763
17553
  title: this.title,
16764
17554
  status: "warn",
16765
17555
  detail: `unexpected top-level entries: ${unexpected.join(", ")}`,
16766
- affected: unexpected.map((n) => resolve40(ctx.syntaurRoot, n)),
17556
+ affected: unexpected.map((n) => resolve41(ctx.syntaurRoot, n)),
16767
17557
  remediation: {
16768
17558
  kind: "manual",
16769
17559
  suggestion: "Review these entries \u2014 they may be leftover state from older versions",
@@ -16792,7 +17582,7 @@ function pass2(check) {
16792
17582
 
16793
17583
  // src/utils/doctor/checks/project.ts
16794
17584
  init_fs();
16795
- import { resolve as resolve41 } from "path";
17585
+ import { resolve as resolve42 } from "path";
16796
17586
  import { readdir as readdir14, stat as stat5 } from "fs/promises";
16797
17587
  var CATEGORY3 = "project";
16798
17588
  var REQUIRED_PROJECT_FILES = [
@@ -16822,10 +17612,10 @@ async function listProjects2(ctx) {
16822
17612
  for (const e of entries) {
16823
17613
  if (!e.isDirectory()) continue;
16824
17614
  if (e.name.startsWith(".") || e.name.startsWith("_")) continue;
16825
- const projectDir = resolve41(dir, e.name);
17615
+ const projectDir = resolve42(dir, e.name);
16826
17616
  let looksLikeProject = false;
16827
17617
  for (const marker of PROJECT_MARKERS) {
16828
- if (await fileExists(resolve41(projectDir, marker))) {
17618
+ if (await fileExists(resolve42(projectDir, marker))) {
16829
17619
  looksLikeProject = true;
16830
17620
  break;
16831
17621
  }
@@ -16844,7 +17634,7 @@ var requiredFiles = {
16844
17634
  for (const projectDir of projects) {
16845
17635
  const missing = [];
16846
17636
  for (const rel of REQUIRED_PROJECT_FILES) {
16847
- const p = resolve41(projectDir, rel);
17637
+ const p = resolve42(projectDir, rel);
16848
17638
  if (!await fileExists(p)) missing.push(rel);
16849
17639
  }
16850
17640
  if (missing.length === 0) continue;
@@ -16854,7 +17644,7 @@ var requiredFiles = {
16854
17644
  title: this.title,
16855
17645
  status: "error",
16856
17646
  detail: `project at ${projectDir} is missing: ${missing.join(", ")}`,
16857
- affected: missing.map((m) => resolve41(projectDir, m)),
17647
+ affected: missing.map((m) => resolve42(projectDir, m)),
16858
17648
  remediation: {
16859
17649
  kind: "manual",
16860
17650
  suggestion: "Recreate the missing scaffold files from templates",
@@ -16877,7 +17667,7 @@ var manifestStale = {
16877
17667
  const projects = await listProjects2(ctx);
16878
17668
  const results = [];
16879
17669
  for (const projectDir of projects) {
16880
- const manifestPath = resolve41(projectDir, "manifest.md");
17670
+ const manifestPath = resolve42(projectDir, "manifest.md");
16881
17671
  if (!await fileExists(manifestPath)) continue;
16882
17672
  const manifestMtime = (await stat5(manifestPath)).mtimeMs;
16883
17673
  const newestAssignment = await newestAssignmentMtime(projectDir);
@@ -16926,7 +17716,7 @@ var orphanFiles = {
16926
17716
  title: this.title,
16927
17717
  status: "warn",
16928
17718
  detail: `project at ${projectDir} has unexpected entries: ${orphans.join(", ")}`,
16929
- affected: orphans.map((o) => resolve41(projectDir, o)),
17719
+ affected: orphans.map((o) => resolve42(projectDir, o)),
16930
17720
  autoFixable: false
16931
17721
  });
16932
17722
  }
@@ -16936,7 +17726,7 @@ var orphanFiles = {
16936
17726
  };
16937
17727
  var projectChecks = [requiredFiles, manifestStale, orphanFiles];
16938
17728
  async function newestAssignmentMtime(projectDir) {
16939
- const assignmentsRoot = resolve41(projectDir, "assignments");
17729
+ const assignmentsRoot = resolve42(projectDir, "assignments");
16940
17730
  if (!await fileExists(assignmentsRoot)) return 0;
16941
17731
  let newest = 0;
16942
17732
  let entries;
@@ -16947,7 +17737,7 @@ async function newestAssignmentMtime(projectDir) {
16947
17737
  }
16948
17738
  for (const e of entries) {
16949
17739
  if (!e.isDirectory()) continue;
16950
- const assignmentMd = resolve41(assignmentsRoot, e.name, "assignment.md");
17740
+ const assignmentMd = resolve42(assignmentsRoot, e.name, "assignment.md");
16951
17741
  try {
16952
17742
  const s = await stat5(assignmentMd);
16953
17743
  if (s.mtimeMs > newest) newest = s.mtimeMs;
@@ -16971,7 +17761,7 @@ init_fs();
16971
17761
  init_parser();
16972
17762
  init_types();
16973
17763
  init_paths();
16974
- import { resolve as resolve42 } from "path";
17764
+ import { resolve as resolve43 } from "path";
16975
17765
  import { readdir as readdir15, readFile as readFile26 } from "fs/promises";
16976
17766
  var CATEGORY4 = "assignment";
16977
17767
  var STATUSES_REQUIRING_HANDOFF = /* @__PURE__ */ new Set(["review", "completed"]);
@@ -16983,16 +17773,16 @@ async function listAssignments(ctx) {
16983
17773
  for (const m of projects) {
16984
17774
  if (!m.isDirectory()) continue;
16985
17775
  if (m.name.startsWith(".") || m.name.startsWith("_")) continue;
16986
- const assignmentsDir2 = resolve42(projectsDir2, m.name, "assignments");
17776
+ const assignmentsDir2 = resolve43(projectsDir2, m.name, "assignments");
16987
17777
  if (!await fileExists(assignmentsDir2)) continue;
16988
17778
  const entries = await readdir15(assignmentsDir2, { withFileTypes: true });
16989
17779
  for (const a of entries) {
16990
17780
  if (!a.isDirectory()) continue;
16991
17781
  if (a.name.startsWith(".") || a.name.startsWith("_")) continue;
16992
- const assignmentDir = resolve42(assignmentsDir2, a.name);
16993
- const assignmentMd = resolve42(assignmentDir, "assignment.md");
17782
+ const assignmentDir = resolve43(assignmentsDir2, a.name);
17783
+ const assignmentMd = resolve43(assignmentDir, "assignment.md");
16994
17784
  const entry = {
16995
- projectDir: resolve42(projectsDir2, m.name),
17785
+ projectDir: resolve43(projectsDir2, m.name),
16996
17786
  projectSlug: m.name,
16997
17787
  assignmentDir,
16998
17788
  assignmentSlug: a.name,
@@ -17012,8 +17802,8 @@ async function listAssignments(ctx) {
17012
17802
  for (const a of entries) {
17013
17803
  if (!a.isDirectory()) continue;
17014
17804
  if (a.name.startsWith(".") || a.name.startsWith("_")) continue;
17015
- const assignmentDir = resolve42(standaloneRoot, a.name);
17016
- const assignmentMd = resolve42(assignmentDir, "assignment.md");
17805
+ const assignmentDir = resolve43(standaloneRoot, a.name);
17806
+ const assignmentMd = resolve43(assignmentDir, "assignment.md");
17017
17807
  const entry = {
17018
17808
  projectDir: standaloneRoot,
17019
17809
  projectSlug: null,
@@ -17091,7 +17881,7 @@ var invalidStatus = {
17091
17881
  const allowed = configuredStatuses(ctx);
17092
17882
  const results = [];
17093
17883
  for (const a of withAssignmentMd) {
17094
- const path = resolve42(a.assignmentDir, "assignment.md");
17884
+ const path = resolve43(a.assignmentDir, "assignment.md");
17095
17885
  const parsed = await parseSafe(path);
17096
17886
  if (!parsed) continue;
17097
17887
  if (!allowed.has(parsed.status)) {
@@ -17124,7 +17914,7 @@ var workspaceMissing = {
17124
17914
  const terminal = terminalStatuses(ctx);
17125
17915
  const results = [];
17126
17916
  for (const a of withAssignmentMd) {
17127
- const path = resolve42(a.assignmentDir, "assignment.md");
17917
+ const path = resolve43(a.assignmentDir, "assignment.md");
17128
17918
  const parsed = await parseSafe(path);
17129
17919
  if (!parsed) continue;
17130
17920
  if (terminal.has(parsed.status)) continue;
@@ -17171,12 +17961,12 @@ var requiredFilesByStatus = {
17171
17961
  const { withAssignmentMd } = await listAssignments(ctx);
17172
17962
  const results = [];
17173
17963
  for (const a of withAssignmentMd) {
17174
- const assignmentPath = resolve42(a.assignmentDir, "assignment.md");
17964
+ const assignmentPath = resolve43(a.assignmentDir, "assignment.md");
17175
17965
  const parsed = await parseSafe(assignmentPath);
17176
17966
  if (!parsed) continue;
17177
17967
  const missing = [];
17178
17968
  if (STATUSES_REQUIRING_HANDOFF.has(parsed.status)) {
17179
- const handoffPath = resolve42(a.assignmentDir, "handoff.md");
17969
+ const handoffPath = resolve43(a.assignmentDir, "handoff.md");
17180
17970
  if (!await fileExists(handoffPath)) missing.push("handoff.md");
17181
17971
  }
17182
17972
  if (missing.length === 0) continue;
@@ -17186,7 +17976,7 @@ var requiredFilesByStatus = {
17186
17976
  title: this.title,
17187
17977
  status: "warn",
17188
17978
  detail: `${a.projectSlug}/${a.assignmentSlug} (status: ${parsed.status}) is missing ${missing.join(", ")}`,
17189
- affected: missing.map((m) => resolve42(a.assignmentDir, m)),
17979
+ affected: missing.map((m) => resolve43(a.assignmentDir, m)),
17190
17980
  remediation: {
17191
17981
  kind: "manual",
17192
17982
  suggestion: `Create the missing ${missing.join(" and ")} files for this assignment`,
@@ -17209,7 +17999,7 @@ var companionFilesScaffolded = {
17209
17999
  for (const a of withAssignmentMd) {
17210
18000
  const missing = [];
17211
18001
  for (const filename of ["progress.md", "comments.md"]) {
17212
- if (!await fileExists(resolve42(a.assignmentDir, filename))) {
18002
+ if (!await fileExists(resolve43(a.assignmentDir, filename))) {
17213
18003
  missing.push(filename);
17214
18004
  }
17215
18005
  }
@@ -17221,7 +18011,7 @@ var companionFilesScaffolded = {
17221
18011
  title: this.title,
17222
18012
  status: "warn",
17223
18013
  detail: `${label} is missing ${missing.join(" and ")} (pre-v2.0 assignment \u2014 not required, but scaffolding them keeps the dashboard and CLIs consistent)`,
17224
- affected: missing.map((m) => resolve42(a.assignmentDir, m)),
18014
+ affected: missing.map((m) => resolve43(a.assignmentDir, m)),
17225
18015
  remediation: {
17226
18016
  kind: "manual",
17227
18017
  suggestion: `Create ${missing.join(" and ")} with the renderProgress/renderComments templates, or re-scaffold via the CLI`,
@@ -17254,7 +18044,7 @@ var typeDefinition = {
17254
18044
  const { withAssignmentMd } = await listAssignments(ctx);
17255
18045
  const results = [];
17256
18046
  for (const a of withAssignmentMd) {
17257
- const path = resolve42(a.assignmentDir, "assignment.md");
18047
+ const path = resolve43(a.assignmentDir, "assignment.md");
17258
18048
  const parsed = await parseSafe(path);
17259
18049
  if (!parsed) continue;
17260
18050
  if (!parsed.type) continue;
@@ -17288,7 +18078,7 @@ var projectFrontmatterMatchesContainer = {
17288
18078
  const { withAssignmentMd } = await listAssignments(ctx);
17289
18079
  const results = [];
17290
18080
  for (const a of withAssignmentMd) {
17291
- const path = resolve42(a.assignmentDir, "assignment.md");
18081
+ const path = resolve43(a.assignmentDir, "assignment.md");
17292
18082
  const parsed = await parseSafe(path);
17293
18083
  if (!parsed) continue;
17294
18084
  if (a.standalone) {
@@ -17362,7 +18152,7 @@ function pass4(check, detail) {
17362
18152
 
17363
18153
  // src/utils/doctor/checks/dashboard.ts
17364
18154
  init_fs();
17365
- import { resolve as resolve43 } from "path";
18155
+ import { resolve as resolve44 } from "path";
17366
18156
  var CATEGORY5 = "dashboard";
17367
18157
  var dbReachable = {
17368
18158
  id: "dashboard.db-reachable",
@@ -17376,7 +18166,7 @@ var dbReachable = {
17376
18166
  title: this.title,
17377
18167
  status: "error",
17378
18168
  detail: `could not open syntaur.db: ${ctx.dbError ?? "unknown error"}`,
17379
- affected: [resolve43(ctx.syntaurRoot, "syntaur.db")],
18169
+ affected: [resolve44(ctx.syntaurRoot, "syntaur.db")],
17380
18170
  remediation: {
17381
18171
  kind: "manual",
17382
18172
  suggestion: "Start the dashboard once (`syntaur dashboard`) to initialize the DB, or restore it from backup",
@@ -17394,7 +18184,7 @@ var dbReachable = {
17394
18184
  title: this.title,
17395
18185
  status: "error",
17396
18186
  detail: 'syntaur.db is missing the expected "sessions" table',
17397
- affected: [resolve43(ctx.syntaurRoot, "syntaur.db")],
18187
+ affected: [resolve44(ctx.syntaurRoot, "syntaur.db")],
17398
18188
  autoFixable: false
17399
18189
  };
17400
18190
  }
@@ -17406,7 +18196,7 @@ var dbReachable = {
17406
18196
  title: this.title,
17407
18197
  status: "error",
17408
18198
  detail: `syntaur.db query failed: ${err2 instanceof Error ? err2.message : String(err2)}`,
17409
- affected: [resolve43(ctx.syntaurRoot, "syntaur.db")],
18199
+ affected: [resolve44(ctx.syntaurRoot, "syntaur.db")],
17410
18200
  autoFixable: false
17411
18201
  };
17412
18202
  }
@@ -17432,7 +18222,7 @@ var ghostSessions = {
17432
18222
  const results = [];
17433
18223
  for (const row of rows) {
17434
18224
  if (!row.project_slug) continue;
17435
- const projectPath = resolve43(projectsDir2, row.project_slug, "project.md");
18225
+ const projectPath = resolve44(projectsDir2, row.project_slug, "project.md");
17436
18226
  if (!await fileExists(projectPath)) {
17437
18227
  results.push({
17438
18228
  id: this.id,
@@ -17451,7 +18241,7 @@ var ghostSessions = {
17451
18241
  continue;
17452
18242
  }
17453
18243
  if (row.assignment_slug) {
17454
- const assignmentPath = resolve43(
18244
+ const assignmentPath = resolve44(
17455
18245
  projectsDir2,
17456
18246
  row.project_slug,
17457
18247
  "assignments",
@@ -17503,7 +18293,7 @@ function skipped(check, reason) {
17503
18293
 
17504
18294
  // src/utils/doctor/checks/integrations.ts
17505
18295
  init_fs();
17506
- import { resolve as resolve44, dirname as dirname12, basename as basename4 } from "path";
18296
+ import { resolve as resolve45, dirname as dirname13, basename as basename5 } from "path";
17507
18297
  import { readdir as readdir16, readFile as readFile27 } from "fs/promises";
17508
18298
  import { homedir as homedir6 } from "os";
17509
18299
  var CATEGORY6 = "integrations";
@@ -17586,7 +18376,7 @@ var backupConfigured = {
17586
18376
  }
17587
18377
  };
17588
18378
  async function readKnownMarketplaces() {
17589
- const path = resolve44(homedir6(), ".claude", "plugins", "known_marketplaces.json");
18379
+ const path = resolve45(homedir6(), ".claude", "plugins", "known_marketplaces.json");
17590
18380
  if (!await fileExists(path)) return {};
17591
18381
  try {
17592
18382
  const raw = await readFile27(path, "utf-8");
@@ -17605,8 +18395,8 @@ var claudeMarketplaceRegistered = {
17605
18395
  if (!await fileExists(dir)) {
17606
18396
  return skipped2(this, "claudePluginDir does not exist (run install-plugin)");
17607
18397
  }
17608
- const pluginsParent = dirname12(dir);
17609
- if (basename4(pluginsParent) !== "plugins") {
18398
+ const pluginsParent = dirname13(dir);
18399
+ if (basename5(pluginsParent) !== "plugins") {
17610
18400
  return {
17611
18401
  id: this.id,
17612
18402
  category: this.category,
@@ -17622,8 +18412,8 @@ var claudeMarketplaceRegistered = {
17622
18412
  autoFixable: false
17623
18413
  };
17624
18414
  }
17625
- const marketplaceRoot = dirname12(pluginsParent);
17626
- const marketplaceManifest = resolve44(marketplaceRoot, ".claude-plugin", "marketplace.json");
18415
+ const marketplaceRoot = dirname13(pluginsParent);
18416
+ const marketplaceManifest = resolve45(marketplaceRoot, ".claude-plugin", "marketplace.json");
17627
18417
  if (!await fileExists(marketplaceManifest)) {
17628
18418
  return {
17629
18419
  id: this.id,
@@ -17654,7 +18444,7 @@ var claudeMarketplaceRegistered = {
17654
18444
  autoFixable: false
17655
18445
  };
17656
18446
  }
17657
- const marketplaceName = parsed.name ?? basename4(marketplaceRoot);
18447
+ const marketplaceName = parsed.name ?? basename5(marketplaceRoot);
17658
18448
  const hasSyntaurEntry = (parsed.plugins ?? []).some((p) => p?.name === "syntaur");
17659
18449
  const known = await readKnownMarketplaces();
17660
18450
  const registered = known[marketplaceName]?.installLocation === marketplaceRoot || Object.values(known).some((v) => v.installLocation === marketplaceRoot);
@@ -17674,7 +18464,7 @@ var claudeMarketplaceRegistered = {
17674
18464
  title: this.title,
17675
18465
  status: "error",
17676
18466
  detail: issues.join("; "),
17677
- affected: [marketplaceManifest, resolve44(homedir6(), ".claude", "plugins", "known_marketplaces.json")],
18467
+ affected: [marketplaceManifest, resolve45(homedir6(), ".claude", "plugins", "known_marketplaces.json")],
17678
18468
  remediation: {
17679
18469
  kind: "manual",
17680
18470
  suggestion: "Re-run install-plugin to ensure both files are in sync.",
@@ -17714,7 +18504,7 @@ function skipped2(check, reason) {
17714
18504
  init_fs();
17715
18505
  init_parser();
17716
18506
  init_types();
17717
- import { resolve as resolve45 } from "path";
18507
+ import { resolve as resolve46 } from "path";
17718
18508
  import { readFile as readFile28 } from "fs/promises";
17719
18509
  var CATEGORY7 = "workspace";
17720
18510
  var ASSIGNMENT_FIELDS = ["projectSlug", "assignmentSlug", "projectDir", "assignmentDir"];
@@ -17727,7 +18517,7 @@ function isStandaloneSession(ctx) {
17727
18517
  return !hasAnyAssignmentField(ctx) && typeof ctx.sessionId === "string" && ctx.sessionId.length > 0;
17728
18518
  }
17729
18519
  async function loadContext(ctx) {
17730
- const path = resolve45(ctx.cwd, ".syntaur", "context.json");
18520
+ const path = resolve46(ctx.cwd, ".syntaur", "context.json");
17731
18521
  if (!await fileExists(path)) {
17732
18522
  return { data: null, path, exists: false, parseError: null };
17733
18523
  }
@@ -17807,7 +18597,7 @@ var contextAssignmentResolves = {
17807
18597
  if (!exists) return skipped3(this, "no context to resolve");
17808
18598
  if (isStandaloneSession(data)) return skipped3(this, "standalone session context \u2014 no assignment to resolve");
17809
18599
  if (!data?.assignmentDir) return skipped3(this, "context has no assignmentDir");
17810
- const assignmentMd = resolve45(data.assignmentDir, "assignment.md");
18600
+ const assignmentMd = resolve46(data.assignmentDir, "assignment.md");
17811
18601
  if (!await fileExists(assignmentMd)) {
17812
18602
  return {
17813
18603
  id: this.id,
@@ -17836,7 +18626,7 @@ var contextTerminal = {
17836
18626
  if (!exists) return skipped3(this, "no context to check");
17837
18627
  if (isStandaloneSession(data)) return skipped3(this, "standalone session context \u2014 no assignment to check");
17838
18628
  if (!data?.assignmentDir) return skipped3(this, "context has no assignmentDir");
17839
- const assignmentMd = resolve45(data.assignmentDir, "assignment.md");
18629
+ const assignmentMd = resolve46(data.assignmentDir, "assignment.md");
17840
18630
  if (!await fileExists(assignmentMd)) return skipped3(this, "assignment file missing");
17841
18631
  try {
17842
18632
  const content = await readFile28(assignmentMd, "utf-8");
@@ -17984,13 +18774,13 @@ var agentChecks = [agentsResolvable];
17984
18774
 
17985
18775
  // src/utils/doctor/checks/skills.ts
17986
18776
  init_fs();
17987
- import { resolve as resolve46, join as join5 } from "path";
18777
+ import { resolve as resolve47, join as join5 } from "path";
17988
18778
  import { readdir as readdir17, readFile as readFile29, lstat as lstat4 } from "fs/promises";
17989
18779
  import { homedir as homedir7 } from "os";
17990
18780
  var CATEGORY9 = "skills";
17991
18781
  var skillTargets = [
17992
- { agent: "claude", dir: resolve46(homedir7(), ".claude", "skills"), label: "~/.claude/skills" },
17993
- { agent: "codex", dir: resolve46(homedir7(), ".codex", "skills"), label: "~/.codex/skills" }
18782
+ { agent: "claude", dir: resolve47(homedir7(), ".claude", "skills"), label: "~/.claude/skills" },
18783
+ { agent: "codex", dir: resolve47(homedir7(), ".codex", "skills"), label: "~/.codex/skills" }
17994
18784
  ];
17995
18785
  var skillsDedupCheck = {
17996
18786
  id: "skills.dedup",
@@ -18156,14 +18946,14 @@ async function finalize(checks) {
18156
18946
  async function readVersion() {
18157
18947
  try {
18158
18948
  const here = fileURLToPath6(import.meta.url);
18159
- let dir = dirname13(here);
18949
+ let dir = dirname14(here);
18160
18950
  for (let i = 0; i < 6; i++) {
18161
18951
  try {
18162
18952
  const raw = await readFile30(join6(dir, "package.json"), "utf-8");
18163
18953
  const parsed = JSON.parse(raw);
18164
18954
  return typeof parsed.version === "string" ? parsed.version : null;
18165
18955
  } catch {
18166
- dir = dirname13(dir);
18956
+ dir = dirname14(dir);
18167
18957
  }
18168
18958
  }
18169
18959
  return null;
@@ -18256,7 +19046,7 @@ var REQUIRED_WORKSPACE_FIELDS = [
18256
19046
  ];
18257
19047
  var ISO_DATE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z$/;
18258
19048
  async function validateAssignmentFile(inputPath, cwd = process.cwd()) {
18259
- const absolute = isAbsolute7(inputPath) ? inputPath : resolve47(cwd, inputPath);
19049
+ const absolute = isAbsolute7(inputPath) ? inputPath : resolve48(cwd, inputPath);
18260
19050
  const errors = [];
18261
19051
  const warnings = [];
18262
19052
  if (!await fileExists(absolute)) {
@@ -18593,7 +19383,7 @@ init_uuid();
18593
19383
  init_timestamp();
18594
19384
  init_assignment_resolver();
18595
19385
  init_templates();
18596
- import { resolve as resolve48 } from "path";
19386
+ import { resolve as resolve49 } from "path";
18597
19387
  import { readFile as readFile32 } from "fs/promises";
18598
19388
  function shortId() {
18599
19389
  return generateId().split("-")[0];
@@ -18624,7 +19414,7 @@ async function commentCommand(target, text, options = {}) {
18624
19414
  if (!isValidSlug(target)) {
18625
19415
  throw new Error(`Invalid assignment slug "${target}".`);
18626
19416
  }
18627
- assignmentDir = resolve48(baseDir, options.project, "assignments", target);
19417
+ assignmentDir = resolve49(baseDir, options.project, "assignments", target);
18628
19418
  assignmentRef = target;
18629
19419
  } else {
18630
19420
  const resolved = await resolveAssignmentById(baseDir, assignmentsDir(), target);
@@ -18634,7 +19424,7 @@ async function commentCommand(target, text, options = {}) {
18634
19424
  assignmentDir = resolved.assignmentDir;
18635
19425
  assignmentRef = resolved.standalone ? resolved.id : resolved.assignmentSlug;
18636
19426
  }
18637
- const commentsPath = resolve48(assignmentDir, "comments.md");
19427
+ const commentsPath = resolve49(assignmentDir, "comments.md");
18638
19428
  const timestamp = nowTimestamp();
18639
19429
  const author = options.author ?? process.env.USER ?? "unknown";
18640
19430
  let currentContent;
@@ -18674,8 +19464,8 @@ ${entry}`;
18674
19464
  }
18675
19465
 
18676
19466
  // src/commands/capture.ts
18677
- import { resolve as resolve51, relative as relative4 } from "path";
18678
- import { copyFile as copyFile3, mkdir as mkdir7, realpath as realpath2, stat as stat6 } from "fs/promises";
19467
+ import { resolve as resolve52, relative as relative4 } from "path";
19468
+ import { copyFile as copyFile3, mkdir as mkdir7, realpath as realpath2, stat as stat7 } from "fs/promises";
18679
19469
 
18680
19470
  // src/utils/assignment-target.ts
18681
19471
  init_paths();
@@ -18684,12 +19474,12 @@ init_config2();
18684
19474
  init_slug();
18685
19475
  init_assignment_resolver();
18686
19476
  init_parser();
18687
- import { resolve as resolve49 } from "path";
19477
+ import { resolve as resolve50 } from "path";
18688
19478
  import { readFile as readFile33 } from "fs/promises";
18689
19479
  var AssignmentTargetError = class extends Error {
18690
19480
  };
18691
19481
  async function readAssignmentFrontmatterId(assignmentDir) {
18692
- const path = resolve49(assignmentDir, "assignment.md");
19482
+ const path = resolve50(assignmentDir, "assignment.md");
18693
19483
  if (!await fileExists(path)) return null;
18694
19484
  try {
18695
19485
  const content = await readFile33(path, "utf-8");
@@ -18700,7 +19490,7 @@ async function readAssignmentFrontmatterId(assignmentDir) {
18700
19490
  }
18701
19491
  }
18702
19492
  async function readContextJson(cwd) {
18703
- const path = resolve49(cwd, ".syntaur", "context.json");
19493
+ const path = resolve50(cwd, ".syntaur", "context.json");
18704
19494
  if (!await fileExists(path)) return null;
18705
19495
  try {
18706
19496
  const raw = await readFile33(path, "utf-8");
@@ -18724,15 +19514,15 @@ async function resolveAssignmentTarget(input4, opts = {}) {
18724
19514
  if (!isValidSlug(input4)) {
18725
19515
  throw new AssignmentTargetError(`Invalid assignment slug "${input4}".`);
18726
19516
  }
18727
- const projectDir = resolve49(baseDir, opts.project);
18728
- const projectMdPath = resolve49(projectDir, "project.md");
19517
+ const projectDir = resolve50(baseDir, opts.project);
19518
+ const projectMdPath = resolve50(projectDir, "project.md");
18729
19519
  if (!await fileExists(projectDir) || !await fileExists(projectMdPath)) {
18730
19520
  throw new AssignmentTargetError(
18731
19521
  `Project "${opts.project}" not found at ${projectDir}.`
18732
19522
  );
18733
19523
  }
18734
- const assignmentDir = resolve49(projectDir, "assignments", input4);
18735
- const assignmentMdPath2 = resolve49(assignmentDir, "assignment.md");
19524
+ const assignmentDir = resolve50(projectDir, "assignments", input4);
19525
+ const assignmentMdPath2 = resolve50(assignmentDir, "assignment.md");
18736
19526
  if (!await fileExists(assignmentMdPath2)) {
18737
19527
  throw new AssignmentTargetError(
18738
19528
  `Assignment "${input4}" not found in project "${opts.project}".`
@@ -18766,7 +19556,7 @@ async function resolveAssignmentTarget(input4, opts = {}) {
18766
19556
  }
18767
19557
  if (ctx.assignmentDir) {
18768
19558
  const dir = expandHome(ctx.assignmentDir);
18769
- const assignmentMdPath2 = resolve49(dir, "assignment.md");
19559
+ const assignmentMdPath2 = resolve50(dir, "assignment.md");
18770
19560
  if (!await fileExists(assignmentMdPath2)) {
18771
19561
  throw new AssignmentTargetError(
18772
19562
  `.syntaur/context.json points to a missing assignment dir: ${dir}.`
@@ -18795,8 +19585,8 @@ async function resolveAssignmentTarget(input4, opts = {}) {
18795
19585
  `.syntaur/context.json contains invalid slugs: project="${ctx.projectSlug}" assignment="${ctx.assignmentSlug}".`
18796
19586
  );
18797
19587
  }
18798
- const assignmentDir = resolve49(baseDir, ctx.projectSlug, "assignments", ctx.assignmentSlug);
18799
- const assignmentMdPath2 = resolve49(assignmentDir, "assignment.md");
19588
+ const assignmentDir = resolve50(baseDir, ctx.projectSlug, "assignments", ctx.assignmentSlug);
19589
+ const assignmentMdPath2 = resolve50(assignmentDir, "assignment.md");
18800
19590
  if (!await fileExists(assignmentMdPath2)) {
18801
19591
  throw new AssignmentTargetError(
18802
19592
  `.syntaur/context.json points to a missing assignment: ${assignmentDir}.`
@@ -18855,48 +19645,122 @@ function isArtifactKind(value) {
18855
19645
  // src/commands/capture.ts
18856
19646
  init_fs();
18857
19647
 
18858
- // src/db/proof-db.ts
18859
- init_paths();
18860
- import Database3 from "better-sqlite3";
18861
- import { resolve as resolve50 } from "path";
18862
- var db2 = null;
18863
- var PROOF_SCHEMA_VERSION = "1";
18864
- var SCHEMA_SQL2 = `
18865
- CREATE TABLE IF NOT EXISTS artifacts (
18866
- id TEXT PRIMARY KEY,
18867
- assignment_id TEXT NOT NULL,
18868
- assignment_dir TEXT NOT NULL,
18869
- criterion_index INTEGER,
18870
- kind TEXT NOT NULL,
18871
- file_path TEXT,
18872
- note TEXT,
18873
- created_at TEXT NOT NULL DEFAULT (datetime('now'))
18874
- );
18875
- CREATE INDEX IF NOT EXISTS idx_artifacts_assignment ON artifacts(assignment_id);
18876
- CREATE INDEX IF NOT EXISTS idx_artifacts_assignment_criterion ON artifacts(assignment_id, criterion_index);
18877
- CREATE TABLE IF NOT EXISTS meta (key TEXT PRIMARY KEY, value TEXT);
18878
- `;
18879
- function initProofDb(dbPath) {
18880
- if (db2) return db2;
18881
- const finalPath = dbPath ?? resolve50(syntaurRoot(), "syntaur.db");
18882
- db2 = new Database3(finalPath);
18883
- db2.pragma("journal_mode = WAL");
18884
- db2.exec(SCHEMA_SQL2);
18885
- db2.prepare("INSERT OR IGNORE INTO meta (key, value) VALUES (?, ?)").run(
18886
- "proof_schema_version",
19648
+ // src/utils/screencapture.ts
19649
+ import { spawn as spawn4 } from "child_process";
19650
+ import { mkdtemp as mkdtemp2, rm as rm6, stat as stat6 } from "fs/promises";
19651
+ import { tmpdir as tmpdir2 } from "os";
19652
+ import { join as join7 } from "path";
19653
+ function argsFor(mode, pngPath) {
19654
+ switch (mode) {
19655
+ case "interactive":
19656
+ return ["-i", pngPath];
19657
+ case "window":
19658
+ return ["-iWo", pngPath];
19659
+ case "fullscreen":
19660
+ return ["-x", pngPath];
19661
+ }
19662
+ }
19663
+ function runScreencapture(args) {
19664
+ return new Promise((resolvePromise, reject) => {
19665
+ const child = spawn4("screencapture", args, { stdio: ["ignore", "pipe", "pipe"] });
19666
+ let settled = false;
19667
+ child.once("error", (err2) => {
19668
+ if (settled) return;
19669
+ settled = true;
19670
+ reject(err2);
19671
+ });
19672
+ child.once("close", (code) => {
19673
+ if (settled) return;
19674
+ settled = true;
19675
+ resolvePromise(code ?? -1);
19676
+ });
19677
+ });
19678
+ }
19679
+ async function captureScreenshot(mode) {
19680
+ if (process.platform !== "darwin") {
19681
+ throw new Error(
19682
+ "screencapture is only available on macOS. Use --file <path> to attach an existing image."
19683
+ );
19684
+ }
19685
+ const tmpDir = await mkdtemp2(join7(tmpdir2(), "syntaur-screenshot-"));
19686
+ const pngPath = join7(tmpDir, "shot.png");
19687
+ const cleanup = async () => {
19688
+ await rm6(tmpDir, { recursive: true, force: true }).catch(() => {
19689
+ });
19690
+ };
19691
+ try {
19692
+ let code;
19693
+ try {
19694
+ code = await runScreencapture(argsFor(mode, pngPath));
19695
+ } catch (err2) {
19696
+ if (err2 && typeof err2 === "object" && err2.code === "ENOENT") {
19697
+ throw new Error(
19698
+ "screencapture binary not found. Is this macOS with the system utility on PATH?"
19699
+ );
19700
+ }
19701
+ throw err2;
19702
+ }
19703
+ if (code !== 0) {
19704
+ throw new Error(`Screenshot canceled or failed (exit ${code}).`);
19705
+ }
19706
+ let size = 0;
19707
+ try {
19708
+ size = (await stat6(pngPath)).size;
19709
+ } catch {
19710
+ throw new Error("screencapture exited 0 but produced no image.");
19711
+ }
19712
+ if (size === 0) {
19713
+ throw new Error("screencapture exited 0 but produced no image.");
19714
+ }
19715
+ return { pngPath, cleanup };
19716
+ } catch (err2) {
19717
+ await cleanup();
19718
+ throw err2;
19719
+ }
19720
+ }
19721
+
19722
+ // src/db/proof-db.ts
19723
+ init_paths();
19724
+ import Database4 from "better-sqlite3";
19725
+ import { resolve as resolve51 } from "path";
19726
+ var db3 = null;
19727
+ var PROOF_SCHEMA_VERSION = "1";
19728
+ var SCHEMA_SQL3 = `
19729
+ CREATE TABLE IF NOT EXISTS artifacts (
19730
+ id TEXT PRIMARY KEY,
19731
+ assignment_id TEXT NOT NULL,
19732
+ assignment_dir TEXT NOT NULL,
19733
+ criterion_index INTEGER,
19734
+ kind TEXT NOT NULL,
19735
+ file_path TEXT,
19736
+ note TEXT,
19737
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
19738
+ );
19739
+ CREATE INDEX IF NOT EXISTS idx_artifacts_assignment ON artifacts(assignment_id);
19740
+ CREATE INDEX IF NOT EXISTS idx_artifacts_assignment_criterion ON artifacts(assignment_id, criterion_index);
19741
+ CREATE TABLE IF NOT EXISTS meta (key TEXT PRIMARY KEY, value TEXT);
19742
+ `;
19743
+ function initProofDb(dbPath) {
19744
+ if (db3) return db3;
19745
+ const finalPath = dbPath ?? resolve51(syntaurRoot(), "syntaur.db");
19746
+ db3 = new Database4(finalPath);
19747
+ db3.pragma("journal_mode = WAL");
19748
+ db3.exec(SCHEMA_SQL3);
19749
+ db3.prepare("INSERT OR IGNORE INTO meta (key, value) VALUES (?, ?)").run(
19750
+ "proof_schema_version",
18887
19751
  PROOF_SCHEMA_VERSION
18888
19752
  );
18889
- const database = db2;
19753
+ const database = db3;
18890
19754
  const runMigrations = database.transaction(() => {
18891
19755
  });
18892
19756
  runMigrations.exclusive();
18893
- return db2;
19757
+ return db3;
18894
19758
  }
18895
19759
  function getProofDb() {
18896
- if (!db2) {
19760
+ if (!db3) {
18897
19761
  throw new Error("Proof database not initialized. Call initProofDb() first.");
18898
19762
  }
18899
- return db2;
19763
+ return db3;
18900
19764
  }
18901
19765
  function insertArtifact(input4) {
18902
19766
  const database = getProofDb();
@@ -18959,6 +19823,27 @@ async function captureCommand(target, options = {}) {
18959
19823
  );
18960
19824
  }
18961
19825
  const kind = options.kind;
19826
+ const shelloutFlagCount = [
19827
+ options.interactive,
19828
+ options.window,
19829
+ options.fullscreen
19830
+ ].filter(Boolean).length;
19831
+ if (shelloutFlagCount > 1) {
19832
+ throw new Error("--interactive, --window, --fullscreen are mutually exclusive.");
19833
+ }
19834
+ const shelloutMode = options.interactive ? "interactive" : options.window ? "window" : options.fullscreen ? "fullscreen" : null;
19835
+ if (shelloutMode) {
19836
+ if (kind !== "screenshot") {
19837
+ throw new Error(
19838
+ "--interactive, --window, and --fullscreen require --kind=screenshot."
19839
+ );
19840
+ }
19841
+ if (options.file) {
19842
+ throw new Error(
19843
+ "--file cannot be combined with --interactive, --window, or --fullscreen."
19844
+ );
19845
+ }
19846
+ }
18962
19847
  if (kind === "text") {
18963
19848
  if (options.file) {
18964
19849
  throw new Error("--kind=text forbids --file. Use --note for text payloads.");
@@ -18967,7 +19852,7 @@ async function captureCommand(target, options = {}) {
18967
19852
  throw new Error("--kind=text requires --note.");
18968
19853
  }
18969
19854
  } else {
18970
- if (kind !== "http" && !options.file) {
19855
+ if (kind !== "http" && !options.file && !shelloutMode) {
18971
19856
  throw new Error(`--kind=${kind} requires --file.`);
18972
19857
  }
18973
19858
  if (kind === "http" && !options.file && (!options.note || options.note.trim() === "")) {
@@ -18981,8 +19866,9 @@ async function captureCommand(target, options = {}) {
18981
19866
  cwd: options.cwd
18982
19867
  });
18983
19868
  let resolvedSource = null;
19869
+ let shelloutCleanup = null;
18984
19870
  if (options.file) {
18985
- const expanded = options.file.startsWith("~/") ? resolve51(process.env.HOME ?? "", options.file.slice(2)) : resolve51(options.file);
19871
+ const expanded = options.file.startsWith("~/") ? resolve52(process.env.HOME ?? "", options.file.slice(2)) : resolve52(options.file);
18986
19872
  if (!await fileExists(expanded)) {
18987
19873
  throw new Error(`--file does not exist: ${options.file}`);
18988
19874
  }
@@ -18994,72 +19880,93 @@ async function captureCommand(target, options = {}) {
18994
19880
  `--file is unreadable: ${options.file} (${e instanceof Error ? e.message : String(e)})`
18995
19881
  );
18996
19882
  }
18997
- const st = await stat6(real);
19883
+ const st = await stat7(real);
18998
19884
  if (!st.isFile()) {
18999
19885
  throw new Error(`--file is not a regular file: ${options.file}`);
19000
19886
  }
19001
19887
  resolvedSource = real;
19888
+ } else if (shelloutMode) {
19889
+ const { pngPath, cleanup } = await captureScreenshot(shelloutMode);
19890
+ resolvedSource = pngPath;
19891
+ shelloutCleanup = cleanup;
19002
19892
  }
19003
- if (!resolved.id || resolved.id.trim() === "") {
19004
- throw new Error(
19005
- `Resolved assignment is missing a frontmatter \`id\`: ${resolved.assignmentDir}. Cannot record artifact.`
19006
- );
19007
- }
19008
- initProofDb();
19009
- const subdir = criterionIndex === null ? "untagged" : String(criterionIndex);
19010
- const destDir = resolve51(proofDir(resolved.assignmentDir), subdir);
19011
- if (resolvedSource) await mkdir7(destDir, { recursive: true });
19012
- const ext = resolvedSource ? extensionForKind(kind) : null;
19013
- let id = null;
19014
- let relativeFilePath = null;
19015
- let absPath = null;
19016
- let lastErr = null;
19017
- for (let attempt = 0; attempt < MAX_ID_RETRIES; attempt += 1) {
19018
- const candidate = generateArtifactId();
19019
- const candidateAbsPath = resolvedSource && ext ? resolve51(destDir, `${candidate}.${ext}`) : null;
19020
- const candidateRel = candidateAbsPath ? relative4(resolved.assignmentDir, candidateAbsPath) : null;
19021
- try {
19022
- insertArtifact({
19023
- id: candidate,
19024
- assignmentId: resolved.id,
19025
- assignmentDir: resolved.assignmentDir,
19026
- criterionIndex,
19027
- kind,
19028
- filePath: candidateRel,
19029
- note: options.note ?? null
19030
- });
19031
- id = candidate;
19032
- absPath = candidateAbsPath;
19033
- relativeFilePath = candidateRel;
19034
- break;
19035
- } catch (e) {
19036
- if (isUniqueConstraintError(e)) {
19037
- lastErr = e;
19038
- continue;
19893
+ try {
19894
+ if (!resolved.id || resolved.id.trim() === "") {
19895
+ throw new Error(
19896
+ `Resolved assignment is missing a frontmatter \`id\`: ${resolved.assignmentDir}. Cannot record artifact.`
19897
+ );
19898
+ }
19899
+ initProofDb();
19900
+ const subdir = criterionIndex === null ? "untagged" : String(criterionIndex);
19901
+ const destDir = resolve52(proofDir(resolved.assignmentDir), subdir);
19902
+ if (resolvedSource) await mkdir7(destDir, { recursive: true });
19903
+ const ext = resolvedSource ? extensionForKind(kind) : null;
19904
+ let id = null;
19905
+ let relativeFilePath = null;
19906
+ let absPath = null;
19907
+ let lastErr = null;
19908
+ for (let attempt = 0; attempt < MAX_ID_RETRIES; attempt += 1) {
19909
+ const candidate = generateArtifactId();
19910
+ const candidateAbsPath = resolvedSource && ext ? resolve52(destDir, `${candidate}.${ext}`) : null;
19911
+ const candidateRel = candidateAbsPath ? relative4(resolved.assignmentDir, candidateAbsPath) : null;
19912
+ try {
19913
+ insertArtifact({
19914
+ id: candidate,
19915
+ assignmentId: resolved.id,
19916
+ assignmentDir: resolved.assignmentDir,
19917
+ criterionIndex,
19918
+ kind,
19919
+ filePath: candidateRel,
19920
+ note: options.note ?? null
19921
+ });
19922
+ id = candidate;
19923
+ absPath = candidateAbsPath;
19924
+ relativeFilePath = candidateRel;
19925
+ break;
19926
+ } catch (e) {
19927
+ if (isUniqueConstraintError(e)) {
19928
+ lastErr = e;
19929
+ continue;
19930
+ }
19931
+ throw e;
19039
19932
  }
19040
- throw e;
19041
19933
  }
19042
- }
19043
- if (!id) {
19044
- throw new Error(
19045
- `Failed to generate a unique artifact id after ${MAX_ID_RETRIES} attempts: ${lastErr instanceof Error ? lastErr.message : String(lastErr)}`
19046
- );
19047
- }
19048
- if (resolvedSource && absPath) {
19049
- await copyFile3(resolvedSource, absPath);
19050
- }
19051
- const ref = resolved.standalone ? resolved.id : `${resolved.projectSlug}/${resolved.assignmentSlug}`;
19052
- const tagSuffix = criterionIndex === null ? "untagged" : `criterion ${criterionIndex}`;
19053
- console.log(`Captured artifact ${id} (${kind}) for ${ref} \u2014 ${tagSuffix}.`);
19054
- if (relativeFilePath) {
19055
- console.log(` file: ${resolve51(resolved.assignmentDir, relativeFilePath)}`);
19934
+ if (!id) {
19935
+ throw new Error(
19936
+ `Failed to generate a unique artifact id after ${MAX_ID_RETRIES} attempts: ${lastErr instanceof Error ? lastErr.message : String(lastErr)}`
19937
+ );
19938
+ }
19939
+ if (resolvedSource && absPath) {
19940
+ try {
19941
+ await copyFile3(resolvedSource, absPath);
19942
+ } catch (err2) {
19943
+ if (shelloutCleanup) {
19944
+ console.error(
19945
+ `Screenshot saved at ${resolvedSource} \u2014 DB row inserted but copy to proof dir failed. Recover with: mv ${resolvedSource} ${absPath}`
19946
+ );
19947
+ shelloutCleanup = null;
19948
+ }
19949
+ throw err2;
19950
+ }
19951
+ }
19952
+ const ref = resolved.standalone ? resolved.id : `${resolved.projectSlug}/${resolved.assignmentSlug}`;
19953
+ const tagSuffix = criterionIndex === null ? "untagged" : `criterion ${criterionIndex}`;
19954
+ console.log(`Captured artifact ${id} (${kind}) for ${ref} \u2014 ${tagSuffix}.`);
19955
+ if (relativeFilePath) {
19956
+ console.log(` file: ${resolve52(resolved.assignmentDir, relativeFilePath)}`);
19957
+ }
19958
+ } finally {
19959
+ if (shelloutCleanup) {
19960
+ await shelloutCleanup();
19961
+ shelloutCleanup = null;
19962
+ }
19056
19963
  }
19057
19964
  }
19058
19965
 
19059
19966
  // src/commands/proof.ts
19060
19967
  import { Command as Command5 } from "commander";
19061
- import { readFile as readFile34, writeFile as writeFile10, rename as rename7, stat as stat7 } from "fs/promises";
19062
- import { resolve as resolve52, relative as relative5, isAbsolute as isAbsolute8 } from "path";
19968
+ import { readFile as readFile34, writeFile as writeFile10, rename as rename7, stat as stat8 } from "fs/promises";
19969
+ import { resolve as resolve53, relative as relative5, isAbsolute as isAbsolute8 } from "path";
19063
19970
  import { randomBytes as randomBytes3 } from "crypto";
19064
19971
 
19065
19972
  // src/utils/acceptance-criteria-parse.ts
@@ -19262,7 +20169,7 @@ function renderProofHtml(params2, inlineFiles = /* @__PURE__ */ new Map()) {
19262
20169
 
19263
20170
  // src/commands/proof.ts
19264
20171
  async function readAssignmentMeta(assignmentDir) {
19265
- const path = resolve52(assignmentDir, "assignment.md");
20172
+ const path = resolve53(assignmentDir, "assignment.md");
19266
20173
  if (!await fileExists(path)) {
19267
20174
  return { title: "", body: "" };
19268
20175
  }
@@ -19313,7 +20220,7 @@ async function loadInlineFiles(rows, assignmentDir) {
19313
20220
  for (const r of rows) {
19314
20221
  if (!r.file_path) continue;
19315
20222
  if (r.kind !== "http" && r.kind !== "text") continue;
19316
- const abs = resolve52(assignmentDir, r.file_path);
20223
+ const abs = resolve53(assignmentDir, r.file_path);
19317
20224
  if (!isWithin(proofRoot, abs)) {
19318
20225
  out.set(r.file_path, null);
19319
20226
  continue;
@@ -19322,7 +20229,7 @@ async function loadInlineFiles(rows, assignmentDir) {
19322
20229
  out.set(r.file_path, null);
19323
20230
  continue;
19324
20231
  }
19325
- const st = await stat7(abs);
20232
+ const st = await stat8(abs);
19326
20233
  if (st.size > INLINE_TEXT_LIMIT_BYTES) {
19327
20234
  out.set(r.file_path, null);
19328
20235
  continue;
@@ -19361,8 +20268,8 @@ async function proofBuildCommand(target, options = {}) {
19361
20268
  };
19362
20269
  const md = renderProofMarkdown(renderParams);
19363
20270
  const html = renderProofHtml(renderParams, inlineFiles);
19364
- const mdPath = resolve52(resolved.assignmentDir, "proof.md");
19365
- const htmlPath = resolve52(resolved.assignmentDir, "proof.html");
20271
+ const mdPath = resolve53(resolved.assignmentDir, "proof.md");
20272
+ const htmlPath = resolve53(resolved.assignmentDir, "proof.html");
19366
20273
  await atomicWrite(mdPath, md);
19367
20274
  await atomicWrite(htmlPath, html);
19368
20275
  console.log(`Wrote ${htmlPath}`);
@@ -19379,6 +20286,478 @@ proofCommand.command("build").description("Render proof.html and proof.md for an
19379
20286
  }
19380
20287
  });
19381
20288
 
20289
+ // src/commands/lease.ts
20290
+ import { Command as Command6 } from "commander";
20291
+ var DURATION_REGEX = /^(\d+)\s*(s|m|h|d)?$/i;
20292
+ function parseDuration(input4, fallbackSeconds) {
20293
+ if (!input4) return fallbackSeconds;
20294
+ const match = DURATION_REGEX.exec(input4.trim());
20295
+ if (!match) {
20296
+ throw new Error(
20297
+ `invalid duration "${input4}" \u2014 use e.g. 30s, 5m, 2h, 1d`
20298
+ );
20299
+ }
20300
+ const n = Number.parseInt(match[1], 10);
20301
+ const unit = (match[2] ?? "s").toLowerCase();
20302
+ const mult = { s: 1, m: 60, h: 3600, d: 86400 };
20303
+ return n * mult[unit];
20304
+ }
20305
+ function parseMetadataFlags(values) {
20306
+ if (!values || values.length === 0) return void 0;
20307
+ const out = {};
20308
+ for (const raw of values) {
20309
+ const eq = raw.indexOf("=");
20310
+ if (eq < 0) {
20311
+ throw new Error(`invalid -m flag "${raw}" \u2014 use key=value`);
20312
+ }
20313
+ out[raw.slice(0, eq)] = raw.slice(eq + 1);
20314
+ }
20315
+ return out;
20316
+ }
20317
+ var leaseCommand = new Command6("lease").description(
20318
+ "Resource leases \u2014 claim and release coordinated shared resources"
20319
+ );
20320
+ leaseCommand.command("create-inventory").description("Create a new inventory of leaseable resources").argument("<slug>", "Inventory slug (e.g. dev-envs, prod-migration-lock)").requiredOption("--kind <kind>", "Inventory kind (free-form label, e.g. dev-env, db-instance, lock)").option("--display-name <text>", "Human-readable display name").option("--default-ttl <duration>", "Default lease TTL (e.g. 30m, 2h). Default: 30m", "30m").action(async (slug, opts) => {
20321
+ try {
20322
+ initLeasesDb();
20323
+ const ttl_s = parseDuration(opts.defaultTtl, 30 * 60);
20324
+ const row = createInventory({
20325
+ slug,
20326
+ kind: opts.kind,
20327
+ display_name: opts.displayName,
20328
+ default_ttl_s: ttl_s
20329
+ });
20330
+ console.log(`Created inventory '${row.slug}' (kind=${row.kind}, default_ttl=${row.default_ttl_s}s).`);
20331
+ } catch (error) {
20332
+ if (error instanceof DuplicateInventoryError) {
20333
+ console.error(`Error: inventory '${error.slug}' already exists`);
20334
+ process.exit(1);
20335
+ }
20336
+ const message = error instanceof Error ? error.message : String(error);
20337
+ console.error(`Error: ${message}`);
20338
+ process.exit(1);
20339
+ }
20340
+ });
20341
+ var memberCommand = leaseCommand.command("member").description("Manage members of an inventory");
20342
+ memberCommand.command("add").description("Add a member to an inventory").argument("<inventory>", "Inventory slug").argument("<member-id>", "Member id (unique within the inventory)").option("-m, --metadata <key=value...>", "Metadata key=value pairs (repeatable)").action(async (inventory, memberId, opts) => {
20343
+ try {
20344
+ initLeasesDb();
20345
+ const metadata = parseMetadataFlags(opts.metadata);
20346
+ addMember({ inventory_slug: inventory, member_id: memberId, metadata });
20347
+ console.log(`Added member '${memberId}' to '${inventory}'.`);
20348
+ } catch (error) {
20349
+ if (error instanceof DuplicateMemberError) {
20350
+ console.error(`Error: member ${error.inventorySlug}/${error.memberId} already exists`);
20351
+ process.exit(1);
20352
+ }
20353
+ const message = error instanceof Error ? error.message : String(error);
20354
+ console.error(`Error: ${message}`);
20355
+ process.exit(1);
20356
+ }
20357
+ });
20358
+ memberCommand.command("retire").description("Retire a member (no longer claimable). Refuses if member is currently leased.").argument("<inventory>", "Inventory slug").argument("<member-id>", "Member id").action(async (inventory, memberId) => {
20359
+ try {
20360
+ initLeasesDb();
20361
+ retireMember(inventory, memberId);
20362
+ console.log(`Retired member '${inventory}/${memberId}'.`);
20363
+ } catch (error) {
20364
+ if (error instanceof MemberInUseError) {
20365
+ console.error(
20366
+ `Error: member ${error.inventorySlug}/${error.memberId} is currently leased; release before retiring`
20367
+ );
20368
+ process.exit(1);
20369
+ }
20370
+ if (error instanceof NotFoundError) {
20371
+ console.error(`Error: member not found`);
20372
+ process.exit(1);
20373
+ }
20374
+ const message = error instanceof Error ? error.message : String(error);
20375
+ console.error(`Error: ${message}`);
20376
+ process.exit(1);
20377
+ }
20378
+ });
20379
+ leaseCommand.command("claim").description("Claim an idle member of an inventory. Fail-fast unless --wait is set.").argument("<inventory>", "Inventory slug").option("--ttl <duration>", "Lease TTL (overrides inventory default)").option("--for <tag>", "Free-form requester tag (session id, assignment slug, etc.)").option(
20380
+ "--wait <duration>",
20381
+ "Block up to <duration> waiting for an idle member. Backoff: 100ms \u2192 200ms \u2192 400ms \u2192 800ms \u2192 1s cap."
20382
+ ).option("--json", "Output JSON instead of a one-line summary").action(
20383
+ async (inventory, opts) => {
20384
+ try {
20385
+ initLeasesDb();
20386
+ const detail = getInventoryDetail(inventory);
20387
+ if (!detail) {
20388
+ console.error(`Error: inventory '${inventory}' not found`);
20389
+ process.exit(1);
20390
+ return;
20391
+ }
20392
+ const ttl_s = parseDuration(opts.ttl, detail.inventory.default_ttl_s);
20393
+ const waitBudgetMs = opts.wait !== void 0 ? parseDuration(opts.wait, 0) * 1e3 : 0;
20394
+ const tryClaim = () => claimLease(inventory, ttl_s, opts.for);
20395
+ let result;
20396
+ if (waitBudgetMs <= 0) {
20397
+ result = tryClaim();
20398
+ } else {
20399
+ const deadline = Date.now() + waitBudgetMs;
20400
+ const backoffSchedule = [100, 200, 400, 800];
20401
+ let attempt = 0;
20402
+ while (true) {
20403
+ try {
20404
+ result = tryClaim();
20405
+ break;
20406
+ } catch (err2) {
20407
+ if (!(err2 instanceof NoIdleMemberError)) throw err2;
20408
+ const remaining = deadline - Date.now();
20409
+ if (remaining <= 0) {
20410
+ console.error(
20411
+ `Error: timed out waiting ${opts.wait} for an idle member of '${inventory}'`
20412
+ );
20413
+ process.exit(1);
20414
+ return;
20415
+ }
20416
+ const delay = Math.min(
20417
+ backoffSchedule[Math.min(attempt, backoffSchedule.length - 1)] ?? 1e3,
20418
+ 1e3,
20419
+ remaining
20420
+ );
20421
+ attempt += 1;
20422
+ await new Promise((r) => setTimeout(r, delay));
20423
+ }
20424
+ }
20425
+ }
20426
+ if (opts.json) {
20427
+ console.log(JSON.stringify(result, null, 2));
20428
+ } else {
20429
+ console.log(
20430
+ `Claimed ${result.member_id} as ${result.lease_id} (expires ${result.expires_at})`
20431
+ );
20432
+ }
20433
+ } catch (error) {
20434
+ if (error instanceof NoIdleMemberError) {
20435
+ console.error(`Error: no idle members in '${error.inventorySlug}'`);
20436
+ process.exit(1);
20437
+ }
20438
+ if (error instanceof LeaseContentionError) {
20439
+ console.error(`Error: contention timeout on '${error.inventorySlug}'; retry`);
20440
+ process.exit(1);
20441
+ }
20442
+ const message = error instanceof Error ? error.message : String(error);
20443
+ console.error(`Error: ${message}`);
20444
+ process.exit(1);
20445
+ }
20446
+ }
20447
+ );
20448
+ leaseCommand.command("release").description("Release a lease by its lease_id").argument("<lease-id>", "Opaque lease id returned by `claim`").action(async (leaseId) => {
20449
+ try {
20450
+ initLeasesDb();
20451
+ releaseLease(leaseId);
20452
+ console.log(`Released lease ${leaseId}.`);
20453
+ } catch (error) {
20454
+ if (error instanceof StaleLeaseError) {
20455
+ console.error(
20456
+ `Error: lease ${error.leaseId} is no longer active (expired or revoked)`
20457
+ );
20458
+ process.exit(1);
20459
+ }
20460
+ if (error instanceof LeaseContentionError) {
20461
+ console.error(`Error: contention timeout; retry`);
20462
+ process.exit(1);
20463
+ }
20464
+ const message = error instanceof Error ? error.message : String(error);
20465
+ console.error(`Error: ${message}`);
20466
+ process.exit(1);
20467
+ }
20468
+ });
20469
+ leaseCommand.command("extend").description("Extend a lease \u2014 sets new expires_at = now + ttl. Refuses if expired or stale.").argument("<lease-id>", "Opaque lease id").requiredOption("--ttl <duration>", "New TTL from now (e.g. 15m)").action(async (leaseId, opts) => {
20470
+ try {
20471
+ initLeasesDb();
20472
+ const ttl_s = parseDuration(opts.ttl, 0);
20473
+ if (ttl_s <= 0) {
20474
+ throw new Error("--ttl must be positive");
20475
+ }
20476
+ const res = extendLease(leaseId, ttl_s);
20477
+ console.log(`Extended lease ${leaseId} (new expires_at=${res.new_expires_at}).`);
20478
+ } catch (error) {
20479
+ if (error instanceof StaleLeaseError) {
20480
+ console.error(
20481
+ `Error: lease ${error.leaseId} cannot be extended (expired, revoked, or member generation advanced)`
20482
+ );
20483
+ process.exit(1);
20484
+ }
20485
+ if (error instanceof LeaseContentionError) {
20486
+ console.error(`Error: contention timeout; retry`);
20487
+ process.exit(1);
20488
+ }
20489
+ const message = error instanceof Error ? error.message : String(error);
20490
+ console.error(`Error: ${message}`);
20491
+ process.exit(1);
20492
+ }
20493
+ });
20494
+ leaseCommand.command("show").description("Show a single lease as JSON").argument("<lease-id>", "Lease id").action(async (leaseId) => {
20495
+ try {
20496
+ initLeasesDb();
20497
+ const row = getLease(leaseId);
20498
+ if (!row) {
20499
+ console.error(`Error: lease ${leaseId} not found`);
20500
+ process.exit(1);
20501
+ return;
20502
+ }
20503
+ console.log(JSON.stringify(row, null, 2));
20504
+ } catch (error) {
20505
+ const message = error instanceof Error ? error.message : String(error);
20506
+ console.error(`Error: ${message}`);
20507
+ process.exit(1);
20508
+ }
20509
+ });
20510
+ var VALID_STATES = /* @__PURE__ */ new Set([
20511
+ "active",
20512
+ "expired",
20513
+ "released",
20514
+ "revoked"
20515
+ ]);
20516
+ leaseCommand.command("list").description("List leases (optionally filtered by inventory and/or state)").option("--inventory <slug>", "Filter by inventory").option("--state <state>", "Filter by state: active|expired|released|revoked").option("--json", "Output JSON instead of a table").action(async (opts) => {
20517
+ try {
20518
+ initLeasesDb();
20519
+ let state;
20520
+ if (opts.state) {
20521
+ if (!VALID_STATES.has(opts.state)) {
20522
+ throw new Error(
20523
+ `--state must be one of: ${[...VALID_STATES].join("|")}`
20524
+ );
20525
+ }
20526
+ state = opts.state;
20527
+ }
20528
+ if (!opts.inventory && !state && !opts.json) {
20529
+ const inventories = listInventories();
20530
+ if (inventories.length === 0) {
20531
+ console.log("(no inventories \u2014 create one with `syntaur lease create-inventory <slug> --kind <kind>`)");
20532
+ return;
20533
+ }
20534
+ for (const inv of inventories) {
20535
+ const detail = getInventoryDetail(inv.slug);
20536
+ if (!detail) continue;
20537
+ const idleCount = detail.members.filter((m) => m.status === "idle").length;
20538
+ const leasedCount = detail.members.filter((m) => m.status === "leased").length;
20539
+ console.log(
20540
+ `${inv.slug.padEnd(24)} kind=${inv.kind} idle=${idleCount} leased=${leasedCount} active_leases=${detail.active_leases.length}`
20541
+ );
20542
+ }
20543
+ return;
20544
+ }
20545
+ const rows = listLeases({ inventory: opts.inventory, state });
20546
+ if (opts.json) {
20547
+ console.log(JSON.stringify(rows, null, 2));
20548
+ return;
20549
+ }
20550
+ if (rows.length === 0) {
20551
+ console.log("(no leases match)");
20552
+ return;
20553
+ }
20554
+ for (const r of rows) {
20555
+ console.log(
20556
+ `${r.lease_id} ${r.inventory_slug}/${r.member_id} ${r.state} expires=${r.expires_at}`
20557
+ );
20558
+ }
20559
+ } catch (error) {
20560
+ const message = error instanceof Error ? error.message : String(error);
20561
+ console.error(`Error: ${message}`);
20562
+ process.exit(1);
20563
+ }
20564
+ });
20565
+ leaseCommand.command("gc").description("Sweep expired leases across all inventories (idempotent, concurrency-safe)").action(async () => {
20566
+ try {
20567
+ initLeasesDb();
20568
+ const n = gcExpiredLeases();
20569
+ console.log(`Expired ${n} lease(s).`);
20570
+ } catch (error) {
20571
+ if (error instanceof LeaseContentionError) {
20572
+ console.error(`Error: contention timeout; retry`);
20573
+ process.exit(1);
20574
+ }
20575
+ const message = error instanceof Error ? error.message : String(error);
20576
+ console.error(`Error: ${message}`);
20577
+ process.exit(1);
20578
+ }
20579
+ });
20580
+ leaseCommand.command("revoke").description(
20581
+ "Force-release a lease (administrative). Idempotent \u2014 already-revoked exits 0."
20582
+ ).argument("<lease-id>", "Lease id to revoke").action(async (leaseId) => {
20583
+ try {
20584
+ initLeasesDb();
20585
+ const existing = getLease(leaseId);
20586
+ if (!existing) {
20587
+ console.error(`Error: lease ${leaseId} not found`);
20588
+ process.exit(1);
20589
+ return;
20590
+ }
20591
+ if (existing.state === "revoked") {
20592
+ console.log(`already revoked ${leaseId}`);
20593
+ return;
20594
+ }
20595
+ const res = forceReleaseLease(leaseId);
20596
+ console.log(`revoked ${leaseId} (member_freed=${res.member_freed})`);
20597
+ } catch (error) {
20598
+ if (error instanceof NotFoundError) {
20599
+ console.error(`Error: lease ${error.id} not found`);
20600
+ process.exit(1);
20601
+ }
20602
+ if (error instanceof LeaseContentionError) {
20603
+ console.error(`Error: contention timeout; retry`);
20604
+ process.exit(1);
20605
+ }
20606
+ const message = error instanceof Error ? error.message : String(error);
20607
+ console.error(`Error: ${message}`);
20608
+ process.exit(1);
20609
+ }
20610
+ });
20611
+ leaseCommand.command("release-all").description("Release every active lease matching --for <tag>.").requiredOption("--for <tag>", "Requester tag to match against `requested_for`").option("--json", "Output JSON {released, stale} as id arrays").action(async (opts) => {
20612
+ try {
20613
+ initLeasesDb();
20614
+ const res = releaseLeasesByRequestedFor(opts.for);
20615
+ if (opts.json) {
20616
+ console.log(JSON.stringify(res, null, 2));
20617
+ return;
20618
+ }
20619
+ for (const lid of res.released) {
20620
+ console.log(`released ${lid}`);
20621
+ }
20622
+ for (const lid of res.stale) {
20623
+ console.log(`skipped ${lid} (stale)`);
20624
+ }
20625
+ console.log(
20626
+ `released ${res.released.length} lease(s) for tag "${opts.for}" (${res.stale.length} stale skipped)`
20627
+ );
20628
+ } catch (error) {
20629
+ if (error instanceof LeaseContentionError) {
20630
+ console.error(`Error: contention timeout; retry`);
20631
+ process.exit(1);
20632
+ }
20633
+ const message = error instanceof Error ? error.message : String(error);
20634
+ console.error(`Error: ${message}`);
20635
+ process.exit(1);
20636
+ }
20637
+ });
20638
+ leaseCommand.command("history").description(
20639
+ "Show lease_events. With a lease-id, print that lease's timeline; without, print the last N events across all leases."
20640
+ ).argument("[lease-id]", "Optional lease id to filter on").option("--limit <n>", "Max events to return (default 50)", "50").option("--json", "Output JSON rows").action(
20641
+ async (leaseId, opts) => {
20642
+ try {
20643
+ initLeasesDb();
20644
+ const limit = Number.parseInt(opts.limit, 10);
20645
+ if (!Number.isFinite(limit) || limit <= 0) {
20646
+ throw new Error("--limit must be a positive integer");
20647
+ }
20648
+ const rows = getLeaseEvents(leaseId, limit);
20649
+ if (opts.json) {
20650
+ console.log(JSON.stringify(rows, null, 2));
20651
+ return;
20652
+ }
20653
+ if (rows.length === 0) {
20654
+ console.log("(no events)");
20655
+ return;
20656
+ }
20657
+ for (const r of rows) {
20658
+ const detail = r.detail_json ? ` ${r.detail_json}` : "";
20659
+ console.log(`${r.at} ${r.event.padEnd(16)} ${r.lease_id}${detail}`);
20660
+ }
20661
+ } catch (error) {
20662
+ const message = error instanceof Error ? error.message : String(error);
20663
+ console.error(`Error: ${message}`);
20664
+ process.exit(1);
20665
+ }
20666
+ }
20667
+ );
20668
+ var inventoryCommand = leaseCommand.command("inventory").description("Manage existing inventories (update, delete)");
20669
+ inventoryCommand.command("update").description(
20670
+ "Update mutable inventory fields. `kind` is immutable in v1."
20671
+ ).argument("<slug>", "Inventory slug").option("--default-ttl <duration>", "New default TTL (e.g. 30m, 2h)").option("--display-name <text>", "New display name").action(
20672
+ async (slug, opts) => {
20673
+ try {
20674
+ initLeasesDb();
20675
+ if (opts.defaultTtl === void 0 && opts.displayName === void 0) {
20676
+ throw new Error(
20677
+ "nothing to update \u2014 pass --default-ttl or --display-name"
20678
+ );
20679
+ }
20680
+ const patch = {};
20681
+ if (opts.defaultTtl !== void 0) {
20682
+ patch.default_ttl_s = parseDuration(opts.defaultTtl, 0);
20683
+ }
20684
+ if (opts.displayName !== void 0) {
20685
+ patch.display_name = opts.displayName;
20686
+ }
20687
+ const row = updateInventory(slug, patch);
20688
+ console.log(
20689
+ `Updated inventory '${row.slug}' (kind=${row.kind}, default_ttl=${row.default_ttl_s}s, display_name=${row.display_name ?? "(none)"}).`
20690
+ );
20691
+ } catch (error) {
20692
+ if (error instanceof NotFoundError) {
20693
+ console.error(`Error: inventory '${slug}' not found`);
20694
+ process.exit(1);
20695
+ }
20696
+ const message = error instanceof Error ? error.message : String(error);
20697
+ console.error(`Error: ${message}`);
20698
+ process.exit(1);
20699
+ }
20700
+ }
20701
+ );
20702
+ inventoryCommand.command("delete").description(
20703
+ "Delete an inventory. Refuses if any lease is active unless --force is set."
20704
+ ).argument("<slug>", "Inventory slug").option("--force", "Revoke all active leases first, then cascade delete").action(async (slug, opts) => {
20705
+ try {
20706
+ initLeasesDb();
20707
+ const res = deleteInventory(slug, { force: opts.force });
20708
+ console.log(
20709
+ `deleted "${slug}" (revoked ${res.revoked} active lease(s))`
20710
+ );
20711
+ } catch (error) {
20712
+ if (error instanceof MemberInUseError) {
20713
+ console.error(
20714
+ `Error: inventory "${slug}" has active leases \u2014 use --force to revoke and delete`
20715
+ );
20716
+ process.exit(1);
20717
+ }
20718
+ if (error instanceof NotFoundError) {
20719
+ console.error(`Error: inventory '${slug}' not found`);
20720
+ process.exit(1);
20721
+ }
20722
+ if (error instanceof LeaseContentionError) {
20723
+ console.error(`Error: contention timeout; retry`);
20724
+ process.exit(1);
20725
+ }
20726
+ const message = error instanceof Error ? error.message : String(error);
20727
+ console.error(`Error: ${message}`);
20728
+ process.exit(1);
20729
+ }
20730
+ });
20731
+ memberCommand.command("list").description("List all members of an inventory (pure roster \u2014 no lease state).").argument("<inventory>", "Inventory slug").option("--json", "Output JSON rows").action(async (inventory, opts) => {
20732
+ try {
20733
+ initLeasesDb();
20734
+ const rows = listMembers(inventory);
20735
+ if (opts.json) {
20736
+ console.log(JSON.stringify(rows, null, 2));
20737
+ return;
20738
+ }
20739
+ if (rows.length === 0) {
20740
+ console.log("(no members)");
20741
+ return;
20742
+ }
20743
+ for (const r of rows) {
20744
+ const meta = r.metadata_json ? ` ${r.metadata_json}` : "";
20745
+ const last = r.last_used_at ? ` last_used=${r.last_used_at}` : "";
20746
+ console.log(
20747
+ `${r.member_id.padEnd(24)} ${r.status.padEnd(8)} gen=${r.generation}${last}${meta}`
20748
+ );
20749
+ }
20750
+ } catch (error) {
20751
+ if (error instanceof NotFoundError) {
20752
+ console.error(`Error: inventory '${inventory}' not found`);
20753
+ process.exit(1);
20754
+ }
20755
+ const message = error instanceof Error ? error.message : String(error);
20756
+ console.error(`Error: ${message}`);
20757
+ process.exit(1);
20758
+ }
20759
+ });
20760
+
19382
20761
  // src/commands/request.ts
19383
20762
  init_paths();
19384
20763
  init_fs();
@@ -19387,7 +20766,7 @@ init_slug();
19387
20766
  init_timestamp();
19388
20767
  init_assignment_resolver();
19389
20768
  init_assignment_todos();
19390
- import { resolve as resolve53 } from "path";
20769
+ import { resolve as resolve54 } from "path";
19391
20770
  import { readFile as readFile35 } from "fs/promises";
19392
20771
  async function requestCommand(target, text, options = {}) {
19393
20772
  if (!text || !text.trim()) {
@@ -19404,7 +20783,7 @@ async function requestCommand(target, text, options = {}) {
19404
20783
  if (!isValidSlug(target)) {
19405
20784
  throw new Error(`Invalid assignment slug "${target}".`);
19406
20785
  }
19407
- assignmentDir = resolve53(baseDir, options.project, "assignments", target);
20786
+ assignmentDir = resolve54(baseDir, options.project, "assignments", target);
19408
20787
  targetRef = target;
19409
20788
  } else {
19410
20789
  const resolved = await resolveAssignmentById(baseDir, assignmentsDir(), target);
@@ -19414,7 +20793,7 @@ async function requestCommand(target, text, options = {}) {
19414
20793
  assignmentDir = resolved.assignmentDir;
19415
20794
  targetRef = resolved.standalone ? resolved.id : resolved.assignmentSlug;
19416
20795
  }
19417
- const assignmentMdPath2 = resolve53(assignmentDir, "assignment.md");
20796
+ const assignmentMdPath2 = resolve54(assignmentDir, "assignment.md");
19418
20797
  if (!await fileExists(assignmentMdPath2)) {
19419
20798
  throw new Error(`assignment.md not found at ${assignmentMdPath2}`);
19420
20799
  }
@@ -19431,11 +20810,11 @@ async function requestCommand(target, text, options = {}) {
19431
20810
  // src/commands/plan.ts
19432
20811
  init_fs();
19433
20812
  init_paths();
19434
- import { Command as Command6 } from "commander";
20813
+ import { Command as Command7 } from "commander";
19435
20814
  import { readFile as readFile36, readdir as readdir18 } from "fs/promises";
19436
- import { resolve as resolve54 } from "path";
20815
+ import { resolve as resolve55 } from "path";
19437
20816
  async function readContextAssignmentDir(cwd) {
19438
- const path = resolve54(cwd, ".syntaur", "context.json");
20817
+ const path = resolve55(cwd, ".syntaur", "context.json");
19439
20818
  if (!await fileExists(path)) return null;
19440
20819
  try {
19441
20820
  const raw = await readFile36(path, "utf-8");
@@ -19452,9 +20831,9 @@ async function resolveAssignmentDir(opts) {
19452
20831
  const cwd = opts.cwd ?? process.cwd();
19453
20832
  if (opts.assignment) {
19454
20833
  if (opts.project) {
19455
- return resolve54(defaultProjectDir(), opts.project, "assignments", opts.assignment);
20834
+ return resolve55(defaultProjectDir(), opts.project, "assignments", opts.assignment);
19456
20835
  }
19457
- return resolve54(assignmentsDir(), opts.assignment);
20836
+ return resolve55(assignmentsDir(), opts.assignment);
19458
20837
  }
19459
20838
  const fromCtx = await readContextAssignmentDir(cwd);
19460
20839
  if (fromCtx) return fromCtx;
@@ -19597,7 +20976,7 @@ async function runPlanVersion(options) {
19597
20976
  if (!await fileExists(assignmentDir)) {
19598
20977
  throw new Error(`Assignment directory does not exist: ${assignmentDir}`);
19599
20978
  }
19600
- const assignmentMdPath2 = resolve54(assignmentDir, "assignment.md");
20979
+ const assignmentMdPath2 = resolve55(assignmentDir, "assignment.md");
19601
20980
  if (!await fileExists(assignmentMdPath2)) {
19602
20981
  throw new Error(`Missing assignment.md at: ${assignmentMdPath2}`);
19603
20982
  }
@@ -19609,14 +20988,14 @@ async function runPlanVersion(options) {
19609
20988
  }
19610
20989
  const current = planFiles[planFiles.length - 1];
19611
20990
  const next = nextPlanFileName(current.version);
19612
- const newPath = resolve54(assignmentDir, next.fileName);
20991
+ const newPath = resolve55(assignmentDir, next.fileName);
19613
20992
  if (await fileExists(newPath) && !options.force) {
19614
20993
  throw new Error(`${next.fileName} already exists. Use --force to overwrite.`);
19615
20994
  }
19616
20995
  const assignmentMd = await readFile36(assignmentMdPath2, "utf-8");
19617
20996
  const slugMatch = assignmentMd.match(/^slug:\s*(.+?)\s*$/m);
19618
20997
  const slug = slugMatch ? slugMatch[1].trim() : assignmentDir.split("/").pop() ?? "";
19619
- const oldPlanPath = resolve54(assignmentDir, current.fileName);
20998
+ const oldPlanPath = resolve55(assignmentDir, current.fileName);
19620
20999
  const oldPlanContent = await readFile36(oldPlanPath, "utf-8");
19621
21000
  const oldBody = oldPlanContent.replace(/^---[\s\S]*?\n---\n?/, "");
19622
21001
  const carriedTodos = extractUncheckedTodos(oldBody);
@@ -19639,7 +21018,7 @@ async function runPlanVersion(options) {
19639
21018
  console.log(`Path: ${newPath}`);
19640
21019
  console.log(`Carried forward: ${carriedTodos.length} unchecked task(s).`);
19641
21020
  }
19642
- var planCommand = new Command6("plan").description("Manage plan files for the active assignment");
21021
+ var planCommand = new Command7("plan").description("Manage plan files for the active assignment");
19643
21022
  planCommand.command("version").description(
19644
21023
  "Create the next plan-v<N>.md, supersede the prior plan in assignment.md ## Todos, and carry forward unchecked tasks"
19645
21024
  ).option("--assignment <slug>", "Assignment slug (UUID for standalone). Defaults to .syntaur/context.json").option("--project <slug>", "Project slug. Required when --assignment is given for a project-nested assignment").option("--force", "Overwrite if the next plan-v<N>.md already exists").action(async (options) => {
@@ -19653,11 +21032,11 @@ planCommand.command("version").description(
19653
21032
 
19654
21033
  // src/commands/session.ts
19655
21034
  init_fs();
19656
- import { Command as Command7 } from "commander";
19657
- import { readFile as readFile37, readdir as readdir19, stat as stat8 } from "fs/promises";
19658
- import { resolve as resolve55 } from "path";
21035
+ import { Command as Command8 } from "commander";
21036
+ import { readFile as readFile37, readdir as readdir19, stat as stat9 } from "fs/promises";
21037
+ import { resolve as resolve56 } from "path";
19659
21038
  async function readContext(cwd) {
19660
- const path = resolve55(cwd, ".syntaur", "context.json");
21039
+ const path = resolve56(cwd, ".syntaur", "context.json");
19661
21040
  if (!await fileExists(path)) return null;
19662
21041
  try {
19663
21042
  const raw = await readFile37(path, "utf-8");
@@ -19667,15 +21046,15 @@ async function readContext(cwd) {
19667
21046
  }
19668
21047
  }
19669
21048
  async function findLatestSessionSummary(assignmentDir) {
19670
- const sessionsRoot = resolve55(assignmentDir, "sessions");
21049
+ const sessionsRoot = resolve56(assignmentDir, "sessions");
19671
21050
  if (!await fileExists(sessionsRoot)) return null;
19672
21051
  const entries = await readdir19(sessionsRoot, { withFileTypes: true });
19673
21052
  let best = null;
19674
21053
  for (const entry of entries) {
19675
21054
  if (!entry.isDirectory()) continue;
19676
- const summaryPath = resolve55(sessionsRoot, entry.name, "summary.md");
21055
+ const summaryPath = resolve56(sessionsRoot, entry.name, "summary.md");
19677
21056
  if (!await fileExists(summaryPath)) continue;
19678
- const st = await stat8(summaryPath);
21057
+ const st = await stat9(summaryPath);
19679
21058
  if (best === null || st.mtime.getTime() > best.mtime.getTime()) {
19680
21059
  best = { sessionId: entry.name, path: summaryPath, mtime: st.mtime };
19681
21060
  }
@@ -19683,7 +21062,7 @@ async function findLatestSessionSummary(assignmentDir) {
19683
21062
  return best;
19684
21063
  }
19685
21064
  async function findOpenHandoff(assignmentDir) {
19686
- const handoffPath = resolve55(assignmentDir, "handoff.md");
21065
+ const handoffPath = resolve56(assignmentDir, "handoff.md");
19687
21066
  if (!await fileExists(handoffPath)) return null;
19688
21067
  const content = await readFile37(handoffPath, "utf-8");
19689
21068
  const body = content.replace(/^---[\s\S]*?\n---\n?/, "").trim();
@@ -19775,7 +21154,7 @@ async function runSessionResume(options, cwd = process.cwd()) {
19775
21154
  }
19776
21155
  return out;
19777
21156
  }
19778
- var sessionCommand = new Command7("session").description("Manage agent sessions for the active assignment");
21157
+ var sessionCommand = new Command8("session").description("Manage agent sessions for the active assignment");
19779
21158
  sessionCommand.command("resume").description(
19780
21159
  "Re-orient a fresh session: print active assignment context, latest saved session summary, and any open handoff. Idempotent \u2014 does not mutate state."
19781
21160
  ).option("--json", "Emit machine-readable JSON instead of human-readable text").action(async (options) => {
@@ -19792,11 +21171,11 @@ sessionCommand.command("resume").description(
19792
21171
  init_git_worktree();
19793
21172
  init_fs();
19794
21173
  init_paths();
19795
- import { Command as Command8 } from "commander";
21174
+ import { Command as Command9 } from "commander";
19796
21175
  import { readFile as readFile38 } from "fs/promises";
19797
- import { resolve as resolve56 } from "path";
21176
+ import { resolve as resolve57 } from "path";
19798
21177
  async function readContext2(cwd) {
19799
- const path = resolve56(cwd, ".syntaur", "context.json");
21178
+ const path = resolve57(cwd, ".syntaur", "context.json");
19800
21179
  if (!await fileExists(path)) return null;
19801
21180
  try {
19802
21181
  return JSON.parse(await readFile38(path, "utf-8"));
@@ -19807,7 +21186,7 @@ async function readContext2(cwd) {
19807
21186
  async function resolveAssignmentPath2(opts) {
19808
21187
  if (opts.assignment) {
19809
21188
  if (opts.project) {
19810
- return resolve56(
21189
+ return resolve57(
19811
21190
  defaultProjectDir(),
19812
21191
  opts.project,
19813
21192
  "assignments",
@@ -19815,10 +21194,10 @@ async function resolveAssignmentPath2(opts) {
19815
21194
  "assignment.md"
19816
21195
  );
19817
21196
  }
19818
- return resolve56(assignmentsDir(), opts.assignment, "assignment.md");
21197
+ return resolve57(assignmentsDir(), opts.assignment, "assignment.md");
19819
21198
  }
19820
21199
  const ctx = await readContext2(opts.cwd);
19821
- if (ctx?.assignmentDir) return resolve56(ctx.assignmentDir, "assignment.md");
21200
+ if (ctx?.assignmentDir) return resolve57(ctx.assignmentDir, "assignment.md");
19822
21201
  throw new Error(
19823
21202
  "No active assignment. Pass --assignment <slug> [--project <slug>] or run from a workspace with .syntaur/context.json."
19824
21203
  );
@@ -19829,7 +21208,7 @@ async function runWorktreeCreate(options, cwd = process.cwd()) {
19829
21208
  }
19830
21209
  const repository = options.repository ?? cwd;
19831
21210
  const parentBranch = options.parentBranch ?? "main";
19832
- const worktreePath = options.worktreePath ?? resolve56(repository, ".worktrees", options.branch);
21211
+ const worktreePath = options.worktreePath ?? resolve57(repository, ".worktrees", options.branch);
19833
21212
  const assignmentPath = await resolveAssignmentPath2({
19834
21213
  assignment: options.assignment,
19835
21214
  project: options.project,
@@ -19847,7 +21226,7 @@ async function runWorktreeCreate(options, cwd = process.cwd()) {
19847
21226
  });
19848
21227
  return { worktreePath, assignmentPath };
19849
21228
  }
19850
- var worktreeCommand = new Command8("worktree").description("Manage git worktrees bound to Syntaur assignments");
21229
+ var worktreeCommand = new Command9("worktree").description("Manage git worktrees bound to Syntaur assignments");
19851
21230
  worktreeCommand.command("create").description(
19852
21231
  "Create a worktree at <repository>/.worktrees/<branch> and record it in the assignment workspace block. Atomic \u2014 rolls back the worktree if writing assignment.md fails."
19853
21232
  ).requiredOption("--branch <name>", "Branch name to create (also used as worktree dir name)").option("--repository <path>", "Repository root (defaults to current working directory)").option("--parent-branch <name>", "Parent branch to fork from", "main").option("--assignment <slug>", "Assignment slug (UUID for standalone). Defaults to .syntaur/context.json").option("--project <slug>", "Project slug. Required when --assignment is given for a project-nested assignment").option("--worktree-path <path>", "Override the computed <repository>/.worktrees/<branch> path").action(async (options) => {
@@ -19865,15 +21244,15 @@ worktreeCommand.command("create").description(
19865
21244
  init_paths();
19866
21245
  init_fs();
19867
21246
  init_slug();
19868
- import { Command as Command9 } from "commander";
19869
- import { resolve as resolve58 } from "path";
21247
+ import { Command as Command10 } from "commander";
21248
+ import { resolve as resolve59 } from "path";
19870
21249
 
19871
21250
  // src/utils/project-indexes.ts
19872
21251
  init_parser();
19873
21252
  init_fs();
19874
21253
  import { readdir as readdir20, readFile as readFile39 } from "fs/promises";
19875
- import { resolve as resolve57 } from "path";
19876
- function nowIso() {
21254
+ import { resolve as resolve58 } from "path";
21255
+ function nowIso2() {
19877
21256
  return (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
19878
21257
  }
19879
21258
  function readProjectSlug(projectDir) {
@@ -19891,14 +21270,14 @@ function joinList(items) {
19891
21270
  return items.length === 0 ? "\u2014" : items.map(escapeCell).join(", ");
19892
21271
  }
19893
21272
  async function rebuildResourcesIndex(projectDir) {
19894
- const dir = resolve57(projectDir, "resources");
21273
+ const dir = resolve58(projectDir, "resources");
19895
21274
  await ensureDir(dir);
19896
21275
  const files = await listSlugFiles(dir);
19897
21276
  const slug = readProjectSlug(projectDir);
19898
21277
  const lines = [];
19899
21278
  lines.push("---");
19900
21279
  lines.push(`project: ${slug}`);
19901
- lines.push(`generated: "${nowIso()}"`);
21280
+ lines.push(`generated: "${nowIso2()}"`);
19902
21281
  lines.push(`total: ${files.length}`);
19903
21282
  lines.push("---");
19904
21283
  lines.push("");
@@ -19907,7 +21286,7 @@ async function rebuildResourcesIndex(projectDir) {
19907
21286
  lines.push("| Name | Category | Source | Related Assignments | Updated |");
19908
21287
  lines.push("|------|----------|--------|---------------------|---------|");
19909
21288
  for (const fileName of files) {
19910
- const content = await readFile39(resolve57(dir, fileName), "utf-8");
21289
+ const content = await readFile39(resolve58(dir, fileName), "utf-8");
19911
21290
  const parsed = parseResource(content);
19912
21291
  const slugBase = fileName.replace(/\.md$/, "");
19913
21292
  const name = parsed.name || slugBase;
@@ -19917,19 +21296,19 @@ async function rebuildResourcesIndex(projectDir) {
19917
21296
  );
19918
21297
  }
19919
21298
  lines.push("");
19920
- const indexPath = resolve57(dir, "_index.md");
21299
+ const indexPath = resolve58(dir, "_index.md");
19921
21300
  await writeFileForce(indexPath, lines.join("\n"));
19922
21301
  return { total: files.length, path: indexPath };
19923
21302
  }
19924
21303
  async function rebuildMemoriesIndex(projectDir) {
19925
- const dir = resolve57(projectDir, "memories");
21304
+ const dir = resolve58(projectDir, "memories");
19926
21305
  await ensureDir(dir);
19927
21306
  const files = await listSlugFiles(dir);
19928
21307
  const slug = readProjectSlug(projectDir);
19929
21308
  const lines = [];
19930
21309
  lines.push("---");
19931
21310
  lines.push(`project: ${slug}`);
19932
- lines.push(`generated: "${nowIso()}"`);
21311
+ lines.push(`generated: "${nowIso2()}"`);
19933
21312
  lines.push(`total: ${files.length}`);
19934
21313
  lines.push("---");
19935
21314
  lines.push("");
@@ -19938,7 +21317,7 @@ async function rebuildMemoriesIndex(projectDir) {
19938
21317
  lines.push("| Name | Source | Scope | Source Assignment | Updated |");
19939
21318
  lines.push("|------|--------|-------|-------------------|---------|");
19940
21319
  for (const fileName of files) {
19941
- const content = await readFile39(resolve57(dir, fileName), "utf-8");
21320
+ const content = await readFile39(resolve58(dir, fileName), "utf-8");
19942
21321
  const parsed = parseMemory(content);
19943
21322
  const slugBase = fileName.replace(/\.md$/, "");
19944
21323
  const name = parsed.name || slugBase;
@@ -19948,13 +21327,13 @@ async function rebuildMemoriesIndex(projectDir) {
19948
21327
  );
19949
21328
  }
19950
21329
  lines.push("");
19951
- const indexPath = resolve57(dir, "_index.md");
21330
+ const indexPath = resolve58(dir, "_index.md");
19952
21331
  await writeFileForce(indexPath, lines.join("\n"));
19953
21332
  return { total: files.length, path: indexPath };
19954
21333
  }
19955
21334
 
19956
21335
  // src/commands/resource.ts
19957
- function nowIso2() {
21336
+ function nowIso3() {
19958
21337
  return (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
19959
21338
  }
19960
21339
  function parseList(value) {
@@ -19965,7 +21344,7 @@ function yamlQuote(value) {
19965
21344
  return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
19966
21345
  }
19967
21346
  function renderResourceFile(opts) {
19968
- const created = nowIso2();
21347
+ const created = nowIso3();
19969
21348
  const related = opts.relatedAssignments.length === 0 ? "[]" : `
19970
21349
  ${opts.relatedAssignments.map((a) => ` - ${a}`).join("\n")}`;
19971
21350
  return `---
@@ -19986,8 +21365,8 @@ async function runResourceAdd(options) {
19986
21365
  if (!isValidSlug(options.project)) {
19987
21366
  throw new Error(`Invalid project slug: "${options.project}".`);
19988
21367
  }
19989
- const projectDir = resolve58(defaultProjectDir(), options.project);
19990
- if (!await fileExists(resolve58(projectDir, "project.md"))) {
21368
+ const projectDir = resolve59(defaultProjectDir(), options.project);
21369
+ if (!await fileExists(resolve59(projectDir, "project.md"))) {
19991
21370
  throw new Error(`Project "${options.project}" not found at ${projectDir}.`);
19992
21371
  }
19993
21372
  if (!options.name) throw new Error("--name is required.");
@@ -19996,7 +21375,7 @@ async function runResourceAdd(options) {
19996
21375
  if (!isValidSlug(slug)) {
19997
21376
  throw new Error(`Invalid resource slug: "${slug}".`);
19998
21377
  }
19999
- const filePath = resolve58(projectDir, "resources", `${slug}.md`);
21378
+ const filePath = resolve59(projectDir, "resources", `${slug}.md`);
20000
21379
  if (await fileExists(filePath) && !options.force) {
20001
21380
  throw new Error(
20002
21381
  `Resource "${slug}" already exists at ${filePath}. Use --force to overwrite.`
@@ -20012,7 +21391,7 @@ async function runResourceAdd(options) {
20012
21391
  const { path: indexPath, total } = await rebuildResourcesIndex(projectDir);
20013
21392
  return { filePath, indexPath, total };
20014
21393
  }
20015
- var resourceCommand = new Command9("resource").description("Manage project-level resource entries");
21394
+ var resourceCommand = new Command10("resource").description("Manage project-level resource entries");
20016
21395
  resourceCommand.command("add").description(
20017
21396
  "Add a project resource at <projectDir>/resources/<slug>.md and regenerate _index.md"
20018
21397
  ).requiredOption("--project <slug>", "Project slug").requiredOption("--name <name>", "Human-readable resource name").requiredOption("--source <url-or-path>", "Resource source (URL or path)").option("--category <name>", "Optional category (e.g. dashboard, doc, ticket)").option("--slug <slug>", "Override the slug (default: slugify(--name))").option("--related-assignments <slugs>", "Comma-separated related assignment slugs").option("--force", "Overwrite if the resource file already exists").action(async (options) => {
@@ -20030,9 +21409,9 @@ resourceCommand.command("add").description(
20030
21409
  init_paths();
20031
21410
  init_fs();
20032
21411
  init_slug();
20033
- import { Command as Command10 } from "commander";
20034
- import { resolve as resolve59 } from "path";
20035
- function nowIso3() {
21412
+ import { Command as Command11 } from "commander";
21413
+ import { resolve as resolve60 } from "path";
21414
+ function nowIso4() {
20036
21415
  return (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
20037
21416
  }
20038
21417
  function parseList2(value) {
@@ -20043,7 +21422,7 @@ function yamlQuote2(value) {
20043
21422
  return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
20044
21423
  }
20045
21424
  function renderMemoryFile(opts) {
20046
- const created = nowIso3();
21425
+ const created = nowIso4();
20047
21426
  const related = opts.relatedAssignments.length === 0 ? "[]" : `
20048
21427
  ${opts.relatedAssignments.map((a) => ` - ${a}`).join("\n")}`;
20049
21428
  const sourceAssignment = opts.sourceAssignment === null ? "null" : yamlQuote2(opts.sourceAssignment);
@@ -20066,8 +21445,8 @@ async function runMemoryAdd(options) {
20066
21445
  if (!isValidSlug(options.project)) {
20067
21446
  throw new Error(`Invalid project slug: "${options.project}".`);
20068
21447
  }
20069
- const projectDir = resolve59(defaultProjectDir(), options.project);
20070
- if (!await fileExists(resolve59(projectDir, "project.md"))) {
21448
+ const projectDir = resolve60(defaultProjectDir(), options.project);
21449
+ if (!await fileExists(resolve60(projectDir, "project.md"))) {
20071
21450
  throw new Error(`Project "${options.project}" not found at ${projectDir}.`);
20072
21451
  }
20073
21452
  if (!options.name) throw new Error("--name is required.");
@@ -20076,7 +21455,7 @@ async function runMemoryAdd(options) {
20076
21455
  if (!isValidSlug(slug)) {
20077
21456
  throw new Error(`Invalid memory slug: "${slug}".`);
20078
21457
  }
20079
- const filePath = resolve59(projectDir, "memories", `${slug}.md`);
21458
+ const filePath = resolve60(projectDir, "memories", `${slug}.md`);
20080
21459
  if (await fileExists(filePath) && !options.force) {
20081
21460
  throw new Error(
20082
21461
  `Memory "${slug}" already exists at ${filePath}. Use --force to overwrite.`
@@ -20093,7 +21472,7 @@ async function runMemoryAdd(options) {
20093
21472
  const { path: indexPath, total } = await rebuildMemoriesIndex(projectDir);
20094
21473
  return { filePath, indexPath, total };
20095
21474
  }
20096
- var memoryCommand = new Command10("memory").description("Manage project-level memory entries");
21475
+ var memoryCommand = new Command11("memory").description("Manage project-level memory entries");
20097
21476
  memoryCommand.command("add").description(
20098
21477
  "Add a project memory at <projectDir>/memories/<slug>.md and regenerate _index.md"
20099
21478
  ).requiredOption("--project <slug>", "Project slug").requiredOption("--name <name>", "Human-readable memory name").requiredOption("--source <text>", "Where this memory came from (conversation, decision, doc URL, etc.)").option("--scope <scope>", "Memory scope (default: project)", "project").option("--source-assignment <slug>", "Assignment slug this memory was captured during").option("--slug <slug>", "Override the slug (default: slugify(--name))").option("--related-assignments <slugs>", "Comma-separated related assignment slugs").option("--force", "Overwrite if the memory file already exists").action(async (options) => {
@@ -20112,9 +21491,9 @@ init_api();
20112
21491
  init_paths();
20113
21492
  init_fs();
20114
21493
  init_frontmatter();
20115
- import { Command as Command11 } from "commander";
21494
+ import { Command as Command12 } from "commander";
20116
21495
  import { readFile as readFile40 } from "fs/promises";
20117
- import { resolve as resolve60 } from "path";
21496
+ import { resolve as resolve61 } from "path";
20118
21497
  var AGE_PATTERN = /^(\d+)([dhwm])$/i;
20119
21498
  function parseAgeToCutoff(age) {
20120
21499
  const match = age.match(AGE_PATTERN);
@@ -20133,7 +21512,7 @@ function parseAgeToCutoff(age) {
20133
21512
  }
20134
21513
  function assignmentMdPath(item) {
20135
21514
  if (item.projectSlug) {
20136
- return resolve60(
21515
+ return resolve61(
20137
21516
  defaultProjectDir(),
20138
21517
  item.projectSlug,
20139
21518
  "assignments",
@@ -20141,7 +21520,7 @@ function assignmentMdPath(item) {
20141
21520
  "assignment.md"
20142
21521
  );
20143
21522
  }
20144
- return resolve60(assignmentsDir(), item.id, "assignment.md");
21523
+ return resolve61(assignmentsDir(), item.id, "assignment.md");
20145
21524
  }
20146
21525
  async function loadTags(item) {
20147
21526
  const path = assignmentMdPath(item);
@@ -20207,7 +21586,7 @@ function renderTable(items) {
20207
21586
  );
20208
21587
  return all.map((row) => row.map((cell, c2) => pad(cell ?? "", widths[c2])).join(" ")).join("\n");
20209
21588
  }
20210
- var lsCommand = new Command11("ls").description(
21589
+ var lsCommand = new Command12("ls").description(
20211
21590
  "List assignments across all projects with optional filters by status, project, tag, or age."
20212
21591
  ).option("--status <list>", "Comma-separated status filter (e.g. pending,in_progress)").option("--project <slug>", "Filter to one project").option("--tag <list>", "Comma-separated tag filter (assignment must have ALL tags)").option("--age <duration>", "Only include assignments updated within duration (e.g. 7d, 24h, 2w, 1m)").option("--json", "Emit JSON instead of a table").action(async (options) => {
20213
21592
  try {
@@ -20266,19 +21645,19 @@ init_paths();
20266
21645
  init_fs();
20267
21646
  import { fileURLToPath as fileURLToPath8 } from "url";
20268
21647
  import { readFile as readFile42 } from "fs/promises";
20269
- import { dirname as dirname15, join as join8, resolve as resolve61 } from "path";
20270
- import { spawn as spawn4 } from "child_process";
21648
+ import { dirname as dirname16, join as join9, resolve as resolve62 } from "path";
21649
+ import { spawn as spawn5 } from "child_process";
20271
21650
  import { createInterface as createInterface2 } from "readline/promises";
20272
21651
 
20273
21652
  // src/utils/version.ts
20274
21653
  import { fileURLToPath as fileURLToPath7 } from "url";
20275
21654
  import { readFile as readFile41 } from "fs/promises";
20276
- import { dirname as dirname14, join as join7 } from "path";
21655
+ import { dirname as dirname15, join as join8 } from "path";
20277
21656
  async function readPackageVersion(scriptUrl) {
20278
21657
  try {
20279
21658
  const scriptPath = fileURLToPath7(scriptUrl);
20280
- const pkgRoot = dirname14(dirname14(scriptPath));
20281
- const raw = await readFile41(join7(pkgRoot, "package.json"), "utf-8");
21659
+ const pkgRoot = dirname15(dirname15(scriptPath));
21660
+ const raw = await readFile41(join8(pkgRoot, "package.json"), "utf-8");
20282
21661
  const parsed = JSON.parse(raw);
20283
21662
  return typeof parsed.version === "string" ? parsed.version : null;
20284
21663
  } catch {
@@ -20287,7 +21666,7 @@ async function readPackageVersion(scriptUrl) {
20287
21666
  }
20288
21667
 
20289
21668
  // src/utils/npx-prompt.ts
20290
- var STATE_FILE = resolve61(syntaurRoot(), "npx-install.json");
21669
+ var STATE_FILE = resolve62(syntaurRoot(), "npx-install.json");
20291
21670
  var META_ARGS = /* @__PURE__ */ new Set(["-h", "--help", "-V", "--version", "help"]);
20292
21671
  var GLOBAL_VERSION_TIMEOUT_MS = 2e3;
20293
21672
  function isRunningViaNpx(scriptUrl) {
@@ -20319,10 +21698,10 @@ async function writeState(state) {
20319
21698
  `);
20320
21699
  }
20321
21700
  async function resolveNpmBin() {
20322
- const nodeDir = dirname15(process.execPath);
21701
+ const nodeDir = dirname16(process.execPath);
20323
21702
  const isWin = process.platform === "win32";
20324
21703
  const npmName = isWin ? "npm.cmd" : "npm";
20325
- const nearNode = join8(nodeDir, npmName);
21704
+ const nearNode = join9(nodeDir, npmName);
20326
21705
  if (await fileExists(nearNode)) {
20327
21706
  return { cmd: nearNode, shell: false };
20328
21707
  }
@@ -20331,7 +21710,7 @@ async function resolveNpmBin() {
20331
21710
  async function installGlobally() {
20332
21711
  const { cmd, shell } = await resolveNpmBin();
20333
21712
  return new Promise((resolvePromise) => {
20334
- const child = spawn4(cmd, ["install", "-g", "syntaur"], {
21713
+ const child = spawn5(cmd, ["install", "-g", "syntaur"], {
20335
21714
  stdio: "inherit",
20336
21715
  shell
20337
21716
  });
@@ -20342,7 +21721,7 @@ async function installGlobally() {
20342
21721
  async function readGlobalVersion() {
20343
21722
  const { cmd, shell } = await resolveNpmBin();
20344
21723
  const rootPath = await new Promise((resolvePromise) => {
20345
- const child = spawn4(cmd, ["root", "-g"], {
21724
+ const child = spawn5(cmd, ["root", "-g"], {
20346
21725
  shell,
20347
21726
  stdio: ["ignore", "pipe", "ignore"]
20348
21727
  });
@@ -20365,7 +21744,7 @@ async function readGlobalVersion() {
20365
21744
  });
20366
21745
  if (!rootPath) return null;
20367
21746
  try {
20368
- const manifestPath = join8(rootPath, "syntaur", "package.json");
21747
+ const manifestPath = join9(rootPath, "syntaur", "package.json");
20369
21748
  if (!await fileExists(manifestPath)) return null;
20370
21749
  const raw = await readFile42(manifestPath, "utf-8");
20371
21750
  const parsed = JSON.parse(raw);
@@ -20486,7 +21865,7 @@ async function maybePromptInstall(scriptUrl) {
20486
21865
 
20487
21866
  // src/index.ts
20488
21867
  await maybePromptInstall(import.meta.url);
20489
- var program = new Command12();
21868
+ var program = new Command13();
20490
21869
  var version = await readPackageVersion(import.meta.url) ?? "0.0.0";
20491
21870
  program.name("syntaur").description("CLI scaffolding tool for the Syntaur protocol").version(version);
20492
21871
  program.command("init").description("Initialize ~/.syntaur/ directory structure and config").option("--force", "Overwrite existing config file").action(async (options) => {
@@ -20537,7 +21916,7 @@ program.command("comment").description("Add a comment to an assignment (CLI-medi
20537
21916
  process.exit(1);
20538
21917
  }
20539
21918
  });
20540
- program.command("capture").description("Capture a typed proof artifact for an assignment").argument("[target]", "Assignment slug (with --project) or UUID; falls back to .syntaur/context.json").option("--kind <type>", "Artifact kind: screenshot | video | asciinema | http | text").option("--file <path>", "Source file to ingest (forbidden for --kind=text)").option("--criterion <index>", "Optional 0-based criterion index to tag").option("--note <text>", "Optional note (required for --kind=text)").option("--project <slug>", "Project slug if the target is project-nested").option("--dir <path>", "Override default project directory").action(async (target, options) => {
21919
+ program.command("capture").description("Capture a typed proof artifact for an assignment").argument("[target]", "Assignment slug (with --project) or UUID; falls back to .syntaur/context.json").option("--kind <type>", "Artifact kind: screenshot | video | asciinema | http | text").option("--file <path>", "Source file to ingest (forbidden for --kind=text)").option("--criterion <index>", "Optional 0-based criterion index to tag").option("--note <text>", "Optional note (required for --kind=text)").option("--project <slug>", "Project slug if the target is project-nested").option("--dir <path>", "Override default project directory").option("--interactive", "Screenshot mode: drag a region (macOS only)").option("--window", "Screenshot mode: window picker (macOS only)").option("--fullscreen", "Screenshot mode: silent full-screen capture (macOS only)").action(async (target, options) => {
20541
21920
  try {
20542
21921
  await captureCommand(target, options);
20543
21922
  } catch (error) {
@@ -20865,6 +22244,7 @@ program.addCommand(worktreeCommand);
20865
22244
  program.addCommand(resourceCommand);
20866
22245
  program.addCommand(memoryCommand);
20867
22246
  program.addCommand(lsCommand);
22247
+ program.addCommand(leaseCommand);
20868
22248
  if (process.argv.length <= 2) {
20869
22249
  process.argv.push(await getDefaultCommandName());
20870
22250
  }