syntaur 0.11.0 → 0.13.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 (77) hide show
  1. package/.claude-plugin/plugin.json +3 -1
  2. package/README.md +21 -0
  3. package/dashboard/dist/assets/{_basePickBy-DPIRyskD.js → _basePickBy-qEDLAY_J.js} +1 -1
  4. package/dashboard/dist/assets/{_baseUniq-CwicZZgc.js → _baseUniq-7tDs18Gn.js} +1 -1
  5. package/dashboard/dist/assets/{arc-CcHe1anq.js → arc-Brdw0dYW.js} +1 -1
  6. package/dashboard/dist/assets/{architectureDiagram-2XIMDMQ5-4lwARCsm.js → architectureDiagram-2XIMDMQ5-BiEdLT9m.js} +1 -1
  7. package/dashboard/dist/assets/{blockDiagram-WCTKOSBZ-SdqET9RP.js → blockDiagram-WCTKOSBZ-HC-j_hPZ.js} +1 -1
  8. package/dashboard/dist/assets/{c4Diagram-IC4MRINW-BAKj5SV2.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-Dn95vjsS.js → chunk-4BX2VUAB-CF3hguyj.js} +1 -1
  11. package/dashboard/dist/assets/{chunk-55IACEB6-C6L5bDoe.js → chunk-55IACEB6-BTeCeIc_.js} +1 -1
  12. package/dashboard/dist/assets/{chunk-FMBD7UC4---Go123J.js → chunk-FMBD7UC4-8qVvxmks.js} +1 -1
  13. package/dashboard/dist/assets/{chunk-JSJVCQXG-C9E63Ryg.js → chunk-JSJVCQXG-DCf8n7EO.js} +1 -1
  14. package/dashboard/dist/assets/{chunk-KX2RTZJC-Yyi0h479.js → chunk-KX2RTZJC-CcpjTmME.js} +1 -1
  15. package/dashboard/dist/assets/{chunk-NQ4KR5QH-2Fh1XO45.js → chunk-NQ4KR5QH-BnFPKo4-.js} +1 -1
  16. package/dashboard/dist/assets/{chunk-QZHKN3VN-B4a3DKz-.js → chunk-QZHKN3VN-IPMVLLv2.js} +1 -1
  17. package/dashboard/dist/assets/{chunk-WL4C6EOR-XtMf_tFy.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-8wHbmnAP.js → cose-bilkent-S5V4N54A-tvJjvBQr.js} +1 -1
  22. package/dashboard/dist/assets/{dagre-KLK3FWXG-uJZOLE_S.js → dagre-KLK3FWXG-qp1vt6LY.js} +1 -1
  23. package/dashboard/dist/assets/{diagram-E7M64L7V-BZih6Ivf.js → diagram-E7M64L7V-VF5eizNr.js} +1 -1
  24. package/dashboard/dist/assets/{diagram-IFDJBPK2-CtR6WhET.js → diagram-IFDJBPK2-C3pCbJ1e.js} +1 -1
  25. package/dashboard/dist/assets/{diagram-P4PSJMXO-DTGhoDSE.js → diagram-P4PSJMXO-D-taXZwH.js} +1 -1
  26. package/dashboard/dist/assets/{erDiagram-INFDFZHY-Yr93h1x4.js → erDiagram-INFDFZHY-QjY-NHVs.js} +1 -1
  27. package/dashboard/dist/assets/{flowDiagram-PKNHOUZH-CZ3QY4eZ.js → flowDiagram-PKNHOUZH-FjO8P5Tq.js} +1 -1
  28. package/dashboard/dist/assets/{ganttDiagram-A5KZAMGK-CSf--huI.js → ganttDiagram-A5KZAMGK-Bsn7CyK3.js} +1 -1
  29. package/dashboard/dist/assets/{gitGraphDiagram-K3NZZRJ6-CokKKyLR.js → gitGraphDiagram-K3NZZRJ6-BZ8DjhnF.js} +1 -1
  30. package/dashboard/dist/assets/{graph-DRs4cE7X.js → graph-AdliIYqX.js} +1 -1
  31. package/dashboard/dist/assets/index-BoR_oyUZ.css +1 -0
  32. package/dashboard/dist/assets/index-RCBI4aX2.js +525 -0
  33. package/dashboard/dist/assets/{infoDiagram-LFFYTUFH-C4CTdWLD.js → infoDiagram-LFFYTUFH-BfZjLA86.js} +1 -1
  34. package/dashboard/dist/assets/{ishikawaDiagram-PHBUUO56-DGrfYHHk.js → ishikawaDiagram-PHBUUO56-BSgSGMp5.js} +1 -1
  35. package/dashboard/dist/assets/{journeyDiagram-4ABVD52K-CEDbuCY2.js → journeyDiagram-4ABVD52K-BlQ7qeMC.js} +1 -1
  36. package/dashboard/dist/assets/{kanban-definition-K7BYSVSG-BjgAWDsE.js → kanban-definition-K7BYSVSG-CifO0AUu.js} +1 -1
  37. package/dashboard/dist/assets/{layout-Coi0pRnm.js → layout-B9r-gpD9.js} +1 -1
  38. package/dashboard/dist/assets/{linear-B4xKytgE.js → linear-CaaIRxVZ.js} +1 -1
  39. package/dashboard/dist/assets/{mermaid.core-dkpoLY2K.js → mermaid.core-C8wCWYw-.js} +4 -4
  40. package/dashboard/dist/assets/{mindmap-definition-YRQLILUH-BgUfAXkB.js → mindmap-definition-YRQLILUH-BpX5T7iT.js} +1 -1
  41. package/dashboard/dist/assets/{pieDiagram-SKSYHLDU-BBuMJroZ.js → pieDiagram-SKSYHLDU-BNGYtWrc.js} +1 -1
  42. package/dashboard/dist/assets/{quadrantDiagram-337W2JSQ-iXygRpen.js → quadrantDiagram-337W2JSQ-CR4GeM9D.js} +1 -1
  43. package/dashboard/dist/assets/{requirementDiagram-Z7DCOOCP-DhJGFHd3.js → requirementDiagram-Z7DCOOCP-CXrPkMLl.js} +1 -1
  44. package/dashboard/dist/assets/{sankeyDiagram-WA2Y5GQK-CLkFLBq1.js → sankeyDiagram-WA2Y5GQK-CIzof7Dm.js} +1 -1
  45. package/dashboard/dist/assets/{sequenceDiagram-2WXFIKYE-DQh28Zt-.js → sequenceDiagram-2WXFIKYE-BJZLt0O2.js} +1 -1
  46. package/dashboard/dist/assets/{stateDiagram-RAJIS63D-BZKsD9LJ.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-DCJlSWVw.js → timeline-definition-YZTLITO2-D_ZSQn1K.js} +1 -1
  49. package/dashboard/dist/assets/{treemap-KZPCXAKY-DEGy-zW9.js → treemap-KZPCXAKY-LPpMEco4.js} +1 -1
  50. package/dashboard/dist/assets/{vennDiagram-LZ73GAT5-CfQAKq7b.js → vennDiagram-LZ73GAT5-CRFZQpr7.js} +1 -1
  51. package/dashboard/dist/assets/{xychartDiagram-JWTSCODW-CgD-asFJ.js → xychartDiagram-JWTSCODW-Bivutynw.js} +1 -1
  52. package/dashboard/dist/index.html +2 -2
  53. package/dist/dashboard/server.js +355 -81
  54. package/dist/dashboard/server.js.map +1 -1
  55. package/dist/db/leases-db.d.ts +178 -0
  56. package/dist/db/leases-db.js +557 -0
  57. package/dist/db/leases-db.js.map +1 -0
  58. package/dist/index.js +1219 -357
  59. package/dist/index.js.map +1 -1
  60. package/package.json +1 -1
  61. package/platforms/claude-code/.claude-plugin/plugin.json +3 -1
  62. package/platforms/claude-code/skills/claim-resource/SKILL.md +89 -0
  63. package/platforms/claude-code/skills/release-resource/SKILL.md +65 -0
  64. package/platforms/claude-code/skills/syntaur-protocol/SKILL.md +1 -0
  65. package/platforms/codex/skills/claim-resource/SKILL.md +89 -0
  66. package/platforms/codex/skills/release-resource/SKILL.md +65 -0
  67. package/platforms/codex/skills/syntaur-protocol/SKILL.md +1 -0
  68. package/skills/claim-resource/SKILL.md +89 -0
  69. package/skills/release-resource/SKILL.md +65 -0
  70. package/skills/syntaur-protocol/SKILL.md +1 -0
  71. package/dashboard/dist/assets/channel-DKdluNz1.js +0 -1
  72. package/dashboard/dist/assets/classDiagram-VBA2DB6C-93dPLc4W.js +0 -1
  73. package/dashboard/dist/assets/classDiagram-v2-RAHNMMFH-93dPLc4W.js +0 -1
  74. package/dashboard/dist/assets/clone-vnWuXQgZ.js +0 -1
  75. package/dashboard/dist/assets/index-B9uCPhOL.js +0 -510
  76. package/dashboard/dist/assets/index-BKoccIZR.css +0 -1
  77. package/dashboard/dist/assets/stateDiagram-v2-FVOUBMTO-DU5UnRV-.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);
@@ -9744,6 +9777,575 @@ function createAgentSessionsRouter(projectsDir2, broadcast, assignmentsDir2) {
9744
9777
  return router;
9745
9778
  }
9746
9779
 
9780
+ // src/dashboard/api-leases.ts
9781
+ import { Router as Router4 } from "express";
9782
+
9783
+ // src/db/leases-db.ts
9784
+ init_paths();
9785
+ import Database2 from "better-sqlite3";
9786
+ import { randomUUID as randomUUID2 } from "crypto";
9787
+ import { resolve as resolve17 } from "path";
9788
+ var db2 = null;
9789
+ var LEASE_SCHEMA_VERSION = "1";
9790
+ var SCHEMA_SQL2 = `
9791
+ CREATE TABLE IF NOT EXISTS meta (
9792
+ key TEXT PRIMARY KEY,
9793
+ value TEXT
9794
+ );
9795
+
9796
+ CREATE TABLE IF NOT EXISTS inventories (
9797
+ slug TEXT PRIMARY KEY,
9798
+ kind TEXT NOT NULL,
9799
+ display_name TEXT,
9800
+ default_ttl_s INTEGER NOT NULL CHECK (default_ttl_s > 0),
9801
+ created_at TEXT NOT NULL
9802
+ );
9803
+
9804
+ CREATE TABLE IF NOT EXISTS inventory_members (
9805
+ inventory_slug TEXT NOT NULL,
9806
+ member_id TEXT NOT NULL,
9807
+ status TEXT NOT NULL DEFAULT 'idle'
9808
+ CHECK (status IN ('idle','leased','retired')),
9809
+ generation INTEGER NOT NULL DEFAULT 0,
9810
+ metadata_json TEXT,
9811
+ last_used_at TEXT,
9812
+ retired_at TEXT,
9813
+ PRIMARY KEY (inventory_slug, member_id),
9814
+ FOREIGN KEY (inventory_slug) REFERENCES inventories(slug)
9815
+ );
9816
+
9817
+ CREATE INDEX IF NOT EXISTS idx_members_idle
9818
+ ON inventory_members (inventory_slug, status, last_used_at);
9819
+
9820
+ CREATE TABLE IF NOT EXISTS leases (
9821
+ lease_id TEXT PRIMARY KEY,
9822
+ inventory_slug TEXT NOT NULL,
9823
+ member_id TEXT NOT NULL,
9824
+ member_gen INTEGER NOT NULL,
9825
+ state TEXT NOT NULL
9826
+ CHECK (state IN ('active','released','expired','revoked')),
9827
+ granted_at TEXT NOT NULL,
9828
+ expires_at TEXT NOT NULL,
9829
+ released_at TEXT,
9830
+ requested_for TEXT,
9831
+ FOREIGN KEY (inventory_slug, member_id)
9832
+ REFERENCES inventory_members(inventory_slug, member_id)
9833
+ );
9834
+
9835
+ CREATE INDEX IF NOT EXISTS idx_leases_gc
9836
+ ON leases (state, expires_at);
9837
+
9838
+ CREATE TABLE IF NOT EXISTS lease_events (
9839
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
9840
+ lease_id TEXT NOT NULL,
9841
+ event TEXT NOT NULL,
9842
+ at TEXT NOT NULL,
9843
+ detail_json TEXT,
9844
+ FOREIGN KEY (lease_id) REFERENCES leases(lease_id)
9845
+ );
9846
+
9847
+ CREATE INDEX IF NOT EXISTS idx_events_lease
9848
+ ON lease_events (lease_id, at);
9849
+ `;
9850
+ var NoIdleMemberError = class extends Error {
9851
+ constructor(inventorySlug) {
9852
+ super(`no idle members in '${inventorySlug}'`);
9853
+ this.inventorySlug = inventorySlug;
9854
+ this.name = "NoIdleMemberError";
9855
+ }
9856
+ };
9857
+ var StaleLeaseError = class extends Error {
9858
+ constructor(leaseId) {
9859
+ super(`lease ${leaseId} is no longer active (expired or revoked)`);
9860
+ this.leaseId = leaseId;
9861
+ this.name = "StaleLeaseError";
9862
+ }
9863
+ };
9864
+ var NotFoundError = class extends Error {
9865
+ constructor(id) {
9866
+ super(`lease ${id} not found`);
9867
+ this.id = id;
9868
+ this.name = "NotFoundError";
9869
+ }
9870
+ };
9871
+ var LeaseContentionError = class extends Error {
9872
+ constructor(inventorySlug) {
9873
+ super(`contention timeout on '${inventorySlug}'; retry`);
9874
+ this.inventorySlug = inventorySlug;
9875
+ this.name = "LeaseContentionError";
9876
+ }
9877
+ };
9878
+ var MemberInUseError = class extends Error {
9879
+ constructor(inventorySlug, memberId) {
9880
+ super(`member ${inventorySlug}/${memberId} is currently leased; release before retiring`);
9881
+ this.inventorySlug = inventorySlug;
9882
+ this.memberId = memberId;
9883
+ this.name = "MemberInUseError";
9884
+ }
9885
+ };
9886
+ var DuplicateInventoryError = class extends Error {
9887
+ constructor(slug) {
9888
+ super(`inventory '${slug}' already exists`);
9889
+ this.slug = slug;
9890
+ this.name = "DuplicateInventoryError";
9891
+ }
9892
+ };
9893
+ var DuplicateMemberError = class extends Error {
9894
+ constructor(inventorySlug, memberId) {
9895
+ super(`member ${inventorySlug}/${memberId} already exists`);
9896
+ this.inventorySlug = inventorySlug;
9897
+ this.memberId = memberId;
9898
+ this.name = "DuplicateMemberError";
9899
+ }
9900
+ };
9901
+ function nowIso() {
9902
+ return (/* @__PURE__ */ new Date()).toISOString();
9903
+ }
9904
+ function isBusyError(err2) {
9905
+ if (!err2 || typeof err2 !== "object") return false;
9906
+ const e = err2;
9907
+ return e.code === "SQLITE_BUSY" || e.code === "SQLITE_BUSY_SNAPSHOT";
9908
+ }
9909
+ function initLeasesDb(dbPath) {
9910
+ if (db2) return db2;
9911
+ const finalPath = dbPath ?? resolve17(syntaurRoot(), "syntaur.db");
9912
+ db2 = new Database2(finalPath);
9913
+ db2.pragma("journal_mode = WAL");
9914
+ db2.pragma("busy_timeout = 5000");
9915
+ db2.pragma("foreign_keys = ON");
9916
+ const database = db2;
9917
+ const runMigrations = database.transaction(() => {
9918
+ database.exec(SCHEMA_SQL2);
9919
+ database.prepare("INSERT OR IGNORE INTO meta (key, value) VALUES (?, ?)").run("lease_schema_version", LEASE_SCHEMA_VERSION);
9920
+ });
9921
+ runMigrations.exclusive();
9922
+ return db2;
9923
+ }
9924
+ function getLeasesDb() {
9925
+ if (!db2) {
9926
+ throw new Error("Leases database not initialized. Call initLeasesDb() first.");
9927
+ }
9928
+ return db2;
9929
+ }
9930
+ function closeLeasesDb() {
9931
+ if (db2) {
9932
+ db2.close();
9933
+ db2 = null;
9934
+ }
9935
+ }
9936
+ function createInventory(input4) {
9937
+ const database = getLeasesDb();
9938
+ const row = {
9939
+ slug: input4.slug,
9940
+ kind: input4.kind,
9941
+ display_name: input4.display_name ?? null,
9942
+ default_ttl_s: input4.default_ttl_s,
9943
+ created_at: nowIso()
9944
+ };
9945
+ try {
9946
+ database.prepare(
9947
+ `INSERT INTO inventories (slug, kind, display_name, default_ttl_s, created_at)
9948
+ VALUES (?, ?, ?, ?, ?)`
9949
+ ).run(row.slug, row.kind, row.display_name, row.default_ttl_s, row.created_at);
9950
+ } catch (err2) {
9951
+ if (err2 instanceof Error && err2.message.includes("UNIQUE")) {
9952
+ throw new DuplicateInventoryError(input4.slug);
9953
+ }
9954
+ throw err2;
9955
+ }
9956
+ return row;
9957
+ }
9958
+ function listInventories() {
9959
+ const database = getLeasesDb();
9960
+ return database.prepare(
9961
+ `SELECT slug, kind, display_name, default_ttl_s, created_at
9962
+ FROM inventories ORDER BY slug`
9963
+ ).all();
9964
+ }
9965
+ function getInventoryDetail(slug) {
9966
+ const database = getLeasesDb();
9967
+ const inv = database.prepare(
9968
+ `SELECT slug, kind, display_name, default_ttl_s, created_at
9969
+ FROM inventories WHERE slug = ?`
9970
+ ).get(slug);
9971
+ if (!inv) return null;
9972
+ const members = database.prepare(
9973
+ `SELECT inventory_slug, member_id, status, generation, metadata_json, last_used_at, retired_at
9974
+ FROM inventory_members WHERE inventory_slug = ?
9975
+ ORDER BY member_id`
9976
+ ).all(slug);
9977
+ const active_leases = database.prepare(
9978
+ `SELECT lease_id, inventory_slug, member_id, member_gen, state, granted_at, expires_at, released_at, requested_for
9979
+ FROM leases WHERE inventory_slug = ? AND state = 'active'
9980
+ ORDER BY granted_at`
9981
+ ).all(slug);
9982
+ return { inventory: inv, members, active_leases };
9983
+ }
9984
+ function addMember(input4) {
9985
+ const database = getLeasesDb();
9986
+ const row = {
9987
+ inventory_slug: input4.inventory_slug,
9988
+ member_id: input4.member_id,
9989
+ status: "idle",
9990
+ generation: 0,
9991
+ metadata_json: input4.metadata ? JSON.stringify(input4.metadata) : null,
9992
+ last_used_at: null,
9993
+ retired_at: null
9994
+ };
9995
+ try {
9996
+ database.prepare(
9997
+ `INSERT INTO inventory_members
9998
+ (inventory_slug, member_id, status, generation, metadata_json)
9999
+ VALUES (?, ?, 'idle', 0, ?)`
10000
+ ).run(row.inventory_slug, row.member_id, row.metadata_json);
10001
+ } catch (err2) {
10002
+ if (err2 instanceof Error && err2.message.includes("UNIQUE")) {
10003
+ throw new DuplicateMemberError(input4.inventory_slug, input4.member_id);
10004
+ }
10005
+ throw err2;
10006
+ }
10007
+ return row;
10008
+ }
10009
+ function retireMember(inventorySlug, memberId) {
10010
+ const database = getLeasesDb();
10011
+ const retire = database.transaction(() => {
10012
+ const row = database.prepare(
10013
+ `SELECT status FROM inventory_members
10014
+ WHERE inventory_slug = ? AND member_id = ?`
10015
+ ).get(inventorySlug, memberId);
10016
+ if (!row) throw new NotFoundError(`${inventorySlug}/${memberId}`);
10017
+ if (row.status === "leased") {
10018
+ throw new MemberInUseError(inventorySlug, memberId);
10019
+ }
10020
+ database.prepare(
10021
+ `UPDATE inventory_members
10022
+ SET status = 'retired', generation = generation + 1, retired_at = ?
10023
+ WHERE inventory_slug = ? AND member_id = ?`
10024
+ ).run(nowIso(), inventorySlug, memberId);
10025
+ });
10026
+ retire.immediate();
10027
+ }
10028
+ function claimLease(slug, ttl_s, requested_for) {
10029
+ const database = getLeasesDb();
10030
+ try {
10031
+ const fn = database.transaction(() => {
10032
+ const now = nowIso();
10033
+ database.prepare(
10034
+ `UPDATE leases
10035
+ SET state = 'expired'
10036
+ WHERE state = 'active'
10037
+ AND expires_at <= ?
10038
+ AND inventory_slug = ?`
10039
+ ).run(now, slug);
10040
+ database.prepare(
10041
+ `UPDATE inventory_members
10042
+ SET status = 'idle', generation = generation + 1
10043
+ WHERE inventory_slug = ?
10044
+ AND status = 'leased'
10045
+ AND (inventory_slug, member_id) IN (
10046
+ SELECT inventory_slug, member_id
10047
+ FROM leases
10048
+ WHERE inventory_slug = ?
10049
+ AND state = 'expired'
10050
+ AND released_at IS NULL
10051
+ )
10052
+ AND generation = (
10053
+ SELECT member_gen FROM leases l
10054
+ WHERE l.inventory_slug = inventory_members.inventory_slug
10055
+ AND l.member_id = inventory_members.member_id
10056
+ AND l.state = 'expired'
10057
+ AND l.released_at IS NULL
10058
+ ORDER BY l.granted_at DESC LIMIT 1
10059
+ )`
10060
+ ).run(slug, slug);
10061
+ database.prepare(
10062
+ `UPDATE leases
10063
+ SET released_at = ?
10064
+ WHERE state = 'expired'
10065
+ AND released_at IS NULL
10066
+ AND inventory_slug = ?`
10067
+ ).run(now, slug);
10068
+ const picked = database.prepare(
10069
+ `UPDATE inventory_members
10070
+ SET status = 'leased',
10071
+ generation = generation + 1,
10072
+ last_used_at = ?
10073
+ WHERE (inventory_slug, member_id) = (
10074
+ SELECT inventory_slug, member_id
10075
+ FROM inventory_members
10076
+ WHERE inventory_slug = ? AND status = 'idle'
10077
+ ORDER BY last_used_at ASC NULLS FIRST
10078
+ LIMIT 1
10079
+ )
10080
+ RETURNING member_id, generation, metadata_json`
10081
+ ).get(now, slug);
10082
+ if (!picked) {
10083
+ throw new NoIdleMemberError(slug);
10084
+ }
10085
+ const lease_id = randomUUID2();
10086
+ const expires_at = new Date(Date.now() + ttl_s * 1e3).toISOString();
10087
+ database.prepare(
10088
+ `INSERT INTO leases
10089
+ (lease_id, inventory_slug, member_id, member_gen,
10090
+ state, granted_at, expires_at, requested_for)
10091
+ VALUES (?, ?, ?, ?, 'active', ?, ?, ?)`
10092
+ ).run(
10093
+ lease_id,
10094
+ slug,
10095
+ picked.member_id,
10096
+ picked.generation,
10097
+ now,
10098
+ expires_at,
10099
+ requested_for ?? null
10100
+ );
10101
+ database.prepare(
10102
+ `INSERT INTO lease_events (lease_id, event, at, detail_json)
10103
+ VALUES (?, 'claimed', ?, ?)`
10104
+ ).run(lease_id, now, JSON.stringify({ member_id: picked.member_id }));
10105
+ return {
10106
+ lease_id,
10107
+ inventory_slug: slug,
10108
+ member_id: picked.member_id,
10109
+ member_gen: picked.generation,
10110
+ granted_at: now,
10111
+ expires_at,
10112
+ metadata: picked.metadata_json ? JSON.parse(picked.metadata_json) : null
10113
+ };
10114
+ });
10115
+ return fn.immediate();
10116
+ } catch (err2) {
10117
+ if (isBusyError(err2)) throw new LeaseContentionError(slug);
10118
+ throw err2;
10119
+ }
10120
+ }
10121
+ function releaseLease(lease_id) {
10122
+ const database = getLeasesDb();
10123
+ try {
10124
+ const fn = database.transaction(() => {
10125
+ const now = nowIso();
10126
+ const memberRes = database.prepare(
10127
+ `UPDATE inventory_members
10128
+ SET status = 'idle', generation = generation + 1
10129
+ WHERE (inventory_slug, member_id) = (
10130
+ SELECT inventory_slug, member_id FROM leases
10131
+ WHERE lease_id = ? AND state = 'active'
10132
+ )
10133
+ AND generation = (
10134
+ SELECT member_gen FROM leases
10135
+ WHERE lease_id = ? AND state = 'active'
10136
+ )`
10137
+ ).run(lease_id, lease_id);
10138
+ if (memberRes.changes === 0) {
10139
+ throw new StaleLeaseError(lease_id);
10140
+ }
10141
+ database.prepare(
10142
+ `UPDATE leases
10143
+ SET state = 'released', released_at = ?
10144
+ WHERE lease_id = ? AND state = 'active'`
10145
+ ).run(now, lease_id);
10146
+ database.prepare(
10147
+ `INSERT INTO lease_events (lease_id, event, at)
10148
+ VALUES (?, 'released', ?)`
10149
+ ).run(lease_id, now);
10150
+ });
10151
+ fn.immediate();
10152
+ } catch (err2) {
10153
+ if (isBusyError(err2)) throw new LeaseContentionError(lease_id);
10154
+ throw err2;
10155
+ }
10156
+ }
10157
+ function extendLease(lease_id, ttl_s) {
10158
+ const database = getLeasesDb();
10159
+ try {
10160
+ const fn = database.transaction(() => {
10161
+ const now = nowIso();
10162
+ const new_expires_at = new Date(Date.now() + ttl_s * 1e3).toISOString();
10163
+ const res = database.prepare(
10164
+ `UPDATE leases
10165
+ SET expires_at = ?
10166
+ WHERE lease_id = ?
10167
+ AND state = 'active'
10168
+ AND expires_at > ?
10169
+ AND member_gen = (
10170
+ SELECT generation FROM inventory_members
10171
+ WHERE inventory_slug = leases.inventory_slug
10172
+ AND member_id = leases.member_id
10173
+ )`
10174
+ ).run(new_expires_at, lease_id, now);
10175
+ if (res.changes === 0) {
10176
+ throw new StaleLeaseError(lease_id);
10177
+ }
10178
+ database.prepare(
10179
+ `INSERT INTO lease_events (lease_id, event, at, detail_json)
10180
+ VALUES (?, 'extended', ?, ?)`
10181
+ ).run(lease_id, now, JSON.stringify({ new_expires_at }));
10182
+ return { new_expires_at };
10183
+ });
10184
+ return fn.immediate();
10185
+ } catch (err2) {
10186
+ if (isBusyError(err2)) throw new LeaseContentionError(lease_id);
10187
+ throw err2;
10188
+ }
10189
+ }
10190
+ function getLease(lease_id) {
10191
+ const database = getLeasesDb();
10192
+ const row = database.prepare(
10193
+ `SELECT lease_id, inventory_slug, member_id, member_gen, state,
10194
+ granted_at, expires_at, released_at, requested_for
10195
+ FROM leases WHERE lease_id = ?`
10196
+ ).get(lease_id);
10197
+ return row ?? null;
10198
+ }
10199
+ function listLeases(filter) {
10200
+ const database = getLeasesDb();
10201
+ const where = [];
10202
+ const params2 = [];
10203
+ if (filter?.inventory) {
10204
+ where.push("inventory_slug = ?");
10205
+ params2.push(filter.inventory);
10206
+ }
10207
+ if (filter?.state) {
10208
+ where.push("state = ?");
10209
+ params2.push(filter.state);
10210
+ }
10211
+ const whereSql = where.length > 0 ? `WHERE ${where.join(" AND ")}` : "";
10212
+ return database.prepare(
10213
+ `SELECT lease_id, inventory_slug, member_id, member_gen, state,
10214
+ granted_at, expires_at, released_at, requested_for
10215
+ FROM leases ${whereSql}
10216
+ ORDER BY granted_at DESC`
10217
+ ).all(...params2);
10218
+ }
10219
+ function gcExpiredLeases() {
10220
+ const database = getLeasesDb();
10221
+ try {
10222
+ const fn = database.transaction(() => {
10223
+ const now = nowIso();
10224
+ const expRes = database.prepare(
10225
+ `UPDATE leases SET state = 'expired'
10226
+ WHERE state = 'active' AND expires_at <= ?`
10227
+ ).run(now);
10228
+ database.prepare(
10229
+ `UPDATE inventory_members
10230
+ SET status = 'idle', generation = generation + 1
10231
+ WHERE status = 'leased'
10232
+ AND (inventory_slug, member_id) IN (
10233
+ SELECT inventory_slug, member_id FROM leases
10234
+ WHERE state = 'expired' AND released_at IS NULL
10235
+ )
10236
+ AND generation = (
10237
+ SELECT member_gen FROM leases l
10238
+ WHERE l.inventory_slug = inventory_members.inventory_slug
10239
+ AND l.member_id = inventory_members.member_id
10240
+ AND l.state = 'expired'
10241
+ AND l.released_at IS NULL
10242
+ ORDER BY l.granted_at DESC LIMIT 1
10243
+ )`
10244
+ ).run();
10245
+ database.prepare(
10246
+ `UPDATE leases SET released_at = ?
10247
+ WHERE state = 'expired' AND released_at IS NULL AND expires_at <= ?`
10248
+ ).run(now, now);
10249
+ return expRes.changes;
10250
+ });
10251
+ return fn.immediate();
10252
+ } catch (err2) {
10253
+ if (isBusyError(err2)) throw new LeaseContentionError("gc");
10254
+ throw err2;
10255
+ }
10256
+ }
10257
+ function forceReleaseLease(lease_id) {
10258
+ const database = getLeasesDb();
10259
+ try {
10260
+ const fn = database.transaction(() => {
10261
+ const now = nowIso();
10262
+ const lookup = database.prepare(
10263
+ `SELECT inventory_slug, member_id, member_gen, state
10264
+ FROM leases WHERE lease_id = ?`
10265
+ ).get(lease_id);
10266
+ if (!lookup) throw new NotFoundError(lease_id);
10267
+ if (lookup.state === "revoked") return { member_freed: false };
10268
+ const memberRes = database.prepare(
10269
+ `UPDATE inventory_members
10270
+ SET status = 'idle', generation = generation + 1
10271
+ WHERE inventory_slug = ? AND member_id = ?
10272
+ AND generation = ?`
10273
+ ).run(lookup.inventory_slug, lookup.member_id, lookup.member_gen);
10274
+ database.prepare(
10275
+ `UPDATE leases
10276
+ SET state = 'revoked', released_at = ?
10277
+ WHERE lease_id = ? AND state != 'revoked'`
10278
+ ).run(now, lease_id);
10279
+ const member_freed = memberRes.changes > 0;
10280
+ database.prepare(
10281
+ `INSERT INTO lease_events (lease_id, event, at, detail_json)
10282
+ VALUES (?, 'force_released', ?, ?)`
10283
+ ).run(lease_id, now, JSON.stringify({ member_freed }));
10284
+ return { member_freed };
10285
+ });
10286
+ return fn.immediate();
10287
+ } catch (err2) {
10288
+ if (isBusyError(err2)) throw new LeaseContentionError(lease_id);
10289
+ throw err2;
10290
+ }
10291
+ }
10292
+
10293
+ // src/dashboard/api-leases.ts
10294
+ function createLeasesRouter(broadcast) {
10295
+ const router = Router4();
10296
+ router.get("/", (_req, res) => {
10297
+ try {
10298
+ initLeasesDb();
10299
+ const inventories = listInventories();
10300
+ const details = inventories.map((inv) => getInventoryDetail(inv.slug)).filter((d) => d !== null);
10301
+ res.json({ inventories: details });
10302
+ } catch (error) {
10303
+ res.status(500).json({
10304
+ error: error instanceof Error ? error.message : "List failed"
10305
+ });
10306
+ }
10307
+ });
10308
+ router.get("/:slug", (req, res) => {
10309
+ try {
10310
+ initLeasesDb();
10311
+ const detail = getInventoryDetail(req.params.slug);
10312
+ if (!detail) {
10313
+ res.status(404).json({ error: "Inventory not found" });
10314
+ return;
10315
+ }
10316
+ res.json(detail);
10317
+ } catch (error) {
10318
+ res.status(500).json({
10319
+ error: error instanceof Error ? error.message : "Read failed"
10320
+ });
10321
+ }
10322
+ });
10323
+ router.post("/:slug/force-release/:lease_id", (req, res) => {
10324
+ try {
10325
+ initLeasesDb();
10326
+ const result = forceReleaseLease(req.params.lease_id);
10327
+ broadcast?.({
10328
+ type: "leases-updated",
10329
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
10330
+ });
10331
+ res.json({ ...result, lease_id: req.params.lease_id });
10332
+ } catch (error) {
10333
+ if (error instanceof NotFoundError) {
10334
+ res.status(404).json({ error: "Lease not found" });
10335
+ return;
10336
+ }
10337
+ if (error instanceof LeaseContentionError) {
10338
+ res.status(503).json({ error: "Contention timeout; retry" });
10339
+ return;
10340
+ }
10341
+ res.status(500).json({
10342
+ error: error instanceof Error ? error.message : "Force release failed"
10343
+ });
10344
+ }
10345
+ });
10346
+ return router;
10347
+ }
10348
+
9747
10349
  // src/dashboard/api-playbooks.ts
9748
10350
  init_api();
9749
10351
  init_parser();
@@ -9752,11 +10354,11 @@ init_timestamp();
9752
10354
  init_fs();
9753
10355
  init_playbook();
9754
10356
  init_playbooks();
9755
- import { Router as Router4 } from "express";
9756
- import { resolve as resolve17 } from "path";
10357
+ import { Router as Router5 } from "express";
10358
+ import { resolve as resolve18 } from "path";
9757
10359
  import { readFile as readFile12, unlink as unlink2 } from "fs/promises";
9758
10360
  function createPlaybooksRouter(playbooksDir3) {
9759
- const router = Router4();
10361
+ const router = Router5();
9760
10362
  router.get("/", async (_req, res) => {
9761
10363
  try {
9762
10364
  const playbooks = await listPlaybooks(playbooksDir3);
@@ -9823,7 +10425,7 @@ function createPlaybooksRouter(playbooksDir3) {
9823
10425
  res.status(404).json({ error: `Playbook "${req.params.slug}" not found` });
9824
10426
  return;
9825
10427
  }
9826
- const filePath = resolve17(playbooksDir3, resolved.filename);
10428
+ const filePath = resolve18(playbooksDir3, resolved.filename);
9827
10429
  const content = await readFile12(filePath, "utf-8");
9828
10430
  res.json({
9829
10431
  documentType: "playbook",
@@ -9849,7 +10451,7 @@ function createPlaybooksRouter(playbooksDir3) {
9849
10451
  return;
9850
10452
  }
9851
10453
  await ensureDir(playbooksDir3);
9852
- const filePath = resolve17(playbooksDir3, `${slug}.md`);
10454
+ const filePath = resolve18(playbooksDir3, `${slug}.md`);
9853
10455
  if (await fileExists(filePath)) {
9854
10456
  res.status(409).json({ error: `Playbook "${slug}" already exists` });
9855
10457
  return;
@@ -9873,7 +10475,7 @@ function createPlaybooksRouter(playbooksDir3) {
9873
10475
  res.status(404).json({ error: `Playbook "${req.params.slug}" not found` });
9874
10476
  return;
9875
10477
  }
9876
- const filePath = resolve17(playbooksDir3, resolved.filename);
10478
+ const filePath = resolve18(playbooksDir3, resolved.filename);
9877
10479
  await writeFileForce(filePath, content);
9878
10480
  await rebuildPlaybookManifest(playbooksDir3);
9879
10481
  res.json({ slug: resolved.slug, path: filePath });
@@ -9892,7 +10494,7 @@ function createPlaybooksRouter(playbooksDir3) {
9892
10494
  res.status(404).json({ error: `Playbook "${req.params.slug}" not found` });
9893
10495
  return;
9894
10496
  }
9895
- const filePath = resolve17(playbooksDir3, resolved.filename);
10497
+ const filePath = resolve18(playbooksDir3, resolved.filename);
9896
10498
  await unlink2(filePath);
9897
10499
  await removeFromDisabledList(resolved.slug);
9898
10500
  await rebuildPlaybookManifest(playbooksDir3);
@@ -9911,9 +10513,9 @@ init_fs_migration();
9911
10513
  init_parser2();
9912
10514
  init_fs();
9913
10515
  init_paths();
9914
- import { Router as Router5 } from "express";
10516
+ import { Router as Router6 } from "express";
9915
10517
  import { readdir as readdir9 } from "fs/promises";
9916
- import { resolve as resolvePath, dirname as dirname4 } from "path";
10518
+ import { resolve as resolvePath, dirname as dirname5 } from "path";
9917
10519
  import { rename as rename3, mkdir as mkdir2 } from "fs/promises";
9918
10520
 
9919
10521
  // src/dashboard/todos-locks.ts
@@ -9959,7 +10561,7 @@ function touchItem(item) {
9959
10561
  item.updatedAt = now;
9960
10562
  }
9961
10563
  function createTodosRouter(todosDir2, broadcast, projectsDir2) {
9962
- const router = Router5();
10564
+ const router = Router6();
9963
10565
  function broadcastUpdate() {
9964
10566
  broadcast({ type: "todos-updated", timestamp: (/* @__PURE__ */ new Date()).toISOString() });
9965
10567
  }
@@ -10089,7 +10691,7 @@ function createTodosRouter(todosDir2, broadcast, projectsDir2) {
10089
10691
  router.post("/:workspace/archive", async (req, res) => {
10090
10692
  try {
10091
10693
  const { archivePath: archivePath2 } = await Promise.resolve().then(() => (init_parser2(), parser_exports));
10092
- const { resolve: resolve62 } = await import("path");
10694
+ const { resolve: resolve63 } = await import("path");
10093
10695
  const { readFile: readFile43 } = await import("fs/promises");
10094
10696
  const { writeFileForce: writeFileForce2 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
10095
10697
  const workspace = getWorkspaceParam(req.params.workspace);
@@ -10106,7 +10708,7 @@ function createTodosRouter(todosDir2, broadcast, projectsDir2) {
10106
10708
  (e) => e.itemIds.every((id) => completedIds.has(id))
10107
10709
  );
10108
10710
  const archFile = archivePath2(todosDir2, workspace, checklist.archiveInterval);
10109
- await ensureDir(resolve62(todosDir2, "archive"));
10711
+ await ensureDir(resolve63(todosDir2, "archive"));
10110
10712
  let archContent = "";
10111
10713
  if (await fileExists(archFile)) {
10112
10714
  archContent = await readFile43(archFile, "utf-8");
@@ -10513,7 +11115,7 @@ workspace: ${workspace}
10513
11115
  if (await fileExists(newPlanDir)) {
10514
11116
  return { status: 409, error: "plan dir already exists in target" };
10515
11117
  }
10516
- await mkdir2(dirname4(newPlanDir), { recursive: true });
11118
+ await mkdir2(dirname5(newPlanDir), { recursive: true });
10517
11119
  await rename3(item.planDir, newPlanDir);
10518
11120
  item.planDir = newPlanDir;
10519
11121
  }
@@ -10589,9 +11191,9 @@ init_parser2();
10589
11191
  init_fs();
10590
11192
  init_paths();
10591
11193
  init_slug();
10592
- import { Router as Router6 } from "express";
11194
+ import { Router as Router7 } from "express";
10593
11195
  import { mkdir as mkdir3, readFile as readFile14, rename as rename4 } from "fs/promises";
10594
- import { resolve as resolve19, dirname as dirname5 } from "path";
11196
+ import { resolve as resolve20, dirname as dirname6 } from "path";
10595
11197
  var WORKSPACE_REGEX2 = /^[a-z0-9_][a-z0-9-]*$/;
10596
11198
  function touchItem2(item) {
10597
11199
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -10606,7 +11208,7 @@ function params(req) {
10606
11208
  return req.params;
10607
11209
  }
10608
11210
  async function projectExists(projectsDir2, slug) {
10609
- return fileExists(resolve19(projectsDir2, slug, "project.md"));
11211
+ return fileExists(resolve20(projectsDir2, slug, "project.md"));
10610
11212
  }
10611
11213
  async function ensureProjectTodosDir(projectsDir2, slug) {
10612
11214
  const todosDir2 = projectTodosDir(projectsDir2, slug);
@@ -10623,7 +11225,7 @@ async function ensureProjectTodosDir(projectsDir2, slug) {
10623
11225
  throw err2;
10624
11226
  }
10625
11227
  try {
10626
- await mkdir3(resolve19(todosDir2, "archive"), { recursive: false });
11228
+ await mkdir3(resolve20(todosDir2, "archive"), { recursive: false });
10627
11229
  } catch (err2) {
10628
11230
  const code = err2.code;
10629
11231
  if (code === "EEXIST") return;
@@ -10639,7 +11241,7 @@ function notFound(res, slug) {
10639
11241
  res.status(404).json({ error: `Project "${slug}" not found` });
10640
11242
  }
10641
11243
  function createProjectTodosRouter(projectsDir2, broadcast, workspaceTodosDir) {
10642
- const router = Router6({ mergeParams: true });
11244
+ const router = Router7({ mergeParams: true });
10643
11245
  function broadcastUpdate(projectSlug) {
10644
11246
  broadcast({ type: "todos-updated", projectSlug, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
10645
11247
  }
@@ -11247,18 +11849,18 @@ workspace: ${slug}
11247
11849
  if (tg.includes("/")) {
11248
11850
  const parts = tg.split("/");
11249
11851
  if (parts.length !== 2) return { error: `Invalid target.assignment "${tg}"` };
11250
- assignmentDir = resolve19(projectsDir2, parts[0], "assignments", parts[1]);
11852
+ assignmentDir = resolve20(projectsDir2, parts[0], "assignments", parts[1]);
11251
11853
  assignmentRef = `${parts[0]}/${parts[1]}`;
11252
11854
  } 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);
11855
+ assignmentDir = resolve20(assignmentsDirFn(), tg);
11254
11856
  assignmentRef = tg;
11255
11857
  } else {
11256
11858
  return { error: `Invalid target.assignment "${tg}"` };
11257
11859
  }
11258
- const assignmentMdPath3 = resolve19(assignmentDir, "assignment.md");
11860
+ const assignmentMdPath3 = resolve20(assignmentDir, "assignment.md");
11259
11861
  if (!await fileExists(assignmentMdPath3)) return { error: `Target assignment not found: ${assignmentMdPath3}` };
11260
11862
  }
11261
- const assignmentMdPath2 = resolve19(assignmentDir, "assignment.md");
11863
+ const assignmentMdPath2 = resolve20(assignmentDir, "assignment.md");
11262
11864
  let content = await readFile14(assignmentMdPath2, "utf-8");
11263
11865
  content = appendTodosToAssignmentBody2(
11264
11866
  content,
@@ -11388,7 +11990,7 @@ workspace: ${slug}
11388
11990
  if (await fileExists(newPlanDir)) {
11389
11991
  return { status: 409, error: "plan dir already exists in target" };
11390
11992
  }
11391
- await mkdir3(dirname5(newPlanDir), { recursive: true });
11993
+ await mkdir3(dirname6(newPlanDir), { recursive: true });
11392
11994
  await rename4(item.planDir, newPlanDir);
11393
11995
  item.planDir = newPlanDir;
11394
11996
  }
@@ -11451,7 +12053,7 @@ workspace: ${slug}
11451
12053
 
11452
12054
  // src/dashboard/api-backup.ts
11453
12055
  init_config2();
11454
- import { Router as Router7 } from "express";
12056
+ import { Router as Router8 } from "express";
11455
12057
 
11456
12058
  // src/utils/github-backup.ts
11457
12059
  init_paths();
@@ -11460,7 +12062,7 @@ init_config2();
11460
12062
  import { execFile as execFile2 } from "child_process";
11461
12063
  import { promisify as promisify2 } from "util";
11462
12064
  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";
12065
+ import { resolve as resolve21, join as join2 } from "path";
11464
12066
  import { tmpdir } from "os";
11465
12067
  var exec2 = promisify2(execFile2);
11466
12068
  var VALID_CATEGORIES = ["projects", "playbooks", "todos", "servers", "config"];
@@ -11500,7 +12102,7 @@ async function resolveCategoryPath(category) {
11500
12102
  case "servers":
11501
12103
  return { sourcePath: serversDir(), repoPath: "servers", isFile: false };
11502
12104
  case "config":
11503
- return { sourcePath: resolve20(syntaurRoot(), "config.md"), repoPath: "config.md", isFile: true };
12105
+ return { sourcePath: resolve21(syntaurRoot(), "config.md"), repoPath: "config.md", isFile: true };
11504
12106
  }
11505
12107
  }
11506
12108
  async function checkGitInstalled() {
@@ -11511,7 +12113,7 @@ async function checkGitInstalled() {
11511
12113
  }
11512
12114
  }
11513
12115
  async function acquireLock() {
11514
- const lockPath = resolve20(syntaurRoot(), LOCK_FILE_NAME);
12116
+ const lockPath = resolve21(syntaurRoot(), LOCK_FILE_NAME);
11515
12117
  await ensureDir(syntaurRoot());
11516
12118
  try {
11517
12119
  const handle = await open(lockPath, "wx");
@@ -11558,7 +12160,7 @@ async function copyRecursive(src, dest) {
11558
12160
  await ensureDir(dest);
11559
12161
  await cp(src, dest, { recursive: true, force: true });
11560
12162
  } else {
11561
- await ensureDir(resolve20(dest, ".."));
12163
+ await ensureDir(resolve21(dest, ".."));
11562
12164
  await cp(src, dest, { force: true });
11563
12165
  }
11564
12166
  }
@@ -11606,7 +12208,7 @@ async function backupToGithub(overrides) {
11606
12208
  }
11607
12209
  if (category === "config") {
11608
12210
  const sanitized = await readSanitizedConfig(sourcePath);
11609
- await ensureDir(resolve20(destPath, ".."));
12211
+ await ensureDir(resolve21(destPath, ".."));
11610
12212
  await writeFile4(destPath, sanitized, "utf-8");
11611
12213
  } else {
11612
12214
  await copyRecursive(sourcePath, destPath);
@@ -11660,7 +12262,7 @@ async function backupToGithub(overrides) {
11660
12262
  }
11661
12263
  async function safeRestoreCategory(localPath, repoSrcPath, isFile) {
11662
12264
  if (isFile) {
11663
- await ensureDir(resolve20(localPath, ".."));
12265
+ await ensureDir(resolve21(localPath, ".."));
11664
12266
  await cp(repoSrcPath, localPath, { force: true });
11665
12267
  return;
11666
12268
  }
@@ -11761,7 +12363,7 @@ async function restoreFromGithub(overrides) {
11761
12363
  }
11762
12364
  async function getBackupStatus() {
11763
12365
  const config = await readConfig();
11764
- const lockPath = resolve20(syntaurRoot(), LOCK_FILE_NAME);
12366
+ const lockPath = resolve21(syntaurRoot(), LOCK_FILE_NAME);
11765
12367
  const locked = await fileExists(lockPath);
11766
12368
  return {
11767
12369
  repo: config.backup?.repo ?? null,
@@ -11774,7 +12376,7 @@ async function getBackupStatus() {
11774
12376
 
11775
12377
  // src/dashboard/api-backup.ts
11776
12378
  function createBackupRouter() {
11777
- const router = Router7();
12379
+ const router = Router8();
11778
12380
  router.get("/", async (_req, res) => {
11779
12381
  try {
11780
12382
  const status = await getBackupStatus();
@@ -12095,10 +12697,11 @@ function createDashboardServer(options) {
12095
12697
  migrateFromMarkdown(projectsDir2).catch((err2) => {
12096
12698
  console.error("Session migration from markdown failed:", err2);
12097
12699
  });
12700
+ initLeasesDb();
12098
12701
  (async () => {
12099
12702
  try {
12100
12703
  const configResult = await migrateLegacyConfig(
12101
- resolve21(syntaurRoot(), "config.md")
12704
+ resolve22(syntaurRoot(), "config.md")
12102
12705
  );
12103
12706
  const projectResult = await migrateLegacyProjectFiles(projectsDir2);
12104
12707
  const summary = summarizeMigration(projectResult, configResult);
@@ -12428,6 +13031,7 @@ function createDashboardServer(options) {
12428
13031
  });
12429
13032
  app.use(createWriteRouter(projectsDir2, assignmentsDir2));
12430
13033
  app.use("/api/servers", createServersRouter(serversDir2, projectsDir2, assignmentsDir2));
13034
+ app.use("/api/leases", createLeasesRouter(broadcast));
12431
13035
  app.use("/api/agent-sessions", createAgentSessionsRouter(projectsDir2, broadcast, assignmentsDir2));
12432
13036
  app.use("/api/playbooks", createPlaybooksRouter(playbooksDir3));
12433
13037
  app.get("/api/memories", async (_req, res) => {
@@ -12453,14 +13057,14 @@ function createDashboardServer(options) {
12453
13057
  app.use("/api/backup", createBackupRouter());
12454
13058
  if (serveStaticUi && dashboardDistPath) {
12455
13059
  const sendOpts = { dotfiles: "allow" };
12456
- app.use("/assets", express.static(resolve21(dashboardDistPath, "assets"), sendOpts));
13060
+ app.use("/assets", express.static(resolve22(dashboardDistPath, "assets"), sendOpts));
12457
13061
  app.use(express.static(dashboardDistPath, { ...sendOpts, index: false, fallthrough: true }));
12458
13062
  app.get("{*path}", async (req, res) => {
12459
13063
  if (req.path.startsWith("/api") || req.path === "/ws" || req.path.startsWith("/assets")) {
12460
13064
  res.status(404).json({ error: "Not Found" });
12461
13065
  return;
12462
13066
  }
12463
- const indexPath = resolve21(dashboardDistPath, "index.html");
13067
+ const indexPath = resolve22(dashboardDistPath, "index.html");
12464
13068
  if (!await fileExists(indexPath)) {
12465
13069
  res.status(503).send(
12466
13070
  'Dashboard not built. Run "npm run build:dashboard" first.'
@@ -12484,6 +13088,7 @@ function createDashboardServer(options) {
12484
13088
  serversDir: serversDir2,
12485
13089
  playbooksDir: playbooksDir3,
12486
13090
  todosDir: todosDir2,
13091
+ dbPath: resolve22(syntaurRoot(), "syntaur.db"),
12487
13092
  onMessage: broadcast
12488
13093
  });
12489
13094
  startAutodiscovery({ serversDir: serversDir2, projectsDir: projectsDir2, assignmentsDir: assignmentsDir2, excludePids: /* @__PURE__ */ new Set([process.pid]) });
@@ -12498,7 +13103,7 @@ function createDashboardServer(options) {
12498
13103
  }
12499
13104
  });
12500
13105
  server.listen(port, () => {
12501
- const portFile = resolve21(syntaurRoot(), "dashboard-port");
13106
+ const portFile = resolve22(syntaurRoot(), "dashboard-port");
12502
13107
  writeFile5(portFile, String(port), "utf-8").catch(() => {
12503
13108
  });
12504
13109
  resolvePromise();
@@ -12511,11 +13116,12 @@ function createDashboardServer(options) {
12511
13116
  await watcherHandle.close();
12512
13117
  }
12513
13118
  closeSessionDb();
13119
+ closeLeasesDb();
12514
13120
  for (const client of clients) {
12515
13121
  client.terminate();
12516
13122
  }
12517
13123
  clients.clear();
12518
- const portFile = resolve21(syntaurRoot(), "dashboard-port");
13124
+ const portFile = resolve22(syntaurRoot(), "dashboard-port");
12519
13125
  await unlink4(portFile).catch(() => {
12520
13126
  });
12521
13127
  server.closeAllConnections?.();
@@ -12595,8 +13201,8 @@ async function dashboardCommand(options) {
12595
13201
  port = availablePort;
12596
13202
  }
12597
13203
  const thisFile = fileURLToPath2(import.meta.url);
12598
- const packageRoot = resolve22(dirname6(thisFile), "..");
12599
- const dashboardDist = resolve22(packageRoot, "dashboard", "dist");
13204
+ const packageRoot = resolve23(dirname7(thisFile), "..");
13205
+ const dashboardDist = resolve23(packageRoot, "dashboard", "dist");
12600
13206
  const server = createDashboardServer({
12601
13207
  port,
12602
13208
  projectsDir: projectsDir2,
@@ -12610,8 +13216,8 @@ async function dashboardCommand(options) {
12610
13216
  await server.start();
12611
13217
  let viteProcess = null;
12612
13218
  if (mode === "dev") {
12613
- const dashboardDir = resolve22(packageRoot, "dashboard");
12614
- const viteBin = resolve22(dashboardDir, "node_modules", ".bin", "vite");
13219
+ const dashboardDir = resolve23(packageRoot, "dashboard");
13220
+ const viteBin = resolve23(dashboardDir, "node_modules", ".bin", "vite");
12615
13221
  if (!await fileExists(viteBin)) {
12616
13222
  console.error(
12617
13223
  'Vite not found. Run "npm ci --prefix dashboard" first, or use the default bundled dashboard mode.'
@@ -12686,7 +13292,7 @@ init_config2();
12686
13292
  init_slug();
12687
13293
  init_lifecycle();
12688
13294
  init_assignment_resolver();
12689
- import { resolve as resolve23 } from "path";
13295
+ import { resolve as resolve24 } from "path";
12690
13296
  async function runTransition(assignment, command, options = {}) {
12691
13297
  const config = await readConfig();
12692
13298
  const baseDir = options.dir ? expandHome(options.dir) : config.defaultProjectDir;
@@ -12697,8 +13303,8 @@ async function runTransition(assignment, command, options = {}) {
12697
13303
  if (!isValidSlug(assignment)) {
12698
13304
  throw new Error(`Invalid assignment slug "${assignment}".`);
12699
13305
  }
12700
- const projectDir = resolve23(baseDir, options.project);
12701
- const projectMdPath = resolve23(projectDir, "project.md");
13306
+ const projectDir = resolve24(baseDir, options.project);
13307
+ const projectMdPath = resolve24(projectDir, "project.md");
12702
13308
  if (!await fileExists(projectDir) || !await fileExists(projectMdPath)) {
12703
13309
  throw new Error(`Project "${options.project}" not found at ${projectDir}.`);
12704
13310
  }
@@ -12729,8 +13335,8 @@ async function runAssign(assignment, agent, options = {}) {
12729
13335
  if (!isValidSlug(assignment)) {
12730
13336
  throw new Error(`Invalid assignment slug "${assignment}".`);
12731
13337
  }
12732
- const projectDir = resolve23(baseDir, options.project);
12733
- const projectMdPath = resolve23(projectDir, "project.md");
13338
+ const projectDir = resolve24(baseDir, options.project);
13339
+ const projectMdPath = resolve24(projectDir, "project.md");
12734
13340
  if (!await fileExists(projectDir) || !await fileExists(projectMdPath)) {
12735
13341
  throw new Error(`Project "${options.project}" not found at ${projectDir}.`);
12736
13342
  }
@@ -12825,20 +13431,20 @@ import {
12825
13431
  } from "fs/promises";
12826
13432
  import { existsSync } from "fs";
12827
13433
  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";
13434
+ import { basename as basename4, dirname as dirname9, isAbsolute as isAbsolute2, relative as relative2, resolve as resolve26 } from "path";
12829
13435
 
12830
13436
  // src/utils/package-root.ts
12831
13437
  init_fs();
12832
- import { dirname as dirname7, resolve as resolve24 } from "path";
13438
+ import { dirname as dirname8, resolve as resolve25 } from "path";
12833
13439
  import { fileURLToPath as fileURLToPath3 } from "url";
12834
13440
  async function findPackageRoot(expectedRelativePath) {
12835
- let currentDir = dirname7(fileURLToPath3(import.meta.url));
13441
+ let currentDir = dirname8(fileURLToPath3(import.meta.url));
12836
13442
  while (true) {
12837
- const candidate = resolve24(currentDir, expectedRelativePath);
13443
+ const candidate = resolve25(currentDir, expectedRelativePath);
12838
13444
  if (await fileExists(candidate)) {
12839
13445
  return currentDir;
12840
13446
  }
12841
- const parentDir = resolve24(currentDir, "..");
13447
+ const parentDir = resolve25(currentDir, "..");
12842
13448
  if (parentDir === currentDir) {
12843
13449
  throw new Error(
12844
13450
  `Could not locate package root containing ${expectedRelativePath}.`
@@ -12859,25 +13465,25 @@ function getPluginManifestRelativePath(pluginKind) {
12859
13465
  }
12860
13466
  function getDefaultPluginTargetDir(pluginKind) {
12861
13467
  const home = homedir2();
12862
- return pluginKind === "claude" ? resolve25(home, ".claude", "plugins", "syntaur") : resolve25(home, "plugins", "syntaur");
13468
+ return pluginKind === "claude" ? resolve26(home, ".claude", "plugins", "syntaur") : resolve26(home, "plugins", "syntaur");
12863
13469
  }
12864
13470
  function getDefaultMarketplacePath() {
12865
- return resolve25(homedir2(), ".agents", "plugins", "marketplace.json");
13471
+ return resolve26(homedir2(), ".agents", "plugins", "marketplace.json");
12866
13472
  }
12867
13473
  function getClaudeMarketplacesRoot() {
12868
- return resolve25(homedir2(), ".claude", "plugins", "marketplaces");
13474
+ return resolve26(homedir2(), ".claude", "plugins", "marketplaces");
12869
13475
  }
12870
13476
  function getClaudeKnownMarketplacesPath() {
12871
- return resolve25(homedir2(), ".claude", "plugins", "known_marketplaces.json");
13477
+ return resolve26(homedir2(), ".claude", "plugins", "known_marketplaces.json");
12872
13478
  }
12873
13479
  function getClaudeInstalledPluginsPath() {
12874
- return resolve25(homedir2(), ".claude", "plugins", "installed_plugins.json");
13480
+ return resolve26(homedir2(), ".claude", "plugins", "installed_plugins.json");
12875
13481
  }
12876
13482
  function getInstallMarkerPath(targetDir) {
12877
- return resolve25(targetDir, INSTALL_MARKER_FILENAME);
13483
+ return resolve26(targetDir, INSTALL_MARKER_FILENAME);
12878
13484
  }
12879
13485
  async function readPackageManifest(packageRoot) {
12880
- const raw = await readFile16(resolve25(packageRoot, "package.json"), "utf-8");
13486
+ const raw = await readFile16(resolve26(packageRoot, "package.json"), "utf-8");
12881
13487
  return JSON.parse(raw);
12882
13488
  }
12883
13489
  async function readJsonFileIfExists(pathValue) {
@@ -12893,11 +13499,11 @@ async function readJsonFileIfExists(pathValue) {
12893
13499
  }
12894
13500
  async function readClaudePluginManifest(pluginDir) {
12895
13501
  return await readJsonFileIfExists(
12896
- resolve25(pluginDir, ".claude-plugin", "plugin.json")
13502
+ resolve26(pluginDir, ".claude-plugin", "plugin.json")
12897
13503
  ) ?? {};
12898
13504
  }
12899
13505
  async function readPluginManifestName(targetDir, pluginKind) {
12900
- const manifestPath = resolve25(targetDir, getPluginManifestRelativePath(pluginKind));
13506
+ const manifestPath = resolve26(targetDir, getPluginManifestRelativePath(pluginKind));
12901
13507
  if (!await fileExists(manifestPath)) {
12902
13508
  return void 0;
12903
13509
  }
@@ -12924,7 +13530,7 @@ async function getInstallStatus(targetDir, pluginKind) {
12924
13530
  const info = await lstat(targetDir);
12925
13531
  if (info.isSymbolicLink()) {
12926
13532
  const symlinkTarget = await readlink(targetDir);
12927
- const resolvedTarget = resolve25(dirname8(targetDir), symlinkTarget);
13533
+ const resolvedTarget = resolve26(dirname9(targetDir), symlinkTarget);
12928
13534
  const manifestName2 = await readPluginManifestName(resolvedTarget, pluginKind);
12929
13535
  return {
12930
13536
  exists: true,
@@ -12961,16 +13567,16 @@ async function writeInstallMetadata(targetDir, pluginKind, installMode, packageM
12961
13567
  );
12962
13568
  }
12963
13569
  async function installCopy(paths, pluginKind) {
12964
- await ensureDir(dirname8(paths.targetDir));
13570
+ await ensureDir(dirname9(paths.targetDir));
12965
13571
  await cp2(paths.sourceDir, paths.targetDir, { recursive: true });
12966
13572
  const packageManifest = await readPackageManifest(paths.packageRoot);
12967
13573
  await writeInstallMetadata(paths.targetDir, pluginKind, "copy", packageManifest);
12968
13574
  }
12969
13575
  async function installLink(paths) {
12970
- await ensureDir(dirname8(paths.targetDir));
13576
+ await ensureDir(dirname9(paths.targetDir));
12971
13577
  await rm3(paths.targetDir, { recursive: true, force: true });
12972
- await ensureDir(dirname8(paths.targetDir));
12973
- await symlink(resolve25(paths.sourceDir), paths.targetDir, "dir");
13578
+ await ensureDir(dirname9(paths.targetDir));
13579
+ await symlink(resolve26(paths.sourceDir), paths.targetDir, "dir");
12974
13580
  }
12975
13581
  async function removeInstallMarker(targetDir) {
12976
13582
  const markerPath = getInstallMarkerPath(targetDir);
@@ -12984,13 +13590,13 @@ function normalizeAbsoluteInstallPath(pathValue, label) {
12984
13590
  if (!isAbsolute2(expanded)) {
12985
13591
  throw new Error(`${label} must be an absolute path.`);
12986
13592
  }
12987
- return resolve25(expanded);
13593
+ return resolve26(expanded);
12988
13594
  }
12989
13595
  async function resolvePluginPaths(pluginKind, targetDir) {
12990
13596
  const packageRoot = await findPackageRoot(getPluginRelativePath(pluginKind));
12991
13597
  return {
12992
13598
  packageRoot,
12993
- sourceDir: resolve25(packageRoot, getPluginRelativePath(pluginKind)),
13599
+ sourceDir: resolve26(packageRoot, getPluginRelativePath(pluginKind)),
12994
13600
  targetDir: targetDir ?? getDefaultPluginTargetDir(pluginKind)
12995
13601
  };
12996
13602
  }
@@ -13024,7 +13630,7 @@ async function readClaudeMarketplaceFile(manifestPath) {
13024
13630
  };
13025
13631
  }
13026
13632
  async function writeClaudeMarketplaceFile(manifestPath, marketplace) {
13027
- await ensureDir(dirname8(manifestPath));
13633
+ await ensureDir(dirname9(manifestPath));
13028
13634
  if (Array.isArray(marketplace.plugins)) {
13029
13635
  marketplace.plugins = [...marketplace.plugins].sort((a, b) => {
13030
13636
  const an = a?.name ?? "";
@@ -13115,8 +13721,8 @@ async function listClaudeMarketplaceCandidates() {
13115
13721
  if (!entry.isDirectory()) {
13116
13722
  continue;
13117
13723
  }
13118
- const candidateRoot = resolve25(rootDir, entry.name);
13119
- const manifestPath = resolve25(candidateRoot, ".claude-plugin", "marketplace.json");
13724
+ const candidateRoot = resolve26(rootDir, entry.name);
13725
+ const manifestPath = resolve26(candidateRoot, ".claude-plugin", "marketplace.json");
13120
13726
  if (!await fileExists(manifestPath)) {
13121
13727
  continue;
13122
13728
  }
@@ -13143,11 +13749,11 @@ async function listClaudeMarketplaceCandidates() {
13143
13749
  if (!installLocation) {
13144
13750
  continue;
13145
13751
  }
13146
- const candidateRoot = resolve25(expandHome(installLocation));
13752
+ const candidateRoot = resolve26(expandHome(installLocation));
13147
13753
  if (seen.has(candidateRoot)) {
13148
13754
  continue;
13149
13755
  }
13150
- const manifestPath = resolve25(candidateRoot, ".claude-plugin", "marketplace.json");
13756
+ const manifestPath = resolve26(candidateRoot, ".claude-plugin", "marketplace.json");
13151
13757
  if (!await fileExists(manifestPath)) {
13152
13758
  continue;
13153
13759
  }
@@ -13175,7 +13781,7 @@ async function getPreferredClaudeMarketplace() {
13175
13781
  name: candidate.name,
13176
13782
  rootDir: candidate.rootDir,
13177
13783
  manifestPath: candidate.manifestPath,
13178
- targetDir: resolve25(candidate.rootDir, "plugins", "syntaur")
13784
+ targetDir: resolve26(candidate.rootDir, "plugins", "syntaur")
13179
13785
  };
13180
13786
  }
13181
13787
  async function registerKnownClaudeMarketplace(name, rootDir) {
@@ -13207,7 +13813,7 @@ async function registerKnownClaudeMarketplace(name, rootDir) {
13207
13813
  };
13208
13814
  existing[name].lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
13209
13815
  existing[name].autoUpdate = true;
13210
- await ensureDir(dirname8(manifestPath));
13816
+ await ensureDir(dirname9(manifestPath));
13211
13817
  if (await fileExists(manifestPath)) {
13212
13818
  const prev = await readFile16(manifestPath, "utf-8");
13213
13819
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
@@ -13223,7 +13829,7 @@ async function ensureKnownClaudeMarketplaceForRoot(options) {
13223
13829
  return registerKnownClaudeMarketplace(options.name, options.rootDir);
13224
13830
  }
13225
13831
  async function setSyntaurPluginEnabled(options) {
13226
- const settingsPath = resolve25(homedir2(), ".claude", "settings.json");
13832
+ const settingsPath = resolve26(homedir2(), ".claude", "settings.json");
13227
13833
  const key = `syntaur@${options.marketplaceName}`;
13228
13834
  let parsed = {};
13229
13835
  if (await fileExists(settingsPath)) {
@@ -13243,7 +13849,7 @@ async function setSyntaurPluginEnabled(options) {
13243
13849
  }
13244
13850
  enabledPlugins[key] = options.enabled;
13245
13851
  parsed.enabledPlugins = enabledPlugins;
13246
- await ensureDir(dirname8(settingsPath));
13852
+ await ensureDir(dirname9(settingsPath));
13247
13853
  if (await fileExists(settingsPath)) {
13248
13854
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
13249
13855
  const prev = await readFile16(settingsPath, "utf-8");
@@ -13260,9 +13866,9 @@ async function ensureClaudeUserMarketplace() {
13260
13866
  if (existing) {
13261
13867
  return existing;
13262
13868
  }
13263
- const rootDir = resolve25(getClaudeMarketplacesRoot(), "user-plugins");
13264
- const manifestPath = resolve25(rootDir, ".claude-plugin", "marketplace.json");
13265
- await ensureDir(resolve25(rootDir, "plugins"));
13869
+ const rootDir = resolve26(getClaudeMarketplacesRoot(), "user-plugins");
13870
+ const manifestPath = resolve26(rootDir, ".claude-plugin", "marketplace.json");
13871
+ await ensureDir(resolve26(rootDir, "plugins"));
13266
13872
  if (!await fileExists(manifestPath)) {
13267
13873
  const scaffold = {
13268
13874
  plugins: []
@@ -13281,22 +13887,22 @@ async function ensureClaudeUserMarketplace() {
13281
13887
  name: "user-plugins",
13282
13888
  rootDir,
13283
13889
  manifestPath,
13284
- targetDir: resolve25(rootDir, "plugins", "syntaur")
13890
+ targetDir: resolve26(rootDir, "plugins", "syntaur")
13285
13891
  };
13286
13892
  }
13287
13893
  async function detectClaudeMarketplaceForTarget(targetDir) {
13288
13894
  const normalizedTargetDir = normalizeAbsoluteInstallPath(targetDir, "Claude plugin target");
13289
- const pluginsDir = dirname8(normalizedTargetDir);
13290
- if (basename3(pluginsDir) !== "plugins") {
13895
+ const pluginsDir = dirname9(normalizedTargetDir);
13896
+ if (basename4(pluginsDir) !== "plugins") {
13291
13897
  return null;
13292
13898
  }
13293
- const rootDir = dirname8(pluginsDir);
13294
- const manifestPath = resolve25(rootDir, ".claude-plugin", "marketplace.json");
13899
+ const rootDir = dirname9(pluginsDir);
13900
+ const manifestPath = resolve26(rootDir, ".claude-plugin", "marketplace.json");
13295
13901
  if (!await fileExists(manifestPath)) {
13296
13902
  return null;
13297
13903
  }
13298
13904
  const marketplace = await readClaudeMarketplaceFile(manifestPath);
13299
- const name = typeof marketplace.name === "string" && marketplace.name.trim() !== "" ? marketplace.name : basename3(rootDir);
13905
+ const name = typeof marketplace.name === "string" && marketplace.name.trim() !== "" ? marketplace.name : basename4(rootDir);
13300
13906
  return {
13301
13907
  name,
13302
13908
  rootDir,
@@ -13307,7 +13913,7 @@ async function detectClaudeMarketplaceForTarget(targetDir) {
13307
13913
  async function findManagedClaudeMarketplacePluginDir() {
13308
13914
  const marketplaces = await listClaudeMarketplaceCandidates();
13309
13915
  for (const marketplace of marketplaces) {
13310
- const targetDir = resolve25(marketplace.rootDir, "plugins", "syntaur");
13916
+ const targetDir = resolve26(marketplace.rootDir, "plugins", "syntaur");
13311
13917
  const status = await getInstallStatus(targetDir, "claude");
13312
13918
  if (status.exists && status.managed) {
13313
13919
  return targetDir;
@@ -13432,7 +14038,7 @@ async function installManagedPlugin(options) {
13432
14038
  `${paths.targetDir} already exists and is not a Syntaur-managed install. Remove it manually before installing Syntaur there.`
13433
14039
  );
13434
14040
  }
13435
- if (desiredMode === "link" && existing.exists && existing.installMode === "link" && existing.symlinkTarget === resolve25(paths.sourceDir) && !force) {
14041
+ if (desiredMode === "link" && existing.exists && existing.installMode === "link" && existing.symlinkTarget === resolve26(paths.sourceDir) && !force) {
13436
14042
  return {
13437
14043
  targetDir: paths.targetDir,
13438
14044
  sourceDir: paths.sourceDir,
@@ -13456,7 +14062,7 @@ async function installManagedPlugin(options) {
13456
14062
  };
13457
14063
  }
13458
14064
  function buildMarketplaceSourcePath(pluginTargetDir, marketplacePath) {
13459
- const relPath = relative2(dirname8(marketplacePath), pluginTargetDir).replaceAll("\\", "/");
14065
+ const relPath = relative2(dirname9(marketplacePath), pluginTargetDir).replaceAll("\\", "/");
13460
14066
  if (relPath === "") {
13461
14067
  return ".";
13462
14068
  }
@@ -13493,7 +14099,7 @@ async function readMarketplaceFile(marketplacePath) {
13493
14099
  };
13494
14100
  }
13495
14101
  async function writeMarketplaceFile(marketplacePath, marketplace) {
13496
- await ensureDir(dirname8(marketplacePath));
14102
+ await ensureDir(dirname9(marketplacePath));
13497
14103
  await writeFile6(marketplacePath, `${JSON.stringify(marketplace, null, 2)}
13498
14104
  `, "utf-8");
13499
14105
  }
@@ -13645,13 +14251,13 @@ async function recommendMarketplacePath() {
13645
14251
  return configuredOrManaged ?? getDefaultMarketplacePath();
13646
14252
  }
13647
14253
  async function isSyntaurDataInstalled() {
13648
- return fileExists(resolve25(syntaurRoot(), "config.md"));
14254
+ return fileExists(resolve26(syntaurRoot(), "config.md"));
13649
14255
  }
13650
14256
  async function removeSyntaurData() {
13651
14257
  await rm3(syntaurRoot(), { recursive: true, force: true });
13652
14258
  }
13653
14259
  async function getConfiguredProjectDir() {
13654
- if (!await fileExists(resolve25(syntaurRoot(), "config.md"))) {
14260
+ if (!await fileExists(resolve26(syntaurRoot(), "config.md"))) {
13655
14261
  return null;
13656
14262
  }
13657
14263
  return (await readConfig()).defaultProjectDir;
@@ -13721,16 +14327,16 @@ async function textPrompt(question, defaultValue) {
13721
14327
  // src/utils/install-skills.ts
13722
14328
  init_fs();
13723
14329
  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";
14330
+ import { resolve as resolve28, relative as relative3, join as join3 } from "path";
13725
14331
  import { homedir as homedir4 } from "os";
13726
14332
 
13727
14333
  // src/utils/plugin-state.ts
13728
14334
  init_fs();
13729
14335
  import { readFile as readFile17 } from "fs/promises";
13730
- import { resolve as resolve26 } from "path";
14336
+ import { resolve as resolve27 } from "path";
13731
14337
  import { homedir as homedir3 } from "os";
13732
14338
  function settingsPathFor(agent) {
13733
- if (agent === "claude") return resolve26(homedir3(), ".claude", "settings.json");
14339
+ if (agent === "claude") return resolve27(homedir3(), ".claude", "settings.json");
13734
14340
  return null;
13735
14341
  }
13736
14342
  async function readJsonOrNull(path) {
@@ -13781,11 +14387,11 @@ var KNOWN_SKILL_NAMES = [
13781
14387
  var KNOWN_SKILLS = KNOWN_SKILL_NAMES;
13782
14388
  async function getSkillsDir() {
13783
14389
  const packageRoot = await findPackageRoot("skills");
13784
- return resolve27(packageRoot, "skills");
14390
+ return resolve28(packageRoot, "skills");
13785
14391
  }
13786
14392
  function defaultSkillTargetDir(target) {
13787
- if (target === "claude") return resolve27(homedir4(), ".claude", "skills");
13788
- return resolve27(homedir4(), ".codex", "skills");
14393
+ if (target === "claude") return resolve28(homedir4(), ".claude", "skills");
14394
+ return resolve28(homedir4(), ".codex", "skills");
13789
14395
  }
13790
14396
  async function walkFiles(root) {
13791
14397
  const out = [];
@@ -14117,7 +14723,7 @@ async function installPluginCommand(options) {
14117
14723
  init_paths();
14118
14724
  init_fs();
14119
14725
  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";
14726
+ import { resolve as resolve30, dirname as dirname11 } from "path";
14121
14727
  import { homedir as homedir5 } from "os";
14122
14728
  import { fileURLToPath as fileURLToPath4 } from "url";
14123
14729
 
@@ -14125,7 +14731,7 @@ import { fileURLToPath as fileURLToPath4 } from "url";
14125
14731
  init_paths();
14126
14732
  init_fs();
14127
14733
  import { readFile as readFile19, writeFile as writeFile7 } from "fs/promises";
14128
- import { resolve as resolve28, dirname as dirname9 } from "path";
14734
+ import { resolve as resolve29, dirname as dirname10 } from "path";
14129
14735
  import { spawnSync } from "child_process";
14130
14736
  import { checkbox, input as input2, confirm } from "@inquirer/prompts";
14131
14737
  var AVAILABLE_SEGMENTS = [
@@ -14146,7 +14752,7 @@ var PRESETS = {
14146
14752
  tracker: { segments: ["git", "assignment", "external", "session"], separator: " \xB7 " }
14147
14753
  };
14148
14754
  function getConfigPath(installRoot) {
14149
- return resolve28(installRoot, "statusline.config.json");
14755
+ return resolve29(installRoot, "statusline.config.json");
14150
14756
  }
14151
14757
  async function readConfig2(path) {
14152
14758
  if (!await fileExists(path)) return null;
@@ -14246,7 +14852,7 @@ function renderPreview(config, statuslineScript, cwd) {
14246
14852
  env: {
14247
14853
  ...process.env,
14248
14854
  // Force the child to pick up the freshly-written config from install root.
14249
- HOME: dirname9(dirname9(statuslineScript))
14855
+ HOME: dirname10(dirname10(statuslineScript))
14250
14856
  }
14251
14857
  });
14252
14858
  if (res.status !== 0) return null;
@@ -14297,14 +14903,14 @@ async function configureStatuslineCommand(options = {}) {
14297
14903
  console.log("(preview mode \u2014 config NOT written)");
14298
14904
  return;
14299
14905
  }
14300
- await ensureDir(dirname9(configPath));
14906
+ await ensureDir(dirname10(configPath));
14301
14907
  await writeFile7(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
14302
14908
  console.log("Wrote statusline config:");
14303
14909
  console.log(` path: ${configPath}`);
14304
14910
  console.log(` segments: ${config.segments.join(", ")}`);
14305
14911
  console.log(` separator: ${JSON.stringify(config.separator)}`);
14306
14912
  if (config.wrap) console.log(` wrap: ${config.wrap}`);
14307
- const script = options.statuslineScript ?? resolve28(installRoot, "statusline.sh");
14913
+ const script = options.statuslineScript ?? resolve29(installRoot, "statusline.sh");
14308
14914
  if (await fileExists(script)) {
14309
14915
  console.log("");
14310
14916
  console.log("Live preview:");
@@ -14324,7 +14930,7 @@ async function configureStatuslineCommand(options = {}) {
14324
14930
  async function writeDefaultConfigIfMissing(installRoot) {
14325
14931
  const path = getConfigPath(installRoot);
14326
14932
  if (await fileExists(path)) return;
14327
- await ensureDir(dirname9(path));
14933
+ await ensureDir(dirname10(path));
14328
14934
  const defaultConfig = {
14329
14935
  segments: ["git", "assignment", "session"],
14330
14936
  separator: " \xB7 "
@@ -14334,8 +14940,8 @@ async function writeDefaultConfigIfMissing(installRoot) {
14334
14940
 
14335
14941
  // src/commands/install-statusline.ts
14336
14942
  function getPackageStatuslineSource() {
14337
- const here = dirname10(fileURLToPath4(import.meta.url));
14338
- return resolve29(here, "..", "statusline", "statusline.sh");
14943
+ const here = dirname11(fileURLToPath4(import.meta.url));
14944
+ return resolve30(here, "..", "statusline", "statusline.sh");
14339
14945
  }
14340
14946
  async function readSettingsJson(settingsPath) {
14341
14947
  if (!await fileExists(settingsPath)) return {};
@@ -14351,7 +14957,7 @@ async function readSettingsJson(settingsPath) {
14351
14957
  }
14352
14958
  }
14353
14959
  async function writeSettingsJson(settingsPath, data) {
14354
- await ensureDir(dirname10(settingsPath));
14960
+ await ensureDir(dirname11(settingsPath));
14355
14961
  await writeFile8(settingsPath, JSON.stringify(data, null, 2) + "\n", "utf-8");
14356
14962
  }
14357
14963
  async function resolveMode(mode, existingCommand, ourCommand) {
@@ -14386,7 +14992,7 @@ function extractExistingCommand(settings) {
14386
14992
  };
14387
14993
  }
14388
14994
  async function backupSettings(settingsSnapshot, backupPath) {
14389
- await ensureDir(dirname10(backupPath));
14995
+ await ensureDir(dirname11(backupPath));
14390
14996
  await writeFile8(
14391
14997
  backupPath,
14392
14998
  JSON.stringify(
@@ -14403,7 +15009,7 @@ async function backupSettings(settingsSnapshot, backupPath) {
14403
15009
  );
14404
15010
  }
14405
15011
  async function installScript(sourceScript, destScript, link) {
14406
- await ensureDir(dirname10(destScript));
15012
+ await ensureDir(dirname11(destScript));
14407
15013
  try {
14408
15014
  const s = await lstat3(destScript);
14409
15015
  if (s.isSymbolicLink() || s.isFile()) {
@@ -14419,12 +15025,12 @@ async function installScript(sourceScript, destScript, link) {
14419
15025
  }
14420
15026
  async function installStatuslineCommand(options = {}) {
14421
15027
  const mode = options.mode ?? "ask";
14422
- const settingsPath = options.settingsPath ?? resolve29(homedir5(), ".claude", "settings.json");
15028
+ const settingsPath = options.settingsPath ?? resolve30(homedir5(), ".claude", "settings.json");
14423
15029
  const installRoot = options.installRoot ?? syntaurRoot();
14424
15030
  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");
15031
+ const destScript = resolve30(installRoot, "statusline.sh");
15032
+ const confPath = resolve30(installRoot, "statusline.conf");
15033
+ const backupPath = resolve30(installRoot, "statusline.backup.json");
14428
15034
  if (!await fileExists(sourceScript)) {
14429
15035
  throw new Error(
14430
15036
  `Statusline source script not found at ${sourceScript}. Try re-installing syntaur (npm install -g syntaur) or pass --source-script explicitly.`
@@ -14460,7 +15066,7 @@ async function installStatuslineCommand(options = {}) {
14460
15066
  if (parsed) {
14461
15067
  wrapTarget = parsed;
14462
15068
  } else {
14463
- const wrapperPath = resolve29(installRoot, "statusline-wrapped.sh");
15069
+ const wrapperPath = resolve30(installRoot, "statusline-wrapped.sh");
14464
15070
  const wrapperBody = `#!/usr/bin/env bash
14465
15071
  # Auto-generated by syntaur install-statusline.
14466
15072
  # Executes the previously configured statusLine command.
@@ -14471,7 +15077,7 @@ exec ${existingCommand}
14471
15077
  wrapTarget = wrapperPath;
14472
15078
  }
14473
15079
  }
14474
- await ensureDir(dirname10(confPath));
15080
+ await ensureDir(dirname11(confPath));
14475
15081
  await writeFile8(
14476
15082
  confPath,
14477
15083
  wrapTarget ? `# Wrap target \u2014 the command below is invoked with the same stdin; its
@@ -14515,12 +15121,12 @@ async function chmodExec(path) {
14515
15121
  }
14516
15122
  }
14517
15123
  async function uninstallStatuslineCommand(options = {}) {
14518
- const settingsPath = options.settingsPath ?? resolve29(homedir5(), ".claude", "settings.json");
15124
+ const settingsPath = options.settingsPath ?? resolve30(homedir5(), ".claude", "settings.json");
14519
15125
  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");
15126
+ const destScript = resolve30(installRoot, "statusline.sh");
15127
+ const confPath = resolve30(installRoot, "statusline.conf");
15128
+ const backupPath = resolve30(installRoot, "statusline.backup.json");
15129
+ const wrapperPath = resolve30(installRoot, "statusline-wrapped.sh");
14524
15130
  const settings = await readSettingsJson(settingsPath);
14525
15131
  const existing = extractExistingCommand(settings);
14526
15132
  const ourCommand = `bash ${destScript}`;
@@ -14548,7 +15154,7 @@ async function uninstallStatuslineCommand(options = {}) {
14548
15154
  await writeSettingsJson(settingsPath, settings);
14549
15155
  }
14550
15156
  if (!options.keepScript) {
14551
- const configPath = resolve29(installRoot, "statusline.config.json");
15157
+ const configPath = resolve30(installRoot, "statusline.config.json");
14552
15158
  for (const path of [destScript, confPath, backupPath, wrapperPath, configPath]) {
14553
15159
  try {
14554
15160
  await rm5(path, { force: true });
@@ -14805,7 +15411,7 @@ async function setupCommand(options) {
14805
15411
  }
14806
15412
 
14807
15413
  // src/commands/uninstall.ts
14808
- import { resolve as resolve30 } from "path";
15414
+ import { resolve as resolve31 } from "path";
14809
15415
  init_paths();
14810
15416
  function expandTargets(options) {
14811
15417
  if (options.all) {
@@ -14885,7 +15491,7 @@ async function uninstallCommand(options) {
14885
15491
  const configuredProjectDir = await getConfiguredProjectDir();
14886
15492
  await removeSyntaurData();
14887
15493
  console.log(`Removed ${syntaurRoot()}`);
14888
- if (configuredProjectDir && resolve30(configuredProjectDir) !== resolve30(syntaurRoot(), "projects")) {
15494
+ if (configuredProjectDir && resolve31(configuredProjectDir) !== resolve31(syntaurRoot(), "projects")) {
14889
15495
  console.warn(
14890
15496
  `Warning: config.md pointed to an external project directory (${configuredProjectDir}). That directory was not removed automatically.`
14891
15497
  );
@@ -14904,7 +15510,7 @@ init_slug();
14904
15510
  init_cursor_rules();
14905
15511
  init_codex_agents();
14906
15512
  init_opencode_config();
14907
- import { resolve as resolve31 } from "path";
15513
+ import { resolve as resolve32 } from "path";
14908
15514
  var SUPPORTED_FRAMEWORKS = ["cursor", "codex", "opencode"];
14909
15515
  async function setupAdapterCommand(framework, options) {
14910
15516
  if (!SUPPORTED_FRAMEWORKS.includes(framework)) {
@@ -14930,19 +15536,19 @@ async function setupAdapterCommand(framework, options) {
14930
15536
  }
14931
15537
  const config = await readConfig();
14932
15538
  const baseDir = options.dir ? expandHome(options.dir) : config.defaultProjectDir;
14933
- const projectDir = resolve31(baseDir, options.project);
14934
- const assignmentDir = resolve31(
15539
+ const projectDir = resolve32(baseDir, options.project);
15540
+ const assignmentDir = resolve32(
14935
15541
  projectDir,
14936
15542
  "assignments",
14937
15543
  options.assignment
14938
15544
  );
14939
- const projectMdPath = resolve31(projectDir, "project.md");
15545
+ const projectMdPath = resolve32(projectDir, "project.md");
14940
15546
  if (!await fileExists(projectDir) || !await fileExists(projectMdPath)) {
14941
15547
  throw new Error(
14942
15548
  `Project "${options.project}" not found at ${projectDir}.`
14943
15549
  );
14944
15550
  }
14945
- const assignmentMdPath2 = resolve31(assignmentDir, "assignment.md");
15551
+ const assignmentMdPath2 = resolve32(assignmentDir, "assignment.md");
14946
15552
  if (!await fileExists(assignmentDir) || !await fileExists(assignmentMdPath2)) {
14947
15553
  throw new Error(
14948
15554
  `Assignment "${options.assignment}" not found at ${assignmentDir}.`
@@ -14970,15 +15576,15 @@ async function setupAdapterCommand(framework, options) {
14970
15576
  }
14971
15577
  }
14972
15578
  if (framework === "cursor") {
14973
- const protocolPath = resolve31(cwd, ".cursor", "rules", "syntaur-protocol.mdc");
14974
- const assignmentPath = resolve31(cwd, ".cursor", "rules", "syntaur-assignment.mdc");
15579
+ const protocolPath = resolve32(cwd, ".cursor", "rules", "syntaur-protocol.mdc");
15580
+ const assignmentPath = resolve32(cwd, ".cursor", "rules", "syntaur-assignment.mdc");
14975
15581
  await writeAdapterFile(protocolPath, renderCursorProtocol());
14976
15582
  await writeAdapterFile(assignmentPath, renderCursorAssignment(rendererParams));
14977
15583
  } else if (framework === "codex" || framework === "opencode") {
14978
- const agentsPath = resolve31(cwd, "AGENTS.md");
15584
+ const agentsPath = resolve32(cwd, "AGENTS.md");
14979
15585
  await writeAdapterFile(agentsPath, renderCodexAgents(rendererParams));
14980
15586
  if (framework === "opencode") {
14981
- const configPath = resolve31(cwd, "opencode.json");
15587
+ const configPath = resolve32(cwd, "opencode.json");
14982
15588
  await writeAdapterFile(configPath, renderOpenCodeConfig({ projectDir }));
14983
15589
  }
14984
15590
  }
@@ -15003,7 +15609,7 @@ async function setupAdapterCommand(framework, options) {
15003
15609
  init_paths();
15004
15610
  init_fs();
15005
15611
  init_config2();
15006
- import { resolve as resolve32 } from "path";
15612
+ import { resolve as resolve33 } from "path";
15007
15613
  async function trackSessionCommand(options) {
15008
15614
  if (!options.agent) {
15009
15615
  throw new Error("--agent <name> is required.");
@@ -15016,7 +15622,7 @@ async function trackSessionCommand(options) {
15016
15622
  if (options.project) {
15017
15623
  const config = await readConfig();
15018
15624
  const baseDir = options.dir ? expandHome(options.dir) : config.defaultProjectDir;
15019
- const projectDir = resolve32(baseDir, options.project);
15625
+ const projectDir = resolve33(baseDir, options.project);
15020
15626
  if (!await fileExists(projectDir)) {
15021
15627
  throw new Error(
15022
15628
  `Project "${options.project}" not found at ${projectDir}.`
@@ -15050,7 +15656,7 @@ init_config2();
15050
15656
  init_paths();
15051
15657
  init_fs();
15052
15658
  import { spawnSync as spawnSync2 } from "child_process";
15053
- import { resolve as resolve34, isAbsolute as isAbsolute4 } from "path";
15659
+ import { resolve as resolve35, isAbsolute as isAbsolute4 } from "path";
15054
15660
  import { readFile as readFile22 } from "fs/promises";
15055
15661
  import { select, confirm as confirm2, input as input3 } from "@inquirer/prompts";
15056
15662
  async function browseCommand(options) {
@@ -15120,7 +15726,7 @@ async function pickAgent(agents) {
15120
15726
  return picked;
15121
15727
  }
15122
15728
  async function ensureWorktree(opts) {
15123
- const assignmentPath = resolve34(
15729
+ const assignmentPath = resolve35(
15124
15730
  opts.projectsDir,
15125
15731
  opts.projectSlug,
15126
15732
  "assignments",
@@ -15201,7 +15807,7 @@ function computeWorktreeDefaults(opts) {
15201
15807
  const repository = opts.existing.repository ?? detectCurrentGitRoot();
15202
15808
  const branch = opts.projectSlug ? `syntaur/${opts.projectSlug}/${opts.assignmentSlug}` : `syntaur/${opts.assignmentSlug}`;
15203
15809
  const parentBranch = opts.existing.parentBranch ?? detectCurrentBranch() ?? "main";
15204
- const worktreeBase = repository ? resolve34(repository, ".worktrees", branch) : resolve34(
15810
+ const worktreeBase = repository ? resolve35(repository, ".worktrees", branch) : resolve35(
15205
15811
  syntaurRoot(),
15206
15812
  "worktrees",
15207
15813
  opts.projectSlug || "standalone",
@@ -15234,7 +15840,7 @@ function detectCurrentBranch() {
15234
15840
  async function runCreate(opts) {
15235
15841
  const { createWorktreeAndRecord: createWorktreeAndRecord2, GitWorktreeError: GitWorktreeError2 } = await Promise.resolve().then(() => (init_git_worktree(), git_worktree_exports));
15236
15842
  const expandedWorktree = expandHome(opts.worktreePath);
15237
- const absWorktree = isAbsolute4(expandedWorktree) ? expandedWorktree : resolve34(expandedWorktree);
15843
+ const absWorktree = isAbsolute4(expandedWorktree) ? expandedWorktree : resolve35(expandedWorktree);
15238
15844
  try {
15239
15845
  await createWorktreeAndRecord2({
15240
15846
  assignmentPath: opts.assignmentPath,
@@ -15263,7 +15869,7 @@ init_paths();
15263
15869
  init_fs();
15264
15870
  init_playbook();
15265
15871
  init_playbooks();
15266
- import { resolve as resolve35 } from "path";
15872
+ import { resolve as resolve36 } from "path";
15267
15873
  async function createPlaybookCommand(name, options) {
15268
15874
  if (!name.trim()) {
15269
15875
  throw new Error("Playbook name cannot be empty.");
@@ -15276,7 +15882,7 @@ async function createPlaybookCommand(name, options) {
15276
15882
  }
15277
15883
  const dir = playbooksDir();
15278
15884
  await ensureDir(dir);
15279
- const filePath = resolve35(dir, `${slug}.md`);
15885
+ const filePath = resolve36(dir, `${slug}.md`);
15280
15886
  if (await fileExists(filePath)) {
15281
15887
  throw new Error(
15282
15888
  `Playbook "${slug}" already exists at ${filePath}
@@ -15299,7 +15905,7 @@ init_fs();
15299
15905
  init_parser();
15300
15906
  init_config2();
15301
15907
  import { readdir as readdir12, readFile as readFile23 } from "fs/promises";
15302
- import { resolve as resolve36 } from "path";
15908
+ import { resolve as resolve37 } from "path";
15303
15909
  async function listPlaybooksCommand(options = {}) {
15304
15910
  const dir = playbooksDir();
15305
15911
  if (!await fileExists(dir)) {
@@ -15314,7 +15920,7 @@ async function listPlaybooksCommand(options = {}) {
15314
15920
  );
15315
15921
  const rows = [];
15316
15922
  for (const entry of mdFiles) {
15317
- const filePath = resolve36(dir, entry.name);
15923
+ const filePath = resolve37(dir, entry.name);
15318
15924
  const raw = await readFile23(filePath, "utf-8");
15319
15925
  const parsed = parsePlaybook(raw);
15320
15926
  const slug = parsed.slug || entry.name.replace(/\.md$/, "");
@@ -15414,7 +16020,7 @@ init_config2();
15414
16020
  init_slug();
15415
16021
  import { Command } from "commander";
15416
16022
  import { readFile as readFile24 } from "fs/promises";
15417
- import { resolve as resolve37 } from "path";
16023
+ import { resolve as resolve38 } from "path";
15418
16024
  var WORKSPACE_REGEX3 = /^[a-z0-9_][a-z0-9-]*$/;
15419
16025
  async function resolveScope(options) {
15420
16026
  const flagCount = [Boolean(options.project), Boolean(options.workspace), Boolean(options.global)].filter(Boolean).length;
@@ -15426,7 +16032,7 @@ async function resolveScope(options) {
15426
16032
  throw new Error(`Invalid project slug: "${options.project}".`);
15427
16033
  }
15428
16034
  const config = await readConfig();
15429
- const projectMd = resolve37(config.defaultProjectDir, options.project, "project.md");
16035
+ const projectMd = resolve38(config.defaultProjectDir, options.project, "project.md");
15430
16036
  if (!await fileExists(projectMd)) {
15431
16037
  throw new Error(`Project "${options.project}" not found.`);
15432
16038
  }
@@ -15745,7 +16351,7 @@ todoCommand.command("archive").description("Archive completed todos and their lo
15745
16351
  (e) => e.itemIds.every((id) => completedIds.has(id))
15746
16352
  );
15747
16353
  const archFile = archivePath(todosPath, workspace, checklist.archiveInterval);
15748
- await ensureDir(resolve37(todosPath, "archive"));
16354
+ await ensureDir(resolve38(todosPath, "archive"));
15749
16355
  let archContent = "";
15750
16356
  if (await fileExists(archFile)) {
15751
16357
  archContent = await readFile24(archFile, "utf-8");
@@ -15977,7 +16583,7 @@ todoCommand.command("plan").description("Create or open a plan directory for a t
15977
16583
  );
15978
16584
  let target;
15979
16585
  if (existingFiles.length === 0) {
15980
- target = resolve37(planDir, "plan.md");
16586
+ target = resolve38(planDir, "plan.md");
15981
16587
  } else {
15982
16588
  const versions = /* @__PURE__ */ new Set();
15983
16589
  for (const f of existingFiles) {
@@ -15987,7 +16593,7 @@ todoCommand.command("plan").description("Create or open a plan directory for a t
15987
16593
  }
15988
16594
  let n = 2;
15989
16595
  while (versions.has(n)) n++;
15990
- target = resolve37(planDir, `plan-v${n}.md`);
16596
+ target = resolve38(planDir, `plan-v${n}.md`);
15991
16597
  }
15992
16598
  if (!await fileExists(target)) {
15993
16599
  const stub = `---
@@ -16058,8 +16664,8 @@ async function moveTodo(id, options) {
16058
16664
  throw new Error(`Plan directory already exists at target: ${newPlanDir}; refusing to move.`);
16059
16665
  }
16060
16666
  const { rename: rename8, mkdir: mkdir8 } = await import("fs/promises");
16061
- const { dirname: dirname16 } = await import("path");
16062
- await mkdir8(dirname16(newPlanDir), { recursive: true });
16667
+ const { dirname: dirname17 } = await import("path");
16668
+ await mkdir8(dirname17(newPlanDir), { recursive: true });
16063
16669
  await rename8(item.planDir, newPlanDir);
16064
16670
  item.planDir = newPlanDir;
16065
16671
  }
@@ -16177,31 +16783,31 @@ backupCommand.command("config").description("Show or update backup configuration
16177
16783
  // src/commands/doctor.ts
16178
16784
  import { Command as Command3 } from "commander";
16179
16785
  import { readFile as readFile31 } from "fs/promises";
16180
- import { isAbsolute as isAbsolute7, resolve as resolve47 } from "path";
16786
+ import { isAbsolute as isAbsolute7, resolve as resolve48 } from "path";
16181
16787
 
16182
16788
  // src/utils/doctor/index.ts
16183
16789
  import { fileURLToPath as fileURLToPath6 } from "url";
16184
16790
  import { readFile as readFile30 } from "fs/promises";
16185
- import { dirname as dirname13, join as join6 } from "path";
16791
+ import { dirname as dirname14, join as join6 } from "path";
16186
16792
 
16187
16793
  // src/utils/doctor/context.ts
16188
16794
  init_config2();
16189
16795
  init_paths();
16190
16796
  init_fs();
16191
- import Database2 from "better-sqlite3";
16192
- import { resolve as resolve38 } from "path";
16797
+ import Database3 from "better-sqlite3";
16798
+ import { resolve as resolve39 } from "path";
16193
16799
  async function buildCheckContext(cwd = process.cwd()) {
16194
16800
  const config = await readConfig();
16195
16801
  const root = syntaurRoot();
16196
- const dbPath = resolve38(root, "syntaur.db");
16197
- let db3 = null;
16802
+ const dbPath = resolve39(root, "syntaur.db");
16803
+ let db4 = null;
16198
16804
  let dbError = null;
16199
16805
  if (await fileExists(dbPath)) {
16200
16806
  try {
16201
- db3 = new Database2(dbPath, { readonly: true, fileMustExist: true });
16807
+ db4 = new Database3(dbPath, { readonly: true, fileMustExist: true });
16202
16808
  } catch (err2) {
16203
16809
  dbError = err2 instanceof Error ? err2.message : String(err2);
16204
- db3 = null;
16810
+ db4 = null;
16205
16811
  }
16206
16812
  } else {
16207
16813
  dbError = `database file not found at ${dbPath}`;
@@ -16209,7 +16815,7 @@ async function buildCheckContext(cwd = process.cwd()) {
16209
16815
  return {
16210
16816
  config,
16211
16817
  syntaurRoot: root,
16212
- db: db3,
16818
+ db: db4,
16213
16819
  dbError,
16214
16820
  cwd,
16215
16821
  now: /* @__PURE__ */ new Date()
@@ -16227,10 +16833,10 @@ function closeCheckContext(ctx) {
16227
16833
  // src/utils/doctor/checks/env.ts
16228
16834
  init_fs();
16229
16835
  init_paths();
16230
- import { resolve as resolve39, isAbsolute as isAbsolute5 } from "path";
16836
+ import { resolve as resolve40, isAbsolute as isAbsolute5 } from "path";
16231
16837
  import { readFile as readFile25, stat as stat3 } from "fs/promises";
16232
16838
  import { fileURLToPath as fileURLToPath5 } from "url";
16233
- import { dirname as dirname11, join as join4 } from "path";
16839
+ import { dirname as dirname12, join as join4 } from "path";
16234
16840
  var CATEGORY = "env";
16235
16841
  var syntaurRootExists = {
16236
16842
  id: "env.syntaur-root-exists",
@@ -16268,7 +16874,7 @@ var configValid = {
16268
16874
  category: CATEGORY,
16269
16875
  title: "~/.syntaur/config.md is valid",
16270
16876
  async run(ctx) {
16271
- const configPath = resolve39(ctx.syntaurRoot, "config.md");
16877
+ const configPath = resolve40(ctx.syntaurRoot, "config.md");
16272
16878
  if (!await fileExists(configPath)) {
16273
16879
  return {
16274
16880
  id: this.id,
@@ -16561,14 +17167,14 @@ async function readLocalVersion() {
16561
17167
  async function readLocalPkg() {
16562
17168
  try {
16563
17169
  const here = fileURLToPath5(import.meta.url);
16564
- let dir = dirname11(here);
17170
+ let dir = dirname12(here);
16565
17171
  for (let i = 0; i < 6; i++) {
16566
17172
  const candidate = join4(dir, "package.json");
16567
17173
  try {
16568
17174
  const text = await readFile25(candidate, "utf-8");
16569
17175
  return JSON.parse(text);
16570
17176
  } catch {
16571
- dir = dirname11(dir);
17177
+ dir = dirname12(dir);
16572
17178
  }
16573
17179
  }
16574
17180
  return null;
@@ -16617,7 +17223,7 @@ function versionGte(a, b) {
16617
17223
 
16618
17224
  // src/utils/doctor/checks/structure.ts
16619
17225
  init_fs();
16620
- import { resolve as resolve40 } from "path";
17226
+ import { resolve as resolve41 } from "path";
16621
17227
  import { readdir as readdir13, stat as stat4 } from "fs/promises";
16622
17228
  var CATEGORY2 = "structure";
16623
17229
  var KNOWN_TOP_LEVEL = /* @__PURE__ */ new Set([
@@ -16637,7 +17243,7 @@ var projectsDir = {
16637
17243
  category: CATEGORY2,
16638
17244
  title: "projects/ directory exists",
16639
17245
  async run(ctx) {
16640
- const p = resolve40(ctx.syntaurRoot, "projects");
17246
+ const p = resolve41(ctx.syntaurRoot, "projects");
16641
17247
  if (!await fileExists(p)) {
16642
17248
  return {
16643
17249
  id: this.id,
@@ -16662,7 +17268,7 @@ var playbooksDir2 = {
16662
17268
  category: CATEGORY2,
16663
17269
  title: "playbooks/ directory exists",
16664
17270
  async run(ctx) {
16665
- const p = resolve40(ctx.syntaurRoot, "playbooks");
17271
+ const p = resolve41(ctx.syntaurRoot, "playbooks");
16666
17272
  if (!await fileExists(p)) {
16667
17273
  return {
16668
17274
  id: this.id,
@@ -16687,7 +17293,7 @@ var todosDirValid = {
16687
17293
  category: CATEGORY2,
16688
17294
  title: "todos/ directory is readable (if present)",
16689
17295
  async run(ctx) {
16690
- const p = resolve40(ctx.syntaurRoot, "todos");
17296
+ const p = resolve41(ctx.syntaurRoot, "todos");
16691
17297
  if (!await fileExists(p)) {
16692
17298
  return {
16693
17299
  id: this.id,
@@ -16718,7 +17324,7 @@ var serversDirValid = {
16718
17324
  category: CATEGORY2,
16719
17325
  title: "servers/ directory is readable (if present)",
16720
17326
  async run(ctx) {
16721
- const p = resolve40(ctx.syntaurRoot, "servers");
17327
+ const p = resolve41(ctx.syntaurRoot, "servers");
16722
17328
  if (!await fileExists(p)) {
16723
17329
  return {
16724
17330
  id: this.id,
@@ -16763,7 +17369,7 @@ var knownFilesRecognized = {
16763
17369
  title: this.title,
16764
17370
  status: "warn",
16765
17371
  detail: `unexpected top-level entries: ${unexpected.join(", ")}`,
16766
- affected: unexpected.map((n) => resolve40(ctx.syntaurRoot, n)),
17372
+ affected: unexpected.map((n) => resolve41(ctx.syntaurRoot, n)),
16767
17373
  remediation: {
16768
17374
  kind: "manual",
16769
17375
  suggestion: "Review these entries \u2014 they may be leftover state from older versions",
@@ -16792,7 +17398,7 @@ function pass2(check) {
16792
17398
 
16793
17399
  // src/utils/doctor/checks/project.ts
16794
17400
  init_fs();
16795
- import { resolve as resolve41 } from "path";
17401
+ import { resolve as resolve42 } from "path";
16796
17402
  import { readdir as readdir14, stat as stat5 } from "fs/promises";
16797
17403
  var CATEGORY3 = "project";
16798
17404
  var REQUIRED_PROJECT_FILES = [
@@ -16822,10 +17428,10 @@ async function listProjects2(ctx) {
16822
17428
  for (const e of entries) {
16823
17429
  if (!e.isDirectory()) continue;
16824
17430
  if (e.name.startsWith(".") || e.name.startsWith("_")) continue;
16825
- const projectDir = resolve41(dir, e.name);
17431
+ const projectDir = resolve42(dir, e.name);
16826
17432
  let looksLikeProject = false;
16827
17433
  for (const marker of PROJECT_MARKERS) {
16828
- if (await fileExists(resolve41(projectDir, marker))) {
17434
+ if (await fileExists(resolve42(projectDir, marker))) {
16829
17435
  looksLikeProject = true;
16830
17436
  break;
16831
17437
  }
@@ -16844,7 +17450,7 @@ var requiredFiles = {
16844
17450
  for (const projectDir of projects) {
16845
17451
  const missing = [];
16846
17452
  for (const rel of REQUIRED_PROJECT_FILES) {
16847
- const p = resolve41(projectDir, rel);
17453
+ const p = resolve42(projectDir, rel);
16848
17454
  if (!await fileExists(p)) missing.push(rel);
16849
17455
  }
16850
17456
  if (missing.length === 0) continue;
@@ -16854,7 +17460,7 @@ var requiredFiles = {
16854
17460
  title: this.title,
16855
17461
  status: "error",
16856
17462
  detail: `project at ${projectDir} is missing: ${missing.join(", ")}`,
16857
- affected: missing.map((m) => resolve41(projectDir, m)),
17463
+ affected: missing.map((m) => resolve42(projectDir, m)),
16858
17464
  remediation: {
16859
17465
  kind: "manual",
16860
17466
  suggestion: "Recreate the missing scaffold files from templates",
@@ -16877,7 +17483,7 @@ var manifestStale = {
16877
17483
  const projects = await listProjects2(ctx);
16878
17484
  const results = [];
16879
17485
  for (const projectDir of projects) {
16880
- const manifestPath = resolve41(projectDir, "manifest.md");
17486
+ const manifestPath = resolve42(projectDir, "manifest.md");
16881
17487
  if (!await fileExists(manifestPath)) continue;
16882
17488
  const manifestMtime = (await stat5(manifestPath)).mtimeMs;
16883
17489
  const newestAssignment = await newestAssignmentMtime(projectDir);
@@ -16926,7 +17532,7 @@ var orphanFiles = {
16926
17532
  title: this.title,
16927
17533
  status: "warn",
16928
17534
  detail: `project at ${projectDir} has unexpected entries: ${orphans.join(", ")}`,
16929
- affected: orphans.map((o) => resolve41(projectDir, o)),
17535
+ affected: orphans.map((o) => resolve42(projectDir, o)),
16930
17536
  autoFixable: false
16931
17537
  });
16932
17538
  }
@@ -16936,7 +17542,7 @@ var orphanFiles = {
16936
17542
  };
16937
17543
  var projectChecks = [requiredFiles, manifestStale, orphanFiles];
16938
17544
  async function newestAssignmentMtime(projectDir) {
16939
- const assignmentsRoot = resolve41(projectDir, "assignments");
17545
+ const assignmentsRoot = resolve42(projectDir, "assignments");
16940
17546
  if (!await fileExists(assignmentsRoot)) return 0;
16941
17547
  let newest = 0;
16942
17548
  let entries;
@@ -16947,7 +17553,7 @@ async function newestAssignmentMtime(projectDir) {
16947
17553
  }
16948
17554
  for (const e of entries) {
16949
17555
  if (!e.isDirectory()) continue;
16950
- const assignmentMd = resolve41(assignmentsRoot, e.name, "assignment.md");
17556
+ const assignmentMd = resolve42(assignmentsRoot, e.name, "assignment.md");
16951
17557
  try {
16952
17558
  const s = await stat5(assignmentMd);
16953
17559
  if (s.mtimeMs > newest) newest = s.mtimeMs;
@@ -16971,7 +17577,7 @@ init_fs();
16971
17577
  init_parser();
16972
17578
  init_types();
16973
17579
  init_paths();
16974
- import { resolve as resolve42 } from "path";
17580
+ import { resolve as resolve43 } from "path";
16975
17581
  import { readdir as readdir15, readFile as readFile26 } from "fs/promises";
16976
17582
  var CATEGORY4 = "assignment";
16977
17583
  var STATUSES_REQUIRING_HANDOFF = /* @__PURE__ */ new Set(["review", "completed"]);
@@ -16983,16 +17589,16 @@ async function listAssignments(ctx) {
16983
17589
  for (const m of projects) {
16984
17590
  if (!m.isDirectory()) continue;
16985
17591
  if (m.name.startsWith(".") || m.name.startsWith("_")) continue;
16986
- const assignmentsDir2 = resolve42(projectsDir2, m.name, "assignments");
17592
+ const assignmentsDir2 = resolve43(projectsDir2, m.name, "assignments");
16987
17593
  if (!await fileExists(assignmentsDir2)) continue;
16988
17594
  const entries = await readdir15(assignmentsDir2, { withFileTypes: true });
16989
17595
  for (const a of entries) {
16990
17596
  if (!a.isDirectory()) continue;
16991
17597
  if (a.name.startsWith(".") || a.name.startsWith("_")) continue;
16992
- const assignmentDir = resolve42(assignmentsDir2, a.name);
16993
- const assignmentMd = resolve42(assignmentDir, "assignment.md");
17598
+ const assignmentDir = resolve43(assignmentsDir2, a.name);
17599
+ const assignmentMd = resolve43(assignmentDir, "assignment.md");
16994
17600
  const entry = {
16995
- projectDir: resolve42(projectsDir2, m.name),
17601
+ projectDir: resolve43(projectsDir2, m.name),
16996
17602
  projectSlug: m.name,
16997
17603
  assignmentDir,
16998
17604
  assignmentSlug: a.name,
@@ -17012,8 +17618,8 @@ async function listAssignments(ctx) {
17012
17618
  for (const a of entries) {
17013
17619
  if (!a.isDirectory()) continue;
17014
17620
  if (a.name.startsWith(".") || a.name.startsWith("_")) continue;
17015
- const assignmentDir = resolve42(standaloneRoot, a.name);
17016
- const assignmentMd = resolve42(assignmentDir, "assignment.md");
17621
+ const assignmentDir = resolve43(standaloneRoot, a.name);
17622
+ const assignmentMd = resolve43(assignmentDir, "assignment.md");
17017
17623
  const entry = {
17018
17624
  projectDir: standaloneRoot,
17019
17625
  projectSlug: null,
@@ -17091,7 +17697,7 @@ var invalidStatus = {
17091
17697
  const allowed = configuredStatuses(ctx);
17092
17698
  const results = [];
17093
17699
  for (const a of withAssignmentMd) {
17094
- const path = resolve42(a.assignmentDir, "assignment.md");
17700
+ const path = resolve43(a.assignmentDir, "assignment.md");
17095
17701
  const parsed = await parseSafe(path);
17096
17702
  if (!parsed) continue;
17097
17703
  if (!allowed.has(parsed.status)) {
@@ -17124,7 +17730,7 @@ var workspaceMissing = {
17124
17730
  const terminal = terminalStatuses(ctx);
17125
17731
  const results = [];
17126
17732
  for (const a of withAssignmentMd) {
17127
- const path = resolve42(a.assignmentDir, "assignment.md");
17733
+ const path = resolve43(a.assignmentDir, "assignment.md");
17128
17734
  const parsed = await parseSafe(path);
17129
17735
  if (!parsed) continue;
17130
17736
  if (terminal.has(parsed.status)) continue;
@@ -17171,12 +17777,12 @@ var requiredFilesByStatus = {
17171
17777
  const { withAssignmentMd } = await listAssignments(ctx);
17172
17778
  const results = [];
17173
17779
  for (const a of withAssignmentMd) {
17174
- const assignmentPath = resolve42(a.assignmentDir, "assignment.md");
17780
+ const assignmentPath = resolve43(a.assignmentDir, "assignment.md");
17175
17781
  const parsed = await parseSafe(assignmentPath);
17176
17782
  if (!parsed) continue;
17177
17783
  const missing = [];
17178
17784
  if (STATUSES_REQUIRING_HANDOFF.has(parsed.status)) {
17179
- const handoffPath = resolve42(a.assignmentDir, "handoff.md");
17785
+ const handoffPath = resolve43(a.assignmentDir, "handoff.md");
17180
17786
  if (!await fileExists(handoffPath)) missing.push("handoff.md");
17181
17787
  }
17182
17788
  if (missing.length === 0) continue;
@@ -17186,7 +17792,7 @@ var requiredFilesByStatus = {
17186
17792
  title: this.title,
17187
17793
  status: "warn",
17188
17794
  detail: `${a.projectSlug}/${a.assignmentSlug} (status: ${parsed.status}) is missing ${missing.join(", ")}`,
17189
- affected: missing.map((m) => resolve42(a.assignmentDir, m)),
17795
+ affected: missing.map((m) => resolve43(a.assignmentDir, m)),
17190
17796
  remediation: {
17191
17797
  kind: "manual",
17192
17798
  suggestion: `Create the missing ${missing.join(" and ")} files for this assignment`,
@@ -17209,7 +17815,7 @@ var companionFilesScaffolded = {
17209
17815
  for (const a of withAssignmentMd) {
17210
17816
  const missing = [];
17211
17817
  for (const filename of ["progress.md", "comments.md"]) {
17212
- if (!await fileExists(resolve42(a.assignmentDir, filename))) {
17818
+ if (!await fileExists(resolve43(a.assignmentDir, filename))) {
17213
17819
  missing.push(filename);
17214
17820
  }
17215
17821
  }
@@ -17221,7 +17827,7 @@ var companionFilesScaffolded = {
17221
17827
  title: this.title,
17222
17828
  status: "warn",
17223
17829
  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)),
17830
+ affected: missing.map((m) => resolve43(a.assignmentDir, m)),
17225
17831
  remediation: {
17226
17832
  kind: "manual",
17227
17833
  suggestion: `Create ${missing.join(" and ")} with the renderProgress/renderComments templates, or re-scaffold via the CLI`,
@@ -17254,7 +17860,7 @@ var typeDefinition = {
17254
17860
  const { withAssignmentMd } = await listAssignments(ctx);
17255
17861
  const results = [];
17256
17862
  for (const a of withAssignmentMd) {
17257
- const path = resolve42(a.assignmentDir, "assignment.md");
17863
+ const path = resolve43(a.assignmentDir, "assignment.md");
17258
17864
  const parsed = await parseSafe(path);
17259
17865
  if (!parsed) continue;
17260
17866
  if (!parsed.type) continue;
@@ -17288,7 +17894,7 @@ var projectFrontmatterMatchesContainer = {
17288
17894
  const { withAssignmentMd } = await listAssignments(ctx);
17289
17895
  const results = [];
17290
17896
  for (const a of withAssignmentMd) {
17291
- const path = resolve42(a.assignmentDir, "assignment.md");
17897
+ const path = resolve43(a.assignmentDir, "assignment.md");
17292
17898
  const parsed = await parseSafe(path);
17293
17899
  if (!parsed) continue;
17294
17900
  if (a.standalone) {
@@ -17362,7 +17968,7 @@ function pass4(check, detail) {
17362
17968
 
17363
17969
  // src/utils/doctor/checks/dashboard.ts
17364
17970
  init_fs();
17365
- import { resolve as resolve43 } from "path";
17971
+ import { resolve as resolve44 } from "path";
17366
17972
  var CATEGORY5 = "dashboard";
17367
17973
  var dbReachable = {
17368
17974
  id: "dashboard.db-reachable",
@@ -17376,7 +17982,7 @@ var dbReachable = {
17376
17982
  title: this.title,
17377
17983
  status: "error",
17378
17984
  detail: `could not open syntaur.db: ${ctx.dbError ?? "unknown error"}`,
17379
- affected: [resolve43(ctx.syntaurRoot, "syntaur.db")],
17985
+ affected: [resolve44(ctx.syntaurRoot, "syntaur.db")],
17380
17986
  remediation: {
17381
17987
  kind: "manual",
17382
17988
  suggestion: "Start the dashboard once (`syntaur dashboard`) to initialize the DB, or restore it from backup",
@@ -17394,7 +18000,7 @@ var dbReachable = {
17394
18000
  title: this.title,
17395
18001
  status: "error",
17396
18002
  detail: 'syntaur.db is missing the expected "sessions" table',
17397
- affected: [resolve43(ctx.syntaurRoot, "syntaur.db")],
18003
+ affected: [resolve44(ctx.syntaurRoot, "syntaur.db")],
17398
18004
  autoFixable: false
17399
18005
  };
17400
18006
  }
@@ -17406,7 +18012,7 @@ var dbReachable = {
17406
18012
  title: this.title,
17407
18013
  status: "error",
17408
18014
  detail: `syntaur.db query failed: ${err2 instanceof Error ? err2.message : String(err2)}`,
17409
- affected: [resolve43(ctx.syntaurRoot, "syntaur.db")],
18015
+ affected: [resolve44(ctx.syntaurRoot, "syntaur.db")],
17410
18016
  autoFixable: false
17411
18017
  };
17412
18018
  }
@@ -17432,7 +18038,7 @@ var ghostSessions = {
17432
18038
  const results = [];
17433
18039
  for (const row of rows) {
17434
18040
  if (!row.project_slug) continue;
17435
- const projectPath = resolve43(projectsDir2, row.project_slug, "project.md");
18041
+ const projectPath = resolve44(projectsDir2, row.project_slug, "project.md");
17436
18042
  if (!await fileExists(projectPath)) {
17437
18043
  results.push({
17438
18044
  id: this.id,
@@ -17451,7 +18057,7 @@ var ghostSessions = {
17451
18057
  continue;
17452
18058
  }
17453
18059
  if (row.assignment_slug) {
17454
- const assignmentPath = resolve43(
18060
+ const assignmentPath = resolve44(
17455
18061
  projectsDir2,
17456
18062
  row.project_slug,
17457
18063
  "assignments",
@@ -17503,7 +18109,7 @@ function skipped(check, reason) {
17503
18109
 
17504
18110
  // src/utils/doctor/checks/integrations.ts
17505
18111
  init_fs();
17506
- import { resolve as resolve44, dirname as dirname12, basename as basename4 } from "path";
18112
+ import { resolve as resolve45, dirname as dirname13, basename as basename5 } from "path";
17507
18113
  import { readdir as readdir16, readFile as readFile27 } from "fs/promises";
17508
18114
  import { homedir as homedir6 } from "os";
17509
18115
  var CATEGORY6 = "integrations";
@@ -17586,7 +18192,7 @@ var backupConfigured = {
17586
18192
  }
17587
18193
  };
17588
18194
  async function readKnownMarketplaces() {
17589
- const path = resolve44(homedir6(), ".claude", "plugins", "known_marketplaces.json");
18195
+ const path = resolve45(homedir6(), ".claude", "plugins", "known_marketplaces.json");
17590
18196
  if (!await fileExists(path)) return {};
17591
18197
  try {
17592
18198
  const raw = await readFile27(path, "utf-8");
@@ -17605,8 +18211,8 @@ var claudeMarketplaceRegistered = {
17605
18211
  if (!await fileExists(dir)) {
17606
18212
  return skipped2(this, "claudePluginDir does not exist (run install-plugin)");
17607
18213
  }
17608
- const pluginsParent = dirname12(dir);
17609
- if (basename4(pluginsParent) !== "plugins") {
18214
+ const pluginsParent = dirname13(dir);
18215
+ if (basename5(pluginsParent) !== "plugins") {
17610
18216
  return {
17611
18217
  id: this.id,
17612
18218
  category: this.category,
@@ -17622,8 +18228,8 @@ var claudeMarketplaceRegistered = {
17622
18228
  autoFixable: false
17623
18229
  };
17624
18230
  }
17625
- const marketplaceRoot = dirname12(pluginsParent);
17626
- const marketplaceManifest = resolve44(marketplaceRoot, ".claude-plugin", "marketplace.json");
18231
+ const marketplaceRoot = dirname13(pluginsParent);
18232
+ const marketplaceManifest = resolve45(marketplaceRoot, ".claude-plugin", "marketplace.json");
17627
18233
  if (!await fileExists(marketplaceManifest)) {
17628
18234
  return {
17629
18235
  id: this.id,
@@ -17654,7 +18260,7 @@ var claudeMarketplaceRegistered = {
17654
18260
  autoFixable: false
17655
18261
  };
17656
18262
  }
17657
- const marketplaceName = parsed.name ?? basename4(marketplaceRoot);
18263
+ const marketplaceName = parsed.name ?? basename5(marketplaceRoot);
17658
18264
  const hasSyntaurEntry = (parsed.plugins ?? []).some((p) => p?.name === "syntaur");
17659
18265
  const known = await readKnownMarketplaces();
17660
18266
  const registered = known[marketplaceName]?.installLocation === marketplaceRoot || Object.values(known).some((v) => v.installLocation === marketplaceRoot);
@@ -17674,7 +18280,7 @@ var claudeMarketplaceRegistered = {
17674
18280
  title: this.title,
17675
18281
  status: "error",
17676
18282
  detail: issues.join("; "),
17677
- affected: [marketplaceManifest, resolve44(homedir6(), ".claude", "plugins", "known_marketplaces.json")],
18283
+ affected: [marketplaceManifest, resolve45(homedir6(), ".claude", "plugins", "known_marketplaces.json")],
17678
18284
  remediation: {
17679
18285
  kind: "manual",
17680
18286
  suggestion: "Re-run install-plugin to ensure both files are in sync.",
@@ -17714,7 +18320,7 @@ function skipped2(check, reason) {
17714
18320
  init_fs();
17715
18321
  init_parser();
17716
18322
  init_types();
17717
- import { resolve as resolve45 } from "path";
18323
+ import { resolve as resolve46 } from "path";
17718
18324
  import { readFile as readFile28 } from "fs/promises";
17719
18325
  var CATEGORY7 = "workspace";
17720
18326
  var ASSIGNMENT_FIELDS = ["projectSlug", "assignmentSlug", "projectDir", "assignmentDir"];
@@ -17727,7 +18333,7 @@ function isStandaloneSession(ctx) {
17727
18333
  return !hasAnyAssignmentField(ctx) && typeof ctx.sessionId === "string" && ctx.sessionId.length > 0;
17728
18334
  }
17729
18335
  async function loadContext(ctx) {
17730
- const path = resolve45(ctx.cwd, ".syntaur", "context.json");
18336
+ const path = resolve46(ctx.cwd, ".syntaur", "context.json");
17731
18337
  if (!await fileExists(path)) {
17732
18338
  return { data: null, path, exists: false, parseError: null };
17733
18339
  }
@@ -17807,7 +18413,7 @@ var contextAssignmentResolves = {
17807
18413
  if (!exists) return skipped3(this, "no context to resolve");
17808
18414
  if (isStandaloneSession(data)) return skipped3(this, "standalone session context \u2014 no assignment to resolve");
17809
18415
  if (!data?.assignmentDir) return skipped3(this, "context has no assignmentDir");
17810
- const assignmentMd = resolve45(data.assignmentDir, "assignment.md");
18416
+ const assignmentMd = resolve46(data.assignmentDir, "assignment.md");
17811
18417
  if (!await fileExists(assignmentMd)) {
17812
18418
  return {
17813
18419
  id: this.id,
@@ -17836,7 +18442,7 @@ var contextTerminal = {
17836
18442
  if (!exists) return skipped3(this, "no context to check");
17837
18443
  if (isStandaloneSession(data)) return skipped3(this, "standalone session context \u2014 no assignment to check");
17838
18444
  if (!data?.assignmentDir) return skipped3(this, "context has no assignmentDir");
17839
- const assignmentMd = resolve45(data.assignmentDir, "assignment.md");
18445
+ const assignmentMd = resolve46(data.assignmentDir, "assignment.md");
17840
18446
  if (!await fileExists(assignmentMd)) return skipped3(this, "assignment file missing");
17841
18447
  try {
17842
18448
  const content = await readFile28(assignmentMd, "utf-8");
@@ -17984,13 +18590,13 @@ var agentChecks = [agentsResolvable];
17984
18590
 
17985
18591
  // src/utils/doctor/checks/skills.ts
17986
18592
  init_fs();
17987
- import { resolve as resolve46, join as join5 } from "path";
18593
+ import { resolve as resolve47, join as join5 } from "path";
17988
18594
  import { readdir as readdir17, readFile as readFile29, lstat as lstat4 } from "fs/promises";
17989
18595
  import { homedir as homedir7 } from "os";
17990
18596
  var CATEGORY9 = "skills";
17991
18597
  var skillTargets = [
17992
- { agent: "claude", dir: resolve46(homedir7(), ".claude", "skills"), label: "~/.claude/skills" },
17993
- { agent: "codex", dir: resolve46(homedir7(), ".codex", "skills"), label: "~/.codex/skills" }
18598
+ { agent: "claude", dir: resolve47(homedir7(), ".claude", "skills"), label: "~/.claude/skills" },
18599
+ { agent: "codex", dir: resolve47(homedir7(), ".codex", "skills"), label: "~/.codex/skills" }
17994
18600
  ];
17995
18601
  var skillsDedupCheck = {
17996
18602
  id: "skills.dedup",
@@ -18156,14 +18762,14 @@ async function finalize(checks) {
18156
18762
  async function readVersion() {
18157
18763
  try {
18158
18764
  const here = fileURLToPath6(import.meta.url);
18159
- let dir = dirname13(here);
18765
+ let dir = dirname14(here);
18160
18766
  for (let i = 0; i < 6; i++) {
18161
18767
  try {
18162
18768
  const raw = await readFile30(join6(dir, "package.json"), "utf-8");
18163
18769
  const parsed = JSON.parse(raw);
18164
18770
  return typeof parsed.version === "string" ? parsed.version : null;
18165
18771
  } catch {
18166
- dir = dirname13(dir);
18772
+ dir = dirname14(dir);
18167
18773
  }
18168
18774
  }
18169
18775
  return null;
@@ -18256,7 +18862,7 @@ var REQUIRED_WORKSPACE_FIELDS = [
18256
18862
  ];
18257
18863
  var ISO_DATE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z$/;
18258
18864
  async function validateAssignmentFile(inputPath, cwd = process.cwd()) {
18259
- const absolute = isAbsolute7(inputPath) ? inputPath : resolve47(cwd, inputPath);
18865
+ const absolute = isAbsolute7(inputPath) ? inputPath : resolve48(cwd, inputPath);
18260
18866
  const errors = [];
18261
18867
  const warnings = [];
18262
18868
  if (!await fileExists(absolute)) {
@@ -18593,7 +19199,7 @@ init_uuid();
18593
19199
  init_timestamp();
18594
19200
  init_assignment_resolver();
18595
19201
  init_templates();
18596
- import { resolve as resolve48 } from "path";
19202
+ import { resolve as resolve49 } from "path";
18597
19203
  import { readFile as readFile32 } from "fs/promises";
18598
19204
  function shortId() {
18599
19205
  return generateId().split("-")[0];
@@ -18624,7 +19230,7 @@ async function commentCommand(target, text, options = {}) {
18624
19230
  if (!isValidSlug(target)) {
18625
19231
  throw new Error(`Invalid assignment slug "${target}".`);
18626
19232
  }
18627
- assignmentDir = resolve48(baseDir, options.project, "assignments", target);
19233
+ assignmentDir = resolve49(baseDir, options.project, "assignments", target);
18628
19234
  assignmentRef = target;
18629
19235
  } else {
18630
19236
  const resolved = await resolveAssignmentById(baseDir, assignmentsDir(), target);
@@ -18634,7 +19240,7 @@ async function commentCommand(target, text, options = {}) {
18634
19240
  assignmentDir = resolved.assignmentDir;
18635
19241
  assignmentRef = resolved.standalone ? resolved.id : resolved.assignmentSlug;
18636
19242
  }
18637
- const commentsPath = resolve48(assignmentDir, "comments.md");
19243
+ const commentsPath = resolve49(assignmentDir, "comments.md");
18638
19244
  const timestamp = nowTimestamp();
18639
19245
  const author = options.author ?? process.env.USER ?? "unknown";
18640
19246
  let currentContent;
@@ -18674,7 +19280,7 @@ ${entry}`;
18674
19280
  }
18675
19281
 
18676
19282
  // src/commands/capture.ts
18677
- import { resolve as resolve51, relative as relative4 } from "path";
19283
+ import { resolve as resolve52, relative as relative4 } from "path";
18678
19284
  import { copyFile as copyFile3, mkdir as mkdir7, realpath as realpath2, stat as stat6 } from "fs/promises";
18679
19285
 
18680
19286
  // src/utils/assignment-target.ts
@@ -18684,12 +19290,12 @@ init_config2();
18684
19290
  init_slug();
18685
19291
  init_assignment_resolver();
18686
19292
  init_parser();
18687
- import { resolve as resolve49 } from "path";
19293
+ import { resolve as resolve50 } from "path";
18688
19294
  import { readFile as readFile33 } from "fs/promises";
18689
19295
  var AssignmentTargetError = class extends Error {
18690
19296
  };
18691
19297
  async function readAssignmentFrontmatterId(assignmentDir) {
18692
- const path = resolve49(assignmentDir, "assignment.md");
19298
+ const path = resolve50(assignmentDir, "assignment.md");
18693
19299
  if (!await fileExists(path)) return null;
18694
19300
  try {
18695
19301
  const content = await readFile33(path, "utf-8");
@@ -18700,7 +19306,7 @@ async function readAssignmentFrontmatterId(assignmentDir) {
18700
19306
  }
18701
19307
  }
18702
19308
  async function readContextJson(cwd) {
18703
- const path = resolve49(cwd, ".syntaur", "context.json");
19309
+ const path = resolve50(cwd, ".syntaur", "context.json");
18704
19310
  if (!await fileExists(path)) return null;
18705
19311
  try {
18706
19312
  const raw = await readFile33(path, "utf-8");
@@ -18724,15 +19330,15 @@ async function resolveAssignmentTarget(input4, opts = {}) {
18724
19330
  if (!isValidSlug(input4)) {
18725
19331
  throw new AssignmentTargetError(`Invalid assignment slug "${input4}".`);
18726
19332
  }
18727
- const projectDir = resolve49(baseDir, opts.project);
18728
- const projectMdPath = resolve49(projectDir, "project.md");
19333
+ const projectDir = resolve50(baseDir, opts.project);
19334
+ const projectMdPath = resolve50(projectDir, "project.md");
18729
19335
  if (!await fileExists(projectDir) || !await fileExists(projectMdPath)) {
18730
19336
  throw new AssignmentTargetError(
18731
19337
  `Project "${opts.project}" not found at ${projectDir}.`
18732
19338
  );
18733
19339
  }
18734
- const assignmentDir = resolve49(projectDir, "assignments", input4);
18735
- const assignmentMdPath2 = resolve49(assignmentDir, "assignment.md");
19340
+ const assignmentDir = resolve50(projectDir, "assignments", input4);
19341
+ const assignmentMdPath2 = resolve50(assignmentDir, "assignment.md");
18736
19342
  if (!await fileExists(assignmentMdPath2)) {
18737
19343
  throw new AssignmentTargetError(
18738
19344
  `Assignment "${input4}" not found in project "${opts.project}".`
@@ -18766,7 +19372,7 @@ async function resolveAssignmentTarget(input4, opts = {}) {
18766
19372
  }
18767
19373
  if (ctx.assignmentDir) {
18768
19374
  const dir = expandHome(ctx.assignmentDir);
18769
- const assignmentMdPath2 = resolve49(dir, "assignment.md");
19375
+ const assignmentMdPath2 = resolve50(dir, "assignment.md");
18770
19376
  if (!await fileExists(assignmentMdPath2)) {
18771
19377
  throw new AssignmentTargetError(
18772
19378
  `.syntaur/context.json points to a missing assignment dir: ${dir}.`
@@ -18795,8 +19401,8 @@ async function resolveAssignmentTarget(input4, opts = {}) {
18795
19401
  `.syntaur/context.json contains invalid slugs: project="${ctx.projectSlug}" assignment="${ctx.assignmentSlug}".`
18796
19402
  );
18797
19403
  }
18798
- const assignmentDir = resolve49(baseDir, ctx.projectSlug, "assignments", ctx.assignmentSlug);
18799
- const assignmentMdPath2 = resolve49(assignmentDir, "assignment.md");
19404
+ const assignmentDir = resolve50(baseDir, ctx.projectSlug, "assignments", ctx.assignmentSlug);
19405
+ const assignmentMdPath2 = resolve50(assignmentDir, "assignment.md");
18800
19406
  if (!await fileExists(assignmentMdPath2)) {
18801
19407
  throw new AssignmentTargetError(
18802
19408
  `.syntaur/context.json points to a missing assignment: ${assignmentDir}.`
@@ -18857,11 +19463,11 @@ init_fs();
18857
19463
 
18858
19464
  // src/db/proof-db.ts
18859
19465
  init_paths();
18860
- import Database3 from "better-sqlite3";
18861
- import { resolve as resolve50 } from "path";
18862
- var db2 = null;
19466
+ import Database4 from "better-sqlite3";
19467
+ import { resolve as resolve51 } from "path";
19468
+ var db3 = null;
18863
19469
  var PROOF_SCHEMA_VERSION = "1";
18864
- var SCHEMA_SQL2 = `
19470
+ var SCHEMA_SQL3 = `
18865
19471
  CREATE TABLE IF NOT EXISTS artifacts (
18866
19472
  id TEXT PRIMARY KEY,
18867
19473
  assignment_id TEXT NOT NULL,
@@ -18877,26 +19483,26 @@ CREATE INDEX IF NOT EXISTS idx_artifacts_assignment_criterion ON artifacts(assig
18877
19483
  CREATE TABLE IF NOT EXISTS meta (key TEXT PRIMARY KEY, value TEXT);
18878
19484
  `;
18879
19485
  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(
19486
+ if (db3) return db3;
19487
+ const finalPath = dbPath ?? resolve51(syntaurRoot(), "syntaur.db");
19488
+ db3 = new Database4(finalPath);
19489
+ db3.pragma("journal_mode = WAL");
19490
+ db3.exec(SCHEMA_SQL3);
19491
+ db3.prepare("INSERT OR IGNORE INTO meta (key, value) VALUES (?, ?)").run(
18886
19492
  "proof_schema_version",
18887
19493
  PROOF_SCHEMA_VERSION
18888
19494
  );
18889
- const database = db2;
19495
+ const database = db3;
18890
19496
  const runMigrations = database.transaction(() => {
18891
19497
  });
18892
19498
  runMigrations.exclusive();
18893
- return db2;
19499
+ return db3;
18894
19500
  }
18895
19501
  function getProofDb() {
18896
- if (!db2) {
19502
+ if (!db3) {
18897
19503
  throw new Error("Proof database not initialized. Call initProofDb() first.");
18898
19504
  }
18899
- return db2;
19505
+ return db3;
18900
19506
  }
18901
19507
  function insertArtifact(input4) {
18902
19508
  const database = getProofDb();
@@ -18982,7 +19588,7 @@ async function captureCommand(target, options = {}) {
18982
19588
  });
18983
19589
  let resolvedSource = null;
18984
19590
  if (options.file) {
18985
- const expanded = options.file.startsWith("~/") ? resolve51(process.env.HOME ?? "", options.file.slice(2)) : resolve51(options.file);
19591
+ const expanded = options.file.startsWith("~/") ? resolve52(process.env.HOME ?? "", options.file.slice(2)) : resolve52(options.file);
18986
19592
  if (!await fileExists(expanded)) {
18987
19593
  throw new Error(`--file does not exist: ${options.file}`);
18988
19594
  }
@@ -19007,7 +19613,7 @@ async function captureCommand(target, options = {}) {
19007
19613
  }
19008
19614
  initProofDb();
19009
19615
  const subdir = criterionIndex === null ? "untagged" : String(criterionIndex);
19010
- const destDir = resolve51(proofDir(resolved.assignmentDir), subdir);
19616
+ const destDir = resolve52(proofDir(resolved.assignmentDir), subdir);
19011
19617
  if (resolvedSource) await mkdir7(destDir, { recursive: true });
19012
19618
  const ext = resolvedSource ? extensionForKind(kind) : null;
19013
19619
  let id = null;
@@ -19016,7 +19622,7 @@ async function captureCommand(target, options = {}) {
19016
19622
  let lastErr = null;
19017
19623
  for (let attempt = 0; attempt < MAX_ID_RETRIES; attempt += 1) {
19018
19624
  const candidate = generateArtifactId();
19019
- const candidateAbsPath = resolvedSource && ext ? resolve51(destDir, `${candidate}.${ext}`) : null;
19625
+ const candidateAbsPath = resolvedSource && ext ? resolve52(destDir, `${candidate}.${ext}`) : null;
19020
19626
  const candidateRel = candidateAbsPath ? relative4(resolved.assignmentDir, candidateAbsPath) : null;
19021
19627
  try {
19022
19628
  insertArtifact({
@@ -19052,14 +19658,14 @@ async function captureCommand(target, options = {}) {
19052
19658
  const tagSuffix = criterionIndex === null ? "untagged" : `criterion ${criterionIndex}`;
19053
19659
  console.log(`Captured artifact ${id} (${kind}) for ${ref} \u2014 ${tagSuffix}.`);
19054
19660
  if (relativeFilePath) {
19055
- console.log(` file: ${resolve51(resolved.assignmentDir, relativeFilePath)}`);
19661
+ console.log(` file: ${resolve52(resolved.assignmentDir, relativeFilePath)}`);
19056
19662
  }
19057
19663
  }
19058
19664
 
19059
19665
  // src/commands/proof.ts
19060
19666
  import { Command as Command5 } from "commander";
19061
19667
  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";
19668
+ import { resolve as resolve53, relative as relative5, isAbsolute as isAbsolute8 } from "path";
19063
19669
  import { randomBytes as randomBytes3 } from "crypto";
19064
19670
 
19065
19671
  // src/utils/acceptance-criteria-parse.ts
@@ -19262,7 +19868,7 @@ function renderProofHtml(params2, inlineFiles = /* @__PURE__ */ new Map()) {
19262
19868
 
19263
19869
  // src/commands/proof.ts
19264
19870
  async function readAssignmentMeta(assignmentDir) {
19265
- const path = resolve52(assignmentDir, "assignment.md");
19871
+ const path = resolve53(assignmentDir, "assignment.md");
19266
19872
  if (!await fileExists(path)) {
19267
19873
  return { title: "", body: "" };
19268
19874
  }
@@ -19313,7 +19919,7 @@ async function loadInlineFiles(rows, assignmentDir) {
19313
19919
  for (const r of rows) {
19314
19920
  if (!r.file_path) continue;
19315
19921
  if (r.kind !== "http" && r.kind !== "text") continue;
19316
- const abs = resolve52(assignmentDir, r.file_path);
19922
+ const abs = resolve53(assignmentDir, r.file_path);
19317
19923
  if (!isWithin(proofRoot, abs)) {
19318
19924
  out.set(r.file_path, null);
19319
19925
  continue;
@@ -19361,8 +19967,8 @@ async function proofBuildCommand(target, options = {}) {
19361
19967
  };
19362
19968
  const md = renderProofMarkdown(renderParams);
19363
19969
  const html = renderProofHtml(renderParams, inlineFiles);
19364
- const mdPath = resolve52(resolved.assignmentDir, "proof.md");
19365
- const htmlPath = resolve52(resolved.assignmentDir, "proof.html");
19970
+ const mdPath = resolve53(resolved.assignmentDir, "proof.md");
19971
+ const htmlPath = resolve53(resolved.assignmentDir, "proof.html");
19366
19972
  await atomicWrite(mdPath, md);
19367
19973
  await atomicWrite(htmlPath, html);
19368
19974
  console.log(`Wrote ${htmlPath}`);
@@ -19379,6 +19985,261 @@ proofCommand.command("build").description("Render proof.html and proof.md for an
19379
19985
  }
19380
19986
  });
19381
19987
 
19988
+ // src/commands/lease.ts
19989
+ import { Command as Command6 } from "commander";
19990
+ var DURATION_REGEX = /^(\d+)\s*(s|m|h|d)?$/i;
19991
+ function parseDuration(input4, fallbackSeconds) {
19992
+ if (!input4) return fallbackSeconds;
19993
+ const match = DURATION_REGEX.exec(input4.trim());
19994
+ if (!match) {
19995
+ throw new Error(
19996
+ `invalid duration "${input4}" \u2014 use e.g. 30s, 5m, 2h, 1d`
19997
+ );
19998
+ }
19999
+ const n = Number.parseInt(match[1], 10);
20000
+ const unit = (match[2] ?? "s").toLowerCase();
20001
+ const mult = { s: 1, m: 60, h: 3600, d: 86400 };
20002
+ return n * mult[unit];
20003
+ }
20004
+ function parseMetadataFlags(values) {
20005
+ if (!values || values.length === 0) return void 0;
20006
+ const out = {};
20007
+ for (const raw of values) {
20008
+ const eq = raw.indexOf("=");
20009
+ if (eq < 0) {
20010
+ throw new Error(`invalid -m flag "${raw}" \u2014 use key=value`);
20011
+ }
20012
+ out[raw.slice(0, eq)] = raw.slice(eq + 1);
20013
+ }
20014
+ return out;
20015
+ }
20016
+ var leaseCommand = new Command6("lease").description(
20017
+ "Resource leases \u2014 claim and release coordinated shared resources"
20018
+ );
20019
+ 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) => {
20020
+ try {
20021
+ initLeasesDb();
20022
+ const ttl_s = parseDuration(opts.defaultTtl, 30 * 60);
20023
+ const row = createInventory({
20024
+ slug,
20025
+ kind: opts.kind,
20026
+ display_name: opts.displayName,
20027
+ default_ttl_s: ttl_s
20028
+ });
20029
+ console.log(`Created inventory '${row.slug}' (kind=${row.kind}, default_ttl=${row.default_ttl_s}s).`);
20030
+ } catch (error) {
20031
+ if (error instanceof DuplicateInventoryError) {
20032
+ console.error(`Error: inventory '${error.slug}' already exists`);
20033
+ process.exit(1);
20034
+ }
20035
+ const message = error instanceof Error ? error.message : String(error);
20036
+ console.error(`Error: ${message}`);
20037
+ process.exit(1);
20038
+ }
20039
+ });
20040
+ var memberCommand = leaseCommand.command("member").description("Manage members of an inventory");
20041
+ 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) => {
20042
+ try {
20043
+ initLeasesDb();
20044
+ const metadata = parseMetadataFlags(opts.metadata);
20045
+ addMember({ inventory_slug: inventory, member_id: memberId, metadata });
20046
+ console.log(`Added member '${memberId}' to '${inventory}'.`);
20047
+ } catch (error) {
20048
+ if (error instanceof DuplicateMemberError) {
20049
+ console.error(`Error: member ${error.inventorySlug}/${error.memberId} already exists`);
20050
+ process.exit(1);
20051
+ }
20052
+ const message = error instanceof Error ? error.message : String(error);
20053
+ console.error(`Error: ${message}`);
20054
+ process.exit(1);
20055
+ }
20056
+ });
20057
+ 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) => {
20058
+ try {
20059
+ initLeasesDb();
20060
+ retireMember(inventory, memberId);
20061
+ console.log(`Retired member '${inventory}/${memberId}'.`);
20062
+ } catch (error) {
20063
+ if (error instanceof MemberInUseError) {
20064
+ console.error(
20065
+ `Error: member ${error.inventorySlug}/${error.memberId} is currently leased; release before retiring`
20066
+ );
20067
+ process.exit(1);
20068
+ }
20069
+ if (error instanceof NotFoundError) {
20070
+ console.error(`Error: member not found`);
20071
+ process.exit(1);
20072
+ }
20073
+ const message = error instanceof Error ? error.message : String(error);
20074
+ console.error(`Error: ${message}`);
20075
+ process.exit(1);
20076
+ }
20077
+ });
20078
+ leaseCommand.command("claim").description("Claim an idle member of an inventory. Fail-fast if none idle.").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("--json", "Output JSON instead of a one-line summary").action(async (inventory, opts) => {
20079
+ try {
20080
+ initLeasesDb();
20081
+ const detail = getInventoryDetail(inventory);
20082
+ if (!detail) {
20083
+ console.error(`Error: inventory '${inventory}' not found`);
20084
+ process.exit(1);
20085
+ return;
20086
+ }
20087
+ const ttl_s = parseDuration(opts.ttl, detail.inventory.default_ttl_s);
20088
+ const result = claimLease(inventory, ttl_s, opts.for);
20089
+ if (opts.json) {
20090
+ console.log(JSON.stringify(result, null, 2));
20091
+ } else {
20092
+ console.log(
20093
+ `Claimed ${result.member_id} as ${result.lease_id} (expires ${result.expires_at})`
20094
+ );
20095
+ }
20096
+ } catch (error) {
20097
+ if (error instanceof NoIdleMemberError) {
20098
+ console.error(`Error: no idle members in '${error.inventorySlug}'`);
20099
+ process.exit(1);
20100
+ }
20101
+ if (error instanceof LeaseContentionError) {
20102
+ console.error(`Error: contention timeout on '${error.inventorySlug}'; retry`);
20103
+ process.exit(1);
20104
+ }
20105
+ const message = error instanceof Error ? error.message : String(error);
20106
+ console.error(`Error: ${message}`);
20107
+ process.exit(1);
20108
+ }
20109
+ });
20110
+ leaseCommand.command("release").description("Release a lease by its lease_id").argument("<lease-id>", "Opaque lease id returned by `claim`").action(async (leaseId) => {
20111
+ try {
20112
+ initLeasesDb();
20113
+ releaseLease(leaseId);
20114
+ console.log(`Released lease ${leaseId}.`);
20115
+ } catch (error) {
20116
+ if (error instanceof StaleLeaseError) {
20117
+ console.error(
20118
+ `Error: lease ${error.leaseId} is no longer active (expired or revoked)`
20119
+ );
20120
+ process.exit(1);
20121
+ }
20122
+ if (error instanceof LeaseContentionError) {
20123
+ console.error(`Error: contention timeout; retry`);
20124
+ process.exit(1);
20125
+ }
20126
+ const message = error instanceof Error ? error.message : String(error);
20127
+ console.error(`Error: ${message}`);
20128
+ process.exit(1);
20129
+ }
20130
+ });
20131
+ 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) => {
20132
+ try {
20133
+ initLeasesDb();
20134
+ const ttl_s = parseDuration(opts.ttl, 0);
20135
+ if (ttl_s <= 0) {
20136
+ throw new Error("--ttl must be positive");
20137
+ }
20138
+ const res = extendLease(leaseId, ttl_s);
20139
+ console.log(`Extended lease ${leaseId} (new expires_at=${res.new_expires_at}).`);
20140
+ } catch (error) {
20141
+ if (error instanceof StaleLeaseError) {
20142
+ console.error(
20143
+ `Error: lease ${error.leaseId} cannot be extended (expired, revoked, or member generation advanced)`
20144
+ );
20145
+ process.exit(1);
20146
+ }
20147
+ if (error instanceof LeaseContentionError) {
20148
+ console.error(`Error: contention timeout; retry`);
20149
+ process.exit(1);
20150
+ }
20151
+ const message = error instanceof Error ? error.message : String(error);
20152
+ console.error(`Error: ${message}`);
20153
+ process.exit(1);
20154
+ }
20155
+ });
20156
+ leaseCommand.command("show").description("Show a single lease as JSON").argument("<lease-id>", "Lease id").action(async (leaseId) => {
20157
+ try {
20158
+ initLeasesDb();
20159
+ const row = getLease(leaseId);
20160
+ if (!row) {
20161
+ console.error(`Error: lease ${leaseId} not found`);
20162
+ process.exit(1);
20163
+ return;
20164
+ }
20165
+ console.log(JSON.stringify(row, null, 2));
20166
+ } catch (error) {
20167
+ const message = error instanceof Error ? error.message : String(error);
20168
+ console.error(`Error: ${message}`);
20169
+ process.exit(1);
20170
+ }
20171
+ });
20172
+ var VALID_STATES = /* @__PURE__ */ new Set([
20173
+ "active",
20174
+ "expired",
20175
+ "released",
20176
+ "revoked"
20177
+ ]);
20178
+ 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) => {
20179
+ try {
20180
+ initLeasesDb();
20181
+ let state;
20182
+ if (opts.state) {
20183
+ if (!VALID_STATES.has(opts.state)) {
20184
+ throw new Error(
20185
+ `--state must be one of: ${[...VALID_STATES].join("|")}`
20186
+ );
20187
+ }
20188
+ state = opts.state;
20189
+ }
20190
+ if (!opts.inventory && !state && !opts.json) {
20191
+ const inventories = listInventories();
20192
+ if (inventories.length === 0) {
20193
+ console.log("(no inventories \u2014 create one with `syntaur lease create-inventory <slug> --kind <kind>`)");
20194
+ return;
20195
+ }
20196
+ for (const inv of inventories) {
20197
+ const detail = getInventoryDetail(inv.slug);
20198
+ if (!detail) continue;
20199
+ const idleCount = detail.members.filter((m) => m.status === "idle").length;
20200
+ const leasedCount = detail.members.filter((m) => m.status === "leased").length;
20201
+ console.log(
20202
+ `${inv.slug.padEnd(24)} kind=${inv.kind} idle=${idleCount} leased=${leasedCount} active_leases=${detail.active_leases.length}`
20203
+ );
20204
+ }
20205
+ return;
20206
+ }
20207
+ const rows = listLeases({ inventory: opts.inventory, state });
20208
+ if (opts.json) {
20209
+ console.log(JSON.stringify(rows, null, 2));
20210
+ return;
20211
+ }
20212
+ if (rows.length === 0) {
20213
+ console.log("(no leases match)");
20214
+ return;
20215
+ }
20216
+ for (const r of rows) {
20217
+ console.log(
20218
+ `${r.lease_id} ${r.inventory_slug}/${r.member_id} ${r.state} expires=${r.expires_at}`
20219
+ );
20220
+ }
20221
+ } catch (error) {
20222
+ const message = error instanceof Error ? error.message : String(error);
20223
+ console.error(`Error: ${message}`);
20224
+ process.exit(1);
20225
+ }
20226
+ });
20227
+ leaseCommand.command("gc").description("Sweep expired leases across all inventories (idempotent, concurrency-safe)").action(async () => {
20228
+ try {
20229
+ initLeasesDb();
20230
+ const n = gcExpiredLeases();
20231
+ console.log(`Expired ${n} lease(s).`);
20232
+ } catch (error) {
20233
+ if (error instanceof LeaseContentionError) {
20234
+ console.error(`Error: contention timeout; retry`);
20235
+ process.exit(1);
20236
+ }
20237
+ const message = error instanceof Error ? error.message : String(error);
20238
+ console.error(`Error: ${message}`);
20239
+ process.exit(1);
20240
+ }
20241
+ });
20242
+
19382
20243
  // src/commands/request.ts
19383
20244
  init_paths();
19384
20245
  init_fs();
@@ -19387,7 +20248,7 @@ init_slug();
19387
20248
  init_timestamp();
19388
20249
  init_assignment_resolver();
19389
20250
  init_assignment_todos();
19390
- import { resolve as resolve53 } from "path";
20251
+ import { resolve as resolve54 } from "path";
19391
20252
  import { readFile as readFile35 } from "fs/promises";
19392
20253
  async function requestCommand(target, text, options = {}) {
19393
20254
  if (!text || !text.trim()) {
@@ -19404,7 +20265,7 @@ async function requestCommand(target, text, options = {}) {
19404
20265
  if (!isValidSlug(target)) {
19405
20266
  throw new Error(`Invalid assignment slug "${target}".`);
19406
20267
  }
19407
- assignmentDir = resolve53(baseDir, options.project, "assignments", target);
20268
+ assignmentDir = resolve54(baseDir, options.project, "assignments", target);
19408
20269
  targetRef = target;
19409
20270
  } else {
19410
20271
  const resolved = await resolveAssignmentById(baseDir, assignmentsDir(), target);
@@ -19414,7 +20275,7 @@ async function requestCommand(target, text, options = {}) {
19414
20275
  assignmentDir = resolved.assignmentDir;
19415
20276
  targetRef = resolved.standalone ? resolved.id : resolved.assignmentSlug;
19416
20277
  }
19417
- const assignmentMdPath2 = resolve53(assignmentDir, "assignment.md");
20278
+ const assignmentMdPath2 = resolve54(assignmentDir, "assignment.md");
19418
20279
  if (!await fileExists(assignmentMdPath2)) {
19419
20280
  throw new Error(`assignment.md not found at ${assignmentMdPath2}`);
19420
20281
  }
@@ -19431,11 +20292,11 @@ async function requestCommand(target, text, options = {}) {
19431
20292
  // src/commands/plan.ts
19432
20293
  init_fs();
19433
20294
  init_paths();
19434
- import { Command as Command6 } from "commander";
20295
+ import { Command as Command7 } from "commander";
19435
20296
  import { readFile as readFile36, readdir as readdir18 } from "fs/promises";
19436
- import { resolve as resolve54 } from "path";
20297
+ import { resolve as resolve55 } from "path";
19437
20298
  async function readContextAssignmentDir(cwd) {
19438
- const path = resolve54(cwd, ".syntaur", "context.json");
20299
+ const path = resolve55(cwd, ".syntaur", "context.json");
19439
20300
  if (!await fileExists(path)) return null;
19440
20301
  try {
19441
20302
  const raw = await readFile36(path, "utf-8");
@@ -19452,9 +20313,9 @@ async function resolveAssignmentDir(opts) {
19452
20313
  const cwd = opts.cwd ?? process.cwd();
19453
20314
  if (opts.assignment) {
19454
20315
  if (opts.project) {
19455
- return resolve54(defaultProjectDir(), opts.project, "assignments", opts.assignment);
20316
+ return resolve55(defaultProjectDir(), opts.project, "assignments", opts.assignment);
19456
20317
  }
19457
- return resolve54(assignmentsDir(), opts.assignment);
20318
+ return resolve55(assignmentsDir(), opts.assignment);
19458
20319
  }
19459
20320
  const fromCtx = await readContextAssignmentDir(cwd);
19460
20321
  if (fromCtx) return fromCtx;
@@ -19597,7 +20458,7 @@ async function runPlanVersion(options) {
19597
20458
  if (!await fileExists(assignmentDir)) {
19598
20459
  throw new Error(`Assignment directory does not exist: ${assignmentDir}`);
19599
20460
  }
19600
- const assignmentMdPath2 = resolve54(assignmentDir, "assignment.md");
20461
+ const assignmentMdPath2 = resolve55(assignmentDir, "assignment.md");
19601
20462
  if (!await fileExists(assignmentMdPath2)) {
19602
20463
  throw new Error(`Missing assignment.md at: ${assignmentMdPath2}`);
19603
20464
  }
@@ -19609,14 +20470,14 @@ async function runPlanVersion(options) {
19609
20470
  }
19610
20471
  const current = planFiles[planFiles.length - 1];
19611
20472
  const next = nextPlanFileName(current.version);
19612
- const newPath = resolve54(assignmentDir, next.fileName);
20473
+ const newPath = resolve55(assignmentDir, next.fileName);
19613
20474
  if (await fileExists(newPath) && !options.force) {
19614
20475
  throw new Error(`${next.fileName} already exists. Use --force to overwrite.`);
19615
20476
  }
19616
20477
  const assignmentMd = await readFile36(assignmentMdPath2, "utf-8");
19617
20478
  const slugMatch = assignmentMd.match(/^slug:\s*(.+?)\s*$/m);
19618
20479
  const slug = slugMatch ? slugMatch[1].trim() : assignmentDir.split("/").pop() ?? "";
19619
- const oldPlanPath = resolve54(assignmentDir, current.fileName);
20480
+ const oldPlanPath = resolve55(assignmentDir, current.fileName);
19620
20481
  const oldPlanContent = await readFile36(oldPlanPath, "utf-8");
19621
20482
  const oldBody = oldPlanContent.replace(/^---[\s\S]*?\n---\n?/, "");
19622
20483
  const carriedTodos = extractUncheckedTodos(oldBody);
@@ -19639,7 +20500,7 @@ async function runPlanVersion(options) {
19639
20500
  console.log(`Path: ${newPath}`);
19640
20501
  console.log(`Carried forward: ${carriedTodos.length} unchecked task(s).`);
19641
20502
  }
19642
- var planCommand = new Command6("plan").description("Manage plan files for the active assignment");
20503
+ var planCommand = new Command7("plan").description("Manage plan files for the active assignment");
19643
20504
  planCommand.command("version").description(
19644
20505
  "Create the next plan-v<N>.md, supersede the prior plan in assignment.md ## Todos, and carry forward unchecked tasks"
19645
20506
  ).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 +20514,11 @@ planCommand.command("version").description(
19653
20514
 
19654
20515
  // src/commands/session.ts
19655
20516
  init_fs();
19656
- import { Command as Command7 } from "commander";
20517
+ import { Command as Command8 } from "commander";
19657
20518
  import { readFile as readFile37, readdir as readdir19, stat as stat8 } from "fs/promises";
19658
- import { resolve as resolve55 } from "path";
20519
+ import { resolve as resolve56 } from "path";
19659
20520
  async function readContext(cwd) {
19660
- const path = resolve55(cwd, ".syntaur", "context.json");
20521
+ const path = resolve56(cwd, ".syntaur", "context.json");
19661
20522
  if (!await fileExists(path)) return null;
19662
20523
  try {
19663
20524
  const raw = await readFile37(path, "utf-8");
@@ -19667,13 +20528,13 @@ async function readContext(cwd) {
19667
20528
  }
19668
20529
  }
19669
20530
  async function findLatestSessionSummary(assignmentDir) {
19670
- const sessionsRoot = resolve55(assignmentDir, "sessions");
20531
+ const sessionsRoot = resolve56(assignmentDir, "sessions");
19671
20532
  if (!await fileExists(sessionsRoot)) return null;
19672
20533
  const entries = await readdir19(sessionsRoot, { withFileTypes: true });
19673
20534
  let best = null;
19674
20535
  for (const entry of entries) {
19675
20536
  if (!entry.isDirectory()) continue;
19676
- const summaryPath = resolve55(sessionsRoot, entry.name, "summary.md");
20537
+ const summaryPath = resolve56(sessionsRoot, entry.name, "summary.md");
19677
20538
  if (!await fileExists(summaryPath)) continue;
19678
20539
  const st = await stat8(summaryPath);
19679
20540
  if (best === null || st.mtime.getTime() > best.mtime.getTime()) {
@@ -19683,7 +20544,7 @@ async function findLatestSessionSummary(assignmentDir) {
19683
20544
  return best;
19684
20545
  }
19685
20546
  async function findOpenHandoff(assignmentDir) {
19686
- const handoffPath = resolve55(assignmentDir, "handoff.md");
20547
+ const handoffPath = resolve56(assignmentDir, "handoff.md");
19687
20548
  if (!await fileExists(handoffPath)) return null;
19688
20549
  const content = await readFile37(handoffPath, "utf-8");
19689
20550
  const body = content.replace(/^---[\s\S]*?\n---\n?/, "").trim();
@@ -19775,7 +20636,7 @@ async function runSessionResume(options, cwd = process.cwd()) {
19775
20636
  }
19776
20637
  return out;
19777
20638
  }
19778
- var sessionCommand = new Command7("session").description("Manage agent sessions for the active assignment");
20639
+ var sessionCommand = new Command8("session").description("Manage agent sessions for the active assignment");
19779
20640
  sessionCommand.command("resume").description(
19780
20641
  "Re-orient a fresh session: print active assignment context, latest saved session summary, and any open handoff. Idempotent \u2014 does not mutate state."
19781
20642
  ).option("--json", "Emit machine-readable JSON instead of human-readable text").action(async (options) => {
@@ -19792,11 +20653,11 @@ sessionCommand.command("resume").description(
19792
20653
  init_git_worktree();
19793
20654
  init_fs();
19794
20655
  init_paths();
19795
- import { Command as Command8 } from "commander";
20656
+ import { Command as Command9 } from "commander";
19796
20657
  import { readFile as readFile38 } from "fs/promises";
19797
- import { resolve as resolve56 } from "path";
20658
+ import { resolve as resolve57 } from "path";
19798
20659
  async function readContext2(cwd) {
19799
- const path = resolve56(cwd, ".syntaur", "context.json");
20660
+ const path = resolve57(cwd, ".syntaur", "context.json");
19800
20661
  if (!await fileExists(path)) return null;
19801
20662
  try {
19802
20663
  return JSON.parse(await readFile38(path, "utf-8"));
@@ -19807,7 +20668,7 @@ async function readContext2(cwd) {
19807
20668
  async function resolveAssignmentPath2(opts) {
19808
20669
  if (opts.assignment) {
19809
20670
  if (opts.project) {
19810
- return resolve56(
20671
+ return resolve57(
19811
20672
  defaultProjectDir(),
19812
20673
  opts.project,
19813
20674
  "assignments",
@@ -19815,10 +20676,10 @@ async function resolveAssignmentPath2(opts) {
19815
20676
  "assignment.md"
19816
20677
  );
19817
20678
  }
19818
- return resolve56(assignmentsDir(), opts.assignment, "assignment.md");
20679
+ return resolve57(assignmentsDir(), opts.assignment, "assignment.md");
19819
20680
  }
19820
20681
  const ctx = await readContext2(opts.cwd);
19821
- if (ctx?.assignmentDir) return resolve56(ctx.assignmentDir, "assignment.md");
20682
+ if (ctx?.assignmentDir) return resolve57(ctx.assignmentDir, "assignment.md");
19822
20683
  throw new Error(
19823
20684
  "No active assignment. Pass --assignment <slug> [--project <slug>] or run from a workspace with .syntaur/context.json."
19824
20685
  );
@@ -19829,7 +20690,7 @@ async function runWorktreeCreate(options, cwd = process.cwd()) {
19829
20690
  }
19830
20691
  const repository = options.repository ?? cwd;
19831
20692
  const parentBranch = options.parentBranch ?? "main";
19832
- const worktreePath = options.worktreePath ?? resolve56(repository, ".worktrees", options.branch);
20693
+ const worktreePath = options.worktreePath ?? resolve57(repository, ".worktrees", options.branch);
19833
20694
  const assignmentPath = await resolveAssignmentPath2({
19834
20695
  assignment: options.assignment,
19835
20696
  project: options.project,
@@ -19847,7 +20708,7 @@ async function runWorktreeCreate(options, cwd = process.cwd()) {
19847
20708
  });
19848
20709
  return { worktreePath, assignmentPath };
19849
20710
  }
19850
- var worktreeCommand = new Command8("worktree").description("Manage git worktrees bound to Syntaur assignments");
20711
+ var worktreeCommand = new Command9("worktree").description("Manage git worktrees bound to Syntaur assignments");
19851
20712
  worktreeCommand.command("create").description(
19852
20713
  "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
20714
  ).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 +20726,15 @@ worktreeCommand.command("create").description(
19865
20726
  init_paths();
19866
20727
  init_fs();
19867
20728
  init_slug();
19868
- import { Command as Command9 } from "commander";
19869
- import { resolve as resolve58 } from "path";
20729
+ import { Command as Command10 } from "commander";
20730
+ import { resolve as resolve59 } from "path";
19870
20731
 
19871
20732
  // src/utils/project-indexes.ts
19872
20733
  init_parser();
19873
20734
  init_fs();
19874
20735
  import { readdir as readdir20, readFile as readFile39 } from "fs/promises";
19875
- import { resolve as resolve57 } from "path";
19876
- function nowIso() {
20736
+ import { resolve as resolve58 } from "path";
20737
+ function nowIso2() {
19877
20738
  return (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
19878
20739
  }
19879
20740
  function readProjectSlug(projectDir) {
@@ -19891,14 +20752,14 @@ function joinList(items) {
19891
20752
  return items.length === 0 ? "\u2014" : items.map(escapeCell).join(", ");
19892
20753
  }
19893
20754
  async function rebuildResourcesIndex(projectDir) {
19894
- const dir = resolve57(projectDir, "resources");
20755
+ const dir = resolve58(projectDir, "resources");
19895
20756
  await ensureDir(dir);
19896
20757
  const files = await listSlugFiles(dir);
19897
20758
  const slug = readProjectSlug(projectDir);
19898
20759
  const lines = [];
19899
20760
  lines.push("---");
19900
20761
  lines.push(`project: ${slug}`);
19901
- lines.push(`generated: "${nowIso()}"`);
20762
+ lines.push(`generated: "${nowIso2()}"`);
19902
20763
  lines.push(`total: ${files.length}`);
19903
20764
  lines.push("---");
19904
20765
  lines.push("");
@@ -19907,7 +20768,7 @@ async function rebuildResourcesIndex(projectDir) {
19907
20768
  lines.push("| Name | Category | Source | Related Assignments | Updated |");
19908
20769
  lines.push("|------|----------|--------|---------------------|---------|");
19909
20770
  for (const fileName of files) {
19910
- const content = await readFile39(resolve57(dir, fileName), "utf-8");
20771
+ const content = await readFile39(resolve58(dir, fileName), "utf-8");
19911
20772
  const parsed = parseResource(content);
19912
20773
  const slugBase = fileName.replace(/\.md$/, "");
19913
20774
  const name = parsed.name || slugBase;
@@ -19917,19 +20778,19 @@ async function rebuildResourcesIndex(projectDir) {
19917
20778
  );
19918
20779
  }
19919
20780
  lines.push("");
19920
- const indexPath = resolve57(dir, "_index.md");
20781
+ const indexPath = resolve58(dir, "_index.md");
19921
20782
  await writeFileForce(indexPath, lines.join("\n"));
19922
20783
  return { total: files.length, path: indexPath };
19923
20784
  }
19924
20785
  async function rebuildMemoriesIndex(projectDir) {
19925
- const dir = resolve57(projectDir, "memories");
20786
+ const dir = resolve58(projectDir, "memories");
19926
20787
  await ensureDir(dir);
19927
20788
  const files = await listSlugFiles(dir);
19928
20789
  const slug = readProjectSlug(projectDir);
19929
20790
  const lines = [];
19930
20791
  lines.push("---");
19931
20792
  lines.push(`project: ${slug}`);
19932
- lines.push(`generated: "${nowIso()}"`);
20793
+ lines.push(`generated: "${nowIso2()}"`);
19933
20794
  lines.push(`total: ${files.length}`);
19934
20795
  lines.push("---");
19935
20796
  lines.push("");
@@ -19938,7 +20799,7 @@ async function rebuildMemoriesIndex(projectDir) {
19938
20799
  lines.push("| Name | Source | Scope | Source Assignment | Updated |");
19939
20800
  lines.push("|------|--------|-------|-------------------|---------|");
19940
20801
  for (const fileName of files) {
19941
- const content = await readFile39(resolve57(dir, fileName), "utf-8");
20802
+ const content = await readFile39(resolve58(dir, fileName), "utf-8");
19942
20803
  const parsed = parseMemory(content);
19943
20804
  const slugBase = fileName.replace(/\.md$/, "");
19944
20805
  const name = parsed.name || slugBase;
@@ -19948,13 +20809,13 @@ async function rebuildMemoriesIndex(projectDir) {
19948
20809
  );
19949
20810
  }
19950
20811
  lines.push("");
19951
- const indexPath = resolve57(dir, "_index.md");
20812
+ const indexPath = resolve58(dir, "_index.md");
19952
20813
  await writeFileForce(indexPath, lines.join("\n"));
19953
20814
  return { total: files.length, path: indexPath };
19954
20815
  }
19955
20816
 
19956
20817
  // src/commands/resource.ts
19957
- function nowIso2() {
20818
+ function nowIso3() {
19958
20819
  return (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
19959
20820
  }
19960
20821
  function parseList(value) {
@@ -19965,7 +20826,7 @@ function yamlQuote(value) {
19965
20826
  return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
19966
20827
  }
19967
20828
  function renderResourceFile(opts) {
19968
- const created = nowIso2();
20829
+ const created = nowIso3();
19969
20830
  const related = opts.relatedAssignments.length === 0 ? "[]" : `
19970
20831
  ${opts.relatedAssignments.map((a) => ` - ${a}`).join("\n")}`;
19971
20832
  return `---
@@ -19986,8 +20847,8 @@ async function runResourceAdd(options) {
19986
20847
  if (!isValidSlug(options.project)) {
19987
20848
  throw new Error(`Invalid project slug: "${options.project}".`);
19988
20849
  }
19989
- const projectDir = resolve58(defaultProjectDir(), options.project);
19990
- if (!await fileExists(resolve58(projectDir, "project.md"))) {
20850
+ const projectDir = resolve59(defaultProjectDir(), options.project);
20851
+ if (!await fileExists(resolve59(projectDir, "project.md"))) {
19991
20852
  throw new Error(`Project "${options.project}" not found at ${projectDir}.`);
19992
20853
  }
19993
20854
  if (!options.name) throw new Error("--name is required.");
@@ -19996,7 +20857,7 @@ async function runResourceAdd(options) {
19996
20857
  if (!isValidSlug(slug)) {
19997
20858
  throw new Error(`Invalid resource slug: "${slug}".`);
19998
20859
  }
19999
- const filePath = resolve58(projectDir, "resources", `${slug}.md`);
20860
+ const filePath = resolve59(projectDir, "resources", `${slug}.md`);
20000
20861
  if (await fileExists(filePath) && !options.force) {
20001
20862
  throw new Error(
20002
20863
  `Resource "${slug}" already exists at ${filePath}. Use --force to overwrite.`
@@ -20012,7 +20873,7 @@ async function runResourceAdd(options) {
20012
20873
  const { path: indexPath, total } = await rebuildResourcesIndex(projectDir);
20013
20874
  return { filePath, indexPath, total };
20014
20875
  }
20015
- var resourceCommand = new Command9("resource").description("Manage project-level resource entries");
20876
+ var resourceCommand = new Command10("resource").description("Manage project-level resource entries");
20016
20877
  resourceCommand.command("add").description(
20017
20878
  "Add a project resource at <projectDir>/resources/<slug>.md and regenerate _index.md"
20018
20879
  ).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 +20891,9 @@ resourceCommand.command("add").description(
20030
20891
  init_paths();
20031
20892
  init_fs();
20032
20893
  init_slug();
20033
- import { Command as Command10 } from "commander";
20034
- import { resolve as resolve59 } from "path";
20035
- function nowIso3() {
20894
+ import { Command as Command11 } from "commander";
20895
+ import { resolve as resolve60 } from "path";
20896
+ function nowIso4() {
20036
20897
  return (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
20037
20898
  }
20038
20899
  function parseList2(value) {
@@ -20043,7 +20904,7 @@ function yamlQuote2(value) {
20043
20904
  return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
20044
20905
  }
20045
20906
  function renderMemoryFile(opts) {
20046
- const created = nowIso3();
20907
+ const created = nowIso4();
20047
20908
  const related = opts.relatedAssignments.length === 0 ? "[]" : `
20048
20909
  ${opts.relatedAssignments.map((a) => ` - ${a}`).join("\n")}`;
20049
20910
  const sourceAssignment = opts.sourceAssignment === null ? "null" : yamlQuote2(opts.sourceAssignment);
@@ -20066,8 +20927,8 @@ async function runMemoryAdd(options) {
20066
20927
  if (!isValidSlug(options.project)) {
20067
20928
  throw new Error(`Invalid project slug: "${options.project}".`);
20068
20929
  }
20069
- const projectDir = resolve59(defaultProjectDir(), options.project);
20070
- if (!await fileExists(resolve59(projectDir, "project.md"))) {
20930
+ const projectDir = resolve60(defaultProjectDir(), options.project);
20931
+ if (!await fileExists(resolve60(projectDir, "project.md"))) {
20071
20932
  throw new Error(`Project "${options.project}" not found at ${projectDir}.`);
20072
20933
  }
20073
20934
  if (!options.name) throw new Error("--name is required.");
@@ -20076,7 +20937,7 @@ async function runMemoryAdd(options) {
20076
20937
  if (!isValidSlug(slug)) {
20077
20938
  throw new Error(`Invalid memory slug: "${slug}".`);
20078
20939
  }
20079
- const filePath = resolve59(projectDir, "memories", `${slug}.md`);
20940
+ const filePath = resolve60(projectDir, "memories", `${slug}.md`);
20080
20941
  if (await fileExists(filePath) && !options.force) {
20081
20942
  throw new Error(
20082
20943
  `Memory "${slug}" already exists at ${filePath}. Use --force to overwrite.`
@@ -20093,7 +20954,7 @@ async function runMemoryAdd(options) {
20093
20954
  const { path: indexPath, total } = await rebuildMemoriesIndex(projectDir);
20094
20955
  return { filePath, indexPath, total };
20095
20956
  }
20096
- var memoryCommand = new Command10("memory").description("Manage project-level memory entries");
20957
+ var memoryCommand = new Command11("memory").description("Manage project-level memory entries");
20097
20958
  memoryCommand.command("add").description(
20098
20959
  "Add a project memory at <projectDir>/memories/<slug>.md and regenerate _index.md"
20099
20960
  ).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 +20973,9 @@ init_api();
20112
20973
  init_paths();
20113
20974
  init_fs();
20114
20975
  init_frontmatter();
20115
- import { Command as Command11 } from "commander";
20976
+ import { Command as Command12 } from "commander";
20116
20977
  import { readFile as readFile40 } from "fs/promises";
20117
- import { resolve as resolve60 } from "path";
20978
+ import { resolve as resolve61 } from "path";
20118
20979
  var AGE_PATTERN = /^(\d+)([dhwm])$/i;
20119
20980
  function parseAgeToCutoff(age) {
20120
20981
  const match = age.match(AGE_PATTERN);
@@ -20133,7 +20994,7 @@ function parseAgeToCutoff(age) {
20133
20994
  }
20134
20995
  function assignmentMdPath(item) {
20135
20996
  if (item.projectSlug) {
20136
- return resolve60(
20997
+ return resolve61(
20137
20998
  defaultProjectDir(),
20138
20999
  item.projectSlug,
20139
21000
  "assignments",
@@ -20141,7 +21002,7 @@ function assignmentMdPath(item) {
20141
21002
  "assignment.md"
20142
21003
  );
20143
21004
  }
20144
- return resolve60(assignmentsDir(), item.id, "assignment.md");
21005
+ return resolve61(assignmentsDir(), item.id, "assignment.md");
20145
21006
  }
20146
21007
  async function loadTags(item) {
20147
21008
  const path = assignmentMdPath(item);
@@ -20207,7 +21068,7 @@ function renderTable(items) {
20207
21068
  );
20208
21069
  return all.map((row) => row.map((cell, c2) => pad(cell ?? "", widths[c2])).join(" ")).join("\n");
20209
21070
  }
20210
- var lsCommand = new Command11("ls").description(
21071
+ var lsCommand = new Command12("ls").description(
20211
21072
  "List assignments across all projects with optional filters by status, project, tag, or age."
20212
21073
  ).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
21074
  try {
@@ -20266,18 +21127,18 @@ init_paths();
20266
21127
  init_fs();
20267
21128
  import { fileURLToPath as fileURLToPath8 } from "url";
20268
21129
  import { readFile as readFile42 } from "fs/promises";
20269
- import { dirname as dirname15, join as join8, resolve as resolve61 } from "path";
21130
+ import { dirname as dirname16, join as join8, resolve as resolve62 } from "path";
20270
21131
  import { spawn as spawn4 } from "child_process";
20271
21132
  import { createInterface as createInterface2 } from "readline/promises";
20272
21133
 
20273
21134
  // src/utils/version.ts
20274
21135
  import { fileURLToPath as fileURLToPath7 } from "url";
20275
21136
  import { readFile as readFile41 } from "fs/promises";
20276
- import { dirname as dirname14, join as join7 } from "path";
21137
+ import { dirname as dirname15, join as join7 } from "path";
20277
21138
  async function readPackageVersion(scriptUrl) {
20278
21139
  try {
20279
21140
  const scriptPath = fileURLToPath7(scriptUrl);
20280
- const pkgRoot = dirname14(dirname14(scriptPath));
21141
+ const pkgRoot = dirname15(dirname15(scriptPath));
20281
21142
  const raw = await readFile41(join7(pkgRoot, "package.json"), "utf-8");
20282
21143
  const parsed = JSON.parse(raw);
20283
21144
  return typeof parsed.version === "string" ? parsed.version : null;
@@ -20287,7 +21148,7 @@ async function readPackageVersion(scriptUrl) {
20287
21148
  }
20288
21149
 
20289
21150
  // src/utils/npx-prompt.ts
20290
- var STATE_FILE = resolve61(syntaurRoot(), "npx-install.json");
21151
+ var STATE_FILE = resolve62(syntaurRoot(), "npx-install.json");
20291
21152
  var META_ARGS = /* @__PURE__ */ new Set(["-h", "--help", "-V", "--version", "help"]);
20292
21153
  var GLOBAL_VERSION_TIMEOUT_MS = 2e3;
20293
21154
  function isRunningViaNpx(scriptUrl) {
@@ -20319,7 +21180,7 @@ async function writeState(state) {
20319
21180
  `);
20320
21181
  }
20321
21182
  async function resolveNpmBin() {
20322
- const nodeDir = dirname15(process.execPath);
21183
+ const nodeDir = dirname16(process.execPath);
20323
21184
  const isWin = process.platform === "win32";
20324
21185
  const npmName = isWin ? "npm.cmd" : "npm";
20325
21186
  const nearNode = join8(nodeDir, npmName);
@@ -20486,7 +21347,7 @@ async function maybePromptInstall(scriptUrl) {
20486
21347
 
20487
21348
  // src/index.ts
20488
21349
  await maybePromptInstall(import.meta.url);
20489
- var program = new Command12();
21350
+ var program = new Command13();
20490
21351
  var version = await readPackageVersion(import.meta.url) ?? "0.0.0";
20491
21352
  program.name("syntaur").description("CLI scaffolding tool for the Syntaur protocol").version(version);
20492
21353
  program.command("init").description("Initialize ~/.syntaur/ directory structure and config").option("--force", "Overwrite existing config file").action(async (options) => {
@@ -20865,6 +21726,7 @@ program.addCommand(worktreeCommand);
20865
21726
  program.addCommand(resourceCommand);
20866
21727
  program.addCommand(memoryCommand);
20867
21728
  program.addCommand(lsCommand);
21729
+ program.addCommand(leaseCommand);
20868
21730
  if (process.argv.length <= 2) {
20869
21731
  process.argv.push(await getDefaultCommandName());
20870
21732
  }