syntaur 0.17.0 → 0.17.2

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 (68) hide show
  1. package/.claude-plugin/plugin.json +2 -1
  2. package/dashboard/dist/assets/{_basePickBy-Dek5-ACb.js → _basePickBy-DiPrdxAJ.js} +1 -1
  3. package/dashboard/dist/assets/{_baseUniq-BeLddDKU.js → _baseUniq-CChFfAnf.js} +1 -1
  4. package/dashboard/dist/assets/{arc-kotdj4Uh.js → arc-CfkGktFw.js} +1 -1
  5. package/dashboard/dist/assets/{architectureDiagram-2XIMDMQ5-BaqFXQ-q.js → architectureDiagram-2XIMDMQ5-hFcMu5eO.js} +1 -1
  6. package/dashboard/dist/assets/{blockDiagram-WCTKOSBZ-BSyGne6h.js → blockDiagram-WCTKOSBZ-GZDth9sZ.js} +1 -1
  7. package/dashboard/dist/assets/{c4Diagram-IC4MRINW-iYjZFKOW.js → c4Diagram-IC4MRINW-BVajqiYz.js} +1 -1
  8. package/dashboard/dist/assets/channel-C3CI1Xiu.js +1 -0
  9. package/dashboard/dist/assets/{chunk-4BX2VUAB-DvY9Y7jB.js → chunk-4BX2VUAB-L7ajwHyw.js} +1 -1
  10. package/dashboard/dist/assets/{chunk-55IACEB6-2vCDm2cJ.js → chunk-55IACEB6-BACmeqT4.js} +1 -1
  11. package/dashboard/dist/assets/{chunk-FMBD7UC4-BVPaoRtV.js → chunk-FMBD7UC4-BYQ2kuuH.js} +1 -1
  12. package/dashboard/dist/assets/{chunk-JSJVCQXG-B9uJpMzd.js → chunk-JSJVCQXG-akulVsXk.js} +1 -1
  13. package/dashboard/dist/assets/{chunk-KX2RTZJC-CHcjjGaq.js → chunk-KX2RTZJC-4ibFxLB_.js} +1 -1
  14. package/dashboard/dist/assets/{chunk-NQ4KR5QH-B8n19pSs.js → chunk-NQ4KR5QH-DU4iJf0Y.js} +1 -1
  15. package/dashboard/dist/assets/{chunk-QZHKN3VN-CF9ZYFGK.js → chunk-QZHKN3VN-ouyGBhQB.js} +1 -1
  16. package/dashboard/dist/assets/{chunk-WL4C6EOR-mY_1Vmdf.js → chunk-WL4C6EOR-Pq-ySwIY.js} +1 -1
  17. package/dashboard/dist/assets/classDiagram-VBA2DB6C-Ck2tXWRf.js +1 -0
  18. package/dashboard/dist/assets/classDiagram-v2-RAHNMMFH-Ck2tXWRf.js +1 -0
  19. package/dashboard/dist/assets/clone-BEaO4u3F.js +1 -0
  20. package/dashboard/dist/assets/{cose-bilkent-S5V4N54A-DOYoG3gB.js → cose-bilkent-S5V4N54A-BfkMWgQb.js} +1 -1
  21. package/dashboard/dist/assets/{dagre-KLK3FWXG-CZFrp-Yq.js → dagre-KLK3FWXG-0yhY8ZsO.js} +1 -1
  22. package/dashboard/dist/assets/{diagram-E7M64L7V-F3rF5YwM.js → diagram-E7M64L7V-DBkanpFi.js} +1 -1
  23. package/dashboard/dist/assets/{diagram-IFDJBPK2-BynWv-Vo.js → diagram-IFDJBPK2-CrucCpxV.js} +1 -1
  24. package/dashboard/dist/assets/{diagram-P4PSJMXO-NIyzhm_M.js → diagram-P4PSJMXO-BJrQ6lK3.js} +1 -1
  25. package/dashboard/dist/assets/{erDiagram-INFDFZHY-Ca9NbihE.js → erDiagram-INFDFZHY--joDpxAm.js} +1 -1
  26. package/dashboard/dist/assets/{flowDiagram-PKNHOUZH-YhS0MOu4.js → flowDiagram-PKNHOUZH-BUXiVwju.js} +1 -1
  27. package/dashboard/dist/assets/{ganttDiagram-A5KZAMGK-G7P3qq_2.js → ganttDiagram-A5KZAMGK-u2XuIMRA.js} +1 -1
  28. package/dashboard/dist/assets/{gitGraphDiagram-K3NZZRJ6-BJAK7M7E.js → gitGraphDiagram-K3NZZRJ6-Ayf0-gwT.js} +1 -1
  29. package/dashboard/dist/assets/{graph-11geMy3f.js → graph-BUGVXq3c.js} +1 -1
  30. package/dashboard/dist/assets/index-Bv6vn4_x.js +535 -0
  31. package/dashboard/dist/assets/{infoDiagram-LFFYTUFH-COcQYcLf.js → infoDiagram-LFFYTUFH-DYehx6g7.js} +1 -1
  32. package/dashboard/dist/assets/{ishikawaDiagram-PHBUUO56-B7Jjyw8J.js → ishikawaDiagram-PHBUUO56-Bo8-BClf.js} +1 -1
  33. package/dashboard/dist/assets/{journeyDiagram-4ABVD52K--snU-QAX.js → journeyDiagram-4ABVD52K-DLm-bRYd.js} +1 -1
  34. package/dashboard/dist/assets/{kanban-definition-K7BYSVSG-BucLoc89.js → kanban-definition-K7BYSVSG-stPdEvds.js} +1 -1
  35. package/dashboard/dist/assets/{layout-Bcgxdz8A.js → layout-B7aPbDvN.js} +1 -1
  36. package/dashboard/dist/assets/{linear-CRF8xwvk.js → linear-CtQ_RMAp.js} +1 -1
  37. package/dashboard/dist/assets/{mermaid.core-D5JD5uYg.js → mermaid.core-CcJoQ5At.js} +4 -4
  38. package/dashboard/dist/assets/{mindmap-definition-YRQLILUH-BqGcTcbk.js → mindmap-definition-YRQLILUH-DahXO6Ui.js} +1 -1
  39. package/dashboard/dist/assets/{pieDiagram-SKSYHLDU-CnQ2EkyX.js → pieDiagram-SKSYHLDU-DXv973_0.js} +1 -1
  40. package/dashboard/dist/assets/{quadrantDiagram-337W2JSQ-CKCfmTRc.js → quadrantDiagram-337W2JSQ-5U0ryvjr.js} +1 -1
  41. package/dashboard/dist/assets/{requirementDiagram-Z7DCOOCP-B9J4bvUK.js → requirementDiagram-Z7DCOOCP-CvGflSyl.js} +1 -1
  42. package/dashboard/dist/assets/{sankeyDiagram-WA2Y5GQK-CMSzI5rg.js → sankeyDiagram-WA2Y5GQK-AeetdpCi.js} +1 -1
  43. package/dashboard/dist/assets/{sequenceDiagram-2WXFIKYE-BFQDtxd0.js → sequenceDiagram-2WXFIKYE-BhRR4qzn.js} +1 -1
  44. package/dashboard/dist/assets/{stateDiagram-RAJIS63D-DgWYKT02.js → stateDiagram-RAJIS63D-CxxF58nH.js} +1 -1
  45. package/dashboard/dist/assets/stateDiagram-v2-FVOUBMTO-DHCNHbs7.js +1 -0
  46. package/dashboard/dist/assets/{timeline-definition-YZTLITO2-jANu0e7M.js → timeline-definition-YZTLITO2-BEQIeygp.js} +1 -1
  47. package/dashboard/dist/assets/{treemap-KZPCXAKY-BFjWMkyM.js → treemap-KZPCXAKY-BCWNTFfj.js} +1 -1
  48. package/dashboard/dist/assets/{vennDiagram-LZ73GAT5-DGwrAJYj.js → vennDiagram-LZ73GAT5-BcuC_UIz.js} +1 -1
  49. package/dashboard/dist/assets/{xychartDiagram-JWTSCODW-BLCPfoFM.js → xychartDiagram-JWTSCODW-CWGez3_f.js} +1 -1
  50. package/dashboard/dist/index.html +1 -1
  51. package/dist/dashboard/server.js +379 -217
  52. package/dist/dashboard/server.js.map +1 -1
  53. package/dist/index.js +512 -349
  54. package/dist/index.js.map +1 -1
  55. package/dist/launch/index.js +20 -1
  56. package/dist/launch/index.js.map +1 -1
  57. package/package.json +1 -1
  58. package/platforms/claude-code/.claude-plugin/plugin.json +2 -1
  59. package/platforms/claude-code/commands/run-playbook/run-playbook.md +16 -0
  60. package/platforms/claude-code/skills/run-playbook/SKILL.md +87 -0
  61. package/platforms/codex/skills/run-playbook/SKILL.md +87 -0
  62. package/skills/run-playbook/SKILL.md +87 -0
  63. package/dashboard/dist/assets/channel-eXdj7ayV.js +0 -1
  64. package/dashboard/dist/assets/classDiagram-VBA2DB6C-B-bBQACr.js +0 -1
  65. package/dashboard/dist/assets/classDiagram-v2-RAHNMMFH-B-bBQACr.js +0 -1
  66. package/dashboard/dist/assets/clone-CL04pv9J.js +0 -1
  67. package/dashboard/dist/assets/index--qwybK3W.js +0 -535
  68. package/dashboard/dist/assets/stateDiagram-v2-FVOUBMTO-Nmwft4cR.js +0 -1
package/dist/index.js CHANGED
@@ -5042,8 +5042,8 @@ async function migrateFromMarkdown(projectsDir2) {
5042
5042
  return allSessions.length;
5043
5043
  }
5044
5044
  async function parseMarkdownSessionsIndex(filePath, projectSlug) {
5045
- const { readFile: readFile54 } = await import("fs/promises");
5046
- const raw = await readFile54(filePath, "utf-8");
5045
+ const { readFile: readFile53 } = await import("fs/promises");
5046
+ const raw = await readFile53(filePath, "utf-8");
5047
5047
  const sessions = [];
5048
5048
  const lines = raw.split("\n");
5049
5049
  let inTable = false;
@@ -5441,13 +5441,16 @@ __export(scanner_exports, {
5441
5441
  autoLinkPane: () => autoLinkPane,
5442
5442
  checkTmuxAvailable: () => checkTmuxAvailable,
5443
5443
  clearScanCache: () => clearScanCache,
5444
+ descendantPidsFromSnapshot: () => descendantPidsFromSnapshot,
5444
5445
  execQuiet: () => execQuiet,
5445
5446
  findListeningPorts: () => findListeningPorts,
5446
5447
  getDescendantPids: () => getDescendantPids,
5447
5448
  getGitInfo: () => getGitInfo,
5448
5449
  getLsofOutput: () => getLsofOutput,
5450
+ getProcessSnapshot: () => getProcessSnapshot,
5449
5451
  listTmuxPanes: () => listTmuxPanes,
5450
5452
  loadWorkspaceRecords: () => loadWorkspaceRecords,
5453
+ parseProcessSnapshot: () => parseProcessSnapshot,
5451
5454
  parseTmuxPaneOutput: () => parseTmuxPaneOutput,
5452
5455
  resolveAndNormalize: () => resolveAndNormalize,
5453
5456
  scanAllSessions: () => scanAllSessions,
@@ -5457,9 +5460,27 @@ __export(scanner_exports, {
5457
5460
  import { execFile } from "child_process";
5458
5461
  import { promisify } from "util";
5459
5462
  import { resolve as resolve15 } from "path";
5460
- import { realpath, readdir as readdir8, readFile as readFile10 } from "fs/promises";
5463
+ import { realpath } from "fs/promises";
5461
5464
  function clearScanCache() {
5462
5465
  cache = null;
5466
+ forceFreshNext = true;
5467
+ scanEpoch++;
5468
+ inFlight = null;
5469
+ tmuxAvailableCache = null;
5470
+ }
5471
+ function emptyScan() {
5472
+ return { sessions: [], tmuxAvailable: false };
5473
+ }
5474
+ function scanKey(serversDir2, projectsDir2, assignmentsDir2) {
5475
+ return `${serversDir2}\0${projectsDir2}\0${assignmentsDir2 ?? ""}`;
5476
+ }
5477
+ function delay(ms) {
5478
+ return new Promise((resolve78) => {
5479
+ const timer2 = setTimeout(resolve78, ms);
5480
+ if (typeof timer2.unref === "function") {
5481
+ timer2.unref();
5482
+ }
5483
+ });
5463
5484
  }
5464
5485
  function parseTmuxPaneOutput(output2) {
5465
5486
  return output2.trim().split("\n").filter((line) => line.length > 0).map((line) => {
@@ -5493,15 +5514,20 @@ function findListeningPorts(lsofOutput, pids) {
5493
5514
  }
5494
5515
  async function execQuiet(cmd, args) {
5495
5516
  try {
5496
- const { stdout } = await exec(cmd, args);
5517
+ const { stdout } = await exec(cmd, args, {
5518
+ timeout: PROBE_TIMEOUT_MS,
5519
+ maxBuffer: PROBE_MAX_BUFFER
5520
+ });
5497
5521
  return stdout.trim();
5498
5522
  } catch {
5499
5523
  return "";
5500
5524
  }
5501
5525
  }
5502
5526
  async function checkTmuxAvailable() {
5503
- const result = await execQuiet("which", ["tmux"]);
5504
- return result.length > 0;
5527
+ if (!tmuxAvailableCache) {
5528
+ tmuxAvailableCache = execQuiet("which", ["tmux"]).then((result) => result.length > 0);
5529
+ }
5530
+ return tmuxAvailableCache;
5505
5531
  }
5506
5532
  async function sessionAlive(name) {
5507
5533
  try {
@@ -5541,6 +5567,40 @@ async function getDescendantPids(rootPid, maxDepth = 4) {
5541
5567
  }
5542
5568
  return all;
5543
5569
  }
5570
+ function parseProcessSnapshot(psOutput) {
5571
+ const childrenOf = /* @__PURE__ */ new Map();
5572
+ for (const line of psOutput.split("\n")) {
5573
+ const parts = line.trim().split(/\s+/);
5574
+ if (parts.length < 2) continue;
5575
+ const pid = parseInt(parts[0], 10);
5576
+ const ppid = parseInt(parts[1], 10);
5577
+ if (isNaN(pid) || isNaN(ppid)) continue;
5578
+ const siblings = childrenOf.get(ppid);
5579
+ if (siblings) siblings.push(pid);
5580
+ else childrenOf.set(ppid, [pid]);
5581
+ }
5582
+ return { childrenOf };
5583
+ }
5584
+ async function getProcessSnapshot() {
5585
+ return parseProcessSnapshot(await execQuiet("ps", ["-axo", "pid=,ppid="]));
5586
+ }
5587
+ function descendantPidsFromSnapshot(rootPid, snapshot, maxDepth = 4) {
5588
+ const all = /* @__PURE__ */ new Set([rootPid]);
5589
+ let frontier = [rootPid];
5590
+ for (let depth = 0; depth < maxDepth && frontier.length > 0; depth++) {
5591
+ const nextFrontier = [];
5592
+ for (const pid of frontier) {
5593
+ for (const child of snapshot.childrenOf.get(pid) ?? []) {
5594
+ if (!all.has(child)) {
5595
+ all.add(child);
5596
+ nextFrontier.push(child);
5597
+ }
5598
+ }
5599
+ }
5600
+ frontier = nextFrontier;
5601
+ }
5602
+ return all;
5603
+ }
5544
5604
  async function getLsofOutput() {
5545
5605
  return execQuiet("lsof", ["-i", "-P", "-n", "-sTCP:LISTEN"]);
5546
5606
  }
@@ -5562,62 +5622,11 @@ async function getGitInfo(cwd) {
5562
5622
  return { branch: branch || null, worktree: isWorktree };
5563
5623
  }
5564
5624
  async function loadWorkspaceRecords(projectsDir2, assignmentsDir2) {
5565
- const records = [];
5566
5625
  try {
5567
- const projects = await listProjects(projectsDir2);
5568
- for (const project of projects) {
5569
- const projectAssignmentsDir = resolve15(projectsDir2, project.slug, "assignments");
5570
- let slugs;
5571
- try {
5572
- slugs = await readdir8(projectAssignmentsDir);
5573
- } catch {
5574
- continue;
5575
- }
5576
- for (const aslug of slugs) {
5577
- const aFile = resolve15(projectAssignmentsDir, aslug, "assignment.md");
5578
- try {
5579
- const raw = await readFile10(aFile, "utf-8");
5580
- const [fm] = extractFrontmatter(raw);
5581
- if (!fm) continue;
5582
- records.push({
5583
- projectSlug: project.slug,
5584
- assignmentSlug: aslug,
5585
- assignmentTitle: getField(fm, "title") ?? aslug,
5586
- worktreePath: getNestedField(fm, "workspace", "worktreePath") ?? null,
5587
- branch: getNestedField(fm, "workspace", "branch") ?? null
5588
- });
5589
- } catch {
5590
- continue;
5591
- }
5592
- }
5593
- }
5626
+ return await listWorkspaceRecords(projectsDir2, assignmentsDir2);
5594
5627
  } catch {
5628
+ return [];
5595
5629
  }
5596
- if (assignmentsDir2) {
5597
- try {
5598
- const entries = await readdir8(assignmentsDir2);
5599
- for (const id of entries) {
5600
- if (id.startsWith(".") || id.startsWith("_")) continue;
5601
- const aFile = resolve15(assignmentsDir2, id, "assignment.md");
5602
- try {
5603
- const raw = await readFile10(aFile, "utf-8");
5604
- const [fm] = extractFrontmatter(raw);
5605
- if (!fm) continue;
5606
- records.push({
5607
- projectSlug: null,
5608
- assignmentSlug: id,
5609
- assignmentTitle: getField(fm, "title") ?? id,
5610
- worktreePath: getNestedField(fm, "workspace", "worktreePath") ?? null,
5611
- branch: getNestedField(fm, "workspace", "branch") ?? null
5612
- });
5613
- } catch {
5614
- continue;
5615
- }
5616
- }
5617
- } catch {
5618
- }
5619
- }
5620
- return records;
5621
5630
  }
5622
5631
  async function resolveAndNormalize(p) {
5623
5632
  try {
@@ -5646,7 +5655,7 @@ async function autoLinkPane(cwd, branch, records) {
5646
5655
  }
5647
5656
  return null;
5648
5657
  }
5649
- async function scanSession(sessionData, lsofOutput, workspaceRecords) {
5658
+ async function scanSession(sessionData, lsofOutput, workspaceRecords, procSnapshot) {
5650
5659
  const now = (/* @__PURE__ */ new Date()).toISOString();
5651
5660
  const alive = await sessionAlive(sessionData.session);
5652
5661
  if (!alive) {
@@ -5669,13 +5678,13 @@ async function scanSession(sessionData, lsofOutput, workspaceRecords) {
5669
5678
  windowMap.get(rp.windowIndex).panes.push(rp);
5670
5679
  }
5671
5680
  const cwdSet = new Set(rawPanes.map((p) => p.cwd));
5672
- const gitInfoCache = /* @__PURE__ */ new Map();
5673
- for (const cwd of cwdSet) {
5674
- gitInfoCache.set(cwd, await getGitInfo(cwd));
5675
- }
5681
+ const gitInfoEntries = await Promise.all(
5682
+ [...cwdSet].map(async (cwd) => [cwd, await getGitInfo(cwd)])
5683
+ );
5684
+ const gitInfoCache = new Map(gitInfoEntries);
5676
5685
  const pidToPaneKey = /* @__PURE__ */ new Map();
5677
5686
  for (const rp of rawPanes) {
5678
- const descendants = await getDescendantPids(rp.pid);
5687
+ const descendants = descendantPidsFromSnapshot(rp.pid, procSnapshot);
5679
5688
  const key = `${rp.windowIndex}:${rp.paneIndex}`;
5680
5689
  for (const pid of descendants) {
5681
5690
  pidToPaneKey.set(pid, key);
@@ -5700,37 +5709,38 @@ async function scanSession(sessionData, lsofOutput, workspaceRecords) {
5700
5709
  }
5701
5710
  const windows = [];
5702
5711
  for (const [windowIndex, { name, panes: rawPanesInWindow }] of windowMap) {
5703
- const panes = [];
5704
- for (const rp of rawPanesInWindow) {
5705
- const key = `${rp.windowIndex}:${rp.paneIndex}`;
5706
- const gitInfo = gitInfoCache.get(rp.cwd) ?? { branch: null, worktree: false };
5707
- const ports = panePorts.get(key) ?? [];
5708
- const urls = ports.map((p) => `http://localhost:${p}`);
5709
- const override = sessionData.overrides[key];
5710
- let assignment = null;
5711
- if (override) {
5712
- const rec = workspaceRecords.find(
5713
- (r) => r.projectSlug === override.project && r.assignmentSlug === override.assignment
5714
- );
5715
- assignment = {
5716
- project: override.project,
5717
- slug: override.assignment,
5718
- title: rec?.assignmentTitle ?? override.assignment
5712
+ const panes = await Promise.all(
5713
+ rawPanesInWindow.map(async (rp) => {
5714
+ const key = `${rp.windowIndex}:${rp.paneIndex}`;
5715
+ const gitInfo = gitInfoCache.get(rp.cwd) ?? { branch: null, worktree: false };
5716
+ const ports = panePorts.get(key) ?? [];
5717
+ const urls = ports.map((p) => `http://localhost:${p}`);
5718
+ const override = sessionData.overrides[key];
5719
+ let assignment = null;
5720
+ if (override) {
5721
+ const rec = workspaceRecords.find(
5722
+ (r) => r.projectSlug === override.project && r.assignmentSlug === override.assignment
5723
+ );
5724
+ assignment = {
5725
+ project: override.project,
5726
+ slug: override.assignment,
5727
+ title: rec?.assignmentTitle ?? override.assignment
5728
+ };
5729
+ } else {
5730
+ assignment = await autoLinkPane(rp.cwd, gitInfo.branch, workspaceRecords);
5731
+ }
5732
+ return {
5733
+ index: rp.paneIndex,
5734
+ command: rp.command,
5735
+ cwd: rp.cwd,
5736
+ branch: gitInfo.branch,
5737
+ worktree: gitInfo.worktree,
5738
+ ports,
5739
+ urls,
5740
+ assignment
5719
5741
  };
5720
- } else {
5721
- assignment = await autoLinkPane(rp.cwd, gitInfo.branch, workspaceRecords);
5722
- }
5723
- panes.push({
5724
- index: rp.paneIndex,
5725
- command: rp.command,
5726
- cwd: rp.cwd,
5727
- branch: gitInfo.branch,
5728
- worktree: gitInfo.worktree,
5729
- ports,
5730
- urls,
5731
- assignment
5732
- });
5733
- }
5742
+ })
5743
+ );
5734
5744
  windows.push({ index: windowIndex, name, panes });
5735
5745
  }
5736
5746
  windows.sort((a, b) => a.index - b.index);
@@ -5802,53 +5812,114 @@ async function scanProcessSession(sessionData, lsofOutput, workspaceRecords) {
5802
5812
  windows: [{ index: 0, name: "process", panes: [pane] }]
5803
5813
  };
5804
5814
  }
5815
+ async function performScan(serversDir2, projectsDir2, options) {
5816
+ const epoch = scanEpoch;
5817
+ const [tmuxAvailable, names, lsofOutput, workspaceRecords, procSnapshot] = await Promise.all([
5818
+ checkTmuxAvailable(),
5819
+ listSessionFiles(serversDir2),
5820
+ getLsofOutput(),
5821
+ loadWorkspaceRecords(projectsDir2, options?.assignmentsDir),
5822
+ getProcessSnapshot()
5823
+ ]);
5824
+ const datas = await Promise.all(names.map((name) => readSessionFile(serversDir2, name)));
5825
+ const scanned = await Promise.all(
5826
+ datas.map(async (data) => {
5827
+ if (!data) return null;
5828
+ if (data.kind === "process") {
5829
+ return scanProcessSession(data, lsofOutput, workspaceRecords);
5830
+ }
5831
+ if (tmuxAvailable) {
5832
+ return scanSession(data, lsofOutput, workspaceRecords, procSnapshot);
5833
+ }
5834
+ return null;
5835
+ })
5836
+ );
5837
+ const result = {
5838
+ sessions: scanned.filter((s) => s !== null),
5839
+ tmuxAvailable
5840
+ };
5841
+ if (epoch === scanEpoch) {
5842
+ cache = { data: result, expiry: Date.now() + CACHE_TTL_MS };
5843
+ lastKnown = result;
5844
+ forceFreshNext = false;
5845
+ }
5846
+ return result;
5847
+ }
5848
+ function refreshInBackground(serversDir2, projectsDir2, options) {
5849
+ if (!inFlight) {
5850
+ const p = performScan(serversDir2, projectsDir2, options);
5851
+ p.finally(() => {
5852
+ if (inFlight === p) inFlight = null;
5853
+ }).catch(() => {
5854
+ });
5855
+ inFlight = p;
5856
+ }
5857
+ return inFlight;
5858
+ }
5805
5859
  async function scanAllSessions(serversDir2, projectsDir2, options) {
5860
+ const key = scanKey(serversDir2, projectsDir2, options?.assignmentsDir);
5861
+ if (key !== cacheKey) {
5862
+ cache = null;
5863
+ lastKnown = null;
5864
+ inFlight = null;
5865
+ forceFreshNext = false;
5866
+ scanEpoch++;
5867
+ cacheKey = key;
5868
+ }
5806
5869
  if (!options?.bypassCache && cache && Date.now() < cache.expiry) {
5807
5870
  return cache.data;
5808
5871
  }
5809
- const tmuxAvailable = await checkTmuxAvailable();
5810
- const names = await listSessionFiles(serversDir2);
5811
- const lsofOutput = await getLsofOutput();
5812
- const workspaceRecords = await loadWorkspaceRecords(projectsDir2, options?.assignmentsDir);
5813
- const sessions = [];
5814
- for (const name of names) {
5815
- const data = await readSessionFile(serversDir2, name);
5816
- if (!data) continue;
5817
- if (data.kind === "process") {
5818
- sessions.push(await scanProcessSession(data, lsofOutput, workspaceRecords));
5819
- } else if (tmuxAvailable) {
5820
- sessions.push(await scanSession(data, lsofOutput, workspaceRecords));
5821
- }
5872
+ const refresh = refreshInBackground(serversDir2, projectsDir2, options);
5873
+ const canServeStale = lastKnown !== null && !options?.bypassCache && !forceFreshNext;
5874
+ if (canServeStale) {
5875
+ void refresh.catch(() => {
5876
+ });
5877
+ return lastKnown;
5822
5878
  }
5823
- const result = { sessions, tmuxAvailable };
5824
- cache = { data: result, expiry: Date.now() + CACHE_TTL_MS };
5825
- return result;
5879
+ if (options?.nonBlocking) {
5880
+ return Promise.race([
5881
+ refresh.catch(() => emptyScan()),
5882
+ delay(COLD_WAIT_BUDGET_MS).then(() => emptyScan())
5883
+ ]);
5884
+ }
5885
+ return refresh;
5826
5886
  }
5827
5887
  async function scanSingleSession(serversDir2, projectsDir2, name, options) {
5828
5888
  const data = await readSessionFile(serversDir2, name);
5829
5889
  if (!data) return null;
5830
- const lsofOutput = await getLsofOutput();
5831
- const workspaceRecords = await loadWorkspaceRecords(projectsDir2, options?.assignmentsDir);
5890
+ const [lsofOutput, workspaceRecords, procSnapshot] = await Promise.all([
5891
+ getLsofOutput(),
5892
+ loadWorkspaceRecords(projectsDir2, options?.assignmentsDir),
5893
+ getProcessSnapshot()
5894
+ ]);
5832
5895
  if (data.kind === "process") {
5833
5896
  return scanProcessSession(data, lsofOutput, workspaceRecords);
5834
5897
  }
5835
- return scanSession(data, lsofOutput, workspaceRecords);
5898
+ return scanSession(data, lsofOutput, workspaceRecords, procSnapshot);
5836
5899
  }
5837
- var exec, cache, CACHE_TTL_MS;
5900
+ var exec, cache, lastKnown, inFlight, forceFreshNext, scanEpoch, cacheKey, CACHE_TTL_MS, COLD_WAIT_BUDGET_MS, PROBE_TIMEOUT_MS, PROBE_MAX_BUFFER, tmuxAvailableCache;
5838
5901
  var init_scanner = __esm({
5839
5902
  "src/dashboard/scanner.ts"() {
5840
5903
  "use strict";
5841
5904
  init_api();
5842
5905
  init_servers();
5843
- init_parser();
5844
5906
  exec = promisify(execFile);
5845
5907
  cache = null;
5908
+ lastKnown = null;
5909
+ inFlight = null;
5910
+ forceFreshNext = false;
5911
+ scanEpoch = 0;
5912
+ cacheKey = null;
5846
5913
  CACHE_TTL_MS = 1e4;
5914
+ COLD_WAIT_BUDGET_MS = 2500;
5915
+ PROBE_TIMEOUT_MS = 15e3;
5916
+ PROBE_MAX_BUFFER = 32 * 1024 * 1024;
5917
+ tmuxAvailableCache = null;
5847
5918
  }
5848
5919
  });
5849
5920
 
5850
5921
  // src/dashboard/api.ts
5851
- import { readdir as readdir9, readFile as readFile11, writeFile as writeFile3 } from "fs/promises";
5922
+ import { readdir as readdir8, readFile as readFile10, writeFile as writeFile3 } from "fs/promises";
5852
5923
  import { resolve as resolve16, dirname as dirname3, basename } from "path";
5853
5924
  function clearFrontmatterField(content, key) {
5854
5925
  const fieldRegex = new RegExp(`^(${escapeRegExp2(key)}:)\\s*.*$`, "m");
@@ -5888,10 +5959,41 @@ function emitTrace(traces, meta) {
5888
5959
  JSON.stringify({ kind: "overview-trace", totalMs, phases: traces.entries, subPhases, ...meta })
5889
5960
  );
5890
5961
  }
5962
+ function invalidateRecordsCache() {
5963
+ projectRecordsCache.clear();
5964
+ standaloneRecordsCache.clear();
5965
+ }
5966
+ function installRecordsInvalidation(router) {
5967
+ for (const method of ["post", "put", "patch", "delete"]) {
5968
+ const original = router[method].bind(router);
5969
+ router[method] = (path, ...handlers) => {
5970
+ if (handlers.length > 0) {
5971
+ const last = handlers[handlers.length - 1];
5972
+ handlers[handlers.length - 1] = async (req, res, next) => {
5973
+ try {
5974
+ return await last(req, res, next);
5975
+ } finally {
5976
+ invalidateRecordsCache();
5977
+ }
5978
+ };
5979
+ }
5980
+ return original(path, ...handlers);
5981
+ };
5982
+ }
5983
+ }
5891
5984
  async function listStandaloneRecords(assignmentsDir2) {
5985
+ const key = assignmentsDir2 ?? "";
5986
+ const cached = standaloneRecordsCache.get(key);
5987
+ if (cached) return cached;
5988
+ const promise = computeStandaloneRecords(assignmentsDir2);
5989
+ standaloneRecordsCache.set(key, promise);
5990
+ promise.catch(() => standaloneRecordsCache.delete(key));
5991
+ return promise;
5992
+ }
5993
+ async function computeStandaloneRecords(assignmentsDir2) {
5892
5994
  if (!assignmentsDir2) return [];
5893
5995
  if (!await fileExists(assignmentsDir2)) return [];
5894
- const entries = await readdir9(assignmentsDir2, { withFileTypes: true });
5996
+ const entries = await readdir8(assignmentsDir2, { withFileTypes: true });
5895
5997
  const records = [];
5896
5998
  for (const entry of entries) {
5897
5999
  if (!entry.isDirectory() || entry.name.startsWith(".") || entry.name.startsWith("_")) continue;
@@ -5899,7 +6001,7 @@ async function listStandaloneRecords(assignmentsDir2) {
5899
6001
  const assignmentMdPath2 = resolve16(assignmentDir, "assignment.md");
5900
6002
  if (!await fileExists(assignmentMdPath2)) continue;
5901
6003
  try {
5902
- const content = await readFile11(assignmentMdPath2, "utf-8");
6004
+ const content = await readFile10(assignmentMdPath2, "utf-8");
5903
6005
  const record = parseAssignmentFull(content);
5904
6006
  records.push({ assignmentDir, id: entry.name, record });
5905
6007
  } catch {
@@ -5975,7 +6077,7 @@ async function listProjects(projectsDir2) {
5975
6077
  async function readWorkspaceRegistry(projectsDir2) {
5976
6078
  const registryPath = resolve16(dirname3(projectsDir2), "workspaces.json");
5977
6079
  try {
5978
- const raw = await readFile11(registryPath, "utf-8");
6080
+ const raw = await readFile10(registryPath, "utf-8");
5979
6081
  const parsed = JSON.parse(raw);
5980
6082
  return Array.isArray(parsed) ? parsed.filter((w) => typeof w === "string") : [];
5981
6083
  } catch {
@@ -6011,6 +6113,34 @@ async function listWorkspaces(projectsDir2, assignmentsDir2) {
6011
6113
  const workspaces = Array.from(workspaceSet).sort();
6012
6114
  return { workspaces, hasUngrouped };
6013
6115
  }
6116
+ async function listWorkspaceRecords(projectsDir2, assignmentsDir2) {
6117
+ const [projectRecords, standaloneRecords] = await Promise.all([
6118
+ listProjectRecords(projectsDir2),
6119
+ listStandaloneRecords(assignmentsDir2)
6120
+ ]);
6121
+ const records = [];
6122
+ for (const project of projectRecords) {
6123
+ for (const assignment of project.assignments) {
6124
+ records.push({
6125
+ projectSlug: project.summary.slug,
6126
+ assignmentSlug: assignment.slug,
6127
+ assignmentTitle: assignment.title || assignment.slug,
6128
+ worktreePath: assignment.workspace.worktreePath ?? null,
6129
+ branch: assignment.workspace.branch ?? null
6130
+ });
6131
+ }
6132
+ }
6133
+ for (const standalone of standaloneRecords) {
6134
+ records.push({
6135
+ projectSlug: null,
6136
+ assignmentSlug: standalone.id,
6137
+ assignmentTitle: standalone.record.title || standalone.id,
6138
+ worktreePath: standalone.record.workspace.worktreePath ?? null,
6139
+ branch: standalone.record.workspace.branch ?? null
6140
+ });
6141
+ }
6142
+ return records;
6143
+ }
6014
6144
  async function createWorkspace(projectsDir2, name) {
6015
6145
  const registered = await readWorkspaceRegistry(projectsDir2);
6016
6146
  if (!registered.includes(name)) {
@@ -6036,7 +6166,7 @@ async function deleteWorkspace(projectsDir2, name, opts = {}) {
6036
6166
  const timestamp = nowTimestamp();
6037
6167
  for (const slug of projectsReferencing) {
6038
6168
  const path = resolve16(projectsDir2, slug, "project.md");
6039
- const raw = await readFile11(path, "utf-8");
6169
+ const raw = await readFile10(path, "utf-8");
6040
6170
  let next = clearFrontmatterField(raw, "workspace");
6041
6171
  next = setUpdatedField(next, timestamp);
6042
6172
  await writeFileForce(path, next);
@@ -6045,7 +6175,7 @@ async function deleteWorkspace(projectsDir2, name, opts = {}) {
6045
6175
  for (const id of standalonesReferencing) {
6046
6176
  if (!opts.assignmentsDir) break;
6047
6177
  const path = resolve16(opts.assignmentsDir, id, "assignment.md");
6048
- const raw = await readFile11(path, "utf-8");
6178
+ const raw = await readFile10(path, "utf-8");
6049
6179
  let next = clearFrontmatterField(raw, "workspaceGroup");
6050
6180
  next = setUpdatedField(next, timestamp);
6051
6181
  await writeFileForce(path, next);
@@ -6055,6 +6185,9 @@ async function deleteWorkspace(projectsDir2, name, opts = {}) {
6055
6185
  const registered = await readWorkspaceRegistry(projectsDir2);
6056
6186
  const filtered = registered.filter((w) => w !== name);
6057
6187
  await writeWorkspaceRegistry(projectsDir2, filtered);
6188
+ if (rewroteFiles) {
6189
+ invalidateRecordsCache();
6190
+ }
6058
6191
  return { rewroteFiles };
6059
6192
  }
6060
6193
  async function getOverview(projectsDir2, serversDir2, assignmentsDir2, options = {}) {
@@ -6098,7 +6231,12 @@ async function getOverview(projectsDir2, serversDir2, assignmentsDir2, options =
6098
6231
  const servers = await timed(
6099
6232
  traces,
6100
6233
  "scan-tmux-sessions",
6101
- () => scanAllSessions2(serversDir2, projectsDir2, { assignmentsDir: assignmentsDir2 })
6234
+ () => (
6235
+ // Overview only needs aggregate counts — never block its render on a
6236
+ // live scan; serve last-known stats and let the scan refresh in the
6237
+ // background (stale-while-revalidate).
6238
+ scanAllSessions2(serversDir2, projectsDir2, { assignmentsDir: assignmentsDir2, nonBlocking: true })
6239
+ )
6102
6240
  );
6103
6241
  if (servers.tmuxAvailable) {
6104
6242
  const alive = servers.sessions.filter((s) => s.alive).length;
@@ -6217,7 +6355,7 @@ async function getEditableDocument(projectsDir2, documentType, projectSlug, assi
6217
6355
  if (!filePath || !await fileExists(filePath)) {
6218
6356
  return null;
6219
6357
  }
6220
- const content = await readFile11(filePath, "utf-8");
6358
+ const content = await readFile10(filePath, "utf-8");
6221
6359
  const title = getEditableDocumentTitle(documentType, projectSlug, assignmentSlug);
6222
6360
  return {
6223
6361
  documentType,
@@ -6243,7 +6381,7 @@ async function getEditableDocumentById(projectsDir2, assignmentsDir2, documentTy
6243
6381
  if (!fileName) return null;
6244
6382
  const filePath = resolve16(resolved.assignmentDir, fileName);
6245
6383
  if (!await fileExists(filePath)) return null;
6246
- const content = await readFile11(filePath, "utf-8");
6384
+ const content = await readFile10(filePath, "utf-8");
6247
6385
  const label = resolved.id;
6248
6386
  const title = documentType === "assignment" ? `Edit Assignment: ${label}` : documentType === "plan" ? `Edit Plan: ${label}` : documentType === "scratchpad" ? `Edit Scratchpad: ${label}` : documentType === "handoff" ? `Append Handoff: ${label}` : `Append Decision: ${label}`;
6249
6387
  return {
@@ -6262,7 +6400,7 @@ async function getProjectDetail(projectsDir2, slug) {
6262
6400
  if (!await fileExists(projectMdPath)) {
6263
6401
  return null;
6264
6402
  }
6265
- const projectContent = await readFile11(projectMdPath, "utf-8");
6403
+ const projectContent = await readFile10(projectMdPath, "utf-8");
6266
6404
  const project = parseProject(projectContent);
6267
6405
  const assignments = await listAssignmentRecords(projectPath);
6268
6406
  const rollup = await buildProjectRollup(projectPath, project, assignments);
@@ -6299,18 +6437,18 @@ async function getAssignmentDetail(projectsDir2, projectSlug, assignmentSlug) {
6299
6437
  if (!await fileExists(assignmentMdPath2)) {
6300
6438
  return null;
6301
6439
  }
6302
- const assignmentContent = await readFile11(assignmentMdPath2, "utf-8");
6440
+ const assignmentContent = await readFile10(assignmentMdPath2, "utf-8");
6303
6441
  const assignment = parseAssignmentFull(assignmentContent);
6304
6442
  let projectWorkspace = null;
6305
6443
  const projectMdPath = resolve16(projectsDir2, projectSlug, "project.md");
6306
6444
  if (await fileExists(projectMdPath)) {
6307
- const projectContent = await readFile11(projectMdPath, "utf-8");
6445
+ const projectContent = await readFile10(projectMdPath, "utf-8");
6308
6446
  projectWorkspace = parseProject(projectContent).workspace;
6309
6447
  }
6310
6448
  let plan = null;
6311
6449
  const planPath = resolve16(assignmentDir, "plan.md");
6312
6450
  if (await fileExists(planPath)) {
6313
- const planContent = await readFile11(planPath, "utf-8");
6451
+ const planContent = await readFile10(planPath, "utf-8");
6314
6452
  const parsed = parsePlan(planContent);
6315
6453
  plan = {
6316
6454
  status: parsed.status,
@@ -6321,7 +6459,7 @@ async function getAssignmentDetail(projectsDir2, projectSlug, assignmentSlug) {
6321
6459
  let scratchpad = null;
6322
6460
  const scratchpadPath = resolve16(assignmentDir, "scratchpad.md");
6323
6461
  if (await fileExists(scratchpadPath)) {
6324
- const scratchpadContent = await readFile11(scratchpadPath, "utf-8");
6462
+ const scratchpadContent = await readFile10(scratchpadPath, "utf-8");
6325
6463
  const parsed = parseScratchpad(scratchpadContent);
6326
6464
  scratchpad = {
6327
6465
  updated: parsed.updated,
@@ -6331,7 +6469,7 @@ async function getAssignmentDetail(projectsDir2, projectSlug, assignmentSlug) {
6331
6469
  let handoff = null;
6332
6470
  const handoffPath = resolve16(assignmentDir, "handoff.md");
6333
6471
  if (await fileExists(handoffPath)) {
6334
- const handoffContent = await readFile11(handoffPath, "utf-8");
6472
+ const handoffContent = await readFile10(handoffPath, "utf-8");
6335
6473
  const parsed = parseHandoff(handoffContent);
6336
6474
  handoff = {
6337
6475
  updated: parsed.updated,
@@ -6342,7 +6480,7 @@ async function getAssignmentDetail(projectsDir2, projectSlug, assignmentSlug) {
6342
6480
  let decisionRecord = null;
6343
6481
  const decisionRecordPath = resolve16(assignmentDir, "decision-record.md");
6344
6482
  if (await fileExists(decisionRecordPath)) {
6345
- const decisionRecordContent = await readFile11(decisionRecordPath, "utf-8");
6483
+ const decisionRecordContent = await readFile10(decisionRecordPath, "utf-8");
6346
6484
  const parsed = parseDecisionRecord(decisionRecordContent);
6347
6485
  decisionRecord = {
6348
6486
  updated: parsed.updated,
@@ -6353,7 +6491,7 @@ async function getAssignmentDetail(projectsDir2, projectSlug, assignmentSlug) {
6353
6491
  let progress = null;
6354
6492
  const progressPath = resolve16(assignmentDir, "progress.md");
6355
6493
  if (await fileExists(progressPath)) {
6356
- const progressContent = await readFile11(progressPath, "utf-8");
6494
+ const progressContent = await readFile10(progressPath, "utf-8");
6357
6495
  const parsed = parseProgress(progressContent);
6358
6496
  progress = {
6359
6497
  updated: parsed.updated,
@@ -6364,7 +6502,7 @@ async function getAssignmentDetail(projectsDir2, projectSlug, assignmentSlug) {
6364
6502
  let comments = null;
6365
6503
  const commentsPath = resolve16(assignmentDir, "comments.md");
6366
6504
  if (await fileExists(commentsPath)) {
6367
- const commentsContent = await readFile11(commentsPath, "utf-8");
6505
+ const commentsContent = await readFile10(commentsPath, "utf-8");
6368
6506
  const parsed = parseComments(commentsContent);
6369
6507
  comments = {
6370
6508
  updated: parsed.updated,
@@ -6515,7 +6653,7 @@ async function countMentionsInAssignment(sourceDir, target) {
6515
6653
  const bodies = [];
6516
6654
  const assignmentMd = resolve16(sourceDir, "assignment.md");
6517
6655
  if (await fileExists(assignmentMd)) {
6518
- const content = await readFile11(assignmentMd, "utf-8");
6656
+ const content = await readFile10(assignmentMd, "utf-8");
6519
6657
  const todosMatch = content.match(/^## Todos\s*$([\s\S]*?)(?=^## |$(?![\r\n]))/m);
6520
6658
  if (todosMatch) bodies.push(todosMatch[1]);
6521
6659
  }
@@ -6523,7 +6661,7 @@ async function countMentionsInAssignment(sourceDir, target) {
6523
6661
  const path = resolve16(sourceDir, filename);
6524
6662
  if (await fileExists(path)) {
6525
6663
  try {
6526
- bodies.push(await readFile11(path, "utf-8"));
6664
+ bodies.push(await readFile10(path, "utf-8"));
6527
6665
  } catch {
6528
6666
  }
6529
6667
  }
@@ -6583,42 +6721,42 @@ async function buildStandaloneAssignmentDetail(resolved) {
6583
6721
  const assignmentDir = resolved.assignmentDir;
6584
6722
  const assignmentMdPath2 = resolve16(assignmentDir, "assignment.md");
6585
6723
  if (!await fileExists(assignmentMdPath2)) return null;
6586
- const assignmentContent = await readFile11(assignmentMdPath2, "utf-8");
6724
+ const assignmentContent = await readFile10(assignmentMdPath2, "utf-8");
6587
6725
  const assignment = parseAssignmentFull(assignmentContent);
6588
6726
  let plan = null;
6589
6727
  const planPath = resolve16(assignmentDir, "plan.md");
6590
6728
  if (await fileExists(planPath)) {
6591
- const parsed = parsePlan(await readFile11(planPath, "utf-8"));
6729
+ const parsed = parsePlan(await readFile10(planPath, "utf-8"));
6592
6730
  plan = { status: parsed.status, updated: parsed.updated, body: parsed.body };
6593
6731
  }
6594
6732
  let scratchpad = null;
6595
6733
  const scratchpadPath = resolve16(assignmentDir, "scratchpad.md");
6596
6734
  if (await fileExists(scratchpadPath)) {
6597
- const parsed = parseScratchpad(await readFile11(scratchpadPath, "utf-8"));
6735
+ const parsed = parseScratchpad(await readFile10(scratchpadPath, "utf-8"));
6598
6736
  scratchpad = { updated: parsed.updated, body: parsed.body };
6599
6737
  }
6600
6738
  let handoff = null;
6601
6739
  const handoffPath = resolve16(assignmentDir, "handoff.md");
6602
6740
  if (await fileExists(handoffPath)) {
6603
- const parsed = parseHandoff(await readFile11(handoffPath, "utf-8"));
6741
+ const parsed = parseHandoff(await readFile10(handoffPath, "utf-8"));
6604
6742
  handoff = { updated: parsed.updated, handoffCount: parsed.handoffCount, body: parsed.body };
6605
6743
  }
6606
6744
  let decisionRecord = null;
6607
6745
  const decisionRecordPath = resolve16(assignmentDir, "decision-record.md");
6608
6746
  if (await fileExists(decisionRecordPath)) {
6609
- const parsed = parseDecisionRecord(await readFile11(decisionRecordPath, "utf-8"));
6747
+ const parsed = parseDecisionRecord(await readFile10(decisionRecordPath, "utf-8"));
6610
6748
  decisionRecord = { updated: parsed.updated, decisionCount: parsed.decisionCount, body: parsed.body };
6611
6749
  }
6612
6750
  let progress = null;
6613
6751
  const progressPath = resolve16(assignmentDir, "progress.md");
6614
6752
  if (await fileExists(progressPath)) {
6615
- const parsed = parseProgress(await readFile11(progressPath, "utf-8"));
6753
+ const parsed = parseProgress(await readFile10(progressPath, "utf-8"));
6616
6754
  progress = { updated: parsed.updated, entryCount: parsed.entryCount, entries: parsed.entries };
6617
6755
  }
6618
6756
  let comments = null;
6619
6757
  const commentsPath = resolve16(assignmentDir, "comments.md");
6620
6758
  if (await fileExists(commentsPath)) {
6621
- const parsed = parseComments(await readFile11(commentsPath, "utf-8"));
6759
+ const parsed = parseComments(await readFile10(commentsPath, "utf-8"));
6622
6760
  comments = { updated: parsed.updated, entryCount: parsed.entryCount, entries: parsed.entries };
6623
6761
  }
6624
6762
  const detail = {
@@ -6655,6 +6793,14 @@ async function buildStandaloneAssignmentDetail(resolved) {
6655
6793
  return detail;
6656
6794
  }
6657
6795
  async function listProjectRecords(projectsDir2, traces) {
6796
+ const cached = projectRecordsCache.get(projectsDir2);
6797
+ if (cached) return cached;
6798
+ const promise = computeProjectRecords(projectsDir2, traces);
6799
+ projectRecordsCache.set(projectsDir2, promise);
6800
+ promise.catch(() => projectRecordsCache.delete(projectsDir2));
6801
+ return promise;
6802
+ }
6803
+ async function computeProjectRecords(projectsDir2, traces) {
6658
6804
  if (!await fileExists(projectsDir2)) {
6659
6805
  return [];
6660
6806
  }
@@ -6662,7 +6808,7 @@ async function listProjectRecords(projectsDir2, traces) {
6662
6808
  migratedProjectsDirs.add(projectsDir2);
6663
6809
  await migrateLegacyProjectFiles(projectsDir2);
6664
6810
  }
6665
- const entries = await readdir9(projectsDir2, { withFileTypes: true });
6811
+ const entries = await readdir8(projectsDir2, { withFileTypes: true });
6666
6812
  const projectDirs = entries.filter((entry) => entry.isDirectory() && !entry.name.startsWith("."));
6667
6813
  const maybeRecords = await Promise.all(
6668
6814
  projectDirs.map(async (entry) => {
@@ -6672,7 +6818,7 @@ async function listProjectRecords(projectsDir2, traces) {
6672
6818
  return null;
6673
6819
  }
6674
6820
  const t0 = traces ? performance.now() : 0;
6675
- const projectContent = await readFile11(projectMdPath, "utf-8");
6821
+ const projectContent = await readFile10(projectMdPath, "utf-8");
6676
6822
  const project = parseProject(projectContent);
6677
6823
  if (traces) accumulatePhase(traces, "parse-project-md", performance.now() - t0);
6678
6824
  const t1 = traces ? performance.now() : 0;
@@ -6717,7 +6863,7 @@ async function listAssignmentRecords(projectPath, traces) {
6717
6863
  if (!await fileExists(assignmentsDir2)) {
6718
6864
  return [];
6719
6865
  }
6720
- const entries = await readdir9(assignmentsDir2, { withFileTypes: true });
6866
+ const entries = await readdir8(assignmentsDir2, { withFileTypes: true });
6721
6867
  const dirEntries = entries.filter((entry) => entry.isDirectory());
6722
6868
  const maybeRecords = await Promise.all(
6723
6869
  dirEntries.map(async (entry) => {
@@ -6726,7 +6872,7 @@ async function listAssignmentRecords(projectPath, traces) {
6726
6872
  return null;
6727
6873
  }
6728
6874
  const t0 = traces ? performance.now() : 0;
6729
- const content = await readFile11(assignmentMd, "utf-8");
6875
+ const content = await readFile10(assignmentMd, "utf-8");
6730
6876
  const parsed = parseAssignmentFull(content);
6731
6877
  if (traces) accumulatePhase(traces, "read-assignment-md", performance.now() - t0);
6732
6878
  return parsed;
@@ -6741,14 +6887,14 @@ async function listResources(projectPath) {
6741
6887
  if (!await fileExists(resourcesDir)) {
6742
6888
  return [];
6743
6889
  }
6744
- const entries = await readdir9(resourcesDir, { withFileTypes: true });
6890
+ const entries = await readdir8(resourcesDir, { withFileTypes: true });
6745
6891
  const results = [];
6746
6892
  for (const entry of entries) {
6747
6893
  if (!entry.isFile() || !entry.name.endsWith(".md") || entry.name.startsWith("_")) {
6748
6894
  continue;
6749
6895
  }
6750
6896
  const filePath = resolve16(resourcesDir, entry.name);
6751
- const content = await readFile11(filePath, "utf-8");
6897
+ const content = await readFile10(filePath, "utf-8");
6752
6898
  const parsed = parseResource(content);
6753
6899
  results.push({
6754
6900
  name: parsed.name,
@@ -6767,14 +6913,14 @@ async function listMemories(projectPath) {
6767
6913
  if (!await fileExists(memoriesDir)) {
6768
6914
  return [];
6769
6915
  }
6770
- const entries = await readdir9(memoriesDir, { withFileTypes: true });
6916
+ const entries = await readdir8(memoriesDir, { withFileTypes: true });
6771
6917
  const results = [];
6772
6918
  for (const entry of entries) {
6773
6919
  if (!entry.isFile() || !entry.name.endsWith(".md") || entry.name.startsWith("_")) {
6774
6920
  continue;
6775
6921
  }
6776
6922
  const filePath = resolve16(memoriesDir, entry.name);
6777
- const content = await readFile11(filePath, "utf-8");
6923
+ const content = await readFile10(filePath, "utf-8");
6778
6924
  const parsed = parseMemory(content);
6779
6925
  results.push({
6780
6926
  name: parsed.name,
@@ -6837,7 +6983,7 @@ async function getMemoryDetail(projectsDir2, projectSlug, itemSlug) {
6837
6983
  if (!projectRecord) return null;
6838
6984
  const filePath = resolve16(projectRecord.projectPath, "memories", `${itemSlug}.md`);
6839
6985
  if (!await fileExists(filePath)) return null;
6840
- const content = await readFile11(filePath, "utf-8");
6986
+ const content = await readFile10(filePath, "utf-8");
6841
6987
  const parsed = parseMemory(content);
6842
6988
  return {
6843
6989
  name: parsed.name,
@@ -6863,7 +7009,7 @@ async function getResourceDetail(projectsDir2, projectSlug, itemSlug) {
6863
7009
  if (!projectRecord) return null;
6864
7010
  const filePath = resolve16(projectRecord.projectPath, "resources", `${itemSlug}.md`);
6865
7011
  if (!await fileExists(filePath)) return null;
6866
- const content = await readFile11(filePath, "utf-8");
7012
+ const content = await readFile10(filePath, "utf-8");
6867
7013
  const parsed = parseResource(content);
6868
7014
  return {
6869
7015
  name: parsed.name,
@@ -6881,7 +7027,7 @@ async function getResourceDetail(projectsDir2, projectSlug, itemSlug) {
6881
7027
  async function loadDependencyGraph(projectPath, assignments) {
6882
7028
  const statusPath = resolve16(projectPath, "_status.md");
6883
7029
  if (await fileExists(statusPath)) {
6884
- const statusContent = await readFile11(statusPath, "utf-8");
7030
+ const statusContent = await readFile10(statusPath, "utf-8");
6885
7031
  const parsed = parseStatus(statusContent);
6886
7032
  const derivedGraph = extractMermaidGraph(parsed.body);
6887
7033
  if (derivedGraph) {
@@ -7042,7 +7188,7 @@ async function getUnmetDependencies(projectPath, dependsOn, terminalStatuses3, d
7042
7188
  unmet.push(`${dependency} (missing)`);
7043
7189
  continue;
7044
7190
  }
7045
- const content = await readFile11(dependencyPath, "utf-8");
7191
+ const content = await readFile10(dependencyPath, "utf-8");
7046
7192
  const parsed = parseAssignmentFull(content);
7047
7193
  if (!terminals.has(parsed.status)) {
7048
7194
  unmet.push(`${dependency} (${parsed.status})`);
@@ -7335,7 +7481,7 @@ async function countOpenQuestions(projectPath, assignmentSlug) {
7335
7481
  return 0;
7336
7482
  }
7337
7483
  try {
7338
- const content = await readFile11(commentsPath, "utf-8");
7484
+ const content = await readFile10(commentsPath, "utf-8");
7339
7485
  const parsed = parseComments(content);
7340
7486
  return parsed.entries.filter(
7341
7487
  (e) => e.type === "question" && e.resolved !== true
@@ -7403,12 +7549,12 @@ async function listPlaybooks(playbooksDir3) {
7403
7549
  if (!await fileExists(playbooksDir3)) return [];
7404
7550
  const config = await readConfig();
7405
7551
  const disabledSet = new Set(config.playbooks.disabled);
7406
- const entries = await readdir9(playbooksDir3, { withFileTypes: true });
7552
+ const entries = await readdir8(playbooksDir3, { withFileTypes: true });
7407
7553
  const playbooks = [];
7408
7554
  for (const entry of entries) {
7409
7555
  if (!entry.isFile() || !entry.name.endsWith(".md") || entry.name.startsWith("_") || entry.name === "manifest.md") continue;
7410
7556
  const filePath = resolve16(playbooksDir3, entry.name);
7411
- const raw = await readFile11(filePath, "utf-8");
7557
+ const raw = await readFile10(filePath, "utf-8");
7412
7558
  const parsed = parsePlaybook(raw);
7413
7559
  const slug = parsed.slug || entry.name.replace(/\.md$/, "");
7414
7560
  playbooks.push({
@@ -7442,7 +7588,7 @@ async function getPlaybookDetail(playbooksDir3, slug) {
7442
7588
  enabled
7443
7589
  };
7444
7590
  }
7445
- var WorkspaceBlockedError, STALE_ASSIGNMENT_MS, RECENT_PROJECTS_LIMIT, RECENT_ACTIVITY_LIMIT, RECENT_SESSIONS_LIMIT, NEWEST_CREATED_LIMIT, SEGMENT_DISPLAY_CAP, STALE_LIMIT_DEFAULT, STALE_LIMIT_MAX, TERMINAL_STATUSES2, STATUS_TO_SEGMENT, HERO_PRIORITY, DEFAULT_TRANSITION_DEFINITIONS, DEFAULT_STATUS_COLORS, _cachedConfig, REFERENCED_BY_LIMIT, migratedProjectsDirs, DEFAULT_GRAPH_COLORS;
7591
+ var WorkspaceBlockedError, STALE_ASSIGNMENT_MS, RECENT_PROJECTS_LIMIT, RECENT_ACTIVITY_LIMIT, RECENT_SESSIONS_LIMIT, NEWEST_CREATED_LIMIT, SEGMENT_DISPLAY_CAP, STALE_LIMIT_DEFAULT, STALE_LIMIT_MAX, TERMINAL_STATUSES2, STATUS_TO_SEGMENT, HERO_PRIORITY, projectRecordsCache, standaloneRecordsCache, DEFAULT_TRANSITION_DEFINITIONS, DEFAULT_STATUS_COLORS, _cachedConfig, REFERENCED_BY_LIMIT, migratedProjectsDirs, DEFAULT_GRAPH_COLORS;
7446
7592
  var init_api = __esm({
7447
7593
  "src/dashboard/api.ts"() {
7448
7594
  "use strict";
@@ -7493,6 +7639,8 @@ var init_api = __esm({
7493
7639
  ["blocked", "blocked"],
7494
7640
  ["stale", "stale"]
7495
7641
  ];
7642
+ projectRecordsCache = /* @__PURE__ */ new Map();
7643
+ standaloneRecordsCache = /* @__PURE__ */ new Map();
7496
7644
  DEFAULT_TRANSITION_DEFINITIONS = [
7497
7645
  {
7498
7646
  command: "start",
@@ -7589,7 +7737,7 @@ __export(git_worktree_exports, {
7589
7737
  removeWorktree: () => removeWorktree
7590
7738
  });
7591
7739
  import { spawn } from "child_process";
7592
- import { readFile as readFile14 } from "fs/promises";
7740
+ import { readFile as readFile13 } from "fs/promises";
7593
7741
  function run(command, args, cwd) {
7594
7742
  return new Promise((resolvePromise) => {
7595
7743
  const child = spawn(command, args, { cwd, stdio: ["ignore", "pipe", "pipe"] });
@@ -7633,7 +7781,7 @@ async function createWorktreeAndRecord(opts) {
7633
7781
  const { assignmentPath, repository, branch, worktreePath, parentBranch } = opts;
7634
7782
  await createWorktree({ repository, branch, worktreePath, parentBranch });
7635
7783
  try {
7636
- const content = await readFile14(assignmentPath, "utf-8");
7784
+ const content = await readFile13(assignmentPath, "utf-8");
7637
7785
  const updated = updateAssignmentWorkspace(content, {
7638
7786
  repository,
7639
7787
  worktreePath,
@@ -9480,7 +9628,7 @@ function mergePatch(current, patch) {
9480
9628
  // src/utils/view-prefs.ts
9481
9629
  init_paths();
9482
9630
  init_fs();
9483
- import { readFile as readFile12, rename as rename3, unlink as unlink3 } from "fs/promises";
9631
+ import { readFile as readFile11, rename as rename3, unlink as unlink3 } from "fs/promises";
9484
9632
  import { resolve as resolve17 } from "path";
9485
9633
  function corruptFilePath() {
9486
9634
  const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
@@ -9503,7 +9651,7 @@ async function readViewPrefsFile() {
9503
9651
  }
9504
9652
  let raw;
9505
9653
  try {
9506
- raw = await readFile12(path, "utf-8");
9654
+ raw = await readFile11(path, "utf-8");
9507
9655
  } catch {
9508
9656
  return { ...DEFAULT_VIEW_PREFS_FILE };
9509
9657
  }
@@ -9686,7 +9834,7 @@ function isDashboardSlot(value) {
9686
9834
  // src/utils/saved-views.ts
9687
9835
  init_paths();
9688
9836
  init_fs();
9689
- import { readFile as readFile13, rename as rename4, unlink as unlink4 } from "fs/promises";
9837
+ import { readFile as readFile12, rename as rename4, unlink as unlink4 } from "fs/promises";
9690
9838
  import { randomUUID as randomUUID2 } from "crypto";
9691
9839
  import { resolve as resolve18 } from "path";
9692
9840
  function corruptFilePath2() {
@@ -9716,7 +9864,7 @@ async function readSavedViewsFile() {
9716
9864
  }
9717
9865
  let raw;
9718
9866
  try {
9719
- raw = await readFile13(path, "utf-8");
9867
+ raw = await readFile12(path, "utf-8");
9720
9868
  } catch {
9721
9869
  return cloneDefault();
9722
9870
  }
@@ -10036,7 +10184,7 @@ init_fs();
10036
10184
  init_git_worktree();
10037
10185
  import { Router as Router2 } from "express";
10038
10186
  import { resolve as resolve21, basename as basename3, isAbsolute as isAbsolute2 } from "path";
10039
- import { rm, readFile as readFile16, open as fsOpen, stat as fsStat, realpath as fsRealpath } from "fs/promises";
10187
+ import { rm, readFile as readFile15, open as fsOpen, stat as fsStat, realpath as fsRealpath } from "fs/promises";
10040
10188
  import { spawnSync as spawnSync3 } from "child_process";
10041
10189
 
10042
10190
  // src/utils/worktree-defaults.ts
@@ -10083,14 +10231,14 @@ function detectCurrentBranch(cwd) {
10083
10231
  // src/dashboard/repository-candidates.ts
10084
10232
  init_fs();
10085
10233
  init_parser();
10086
- import { readdir as readdir10, readFile as readFile15 } from "fs/promises";
10234
+ import { readdir as readdir9, readFile as readFile14 } from "fs/promises";
10087
10235
  import { resolve as resolve20 } from "path";
10088
10236
  async function getProjectRepositoryCandidates(projectsDir2, projectSlug) {
10089
10237
  const seen = /* @__PURE__ */ new Set();
10090
10238
  const out = [];
10091
10239
  const projectPath = resolve20(projectsDir2, projectSlug, "project.md");
10092
10240
  if (await fileExists(projectPath)) {
10093
- const project = parseProject(await readFile15(projectPath, "utf-8"));
10241
+ const project = parseProject(await readFile14(projectPath, "utf-8"));
10094
10242
  for (const raw of project.repositories) {
10095
10243
  const path = raw.trim();
10096
10244
  if (!path) continue;
@@ -10102,12 +10250,12 @@ async function getProjectRepositoryCandidates(projectsDir2, projectSlug) {
10102
10250
  }
10103
10251
  const assignmentsDir2 = resolve20(projectsDir2, projectSlug, "assignments");
10104
10252
  if (await fileExists(assignmentsDir2)) {
10105
- const entries = await readdir10(assignmentsDir2, { withFileTypes: true });
10253
+ const entries = await readdir9(assignmentsDir2, { withFileTypes: true });
10106
10254
  for (const entry of entries) {
10107
10255
  if (!entry.isDirectory()) continue;
10108
10256
  const assignmentMd = resolve20(assignmentsDir2, entry.name, "assignment.md");
10109
10257
  if (!await fileExists(assignmentMd)) continue;
10110
- const parsed = parseAssignmentFull(await readFile15(assignmentMd, "utf-8"));
10258
+ const parsed = parseAssignmentFull(await readFile14(assignmentMd, "utf-8"));
10111
10259
  const repo = parsed.workspace.repository?.trim();
10112
10260
  if (!repo) continue;
10113
10261
  const abs = resolve20(repo);
@@ -10124,13 +10272,13 @@ async function getStandaloneRepositoryCandidates(assignmentsDir2, excludeAssignm
10124
10272
  }
10125
10273
  const seen = /* @__PURE__ */ new Set();
10126
10274
  const out = [];
10127
- const entries = await readdir10(assignmentsDir2, { withFileTypes: true });
10275
+ const entries = await readdir9(assignmentsDir2, { withFileTypes: true });
10128
10276
  for (const entry of entries) {
10129
10277
  if (!entry.isDirectory()) continue;
10130
10278
  if (entry.name === excludeAssignmentId) continue;
10131
10279
  const assignmentMd = resolve20(assignmentsDir2, entry.name, "assignment.md");
10132
10280
  if (!await fileExists(assignmentMd)) continue;
10133
- const parsed = parseAssignmentFull(await readFile15(assignmentMd, "utf-8"));
10281
+ const parsed = parseAssignmentFull(await readFile14(assignmentMd, "utf-8"));
10134
10282
  const repo = parsed.workspace.repository?.trim();
10135
10283
  if (!repo) continue;
10136
10284
  const abs = resolve20(repo);
@@ -10295,14 +10443,14 @@ async function readCurrentDocument(filePath) {
10295
10443
  if (!await fileExists(filePath)) {
10296
10444
  return null;
10297
10445
  }
10298
- return readFile16(filePath, "utf-8");
10446
+ return readFile15(filePath, "utf-8");
10299
10447
  }
10300
10448
  async function handleWorktreeCreate(req, res, ctx) {
10301
10449
  if (!await fileExists(ctx.assignmentPath)) {
10302
10450
  res.status(404).json({ error: "Assignment not found" });
10303
10451
  return;
10304
10452
  }
10305
- const parsed = parseAssignmentFull(await readFile16(ctx.assignmentPath, "utf-8"));
10453
+ const parsed = parseAssignmentFull(await readFile15(ctx.assignmentPath, "utf-8"));
10306
10454
  if (parsed.workspace.worktreePath) {
10307
10455
  res.status(409).json({ error: "Worktree already configured for this assignment" });
10308
10456
  return;
@@ -10394,6 +10542,7 @@ async function handleWorktreeCreate(req, res, ctx) {
10394
10542
  function createWriteRouter(projectsDir2, assignmentsDir2, todosDir2) {
10395
10543
  const linkedTodosLookup = todosDir2 ? { todosDir: todosDir2, projectsDir: projectsDir2 } : void 0;
10396
10544
  const router = Router2();
10545
+ installRecordsInvalidation(router);
10397
10546
  router.get("/api/templates/project", (_req, res) => {
10398
10547
  const content = renderProject({
10399
10548
  id: generateId(),
@@ -10671,7 +10820,7 @@ ${body.startsWith("\n") ? body.slice(1) : body}${body.endsWith("\n") ? "" : "\n"
10671
10820
  }
10672
10821
  const nextContentRaw = requireContent(req, res);
10673
10822
  if (!nextContentRaw) return;
10674
- const currentContent = await readFile16(filePath, "utf-8");
10823
+ const currentContent = await readFile15(filePath, "utf-8");
10675
10824
  const frontmatterBlock = extractFrontmatterBlock(currentContent);
10676
10825
  if (!frontmatterBlock) {
10677
10826
  res.status(500).json({ error: `${kind} file is malformed (no frontmatter)` });
@@ -11122,7 +11271,7 @@ ${nextBody}${nextBody.endsWith("\n") ? "" : "\n"}`;
11122
11271
  let currentContent;
11123
11272
  let currentCount = 0;
11124
11273
  if (await fileExists(commentsPath)) {
11125
- currentContent = await readFile16(commentsPath, "utf-8");
11274
+ currentContent = await readFile15(commentsPath, "utf-8");
11126
11275
  const countMatch = currentContent.match(/^entryCount:\s*(\d+)/m);
11127
11276
  if (countMatch) currentCount = parseInt(countMatch[1], 10);
11128
11277
  } else {
@@ -11179,7 +11328,7 @@ ${entry}`;
11179
11328
  res.status(400).json({ error: "resolved (boolean) is required" });
11180
11329
  return;
11181
11330
  }
11182
- const content = await readFile16(commentsPath, "utf-8");
11331
+ const content = await readFile15(commentsPath, "utf-8");
11183
11332
  const parsed = parseComments(content);
11184
11333
  const target = parsed.entries.find((e) => e.id === commentId);
11185
11334
  if (!target) {
@@ -11226,7 +11375,7 @@ ${entry}`;
11226
11375
  });
11227
11376
  return;
11228
11377
  }
11229
- let content = await readFile16(projectPath, "utf-8");
11378
+ let content = await readFile15(projectPath, "utf-8");
11230
11379
  content = setTopLevelField(content, "workspace", workspace ?? null);
11231
11380
  content = setTopLevelField(content, "updated", nowTimestamp());
11232
11381
  await writeFileForce(projectPath, content);
@@ -11263,7 +11412,7 @@ ${entry}`;
11263
11412
  return;
11264
11413
  }
11265
11414
  const assignmentPath = resolve21(resolved.assignmentDir, "assignment.md");
11266
- let content = await readFile16(assignmentPath, "utf-8");
11415
+ let content = await readFile15(assignmentPath, "utf-8");
11267
11416
  content = setTopLevelField(content, "workspaceGroup", workspaceGroup ?? null);
11268
11417
  content = setTopLevelField(content, "updated", nowTimestamp());
11269
11418
  await writeFileForce(assignmentPath, content);
@@ -11358,7 +11507,7 @@ ${entry}`;
11358
11507
  return;
11359
11508
  }
11360
11509
  const assignmentPath = resolve21(resolved.assignmentDir, "assignment.md");
11361
- const parsedForSlug = parseAssignmentFull(await readFile16(assignmentPath, "utf-8"));
11510
+ const parsedForSlug = parseAssignmentFull(await readFile15(assignmentPath, "utf-8"));
11362
11511
  const assignmentSlugForBranch = parsedForSlug.slug || resolved.id;
11363
11512
  await handleWorktreeCreate(req, res, {
11364
11513
  assignmentPath,
@@ -11387,7 +11536,7 @@ ${entry}`;
11387
11536
  res.status(400).json({ error: `Invalid status. Must be one of: ${validStatuses.join(", ")}, or null to clear.` });
11388
11537
  return;
11389
11538
  }
11390
- let content = await readFile16(projectPath, "utf-8");
11539
+ let content = await readFile15(projectPath, "utf-8");
11391
11540
  content = setTopLevelField(content, "statusOverride", status ?? null);
11392
11541
  content = setTopLevelField(content, "updated", nowTimestamp());
11393
11542
  await writeFileForce(projectPath, content);
@@ -11420,7 +11569,7 @@ ${entry}`;
11420
11569
  res.status(400).json({ error: `Invalid status. Must be one of: ${validStatuses.join(", ")}.` });
11421
11570
  return;
11422
11571
  }
11423
- let content = await readFile16(assignmentPath, "utf-8");
11572
+ let content = await readFile15(assignmentPath, "utf-8");
11424
11573
  content = setTopLevelField(content, "status", status);
11425
11574
  content = setTopLevelField(content, "updated", nowTimestamp());
11426
11575
  if (status !== "blocked") {
@@ -11454,7 +11603,7 @@ ${entry}`;
11454
11603
  res.status(400).json({ error: validation.error });
11455
11604
  return;
11456
11605
  }
11457
- let content = await readFile16(assignmentPath, "utf-8");
11606
+ let content = await readFile15(assignmentPath, "utf-8");
11458
11607
  content = setTopLevelField(content, "assignee", validation.value);
11459
11608
  content = setTopLevelField(content, "updated", nowTimestamp());
11460
11609
  await writeFileForce(assignmentPath, content);
@@ -11485,7 +11634,7 @@ ${entry}`;
11485
11634
  res.status(400).json({ error: validation.error });
11486
11635
  return;
11487
11636
  }
11488
- let content = await readFile16(assignmentPath, "utf-8");
11637
+ let content = await readFile15(assignmentPath, "utf-8");
11489
11638
  content = setTopLevelField(content, "title", validation.value);
11490
11639
  content = setTopLevelField(content, "updated", nowTimestamp());
11491
11640
  await writeFileForce(assignmentPath, content);
@@ -12003,7 +12152,7 @@ ${entry}`;
12003
12152
  res.status(400).json({ error: `Invalid status. Must be one of: ${validStatuses.join(", ")}.` });
12004
12153
  return;
12005
12154
  }
12006
- let content = await readFile16(assignmentPath, "utf-8");
12155
+ let content = await readFile15(assignmentPath, "utf-8");
12007
12156
  content = setTopLevelField(content, "status", status);
12008
12157
  content = setTopLevelField(content, "updated", nowTimestamp());
12009
12158
  if (status !== "blocked") {
@@ -12039,7 +12188,7 @@ ${entry}`;
12039
12188
  res.status(400).json({ error: validation.error });
12040
12189
  return;
12041
12190
  }
12042
- let content = await readFile16(assignmentPath, "utf-8");
12191
+ let content = await readFile15(assignmentPath, "utf-8");
12043
12192
  content = setTopLevelField(content, "assignee", validation.value);
12044
12193
  content = setTopLevelField(content, "updated", nowTimestamp());
12045
12194
  await writeFileForce(assignmentPath, content);
@@ -12072,7 +12221,7 @@ ${entry}`;
12072
12221
  res.status(400).json({ error: validation.error });
12073
12222
  return;
12074
12223
  }
12075
- let content = await readFile16(assignmentPath, "utf-8");
12224
+ let content = await readFile15(assignmentPath, "utf-8");
12076
12225
  content = setTopLevelField(content, "title", validation.value);
12077
12226
  content = setTopLevelField(content, "updated", nowTimestamp());
12078
12227
  await writeFileForce(assignmentPath, content);
@@ -12141,7 +12290,7 @@ ${entry}`;
12141
12290
  if (!await fileExists(assignmentPath)) {
12142
12291
  throw new Error("assignment file not found");
12143
12292
  }
12144
- let content = await readFile16(assignmentPath, "utf-8");
12293
+ let content = await readFile15(assignmentPath, "utf-8");
12145
12294
  content = setTopLevelField(content, "status", status);
12146
12295
  content = setTopLevelField(content, "updated", timestamp);
12147
12296
  if (status !== "blocked") {
@@ -12297,7 +12446,7 @@ async function appendCommentTo(assignmentDir, assignmentRef, req, res, reloadDet
12297
12446
  let currentContent;
12298
12447
  let currentCount = 0;
12299
12448
  if (await fileExists(commentsPath)) {
12300
- currentContent = await readFile16(commentsPath, "utf-8");
12449
+ currentContent = await readFile15(commentsPath, "utf-8");
12301
12450
  const countMatch = currentContent.match(/^entryCount:\s*(\d+)/m);
12302
12451
  if (countMatch) currentCount = parseInt(countMatch[1], 10);
12303
12452
  } else {
@@ -12337,7 +12486,7 @@ async function toggleCommentResolvedAt(assignmentDir, commentId, req, res, reloa
12337
12486
  res.status(400).json({ error: "resolved (boolean) is required" });
12338
12487
  return;
12339
12488
  }
12340
- const content = await readFile16(commentsPath, "utf-8");
12489
+ const content = await readFile15(commentsPath, "utf-8");
12341
12490
  const parsed = parseComments(content);
12342
12491
  const target = parsed.entries.find((e) => e.id === commentId);
12343
12492
  if (!target) {
@@ -13065,26 +13214,26 @@ import { Router as Router8 } from "express";
13065
13214
 
13066
13215
  // src/utils/status-config-resolution.ts
13067
13216
  init_frontmatter();
13068
- import { readFile as readFile17, writeFile as writeFile4, rm as rm2 } from "fs/promises";
13217
+ import { readFile as readFile16, writeFile as writeFile4, rm as rm2 } from "fs/promises";
13069
13218
  import { dirname as dirname5 } from "path";
13070
13219
 
13071
13220
  // src/utils/assignment-walk.ts
13072
13221
  init_fs();
13073
13222
  import { resolve as resolve23 } from "path";
13074
- import { readdir as readdir11 } from "fs/promises";
13223
+ import { readdir as readdir10 } from "fs/promises";
13075
13224
  async function listAssignmentsByProject(projectsDir2, standaloneDir) {
13076
13225
  const result = {
13077
13226
  withAssignmentMd: [],
13078
13227
  orphanFolders: []
13079
13228
  };
13080
13229
  if (await fileExists(projectsDir2)) {
13081
- const projects = await readdir11(projectsDir2, { withFileTypes: true });
13230
+ const projects = await readdir10(projectsDir2, { withFileTypes: true });
13082
13231
  for (const m of projects) {
13083
13232
  if (!m.isDirectory()) continue;
13084
13233
  if (m.name.startsWith(".") || m.name.startsWith("_")) continue;
13085
13234
  const assignmentsDir2 = resolve23(projectsDir2, m.name, "assignments");
13086
13235
  if (!await fileExists(assignmentsDir2)) continue;
13087
- const entries = await readdir11(assignmentsDir2, { withFileTypes: true });
13236
+ const entries = await readdir10(assignmentsDir2, { withFileTypes: true });
13088
13237
  for (const a of entries) {
13089
13238
  if (!a.isDirectory()) continue;
13090
13239
  if (a.name.startsWith(".") || a.name.startsWith("_")) continue;
@@ -13106,7 +13255,7 @@ async function listAssignmentsByProject(projectsDir2, standaloneDir) {
13106
13255
  }
13107
13256
  }
13108
13257
  if (standaloneDir !== null && await fileExists(standaloneDir)) {
13109
- const entries = await readdir11(standaloneDir, { withFileTypes: true });
13258
+ const entries = await readdir10(standaloneDir, { withFileTypes: true });
13110
13259
  for (const a of entries) {
13111
13260
  if (!a.isDirectory()) continue;
13112
13261
  if (a.name.startsWith(".") || a.name.startsWith("_")) continue;
@@ -13148,7 +13297,7 @@ async function scanAssignmentsByStatus(projectsDir2, standaloneDir, ids) {
13148
13297
  const assignmentPath = `${entry.assignmentDir}/assignment.md`;
13149
13298
  let content;
13150
13299
  try {
13151
- content = await readFile17(assignmentPath, "utf-8");
13300
+ content = await readFile16(assignmentPath, "utf-8");
13152
13301
  } catch (err2) {
13153
13302
  const code = err2?.code;
13154
13303
  if (code === "ENOENT") {
@@ -13211,7 +13360,7 @@ async function applyStatusResolutions(resolutions, affected, validTargets) {
13211
13360
  const list = affected.get(r.id) ?? [];
13212
13361
  for (const a of list) {
13213
13362
  try {
13214
- const content = await readFile17(a.path, "utf-8");
13363
+ const content = await readFile16(a.path, "utf-8");
13215
13364
  buffer.set(a.path, content);
13216
13365
  } catch (err2) {
13217
13366
  const code = err2?.code;
@@ -13239,7 +13388,7 @@ async function applyStatusResolutions(resolutions, affected, validTargets) {
13239
13388
  for (const a of list) {
13240
13389
  let current;
13241
13390
  try {
13242
- current = await readFile17(a.path, "utf-8");
13391
+ current = await readFile16(a.path, "utf-8");
13243
13392
  } catch (err2) {
13244
13393
  const code = err2?.code;
13245
13394
  if (code === "ENOENT") {
@@ -13288,7 +13437,7 @@ async function applyStatusResolutions(resolutions, affected, validTargets) {
13288
13437
  const list = affected.get(r.id) ?? [];
13289
13438
  for (const a of list) {
13290
13439
  try {
13291
- const current = await readFile17(a.path, "utf-8");
13440
+ const current = await readFile16(a.path, "utf-8");
13292
13441
  const fm = parseAssignmentFrontmatter(current);
13293
13442
  if (fm.status !== r.id) {
13294
13443
  console.warn(
@@ -13424,6 +13573,7 @@ function mapResolutionErrorToHttp(err2, applied) {
13424
13573
  }
13425
13574
  function createStatusConfigRouter(projectsDir2, assignmentsDir2) {
13426
13575
  const router = Router8();
13576
+ installRecordsInvalidation(router);
13427
13577
  router.get("/", async (_req, res) => {
13428
13578
  try {
13429
13579
  const config = await getStatusConfig();
@@ -14648,7 +14798,7 @@ init_playbook();
14648
14798
  init_playbooks();
14649
14799
  import { Router as Router11 } from "express";
14650
14800
  import { resolve as resolve26 } from "path";
14651
- import { readFile as readFile18 } from "fs/promises";
14801
+ import { readFile as readFile17 } from "fs/promises";
14652
14802
  function statusForPlaybookError(code) {
14653
14803
  switch (code) {
14654
14804
  case "manifest":
@@ -14730,7 +14880,7 @@ function createPlaybooksRouter(playbooksDir3) {
14730
14880
  return;
14731
14881
  }
14732
14882
  const filePath = resolve26(playbooksDir3, resolved.filename);
14733
- const content = await readFile18(filePath, "utf-8");
14883
+ const content = await readFile17(filePath, "utf-8");
14734
14884
  res.json({
14735
14885
  documentType: "playbook",
14736
14886
  title: `Edit Playbook: ${resolved.slug}`,
@@ -14827,11 +14977,12 @@ init_parser2();
14827
14977
  init_fs();
14828
14978
  init_paths();
14829
14979
  import { Router as Router12 } from "express";
14830
- import { readdir as readdir12 } from "fs/promises";
14980
+ import { readdir as readdir11 } from "fs/promises";
14831
14981
  import { resolve as resolvePath, dirname as dirname6 } from "path";
14832
14982
  import { rename as rename5, mkdir as mkdir2 } from "fs/promises";
14833
14983
  init_slug();
14834
14984
  init_promote_todos();
14985
+ init_api();
14835
14986
  var WORKSPACE_REGEX = /^[a-z0-9_][a-z0-9-]*$/;
14836
14987
  function getWorkspaceParam(value) {
14837
14988
  if (Array.isArray(value)) {
@@ -14846,6 +14997,7 @@ function touchItem3(item) {
14846
14997
  }
14847
14998
  function createTodosRouter(todosDir2, broadcast, projectsDir2) {
14848
14999
  const router = Router12();
15000
+ installRecordsInvalidation(router);
14849
15001
  function broadcastUpdate() {
14850
15002
  broadcast({ type: "todos-updated", timestamp: (/* @__PURE__ */ new Date()).toISOString() });
14851
15003
  }
@@ -14965,7 +15117,7 @@ function createTodosRouter(todosDir2, broadcast, projectsDir2) {
14965
15117
  router.get("/", async (_req, res) => {
14966
15118
  try {
14967
15119
  await ensureDir(todosDir2);
14968
- const files = await readdir12(todosDir2).catch(() => []);
15120
+ const files = await readdir11(todosDir2).catch(() => []);
14969
15121
  const workspaces = [];
14970
15122
  for (const file of files) {
14971
15123
  if (typeof file !== "string") continue;
@@ -15080,7 +15232,7 @@ function createTodosRouter(todosDir2, broadcast, projectsDir2) {
15080
15232
  try {
15081
15233
  const { archivePath: archivePath2 } = await Promise.resolve().then(() => (init_parser2(), parser_exports));
15082
15234
  const { resolve: resolve78 } = await import("path");
15083
- const { readFile: readFile54 } = await import("fs/promises");
15235
+ const { readFile: readFile53 } = await import("fs/promises");
15084
15236
  const { writeFileForce: writeFileForce2 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
15085
15237
  const workspace = getWorkspaceParam(req.params.workspace);
15086
15238
  const checklist = await readChecklist(todosDir2, workspace);
@@ -15099,7 +15251,7 @@ function createTodosRouter(todosDir2, broadcast, projectsDir2) {
15099
15251
  await ensureDir(resolve78(todosDir2, "archive"));
15100
15252
  let archContent = "";
15101
15253
  if (await fileExists(archFile)) {
15102
- archContent = await readFile54(archFile, "utf-8");
15254
+ archContent = await readFile53(archFile, "utf-8");
15103
15255
  archContent = archContent.trimEnd() + "\n\n";
15104
15256
  } else {
15105
15257
  archContent = `---
@@ -15381,7 +15533,7 @@ workspace: ${workspace}
15381
15533
  const { readConfig: readConfig3 } = await Promise.resolve().then(() => (init_config2(), config_exports));
15382
15534
  const { assignmentsDir: assignmentsDirFn } = await Promise.resolve().then(() => (init_paths(), paths_exports));
15383
15535
  const { fileExists: fileExists2, writeFileForce: writeFileForce2 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
15384
- const { readFile: readFile54 } = await import("fs/promises");
15536
+ const { readFile: readFile53 } = await import("fs/promises");
15385
15537
  const { appendTodosToAssignmentBody: appendTodosToAssignmentBody2, touchAssignmentUpdated: touchAssignmentUpdated2 } = await Promise.resolve().then(() => (init_assignment_todos(), assignment_todos_exports));
15386
15538
  const { nowTimestamp: nowTimestamp3 } = await Promise.resolve().then(() => (init_timestamp(), timestamp_exports));
15387
15539
  let assignmentRef;
@@ -15402,7 +15554,7 @@ workspace: ${workspace}
15402
15554
  }
15403
15555
  const assignmentMdPath2 = resolvePath2(assignmentDir, "assignment.md");
15404
15556
  if (!await fileExists2(assignmentMdPath2)) return { error: `Target assignment not found: ${assignmentMdPath2}` };
15405
- let content = await readFile54(assignmentMdPath2, "utf-8");
15557
+ let content = await readFile53(assignmentMdPath2, "utf-8");
15406
15558
  content = appendTodosToAssignmentBody2(
15407
15559
  content,
15408
15560
  items.map((it) => ({
@@ -15590,9 +15742,10 @@ init_fs();
15590
15742
  init_paths();
15591
15743
  init_slug();
15592
15744
  import { Router as Router13 } from "express";
15593
- import { mkdir as mkdir3, readFile as readFile19, rename as rename6 } from "fs/promises";
15745
+ import { mkdir as mkdir3, readFile as readFile18, rename as rename6 } from "fs/promises";
15594
15746
  import { resolve as resolve27, dirname as dirname7 } from "path";
15595
15747
  init_promote_todos();
15748
+ init_api();
15596
15749
  var WORKSPACE_REGEX2 = /^[a-z0-9_][a-z0-9-]*$/;
15597
15750
  function touchItem4(item) {
15598
15751
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -15641,6 +15794,7 @@ function notFound(res, slug) {
15641
15794
  }
15642
15795
  function createProjectTodosRouter(projectsDir2, broadcast, workspaceTodosDir) {
15643
15796
  const router = Router13({ mergeParams: true });
15797
+ installRecordsInvalidation(router);
15644
15798
  function broadcastUpdate(projectSlug) {
15645
15799
  broadcast({ type: "todos-updated", projectSlug, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
15646
15800
  }
@@ -15813,7 +15967,7 @@ function createProjectTodosRouter(projectsDir2, broadcast, workspaceTodosDir) {
15813
15967
  const archFile = archivePath(todosDir2, slug, checklist.archiveInterval);
15814
15968
  let archContent = "";
15815
15969
  if (await fileExists(archFile)) {
15816
- archContent = await readFile19(archFile, "utf-8");
15970
+ archContent = await readFile18(archFile, "utf-8");
15817
15971
  archContent = archContent.trimEnd() + "\n\n";
15818
15972
  } else {
15819
15973
  archContent = `---
@@ -16270,7 +16424,7 @@ workspace: ${slug}
16270
16424
  }
16271
16425
  const assignmentMdPath2 = resolve27(assignmentDir, "assignment.md");
16272
16426
  if (!await fileExists(assignmentMdPath2)) return { error: `Target assignment not found: ${assignmentMdPath2}` };
16273
- let content = await readFile19(assignmentMdPath2, "utf-8");
16427
+ let content = await readFile18(assignmentMdPath2, "utf-8");
16274
16428
  content = appendTodosToAssignmentBody2(
16275
16429
  content,
16276
16430
  items.map((it) => ({
@@ -16466,7 +16620,7 @@ workspace: ${slug}
16466
16620
 
16467
16621
  // src/dashboard/api-bundles.ts
16468
16622
  import { Router as Router14 } from "express";
16469
- import { readdir as readdir13 } from "fs/promises";
16623
+ import { readdir as readdir12 } from "fs/promises";
16470
16624
 
16471
16625
  // src/todos/bundle-parser.ts
16472
16626
  init_parser();
@@ -16474,7 +16628,7 @@ init_fs();
16474
16628
  init_paths();
16475
16629
  init_parser2();
16476
16630
  import { randomBytes as randomBytes2 } from "crypto";
16477
- import { readFile as readFile20 } from "fs/promises";
16631
+ import { readFile as readFile19 } from "fs/promises";
16478
16632
  var BUNDLE_ID_REGEX = /^[a-f0-9]{4}$/;
16479
16633
  var SCOPE_VALUES = /* @__PURE__ */ new Set(["workspace", "project", "global"]);
16480
16634
  var SCOPE_ID_REGEX = /^[a-z0-9_][a-z0-9_-]*$/;
@@ -16577,7 +16731,7 @@ ${lines}
16577
16731
  async function readBundles(todosDir2) {
16578
16732
  const path = bundlesPath(todosDir2);
16579
16733
  if (!await fileExists(path)) return [];
16580
- const content = await readFile20(path, "utf-8");
16734
+ const content = await readFile19(path, "utf-8");
16581
16735
  return parseBundles(content).bundles;
16582
16736
  }
16583
16737
  async function writeBundles(todosDir2, bundles) {
@@ -16630,7 +16784,7 @@ function createBundlesRouter(todosDir2, broadcast) {
16630
16784
  try {
16631
16785
  await ensureDir(todosDir2);
16632
16786
  const bundles = await readBundles(todosDir2);
16633
- const workspaceFiles = await readdir13(todosDir2).catch(() => []);
16787
+ const workspaceFiles = await readdir12(todosDir2).catch(() => []);
16634
16788
  const itemsByKey = /* @__PURE__ */ new Map();
16635
16789
  for (const f of workspaceFiles) {
16636
16790
  if (typeof f !== "string") continue;
@@ -16744,6 +16898,8 @@ function createProjectBundlesRouter(projectsDir2, broadcast) {
16744
16898
 
16745
16899
  // src/dashboard/api-backup.ts
16746
16900
  init_config2();
16901
+ init_api();
16902
+ init_scanner();
16747
16903
  import { Router as Router16 } from "express";
16748
16904
 
16749
16905
  // src/utils/github-backup.ts
@@ -16752,7 +16908,7 @@ init_fs();
16752
16908
  init_config2();
16753
16909
  import { execFile as execFile2 } from "child_process";
16754
16910
  import { promisify as promisify2 } from "util";
16755
- import { cp, mkdtemp, rm as rm3, readFile as readFile21, writeFile as writeFile5, unlink as unlink5, stat, open as open2, rename as rename7 } from "fs/promises";
16911
+ import { cp, mkdtemp, rm as rm3, readFile as readFile20, writeFile as writeFile5, unlink as unlink5, stat, open as open2, rename as rename7 } from "fs/promises";
16756
16912
  import { resolve as resolve29, join as join2 } from "path";
16757
16913
  import { tmpdir } from "os";
16758
16914
  var exec2 = promisify2(execFile2);
@@ -16813,7 +16969,7 @@ async function acquireLock() {
16813
16969
  return lockPath;
16814
16970
  } catch (err2) {
16815
16971
  if (err2.code === "EEXIST") {
16816
- const pid = await readFile21(lockPath, "utf-8").catch(() => "");
16972
+ const pid = await readFile20(lockPath, "utf-8").catch(() => "");
16817
16973
  throw new Error(
16818
16974
  `Backup operation already in progress (lock file at ${lockPath}, pid ${pid.trim() || "unknown"}). If stale, delete the file and retry.`
16819
16975
  );
@@ -16860,7 +17016,7 @@ function resolveCategoriesStrict(csv) {
16860
17016
  return parseCategoriesStrict(parts);
16861
17017
  }
16862
17018
  async function readSanitizedConfig(configPath) {
16863
- const content = await readFile21(configPath, "utf-8");
17019
+ const content = await readFile20(configPath, "utf-8");
16864
17020
  return content.replace(/^(\s*lastBackup:\s*).*$/m, "$1null").replace(/^(\s*lastRestore:\s*).*$/m, "$1null");
16865
17021
  }
16866
17022
  async function backupToGithub(overrides) {
@@ -17142,6 +17298,9 @@ function createBackupRouter() {
17142
17298
  success: false,
17143
17299
  error: error instanceof Error ? error.message : String(error)
17144
17300
  });
17301
+ } finally {
17302
+ invalidateRecordsCache();
17303
+ clearScanCache();
17145
17304
  }
17146
17305
  });
17147
17306
  return router;
@@ -17380,6 +17539,9 @@ function createDashboardServer(options) {
17380
17539
  });
17381
17540
  });
17382
17541
  function broadcast(message) {
17542
+ if (message.type === "project-updated" || message.type === "assignment-updated") {
17543
+ invalidateRecordsCache();
17544
+ }
17383
17545
  const data = JSON.stringify(message);
17384
17546
  for (const client of clients) {
17385
17547
  if (client.readyState === WebSocket.OPEN) {
@@ -18248,7 +18410,7 @@ init_config2();
18248
18410
  init_frontmatter();
18249
18411
  init_timestamp();
18250
18412
  import { resolve as resolve33 } from "path";
18251
- import { readdir as readdir14, readFile as readFile22 } from "fs/promises";
18413
+ import { readdir as readdir13, readFile as readFile21 } from "fs/promises";
18252
18414
  var PROMOTABLE_STATUSES = /* @__PURE__ */ new Set(["pending"]);
18253
18415
  function objectiveIsFleshedOut(content) {
18254
18416
  const match = content.match(/##\s+Objective\s*\n([\s\S]*?)(?=\n##\s+|$)/);
@@ -18266,7 +18428,7 @@ async function collectCandidates(baseDirs) {
18266
18428
  const candidates = [];
18267
18429
  for (const baseDir of baseDirs) {
18268
18430
  if (!await fileExists(baseDir)) continue;
18269
- const projects = await readdir14(baseDir, { withFileTypes: true });
18431
+ const projects = await readdir13(baseDir, { withFileTypes: true });
18270
18432
  for (const m of projects) {
18271
18433
  if (!m.isDirectory()) continue;
18272
18434
  if (m.name.startsWith(".") || m.name.startsWith("_")) continue;
@@ -18274,7 +18436,7 @@ async function collectCandidates(baseDirs) {
18274
18436
  if (await fileExists(directAssignmentMd)) {
18275
18437
  const fm = await parseSafe(directAssignmentMd);
18276
18438
  if (fm && PROMOTABLE_STATUSES.has(fm.status)) {
18277
- const content = await readFile22(directAssignmentMd, "utf-8");
18439
+ const content = await readFile21(directAssignmentMd, "utf-8");
18278
18440
  if (objectiveIsFleshedOut(content) && hasAcceptanceCriteria(content)) {
18279
18441
  candidates.push({
18280
18442
  projectSlug: null,
@@ -18289,7 +18451,7 @@ async function collectCandidates(baseDirs) {
18289
18451
  }
18290
18452
  const assignmentsDir2 = resolve33(baseDir, m.name, "assignments");
18291
18453
  if (!await fileExists(assignmentsDir2)) continue;
18292
- const entries = await readdir14(assignmentsDir2, { withFileTypes: true });
18454
+ const entries = await readdir13(assignmentsDir2, { withFileTypes: true });
18293
18455
  for (const a of entries) {
18294
18456
  if (!a.isDirectory()) continue;
18295
18457
  if (a.name.startsWith(".") || a.name.startsWith("_")) continue;
@@ -18297,7 +18459,7 @@ async function collectCandidates(baseDirs) {
18297
18459
  if (!await fileExists(assignmentMd)) continue;
18298
18460
  const fm = await parseSafe(assignmentMd);
18299
18461
  if (!fm || !PROMOTABLE_STATUSES.has(fm.status)) continue;
18300
- const content = await readFile22(assignmentMd, "utf-8");
18462
+ const content = await readFile21(assignmentMd, "utf-8");
18301
18463
  if (!objectiveIsFleshedOut(content)) continue;
18302
18464
  if (!hasAcceptanceCriteria(content)) continue;
18303
18465
  candidates.push({
@@ -18314,7 +18476,7 @@ async function collectCandidates(baseDirs) {
18314
18476
  }
18315
18477
  async function parseSafe(path) {
18316
18478
  try {
18317
- const content = await readFile22(path, "utf-8");
18479
+ const content = await readFile21(path, "utf-8");
18318
18480
  return parseAssignmentFrontmatter(content);
18319
18481
  } catch {
18320
18482
  return null;
@@ -18343,7 +18505,7 @@ async function migrateStatusesCommand(options) {
18343
18505
  const now = nowTimestamp();
18344
18506
  let migrated = 0;
18345
18507
  for (const c2 of candidates) {
18346
- const content = await readFile22(c2.assignmentMd, "utf-8");
18508
+ const content = await readFile21(c2.assignmentMd, "utf-8");
18347
18509
  const updated = updateAssignmentFile(content, {
18348
18510
  status: c2.toStatus,
18349
18511
  updated: now
@@ -18398,10 +18560,10 @@ init_config2();
18398
18560
  init_fs();
18399
18561
  import {
18400
18562
  cp as cp2,
18401
- readdir as readdir15,
18563
+ readdir as readdir14,
18402
18564
  symlink,
18403
18565
  lstat,
18404
- readFile as readFile23,
18566
+ readFile as readFile22,
18405
18567
  readlink,
18406
18568
  rename as rename8,
18407
18569
  rm as rm4,
@@ -18462,7 +18624,7 @@ function getInstallMarkerPath(targetDir) {
18462
18624
  return resolve35(targetDir, INSTALL_MARKER_FILENAME);
18463
18625
  }
18464
18626
  async function readPackageManifest(packageRoot) {
18465
- const raw = await readFile23(resolve35(packageRoot, "package.json"), "utf-8");
18627
+ const raw = await readFile22(resolve35(packageRoot, "package.json"), "utf-8");
18466
18628
  return JSON.parse(raw);
18467
18629
  }
18468
18630
  async function readJsonFileIfExists(pathValue) {
@@ -18470,7 +18632,7 @@ async function readJsonFileIfExists(pathValue) {
18470
18632
  return null;
18471
18633
  }
18472
18634
  try {
18473
- const raw = await readFile23(pathValue, "utf-8");
18635
+ const raw = await readFile22(pathValue, "utf-8");
18474
18636
  return JSON.parse(raw);
18475
18637
  } catch {
18476
18638
  return null;
@@ -18486,7 +18648,7 @@ async function readPluginManifestName(targetDir, pluginKind) {
18486
18648
  if (!await fileExists(manifestPath)) {
18487
18649
  return void 0;
18488
18650
  }
18489
- const raw = await readFile23(manifestPath, "utf-8");
18651
+ const raw = await readFile22(manifestPath, "utf-8");
18490
18652
  const parsed = JSON.parse(raw);
18491
18653
  return parsed.name;
18492
18654
  }
@@ -18496,7 +18658,7 @@ async function readInstallMetadata(targetDir) {
18496
18658
  return null;
18497
18659
  }
18498
18660
  try {
18499
- const raw = await readFile23(markerPath, "utf-8");
18661
+ const raw = await readFile22(markerPath, "utf-8");
18500
18662
  return JSON.parse(raw);
18501
18663
  } catch {
18502
18664
  return null;
@@ -18619,7 +18781,7 @@ async function writeClaudeMarketplaceFile(manifestPath, marketplace) {
18619
18781
  }
18620
18782
  if (await fileExists(manifestPath)) {
18621
18783
  try {
18622
- const prev = await readFile23(manifestPath, "utf-8");
18784
+ const prev = await readFile22(manifestPath, "utf-8");
18623
18785
  JSON.parse(prev);
18624
18786
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
18625
18787
  await writeFile7(`${manifestPath}.bak-${stamp}`, prev, "utf-8");
@@ -18692,7 +18854,7 @@ async function listClaudeMarketplaceCandidates() {
18692
18854
  const [knownMarketplaces, activeMarketplaceNames, entries] = await Promise.all([
18693
18855
  readKnownClaudeMarketplaceRecords(),
18694
18856
  readInstalledClaudeMarketplaceNames(),
18695
- readdir15(rootDir, { withFileTypes: true })
18857
+ readdir14(rootDir, { withFileTypes: true })
18696
18858
  ]);
18697
18859
  const candidates = [];
18698
18860
  const seen = /* @__PURE__ */ new Set();
@@ -18767,7 +18929,7 @@ async function registerKnownClaudeMarketplace(name, rootDir) {
18767
18929
  const manifestPath = getClaudeKnownMarketplacesPath();
18768
18930
  let existing = {};
18769
18931
  if (await fileExists(manifestPath)) {
18770
- const raw = await readFile23(manifestPath, "utf-8");
18932
+ const raw = await readFile22(manifestPath, "utf-8");
18771
18933
  try {
18772
18934
  const parsed = JSON.parse(raw);
18773
18935
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
@@ -18794,7 +18956,7 @@ async function registerKnownClaudeMarketplace(name, rootDir) {
18794
18956
  existing[name].autoUpdate = true;
18795
18957
  await ensureDir(dirname10(manifestPath));
18796
18958
  if (await fileExists(manifestPath)) {
18797
- const prev = await readFile23(manifestPath, "utf-8");
18959
+ const prev = await readFile22(manifestPath, "utf-8");
18798
18960
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
18799
18961
  await writeFile7(`${manifestPath}.bak-${stamp}`, prev, "utf-8");
18800
18962
  }
@@ -18812,7 +18974,7 @@ async function setSyntaurPluginEnabled(options) {
18812
18974
  const key = `syntaur@${options.marketplaceName}`;
18813
18975
  let parsed = {};
18814
18976
  if (await fileExists(settingsPath)) {
18815
- const raw = await readFile23(settingsPath, "utf-8");
18977
+ const raw = await readFile22(settingsPath, "utf-8");
18816
18978
  try {
18817
18979
  parsed = JSON.parse(raw);
18818
18980
  } catch {
@@ -18831,7 +18993,7 @@ async function setSyntaurPluginEnabled(options) {
18831
18993
  await ensureDir(dirname10(settingsPath));
18832
18994
  if (await fileExists(settingsPath)) {
18833
18995
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
18834
- const prev = await readFile23(settingsPath, "utf-8");
18996
+ const prev = await readFile22(settingsPath, "utf-8");
18835
18997
  await writeFile7(`${settingsPath}.bak-${stamp}`, prev, "utf-8");
18836
18998
  }
18837
18999
  const tmpPath = `${settingsPath}.tmp`;
@@ -19069,7 +19231,7 @@ async function readMarketplaceFile(marketplacePath) {
19069
19231
  plugins: []
19070
19232
  };
19071
19233
  }
19072
- const raw = await readFile23(marketplacePath, "utf-8");
19234
+ const raw = await readFile22(marketplacePath, "utf-8");
19073
19235
  const parsed = JSON.parse(raw);
19074
19236
  return {
19075
19237
  name: parsed.name ?? "local",
@@ -19305,13 +19467,13 @@ async function textPrompt(question, defaultValue) {
19305
19467
 
19306
19468
  // src/utils/install-skills.ts
19307
19469
  init_fs();
19308
- import { readFile as readFile25, readdir as readdir16, mkdir as mkdir4, copyFile, rm as rm5, lstat as lstat2 } from "fs/promises";
19470
+ import { readFile as readFile24, readdir as readdir15, mkdir as mkdir4, copyFile, rm as rm5, lstat as lstat2 } from "fs/promises";
19309
19471
  import { resolve as resolve37, relative as relative3, join as join3 } from "path";
19310
19472
  import { homedir as homedir4 } from "os";
19311
19473
 
19312
19474
  // src/utils/plugin-state.ts
19313
19475
  init_fs();
19314
- import { readFile as readFile24 } from "fs/promises";
19476
+ import { readFile as readFile23 } from "fs/promises";
19315
19477
  import { resolve as resolve36 } from "path";
19316
19478
  import { homedir as homedir3 } from "os";
19317
19479
  function settingsPathFor(agent) {
@@ -19322,7 +19484,7 @@ async function readJsonOrNull(path) {
19322
19484
  if (!path) return null;
19323
19485
  if (!await fileExists(path)) return null;
19324
19486
  try {
19325
- const raw = await readFile24(path, "utf-8");
19487
+ const raw = await readFile23(path, "utf-8");
19326
19488
  return JSON.parse(raw);
19327
19489
  } catch {
19328
19490
  return null;
@@ -19365,7 +19527,8 @@ var KNOWN_SKILL_NAMES = [
19365
19527
  "claim-resource",
19366
19528
  "release-resource",
19367
19529
  "extend-resource",
19368
- "list-resources"
19530
+ "list-resources",
19531
+ "run-playbook"
19369
19532
  ];
19370
19533
  var KNOWN_SKILLS = KNOWN_SKILL_NAMES;
19371
19534
  async function getSkillsDir() {
@@ -19379,7 +19542,7 @@ function defaultSkillTargetDir(target) {
19379
19542
  async function walkFiles(root) {
19380
19543
  const out = [];
19381
19544
  async function walk(dir) {
19382
- const entries = await readdir16(dir, { withFileTypes: true });
19545
+ const entries = await readdir15(dir, { withFileTypes: true });
19383
19546
  for (const entry of entries) {
19384
19547
  const full = join3(dir, entry.name);
19385
19548
  if (entry.isDirectory()) {
@@ -19394,7 +19557,7 @@ async function walkFiles(root) {
19394
19557
  }
19395
19558
  async function filesEqual(a, b) {
19396
19559
  try {
19397
- const [ba, bb] = await Promise.all([readFile25(a), readFile25(b)]);
19560
+ const [ba, bb] = await Promise.all([readFile24(a), readFile24(b)]);
19398
19561
  if (ba.length !== bb.length) return false;
19399
19562
  return ba.equals(bb);
19400
19563
  } catch {
@@ -19403,7 +19566,7 @@ async function filesEqual(a, b) {
19403
19566
  }
19404
19567
  async function copyDir(srcDir, destDir) {
19405
19568
  await mkdir4(destDir, { recursive: true });
19406
- const entries = await readdir16(srcDir, { withFileTypes: true });
19569
+ const entries = await readdir15(srcDir, { withFileTypes: true });
19407
19570
  for (const entry of entries) {
19408
19571
  const src = join3(srcDir, entry.name);
19409
19572
  const dest = join3(destDir, entry.name);
@@ -19457,7 +19620,7 @@ async function installSkillDir(srcDir, destDir, skillName, force) {
19457
19620
  return { skill: skillName, status: "differs-preserved", targetPath: destDir };
19458
19621
  }
19459
19622
  async function discoverSkillNames(sourceDir) {
19460
- const entries = await readdir16(sourceDir, { withFileTypes: true });
19623
+ const entries = await readdir15(sourceDir, { withFileTypes: true });
19461
19624
  const names = [];
19462
19625
  for (const entry of entries) {
19463
19626
  if (!entry.isDirectory()) continue;
@@ -19516,7 +19679,7 @@ async function uninstallSkills(options) {
19516
19679
  if (await isSymlink(destDir)) continue;
19517
19680
  const skillMd = join3(destDir, "SKILL.md");
19518
19681
  if (!await fileExists(skillMd)) continue;
19519
- const content = await readFile25(skillMd, "utf-8").catch(() => "");
19682
+ const content = await readFile24(skillMd, "utf-8").catch(() => "");
19520
19683
  const match = content.match(/^name:\s*(\S+)\s*$/m);
19521
19684
  if (!match || match[1] !== skill) continue;
19522
19685
  await rm5(destDir, { recursive: true, force: true });
@@ -19705,7 +19868,7 @@ async function installPluginCommand(options) {
19705
19868
  // src/commands/install-statusline.ts
19706
19869
  init_paths();
19707
19870
  init_fs();
19708
- import { readFile as readFile27, writeFile as writeFile9, copyFile as copyFile2, rm as rm6, stat as stat2, symlink as symlink2, unlink as unlink8, lstat as lstat3 } from "fs/promises";
19871
+ import { readFile as readFile26, writeFile as writeFile9, copyFile as copyFile2, rm as rm6, stat as stat2, symlink as symlink2, unlink as unlink8, lstat as lstat3 } from "fs/promises";
19709
19872
  import { resolve as resolve39, dirname as dirname12 } from "path";
19710
19873
  import { homedir as homedir5 } from "os";
19711
19874
  import { fileURLToPath as fileURLToPath4 } from "url";
@@ -19713,7 +19876,7 @@ import { fileURLToPath as fileURLToPath4 } from "url";
19713
19876
  // src/commands/configure-statusline.ts
19714
19877
  init_paths();
19715
19878
  init_fs();
19716
- import { readFile as readFile26, writeFile as writeFile8 } from "fs/promises";
19879
+ import { readFile as readFile25, writeFile as writeFile8 } from "fs/promises";
19717
19880
  import { resolve as resolve38, dirname as dirname11 } from "path";
19718
19881
  import { spawnSync as spawnSync5 } from "child_process";
19719
19882
  import { checkbox, input as input2, confirm } from "@inquirer/prompts";
@@ -19740,7 +19903,7 @@ function getConfigPath(installRoot) {
19740
19903
  async function readConfig2(path) {
19741
19904
  if (!await fileExists(path)) return null;
19742
19905
  try {
19743
- const raw = await readFile26(path, "utf-8");
19906
+ const raw = await readFile25(path, "utf-8");
19744
19907
  const parsed = JSON.parse(raw);
19745
19908
  if (!parsed || typeof parsed !== "object") return null;
19746
19909
  const segments = Array.isArray(parsed.segments) ? parsed.segments.filter(isSegmentName) : [];
@@ -19928,7 +20091,7 @@ function getPackageStatuslineSource() {
19928
20091
  }
19929
20092
  async function readSettingsJson(settingsPath) {
19930
20093
  if (!await fileExists(settingsPath)) return {};
19931
- const raw = await readFile27(settingsPath, "utf-8");
20094
+ const raw = await readFile26(settingsPath, "utf-8");
19932
20095
  if (raw.trim() === "") return {};
19933
20096
  try {
19934
20097
  const parsed = JSON.parse(raw);
@@ -20116,7 +20279,7 @@ async function uninstallStatuslineCommand(options = {}) {
20116
20279
  let restored = null;
20117
20280
  if (await fileExists(backupPath)) {
20118
20281
  try {
20119
- const raw = await readFile27(backupPath, "utf-8");
20282
+ const raw = await readFile26(backupPath, "utf-8");
20120
20283
  const parsed = JSON.parse(raw);
20121
20284
  const prev = parsed?.previousStatusLine;
20122
20285
  if (prev && typeof prev === "object" && typeof prev.command === "string") {
@@ -21149,7 +21312,7 @@ function normalizeSlashes(p) {
21149
21312
  }
21150
21313
  function detectInstallKind(scriptUrl, opts = {}) {
21151
21314
  const realpath3 = opts.realpath ?? realpathSync.native;
21152
- const readFile54 = opts.readFile ?? ((p) => readFileSync(p, "utf-8"));
21315
+ const readFile53 = opts.readFile ?? ((p) => readFileSync(p, "utf-8"));
21153
21316
  const ua = opts.envUserAgent !== void 0 ? opts.envUserAgent : process.env.npm_config_user_agent ?? "";
21154
21317
  const resolved = resolveScriptPath(scriptUrl, realpath3);
21155
21318
  if (resolved === null) {
@@ -21170,7 +21333,7 @@ function detectInstallKind(scriptUrl, opts = {}) {
21170
21333
  const pkgJsonPath = join4(dir, "package.json");
21171
21334
  let raw;
21172
21335
  try {
21173
- raw = readFile54(pkgJsonPath);
21336
+ raw = readFile53(pkgJsonPath);
21174
21337
  } catch {
21175
21338
  const parent2 = dirname13(dir);
21176
21339
  if (parent2 === dir) break;
@@ -21344,7 +21507,7 @@ init_config2();
21344
21507
  init_paths();
21345
21508
  init_fs();
21346
21509
  import { resolve as resolve46, isAbsolute as isAbsolute7 } from "path";
21347
- import { readFile as readFile28 } from "fs/promises";
21510
+ import { readFile as readFile27 } from "fs/promises";
21348
21511
  import { select, confirm as confirm2, input as input3 } from "@inquirer/prompts";
21349
21512
  async function browseCommand(options) {
21350
21513
  const config = await readConfig();
@@ -21423,7 +21586,7 @@ async function ensureWorktree(opts) {
21423
21586
  if (!await fileExists(assignmentPath)) {
21424
21587
  return void 0;
21425
21588
  }
21426
- const content = await readFile28(assignmentPath, "utf-8");
21589
+ const content = await readFile27(assignmentPath, "utf-8");
21427
21590
  const { parseAssignmentFrontmatter: parseAssignmentFrontmatter2 } = await Promise.resolve().then(() => (init_frontmatter(), frontmatter_exports));
21428
21591
  const fm = parseAssignmentFrontmatter2(content);
21429
21592
  const { workspace } = fm;
@@ -21557,7 +21720,7 @@ init_paths();
21557
21720
  init_fs();
21558
21721
  init_parser();
21559
21722
  init_config2();
21560
- import { readdir as readdir17, readFile as readFile29 } from "fs/promises";
21723
+ import { readdir as readdir16, readFile as readFile28 } from "fs/promises";
21561
21724
  import { resolve as resolve48 } from "path";
21562
21725
  async function listPlaybooksCommand(options = {}) {
21563
21726
  const dir = playbooksDir();
@@ -21567,14 +21730,14 @@ async function listPlaybooksCommand(options = {}) {
21567
21730
  }
21568
21731
  const config = await readConfig();
21569
21732
  const disabledSet = new Set(config.playbooks.disabled);
21570
- const entries = await readdir17(dir, { withFileTypes: true });
21733
+ const entries = await readdir16(dir, { withFileTypes: true });
21571
21734
  const mdFiles = entries.filter(
21572
21735
  (e) => e.isFile() && e.name.endsWith(".md") && !e.name.startsWith("_") && e.name !== "manifest.md"
21573
21736
  );
21574
21737
  const rows = [];
21575
21738
  for (const entry of mdFiles) {
21576
21739
  const filePath = resolve48(dir, entry.name);
21577
- const raw = await readFile29(filePath, "utf-8");
21740
+ const raw = await readFile28(filePath, "utf-8");
21578
21741
  const parsed = parsePlaybook(raw);
21579
21742
  const slug = parsed.slug || entry.name.replace(/\.md$/, "");
21580
21743
  const disabled = disabledSet.has(slug);
@@ -21697,13 +21860,13 @@ init_fs();
21697
21860
  init_config2();
21698
21861
  init_slug();
21699
21862
  import { Command as Command2 } from "commander";
21700
- import { readFile as readFile31 } from "fs/promises";
21863
+ import { readFile as readFile30 } from "fs/promises";
21701
21864
  import { resolve as resolve50 } from "path";
21702
21865
 
21703
21866
  // src/commands/bundle.ts
21704
21867
  init_paths();
21705
21868
  import { Command } from "commander";
21706
- import { mkdir as mkdir7, readFile as readFile30, readdir as readdir18, rm as rm7, writeFile as writeFile11 } from "fs/promises";
21869
+ import { mkdir as mkdir7, readFile as readFile29, readdir as readdir17, rm as rm7, writeFile as writeFile11 } from "fs/promises";
21707
21870
  import { resolve as resolve49 } from "path";
21708
21871
  init_parser2();
21709
21872
  init_fs();
@@ -21996,7 +22159,7 @@ bundleCommand.command("plan").description("Create or open the bundle's shared pl
21996
22159
  }
21997
22160
  const planDir = bundlePlanDir(sc.todosPath, sc.scopeId, id);
21998
22161
  await ensureDir(planDir);
21999
- const existing = (await readdir18(planDir).catch(() => [])).filter((f) => /^plan(?:-v\d+)?\.md$/.test(f));
22162
+ const existing = (await readdir17(planDir).catch(() => [])).filter((f) => /^plan(?:-v\d+)?\.md$/.test(f));
22000
22163
  const { target } = pickNextPlanFile(planDir, existing);
22001
22164
  const checklist = await readChecklist(sc.todosPath, sc.checklistKey);
22002
22165
  if (!await fileExists(target)) {
@@ -22065,8 +22228,8 @@ bundleCommand.command("worktree").description("Create a git worktree for the bun
22065
22228
  }
22066
22229
  const bundlesFilePath = bundlesPath(sc.todosPath);
22067
22230
  const checklistFilePath = checklistPath(sc.todosPath, sc.checklistKey);
22068
- const bundlesSnapshot = await fileExists(bundlesFilePath) ? await readFile30(bundlesFilePath, "utf-8") : null;
22069
- const checklistSnapshot = await fileExists(checklistFilePath) ? await readFile30(checklistFilePath, "utf-8") : null;
22231
+ const bundlesSnapshot = await fileExists(bundlesFilePath) ? await readFile29(bundlesFilePath, "utf-8") : null;
22232
+ const checklistSnapshot = await fileExists(checklistFilePath) ? await readFile29(checklistFilePath, "utf-8") : null;
22070
22233
  const record = async () => {
22071
22234
  bundle.branch = options.branch;
22072
22235
  bundle.worktreePath = worktreePath;
@@ -22612,7 +22775,7 @@ todoCommand.command("archive").description("Archive completed todos and their lo
22612
22775
  await ensureDir(resolve50(todosPath, "archive"));
22613
22776
  let archContent = "";
22614
22777
  if (await fileExists(archFile)) {
22615
- archContent = await readFile31(archFile, "utf-8");
22778
+ archContent = await readFile30(archFile, "utf-8");
22616
22779
  archContent = archContent.trimEnd() + "\n\n";
22617
22780
  } else {
22618
22781
  archContent = `---
@@ -22789,12 +22952,12 @@ function describeScope(scope) {
22789
22952
  }
22790
22953
  async function injectPromotedTodos(assignmentDir, todos, scopeLabel) {
22791
22954
  const { resolve: resolvePath2 } = await import("path");
22792
- const { readFile: readFile54 } = await import("fs/promises");
22955
+ const { readFile: readFile53 } = await import("fs/promises");
22793
22956
  const { writeFileForce: writeFileForce2 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
22794
22957
  const { appendTodosToAssignmentBody: appendTodosToAssignmentBody2, touchAssignmentUpdated: touchAssignmentUpdated2 } = await Promise.resolve().then(() => (init_assignment_todos(), assignment_todos_exports));
22795
22958
  const { nowTimestamp: nowTimestamp3 } = await Promise.resolve().then(() => (init_timestamp(), timestamp_exports));
22796
22959
  const assignmentMdPath2 = resolvePath2(assignmentDir, "assignment.md");
22797
- let content = await readFile54(assignmentMdPath2, "utf-8");
22960
+ let content = await readFile53(assignmentMdPath2, "utf-8");
22798
22961
  content = appendTodosToAssignmentBody2(
22799
22962
  content,
22800
22963
  todos.map((t) => ({
@@ -22839,8 +23002,8 @@ todoCommand.command("plan").description("Create or open a plan directory for a t
22839
23002
  }
22840
23003
  const planDir = todoPlanDir(todosPath, workspace, id);
22841
23004
  await ensureDir(planDir);
22842
- const { readdir: readdir30 } = await import("fs/promises");
22843
- const existingFiles = (await readdir30(planDir).catch(() => [])).filter(
23005
+ const { readdir: readdir29 } = await import("fs/promises");
23006
+ const existingFiles = (await readdir29(planDir).catch(() => [])).filter(
22844
23007
  (f) => /^plan(?:-v\d+)?\.md$/.test(f)
22845
23008
  );
22846
23009
  let target;
@@ -23044,12 +23207,12 @@ backupCommand.command("config").description("Show or update backup configuration
23044
23207
 
23045
23208
  // src/commands/doctor.ts
23046
23209
  import { Command as Command4 } from "commander";
23047
- import { readFile as readFile39 } from "fs/promises";
23210
+ import { readFile as readFile38 } from "fs/promises";
23048
23211
  import { isAbsolute as isAbsolute10, resolve as resolve62 } from "path";
23049
23212
 
23050
23213
  // src/utils/doctor/index.ts
23051
23214
  import { fileURLToPath as fileURLToPath8 } from "url";
23052
- import { readFile as readFile38 } from "fs/promises";
23215
+ import { readFile as readFile37 } from "fs/promises";
23053
23216
  import { dirname as dirname17, join as join8 } from "path";
23054
23217
 
23055
23218
  // src/utils/doctor/context.ts
@@ -23096,7 +23259,7 @@ function closeCheckContext(ctx) {
23096
23259
  init_fs();
23097
23260
  init_paths();
23098
23261
  import { resolve as resolve52, isAbsolute as isAbsolute8 } from "path";
23099
- import { readFile as readFile32, stat as stat3 } from "fs/promises";
23262
+ import { readFile as readFile31, stat as stat3 } from "fs/promises";
23100
23263
  import { fileURLToPath as fileURLToPath7 } from "url";
23101
23264
  import { dirname as dirname15, join as join6 } from "path";
23102
23265
  var CATEGORY = "env";
@@ -23153,7 +23316,7 @@ var configValid = {
23153
23316
  autoFixable: false
23154
23317
  };
23155
23318
  }
23156
- const content = await readFile32(configPath, "utf-8");
23319
+ const content = await readFile31(configPath, "utf-8");
23157
23320
  const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
23158
23321
  if (!fmMatch || fmMatch[1].trim() === "") {
23159
23322
  return {
@@ -23433,7 +23596,7 @@ async function readLocalPkg() {
23433
23596
  for (let i = 0; i < 6; i++) {
23434
23597
  const candidate = join6(dir, "package.json");
23435
23598
  try {
23436
- const text = await readFile32(candidate, "utf-8");
23599
+ const text = await readFile31(candidate, "utf-8");
23437
23600
  return JSON.parse(text);
23438
23601
  } catch {
23439
23602
  dir = dirname15(dir);
@@ -23486,7 +23649,7 @@ function versionGte(a, b) {
23486
23649
  // src/utils/doctor/checks/structure.ts
23487
23650
  init_fs();
23488
23651
  import { resolve as resolve53 } from "path";
23489
- import { readdir as readdir19, stat as stat4 } from "fs/promises";
23652
+ import { readdir as readdir18, stat as stat4 } from "fs/promises";
23490
23653
  var CATEGORY2 = "structure";
23491
23654
  var KNOWN_TOP_LEVEL = /* @__PURE__ */ new Set([
23492
23655
  "projects",
@@ -23617,7 +23780,7 @@ var knownFilesRecognized = {
23617
23780
  category: CATEGORY2,
23618
23781
  title: "No unexpected top-level entries under ~/.syntaur/",
23619
23782
  async run(ctx) {
23620
- const entries = await readdir19(ctx.syntaurRoot, { withFileTypes: true });
23783
+ const entries = await readdir18(ctx.syntaurRoot, { withFileTypes: true });
23621
23784
  const unexpected = [];
23622
23785
  for (const e of entries) {
23623
23786
  if (e.name.startsWith(".")) continue;
@@ -23661,7 +23824,7 @@ function pass2(check) {
23661
23824
  // src/utils/doctor/checks/project.ts
23662
23825
  init_fs();
23663
23826
  import { resolve as resolve54 } from "path";
23664
- import { readdir as readdir20, stat as stat5 } from "fs/promises";
23827
+ import { readdir as readdir19, stat as stat5 } from "fs/promises";
23665
23828
  var CATEGORY3 = "project";
23666
23829
  var REQUIRED_PROJECT_FILES = [
23667
23830
  "project.md",
@@ -23685,7 +23848,7 @@ var PROJECT_MARKERS = ["project.md", "manifest.md", "assignments"];
23685
23848
  async function listProjects2(ctx) {
23686
23849
  const dir = ctx.config.defaultProjectDir;
23687
23850
  if (!await fileExists(dir)) return [];
23688
- const entries = await readdir20(dir, { withFileTypes: true });
23851
+ const entries = await readdir19(dir, { withFileTypes: true });
23689
23852
  const result = [];
23690
23853
  for (const e of entries) {
23691
23854
  if (!e.isDirectory()) continue;
@@ -23779,7 +23942,7 @@ var orphanFiles = {
23779
23942
  const projects = await listProjects2(ctx);
23780
23943
  const results = [];
23781
23944
  for (const projectDir of projects) {
23782
- const entries = await readdir20(projectDir, { withFileTypes: true });
23945
+ const entries = await readdir19(projectDir, { withFileTypes: true });
23783
23946
  const orphans = [];
23784
23947
  for (const e of entries) {
23785
23948
  if (e.name.startsWith(".")) continue;
@@ -23809,7 +23972,7 @@ async function newestAssignmentMtime(projectDir) {
23809
23972
  let newest = 0;
23810
23973
  let entries;
23811
23974
  try {
23812
- entries = await readdir20(assignmentsRoot, { withFileTypes: true });
23975
+ entries = await readdir19(assignmentsRoot, { withFileTypes: true });
23813
23976
  } catch {
23814
23977
  return 0;
23815
23978
  }
@@ -23840,7 +24003,7 @@ init_parser();
23840
24003
  init_types();
23841
24004
  init_paths();
23842
24005
  import { resolve as resolve55 } from "path";
23843
- import { readFile as readFile33, readdir as readdir21 } from "fs/promises";
24006
+ import { readFile as readFile32, readdir as readdir20 } from "fs/promises";
23844
24007
  var CATEGORY4 = "assignment";
23845
24008
  var STATUSES_REQUIRING_HANDOFF = /* @__PURE__ */ new Set(["review", "completed"]);
23846
24009
  var PRE_WORKSPACE_STATUSES = /* @__PURE__ */ new Set([
@@ -24188,7 +24351,7 @@ var draftMissingObjective = {
24188
24351
  if (parsed.status !== "draft") continue;
24189
24352
  let raw;
24190
24353
  try {
24191
- raw = await readFile33(path, "utf-8");
24354
+ raw = await readFile32(path, "utf-8");
24192
24355
  } catch {
24193
24356
  continue;
24194
24357
  }
@@ -24225,12 +24388,12 @@ var readyToImplementMissingPlan = {
24225
24388
  const parsed = await parseSafe2(path);
24226
24389
  if (!parsed) continue;
24227
24390
  if (parsed.status !== "ready_to_implement") continue;
24228
- const entries = await readdir21(a.assignmentDir).catch(() => []);
24391
+ const entries = await readdir20(a.assignmentDir).catch(() => []);
24229
24392
  const planFiles = entries.filter((f) => /^plan(?:-v\d+)?\.md$/i.test(f));
24230
24393
  let hasPlanContent = false;
24231
24394
  for (const f of planFiles) {
24232
24395
  try {
24233
- const c2 = await readFile33(resolve55(a.assignmentDir, f), "utf-8");
24396
+ const c2 = await readFile32(resolve55(a.assignmentDir, f), "utf-8");
24234
24397
  if (c2.trim().length > 0) {
24235
24398
  hasPlanContent = true;
24236
24399
  break;
@@ -24273,7 +24436,7 @@ var assignmentChecks = [
24273
24436
  ];
24274
24437
  async function parseSafe2(path) {
24275
24438
  try {
24276
- const content = await readFile33(path, "utf-8");
24439
+ const content = await readFile32(path, "utf-8");
24277
24440
  return parseAssignmentFull(content);
24278
24441
  } catch {
24279
24442
  return null;
@@ -24434,7 +24597,7 @@ function skipped(check, reason) {
24434
24597
  // src/utils/doctor/checks/integrations.ts
24435
24598
  init_fs();
24436
24599
  import { resolve as resolve57, dirname as dirname16, basename as basename5 } from "path";
24437
- import { readdir as readdir22, readFile as readFile34 } from "fs/promises";
24600
+ import { readdir as readdir21, readFile as readFile33 } from "fs/promises";
24438
24601
  import { homedir as homedir7 } from "os";
24439
24602
  var CATEGORY6 = "integrations";
24440
24603
  var claudePluginLinked = {
@@ -24497,7 +24660,7 @@ var backupConfigured = {
24497
24660
  if (ctx.config.backup?.repo) return pass6(this);
24498
24661
  const projectsDir2 = ctx.config.defaultProjectDir;
24499
24662
  if (!await fileExists(projectsDir2)) return skipped2(this, "no projects dir");
24500
- const entries = await readdir22(projectsDir2, { withFileTypes: true });
24663
+ const entries = await readdir21(projectsDir2, { withFileTypes: true });
24501
24664
  const hasProjects = entries.some((e) => e.isDirectory() && !e.name.startsWith(".") && !e.name.startsWith("_"));
24502
24665
  if (!hasProjects) return skipped2(this, "no projects yet");
24503
24666
  return {
@@ -24519,7 +24682,7 @@ async function readKnownMarketplaces() {
24519
24682
  const path = resolve57(homedir7(), ".claude", "plugins", "known_marketplaces.json");
24520
24683
  if (!await fileExists(path)) return {};
24521
24684
  try {
24522
- const raw = await readFile34(path, "utf-8");
24685
+ const raw = await readFile33(path, "utf-8");
24523
24686
  return JSON.parse(raw);
24524
24687
  } catch {
24525
24688
  return {};
@@ -24572,7 +24735,7 @@ var claudeMarketplaceRegistered = {
24572
24735
  }
24573
24736
  let parsed = {};
24574
24737
  try {
24575
- parsed = JSON.parse(await readFile34(marketplaceManifest, "utf-8"));
24738
+ parsed = JSON.parse(await readFile33(marketplaceManifest, "utf-8"));
24576
24739
  } catch {
24577
24740
  return {
24578
24741
  id: this.id,
@@ -24645,7 +24808,7 @@ init_fs();
24645
24808
  init_parser();
24646
24809
  init_types();
24647
24810
  import { resolve as resolve58 } from "path";
24648
- import { readFile as readFile35 } from "fs/promises";
24811
+ import { readFile as readFile34 } from "fs/promises";
24649
24812
  var CATEGORY7 = "workspace";
24650
24813
  var ASSIGNMENT_FIELDS = ["projectSlug", "assignmentSlug", "projectDir", "assignmentDir"];
24651
24814
  var BUNDLE_FIELDS = ["bundleId", "bundleScope", "bundleScopeId"];
@@ -24671,7 +24834,7 @@ async function loadContext(ctx) {
24671
24834
  return { data: null, path, exists: false, parseError: null };
24672
24835
  }
24673
24836
  try {
24674
- const raw = await readFile35(path, "utf-8");
24837
+ const raw = await readFile34(path, "utf-8");
24675
24838
  return { data: JSON.parse(raw), path, exists: true, parseError: null };
24676
24839
  } catch (err2) {
24677
24840
  return {
@@ -24798,7 +24961,7 @@ var contextTerminal = {
24798
24961
  const assignmentMd = resolve58(data.assignmentDir, "assignment.md");
24799
24962
  if (!await fileExists(assignmentMd)) return skipped3(this, "assignment file missing");
24800
24963
  try {
24801
- const content = await readFile35(assignmentMd, "utf-8");
24964
+ const content = await readFile34(assignmentMd, "utf-8");
24802
24965
  const parsed = parseAssignmentFull(content);
24803
24966
  const terminal = terminalStatuses2(ctx);
24804
24967
  if (terminal.has(parsed.status)) {
@@ -24944,7 +25107,7 @@ var agentChecks = [agentsResolvable];
24944
25107
  // src/utils/doctor/checks/terminal.ts
24945
25108
  init_config2();
24946
25109
  import { spawnSync as spawnSync8 } from "child_process";
24947
- import { readFile as readFile36 } from "fs/promises";
25110
+ import { readFile as readFile35 } from "fs/promises";
24948
25111
  import { resolve as resolve59 } from "path";
24949
25112
  init_paths();
24950
25113
  init_fs();
@@ -24952,7 +25115,7 @@ var CATEGORY9 = "terminal";
24952
25115
  async function readRawTerminalKey() {
24953
25116
  const configPath = resolve59(syntaurRoot(), "config.md");
24954
25117
  if (!await fileExists(configPath)) return null;
24955
- const content = await readFile36(configPath, "utf-8");
25118
+ const content = await readFile35(configPath, "utf-8");
24956
25119
  const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
24957
25120
  if (!fmMatch) return null;
24958
25121
  const line = fmMatch[1].split("\n").find((l) => /^terminal:\s*/.test(l));
@@ -25103,7 +25266,7 @@ var terminalChecks = [
25103
25266
  // src/utils/doctor/checks/skills.ts
25104
25267
  init_fs();
25105
25268
  import { resolve as resolve60, join as join7 } from "path";
25106
- import { readdir as readdir23, readFile as readFile37, lstat as lstat4 } from "fs/promises";
25269
+ import { readdir as readdir22, readFile as readFile36, lstat as lstat4 } from "fs/promises";
25107
25270
  import { homedir as homedir8 } from "os";
25108
25271
  var CATEGORY10 = "skills";
25109
25272
  var skillTargets = [
@@ -25123,7 +25286,7 @@ var skillsDedupCheck = {
25123
25286
  const present = [];
25124
25287
  let entries;
25125
25288
  try {
25126
- entries = await readdir23(dir, { withFileTypes: true });
25289
+ entries = await readdir22(dir, { withFileTypes: true });
25127
25290
  } catch {
25128
25291
  continue;
25129
25292
  }
@@ -25132,7 +25295,7 @@ var skillsDedupCheck = {
25132
25295
  if (!KNOWN_SKILLS.includes(entry.name)) continue;
25133
25296
  const skillMd = join7(dir, entry.name, "SKILL.md");
25134
25297
  if (!await fileExists(skillMd)) continue;
25135
- const content = await readFile37(skillMd, "utf-8").catch(() => "");
25298
+ const content = await readFile36(skillMd, "utf-8").catch(() => "");
25136
25299
  const match = content.match(/^name:\s*(\S+)\s*$/m);
25137
25300
  if (!match || match[1] !== entry.name) continue;
25138
25301
  let isSymlink2 = false;
@@ -25184,7 +25347,7 @@ var skillsChecks = [skillsDedupCheck];
25184
25347
  init_fs();
25185
25348
  init_paths();
25186
25349
  import { resolve as resolve61 } from "path";
25187
- import { readdir as readdir24 } from "fs/promises";
25350
+ import { readdir as readdir23 } from "fs/promises";
25188
25351
  import { spawnSync as spawnSync9 } from "child_process";
25189
25352
  init_parser2();
25190
25353
  var CATEGORY11 = "bundles";
@@ -25192,7 +25355,7 @@ async function listScopes(ctx) {
25192
25355
  const out = [];
25193
25356
  const td = todosDir();
25194
25357
  if (await fileExists(td)) {
25195
- const entries = await readdir24(td).catch(() => []);
25358
+ const entries = await readdir23(td).catch(() => []);
25196
25359
  for (const f of entries) {
25197
25360
  if (typeof f !== "string") continue;
25198
25361
  if (!f.endsWith(".md") || f.endsWith("-log.md")) continue;
@@ -25208,7 +25371,7 @@ async function listScopes(ctx) {
25208
25371
  }
25209
25372
  }
25210
25373
  if (await fileExists(ctx.config.defaultProjectDir)) {
25211
- const projectEntries = await readdir24(ctx.config.defaultProjectDir, { withFileTypes: true }).catch(() => []);
25374
+ const projectEntries = await readdir23(ctx.config.defaultProjectDir, { withFileTypes: true }).catch(() => []);
25212
25375
  for (const e of projectEntries) {
25213
25376
  if (!e.isDirectory()) continue;
25214
25377
  const slug = e.name;
@@ -25504,7 +25667,7 @@ async function readVersion() {
25504
25667
  let dir = dirname17(here);
25505
25668
  for (let i = 0; i < 6; i++) {
25506
25669
  try {
25507
- const raw = await readFile38(join8(dir, "package.json"), "utf-8");
25670
+ const raw = await readFile37(join8(dir, "package.json"), "utf-8");
25508
25671
  const parsed = JSON.parse(raw);
25509
25672
  return typeof parsed.version === "string" ? parsed.version : null;
25510
25673
  } catch {
@@ -25614,7 +25777,7 @@ async function validateAssignmentFile(inputPath, cwd = process.cwd()) {
25614
25777
  }
25615
25778
  let content;
25616
25779
  try {
25617
- content = await readFile39(absolute, "utf-8");
25780
+ content = await readFile38(absolute, "utf-8");
25618
25781
  } catch (err2) {
25619
25782
  return {
25620
25783
  ok: false,
@@ -25939,7 +26102,7 @@ init_timestamp();
25939
26102
  init_assignment_resolver();
25940
26103
  init_templates();
25941
26104
  import { resolve as resolve63 } from "path";
25942
- import { readFile as readFile40 } from "fs/promises";
26105
+ import { readFile as readFile39 } from "fs/promises";
25943
26106
  function shortId() {
25944
26107
  return generateId().split("-")[0];
25945
26108
  }
@@ -25985,7 +26148,7 @@ async function commentCommand(target, text, options = {}) {
25985
26148
  let currentContent;
25986
26149
  let currentCount = 0;
25987
26150
  if (await fileExists(commentsPath)) {
25988
- currentContent = await readFile40(commentsPath, "utf-8");
26151
+ currentContent = await readFile39(commentsPath, "utf-8");
25989
26152
  const countMatch = currentContent.match(/^entryCount:\s*(\d+)/m);
25990
26153
  if (countMatch) currentCount = parseInt(countMatch[1], 10);
25991
26154
  } else {
@@ -26031,7 +26194,7 @@ init_slug();
26031
26194
  init_assignment_resolver();
26032
26195
  init_parser();
26033
26196
  import { resolve as resolve64 } from "path";
26034
- import { readFile as readFile41 } from "fs/promises";
26197
+ import { readFile as readFile40 } from "fs/promises";
26035
26198
  var AssignmentTargetError = class extends Error {
26036
26199
  };
26037
26200
  function classifyContext(ctx) {
@@ -26046,7 +26209,7 @@ async function readAssignmentFrontmatterId(assignmentDir) {
26046
26209
  const path = resolve64(assignmentDir, "assignment.md");
26047
26210
  if (!await fileExists(path)) return null;
26048
26211
  try {
26049
- const content = await readFile41(path, "utf-8");
26212
+ const content = await readFile40(path, "utf-8");
26050
26213
  const [fm] = extractFrontmatter(content);
26051
26214
  return getField(fm, "id");
26052
26215
  } catch {
@@ -26057,7 +26220,7 @@ async function readContextJson(cwd) {
26057
26220
  const path = resolve64(cwd, ".syntaur", "context.json");
26058
26221
  if (!await fileExists(path)) return null;
26059
26222
  try {
26060
- const raw = await readFile41(path, "utf-8");
26223
+ const raw = await readFile40(path, "utf-8");
26061
26224
  return JSON.parse(raw);
26062
26225
  } catch {
26063
26226
  return null;
@@ -26290,7 +26453,7 @@ async function captureScreenshot(mode) {
26290
26453
 
26291
26454
  // src/utils/asciinema.ts
26292
26455
  import { spawn as spawn6 } from "child_process";
26293
- import { mkdtemp as mkdtemp3, readFile as readFile42, rm as rm9 } from "fs/promises";
26456
+ import { mkdtemp as mkdtemp3, readFile as readFile41, rm as rm9 } from "fs/promises";
26294
26457
  import { tmpdir as tmpdir4 } from "os";
26295
26458
  import { join as join10 } from "path";
26296
26459
  var SAFE_RE = /^[A-Za-z0-9_@%+=:,./-]+$/;
@@ -26353,7 +26516,7 @@ async function captureAsciinema(opts) {
26353
26516
  }
26354
26517
  throw err2;
26355
26518
  }
26356
- const text = await readFile42(castPath, "utf8").catch(() => null);
26519
+ const text = await readFile41(castPath, "utf8").catch(() => null);
26357
26520
  if (text === null) {
26358
26521
  throw new Error(
26359
26522
  `asciinema produced no cast file at ${castPath} (exit ${exitCode}). Try running 'asciinema rec ${castPath}' directly to diagnose.`
@@ -26381,7 +26544,7 @@ async function captureAsciinema(opts) {
26381
26544
  // src/utils/recording.ts
26382
26545
  init_paths();
26383
26546
  import { spawn as spawn7 } from "child_process";
26384
- import { mkdir as mkdir8, mkdtemp as mkdtemp4, open as open3, readFile as readFile43, rm as rm10, stat as stat7, unlink as unlink9, writeFile as writeFile12 } from "fs/promises";
26547
+ import { mkdir as mkdir8, mkdtemp as mkdtemp4, open as open3, readFile as readFile42, rm as rm10, stat as stat7, unlink as unlink9, writeFile as writeFile12 } from "fs/promises";
26385
26548
  import { tmpdir as tmpdir5 } from "os";
26386
26549
  import { join as join11, resolve as resolve65 } from "path";
26387
26550
  import { setTimeout as sleep } from "timers/promises";
@@ -26455,7 +26618,7 @@ async function acquirePidfile(pidfile) {
26455
26618
  } catch (err2) {
26456
26619
  if (err2.code !== "EEXIST") throw err2;
26457
26620
  if (attempt === 1) throw err2;
26458
- const existing = (await readFile43(pidfile, "utf-8").catch(() => "")).trim();
26621
+ const existing = (await readFile42(pidfile, "utf-8").catch(() => "")).trim();
26459
26622
  if (existing.startsWith(STARTING_SENTINEL_PREFIX)) {
26460
26623
  const parentPidRaw = existing.slice(STARTING_SENTINEL_PREFIX.length);
26461
26624
  const parentPid = Number.parseInt(parentPidRaw, 10);
@@ -26557,7 +26720,7 @@ async function startRecording(input4) {
26557
26720
  logHandle = null;
26558
26721
  if (warmupMs > 0) await sleep(warmupMs);
26559
26722
  if (!await isProcessAlive(pid)) {
26560
- const tail = await readFile43(log, "utf-8").then((s) => s.split("\n").slice(-20).join("\n")).catch(() => "");
26723
+ const tail = await readFile42(log, "utf-8").then((s) => s.split("\n").slice(-20).join("\n")).catch(() => "");
26561
26724
  acquiredPid = null;
26562
26725
  throw new Error(
26563
26726
  `ffmpeg exited during startup \u2014 likely macOS Screen Recording permission missing. Grant access to your terminal in System Settings \u2192 Privacy & Security \u2192 Screen Recording, then retry. Log: ${log}
@@ -26614,7 +26777,7 @@ ${tail}`
26614
26777
  async function stopRecording() {
26615
26778
  const pidfile = pidfilePath();
26616
26779
  const sidecar = sidecarPath();
26617
- const pidRaw = await readFile43(pidfile, "utf-8").catch(() => null);
26780
+ const pidRaw = await readFile42(pidfile, "utf-8").catch(() => null);
26618
26781
  if (pidRaw === null) {
26619
26782
  throw new Error(
26620
26783
  `No active recording found (no pidfile at ${pidfile}). Did you run --start?`
@@ -26624,7 +26787,7 @@ async function stopRecording() {
26624
26787
  if (!Number.isInteger(pid) || pid <= 0) {
26625
26788
  throw new Error(`Pidfile at ${pidfile} is corrupt (got "${pidRaw}").`);
26626
26789
  }
26627
- const sidecarRaw = await readFile43(sidecar, "utf-8").catch(() => null);
26790
+ const sidecarRaw = await readFile42(sidecar, "utf-8").catch(() => null);
26628
26791
  if (sidecarRaw === null) {
26629
26792
  throw new Error(
26630
26793
  `No recording sidecar at ${sidecar}. The recording state is inconsistent \u2014 delete ${pidfile} and re-run --start.`
@@ -26757,7 +26920,7 @@ function listArtifactsByAssignment(assignmentId) {
26757
26920
 
26758
26921
  // src/utils/transcribers/elevenlabs.ts
26759
26922
  import { spawn as spawn8 } from "child_process";
26760
- import { mkdtemp as mkdtemp5, readFile as readFile44, rm as rm11 } from "fs/promises";
26923
+ import { mkdtemp as mkdtemp5, readFile as readFile43, rm as rm11 } from "fs/promises";
26761
26924
  import { tmpdir as tmpdir6 } from "os";
26762
26925
  import { join as join12 } from "path";
26763
26926
  var SCRIBE_URL = "https://api.elevenlabs.io/v1/speech-to-text";
@@ -26821,7 +26984,7 @@ async function extractAudio(videoAbsPath, wavOut) {
26821
26984
  throw new TranscribeFfmpegError(`ffmpeg failed (exit ${result.code}): ${tail}`);
26822
26985
  }
26823
26986
  async function callScribe(wavPath, apiKey, opts) {
26824
- const audio = await readFile44(wavPath);
26987
+ const audio = await readFile43(wavPath);
26825
26988
  const form = new FormData();
26826
26989
  form.set("file", new Blob([new Uint8Array(audio)], { type: "audio/wav" }), "audio.wav");
26827
26990
  form.set("model_id", "scribe_v1");
@@ -27286,7 +27449,7 @@ async function captureCommand(target, options = {}) {
27286
27449
 
27287
27450
  // src/commands/proof.ts
27288
27451
  import { Command as Command6 } from "commander";
27289
- import { readFile as readFile45, writeFile as writeFile14, rename as rename9, stat as stat9 } from "fs/promises";
27452
+ import { readFile as readFile44, writeFile as writeFile14, rename as rename9, stat as stat9 } from "fs/promises";
27290
27453
  import { resolve as resolve68, relative as relative5, isAbsolute as isAbsolute11, dirname as dirname19 } from "path";
27291
27454
  import { randomBytes as randomBytes4 } from "crypto";
27292
27455
 
@@ -27531,7 +27694,7 @@ async function readAssignmentMeta(assignmentDir) {
27531
27694
  if (!await fileExists(path)) {
27532
27695
  return { title: "", body: "" };
27533
27696
  }
27534
- const content = await readFile45(path, "utf-8");
27697
+ const content = await readFile44(path, "utf-8");
27535
27698
  const fmMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?/);
27536
27699
  let title = "";
27537
27700
  if (fmMatch) {
@@ -27593,7 +27756,7 @@ async function loadInlineFiles(rows, assignmentDir) {
27593
27756
  continue;
27594
27757
  }
27595
27758
  try {
27596
- out.set(r.file_path, await readFile45(abs, "utf-8"));
27759
+ out.set(r.file_path, await readFile44(abs, "utf-8"));
27597
27760
  } catch {
27598
27761
  out.set(r.file_path, null);
27599
27762
  }
@@ -27612,7 +27775,7 @@ async function loadTranscriptSidecars(rows, assignmentDir) {
27612
27775
  const st = await stat9(sidecar);
27613
27776
  if (st.size > INLINE_TEXT_LIMIT_BYTES) continue;
27614
27777
  try {
27615
- out.set(r.id, await readFile45(sidecar, "utf-8"));
27778
+ out.set(r.id, await readFile44(sidecar, "utf-8"));
27616
27779
  } catch {
27617
27780
  }
27618
27781
  }
@@ -27791,13 +27954,13 @@ leaseCommand.command("claim").description("Claim an idle member of an inventory.
27791
27954
  process.exit(1);
27792
27955
  return;
27793
27956
  }
27794
- const delay = Math.min(
27957
+ const delay2 = Math.min(
27795
27958
  backoffSchedule[Math.min(attempt, backoffSchedule.length - 1)] ?? 1e3,
27796
27959
  1e3,
27797
27960
  remaining
27798
27961
  );
27799
27962
  attempt += 1;
27800
- await new Promise((r) => setTimeout(r, delay));
27963
+ await new Promise((r) => setTimeout(r, delay2));
27801
27964
  }
27802
27965
  }
27803
27966
  }
@@ -28370,7 +28533,7 @@ function isoToCcusageDate(iso) {
28370
28533
 
28371
28534
  // src/usage/cwd-extractor.ts
28372
28535
  init_paths();
28373
- import { open as open4, readdir as readdir25, stat as stat10 } from "fs/promises";
28536
+ import { open as open4, readdir as readdir24, stat as stat10 } from "fs/promises";
28374
28537
  import { join as join13 } from "path";
28375
28538
  import { homedir as homedir9 } from "os";
28376
28539
  var SCAN_LINE_CAP = 50;
@@ -28493,7 +28656,7 @@ function resolveCodexSessionsRoot(override) {
28493
28656
  }
28494
28657
  async function listDirSafe(path) {
28495
28658
  try {
28496
- const entries = await readdir25(path, { withFileTypes: true });
28659
+ const entries = await readdir24(path, { withFileTypes: true });
28497
28660
  return entries.map((e) => ({
28498
28661
  name: e.name,
28499
28662
  isFile: e.isFile(),
@@ -28849,7 +29012,7 @@ init_timestamp();
28849
29012
  init_assignment_resolver();
28850
29013
  init_assignment_todos();
28851
29014
  import { resolve as resolve69 } from "path";
28852
- import { readFile as readFile46 } from "fs/promises";
29015
+ import { readFile as readFile45 } from "fs/promises";
28853
29016
  async function requestCommand(target, text, options = {}) {
28854
29017
  if (!text || !text.trim()) {
28855
29018
  throw new Error("Request text cannot be empty.");
@@ -28880,7 +29043,7 @@ async function requestCommand(target, text, options = {}) {
28880
29043
  throw new Error(`assignment.md not found at ${assignmentMdPath2}`);
28881
29044
  }
28882
29045
  const source = options.from ?? process.env.SYNTAUR_ASSIGNMENT ?? "unknown";
28883
- let content = await readFile46(assignmentMdPath2, "utf-8");
29046
+ let content = await readFile45(assignmentMdPath2, "utf-8");
28884
29047
  content = appendTodosToAssignmentBody(content, [
28885
29048
  { description: `${text.trim()} (from: ${source})` }
28886
29049
  ]);
@@ -28893,13 +29056,13 @@ async function requestCommand(target, text, options = {}) {
28893
29056
  init_fs();
28894
29057
  init_paths();
28895
29058
  import { Command as Command9 } from "commander";
28896
- import { readFile as readFile47, readdir as readdir26 } from "fs/promises";
29059
+ import { readFile as readFile46, readdir as readdir25 } from "fs/promises";
28897
29060
  import { resolve as resolve70 } from "path";
28898
29061
  async function readContextAssignmentDir(cwd) {
28899
29062
  const path = resolve70(cwd, ".syntaur", "context.json");
28900
29063
  if (!await fileExists(path)) return null;
28901
29064
  try {
28902
- const raw = await readFile47(path, "utf-8");
29065
+ const raw = await readFile46(path, "utf-8");
28903
29066
  const ctx = JSON.parse(raw);
28904
29067
  if (typeof ctx.assignmentDir === "string" && ctx.assignmentDir.length > 0) {
28905
29068
  return ctx.assignmentDir;
@@ -28926,7 +29089,7 @@ async function resolveAssignmentDir(opts) {
28926
29089
  var PLAN_PATTERN = /^plan(?:-v(\d+))?\.md$/;
28927
29090
  async function listPlanFiles(assignmentDir) {
28928
29091
  if (!await fileExists(assignmentDir)) return [];
28929
- const entries = await readdir26(assignmentDir, { withFileTypes: true });
29092
+ const entries = await readdir25(assignmentDir, { withFileTypes: true });
28930
29093
  const out = [];
28931
29094
  for (const e of entries) {
28932
29095
  if (!e.isFile()) continue;
@@ -29074,11 +29237,11 @@ async function runPlanVersion(options) {
29074
29237
  if (await fileExists(newPath) && !options.force) {
29075
29238
  throw new Error(`${next.fileName} already exists. Use --force to overwrite.`);
29076
29239
  }
29077
- const assignmentMd = await readFile47(assignmentMdPath2, "utf-8");
29240
+ const assignmentMd = await readFile46(assignmentMdPath2, "utf-8");
29078
29241
  const slugMatch = assignmentMd.match(/^slug:\s*(.+?)\s*$/m);
29079
29242
  const slug = slugMatch ? slugMatch[1].trim() : assignmentDir.split("/").pop() ?? "";
29080
29243
  const oldPlanPath = resolve70(assignmentDir, current.fileName);
29081
- const oldPlanContent = await readFile47(oldPlanPath, "utf-8");
29244
+ const oldPlanContent = await readFile46(oldPlanPath, "utf-8");
29082
29245
  const oldBody = oldPlanContent.replace(/^---[\s\S]*?\n---\n?/, "");
29083
29246
  const carriedTodos = extractUncheckedTodos(oldBody);
29084
29247
  const stub = buildNewPlanStub({
@@ -29115,13 +29278,13 @@ planCommand.command("version").description(
29115
29278
  // src/commands/session.ts
29116
29279
  init_fs();
29117
29280
  import { Command as Command10 } from "commander";
29118
- import { readFile as readFile48, readdir as readdir27, stat as stat11 } from "fs/promises";
29281
+ import { readFile as readFile47, readdir as readdir26, stat as stat11 } from "fs/promises";
29119
29282
  import { resolve as resolve71 } from "path";
29120
29283
  async function readContext(cwd) {
29121
29284
  const path = resolve71(cwd, ".syntaur", "context.json");
29122
29285
  if (!await fileExists(path)) return null;
29123
29286
  try {
29124
- const raw = await readFile48(path, "utf-8");
29287
+ const raw = await readFile47(path, "utf-8");
29125
29288
  return JSON.parse(raw);
29126
29289
  } catch {
29127
29290
  return null;
@@ -29130,7 +29293,7 @@ async function readContext(cwd) {
29130
29293
  async function findLatestSessionSummary(assignmentDir) {
29131
29294
  const sessionsRoot = resolve71(assignmentDir, "sessions");
29132
29295
  if (!await fileExists(sessionsRoot)) return null;
29133
- const entries = await readdir27(sessionsRoot, { withFileTypes: true });
29296
+ const entries = await readdir26(sessionsRoot, { withFileTypes: true });
29134
29297
  let best = null;
29135
29298
  for (const entry of entries) {
29136
29299
  if (!entry.isDirectory()) continue;
@@ -29146,7 +29309,7 @@ async function findLatestSessionSummary(assignmentDir) {
29146
29309
  async function findOpenHandoff(assignmentDir) {
29147
29310
  const handoffPath = resolve71(assignmentDir, "handoff.md");
29148
29311
  if (!await fileExists(handoffPath)) return null;
29149
- const content = await readFile48(handoffPath, "utf-8");
29312
+ const content = await readFile47(handoffPath, "utf-8");
29150
29313
  const body = content.replace(/^---[\s\S]*?\n---\n?/, "").trim();
29151
29314
  if (body.length === 0) return null;
29152
29315
  if (/^<!--[\s\S]*-->$/.test(body)) return null;
@@ -29254,13 +29417,13 @@ init_git_worktree();
29254
29417
  init_fs();
29255
29418
  init_paths();
29256
29419
  import { Command as Command11 } from "commander";
29257
- import { readFile as readFile49 } from "fs/promises";
29420
+ import { readFile as readFile48 } from "fs/promises";
29258
29421
  import { resolve as resolve72 } from "path";
29259
29422
  async function readContext2(cwd) {
29260
29423
  const path = resolve72(cwd, ".syntaur", "context.json");
29261
29424
  if (!await fileExists(path)) return null;
29262
29425
  try {
29263
- return JSON.parse(await readFile49(path, "utf-8"));
29426
+ return JSON.parse(await readFile48(path, "utf-8"));
29264
29427
  } catch {
29265
29428
  return null;
29266
29429
  }
@@ -29332,7 +29495,7 @@ import { resolve as resolve74 } from "path";
29332
29495
  // src/utils/project-indexes.ts
29333
29496
  init_parser();
29334
29497
  init_fs();
29335
- import { readdir as readdir28, readFile as readFile50 } from "fs/promises";
29498
+ import { readdir as readdir27, readFile as readFile49 } from "fs/promises";
29336
29499
  import { resolve as resolve73 } from "path";
29337
29500
  function nowIso3() {
29338
29501
  return (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
@@ -29342,7 +29505,7 @@ function readProjectSlug(projectDir) {
29342
29505
  }
29343
29506
  async function listSlugFiles(dir) {
29344
29507
  if (!await fileExists(dir)) return [];
29345
- const entries = await readdir28(dir, { withFileTypes: true });
29508
+ const entries = await readdir27(dir, { withFileTypes: true });
29346
29509
  return entries.filter((e) => e.isFile() && e.name.endsWith(".md") && !e.name.startsWith("_")).map((e) => e.name).sort();
29347
29510
  }
29348
29511
  function escapeCell(value) {
@@ -29368,7 +29531,7 @@ async function rebuildResourcesIndex(projectDir) {
29368
29531
  lines.push("| Name | Category | Source | Related Assignments | Updated |");
29369
29532
  lines.push("|------|----------|--------|---------------------|---------|");
29370
29533
  for (const fileName of files) {
29371
- const content = await readFile50(resolve73(dir, fileName), "utf-8");
29534
+ const content = await readFile49(resolve73(dir, fileName), "utf-8");
29372
29535
  const parsed = parseResource(content);
29373
29536
  const slugBase = fileName.replace(/\.md$/, "");
29374
29537
  const name = parsed.name || slugBase;
@@ -29399,7 +29562,7 @@ async function rebuildMemoriesIndex(projectDir) {
29399
29562
  lines.push("| Name | Source | Scope | Source Assignment | Updated |");
29400
29563
  lines.push("|------|--------|-------|-------------------|---------|");
29401
29564
  for (const fileName of files) {
29402
- const content = await readFile50(resolve73(dir, fileName), "utf-8");
29565
+ const content = await readFile49(resolve73(dir, fileName), "utf-8");
29403
29566
  const parsed = parseMemory(content);
29404
29567
  const slugBase = fileName.replace(/\.md$/, "");
29405
29568
  const name = parsed.name || slugBase;
@@ -29574,7 +29737,7 @@ init_paths();
29574
29737
  init_fs();
29575
29738
  init_frontmatter();
29576
29739
  import { Command as Command14 } from "commander";
29577
- import { readFile as readFile51 } from "fs/promises";
29740
+ import { readFile as readFile50 } from "fs/promises";
29578
29741
  import { resolve as resolve76 } from "path";
29579
29742
  var AGE_PATTERN = /^(\d+)([dhwm])$/i;
29580
29743
  function parseAgeToCutoff(age) {
@@ -29608,7 +29771,7 @@ async function loadTags(item) {
29608
29771
  const path = assignmentMdPath(item);
29609
29772
  if (!await fileExists(path)) return [];
29610
29773
  try {
29611
- const content = await readFile51(path, "utf-8");
29774
+ const content = await readFile50(path, "utf-8");
29612
29775
  return parseAssignmentFrontmatter(content).tags;
29613
29776
  } catch {
29614
29777
  return [];
@@ -29686,10 +29849,10 @@ var lsCommand = new Command14("ls").description(
29686
29849
 
29687
29850
  // src/cli-default-command.ts
29688
29851
  init_config2();
29689
- import { readdir as readdir29 } from "fs/promises";
29852
+ import { readdir as readdir28 } from "fs/promises";
29690
29853
  async function hasAnyProjectContent(projectsDir2) {
29691
29854
  try {
29692
- const entries = await readdir29(projectsDir2, { withFileTypes: true });
29855
+ const entries = await readdir28(projectsDir2, { withFileTypes: true });
29693
29856
  return entries.some((entry) => entry.isDirectory());
29694
29857
  } catch {
29695
29858
  return false;
@@ -29726,20 +29889,20 @@ async function getDefaultCommandName() {
29726
29889
  init_paths();
29727
29890
  init_fs();
29728
29891
  import { fileURLToPath as fileURLToPath10 } from "url";
29729
- import { readFile as readFile53 } from "fs/promises";
29892
+ import { readFile as readFile52 } from "fs/promises";
29730
29893
  import { dirname as dirname21, join as join15, resolve as resolve77 } from "path";
29731
29894
  import { spawn as spawn10 } from "child_process";
29732
29895
  import { createInterface as createInterface2 } from "readline/promises";
29733
29896
 
29734
29897
  // src/utils/version.ts
29735
29898
  import { fileURLToPath as fileURLToPath9 } from "url";
29736
- import { readFile as readFile52 } from "fs/promises";
29899
+ import { readFile as readFile51 } from "fs/promises";
29737
29900
  import { dirname as dirname20, join as join14 } from "path";
29738
29901
  async function readPackageVersion(scriptUrl) {
29739
29902
  try {
29740
29903
  const scriptPath = fileURLToPath9(scriptUrl);
29741
29904
  const pkgRoot = dirname20(dirname20(scriptPath));
29742
- const raw = await readFile52(join14(pkgRoot, "package.json"), "utf-8");
29905
+ const raw = await readFile51(join14(pkgRoot, "package.json"), "utf-8");
29743
29906
  const parsed = JSON.parse(raw);
29744
29907
  return typeof parsed.version === "string" ? parsed.version : null;
29745
29908
  } catch {
@@ -29769,7 +29932,7 @@ function isRunningViaNpx(scriptUrl) {
29769
29932
  async function readState() {
29770
29933
  if (!await fileExists(STATE_FILE)) return null;
29771
29934
  try {
29772
- const raw = await readFile53(STATE_FILE, "utf-8");
29935
+ const raw = await readFile52(STATE_FILE, "utf-8");
29773
29936
  return JSON.parse(raw);
29774
29937
  } catch {
29775
29938
  return null;
@@ -29828,7 +29991,7 @@ async function readGlobalVersion() {
29828
29991
  try {
29829
29992
  const manifestPath = join15(rootPath, "syntaur", "package.json");
29830
29993
  if (!await fileExists(manifestPath)) return null;
29831
- const raw = await readFile53(manifestPath, "utf-8");
29994
+ const raw = await readFile52(manifestPath, "utf-8");
29832
29995
  const parsed = JSON.parse(raw);
29833
29996
  return typeof parsed.version === "string" ? parsed.version : null;
29834
29997
  } catch {