syntaur 0.16.0 → 0.16.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 (65) hide show
  1. package/dashboard/dist/assets/{_basePickBy-B1S22H85.js → _basePickBy-DTuHiZCP.js} +1 -1
  2. package/dashboard/dist/assets/{_baseUniq-CJIRCCsh.js → _baseUniq-BjbISCNN.js} +1 -1
  3. package/dashboard/dist/assets/{arc-CdlNYTcd.js → arc-D9mCa8If.js} +1 -1
  4. package/dashboard/dist/assets/{architectureDiagram-2XIMDMQ5-KVI0HWOy.js → architectureDiagram-2XIMDMQ5-CLWheiZu.js} +1 -1
  5. package/dashboard/dist/assets/{blockDiagram-WCTKOSBZ-ClnG7gaq.js → blockDiagram-WCTKOSBZ-BBxrVTWB.js} +1 -1
  6. package/dashboard/dist/assets/{c4Diagram-IC4MRINW-BD9lkttL.js → c4Diagram-IC4MRINW-DnhDZ2W3.js} +1 -1
  7. package/dashboard/dist/assets/channel-CN5VmjNa.js +1 -0
  8. package/dashboard/dist/assets/{chunk-4BX2VUAB-BM3DXZMG.js → chunk-4BX2VUAB-DeAfVeDa.js} +1 -1
  9. package/dashboard/dist/assets/{chunk-55IACEB6-DUIbFJ3r.js → chunk-55IACEB6-cKEeGDNI.js} +1 -1
  10. package/dashboard/dist/assets/{chunk-FMBD7UC4-B_cxCRkA.js → chunk-FMBD7UC4-7RuZ6HEq.js} +1 -1
  11. package/dashboard/dist/assets/{chunk-JSJVCQXG-2mLf1WoM.js → chunk-JSJVCQXG-B5dwG0bv.js} +1 -1
  12. package/dashboard/dist/assets/{chunk-KX2RTZJC-BMz3ND2R.js → chunk-KX2RTZJC-DeyQYDVj.js} +1 -1
  13. package/dashboard/dist/assets/{chunk-NQ4KR5QH-DiCxpw3L.js → chunk-NQ4KR5QH-NYo4hSqA.js} +1 -1
  14. package/dashboard/dist/assets/{chunk-QZHKN3VN-BcaWPzd5.js → chunk-QZHKN3VN-CtJFICF8.js} +1 -1
  15. package/dashboard/dist/assets/{chunk-WL4C6EOR-BbsfhS6J.js → chunk-WL4C6EOR-DZ3WYdab.js} +1 -1
  16. package/dashboard/dist/assets/classDiagram-VBA2DB6C-CqLb9GuU.js +1 -0
  17. package/dashboard/dist/assets/classDiagram-v2-RAHNMMFH-CqLb9GuU.js +1 -0
  18. package/dashboard/dist/assets/clone-DwvrjCQs.js +1 -0
  19. package/dashboard/dist/assets/{cose-bilkent-S5V4N54A-iuQzyyrA.js → cose-bilkent-S5V4N54A-DaYOsf-M.js} +1 -1
  20. package/dashboard/dist/assets/{dagre-KLK3FWXG-C0TB1zg5.js → dagre-KLK3FWXG-DzLc7ykF.js} +1 -1
  21. package/dashboard/dist/assets/{diagram-E7M64L7V-Ck8tvxP2.js → diagram-E7M64L7V-kpjEdOqb.js} +1 -1
  22. package/dashboard/dist/assets/{diagram-IFDJBPK2-rQKIi-sc.js → diagram-IFDJBPK2-D3EHoh6j.js} +1 -1
  23. package/dashboard/dist/assets/{diagram-P4PSJMXO-DS1RGSgx.js → diagram-P4PSJMXO-Cp6Un2ys.js} +1 -1
  24. package/dashboard/dist/assets/{erDiagram-INFDFZHY-BqWCybWW.js → erDiagram-INFDFZHY-DDjOUPWk.js} +1 -1
  25. package/dashboard/dist/assets/{flowDiagram-PKNHOUZH-BCoQtyQ5.js → flowDiagram-PKNHOUZH-CfO51xge.js} +1 -1
  26. package/dashboard/dist/assets/{ganttDiagram-A5KZAMGK-CD1BmicP.js → ganttDiagram-A5KZAMGK-BDzAtkcp.js} +1 -1
  27. package/dashboard/dist/assets/{gitGraphDiagram-K3NZZRJ6-tGDYOZuO.js → gitGraphDiagram-K3NZZRJ6-JPMFN2zF.js} +1 -1
  28. package/dashboard/dist/assets/{graph-BT0s-094.js → graph-DXDryilu.js} +1 -1
  29. package/dashboard/dist/assets/index-D7UtkCYM.js +515 -0
  30. package/dashboard/dist/assets/{index-D2Yoi6DJ.css → index-DTXSWSzH.css} +1 -1
  31. package/dashboard/dist/assets/{infoDiagram-LFFYTUFH-E4dVb8BD.js → infoDiagram-LFFYTUFH-CYi5kkvz.js} +1 -1
  32. package/dashboard/dist/assets/{ishikawaDiagram-PHBUUO56-CubuM_ev.js → ishikawaDiagram-PHBUUO56-DQl_IUe8.js} +1 -1
  33. package/dashboard/dist/assets/{journeyDiagram-4ABVD52K-DfyJbgMF.js → journeyDiagram-4ABVD52K-BXXGPcrS.js} +1 -1
  34. package/dashboard/dist/assets/{kanban-definition-K7BYSVSG-BXamajyZ.js → kanban-definition-K7BYSVSG-BqJestUY.js} +1 -1
  35. package/dashboard/dist/assets/{layout-6b_SlWD_.js → layout-D5VdYmWn.js} +1 -1
  36. package/dashboard/dist/assets/{linear-BE0O-66A.js → linear-dZA_O_GN.js} +1 -1
  37. package/dashboard/dist/assets/{mermaid.core-DM3qWr07.js → mermaid.core-B0Ixd1yP.js} +4 -4
  38. package/dashboard/dist/assets/{mindmap-definition-YRQLILUH-puOtERrG.js → mindmap-definition-YRQLILUH-CSJYdSMG.js} +1 -1
  39. package/dashboard/dist/assets/{pieDiagram-SKSYHLDU-cE6l8UBX.js → pieDiagram-SKSYHLDU-DmYrRZHN.js} +1 -1
  40. package/dashboard/dist/assets/{quadrantDiagram-337W2JSQ-CgDAyZQP.js → quadrantDiagram-337W2JSQ-La3ce5kE.js} +1 -1
  41. package/dashboard/dist/assets/{requirementDiagram-Z7DCOOCP-CJrai_8U.js → requirementDiagram-Z7DCOOCP-DPitIZQl.js} +1 -1
  42. package/dashboard/dist/assets/{sankeyDiagram-WA2Y5GQK-Bf72l7UW.js → sankeyDiagram-WA2Y5GQK-CAmCqGkr.js} +1 -1
  43. package/dashboard/dist/assets/{sequenceDiagram-2WXFIKYE-0D8RN7ds.js → sequenceDiagram-2WXFIKYE-6lEzNTWG.js} +1 -1
  44. package/dashboard/dist/assets/{stateDiagram-RAJIS63D-B8LucUIt.js → stateDiagram-RAJIS63D-DyGKCS2C.js} +1 -1
  45. package/dashboard/dist/assets/stateDiagram-v2-FVOUBMTO-BOmRICoY.js +1 -0
  46. package/dashboard/dist/assets/{timeline-definition-YZTLITO2-DL3Hq8ii.js → timeline-definition-YZTLITO2-D9RxQE3j.js} +1 -1
  47. package/dashboard/dist/assets/{treemap-KZPCXAKY-wF1X4_uI.js → treemap-KZPCXAKY-TuXbGNN4.js} +1 -1
  48. package/dashboard/dist/assets/{vennDiagram-LZ73GAT5-DDAjoGTZ.js → vennDiagram-LZ73GAT5-Bly5knr-.js} +1 -1
  49. package/dashboard/dist/assets/{xychartDiagram-JWTSCODW-T-YmzRW8.js → xychartDiagram-JWTSCODW-CA-5z7-4.js} +1 -1
  50. package/dashboard/dist/index.html +2 -2
  51. package/dist/dashboard/server.js +820 -166
  52. package/dist/dashboard/server.js.map +1 -1
  53. package/dist/index.js +1244 -774
  54. package/dist/index.js.map +1 -1
  55. package/dist/launch/index.d.ts +20 -2
  56. package/dist/launch/index.js +67 -20
  57. package/dist/launch/index.js.map +1 -1
  58. package/package.json +1 -1
  59. package/scripts/install-macos-url-handler.mjs +40 -7
  60. package/dashboard/dist/assets/channel-Cm_FzKJq.js +0 -1
  61. package/dashboard/dist/assets/classDiagram-VBA2DB6C-Bdan2Dp5.js +0 -1
  62. package/dashboard/dist/assets/classDiagram-v2-RAHNMMFH-Bdan2Dp5.js +0 -1
  63. package/dashboard/dist/assets/clone-9PoPEM_s.js +0 -1
  64. package/dashboard/dist/assets/index-BdSkANCC.js +0 -510
  65. package/dashboard/dist/assets/stateDiagram-v2-FVOUBMTO-D0dc7wxQ.js +0 -1
package/dist/index.js CHANGED
@@ -131,7 +131,6 @@ backup:
131
131
  categories: projects, playbooks, todos, servers, config
132
132
  lastBackup: null
133
133
  lastRestore: null
134
- terminal: terminal-app
135
134
  ---
136
135
 
137
136
  # Syntaur Configuration
@@ -193,6 +192,12 @@ function parseListField(frontmatter, fieldName) {
193
192
  }
194
193
  return results;
195
194
  }
195
+ function unquoteYamlString(value) {
196
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
197
+ return value.slice(1, -1);
198
+ }
199
+ return value;
200
+ }
196
201
  function parseProject(fileContent) {
197
202
  const [fm, body] = extractFrontmatter(fileContent);
198
203
  const slug = getField(fm, "slug") ?? getField(fm, "mission") ?? "";
@@ -208,6 +213,7 @@ function parseProject(fileContent) {
208
213
  updated: getField(fm, "updated") ?? "",
209
214
  tags: parseListField(fm, "tags"),
210
215
  workspace: getField(fm, "workspace"),
216
+ repositories: parseListField(fm, "repositories").map(unquoteYamlString),
211
217
  externalIds: parseExternalIds(fm),
212
218
  body
213
219
  };
@@ -700,6 +706,22 @@ var init_agents_schema = __esm({
700
706
  }
701
707
  });
702
708
 
709
+ // src/utils/terminal-schema.ts
710
+ var TERMINAL_CHOICES;
711
+ var init_terminal_schema = __esm({
712
+ "src/utils/terminal-schema.ts"() {
713
+ "use strict";
714
+ TERMINAL_CHOICES = [
715
+ "terminal-app",
716
+ "iterm",
717
+ "ghostty",
718
+ "alacritty",
719
+ "warp",
720
+ "kitty"
721
+ ];
722
+ }
723
+ });
724
+
703
725
  // src/utils/config.ts
704
726
  var config_exports = {};
705
727
  __export(config_exports, {
@@ -713,6 +735,7 @@ __export(config_exports, {
713
735
  deleteAgentsConfig: () => deleteAgentsConfig,
714
736
  deleteHotkeyBindingsConfig: () => deleteHotkeyBindingsConfig,
715
737
  deleteStatusConfig: () => deleteStatusConfig,
738
+ deleteTerminalConfig: () => deleteTerminalConfig,
716
739
  deleteThemeConfig: () => deleteThemeConfig,
717
740
  getAgents: () => getAgents,
718
741
  getAssignmentTypes: () => getAssignmentTypes,
@@ -729,9 +752,11 @@ __export(config_exports, {
729
752
  writeAgentsConfig: () => writeAgentsConfig,
730
753
  writeHotkeyBindingsConfig: () => writeHotkeyBindingsConfig,
731
754
  writeStatusConfig: () => writeStatusConfig,
755
+ writeTerminalConfig: () => writeTerminalConfig,
732
756
  writeThemeConfig: () => writeThemeConfig
733
757
  });
734
758
  import { readFile as readFile2 } from "fs/promises";
759
+ import { spawnSync } from "child_process";
735
760
  import { resolve as resolve3, isAbsolute } from "path";
736
761
  function parseAgentCommand(value, agentId) {
737
762
  if (typeof value !== "string" || value.trim() === "") {
@@ -1144,6 +1169,52 @@ ${cleanedFm}
1144
1169
  ---${afterFrontmatter}`;
1145
1170
  await writeFileForce(configPath, newContent);
1146
1171
  }
1172
+ function stripTopLevelScalar(fmBlock, key) {
1173
+ const lines = fmBlock.split("\n");
1174
+ const keyRegex = new RegExp(`^${key}:\\s*\\S`);
1175
+ const filtered = lines.filter((line) => !keyRegex.test(line));
1176
+ return filtered.join("\n").replace(/\n+$/, "");
1177
+ }
1178
+ async function writeTerminalConfig(terminal) {
1179
+ const configPath = resolve3(syntaurRoot(), "config.md");
1180
+ const terminalLine = `terminal: ${terminal}`;
1181
+ const existing = await fileExists(configPath) ? await readFile2(configPath, "utf-8") : renderConfig({ defaultProjectDir: defaultProjectDir() });
1182
+ const fmMatch = existing.match(/^(---\n)([\s\S]*?)\n(---)/);
1183
+ if (!fmMatch) {
1184
+ const content = `---
1185
+ version: "2.0"
1186
+ defaultProjectDir: ${defaultProjectDir()}
1187
+ ${terminalLine}
1188
+ ---
1189
+ ${existing}`;
1190
+ await writeFileForce(configPath, content);
1191
+ return;
1192
+ }
1193
+ const fmBlock = fmMatch[2];
1194
+ const afterFrontmatter = existing.slice(fmMatch[0].length);
1195
+ const cleanedFm = stripTopLevelScalar(fmBlock, "terminal");
1196
+ const newFm = `${cleanedFm}
1197
+ ${terminalLine}`.replace(/^\n+/, "");
1198
+ const normalizedFm = newFm.replace(/\n+$/, "");
1199
+ const newContent = `---
1200
+ ${normalizedFm}
1201
+ ---${afterFrontmatter}`;
1202
+ await writeFileForce(configPath, newContent);
1203
+ }
1204
+ async function deleteTerminalConfig() {
1205
+ const configPath = resolve3(syntaurRoot(), "config.md");
1206
+ if (!await fileExists(configPath)) return;
1207
+ const existing = await readFile2(configPath, "utf-8");
1208
+ const fmMatch = existing.match(/^(---\n)([\s\S]*?)\n(---)/);
1209
+ if (!fmMatch) return;
1210
+ const fmBlock = fmMatch[2];
1211
+ const afterFrontmatter = existing.slice(fmMatch[0].length);
1212
+ const cleanedFm = stripTopLevelScalar(fmBlock, "terminal");
1213
+ const newContent = `---
1214
+ ${cleanedFm}
1215
+ ---${afterFrontmatter}`;
1216
+ await writeFileForce(configPath, newContent);
1217
+ }
1147
1218
  function parseHotkeyBindingsConfig(content) {
1148
1219
  const match = content.match(/^---\n([\s\S]*?)\n---/);
1149
1220
  if (!match) return null;
@@ -1839,7 +1910,18 @@ function parseTerminalConfig(value) {
1839
1910
  return trimmed;
1840
1911
  }
1841
1912
  function getTerminal(config) {
1842
- return config.terminal ?? "terminal-app";
1913
+ if (config.terminal) return config.terminal;
1914
+ if (process.platform === "darwin") return "terminal-app";
1915
+ if (process.platform === "linux") {
1916
+ const order = ["kitty", "alacritty", "warp"];
1917
+ for (const candidate of order) {
1918
+ const result = spawnSync("which", [candidate], { encoding: "utf-8" });
1919
+ if (result.status === 0 && result.stdout.trim().length > 0) {
1920
+ return candidate;
1921
+ }
1922
+ }
1923
+ }
1924
+ return "terminal-app";
1843
1925
  }
1844
1926
  async function updateAgentsConfig(mutation, options = {}) {
1845
1927
  const config = await readConfig();
@@ -1852,7 +1934,7 @@ async function updateAgentsConfig(mutation, options = {}) {
1852
1934
  await writeAgentsConfig(next);
1853
1935
  return { previous, next, written: true };
1854
1936
  }
1855
- var DEFAULT_ASSIGNMENT_TYPES, TERMINAL_CHOICES, DEFAULT_CONFIG, AUTO_CREATE_WORKTREE_VALUES, AgentConfigError, KNOWN_AGENT_SCALAR_FIELDS, migratedConfigPaths, TerminalConfigError;
1937
+ var DEFAULT_ASSIGNMENT_TYPES, DEFAULT_CONFIG, AUTO_CREATE_WORKTREE_VALUES, AgentConfigError, KNOWN_AGENT_SCALAR_FIELDS, migratedConfigPaths, TerminalConfigError;
1856
1938
  var init_config2 = __esm({
1857
1939
  "src/utils/config.ts"() {
1858
1940
  "use strict";
@@ -1862,6 +1944,7 @@ var init_config2 = __esm({
1862
1944
  init_fs_migration();
1863
1945
  init_hotkeysCatalog();
1864
1946
  init_agents_schema();
1947
+ init_terminal_schema();
1865
1948
  DEFAULT_ASSIGNMENT_TYPES = {
1866
1949
  definitions: [
1867
1950
  { id: "feature", label: "Feature" },
@@ -1872,14 +1955,6 @@ var init_config2 = __esm({
1872
1955
  ],
1873
1956
  default: "feature"
1874
1957
  };
1875
- TERMINAL_CHOICES = [
1876
- "terminal-app",
1877
- "iterm",
1878
- "ghostty",
1879
- "alacritty",
1880
- "warp",
1881
- "kitty"
1882
- ];
1883
1958
  DEFAULT_CONFIG = {
1884
1959
  version: "2.0",
1885
1960
  defaultProjectDir: defaultProjectDir(),
@@ -2182,10 +2257,17 @@ var init_yaml = __esm({
2182
2257
  });
2183
2258
 
2184
2259
  // src/templates/project.ts
2260
+ function renderRepositoriesBlock(repos) {
2261
+ if (!repos || repos.length === 0) {
2262
+ return "repositories: []";
2263
+ }
2264
+ return ["repositories:", ...repos.map((p) => ` - ${escapeYamlString(p)}`)].join("\n");
2265
+ }
2185
2266
  function renderProject(params2) {
2186
2267
  const safeTitle = escapeYamlString(params2.title);
2187
2268
  const workspaceLine = params2.workspace ? `
2188
2269
  workspace: ${params2.workspace}` : "";
2270
+ const repositoriesBlock = renderRepositoriesBlock(params2.repositories);
2189
2271
  return `---
2190
2272
  id: ${params2.id}
2191
2273
  slug: ${params2.slug}
@@ -2196,7 +2278,8 @@ archivedReason: null
2196
2278
  created: "${params2.timestamp}"
2197
2279
  updated: "${params2.timestamp}"
2198
2280
  externalIds: []
2199
- tags: []${workspaceLine}
2281
+ tags: []
2282
+ ${repositoriesBlock}${workspaceLine}
2200
2283
  ---
2201
2284
 
2202
2285
  # ${params2.title}
@@ -4924,8 +5007,8 @@ async function migrateFromMarkdown(projectsDir2) {
4924
5007
  return allSessions.length;
4925
5008
  }
4926
5009
  async function parseMarkdownSessionsIndex(filePath, projectSlug) {
4927
- const { readFile: readFile49 } = await import("fs/promises");
4928
- const raw = await readFile49(filePath, "utf-8");
5010
+ const { readFile: readFile50 } = await import("fs/promises");
5011
+ const raw = await readFile50(filePath, "utf-8");
4929
5012
  const sessions = [];
4930
5013
  const lines = raw.split("\n");
4931
5014
  let inTable = false;
@@ -6114,7 +6197,8 @@ async function getProjectDetail(projectsDir2, slug) {
6114
6197
  resources,
6115
6198
  memories,
6116
6199
  dependencyGraph,
6117
- workspace: project.workspace
6200
+ workspace: project.workspace,
6201
+ repositories: project.repositories
6118
6202
  };
6119
6203
  }
6120
6204
  async function getAssignmentDetail(projectsDir2, projectSlug, assignmentSlug) {
@@ -7340,6 +7424,114 @@ var init_api = __esm({
7340
7424
  }
7341
7425
  });
7342
7426
 
7427
+ // src/utils/git-worktree.ts
7428
+ var git_worktree_exports = {};
7429
+ __export(git_worktree_exports, {
7430
+ GitWorktreeError: () => GitWorktreeError,
7431
+ createWorktree: () => createWorktree,
7432
+ createWorktreeAndRecord: () => createWorktreeAndRecord,
7433
+ deleteBranch: () => deleteBranch,
7434
+ formatRollbackError: () => formatRollbackError,
7435
+ removeWorktree: () => removeWorktree
7436
+ });
7437
+ import { spawn } from "child_process";
7438
+ import { readFile as readFile13 } from "fs/promises";
7439
+ function run(command, args, cwd) {
7440
+ return new Promise((resolvePromise) => {
7441
+ const child = spawn(command, args, { cwd, stdio: ["ignore", "pipe", "pipe"] });
7442
+ let stdout = "";
7443
+ let stderr = "";
7444
+ child.stdout.on("data", (chunk) => stdout += chunk.toString());
7445
+ child.stderr.on("data", (chunk) => stderr += chunk.toString());
7446
+ child.on("error", (err2) => {
7447
+ resolvePromise({ code: -1, stdout, stderr: stderr + String(err2) });
7448
+ });
7449
+ child.on("close", (code) => {
7450
+ resolvePromise({ code: code ?? -1, stdout, stderr });
7451
+ });
7452
+ });
7453
+ }
7454
+ async function createWorktree(opts) {
7455
+ const { repository, branch, worktreePath, parentBranch } = opts;
7456
+ const result = await run(
7457
+ "git",
7458
+ ["-C", repository, "worktree", "add", "-b", branch, worktreePath, parentBranch]
7459
+ );
7460
+ if (result.code !== 0) {
7461
+ throw new GitWorktreeError(
7462
+ `git worktree add failed (exit ${result.code}): ${result.stderr.trim() || "(no stderr)"}`,
7463
+ result.stderr
7464
+ );
7465
+ }
7466
+ }
7467
+ async function removeWorktree(repository, worktreePath) {
7468
+ const result = await run(
7469
+ "git",
7470
+ ["-C", repository, "worktree", "remove", "--force", worktreePath]
7471
+ );
7472
+ return { ok: result.code === 0, stderr: result.stderr };
7473
+ }
7474
+ async function deleteBranch(repository, branch) {
7475
+ const result = await run("git", ["-C", repository, "branch", "-D", branch]);
7476
+ return { ok: result.code === 0, stderr: result.stderr };
7477
+ }
7478
+ async function createWorktreeAndRecord(opts) {
7479
+ const { assignmentPath, repository, branch, worktreePath, parentBranch } = opts;
7480
+ await createWorktree({ repository, branch, worktreePath, parentBranch });
7481
+ try {
7482
+ const content = await readFile13(assignmentPath, "utf-8");
7483
+ const updated = updateAssignmentWorkspace(content, {
7484
+ repository,
7485
+ worktreePath,
7486
+ branch,
7487
+ parentBranch
7488
+ });
7489
+ await writeFileForce(assignmentPath, updated);
7490
+ } catch (writeErr) {
7491
+ const cleanup = await removeWorktree(repository, worktreePath);
7492
+ const branchCleanup = await deleteBranch(repository, branch);
7493
+ const writeMsg = writeErr instanceof Error ? writeErr.message : String(writeErr);
7494
+ throw new Error(
7495
+ formatRollbackError({
7496
+ writeMsg,
7497
+ worktreePath,
7498
+ branch,
7499
+ worktreeCleanup: cleanup,
7500
+ branchCleanup
7501
+ })
7502
+ );
7503
+ }
7504
+ }
7505
+ function formatRollbackError(opts) {
7506
+ const { writeMsg, worktreePath, branch, worktreeCleanup, branchCleanup } = opts;
7507
+ const wtMsg = worktreeCleanup.stderr.trim() || "(no stderr)";
7508
+ const brMsg = branchCleanup.stderr.trim() || "(no stderr)";
7509
+ if (!worktreeCleanup.ok && !branchCleanup.ok) {
7510
+ return `Failed to update assignment frontmatter AND failed to clean up both worktree and branch. Write error: ${writeMsg}. Worktree cleanup error: ${wtMsg}. Branch cleanup error: ${brMsg}. Orphan worktree at ${worktreePath} and orphan branch "${branch}" \u2014 remove them manually.`;
7511
+ }
7512
+ if (!worktreeCleanup.ok) {
7513
+ return `Failed to update assignment frontmatter AND failed to clean up worktree. Write error: ${writeMsg}. Worktree cleanup error: ${wtMsg}. Orphan worktree at ${worktreePath} \u2014 remove it manually.`;
7514
+ }
7515
+ if (!branchCleanup.ok) {
7516
+ return `Failed to update assignment frontmatter: ${writeMsg}. Rolled back git worktree at ${worktreePath}, but could not delete branch "${branch}": ${brMsg}. Remove the branch manually.`;
7517
+ }
7518
+ return `Failed to update assignment frontmatter: ${writeMsg}. Rolled back git worktree at ${worktreePath} and branch "${branch}".`;
7519
+ }
7520
+ var GitWorktreeError;
7521
+ var init_git_worktree = __esm({
7522
+ "src/utils/git-worktree.ts"() {
7523
+ "use strict";
7524
+ init_frontmatter();
7525
+ init_fs();
7526
+ GitWorktreeError = class extends Error {
7527
+ constructor(message, stderr) {
7528
+ super(message);
7529
+ this.stderr = stderr;
7530
+ }
7531
+ };
7532
+ }
7533
+ });
7534
+
7343
7535
  // src/utils/promote-todos.ts
7344
7536
  var promote_todos_exports = {};
7345
7537
  __export(promote_todos_exports, {
@@ -7512,9 +7704,9 @@ __export(launch_exports, {
7512
7704
  launchAgent: () => launchAgent,
7513
7705
  shellQuote: () => shellQuote
7514
7706
  });
7515
- import { spawn as spawn2 } from "child_process";
7707
+ import { spawn as spawn3 } from "child_process";
7516
7708
  import { mkdir as mkdir6, writeFile as writeFile9 } from "fs/promises";
7517
- import { isAbsolute as isAbsolute3, resolve as resolve37 } from "path";
7709
+ import { isAbsolute as isAbsolute4, resolve as resolve39 } from "path";
7518
7710
  function formatFallbackCwdWarning(opts) {
7519
7711
  const missing = [];
7520
7712
  if (!opts.worktreePath) missing.push("worktreePath");
@@ -7535,7 +7727,7 @@ function buildAgentArgv(agent, prompt, env = process.env) {
7535
7727
  const requested = env.SHELL;
7536
7728
  let shell = requested;
7537
7729
  let warning = null;
7538
- if (!shell || !isAbsolute3(shell)) {
7730
+ if (!shell || !isAbsolute4(shell)) {
7539
7731
  warning = `syntaur: $SHELL ${requested ? `("${requested}") is not absolute` : "is unset"} \u2014 falling back to /bin/sh for shell-alias resolution`;
7540
7732
  shell = "/bin/sh";
7541
7733
  }
@@ -7558,8 +7750,8 @@ async function launchAgent(options) {
7558
7750
  console.error(`Assignment not found: ${projectSlug}/${assignmentSlug}`);
7559
7751
  process.exit(1);
7560
7752
  }
7561
- const projectDir = resolve37(projectsDir2, projectSlug);
7562
- const assignmentDir = resolve37(projectDir, "assignments", assignmentSlug);
7753
+ const projectDir = resolve39(projectsDir2, projectSlug);
7754
+ const assignmentDir = resolve39(projectDir, "assignments", assignmentSlug);
7563
7755
  const resolvedFromWorkspace = cwdOverride ?? detail.workspace.worktreePath ?? (detail.workspace.repository?.startsWith("/") ? detail.workspace.repository : null);
7564
7756
  const workspaceDir = resolvedFromWorkspace ?? process.cwd();
7565
7757
  if (!cwdOverride) {
@@ -7571,7 +7763,7 @@ async function launchAgent(options) {
7571
7763
  });
7572
7764
  if (warning) console.warn(warning);
7573
7765
  }
7574
- const contextDir = resolve37(workspaceDir, ".syntaur");
7766
+ const contextDir = resolve39(workspaceDir, ".syntaur");
7575
7767
  await mkdir6(contextDir, { recursive: true });
7576
7768
  const context = {
7577
7769
  projectSlug,
@@ -7584,7 +7776,7 @@ async function launchAgent(options) {
7584
7776
  grabbedAt: (/* @__PURE__ */ new Date()).toISOString()
7585
7777
  };
7586
7778
  await writeFile9(
7587
- resolve37(contextDir, "context.json"),
7779
+ resolve39(contextDir, "context.json"),
7588
7780
  JSON.stringify(context, null, 2) + "\n"
7589
7781
  );
7590
7782
  const { argv, shellFallbackWarning } = buildAgentArgv(
@@ -7595,7 +7787,7 @@ async function launchAgent(options) {
7595
7787
  console.warn(shellFallbackWarning);
7596
7788
  }
7597
7789
  return new Promise((resolvePromise) => {
7598
- const child = spawn2(argv.command, argv.args, {
7790
+ const child = spawn3(argv.command, argv.args, {
7599
7791
  cwd: workspaceDir,
7600
7792
  stdio: "inherit"
7601
7793
  });
@@ -7656,9 +7848,9 @@ import {
7656
7848
  unlinkSync
7657
7849
  } from "fs";
7658
7850
  import { fileURLToPath as fileURLToPath6, pathToFileURL } from "url";
7659
- import { dirname as dirname13, resolve as resolve39, join as join5 } from "path";
7851
+ import { dirname as dirname13, resolve as resolve41, join as join5 } from "path";
7660
7852
  import { homedir as homedir6, tmpdir as tmpdir2 } from "os";
7661
- import { spawnSync as spawnSync2 } from "child_process";
7853
+ import { spawnSync as spawnSync6 } from "child_process";
7662
7854
  function syntaurRootMjs() {
7663
7855
  const override = process.env.SYNTAUR_HOME;
7664
7856
  if (override && override.length > 0) {
@@ -7678,7 +7870,7 @@ async function registerMacosUrlHandler(options = { throwOnFailure: false }) {
7678
7870
  }
7679
7871
  const stateRoot = syntaurRootMjs();
7680
7872
  mkdirSync2(stateRoot, { recursive: true });
7681
- const lockPath = resolve39(stateRoot, "install-url-handler.lock");
7873
+ const lockPath = resolve41(stateRoot, "install-url-handler.lock");
7682
7874
  let lockFd;
7683
7875
  try {
7684
7876
  lockFd = openSync(lockPath, "wx");
@@ -7694,8 +7886,8 @@ async function registerMacosUrlHandler(options = { throwOnFailure: false }) {
7694
7886
  throw err2;
7695
7887
  }
7696
7888
  try {
7697
- const pkgRoot = resolve39(dirname13(fileURLToPath6(import.meta.url)), "..");
7698
- const cliBin = realpathSync2(resolve39(pkgRoot, "bin/syntaur.js"));
7889
+ const pkgRoot = resolve41(dirname13(fileURLToPath6(import.meta.url)), "..");
7890
+ const cliBin = realpathSync2(resolve41(pkgRoot, "bin/syntaur.js"));
7699
7891
  const nodeBin = process.execPath;
7700
7892
  const bundleParent = join5(
7701
7893
  homedir6(),
@@ -7718,7 +7910,7 @@ async function registerMacosUrlHandler(options = { throwOnFailure: false }) {
7718
7910
  renderAppleScript({ nodeBin, cliBin, installedTerminals }),
7719
7911
  "utf-8"
7720
7912
  );
7721
- const compile = spawnSync2(
7913
+ const compile = spawnSync6(
7722
7914
  OSACOMPILE,
7723
7915
  ["-o", bundlePath, scriptPath],
7724
7916
  { stdio: "pipe", encoding: "utf-8" }
@@ -7762,7 +7954,7 @@ async function registerMacosUrlHandler(options = { throwOnFailure: false }) {
7762
7954
  ["Add :CFBundleURLTypes:0:CFBundleURLSchemes array", null],
7763
7955
  [`Add :CFBundleURLTypes:0:CFBundleURLSchemes:0 string ${URL_SCHEME}`, null]
7764
7956
  ]);
7765
- const sign = spawnSync2(
7957
+ const sign = spawnSync6(
7766
7958
  "/usr/bin/codesign",
7767
7959
  ["--force", "--deep", "--sign", "-", bundlePath],
7768
7960
  { stdio: "pipe", encoding: "utf-8" }
@@ -7774,7 +7966,7 @@ async function registerMacosUrlHandler(options = { throwOnFailure: false }) {
7774
7966
  }
7775
7967
  console.warn(`syntaur: ${msg} \u2014 macOS may deny Automation permission.`);
7776
7968
  }
7777
- const ls = spawnSync2(LSREGISTER, ["-f", bundlePath], {
7969
+ const ls = spawnSync6(LSREGISTER, ["-f", bundlePath], {
7778
7970
  stdio: "pipe",
7779
7971
  encoding: "utf-8"
7780
7972
  });
@@ -7815,10 +8007,11 @@ function detectInstalledTerminals() {
7815
8007
  const installed = /* @__PURE__ */ new Set(["terminal-app"]);
7816
8008
  const bundleIds = {
7817
8009
  iterm: "com.googlecode.iterm2",
7818
- ghostty: "com.mitchellh.ghostty"
8010
+ ghostty: "com.mitchellh.ghostty",
8011
+ warp: "dev.warp.Warp-Stable"
7819
8012
  };
7820
8013
  for (const [id, bundleId] of Object.entries(bundleIds)) {
7821
- const r = spawnSync2(
8014
+ const r = spawnSync6(
7822
8015
  "mdfind",
7823
8016
  [`kMDItemCFBundleIdentifier == '${bundleId}'`],
7824
8017
  { encoding: "utf-8" }
@@ -7858,13 +8051,28 @@ function buildTerminalDispatch(installedTerminals) {
7858
8051
  branches.push({
7859
8052
  id: "ghostty",
7860
8053
  block: [
7861
- ' tell application "Ghostty"',
7862
- " activate",
7863
- " set newWin to (new window)",
7864
- " delay 0.2",
7865
- " set t to terminal 1 of selected tab of newWin",
7866
- " input text shellCmd to t",
7867
- ' send key "enter" to t',
8054
+ ' tell application "Ghostty" to activate',
8055
+ " delay 0.3",
8056
+ ' tell application "System Events"',
8057
+ ' keystroke "n" using command down',
8058
+ " delay 0.4",
8059
+ " keystroke shellCmd",
8060
+ " key code 36",
8061
+ " end tell"
8062
+ ]
8063
+ });
8064
+ }
8065
+ if (installedTerminals.has("warp")) {
8066
+ branches.push({
8067
+ id: "warp",
8068
+ block: [
8069
+ ' tell application "Warp" to activate',
8070
+ " delay 0.3",
8071
+ ' tell application "System Events"',
8072
+ ' keystroke "n" using command down',
8073
+ " delay 0.4",
8074
+ " keystroke shellCmd",
8075
+ " key code 36",
7868
8076
  " end tell"
7869
8077
  ]
7870
8078
  });
@@ -7949,13 +8157,13 @@ function runPlistBuddy(plistPath, steps) {
7949
8157
  for (const step of steps) {
7950
8158
  if (Array.isArray(step)) {
7951
8159
  const [primary, fallback] = step;
7952
- const a = spawnSync2("/usr/libexec/PlistBuddy", ["-c", primary, plistPath], {
8160
+ const a = spawnSync6("/usr/libexec/PlistBuddy", ["-c", primary, plistPath], {
7953
8161
  stdio: "pipe",
7954
8162
  encoding: "utf-8"
7955
8163
  });
7956
8164
  if (a.status === 0) continue;
7957
8165
  if (fallback === null) continue;
7958
- const b = spawnSync2("/usr/libexec/PlistBuddy", ["-c", fallback, plistPath], {
8166
+ const b = spawnSync6("/usr/libexec/PlistBuddy", ["-c", fallback, plistPath], {
7959
8167
  stdio: "pipe",
7960
8168
  encoding: "utf-8"
7961
8169
  });
@@ -7965,7 +8173,7 @@ function runPlistBuddy(plistPath, steps) {
7965
8173
  );
7966
8174
  }
7967
8175
  } else {
7968
- const r = spawnSync2("/usr/libexec/PlistBuddy", ["-c", step, plistPath], {
8176
+ const r = spawnSync6("/usr/libexec/PlistBuddy", ["-c", step, plistPath], {
7969
8177
  stdio: "pipe",
7970
8178
  encoding: "utf-8"
7971
8179
  });
@@ -8500,114 +8708,6 @@ var init_App = __esm({
8500
8708
  }
8501
8709
  });
8502
8710
 
8503
- // src/utils/git-worktree.ts
8504
- var git_worktree_exports = {};
8505
- __export(git_worktree_exports, {
8506
- GitWorktreeError: () => GitWorktreeError,
8507
- createWorktree: () => createWorktree,
8508
- createWorktreeAndRecord: () => createWorktreeAndRecord,
8509
- deleteBranch: () => deleteBranch,
8510
- formatRollbackError: () => formatRollbackError,
8511
- removeWorktree: () => removeWorktree
8512
- });
8513
- import { spawn as spawn4 } from "child_process";
8514
- import { readFile as readFile23 } from "fs/promises";
8515
- function run(command, args, cwd) {
8516
- return new Promise((resolvePromise) => {
8517
- const child = spawn4(command, args, { cwd, stdio: ["ignore", "pipe", "pipe"] });
8518
- let stdout = "";
8519
- let stderr = "";
8520
- child.stdout.on("data", (chunk) => stdout += chunk.toString());
8521
- child.stderr.on("data", (chunk) => stderr += chunk.toString());
8522
- child.on("error", (err2) => {
8523
- resolvePromise({ code: -1, stdout, stderr: stderr + String(err2) });
8524
- });
8525
- child.on("close", (code) => {
8526
- resolvePromise({ code: code ?? -1, stdout, stderr });
8527
- });
8528
- });
8529
- }
8530
- async function createWorktree(opts) {
8531
- const { repository, branch, worktreePath, parentBranch } = opts;
8532
- const result = await run(
8533
- "git",
8534
- ["-C", repository, "worktree", "add", "-b", branch, worktreePath, parentBranch]
8535
- );
8536
- if (result.code !== 0) {
8537
- throw new GitWorktreeError(
8538
- `git worktree add failed (exit ${result.code}): ${result.stderr.trim() || "(no stderr)"}`,
8539
- result.stderr
8540
- );
8541
- }
8542
- }
8543
- async function removeWorktree(repository, worktreePath) {
8544
- const result = await run(
8545
- "git",
8546
- ["-C", repository, "worktree", "remove", "--force", worktreePath]
8547
- );
8548
- return { ok: result.code === 0, stderr: result.stderr };
8549
- }
8550
- async function deleteBranch(repository, branch) {
8551
- const result = await run("git", ["-C", repository, "branch", "-D", branch]);
8552
- return { ok: result.code === 0, stderr: result.stderr };
8553
- }
8554
- async function createWorktreeAndRecord(opts) {
8555
- const { assignmentPath, repository, branch, worktreePath, parentBranch } = opts;
8556
- await createWorktree({ repository, branch, worktreePath, parentBranch });
8557
- try {
8558
- const content = await readFile23(assignmentPath, "utf-8");
8559
- const updated = updateAssignmentWorkspace(content, {
8560
- repository,
8561
- worktreePath,
8562
- branch,
8563
- parentBranch
8564
- });
8565
- await writeFileForce(assignmentPath, updated);
8566
- } catch (writeErr) {
8567
- const cleanup = await removeWorktree(repository, worktreePath);
8568
- const branchCleanup = await deleteBranch(repository, branch);
8569
- const writeMsg = writeErr instanceof Error ? writeErr.message : String(writeErr);
8570
- throw new Error(
8571
- formatRollbackError({
8572
- writeMsg,
8573
- worktreePath,
8574
- branch,
8575
- worktreeCleanup: cleanup,
8576
- branchCleanup
8577
- })
8578
- );
8579
- }
8580
- }
8581
- function formatRollbackError(opts) {
8582
- const { writeMsg, worktreePath, branch, worktreeCleanup, branchCleanup } = opts;
8583
- const wtMsg = worktreeCleanup.stderr.trim() || "(no stderr)";
8584
- const brMsg = branchCleanup.stderr.trim() || "(no stderr)";
8585
- if (!worktreeCleanup.ok && !branchCleanup.ok) {
8586
- return `Failed to update assignment frontmatter AND failed to clean up both worktree and branch. Write error: ${writeMsg}. Worktree cleanup error: ${wtMsg}. Branch cleanup error: ${brMsg}. Orphan worktree at ${worktreePath} and orphan branch "${branch}" \u2014 remove them manually.`;
8587
- }
8588
- if (!worktreeCleanup.ok) {
8589
- return `Failed to update assignment frontmatter AND failed to clean up worktree. Write error: ${writeMsg}. Worktree cleanup error: ${wtMsg}. Orphan worktree at ${worktreePath} \u2014 remove it manually.`;
8590
- }
8591
- if (!branchCleanup.ok) {
8592
- return `Failed to update assignment frontmatter: ${writeMsg}. Rolled back git worktree at ${worktreePath}, but could not delete branch "${branch}": ${brMsg}. Remove the branch manually.`;
8593
- }
8594
- return `Failed to update assignment frontmatter: ${writeMsg}. Rolled back git worktree at ${worktreePath} and branch "${branch}".`;
8595
- }
8596
- var GitWorktreeError;
8597
- var init_git_worktree = __esm({
8598
- "src/utils/git-worktree.ts"() {
8599
- "use strict";
8600
- init_frontmatter();
8601
- init_fs();
8602
- GitWorktreeError = class extends Error {
8603
- constructor(message, stderr) {
8604
- super(message);
8605
- this.stderr = stderr;
8606
- }
8607
- };
8608
- }
8609
- });
8610
-
8611
8711
  // src/index.ts
8612
8712
  import { Command as Command13, InvalidArgumentError } from "commander";
8613
8713
 
@@ -8765,9 +8865,9 @@ init_create_assignment();
8765
8865
 
8766
8866
  // src/commands/dashboard.ts
8767
8867
  init_config2();
8768
- import { spawn } from "child_process";
8868
+ import { spawn as spawn2 } from "child_process";
8769
8869
  import { createServer as createNetServer } from "net";
8770
- import { resolve as resolve25, dirname as dirname7 } from "path";
8870
+ import { resolve as resolve27, dirname as dirname7 } from "path";
8771
8871
  import { fileURLToPath as fileURLToPath2 } from "url";
8772
8872
 
8773
8873
  // src/dashboard/server.ts
@@ -8777,7 +8877,7 @@ init_assignment_resolver();
8777
8877
  init_agent_sessions();
8778
8878
  import express from "express";
8779
8879
  import { createServer } from "http";
8780
- import { resolve as resolve24 } from "path";
8880
+ import { resolve as resolve26 } from "path";
8781
8881
  import { writeFile as writeFile5, unlink as unlink5 } from "fs/promises";
8782
8882
  import { WebSocketServer, WebSocket } from "ws";
8783
8883
 
@@ -9312,10 +9412,116 @@ init_slug();
9312
9412
  init_uuid();
9313
9413
  init_timestamp();
9314
9414
  init_fs();
9315
- init_parser();
9415
+ init_git_worktree();
9316
9416
  import { Router } from "express";
9317
- import { resolve as resolve18, basename as basename3 } from "path";
9318
- import { rm, readFile as readFile13, open as fsOpen } from "fs/promises";
9417
+ import { resolve as resolve20, basename as basename3, isAbsolute as isAbsolute2 } from "path";
9418
+ import { rm, readFile as readFile15, open as fsOpen, stat as fsStat, realpath as fsRealpath } from "fs/promises";
9419
+ import { spawnSync as spawnSync3 } from "child_process";
9420
+
9421
+ // src/utils/worktree-defaults.ts
9422
+ init_paths();
9423
+ import { spawnSync as spawnSync2 } from "child_process";
9424
+ import { resolve as resolve18 } from "path";
9425
+ function computeWorktreeDefaults(opts) {
9426
+ const repository = opts.existing.repository ?? detectCurrentGitRoot(opts.cwd);
9427
+ const branch = opts.projectSlug ? `syntaur/${opts.projectSlug}/${opts.assignmentSlug}` : `syntaur/${opts.assignmentSlug}`;
9428
+ const parentBranch = opts.existing.parentBranch ?? detectCurrentBranch(opts.cwd) ?? "main";
9429
+ const worktreeBase = repository ? resolve18(repository, ".worktrees", branch) : resolve18(
9430
+ syntaurRoot(),
9431
+ "worktrees",
9432
+ opts.projectSlug || "standalone",
9433
+ opts.assignmentSlug
9434
+ );
9435
+ return {
9436
+ ...repository ? { repository } : {},
9437
+ branch,
9438
+ parentBranch,
9439
+ worktreePath: worktreeBase
9440
+ };
9441
+ }
9442
+ function detectCurrentGitRoot(cwd) {
9443
+ const result = spawnSync2("git", ["rev-parse", "--show-toplevel"], {
9444
+ cwd,
9445
+ encoding: "utf-8"
9446
+ });
9447
+ if (result.status !== 0) return void 0;
9448
+ const out = result.stdout.trim();
9449
+ return out.length > 0 ? out : void 0;
9450
+ }
9451
+ function detectCurrentBranch(cwd) {
9452
+ const result = spawnSync2("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
9453
+ cwd,
9454
+ encoding: "utf-8"
9455
+ });
9456
+ if (result.status !== 0) return void 0;
9457
+ const out = result.stdout.trim();
9458
+ if (!out || out === "HEAD") return void 0;
9459
+ return out;
9460
+ }
9461
+
9462
+ // src/dashboard/repository-candidates.ts
9463
+ init_fs();
9464
+ init_parser();
9465
+ import { readdir as readdir10, readFile as readFile14 } from "fs/promises";
9466
+ import { resolve as resolve19 } from "path";
9467
+ async function getProjectRepositoryCandidates(projectsDir2, projectSlug) {
9468
+ const seen = /* @__PURE__ */ new Set();
9469
+ const out = [];
9470
+ const projectPath = resolve19(projectsDir2, projectSlug, "project.md");
9471
+ if (await fileExists(projectPath)) {
9472
+ const project = parseProject(await readFile14(projectPath, "utf-8"));
9473
+ for (const raw of project.repositories) {
9474
+ const path = raw.trim();
9475
+ if (!path) continue;
9476
+ const abs = resolve19(path);
9477
+ if (seen.has(abs)) continue;
9478
+ seen.add(abs);
9479
+ out.push({ path: abs, source: "project", sourceAssignmentSlug: null });
9480
+ }
9481
+ }
9482
+ const assignmentsDir2 = resolve19(projectsDir2, projectSlug, "assignments");
9483
+ if (await fileExists(assignmentsDir2)) {
9484
+ const entries = await readdir10(assignmentsDir2, { withFileTypes: true });
9485
+ for (const entry of entries) {
9486
+ if (!entry.isDirectory()) continue;
9487
+ const assignmentMd = resolve19(assignmentsDir2, entry.name, "assignment.md");
9488
+ if (!await fileExists(assignmentMd)) continue;
9489
+ const parsed = parseAssignmentFull(await readFile14(assignmentMd, "utf-8"));
9490
+ const repo = parsed.workspace.repository?.trim();
9491
+ if (!repo) continue;
9492
+ const abs = resolve19(repo);
9493
+ if (seen.has(abs)) continue;
9494
+ seen.add(abs);
9495
+ out.push({ path: abs, source: "sibling", sourceAssignmentSlug: parsed.slug });
9496
+ }
9497
+ }
9498
+ return out;
9499
+ }
9500
+ async function getStandaloneRepositoryCandidates(assignmentsDir2, excludeAssignmentId) {
9501
+ if (!await fileExists(assignmentsDir2)) {
9502
+ return [];
9503
+ }
9504
+ const seen = /* @__PURE__ */ new Set();
9505
+ const out = [];
9506
+ const entries = await readdir10(assignmentsDir2, { withFileTypes: true });
9507
+ for (const entry of entries) {
9508
+ if (!entry.isDirectory()) continue;
9509
+ if (entry.name === excludeAssignmentId) continue;
9510
+ const assignmentMd = resolve19(assignmentsDir2, entry.name, "assignment.md");
9511
+ if (!await fileExists(assignmentMd)) continue;
9512
+ const parsed = parseAssignmentFull(await readFile14(assignmentMd, "utf-8"));
9513
+ const repo = parsed.workspace.repository?.trim();
9514
+ if (!repo) continue;
9515
+ const abs = resolve19(repo);
9516
+ if (seen.has(abs)) continue;
9517
+ seen.add(abs);
9518
+ out.push({ path: abs, source: "sibling", sourceAssignmentSlug: parsed.slug });
9519
+ }
9520
+ return out;
9521
+ }
9522
+
9523
+ // src/dashboard/api-write.ts
9524
+ init_parser();
9319
9525
 
9320
9526
  // src/dashboard/acceptance-criteria.ts
9321
9527
  function splitFrontmatter(content) {
@@ -9468,7 +9674,101 @@ async function readCurrentDocument(filePath) {
9468
9674
  if (!await fileExists(filePath)) {
9469
9675
  return null;
9470
9676
  }
9471
- return readFile13(filePath, "utf-8");
9677
+ return readFile15(filePath, "utf-8");
9678
+ }
9679
+ async function handleWorktreeCreate(req, res, ctx) {
9680
+ if (!await fileExists(ctx.assignmentPath)) {
9681
+ res.status(404).json({ error: "Assignment not found" });
9682
+ return;
9683
+ }
9684
+ const parsed = parseAssignmentFull(await readFile15(ctx.assignmentPath, "utf-8"));
9685
+ if (parsed.workspace.worktreePath) {
9686
+ res.status(409).json({ error: "Worktree already configured for this assignment" });
9687
+ return;
9688
+ }
9689
+ const { repository, branch: bodyBranch, parentBranch: bodyParent } = req.body ?? {};
9690
+ if (typeof repository !== "string" || !repository.trim()) {
9691
+ res.status(400).json({ error: "`repository` is required." });
9692
+ return;
9693
+ }
9694
+ if (!isAbsolute2(repository)) {
9695
+ res.status(400).json({ error: "`repository` must be an absolute path." });
9696
+ return;
9697
+ }
9698
+ try {
9699
+ const st = await fsStat(repository);
9700
+ if (!st.isDirectory()) {
9701
+ res.status(400).json({ error: `Repository path is not a directory: ${repository}` });
9702
+ return;
9703
+ }
9704
+ } catch {
9705
+ res.status(400).json({ error: `Repository path does not exist: ${repository}` });
9706
+ return;
9707
+ }
9708
+ const topLevel = spawnSync3("git", ["-C", repository, "rev-parse", "--show-toplevel"], {
9709
+ encoding: "utf-8"
9710
+ });
9711
+ const topLevelOut = topLevel.stdout.trim();
9712
+ if (topLevel.status !== 0 || !topLevelOut) {
9713
+ res.status(400).json({ error: `Repository path is not a git working tree: ${repository}` });
9714
+ return;
9715
+ }
9716
+ const [requestReal, topLevelReal] = await Promise.all([
9717
+ fsRealpath(repository),
9718
+ fsRealpath(topLevelOut)
9719
+ ]);
9720
+ if (requestReal !== topLevelReal) {
9721
+ res.status(400).json({
9722
+ error: `Repository path must be the git working-tree root. Got ${repository}; the enclosing repo root is ${topLevelOut}.`
9723
+ });
9724
+ return;
9725
+ }
9726
+ const defaults = computeWorktreeDefaults({
9727
+ projectSlug: ctx.projectSlug,
9728
+ assignmentSlug: ctx.assignmentSlug,
9729
+ existing: parsed.workspace,
9730
+ cwd: repository
9731
+ });
9732
+ const branch = typeof bodyBranch === "string" && bodyBranch.trim() ? bodyBranch.trim() : defaults.branch;
9733
+ const parentBranch = typeof bodyParent === "string" && bodyParent.trim() ? bodyParent.trim() : defaults.parentBranch;
9734
+ const worktreePath = resolve20(repository, ".worktrees", branch);
9735
+ try {
9736
+ await fsStat(worktreePath);
9737
+ res.status(409).json({
9738
+ error: `A file or directory already exists at ${worktreePath}. Remove it or choose a different branch.`
9739
+ });
9740
+ return;
9741
+ } catch {
9742
+ }
9743
+ const parentCheck = spawnSync3(
9744
+ "git",
9745
+ ["-C", repository, "rev-parse", "--verify", "--quiet", parentBranch],
9746
+ { encoding: "utf-8" }
9747
+ );
9748
+ if (parentCheck.status !== 0) {
9749
+ res.status(400).json({
9750
+ error: `Parent branch "${parentBranch}" does not exist in ${repository}.`
9751
+ });
9752
+ return;
9753
+ }
9754
+ try {
9755
+ await createWorktreeAndRecord({
9756
+ assignmentPath: ctx.assignmentPath,
9757
+ repository,
9758
+ branch,
9759
+ worktreePath,
9760
+ parentBranch
9761
+ });
9762
+ } catch (error) {
9763
+ if (error instanceof GitWorktreeError) {
9764
+ res.status(400).json({ error: error.message, stderr: error.stderr });
9765
+ return;
9766
+ }
9767
+ res.status(500).json({ error: error.message });
9768
+ return;
9769
+ }
9770
+ const assignment = await ctx.reload();
9771
+ res.json({ assignment });
9472
9772
  }
9473
9773
  function createWriteRouter(projectsDir2, assignmentsDir2, todosDir2) {
9474
9774
  const linkedTodosLookup = todosDir2 ? { todosDir: todosDir2, projectsDir: projectsDir2 } : void 0;
@@ -9697,9 +9997,9 @@ ${body.startsWith("\n") ? body.slice(1) : body}${body.endsWith("\n") ? "" : "\n"
9697
9997
  });
9698
9998
  return;
9699
9999
  }
9700
- const folderPath = resolve18(projectDir, folder);
10000
+ const folderPath = resolve20(projectDir, folder);
9701
10001
  await ensureDir(folderPath);
9702
- const filePath = resolve18(folderPath, `${requestedSlug}.md`);
10002
+ const filePath = resolve20(folderPath, `${requestedSlug}.md`);
9703
10003
  const timestamp = nowTimestamp();
9704
10004
  let content = renderItemStub(kind, {
9705
10005
  slug: requestedSlug,
@@ -9743,14 +10043,14 @@ ${body.startsWith("\n") ? body.slice(1) : body}${body.endsWith("\n") ? "" : "\n"
9743
10043
  res.status(404).json({ error: `Project "${projectSlug}" not found` });
9744
10044
  return;
9745
10045
  }
9746
- const filePath = resolve18(projectDir, folder, `${itemSlug}.md`);
10046
+ const filePath = resolve20(projectDir, folder, `${itemSlug}.md`);
9747
10047
  if (!await fileExists(filePath)) {
9748
10048
  res.status(404).json({ error: `${kind === "memory" ? "Memory" : "Resource"} not found` });
9749
10049
  return;
9750
10050
  }
9751
10051
  const nextContentRaw = requireContent(req, res);
9752
10052
  if (!nextContentRaw) return;
9753
- const currentContent = await readFile13(filePath, "utf-8");
10053
+ const currentContent = await readFile15(filePath, "utf-8");
9754
10054
  const frontmatterBlock = extractFrontmatterBlock(currentContent);
9755
10055
  if (!frontmatterBlock) {
9756
10056
  res.status(500).json({ error: `${kind} file is malformed (no frontmatter)` });
@@ -9779,7 +10079,7 @@ ${nextBody}${nextBody.endsWith("\n") ? "" : "\n"}`;
9779
10079
  res.status(404).json({ error: `Project "${projectSlug}" not found` });
9780
10080
  return;
9781
10081
  }
9782
- const filePath = resolve18(projectDir, folder, `${itemSlug}.md`);
10082
+ const filePath = resolve20(projectDir, folder, `${itemSlug}.md`);
9783
10083
  if (!await fileExists(filePath)) {
9784
10084
  res.status(404).json({ error: `${kind === "memory" ? "Memory" : "Resource"} not found` });
9785
10085
  return;
@@ -9813,26 +10113,26 @@ ${nextBody}${nextBody.endsWith("\n") ? "" : "\n"}`;
9813
10113
  res.status(400).json({ error: `Invalid slug "${slug}". Must be lowercase and hyphen-separated.` });
9814
10114
  return;
9815
10115
  }
9816
- const projectDir = resolve18(projectsDir2, slug);
10116
+ const projectDir = resolve20(projectsDir2, slug);
9817
10117
  if (await fileExists(projectDir)) {
9818
10118
  res.status(409).json({ error: `Project "${slug}" already exists` });
9819
10119
  return;
9820
10120
  }
9821
10121
  const title = fields.title;
9822
10122
  const timestamp = fields.created || nowTimestamp();
9823
- await ensureDir(resolve18(projectDir, "assignments"));
9824
- await ensureDir(resolve18(projectDir, "resources"));
9825
- await ensureDir(resolve18(projectDir, "memories"));
9826
- await writeFileForce(resolve18(projectDir, "project.md"), content);
10123
+ await ensureDir(resolve20(projectDir, "assignments"));
10124
+ await ensureDir(resolve20(projectDir, "resources"));
10125
+ await ensureDir(resolve20(projectDir, "memories"));
10126
+ await writeFileForce(resolve20(projectDir, "project.md"), content);
9827
10127
  try {
9828
10128
  const companions = [
9829
- [resolve18(projectDir, "manifest.md"), renderManifest({ slug, timestamp })],
9830
- [resolve18(projectDir, "_index-assignments.md"), renderIndexAssignments({ slug, title, timestamp })],
9831
- [resolve18(projectDir, "_index-plans.md"), renderIndexPlans({ slug, title, timestamp })],
9832
- [resolve18(projectDir, "_index-decisions.md"), renderIndexDecisions({ slug, title, timestamp })],
9833
- [resolve18(projectDir, "_status.md"), renderStatus({ slug, title, timestamp })],
9834
- [resolve18(projectDir, "resources", "_index.md"), renderResourcesIndex({ slug, title, timestamp })],
9835
- [resolve18(projectDir, "memories", "_index.md"), renderMemoriesIndex({ slug, title, timestamp })]
10129
+ [resolve20(projectDir, "manifest.md"), renderManifest({ slug, timestamp })],
10130
+ [resolve20(projectDir, "_index-assignments.md"), renderIndexAssignments({ slug, title, timestamp })],
10131
+ [resolve20(projectDir, "_index-plans.md"), renderIndexPlans({ slug, title, timestamp })],
10132
+ [resolve20(projectDir, "_index-decisions.md"), renderIndexDecisions({ slug, title, timestamp })],
10133
+ [resolve20(projectDir, "_status.md"), renderStatus({ slug, title, timestamp })],
10134
+ [resolve20(projectDir, "resources", "_index.md"), renderResourcesIndex({ slug, title, timestamp })],
10135
+ [resolve20(projectDir, "memories", "_index.md"), renderMemoriesIndex({ slug, title, timestamp })]
9836
10136
  ];
9837
10137
  for (const [filePath, fileContent] of companions) {
9838
10138
  await writeFileForce(filePath, fileContent);
@@ -9853,8 +10153,8 @@ ${nextBody}${nextBody.endsWith("\n") ? "" : "\n"}`;
9853
10153
  router.post("/api/projects/:slug/assignments", async (req, res) => {
9854
10154
  try {
9855
10155
  const projectSlug = getParam(req.params.slug);
9856
- const projectDir = resolve18(projectsDir2, projectSlug);
9857
- const projectMdPath = resolve18(projectDir, "project.md");
10156
+ const projectDir = resolve20(projectsDir2, projectSlug);
10157
+ const projectMdPath = resolve20(projectDir, "project.md");
9858
10158
  if (!await fileExists(projectMdPath)) {
9859
10159
  res.status(404).json({ error: `Project "${projectSlug}" not found` });
9860
10160
  return;
@@ -9884,7 +10184,7 @@ ${nextBody}${nextBody.endsWith("\n") ? "" : "\n"}`;
9884
10184
  res.status(400).json({ error: `Invalid priority "${priority}". Must be low, medium, high, or critical.` });
9885
10185
  return;
9886
10186
  }
9887
- const assignmentDir = resolve18(projectDir, "assignments", assignmentSlug);
10187
+ const assignmentDir = resolve20(projectDir, "assignments", assignmentSlug);
9888
10188
  if (await fileExists(assignmentDir)) {
9889
10189
  res.status(409).json({
9890
10190
  error: `Assignment "${assignmentSlug}" already exists in project "${projectSlug}"`
@@ -9893,12 +10193,12 @@ ${nextBody}${nextBody.endsWith("\n") ? "" : "\n"}`;
9893
10193
  }
9894
10194
  const timestamp = fields.created || nowTimestamp();
9895
10195
  await ensureDir(assignmentDir);
9896
- await writeFileForce(resolve18(assignmentDir, "assignment.md"), content);
10196
+ await writeFileForce(resolve20(assignmentDir, "assignment.md"), content);
9897
10197
  try {
9898
10198
  const companions = [
9899
- [resolve18(assignmentDir, "scratchpad.md"), renderScratchpad({ assignmentSlug, timestamp })],
9900
- [resolve18(assignmentDir, "handoff.md"), renderHandoff({ assignmentSlug, timestamp })],
9901
- [resolve18(assignmentDir, "decision-record.md"), renderDecisionRecord({ assignmentSlug, timestamp })]
10199
+ [resolve20(assignmentDir, "scratchpad.md"), renderScratchpad({ assignmentSlug, timestamp })],
10200
+ [resolve20(assignmentDir, "handoff.md"), renderHandoff({ assignmentSlug, timestamp })],
10201
+ [resolve20(assignmentDir, "decision-record.md"), renderDecisionRecord({ assignmentSlug, timestamp })]
9902
10202
  ];
9903
10203
  for (const [filePath, fileContent] of companions) {
9904
10204
  await writeFileForce(filePath, fileContent);
@@ -9919,7 +10219,7 @@ ${nextBody}${nextBody.endsWith("\n") ? "" : "\n"}`;
9919
10219
  router.patch("/api/projects/:slug", async (req, res) => {
9920
10220
  try {
9921
10221
  const projectSlug = getParam(req.params.slug);
9922
- const projectPath = resolve18(projectsDir2, projectSlug, "project.md");
10222
+ const projectPath = resolve20(projectsDir2, projectSlug, "project.md");
9923
10223
  const currentContent = await readCurrentDocument(projectPath);
9924
10224
  if (!currentContent) {
9925
10225
  res.status(404).json({ error: `Project "${projectSlug}" not found` });
@@ -9952,7 +10252,7 @@ ${nextBody}${nextBody.endsWith("\n") ? "" : "\n"}`;
9952
10252
  try {
9953
10253
  const projectSlug = getParam(req.params.slug);
9954
10254
  const assignmentSlug = getParam(req.params.aslug);
9955
- const assignmentPath = resolve18(
10255
+ const assignmentPath = resolve20(
9956
10256
  projectsDir2,
9957
10257
  projectSlug,
9958
10258
  "assignments",
@@ -9995,7 +10295,7 @@ ${nextBody}${nextBody.endsWith("\n") ? "" : "\n"}`;
9995
10295
  try {
9996
10296
  const projectSlug = getParam(req.params.slug);
9997
10297
  const assignmentSlug = getParam(req.params.aslug);
9998
- const assignmentPath = resolve18(
10298
+ const assignmentPath = resolve20(
9999
10299
  projectsDir2,
10000
10300
  projectSlug,
10001
10301
  "assignments",
@@ -10031,7 +10331,7 @@ ${nextBody}${nextBody.endsWith("\n") ? "" : "\n"}`;
10031
10331
  try {
10032
10332
  const projectSlug = getParam(req.params.slug);
10033
10333
  const assignmentSlug = getParam(req.params.aslug);
10034
- const planPath = resolve18(
10334
+ const planPath = resolve20(
10035
10335
  projectsDir2,
10036
10336
  projectSlug,
10037
10337
  "assignments",
@@ -10069,7 +10369,7 @@ ${nextBody}${nextBody.endsWith("\n") ? "" : "\n"}`;
10069
10369
  try {
10070
10370
  const projectSlug = getParam(req.params.slug);
10071
10371
  const assignmentSlug = getParam(req.params.aslug);
10072
- const scratchpadPath = resolve18(
10372
+ const scratchpadPath = resolve20(
10073
10373
  projectsDir2,
10074
10374
  projectSlug,
10075
10375
  "assignments",
@@ -10107,7 +10407,7 @@ ${nextBody}${nextBody.endsWith("\n") ? "" : "\n"}`;
10107
10407
  try {
10108
10408
  const projectSlug = getParam(req.params.slug);
10109
10409
  const assignmentSlug = getParam(req.params.aslug);
10110
- const handoffPath = resolve18(
10410
+ const handoffPath = resolve20(
10111
10411
  projectsDir2,
10112
10412
  projectSlug,
10113
10413
  "assignments",
@@ -10145,7 +10445,7 @@ ${nextBody}${nextBody.endsWith("\n") ? "" : "\n"}`;
10145
10445
  try {
10146
10446
  const projectSlug = getParam(req.params.slug);
10147
10447
  const assignmentSlug = getParam(req.params.aslug);
10148
- const decisionPath = resolve18(
10448
+ const decisionPath = resolve20(
10149
10449
  projectsDir2,
10150
10450
  projectSlug,
10151
10451
  "assignments",
@@ -10183,7 +10483,7 @@ ${nextBody}${nextBody.endsWith("\n") ? "" : "\n"}`;
10183
10483
  try {
10184
10484
  const projectSlug = getParam(req.params.slug);
10185
10485
  const assignmentSlug = getParam(req.params.aslug);
10186
- const commentsPath = resolve18(
10486
+ const commentsPath = resolve20(
10187
10487
  projectsDir2,
10188
10488
  projectSlug,
10189
10489
  "assignments",
@@ -10201,7 +10501,7 @@ ${nextBody}${nextBody.endsWith("\n") ? "" : "\n"}`;
10201
10501
  let currentContent;
10202
10502
  let currentCount = 0;
10203
10503
  if (await fileExists(commentsPath)) {
10204
- currentContent = await readFile13(commentsPath, "utf-8");
10504
+ currentContent = await readFile15(commentsPath, "utf-8");
10205
10505
  const countMatch = currentContent.match(/^entryCount:\s*(\d+)/m);
10206
10506
  if (countMatch) currentCount = parseInt(countMatch[1], 10);
10207
10507
  } else {
@@ -10242,7 +10542,7 @@ ${entry}`;
10242
10542
  const projectSlug = getParam(req.params.slug);
10243
10543
  const assignmentSlug = getParam(req.params.aslug);
10244
10544
  const commentId = getParam(req.params.commentId);
10245
- const commentsPath = resolve18(
10545
+ const commentsPath = resolve20(
10246
10546
  projectsDir2,
10247
10547
  projectSlug,
10248
10548
  "assignments",
@@ -10258,7 +10558,7 @@ ${entry}`;
10258
10558
  res.status(400).json({ error: "resolved (boolean) is required" });
10259
10559
  return;
10260
10560
  }
10261
- const content = await readFile13(commentsPath, "utf-8");
10561
+ const content = await readFile15(commentsPath, "utf-8");
10262
10562
  const parsed = parseComments(content);
10263
10563
  const target = parsed.entries.find((e) => e.id === commentId);
10264
10564
  if (!target) {
@@ -10293,7 +10593,7 @@ ${entry}`;
10293
10593
  router.post("/api/projects/:slug/move-workspace", async (req, res) => {
10294
10594
  try {
10295
10595
  const projectSlug = getParam(req.params.slug);
10296
- const projectPath = resolve18(projectsDir2, projectSlug, "project.md");
10596
+ const projectPath = resolve20(projectsDir2, projectSlug, "project.md");
10297
10597
  if (!await fileExists(projectPath)) {
10298
10598
  res.status(404).json({ error: `Project "${projectSlug}" not found` });
10299
10599
  return;
@@ -10305,7 +10605,7 @@ ${entry}`;
10305
10605
  });
10306
10606
  return;
10307
10607
  }
10308
- let content = await readFile13(projectPath, "utf-8");
10608
+ let content = await readFile15(projectPath, "utf-8");
10309
10609
  content = setTopLevelField(content, "workspace", workspace ?? null);
10310
10610
  content = setTopLevelField(content, "updated", nowTimestamp());
10311
10611
  await writeFileForce(projectPath, content);
@@ -10332,31 +10632,129 @@ ${entry}`;
10332
10632
  res.status(400).json({
10333
10633
  error: "Project-nested assignments inherit workspace from their parent project. Move the project instead."
10334
10634
  });
10335
- return;
10635
+ return;
10636
+ }
10637
+ const { workspaceGroup } = req.body || {};
10638
+ if (workspaceGroup !== null && (typeof workspaceGroup !== "string" || !workspaceGroup.trim() || !isValidSlug(workspaceGroup))) {
10639
+ res.status(400).json({
10640
+ error: "workspaceGroup must be a valid slug (lowercase letters, numbers, hyphens) or null (for ungrouped)."
10641
+ });
10642
+ return;
10643
+ }
10644
+ const assignmentPath = resolve20(resolved.assignmentDir, "assignment.md");
10645
+ let content = await readFile15(assignmentPath, "utf-8");
10646
+ content = setTopLevelField(content, "workspaceGroup", workspaceGroup ?? null);
10647
+ content = setTopLevelField(content, "updated", nowTimestamp());
10648
+ await writeFileForce(assignmentPath, content);
10649
+ const assignment = await getAssignmentDetailById(projectsDir2, assignmentsDir2, id);
10650
+ res.json({ assignment });
10651
+ } catch (error) {
10652
+ console.error("Error moving assignment workspace:", error);
10653
+ res.status(500).json({ error: `Failed to move workspace: ${error.message}` });
10654
+ }
10655
+ });
10656
+ router.get(
10657
+ "/api/projects/:slug/repository-candidates",
10658
+ async (req, res) => {
10659
+ try {
10660
+ const projectSlug = getParam(req.params.slug);
10661
+ const projectPath = resolve20(projectsDir2, projectSlug, "project.md");
10662
+ if (!await fileExists(projectPath)) {
10663
+ res.status(404).json({ error: `Project "${projectSlug}" not found` });
10664
+ return;
10665
+ }
10666
+ const candidates = await getProjectRepositoryCandidates(projectsDir2, projectSlug);
10667
+ res.json({ candidates });
10668
+ } catch (error) {
10669
+ console.error("Error listing repository candidates:", error);
10670
+ res.status(500).json({
10671
+ error: `Failed to list repository candidates: ${error.message}`
10672
+ });
10673
+ }
10674
+ }
10675
+ );
10676
+ router.get(
10677
+ "/api/assignments/:id/repository-candidates",
10678
+ async (req, res) => {
10679
+ try {
10680
+ if (!assignmentsDir2) {
10681
+ res.status(501).json({ error: "Standalone assignments not configured on this server" });
10682
+ return;
10683
+ }
10684
+ const id = getParam(req.params.id);
10685
+ const resolved = await resolveAssignmentById(projectsDir2, assignmentsDir2, id);
10686
+ if (!resolved) {
10687
+ res.status(404).json({ error: `Assignment "${id}" not found` });
10688
+ return;
10689
+ }
10690
+ const candidates = resolved.standalone ? await getStandaloneRepositoryCandidates(assignmentsDir2, id) : await getProjectRepositoryCandidates(projectsDir2, resolved.projectSlug);
10691
+ res.json({ candidates });
10692
+ } catch (error) {
10693
+ console.error("Error listing repository candidates:", error);
10694
+ res.status(500).json({
10695
+ error: `Failed to list repository candidates: ${error.message}`
10696
+ });
10697
+ }
10698
+ }
10699
+ );
10700
+ router.post(
10701
+ "/api/projects/:slug/assignments/:aslug/worktree",
10702
+ async (req, res) => {
10703
+ try {
10704
+ const projectSlug = getParam(req.params.slug);
10705
+ const assignmentSlug = getParam(req.params.aslug);
10706
+ const assignmentPath = resolve20(
10707
+ projectsDir2,
10708
+ projectSlug,
10709
+ "assignments",
10710
+ assignmentSlug,
10711
+ "assignment.md"
10712
+ );
10713
+ await handleWorktreeCreate(req, res, {
10714
+ assignmentPath,
10715
+ projectSlug,
10716
+ assignmentSlug,
10717
+ reload: () => getAssignmentDetail(projectsDir2, projectSlug, assignmentSlug)
10718
+ });
10719
+ } catch (error) {
10720
+ console.error("Error creating worktree:", error);
10721
+ res.status(500).json({ error: `Failed to create worktree: ${error.message}` });
10336
10722
  }
10337
- const { workspaceGroup } = req.body || {};
10338
- if (workspaceGroup !== null && (typeof workspaceGroup !== "string" || !workspaceGroup.trim() || !isValidSlug(workspaceGroup))) {
10339
- res.status(400).json({
10340
- error: "workspaceGroup must be a valid slug (lowercase letters, numbers, hyphens) or null (for ungrouped)."
10723
+ }
10724
+ );
10725
+ router.post(
10726
+ "/api/assignments/:id/worktree",
10727
+ async (req, res) => {
10728
+ try {
10729
+ if (!assignmentsDir2) {
10730
+ res.status(501).json({ error: "Standalone assignments not configured on this server" });
10731
+ return;
10732
+ }
10733
+ const id = getParam(req.params.id);
10734
+ const resolved = await resolveAssignmentById(projectsDir2, assignmentsDir2, id);
10735
+ if (!resolved) {
10736
+ res.status(404).json({ error: `Assignment "${id}" not found` });
10737
+ return;
10738
+ }
10739
+ const assignmentPath = resolve20(resolved.assignmentDir, "assignment.md");
10740
+ const parsedForSlug = parseAssignmentFull(await readFile15(assignmentPath, "utf-8"));
10741
+ const assignmentSlugForBranch = parsedForSlug.slug || resolved.id;
10742
+ await handleWorktreeCreate(req, res, {
10743
+ assignmentPath,
10744
+ projectSlug: resolved.projectSlug ?? "",
10745
+ assignmentSlug: assignmentSlugForBranch,
10746
+ reload: () => getAssignmentDetailById(projectsDir2, assignmentsDir2, id)
10341
10747
  });
10342
- return;
10748
+ } catch (error) {
10749
+ console.error("Error creating worktree:", error);
10750
+ res.status(500).json({ error: `Failed to create worktree: ${error.message}` });
10343
10751
  }
10344
- const assignmentPath = resolve18(resolved.assignmentDir, "assignment.md");
10345
- let content = await readFile13(assignmentPath, "utf-8");
10346
- content = setTopLevelField(content, "workspaceGroup", workspaceGroup ?? null);
10347
- content = setTopLevelField(content, "updated", nowTimestamp());
10348
- await writeFileForce(assignmentPath, content);
10349
- const assignment = await getAssignmentDetailById(projectsDir2, assignmentsDir2, id);
10350
- res.json({ assignment });
10351
- } catch (error) {
10352
- console.error("Error moving assignment workspace:", error);
10353
- res.status(500).json({ error: `Failed to move workspace: ${error.message}` });
10354
10752
  }
10355
- });
10753
+ );
10356
10754
  router.post("/api/projects/:slug/status-override", async (req, res) => {
10357
10755
  try {
10358
10756
  const projectSlug = getParam(req.params.slug);
10359
- const projectPath = resolve18(projectsDir2, projectSlug, "project.md");
10757
+ const projectPath = resolve20(projectsDir2, projectSlug, "project.md");
10360
10758
  if (!await fileExists(projectPath)) {
10361
10759
  res.status(404).json({ error: `Project "${projectSlug}" not found` });
10362
10760
  return;
@@ -10368,7 +10766,7 @@ ${entry}`;
10368
10766
  res.status(400).json({ error: `Invalid status. Must be one of: ${validStatuses.join(", ")}, or null to clear.` });
10369
10767
  return;
10370
10768
  }
10371
- let content = await readFile13(projectPath, "utf-8");
10769
+ let content = await readFile15(projectPath, "utf-8");
10372
10770
  content = setTopLevelField(content, "statusOverride", status ?? null);
10373
10771
  content = setTopLevelField(content, "updated", nowTimestamp());
10374
10772
  await writeFileForce(projectPath, content);
@@ -10383,7 +10781,7 @@ ${entry}`;
10383
10781
  try {
10384
10782
  const projectSlug = getParam(req.params.slug);
10385
10783
  const assignmentSlug = getParam(req.params.aslug);
10386
- const assignmentPath = resolve18(
10784
+ const assignmentPath = resolve20(
10387
10785
  projectsDir2,
10388
10786
  projectSlug,
10389
10787
  "assignments",
@@ -10401,7 +10799,7 @@ ${entry}`;
10401
10799
  res.status(400).json({ error: `Invalid status. Must be one of: ${validStatuses.join(", ")}.` });
10402
10800
  return;
10403
10801
  }
10404
- let content = await readFile13(assignmentPath, "utf-8");
10802
+ let content = await readFile15(assignmentPath, "utf-8");
10405
10803
  content = setTopLevelField(content, "status", status);
10406
10804
  content = setTopLevelField(content, "updated", nowTimestamp());
10407
10805
  if (status !== "blocked") {
@@ -10419,7 +10817,7 @@ ${entry}`;
10419
10817
  try {
10420
10818
  const projectSlug = getParam(req.params.slug);
10421
10819
  const assignmentSlug = getParam(req.params.aslug);
10422
- const assignmentPath = resolve18(
10820
+ const assignmentPath = resolve20(
10423
10821
  projectsDir2,
10424
10822
  projectSlug,
10425
10823
  "assignments",
@@ -10435,7 +10833,7 @@ ${entry}`;
10435
10833
  res.status(400).json({ error: validation.error });
10436
10834
  return;
10437
10835
  }
10438
- let content = await readFile13(assignmentPath, "utf-8");
10836
+ let content = await readFile15(assignmentPath, "utf-8");
10439
10837
  content = setTopLevelField(content, "assignee", validation.value);
10440
10838
  content = setTopLevelField(content, "updated", nowTimestamp());
10441
10839
  await writeFileForce(assignmentPath, content);
@@ -10457,8 +10855,8 @@ ${entry}`;
10457
10855
  res.status(400).json({ error: `Unsupported transition command "${req.params.command}"` });
10458
10856
  return;
10459
10857
  }
10460
- const projectDir = resolve18(projectsDir2, projectSlug);
10461
- const assignmentPath = resolve18(projectDir, "assignments", assignmentSlug, "assignment.md");
10858
+ const projectDir = resolve20(projectsDir2, projectSlug);
10859
+ const assignmentPath = resolve20(projectDir, "assignments", assignmentSlug, "assignment.md");
10462
10860
  if (!await fileExists(assignmentPath)) {
10463
10861
  res.status(404).json({ error: "Assignment not found" });
10464
10862
  return;
@@ -10485,8 +10883,8 @@ ${entry}`;
10485
10883
  try {
10486
10884
  const projectSlug = getParam(req.params.slug);
10487
10885
  const assignmentSlug = getParam(req.params.aslug);
10488
- const assignmentDir = resolve18(projectsDir2, projectSlug, "assignments", assignmentSlug);
10489
- const assignmentPath = resolve18(assignmentDir, "assignment.md");
10886
+ const assignmentDir = resolve20(projectsDir2, projectSlug, "assignments", assignmentSlug);
10887
+ const assignmentPath = resolve20(assignmentDir, "assignment.md");
10490
10888
  if (!await fileExists(assignmentPath)) {
10491
10889
  res.status(404).json({ error: `Assignment "${assignmentSlug}" not found in project "${projectSlug}"` });
10492
10890
  return;
@@ -10541,7 +10939,7 @@ ${entry}`;
10541
10939
  return;
10542
10940
  }
10543
10941
  const id2 = generateId();
10544
- const assignmentDir2 = resolve18(assignmentsDir2, id2);
10942
+ const assignmentDir2 = resolve20(assignmentsDir2, id2);
10545
10943
  if (await fileExists(assignmentDir2)) {
10546
10944
  res.status(500).json({ error: "UUID collision \u2014 try again" });
10547
10945
  return;
@@ -10549,25 +10947,25 @@ ${entry}`;
10549
10947
  const timestamp2 = fields.created || nowTimestamp();
10550
10948
  await ensureDir(assignmentDir2);
10551
10949
  const normalizedContent = setTopLevelField(rawContent, "id", id2);
10552
- await writeFileForce(resolve18(assignmentDir2, "assignment.md"), normalizedContent);
10950
+ await writeFileForce(resolve20(assignmentDir2, "assignment.md"), normalizedContent);
10553
10951
  await writeFileForce(
10554
- resolve18(assignmentDir2, "scratchpad.md"),
10952
+ resolve20(assignmentDir2, "scratchpad.md"),
10555
10953
  renderScratchpad({ assignmentSlug: id2, timestamp: timestamp2 })
10556
10954
  );
10557
10955
  await writeFileForce(
10558
- resolve18(assignmentDir2, "handoff.md"),
10956
+ resolve20(assignmentDir2, "handoff.md"),
10559
10957
  renderHandoff({ assignmentSlug: id2, timestamp: timestamp2 })
10560
10958
  );
10561
10959
  await writeFileForce(
10562
- resolve18(assignmentDir2, "decision-record.md"),
10960
+ resolve20(assignmentDir2, "decision-record.md"),
10563
10961
  renderDecisionRecord({ assignmentSlug: id2, timestamp: timestamp2 })
10564
10962
  );
10565
10963
  await writeFileForce(
10566
- resolve18(assignmentDir2, "progress.md"),
10964
+ resolve20(assignmentDir2, "progress.md"),
10567
10965
  renderProgress({ assignment: id2, timestamp: timestamp2 })
10568
10966
  );
10569
10967
  await writeFileForce(
10570
- resolve18(assignmentDir2, "comments.md"),
10968
+ resolve20(assignmentDir2, "comments.md"),
10571
10969
  renderComments({ assignment: id2, timestamp: timestamp2 })
10572
10970
  );
10573
10971
  const detail2 = await getAssignmentDetailById(projectsDir2, assignmentsDir2, id2);
@@ -10585,7 +10983,7 @@ ${entry}`;
10585
10983
  return;
10586
10984
  }
10587
10985
  const id = generateId();
10588
- const assignmentDir = resolve18(assignmentsDir2, id);
10986
+ const assignmentDir = resolve20(assignmentsDir2, id);
10589
10987
  if (await fileExists(assignmentDir)) {
10590
10988
  res.status(500).json({ error: "UUID collision \u2014 try again" });
10591
10989
  return;
@@ -10605,25 +11003,25 @@ ${entry}`;
10605
11003
  project: null,
10606
11004
  type: typeof type === "string" ? type : void 0
10607
11005
  });
10608
- await writeFileForce(resolve18(assignmentDir, "assignment.md"), assignmentContent);
11006
+ await writeFileForce(resolve20(assignmentDir, "assignment.md"), assignmentContent);
10609
11007
  await writeFileForce(
10610
- resolve18(assignmentDir, "scratchpad.md"),
11008
+ resolve20(assignmentDir, "scratchpad.md"),
10611
11009
  renderScratchpad({ assignmentSlug: id, timestamp })
10612
11010
  );
10613
11011
  await writeFileForce(
10614
- resolve18(assignmentDir, "handoff.md"),
11012
+ resolve20(assignmentDir, "handoff.md"),
10615
11013
  renderHandoff({ assignmentSlug: id, timestamp })
10616
11014
  );
10617
11015
  await writeFileForce(
10618
- resolve18(assignmentDir, "decision-record.md"),
11016
+ resolve20(assignmentDir, "decision-record.md"),
10619
11017
  renderDecisionRecord({ assignmentSlug: id, timestamp })
10620
11018
  );
10621
11019
  await writeFileForce(
10622
- resolve18(assignmentDir, "progress.md"),
11020
+ resolve20(assignmentDir, "progress.md"),
10623
11021
  renderProgress({ assignment: id, timestamp })
10624
11022
  );
10625
11023
  await writeFileForce(
10626
- resolve18(assignmentDir, "comments.md"),
11024
+ resolve20(assignmentDir, "comments.md"),
10627
11025
  renderComments({ assignment: id, timestamp })
10628
11026
  );
10629
11027
  const detail = await getAssignmentDetailById(projectsDir2, assignmentsDir2, id);
@@ -10751,7 +11149,7 @@ ${entry}`;
10751
11149
  res.status(404).json({ error: `Assignment "${id}" not found` });
10752
11150
  return;
10753
11151
  }
10754
- const assignmentPath = resolve18(resolved.assignmentDir, "assignment.md");
11152
+ const assignmentPath = resolve20(resolved.assignmentDir, "assignment.md");
10755
11153
  const currentContent = await readCurrentDocument(assignmentPath);
10756
11154
  if (!currentContent) {
10757
11155
  res.status(404).json({ error: "Assignment not found" });
@@ -10793,7 +11191,7 @@ ${entry}`;
10793
11191
  res.status(404).json({ error: `Assignment "${id}" not found` });
10794
11192
  return;
10795
11193
  }
10796
- const planPath = resolve18(resolved.assignmentDir, "plan.md");
11194
+ const planPath = resolve20(resolved.assignmentDir, "plan.md");
10797
11195
  const currentContent = await readCurrentDocument(planPath);
10798
11196
  if (!currentContent) {
10799
11197
  res.status(404).json({ error: "Plan not found" });
@@ -10827,7 +11225,7 @@ ${entry}`;
10827
11225
  res.status(404).json({ error: `Assignment "${id}" not found` });
10828
11226
  return;
10829
11227
  }
10830
- const scratchpadPath = resolve18(resolved.assignmentDir, "scratchpad.md");
11228
+ const scratchpadPath = resolve20(resolved.assignmentDir, "scratchpad.md");
10831
11229
  const currentContent = await readCurrentDocument(scratchpadPath);
10832
11230
  if (!currentContent) {
10833
11231
  res.status(404).json({ error: "Scratchpad not found" });
@@ -10861,7 +11259,7 @@ ${entry}`;
10861
11259
  res.status(404).json({ error: `Assignment "${id}" not found` });
10862
11260
  return;
10863
11261
  }
10864
- const handoffPath = resolve18(resolved.assignmentDir, "handoff.md");
11262
+ const handoffPath = resolve20(resolved.assignmentDir, "handoff.md");
10865
11263
  const currentContent = await readCurrentDocument(handoffPath);
10866
11264
  if (!currentContent) {
10867
11265
  res.status(404).json({ error: "Handoff log not found" });
@@ -10901,7 +11299,7 @@ ${entry}`;
10901
11299
  res.status(404).json({ error: `Assignment "${id}" not found` });
10902
11300
  return;
10903
11301
  }
10904
- const decisionPath = resolve18(resolved.assignmentDir, "decision-record.md");
11302
+ const decisionPath = resolve20(resolved.assignmentDir, "decision-record.md");
10905
11303
  const currentContent = await readCurrentDocument(decisionPath);
10906
11304
  if (!currentContent) {
10907
11305
  res.status(404).json({ error: "Decision record not found" });
@@ -10941,7 +11339,7 @@ ${entry}`;
10941
11339
  res.status(404).json({ error: `Assignment "${id}" not found` });
10942
11340
  return;
10943
11341
  }
10944
- const assignmentPath = resolve18(resolved.assignmentDir, "assignment.md");
11342
+ const assignmentPath = resolve20(resolved.assignmentDir, "assignment.md");
10945
11343
  if (!await fileExists(assignmentPath)) {
10946
11344
  res.status(404).json({ error: "Assignment not found" });
10947
11345
  return;
@@ -10953,7 +11351,7 @@ ${entry}`;
10953
11351
  res.status(400).json({ error: `Invalid status. Must be one of: ${validStatuses.join(", ")}.` });
10954
11352
  return;
10955
11353
  }
10956
- let content = await readFile13(assignmentPath, "utf-8");
11354
+ let content = await readFile15(assignmentPath, "utf-8");
10957
11355
  content = setTopLevelField(content, "status", status);
10958
11356
  content = setTopLevelField(content, "updated", nowTimestamp());
10959
11357
  if (status !== "blocked") {
@@ -10979,7 +11377,7 @@ ${entry}`;
10979
11377
  res.status(404).json({ error: `Assignment "${id}" not found` });
10980
11378
  return;
10981
11379
  }
10982
- const assignmentPath = resolve18(resolved.assignmentDir, "assignment.md");
11380
+ const assignmentPath = resolve20(resolved.assignmentDir, "assignment.md");
10983
11381
  if (!await fileExists(assignmentPath)) {
10984
11382
  res.status(404).json({ error: "Assignment not found" });
10985
11383
  return;
@@ -10989,7 +11387,7 @@ ${entry}`;
10989
11387
  res.status(400).json({ error: validation.error });
10990
11388
  return;
10991
11389
  }
10992
- let content = await readFile13(assignmentPath, "utf-8");
11390
+ let content = await readFile15(assignmentPath, "utf-8");
10993
11391
  content = setTopLevelField(content, "assignee", validation.value);
10994
11392
  content = setTopLevelField(content, "updated", nowTimestamp());
10995
11393
  await writeFileForce(assignmentPath, content);
@@ -11043,9 +11441,9 @@ ${entry}`;
11043
11441
  if (!resolved) {
11044
11442
  throw new Error(`assignment "${item.id}" not found`);
11045
11443
  }
11046
- assignmentPath = resolve18(resolved.assignmentDir, "assignment.md");
11444
+ assignmentPath = resolve20(resolved.assignmentDir, "assignment.md");
11047
11445
  } else if (typeof item.projectSlug === "string" && typeof item.assignmentSlug === "string" && item.projectSlug && item.assignmentSlug) {
11048
- assignmentPath = resolve18(
11446
+ assignmentPath = resolve20(
11049
11447
  projectsDir2,
11050
11448
  item.projectSlug,
11051
11449
  "assignments",
@@ -11058,7 +11456,7 @@ ${entry}`;
11058
11456
  if (!await fileExists(assignmentPath)) {
11059
11457
  throw new Error("assignment file not found");
11060
11458
  }
11061
- let content = await readFile13(assignmentPath, "utf-8");
11459
+ let content = await readFile15(assignmentPath, "utf-8");
11062
11460
  content = setTopLevelField(content, "status", status);
11063
11461
  content = setTopLevelField(content, "updated", timestamp);
11064
11462
  if (status !== "blocked") {
@@ -11090,7 +11488,7 @@ ${entry}`;
11090
11488
  res.status(404).json({ error: `Assignment "${id}" not found` });
11091
11489
  return;
11092
11490
  }
11093
- const assignmentPath = resolve18(resolved.assignmentDir, "assignment.md");
11491
+ const assignmentPath = resolve20(resolved.assignmentDir, "assignment.md");
11094
11492
  const currentContent = await readCurrentDocument(assignmentPath);
11095
11493
  if (!currentContent) {
11096
11494
  res.status(404).json({ error: "Assignment not found" });
@@ -11182,7 +11580,7 @@ function buildBulkItemKey(raw, index) {
11182
11580
  return `#${index}`;
11183
11581
  }
11184
11582
  async function appendCommentTo(assignmentDir, assignmentRef, req, res, reloadDetail) {
11185
- const commentsPath = resolve18(assignmentDir, "comments.md");
11583
+ const commentsPath = resolve20(assignmentDir, "comments.md");
11186
11584
  const { body, author, type, replyTo } = req.body || {};
11187
11585
  if (!body || typeof body !== "string" || !body.trim()) {
11188
11586
  res.status(400).json({ error: "body is required" });
@@ -11194,7 +11592,7 @@ async function appendCommentTo(assignmentDir, assignmentRef, req, res, reloadDet
11194
11592
  let currentContent;
11195
11593
  let currentCount = 0;
11196
11594
  if (await fileExists(commentsPath)) {
11197
- currentContent = await readFile13(commentsPath, "utf-8");
11595
+ currentContent = await readFile15(commentsPath, "utf-8");
11198
11596
  const countMatch = currentContent.match(/^entryCount:\s*(\d+)/m);
11199
11597
  if (countMatch) currentCount = parseInt(countMatch[1], 10);
11200
11598
  } else {
@@ -11224,7 +11622,7 @@ ${entry}`;
11224
11622
  res.status(201).json({ assignment, comment: { id: comment.id } });
11225
11623
  }
11226
11624
  async function toggleCommentResolvedAt(assignmentDir, commentId, req, res, reloadDetail) {
11227
- const commentsPath = resolve18(assignmentDir, "comments.md");
11625
+ const commentsPath = resolve20(assignmentDir, "comments.md");
11228
11626
  if (!await fileExists(commentsPath)) {
11229
11627
  res.status(404).json({ error: "Comments file not found" });
11230
11628
  return;
@@ -11234,7 +11632,7 @@ async function toggleCommentResolvedAt(assignmentDir, commentId, req, res, reloa
11234
11632
  res.status(400).json({ error: "resolved (boolean) is required" });
11235
11633
  return;
11236
11634
  }
11237
- const content = await readFile13(commentsPath, "utf-8");
11635
+ const content = await readFile15(commentsPath, "utf-8");
11238
11636
  const parsed = parseComments(content);
11239
11637
  const target = parsed.entries.find((e) => e.id === commentId);
11240
11638
  if (!target) {
@@ -11381,7 +11779,7 @@ function createServersRouter(serversDir2, projectsDir2, assignmentsDir2) {
11381
11779
  init_agent_sessions();
11382
11780
  init_fs();
11383
11781
  import { Router as Router3 } from "express";
11384
- import { resolve as resolve19 } from "path";
11782
+ import { resolve as resolve21 } from "path";
11385
11783
 
11386
11784
  // src/utils/transcript.ts
11387
11785
  import { open } from "fs/promises";
@@ -11479,7 +11877,7 @@ function createAgentSessionsRouter(projectsDir2, broadcast, assignmentsDir2) {
11479
11877
  try {
11480
11878
  const { projectSlug } = req.params;
11481
11879
  const assignment = req.query.assignment;
11482
- const projectDir = resolve19(projectsDir2, projectSlug);
11880
+ const projectDir = resolve21(projectsDir2, projectSlug);
11483
11881
  if (!await fileExists(projectDir)) {
11484
11882
  res.status(404).json({ error: `Project "${projectSlug}" not found` });
11485
11883
  return;
@@ -11509,7 +11907,7 @@ function createAgentSessionsRouter(projectsDir2, broadcast, assignmentsDir2) {
11509
11907
  return;
11510
11908
  }
11511
11909
  if (projectSlug) {
11512
- const projectDir = resolve19(projectsDir2, projectSlug);
11910
+ const projectDir = resolve21(projectsDir2, projectSlug);
11513
11911
  if (!await fileExists(projectDir)) {
11514
11912
  res.status(404).json({ error: `Project "${projectSlug}" not found` });
11515
11913
  return;
@@ -11833,14 +12231,136 @@ function createAgentsRouter() {
11833
12231
  return router;
11834
12232
  }
11835
12233
 
11836
- // src/dashboard/api-leases.ts
12234
+ // src/dashboard/api-launch-preflight.ts
12235
+ init_config2();
11837
12236
  import { Router as Router5 } from "express";
11838
12237
 
12238
+ // src/utils/terminal-probe.ts
12239
+ import { spawnSync as spawnSync4 } from "child_process";
12240
+ var APP_BUNDLE_IDS = {
12241
+ "terminal-app": "com.apple.Terminal",
12242
+ iterm: "com.googlecode.iterm2",
12243
+ ghostty: "com.mitchellh.ghostty",
12244
+ warp: "dev.warp.Warp-Stable"
12245
+ };
12246
+ var CLI_NAMES = {
12247
+ alacritty: "alacritty",
12248
+ kitty: "kitty"
12249
+ };
12250
+ function probeTerminalInstalled(terminal) {
12251
+ const bundleId = APP_BUNDLE_IDS[terminal];
12252
+ if (bundleId) {
12253
+ const result = spawnSync4(
12254
+ "mdfind",
12255
+ [`kMDItemCFBundleIdentifier == '${bundleId}'`],
12256
+ { encoding: "utf-8" }
12257
+ );
12258
+ if (result.status === 0 && result.stdout.trim().length > 0) {
12259
+ return { ok: true, foundPath: result.stdout.trim().split("\n")[0] };
12260
+ }
12261
+ return { ok: false, reason: "not-installed" };
12262
+ }
12263
+ const cliName = CLI_NAMES[terminal];
12264
+ if (cliName) {
12265
+ const result = spawnSync4("which", [cliName], { encoding: "utf-8" });
12266
+ if (result.status === 0 && result.stdout.trim().length > 0) {
12267
+ return { ok: true, foundPath: result.stdout.trim() };
12268
+ }
12269
+ return { ok: false, reason: "not-installed" };
12270
+ }
12271
+ return { ok: false, reason: "no-probe-available" };
12272
+ }
12273
+
12274
+ // src/dashboard/api-launch-preflight.ts
12275
+ function createLaunchPreflightRouter() {
12276
+ const router = Router5();
12277
+ router.post("/preflight", async (req, res) => {
12278
+ try {
12279
+ const body = req.body ?? {};
12280
+ if (body.terminal !== void 0 && (typeof body.terminal !== "string" || !TERMINAL_CHOICES.includes(body.terminal))) {
12281
+ res.status(400).json({
12282
+ error: `terminal must be one of: ${TERMINAL_CHOICES.join(", ")}`
12283
+ });
12284
+ return;
12285
+ }
12286
+ const config = await readConfig();
12287
+ const terminal = body.terminal ?? getTerminal(config);
12288
+ const probe = probeTerminalInstalled(terminal);
12289
+ if (probe.ok) {
12290
+ const response2 = { ok: true, terminal };
12291
+ res.json(response2);
12292
+ return;
12293
+ }
12294
+ const suggestedFallback = getTerminal({ ...config, terminal: null });
12295
+ const response = {
12296
+ ok: false,
12297
+ terminal,
12298
+ reason: "not-installed",
12299
+ suggestedFallback
12300
+ };
12301
+ res.json(response);
12302
+ } catch (error) {
12303
+ console.error("Error in launch preflight:", error);
12304
+ res.status(500).json({ error: "preflight failed" });
12305
+ }
12306
+ });
12307
+ return router;
12308
+ }
12309
+
12310
+ // src/dashboard/api-terminal-config.ts
12311
+ init_config2();
12312
+ import { Router as Router6 } from "express";
12313
+ function createTerminalConfigRouter() {
12314
+ const router = Router6();
12315
+ router.get("/", async (_req, res) => {
12316
+ try {
12317
+ const config = await readConfig();
12318
+ res.json({
12319
+ terminal: getTerminal(config),
12320
+ custom: config.terminal !== null
12321
+ });
12322
+ } catch (error) {
12323
+ console.error("Error getting terminal config:", error);
12324
+ res.status(500).json({ error: "Failed to get terminal config" });
12325
+ }
12326
+ });
12327
+ router.post("/", async (req, res) => {
12328
+ try {
12329
+ const { terminal } = req.body ?? {};
12330
+ if (typeof terminal !== "string" || !TERMINAL_CHOICES.includes(terminal)) {
12331
+ res.status(400).json({
12332
+ error: `terminal must be one of: ${TERMINAL_CHOICES.join(", ")}`
12333
+ });
12334
+ return;
12335
+ }
12336
+ await writeTerminalConfig(terminal);
12337
+ res.json({ terminal, custom: true });
12338
+ } catch (error) {
12339
+ console.error("Error saving terminal config:", error);
12340
+ res.status(500).json({ error: "Failed to save terminal config" });
12341
+ }
12342
+ });
12343
+ router.delete("/", async (_req, res) => {
12344
+ try {
12345
+ await deleteTerminalConfig();
12346
+ const config = await readConfig();
12347
+ res.json({ terminal: getTerminal(config), custom: false });
12348
+ } catch (error) {
12349
+ console.error("Error resetting terminal config:", error);
12350
+ res.status(500).json({ error: "Failed to reset terminal config" });
12351
+ }
12352
+ });
12353
+ return router;
12354
+ }
12355
+
12356
+ // src/dashboard/api-leases.ts
12357
+ import { Router as Router7 } from "express";
12358
+
11839
12359
  // src/db/leases-db.ts
11840
12360
  init_paths();
11841
12361
  import Database2 from "better-sqlite3";
11842
12362
  import { randomUUID as randomUUID2 } from "crypto";
11843
- import { resolve as resolve20 } from "path";
12363
+ import { resolve as resolve22 } from "path";
11844
12364
  var db2 = null;
11845
12365
  var LEASE_SCHEMA_VERSION = "1";
11846
12366
  var SCHEMA_SQL2 = `
@@ -11964,7 +12484,7 @@ function isBusyError(err2) {
11964
12484
  }
11965
12485
  function initLeasesDb(dbPath) {
11966
12486
  if (db2) return db2;
11967
- const finalPath = dbPath ?? resolve20(syntaurRoot(), "syntaur.db");
12487
+ const finalPath = dbPath ?? resolve22(syntaurRoot(), "syntaur.db");
11968
12488
  db2 = new Database2(finalPath);
11969
12489
  db2.pragma("journal_mode = WAL");
11970
12490
  db2.pragma("busy_timeout = 5000");
@@ -12465,7 +12985,7 @@ function releaseLeasesByRequestedFor(tag) {
12465
12985
 
12466
12986
  // src/dashboard/api-leases.ts
12467
12987
  function createLeasesRouter(broadcast) {
12468
- const router = Router5();
12988
+ const router = Router7();
12469
12989
  router.get("/", (_req, res) => {
12470
12990
  try {
12471
12991
  initLeasesDb();
@@ -12527,9 +13047,9 @@ init_timestamp();
12527
13047
  init_fs();
12528
13048
  init_playbook();
12529
13049
  init_playbooks();
12530
- import { Router as Router6 } from "express";
12531
- import { resolve as resolve21 } from "path";
12532
- import { readFile as readFile14 } from "fs/promises";
13050
+ import { Router as Router8 } from "express";
13051
+ import { resolve as resolve23 } from "path";
13052
+ import { readFile as readFile16 } from "fs/promises";
12533
13053
  function statusForPlaybookError(code) {
12534
13054
  switch (code) {
12535
13055
  case "manifest":
@@ -12543,7 +13063,7 @@ function statusForPlaybookError(code) {
12543
13063
  }
12544
13064
  }
12545
13065
  function createPlaybooksRouter(playbooksDir3) {
12546
- const router = Router6();
13066
+ const router = Router8();
12547
13067
  router.get("/", async (_req, res) => {
12548
13068
  try {
12549
13069
  const playbooks = await listPlaybooks(playbooksDir3);
@@ -12610,8 +13130,8 @@ function createPlaybooksRouter(playbooksDir3) {
12610
13130
  res.status(404).json({ error: `Playbook "${req.params.slug}" not found` });
12611
13131
  return;
12612
13132
  }
12613
- const filePath = resolve21(playbooksDir3, resolved.filename);
12614
- const content = await readFile14(filePath, "utf-8");
13133
+ const filePath = resolve23(playbooksDir3, resolved.filename);
13134
+ const content = await readFile16(filePath, "utf-8");
12615
13135
  res.json({
12616
13136
  documentType: "playbook",
12617
13137
  title: `Edit Playbook: ${resolved.slug}`,
@@ -12636,7 +13156,7 @@ function createPlaybooksRouter(playbooksDir3) {
12636
13156
  return;
12637
13157
  }
12638
13158
  await ensureDir(playbooksDir3);
12639
- const filePath = resolve21(playbooksDir3, `${slug}.md`);
13159
+ const filePath = resolve23(playbooksDir3, `${slug}.md`);
12640
13160
  if (await fileExists(filePath)) {
12641
13161
  res.status(409).json({ error: `Playbook "${slug}" already exists` });
12642
13162
  return;
@@ -12660,7 +13180,7 @@ function createPlaybooksRouter(playbooksDir3) {
12660
13180
  res.status(404).json({ error: `Playbook "${req.params.slug}" not found` });
12661
13181
  return;
12662
13182
  }
12663
- const filePath = resolve21(playbooksDir3, resolved.filename);
13183
+ const filePath = resolve23(playbooksDir3, resolved.filename);
12664
13184
  await writeFileForce(filePath, content);
12665
13185
  await rebuildPlaybookManifest(playbooksDir3);
12666
13186
  res.json({ slug: resolved.slug, path: filePath });
@@ -12707,8 +13227,8 @@ init_fs_migration();
12707
13227
  init_parser2();
12708
13228
  init_fs();
12709
13229
  init_paths();
12710
- import { Router as Router7 } from "express";
12711
- import { readdir as readdir10 } from "fs/promises";
13230
+ import { Router as Router9 } from "express";
13231
+ import { readdir as readdir11 } from "fs/promises";
12712
13232
  import { resolve as resolvePath, dirname as dirname5 } from "path";
12713
13233
  import { rename as rename4, mkdir as mkdir2 } from "fs/promises";
12714
13234
  init_slug();
@@ -12726,7 +13246,7 @@ function touchItem3(item) {
12726
13246
  item.updatedAt = now;
12727
13247
  }
12728
13248
  function createTodosRouter(todosDir2, broadcast, projectsDir2) {
12729
- const router = Router7();
13249
+ const router = Router9();
12730
13250
  function broadcastUpdate() {
12731
13251
  broadcast({ type: "todos-updated", timestamp: (/* @__PURE__ */ new Date()).toISOString() });
12732
13252
  }
@@ -12842,7 +13362,7 @@ function createTodosRouter(todosDir2, broadcast, projectsDir2) {
12842
13362
  router.get("/", async (_req, res) => {
12843
13363
  try {
12844
13364
  await ensureDir(todosDir2);
12845
- const files = await readdir10(todosDir2).catch(() => []);
13365
+ const files = await readdir11(todosDir2).catch(() => []);
12846
13366
  const workspaces = [];
12847
13367
  for (const file of files) {
12848
13368
  if (typeof file !== "string") continue;
@@ -12955,8 +13475,8 @@ function createTodosRouter(todosDir2, broadcast, projectsDir2) {
12955
13475
  router.post("/:workspace/archive", async (req, res) => {
12956
13476
  try {
12957
13477
  const { archivePath: archivePath2 } = await Promise.resolve().then(() => (init_parser2(), parser_exports));
12958
- const { resolve: resolve70 } = await import("path");
12959
- const { readFile: readFile49 } = await import("fs/promises");
13478
+ const { resolve: resolve72 } = await import("path");
13479
+ const { readFile: readFile50 } = await import("fs/promises");
12960
13480
  const { writeFileForce: writeFileForce2 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
12961
13481
  const workspace = getWorkspaceParam(req.params.workspace);
12962
13482
  const checklist = await readChecklist(todosDir2, workspace);
@@ -12972,10 +13492,10 @@ function createTodosRouter(todosDir2, broadcast, projectsDir2) {
12972
13492
  (e) => e.itemIds.every((id) => completedIds.has(id))
12973
13493
  );
12974
13494
  const archFile = archivePath2(todosDir2, workspace, checklist.archiveInterval);
12975
- await ensureDir(resolve70(todosDir2, "archive"));
13495
+ await ensureDir(resolve72(todosDir2, "archive"));
12976
13496
  let archContent = "";
12977
13497
  if (await fileExists(archFile)) {
12978
- archContent = await readFile49(archFile, "utf-8");
13498
+ archContent = await readFile50(archFile, "utf-8");
12979
13499
  archContent = archContent.trimEnd() + "\n\n";
12980
13500
  } else {
12981
13501
  archContent = `---
@@ -13254,7 +13774,7 @@ workspace: ${workspace}
13254
13774
  const { readConfig: readConfig3 } = await Promise.resolve().then(() => (init_config2(), config_exports));
13255
13775
  const { assignmentsDir: assignmentsDirFn } = await Promise.resolve().then(() => (init_paths(), paths_exports));
13256
13776
  const { fileExists: fileExists2, writeFileForce: writeFileForce2 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
13257
- const { readFile: readFile49 } = await import("fs/promises");
13777
+ const { readFile: readFile50 } = await import("fs/promises");
13258
13778
  const { appendTodosToAssignmentBody: appendTodosToAssignmentBody2, touchAssignmentUpdated: touchAssignmentUpdated2 } = await Promise.resolve().then(() => (init_assignment_todos(), assignment_todos_exports));
13259
13779
  const { nowTimestamp: nowTimestamp3 } = await Promise.resolve().then(() => (init_timestamp(), timestamp_exports));
13260
13780
  let assignmentRef;
@@ -13275,7 +13795,7 @@ workspace: ${workspace}
13275
13795
  }
13276
13796
  const assignmentMdPath2 = resolvePath2(assignmentDir, "assignment.md");
13277
13797
  if (!await fileExists2(assignmentMdPath2)) return { error: `Target assignment not found: ${assignmentMdPath2}` };
13278
- let content = await readFile49(assignmentMdPath2, "utf-8");
13798
+ let content = await readFile50(assignmentMdPath2, "utf-8");
13279
13799
  content = appendTodosToAssignmentBody2(
13280
13800
  content,
13281
13801
  items.map((it) => ({
@@ -13458,9 +13978,9 @@ init_parser2();
13458
13978
  init_fs();
13459
13979
  init_paths();
13460
13980
  init_slug();
13461
- import { Router as Router8 } from "express";
13462
- import { mkdir as mkdir3, readFile as readFile15, rename as rename5 } from "fs/promises";
13463
- import { resolve as resolve22, dirname as dirname6 } from "path";
13981
+ import { Router as Router10 } from "express";
13982
+ import { mkdir as mkdir3, readFile as readFile17, rename as rename5 } from "fs/promises";
13983
+ import { resolve as resolve24, dirname as dirname6 } from "path";
13464
13984
  init_promote_todos();
13465
13985
  var WORKSPACE_REGEX2 = /^[a-z0-9_][a-z0-9-]*$/;
13466
13986
  function touchItem4(item) {
@@ -13476,7 +13996,7 @@ function params(req) {
13476
13996
  return req.params;
13477
13997
  }
13478
13998
  async function projectExists(projectsDir2, slug) {
13479
- return fileExists(resolve22(projectsDir2, slug, "project.md"));
13999
+ return fileExists(resolve24(projectsDir2, slug, "project.md"));
13480
14000
  }
13481
14001
  async function ensureProjectTodosDir(projectsDir2, slug) {
13482
14002
  const todosDir2 = projectTodosDir(projectsDir2, slug);
@@ -13493,7 +14013,7 @@ async function ensureProjectTodosDir(projectsDir2, slug) {
13493
14013
  throw err2;
13494
14014
  }
13495
14015
  try {
13496
- await mkdir3(resolve22(todosDir2, "archive"), { recursive: false });
14016
+ await mkdir3(resolve24(todosDir2, "archive"), { recursive: false });
13497
14017
  } catch (err2) {
13498
14018
  const code = err2.code;
13499
14019
  if (code === "EEXIST") return;
@@ -13509,7 +14029,7 @@ function notFound(res, slug) {
13509
14029
  res.status(404).json({ error: `Project "${slug}" not found` });
13510
14030
  }
13511
14031
  function createProjectTodosRouter(projectsDir2, broadcast, workspaceTodosDir) {
13512
- const router = Router8({ mergeParams: true });
14032
+ const router = Router10({ mergeParams: true });
13513
14033
  function broadcastUpdate(projectSlug) {
13514
14034
  broadcast({ type: "todos-updated", projectSlug, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
13515
14035
  }
@@ -13681,7 +14201,7 @@ function createProjectTodosRouter(projectsDir2, broadcast, workspaceTodosDir) {
13681
14201
  const archFile = archivePath(todosDir2, slug, checklist.archiveInterval);
13682
14202
  let archContent = "";
13683
14203
  if (await fileExists(archFile)) {
13684
- archContent = await readFile15(archFile, "utf-8");
14204
+ archContent = await readFile17(archFile, "utf-8");
13685
14205
  archContent = archContent.trimEnd() + "\n\n";
13686
14206
  } else {
13687
14207
  archContent = `---
@@ -14125,17 +14645,17 @@ workspace: ${slug}
14125
14645
  if (tg.includes("/")) {
14126
14646
  const parts = tg.split("/");
14127
14647
  if (parts.length !== 2) return { error: `Invalid target.assignment "${tg}"` };
14128
- assignmentDir = resolve22(projectsDir2, parts[0], "assignments", parts[1]);
14648
+ assignmentDir = resolve24(projectsDir2, parts[0], "assignments", parts[1]);
14129
14649
  assignmentRef = `${parts[0]}/${parts[1]}`;
14130
14650
  } else if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(tg)) {
14131
- assignmentDir = resolve22(assignmentsDirFn(), tg);
14651
+ assignmentDir = resolve24(assignmentsDirFn(), tg);
14132
14652
  assignmentRef = tg;
14133
14653
  } else {
14134
14654
  return { error: `Invalid target.assignment "${tg}"` };
14135
14655
  }
14136
- const assignmentMdPath2 = resolve22(assignmentDir, "assignment.md");
14656
+ const assignmentMdPath2 = resolve24(assignmentDir, "assignment.md");
14137
14657
  if (!await fileExists(assignmentMdPath2)) return { error: `Target assignment not found: ${assignmentMdPath2}` };
14138
- let content = await readFile15(assignmentMdPath2, "utf-8");
14658
+ let content = await readFile17(assignmentMdPath2, "utf-8");
14139
14659
  content = appendTodosToAssignmentBody2(
14140
14660
  content,
14141
14661
  items.map((it) => ({
@@ -14327,7 +14847,7 @@ workspace: ${slug}
14327
14847
 
14328
14848
  // src/dashboard/api-backup.ts
14329
14849
  init_config2();
14330
- import { Router as Router9 } from "express";
14850
+ import { Router as Router11 } from "express";
14331
14851
 
14332
14852
  // src/utils/github-backup.ts
14333
14853
  init_paths();
@@ -14335,8 +14855,8 @@ init_fs();
14335
14855
  init_config2();
14336
14856
  import { execFile as execFile2 } from "child_process";
14337
14857
  import { promisify as promisify2 } from "util";
14338
- import { cp, mkdtemp, rm as rm2, readFile as readFile16, writeFile as writeFile4, unlink as unlink4, stat, open as open2, rename as rename6 } from "fs/promises";
14339
- import { resolve as resolve23, join as join2 } from "path";
14858
+ import { cp, mkdtemp, rm as rm2, readFile as readFile18, writeFile as writeFile4, unlink as unlink4, stat, open as open2, rename as rename6 } from "fs/promises";
14859
+ import { resolve as resolve25, join as join2 } from "path";
14340
14860
  import { tmpdir } from "os";
14341
14861
  var exec2 = promisify2(execFile2);
14342
14862
  var VALID_CATEGORIES = ["projects", "playbooks", "todos", "servers", "config"];
@@ -14376,7 +14896,7 @@ async function resolveCategoryPath(category) {
14376
14896
  case "servers":
14377
14897
  return { sourcePath: serversDir(), repoPath: "servers", isFile: false };
14378
14898
  case "config":
14379
- return { sourcePath: resolve23(syntaurRoot(), "config.md"), repoPath: "config.md", isFile: true };
14899
+ return { sourcePath: resolve25(syntaurRoot(), "config.md"), repoPath: "config.md", isFile: true };
14380
14900
  }
14381
14901
  }
14382
14902
  async function checkGitInstalled() {
@@ -14387,7 +14907,7 @@ async function checkGitInstalled() {
14387
14907
  }
14388
14908
  }
14389
14909
  async function acquireLock() {
14390
- const lockPath = resolve23(syntaurRoot(), LOCK_FILE_NAME);
14910
+ const lockPath = resolve25(syntaurRoot(), LOCK_FILE_NAME);
14391
14911
  await ensureDir(syntaurRoot());
14392
14912
  try {
14393
14913
  const handle = await open2(lockPath, "wx");
@@ -14396,7 +14916,7 @@ async function acquireLock() {
14396
14916
  return lockPath;
14397
14917
  } catch (err2) {
14398
14918
  if (err2.code === "EEXIST") {
14399
- const pid = await readFile16(lockPath, "utf-8").catch(() => "");
14919
+ const pid = await readFile18(lockPath, "utf-8").catch(() => "");
14400
14920
  throw new Error(
14401
14921
  `Backup operation already in progress (lock file at ${lockPath}, pid ${pid.trim() || "unknown"}). If stale, delete the file and retry.`
14402
14922
  );
@@ -14434,7 +14954,7 @@ async function copyRecursive(src, dest) {
14434
14954
  await ensureDir(dest);
14435
14955
  await cp(src, dest, { recursive: true, force: true });
14436
14956
  } else {
14437
- await ensureDir(resolve23(dest, ".."));
14957
+ await ensureDir(resolve25(dest, ".."));
14438
14958
  await cp(src, dest, { force: true });
14439
14959
  }
14440
14960
  }
@@ -14443,7 +14963,7 @@ function resolveCategoriesStrict(csv) {
14443
14963
  return parseCategoriesStrict(parts);
14444
14964
  }
14445
14965
  async function readSanitizedConfig(configPath) {
14446
- const content = await readFile16(configPath, "utf-8");
14966
+ const content = await readFile18(configPath, "utf-8");
14447
14967
  return content.replace(/^(\s*lastBackup:\s*).*$/m, "$1null").replace(/^(\s*lastRestore:\s*).*$/m, "$1null");
14448
14968
  }
14449
14969
  async function backupToGithub(overrides) {
@@ -14482,7 +15002,7 @@ async function backupToGithub(overrides) {
14482
15002
  }
14483
15003
  if (category === "config") {
14484
15004
  const sanitized = await readSanitizedConfig(sourcePath);
14485
- await ensureDir(resolve23(destPath, ".."));
15005
+ await ensureDir(resolve25(destPath, ".."));
14486
15006
  await writeFile4(destPath, sanitized, "utf-8");
14487
15007
  } else {
14488
15008
  await copyRecursive(sourcePath, destPath);
@@ -14536,7 +15056,7 @@ async function backupToGithub(overrides) {
14536
15056
  }
14537
15057
  async function safeRestoreCategory(localPath, repoSrcPath, isFile) {
14538
15058
  if (isFile) {
14539
- await ensureDir(resolve23(localPath, ".."));
15059
+ await ensureDir(resolve25(localPath, ".."));
14540
15060
  await cp(repoSrcPath, localPath, { force: true });
14541
15061
  return;
14542
15062
  }
@@ -14637,7 +15157,7 @@ async function restoreFromGithub(overrides) {
14637
15157
  }
14638
15158
  async function getBackupStatus() {
14639
15159
  const config = await readConfig();
14640
- const lockPath = resolve23(syntaurRoot(), LOCK_FILE_NAME);
15160
+ const lockPath = resolve25(syntaurRoot(), LOCK_FILE_NAME);
14641
15161
  const locked = await fileExists(lockPath);
14642
15162
  return {
14643
15163
  repo: config.backup?.repo ?? null,
@@ -14650,7 +15170,7 @@ async function getBackupStatus() {
14650
15170
 
14651
15171
  // src/dashboard/api-backup.ts
14652
15172
  function createBackupRouter() {
14653
- const router = Router9();
15173
+ const router = Router11();
14654
15174
  router.get("/", async (_req, res) => {
14655
15175
  try {
14656
15176
  const status = await getBackupStatus();
@@ -14978,7 +15498,7 @@ function createDashboardServer(options) {
14978
15498
  (async () => {
14979
15499
  try {
14980
15500
  const configResult = await migrateLegacyConfig(
14981
- resolve24(syntaurRoot(), "config.md")
15501
+ resolve26(syntaurRoot(), "config.md")
14982
15502
  );
14983
15503
  const projectResult = await migrateLegacyProjectFiles(projectsDir2);
14984
15504
  const summary = summarizeMigration(projectResult, configResult);
@@ -15101,6 +15621,7 @@ function createDashboardServer(options) {
15101
15621
  res.status(500).json({ error: "Failed to reset theme config" });
15102
15622
  }
15103
15623
  });
15624
+ app.use("/api/config/terminal", createTerminalConfigRouter());
15104
15625
  app.get("/api/config/hotkeys", async (_req, res) => {
15105
15626
  try {
15106
15627
  const config = await readConfig();
@@ -15486,6 +16007,7 @@ function createDashboardServer(options) {
15486
16007
  app.use("/api/leases", createLeasesRouter(broadcast));
15487
16008
  app.use("/api/agent-sessions", createAgentSessionsRouter(projectsDir2, broadcast, assignmentsDir2));
15488
16009
  app.use("/api/config/agents", createAgentsRouter());
16010
+ app.use("/api/launch", createLaunchPreflightRouter());
15489
16011
  app.use("/api/playbooks", createPlaybooksRouter(playbooksDir3));
15490
16012
  app.get("/api/memories", async (_req, res) => {
15491
16013
  try {
@@ -15510,14 +16032,14 @@ function createDashboardServer(options) {
15510
16032
  app.use("/api/backup", createBackupRouter());
15511
16033
  if (serveStaticUi && dashboardDistPath) {
15512
16034
  const sendOpts = { dotfiles: "allow" };
15513
- app.use("/assets", express.static(resolve24(dashboardDistPath, "assets"), sendOpts));
16035
+ app.use("/assets", express.static(resolve26(dashboardDistPath, "assets"), sendOpts));
15514
16036
  app.use(express.static(dashboardDistPath, { ...sendOpts, index: false, fallthrough: true }));
15515
16037
  app.get("{*path}", async (req, res) => {
15516
16038
  if (req.path.startsWith("/api") || req.path === "/ws" || req.path.startsWith("/assets")) {
15517
16039
  res.status(404).json({ error: "Not Found" });
15518
16040
  return;
15519
16041
  }
15520
- const indexPath = resolve24(dashboardDistPath, "index.html");
16042
+ const indexPath = resolve26(dashboardDistPath, "index.html");
15521
16043
  if (!await fileExists(indexPath)) {
15522
16044
  res.status(503).send(
15523
16045
  'Dashboard not built. Run "npm run build:dashboard" first.'
@@ -15541,7 +16063,7 @@ function createDashboardServer(options) {
15541
16063
  serversDir: serversDir2,
15542
16064
  playbooksDir: playbooksDir3,
15543
16065
  todosDir: todosDir2,
15544
- dbPath: resolve24(syntaurRoot(), "syntaur.db"),
16066
+ dbPath: resolve26(syntaurRoot(), "syntaur.db"),
15545
16067
  onMessage: broadcast
15546
16068
  });
15547
16069
  startAutodiscovery({ serversDir: serversDir2, projectsDir: projectsDir2, assignmentsDir: assignmentsDir2, excludePids: /* @__PURE__ */ new Set([process.pid]) });
@@ -15556,7 +16078,7 @@ function createDashboardServer(options) {
15556
16078
  }
15557
16079
  });
15558
16080
  server.listen(port, () => {
15559
- const portFile = resolve24(syntaurRoot(), "dashboard-port");
16081
+ const portFile = resolve26(syntaurRoot(), "dashboard-port");
15560
16082
  writeFile5(portFile, String(port), "utf-8").catch(() => {
15561
16083
  });
15562
16084
  resolvePromise();
@@ -15574,7 +16096,7 @@ function createDashboardServer(options) {
15574
16096
  client.terminate();
15575
16097
  }
15576
16098
  clients.clear();
15577
- const portFile = resolve24(syntaurRoot(), "dashboard-port");
16099
+ const portFile = resolve26(syntaurRoot(), "dashboard-port");
15578
16100
  await unlink5(portFile).catch(() => {
15579
16101
  });
15580
16102
  server.closeAllConnections?.();
@@ -15654,8 +16176,8 @@ async function dashboardCommand(options) {
15654
16176
  port = availablePort;
15655
16177
  }
15656
16178
  const thisFile = fileURLToPath2(import.meta.url);
15657
- const packageRoot = resolve25(dirname7(thisFile), "..");
15658
- const dashboardDist = resolve25(packageRoot, "dashboard", "dist");
16179
+ const packageRoot = resolve27(dirname7(thisFile), "..");
16180
+ const dashboardDist = resolve27(packageRoot, "dashboard", "dist");
15659
16181
  const server = createDashboardServer({
15660
16182
  port,
15661
16183
  projectsDir: projectsDir2,
@@ -15669,8 +16191,8 @@ async function dashboardCommand(options) {
15669
16191
  await server.start();
15670
16192
  let viteProcess = null;
15671
16193
  if (mode === "dev") {
15672
- const dashboardDir = resolve25(packageRoot, "dashboard");
15673
- const viteBin = resolve25(dashboardDir, "node_modules", ".bin", "vite");
16194
+ const dashboardDir = resolve27(packageRoot, "dashboard");
16195
+ const viteBin = resolve27(dashboardDir, "node_modules", ".bin", "vite");
15674
16196
  if (!await fileExists(viteBin)) {
15675
16197
  console.error(
15676
16198
  'Vite not found. Run "npm ci --prefix dashboard" first, or use the default bundled dashboard mode.'
@@ -15680,7 +16202,7 @@ async function dashboardCommand(options) {
15680
16202
  }
15681
16203
  console.log(`API server running on http://localhost:${port}`);
15682
16204
  console.log("Starting Vite dev server...");
15683
- viteProcess = spawn(viteBin, [], {
16205
+ viteProcess = spawn2(viteBin, [], {
15684
16206
  cwd: dashboardDir,
15685
16207
  env: {
15686
16208
  ...process.env,
@@ -15745,7 +16267,7 @@ init_config2();
15745
16267
  init_slug();
15746
16268
  init_lifecycle();
15747
16269
  init_assignment_resolver();
15748
- import { resolve as resolve26 } from "path";
16270
+ import { resolve as resolve28 } from "path";
15749
16271
  function resolveLinkedTodosLookup(projectsDir2) {
15750
16272
  return { todosDir: todosDir(), projectsDir: projectsDir2 };
15751
16273
  }
@@ -15759,8 +16281,8 @@ async function runTransition(assignment, command, options = {}) {
15759
16281
  if (!isValidSlug(assignment)) {
15760
16282
  throw new Error(`Invalid assignment slug "${assignment}".`);
15761
16283
  }
15762
- const projectDir = resolve26(baseDir, options.project);
15763
- const projectMdPath = resolve26(projectDir, "project.md");
16284
+ const projectDir = resolve28(baseDir, options.project);
16285
+ const projectMdPath = resolve28(projectDir, "project.md");
15764
16286
  if (!await fileExists(projectDir) || !await fileExists(projectMdPath)) {
15765
16287
  throw new Error(`Project "${options.project}" not found at ${projectDir}.`);
15766
16288
  }
@@ -15793,8 +16315,8 @@ async function runAssign(assignment, agent, options = {}) {
15793
16315
  if (!isValidSlug(assignment)) {
15794
16316
  throw new Error(`Invalid assignment slug "${assignment}".`);
15795
16317
  }
15796
- const projectDir = resolve26(baseDir, options.project);
15797
- const projectMdPath = resolve26(projectDir, "project.md");
16318
+ const projectDir = resolve28(baseDir, options.project);
16319
+ const projectMdPath = resolve28(projectDir, "project.md");
15798
16320
  if (!await fileExists(projectDir) || !await fileExists(projectMdPath)) {
15799
16321
  throw new Error(`Project "${options.project}" not found at ${projectDir}.`);
15800
16322
  }
@@ -15857,8 +16379,8 @@ init_fs();
15857
16379
  init_config2();
15858
16380
  init_frontmatter();
15859
16381
  init_timestamp();
15860
- import { resolve as resolve27 } from "path";
15861
- import { readdir as readdir11, readFile as readFile17 } from "fs/promises";
16382
+ import { resolve as resolve29 } from "path";
16383
+ import { readdir as readdir12, readFile as readFile19 } from "fs/promises";
15862
16384
  var PROMOTABLE_STATUSES = /* @__PURE__ */ new Set(["pending"]);
15863
16385
  function objectiveIsFleshedOut(content) {
15864
16386
  const match = content.match(/##\s+Objective\s*\n([\s\S]*?)(?=\n##\s+|$)/);
@@ -15876,15 +16398,15 @@ async function collectCandidates(baseDirs) {
15876
16398
  const candidates = [];
15877
16399
  for (const baseDir of baseDirs) {
15878
16400
  if (!await fileExists(baseDir)) continue;
15879
- const projects = await readdir11(baseDir, { withFileTypes: true });
16401
+ const projects = await readdir12(baseDir, { withFileTypes: true });
15880
16402
  for (const m of projects) {
15881
16403
  if (!m.isDirectory()) continue;
15882
16404
  if (m.name.startsWith(".") || m.name.startsWith("_")) continue;
15883
- const directAssignmentMd = resolve27(baseDir, m.name, "assignment.md");
16405
+ const directAssignmentMd = resolve29(baseDir, m.name, "assignment.md");
15884
16406
  if (await fileExists(directAssignmentMd)) {
15885
16407
  const fm = await parseSafe(directAssignmentMd);
15886
16408
  if (fm && PROMOTABLE_STATUSES.has(fm.status)) {
15887
- const content = await readFile17(directAssignmentMd, "utf-8");
16409
+ const content = await readFile19(directAssignmentMd, "utf-8");
15888
16410
  if (objectiveIsFleshedOut(content) && hasAcceptanceCriteria(content)) {
15889
16411
  candidates.push({
15890
16412
  projectSlug: null,
@@ -15897,17 +16419,17 @@ async function collectCandidates(baseDirs) {
15897
16419
  }
15898
16420
  continue;
15899
16421
  }
15900
- const assignmentsDir2 = resolve27(baseDir, m.name, "assignments");
16422
+ const assignmentsDir2 = resolve29(baseDir, m.name, "assignments");
15901
16423
  if (!await fileExists(assignmentsDir2)) continue;
15902
- const entries = await readdir11(assignmentsDir2, { withFileTypes: true });
16424
+ const entries = await readdir12(assignmentsDir2, { withFileTypes: true });
15903
16425
  for (const a of entries) {
15904
16426
  if (!a.isDirectory()) continue;
15905
16427
  if (a.name.startsWith(".") || a.name.startsWith("_")) continue;
15906
- const assignmentMd = resolve27(assignmentsDir2, a.name, "assignment.md");
16428
+ const assignmentMd = resolve29(assignmentsDir2, a.name, "assignment.md");
15907
16429
  if (!await fileExists(assignmentMd)) continue;
15908
16430
  const fm = await parseSafe(assignmentMd);
15909
16431
  if (!fm || !PROMOTABLE_STATUSES.has(fm.status)) continue;
15910
- const content = await readFile17(assignmentMd, "utf-8");
16432
+ const content = await readFile19(assignmentMd, "utf-8");
15911
16433
  if (!objectiveIsFleshedOut(content)) continue;
15912
16434
  if (!hasAcceptanceCriteria(content)) continue;
15913
16435
  candidates.push({
@@ -15924,7 +16446,7 @@ async function collectCandidates(baseDirs) {
15924
16446
  }
15925
16447
  async function parseSafe(path) {
15926
16448
  try {
15927
- const content = await readFile17(path, "utf-8");
16449
+ const content = await readFile19(path, "utf-8");
15928
16450
  return parseAssignmentFrontmatter(content);
15929
16451
  } catch {
15930
16452
  return null;
@@ -15953,7 +16475,7 @@ async function migrateStatusesCommand(options) {
15953
16475
  const now = nowTimestamp();
15954
16476
  let migrated = 0;
15955
16477
  for (const c2 of candidates) {
15956
- const content = await readFile17(c2.assignmentMd, "utf-8");
16478
+ const content = await readFile19(c2.assignmentMd, "utf-8");
15957
16479
  const updated = updateAssignmentFile(content, {
15958
16480
  status: c2.toStatus,
15959
16481
  updated: now
@@ -16008,10 +16530,10 @@ init_config2();
16008
16530
  init_fs();
16009
16531
  import {
16010
16532
  cp as cp2,
16011
- readdir as readdir12,
16533
+ readdir as readdir13,
16012
16534
  symlink,
16013
16535
  lstat,
16014
- readFile as readFile18,
16536
+ readFile as readFile20,
16015
16537
  readlink,
16016
16538
  rename as rename7,
16017
16539
  rm as rm3,
@@ -16020,20 +16542,20 @@ import {
16020
16542
  } from "fs/promises";
16021
16543
  import { existsSync } from "fs";
16022
16544
  import { homedir as homedir2 } from "os";
16023
- import { basename as basename4, dirname as dirname9, isAbsolute as isAbsolute2, relative as relative2, resolve as resolve29 } from "path";
16545
+ import { basename as basename4, dirname as dirname9, isAbsolute as isAbsolute3, relative as relative2, resolve as resolve31 } from "path";
16024
16546
 
16025
16547
  // src/utils/package-root.ts
16026
16548
  init_fs();
16027
- import { dirname as dirname8, resolve as resolve28 } from "path";
16549
+ import { dirname as dirname8, resolve as resolve30 } from "path";
16028
16550
  import { fileURLToPath as fileURLToPath3 } from "url";
16029
16551
  async function findPackageRoot(expectedRelativePath) {
16030
16552
  let currentDir = dirname8(fileURLToPath3(import.meta.url));
16031
16553
  while (true) {
16032
- const candidate = resolve28(currentDir, expectedRelativePath);
16554
+ const candidate = resolve30(currentDir, expectedRelativePath);
16033
16555
  if (await fileExists(candidate)) {
16034
16556
  return currentDir;
16035
16557
  }
16036
- const parentDir = resolve28(currentDir, "..");
16558
+ const parentDir = resolve30(currentDir, "..");
16037
16559
  if (parentDir === currentDir) {
16038
16560
  throw new Error(
16039
16561
  `Could not locate package root containing ${expectedRelativePath}.`
@@ -16054,25 +16576,25 @@ function getPluginManifestRelativePath(pluginKind) {
16054
16576
  }
16055
16577
  function getDefaultPluginTargetDir(pluginKind) {
16056
16578
  const home = homedir2();
16057
- return pluginKind === "claude" ? resolve29(home, ".claude", "plugins", "syntaur") : resolve29(home, "plugins", "syntaur");
16579
+ return pluginKind === "claude" ? resolve31(home, ".claude", "plugins", "syntaur") : resolve31(home, "plugins", "syntaur");
16058
16580
  }
16059
16581
  function getDefaultMarketplacePath() {
16060
- return resolve29(homedir2(), ".agents", "plugins", "marketplace.json");
16582
+ return resolve31(homedir2(), ".agents", "plugins", "marketplace.json");
16061
16583
  }
16062
16584
  function getClaudeMarketplacesRoot() {
16063
- return resolve29(homedir2(), ".claude", "plugins", "marketplaces");
16585
+ return resolve31(homedir2(), ".claude", "plugins", "marketplaces");
16064
16586
  }
16065
16587
  function getClaudeKnownMarketplacesPath() {
16066
- return resolve29(homedir2(), ".claude", "plugins", "known_marketplaces.json");
16588
+ return resolve31(homedir2(), ".claude", "plugins", "known_marketplaces.json");
16067
16589
  }
16068
16590
  function getClaudeInstalledPluginsPath() {
16069
- return resolve29(homedir2(), ".claude", "plugins", "installed_plugins.json");
16591
+ return resolve31(homedir2(), ".claude", "plugins", "installed_plugins.json");
16070
16592
  }
16071
16593
  function getInstallMarkerPath(targetDir) {
16072
- return resolve29(targetDir, INSTALL_MARKER_FILENAME);
16594
+ return resolve31(targetDir, INSTALL_MARKER_FILENAME);
16073
16595
  }
16074
16596
  async function readPackageManifest(packageRoot) {
16075
- const raw = await readFile18(resolve29(packageRoot, "package.json"), "utf-8");
16597
+ const raw = await readFile20(resolve31(packageRoot, "package.json"), "utf-8");
16076
16598
  return JSON.parse(raw);
16077
16599
  }
16078
16600
  async function readJsonFileIfExists(pathValue) {
@@ -16080,7 +16602,7 @@ async function readJsonFileIfExists(pathValue) {
16080
16602
  return null;
16081
16603
  }
16082
16604
  try {
16083
- const raw = await readFile18(pathValue, "utf-8");
16605
+ const raw = await readFile20(pathValue, "utf-8");
16084
16606
  return JSON.parse(raw);
16085
16607
  } catch {
16086
16608
  return null;
@@ -16088,15 +16610,15 @@ async function readJsonFileIfExists(pathValue) {
16088
16610
  }
16089
16611
  async function readClaudePluginManifest(pluginDir) {
16090
16612
  return await readJsonFileIfExists(
16091
- resolve29(pluginDir, ".claude-plugin", "plugin.json")
16613
+ resolve31(pluginDir, ".claude-plugin", "plugin.json")
16092
16614
  ) ?? {};
16093
16615
  }
16094
16616
  async function readPluginManifestName(targetDir, pluginKind) {
16095
- const manifestPath = resolve29(targetDir, getPluginManifestRelativePath(pluginKind));
16617
+ const manifestPath = resolve31(targetDir, getPluginManifestRelativePath(pluginKind));
16096
16618
  if (!await fileExists(manifestPath)) {
16097
16619
  return void 0;
16098
16620
  }
16099
- const raw = await readFile18(manifestPath, "utf-8");
16621
+ const raw = await readFile20(manifestPath, "utf-8");
16100
16622
  const parsed = JSON.parse(raw);
16101
16623
  return parsed.name;
16102
16624
  }
@@ -16106,7 +16628,7 @@ async function readInstallMetadata(targetDir) {
16106
16628
  return null;
16107
16629
  }
16108
16630
  try {
16109
- const raw = await readFile18(markerPath, "utf-8");
16631
+ const raw = await readFile20(markerPath, "utf-8");
16110
16632
  return JSON.parse(raw);
16111
16633
  } catch {
16112
16634
  return null;
@@ -16119,7 +16641,7 @@ async function getInstallStatus(targetDir, pluginKind) {
16119
16641
  const info = await lstat(targetDir);
16120
16642
  if (info.isSymbolicLink()) {
16121
16643
  const symlinkTarget = await readlink(targetDir);
16122
- const resolvedTarget = resolve29(dirname9(targetDir), symlinkTarget);
16644
+ const resolvedTarget = resolve31(dirname9(targetDir), symlinkTarget);
16123
16645
  const manifestName2 = await readPluginManifestName(resolvedTarget, pluginKind);
16124
16646
  return {
16125
16647
  exists: true,
@@ -16165,7 +16687,7 @@ async function installLink(paths) {
16165
16687
  await ensureDir(dirname9(paths.targetDir));
16166
16688
  await rm3(paths.targetDir, { recursive: true, force: true });
16167
16689
  await ensureDir(dirname9(paths.targetDir));
16168
- await symlink(resolve29(paths.sourceDir), paths.targetDir, "dir");
16690
+ await symlink(resolve31(paths.sourceDir), paths.targetDir, "dir");
16169
16691
  }
16170
16692
  async function removeInstallMarker(targetDir) {
16171
16693
  const markerPath = getInstallMarkerPath(targetDir);
@@ -16176,16 +16698,16 @@ async function removeInstallMarker(targetDir) {
16176
16698
  }
16177
16699
  function normalizeAbsoluteInstallPath(pathValue, label) {
16178
16700
  const expanded = expandHome(pathValue.trim());
16179
- if (!isAbsolute2(expanded)) {
16701
+ if (!isAbsolute3(expanded)) {
16180
16702
  throw new Error(`${label} must be an absolute path.`);
16181
16703
  }
16182
- return resolve29(expanded);
16704
+ return resolve31(expanded);
16183
16705
  }
16184
16706
  async function resolvePluginPaths(pluginKind, targetDir) {
16185
16707
  const packageRoot = await findPackageRoot(getPluginRelativePath(pluginKind));
16186
16708
  return {
16187
16709
  packageRoot,
16188
- sourceDir: resolve29(packageRoot, getPluginRelativePath(pluginKind)),
16710
+ sourceDir: resolve31(packageRoot, getPluginRelativePath(pluginKind)),
16189
16711
  targetDir: targetDir ?? getDefaultPluginTargetDir(pluginKind)
16190
16712
  };
16191
16713
  }
@@ -16229,7 +16751,7 @@ async function writeClaudeMarketplaceFile(manifestPath, marketplace) {
16229
16751
  }
16230
16752
  if (await fileExists(manifestPath)) {
16231
16753
  try {
16232
- const prev = await readFile18(manifestPath, "utf-8");
16754
+ const prev = await readFile20(manifestPath, "utf-8");
16233
16755
  JSON.parse(prev);
16234
16756
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
16235
16757
  await writeFile6(`${manifestPath}.bak-${stamp}`, prev, "utf-8");
@@ -16302,7 +16824,7 @@ async function listClaudeMarketplaceCandidates() {
16302
16824
  const [knownMarketplaces, activeMarketplaceNames, entries] = await Promise.all([
16303
16825
  readKnownClaudeMarketplaceRecords(),
16304
16826
  readInstalledClaudeMarketplaceNames(),
16305
- readdir12(rootDir, { withFileTypes: true })
16827
+ readdir13(rootDir, { withFileTypes: true })
16306
16828
  ]);
16307
16829
  const candidates = [];
16308
16830
  const seen = /* @__PURE__ */ new Set();
@@ -16310,8 +16832,8 @@ async function listClaudeMarketplaceCandidates() {
16310
16832
  if (!entry.isDirectory()) {
16311
16833
  continue;
16312
16834
  }
16313
- const candidateRoot = resolve29(rootDir, entry.name);
16314
- const manifestPath = resolve29(candidateRoot, ".claude-plugin", "marketplace.json");
16835
+ const candidateRoot = resolve31(rootDir, entry.name);
16836
+ const manifestPath = resolve31(candidateRoot, ".claude-plugin", "marketplace.json");
16315
16837
  if (!await fileExists(manifestPath)) {
16316
16838
  continue;
16317
16839
  }
@@ -16338,11 +16860,11 @@ async function listClaudeMarketplaceCandidates() {
16338
16860
  if (!installLocation) {
16339
16861
  continue;
16340
16862
  }
16341
- const candidateRoot = resolve29(expandHome(installLocation));
16863
+ const candidateRoot = resolve31(expandHome(installLocation));
16342
16864
  if (seen.has(candidateRoot)) {
16343
16865
  continue;
16344
16866
  }
16345
- const manifestPath = resolve29(candidateRoot, ".claude-plugin", "marketplace.json");
16867
+ const manifestPath = resolve31(candidateRoot, ".claude-plugin", "marketplace.json");
16346
16868
  if (!await fileExists(manifestPath)) {
16347
16869
  continue;
16348
16870
  }
@@ -16370,14 +16892,14 @@ async function getPreferredClaudeMarketplace() {
16370
16892
  name: candidate.name,
16371
16893
  rootDir: candidate.rootDir,
16372
16894
  manifestPath: candidate.manifestPath,
16373
- targetDir: resolve29(candidate.rootDir, "plugins", "syntaur")
16895
+ targetDir: resolve31(candidate.rootDir, "plugins", "syntaur")
16374
16896
  };
16375
16897
  }
16376
16898
  async function registerKnownClaudeMarketplace(name, rootDir) {
16377
16899
  const manifestPath = getClaudeKnownMarketplacesPath();
16378
16900
  let existing = {};
16379
16901
  if (await fileExists(manifestPath)) {
16380
- const raw = await readFile18(manifestPath, "utf-8");
16902
+ const raw = await readFile20(manifestPath, "utf-8");
16381
16903
  try {
16382
16904
  const parsed = JSON.parse(raw);
16383
16905
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
@@ -16404,7 +16926,7 @@ async function registerKnownClaudeMarketplace(name, rootDir) {
16404
16926
  existing[name].autoUpdate = true;
16405
16927
  await ensureDir(dirname9(manifestPath));
16406
16928
  if (await fileExists(manifestPath)) {
16407
- const prev = await readFile18(manifestPath, "utf-8");
16929
+ const prev = await readFile20(manifestPath, "utf-8");
16408
16930
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
16409
16931
  await writeFile6(`${manifestPath}.bak-${stamp}`, prev, "utf-8");
16410
16932
  }
@@ -16418,11 +16940,11 @@ async function ensureKnownClaudeMarketplaceForRoot(options) {
16418
16940
  return registerKnownClaudeMarketplace(options.name, options.rootDir);
16419
16941
  }
16420
16942
  async function setSyntaurPluginEnabled(options) {
16421
- const settingsPath = resolve29(homedir2(), ".claude", "settings.json");
16943
+ const settingsPath = resolve31(homedir2(), ".claude", "settings.json");
16422
16944
  const key = `syntaur@${options.marketplaceName}`;
16423
16945
  let parsed = {};
16424
16946
  if (await fileExists(settingsPath)) {
16425
- const raw = await readFile18(settingsPath, "utf-8");
16947
+ const raw = await readFile20(settingsPath, "utf-8");
16426
16948
  try {
16427
16949
  parsed = JSON.parse(raw);
16428
16950
  } catch {
@@ -16441,7 +16963,7 @@ async function setSyntaurPluginEnabled(options) {
16441
16963
  await ensureDir(dirname9(settingsPath));
16442
16964
  if (await fileExists(settingsPath)) {
16443
16965
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
16444
- const prev = await readFile18(settingsPath, "utf-8");
16966
+ const prev = await readFile20(settingsPath, "utf-8");
16445
16967
  await writeFile6(`${settingsPath}.bak-${stamp}`, prev, "utf-8");
16446
16968
  }
16447
16969
  const tmpPath = `${settingsPath}.tmp`;
@@ -16455,9 +16977,9 @@ async function ensureClaudeUserMarketplace() {
16455
16977
  if (existing) {
16456
16978
  return existing;
16457
16979
  }
16458
- const rootDir = resolve29(getClaudeMarketplacesRoot(), "user-plugins");
16459
- const manifestPath = resolve29(rootDir, ".claude-plugin", "marketplace.json");
16460
- await ensureDir(resolve29(rootDir, "plugins"));
16980
+ const rootDir = resolve31(getClaudeMarketplacesRoot(), "user-plugins");
16981
+ const manifestPath = resolve31(rootDir, ".claude-plugin", "marketplace.json");
16982
+ await ensureDir(resolve31(rootDir, "plugins"));
16461
16983
  if (!await fileExists(manifestPath)) {
16462
16984
  const scaffold = {
16463
16985
  plugins: []
@@ -16476,7 +16998,7 @@ async function ensureClaudeUserMarketplace() {
16476
16998
  name: "user-plugins",
16477
16999
  rootDir,
16478
17000
  manifestPath,
16479
- targetDir: resolve29(rootDir, "plugins", "syntaur")
17001
+ targetDir: resolve31(rootDir, "plugins", "syntaur")
16480
17002
  };
16481
17003
  }
16482
17004
  async function detectClaudeMarketplaceForTarget(targetDir) {
@@ -16486,7 +17008,7 @@ async function detectClaudeMarketplaceForTarget(targetDir) {
16486
17008
  return null;
16487
17009
  }
16488
17010
  const rootDir = dirname9(pluginsDir);
16489
- const manifestPath = resolve29(rootDir, ".claude-plugin", "marketplace.json");
17011
+ const manifestPath = resolve31(rootDir, ".claude-plugin", "marketplace.json");
16490
17012
  if (!await fileExists(manifestPath)) {
16491
17013
  return null;
16492
17014
  }
@@ -16502,7 +17024,7 @@ async function detectClaudeMarketplaceForTarget(targetDir) {
16502
17024
  async function findManagedClaudeMarketplacePluginDir() {
16503
17025
  const marketplaces = await listClaudeMarketplaceCandidates();
16504
17026
  for (const marketplace of marketplaces) {
16505
- const targetDir = resolve29(marketplace.rootDir, "plugins", "syntaur");
17027
+ const targetDir = resolve31(marketplace.rootDir, "plugins", "syntaur");
16506
17028
  const status = await getInstallStatus(targetDir, "claude");
16507
17029
  if (status.exists && status.managed) {
16508
17030
  return targetDir;
@@ -16627,7 +17149,7 @@ async function installManagedPlugin(options) {
16627
17149
  `${paths.targetDir} already exists and is not a Syntaur-managed install. Remove it manually before installing Syntaur there.`
16628
17150
  );
16629
17151
  }
16630
- if (desiredMode === "link" && existing.exists && existing.installMode === "link" && existing.symlinkTarget === resolve29(paths.sourceDir) && !force) {
17152
+ if (desiredMode === "link" && existing.exists && existing.installMode === "link" && existing.symlinkTarget === resolve31(paths.sourceDir) && !force) {
16631
17153
  return {
16632
17154
  targetDir: paths.targetDir,
16633
17155
  sourceDir: paths.sourceDir,
@@ -16679,7 +17201,7 @@ async function readMarketplaceFile(marketplacePath) {
16679
17201
  plugins: []
16680
17202
  };
16681
17203
  }
16682
- const raw = await readFile18(marketplacePath, "utf-8");
17204
+ const raw = await readFile20(marketplacePath, "utf-8");
16683
17205
  const parsed = JSON.parse(raw);
16684
17206
  return {
16685
17207
  name: parsed.name ?? "local",
@@ -16840,13 +17362,13 @@ async function recommendMarketplacePath() {
16840
17362
  return configuredOrManaged ?? getDefaultMarketplacePath();
16841
17363
  }
16842
17364
  async function isSyntaurDataInstalled() {
16843
- return fileExists(resolve29(syntaurRoot(), "config.md"));
17365
+ return fileExists(resolve31(syntaurRoot(), "config.md"));
16844
17366
  }
16845
17367
  async function removeSyntaurData() {
16846
17368
  await rm3(syntaurRoot(), { recursive: true, force: true });
16847
17369
  }
16848
17370
  async function getConfiguredProjectDir() {
16849
- if (!await fileExists(resolve29(syntaurRoot(), "config.md"))) {
17371
+ if (!await fileExists(resolve31(syntaurRoot(), "config.md"))) {
16850
17372
  return null;
16851
17373
  }
16852
17374
  return (await readConfig()).defaultProjectDir;
@@ -16915,24 +17437,24 @@ async function textPrompt(question, defaultValue) {
16915
17437
 
16916
17438
  // src/utils/install-skills.ts
16917
17439
  init_fs();
16918
- import { readFile as readFile20, readdir as readdir13, mkdir as mkdir4, copyFile, rm as rm4, lstat as lstat2 } from "fs/promises";
16919
- import { resolve as resolve31, relative as relative3, join as join3 } from "path";
17440
+ import { readFile as readFile22, readdir as readdir14, mkdir as mkdir4, copyFile, rm as rm4, lstat as lstat2 } from "fs/promises";
17441
+ import { resolve as resolve33, relative as relative3, join as join3 } from "path";
16920
17442
  import { homedir as homedir4 } from "os";
16921
17443
 
16922
17444
  // src/utils/plugin-state.ts
16923
17445
  init_fs();
16924
- import { readFile as readFile19 } from "fs/promises";
16925
- import { resolve as resolve30 } from "path";
17446
+ import { readFile as readFile21 } from "fs/promises";
17447
+ import { resolve as resolve32 } from "path";
16926
17448
  import { homedir as homedir3 } from "os";
16927
17449
  function settingsPathFor(agent) {
16928
- if (agent === "claude") return resolve30(homedir3(), ".claude", "settings.json");
17450
+ if (agent === "claude") return resolve32(homedir3(), ".claude", "settings.json");
16929
17451
  return null;
16930
17452
  }
16931
17453
  async function readJsonOrNull(path) {
16932
17454
  if (!path) return null;
16933
17455
  if (!await fileExists(path)) return null;
16934
17456
  try {
16935
- const raw = await readFile19(path, "utf-8");
17457
+ const raw = await readFile21(path, "utf-8");
16936
17458
  return JSON.parse(raw);
16937
17459
  } catch {
16938
17460
  return null;
@@ -16980,16 +17502,16 @@ var KNOWN_SKILL_NAMES = [
16980
17502
  var KNOWN_SKILLS = KNOWN_SKILL_NAMES;
16981
17503
  async function getSkillsDir() {
16982
17504
  const packageRoot = await findPackageRoot("skills");
16983
- return resolve31(packageRoot, "skills");
17505
+ return resolve33(packageRoot, "skills");
16984
17506
  }
16985
17507
  function defaultSkillTargetDir(target) {
16986
- if (target === "claude") return resolve31(homedir4(), ".claude", "skills");
16987
- return resolve31(homedir4(), ".codex", "skills");
17508
+ if (target === "claude") return resolve33(homedir4(), ".claude", "skills");
17509
+ return resolve33(homedir4(), ".codex", "skills");
16988
17510
  }
16989
17511
  async function walkFiles(root) {
16990
17512
  const out = [];
16991
17513
  async function walk(dir) {
16992
- const entries = await readdir13(dir, { withFileTypes: true });
17514
+ const entries = await readdir14(dir, { withFileTypes: true });
16993
17515
  for (const entry of entries) {
16994
17516
  const full = join3(dir, entry.name);
16995
17517
  if (entry.isDirectory()) {
@@ -17004,7 +17526,7 @@ async function walkFiles(root) {
17004
17526
  }
17005
17527
  async function filesEqual(a, b) {
17006
17528
  try {
17007
- const [ba, bb] = await Promise.all([readFile20(a), readFile20(b)]);
17529
+ const [ba, bb] = await Promise.all([readFile22(a), readFile22(b)]);
17008
17530
  if (ba.length !== bb.length) return false;
17009
17531
  return ba.equals(bb);
17010
17532
  } catch {
@@ -17013,7 +17535,7 @@ async function filesEqual(a, b) {
17013
17535
  }
17014
17536
  async function copyDir(srcDir, destDir) {
17015
17537
  await mkdir4(destDir, { recursive: true });
17016
- const entries = await readdir13(srcDir, { withFileTypes: true });
17538
+ const entries = await readdir14(srcDir, { withFileTypes: true });
17017
17539
  for (const entry of entries) {
17018
17540
  const src = join3(srcDir, entry.name);
17019
17541
  const dest = join3(destDir, entry.name);
@@ -17067,7 +17589,7 @@ async function installSkillDir(srcDir, destDir, skillName, force) {
17067
17589
  return { skill: skillName, status: "differs-preserved", targetPath: destDir };
17068
17590
  }
17069
17591
  async function discoverSkillNames(sourceDir) {
17070
- const entries = await readdir13(sourceDir, { withFileTypes: true });
17592
+ const entries = await readdir14(sourceDir, { withFileTypes: true });
17071
17593
  const names = [];
17072
17594
  for (const entry of entries) {
17073
17595
  if (!entry.isDirectory()) continue;
@@ -17126,7 +17648,7 @@ async function uninstallSkills(options) {
17126
17648
  if (await isSymlink(destDir)) continue;
17127
17649
  const skillMd = join3(destDir, "SKILL.md");
17128
17650
  if (!await fileExists(skillMd)) continue;
17129
- const content = await readFile20(skillMd, "utf-8").catch(() => "");
17651
+ const content = await readFile22(skillMd, "utf-8").catch(() => "");
17130
17652
  const match = content.match(/^name:\s*(\S+)\s*$/m);
17131
17653
  if (!match || match[1] !== skill) continue;
17132
17654
  await rm4(destDir, { recursive: true, force: true });
@@ -17315,17 +17837,17 @@ async function installPluginCommand(options) {
17315
17837
  // src/commands/install-statusline.ts
17316
17838
  init_paths();
17317
17839
  init_fs();
17318
- import { readFile as readFile22, writeFile as writeFile8, copyFile as copyFile2, rm as rm5, stat as stat2, symlink as symlink2, unlink as unlink7, lstat as lstat3 } from "fs/promises";
17319
- import { resolve as resolve33, dirname as dirname11 } from "path";
17840
+ import { readFile as readFile24, writeFile as writeFile8, copyFile as copyFile2, rm as rm5, stat as stat2, symlink as symlink2, unlink as unlink7, lstat as lstat3 } from "fs/promises";
17841
+ import { resolve as resolve35, dirname as dirname11 } from "path";
17320
17842
  import { homedir as homedir5 } from "os";
17321
17843
  import { fileURLToPath as fileURLToPath4 } from "url";
17322
17844
 
17323
17845
  // src/commands/configure-statusline.ts
17324
17846
  init_paths();
17325
17847
  init_fs();
17326
- import { readFile as readFile21, writeFile as writeFile7 } from "fs/promises";
17327
- import { resolve as resolve32, dirname as dirname10 } from "path";
17328
- import { spawnSync } from "child_process";
17848
+ import { readFile as readFile23, writeFile as writeFile7 } from "fs/promises";
17849
+ import { resolve as resolve34, dirname as dirname10 } from "path";
17850
+ import { spawnSync as spawnSync5 } from "child_process";
17329
17851
  import { checkbox, input as input2, confirm } from "@inquirer/prompts";
17330
17852
  var AVAILABLE_SEGMENTS = [
17331
17853
  { name: "git", preview: "syntaur:main* +2", description: "repo:branch (with dirty marker and ahead/behind)" },
@@ -17345,12 +17867,12 @@ var PRESETS = {
17345
17867
  tracker: { segments: ["git", "assignment", "external", "session"], separator: " \xB7 " }
17346
17868
  };
17347
17869
  function getConfigPath(installRoot) {
17348
- return resolve32(installRoot, "statusline.config.json");
17870
+ return resolve34(installRoot, "statusline.config.json");
17349
17871
  }
17350
17872
  async function readConfig2(path) {
17351
17873
  if (!await fileExists(path)) return null;
17352
17874
  try {
17353
- const raw = await readFile21(path, "utf-8");
17875
+ const raw = await readFile23(path, "utf-8");
17354
17876
  const parsed = JSON.parse(raw);
17355
17877
  if (!parsed || typeof parsed !== "object") return null;
17356
17878
  const segments = Array.isArray(parsed.segments) ? parsed.segments.filter(isSegmentName) : [];
@@ -17439,7 +17961,7 @@ function renderPreview(config, statuslineScript, cwd) {
17439
17961
  model: { display_name: "Opus 4.7" },
17440
17962
  context_window: { used_percentage: 42 }
17441
17963
  };
17442
- const res = spawnSync("bash", [statuslineScript], {
17964
+ const res = spawnSync5("bash", [statuslineScript], {
17443
17965
  input: JSON.stringify(payload),
17444
17966
  encoding: "utf-8",
17445
17967
  env: {
@@ -17503,7 +18025,7 @@ async function configureStatuslineCommand(options = {}) {
17503
18025
  console.log(` segments: ${config.segments.join(", ")}`);
17504
18026
  console.log(` separator: ${JSON.stringify(config.separator)}`);
17505
18027
  if (config.wrap) console.log(` wrap: ${config.wrap}`);
17506
- const script = options.statuslineScript ?? resolve32(installRoot, "statusline.sh");
18028
+ const script = options.statuslineScript ?? resolve34(installRoot, "statusline.sh");
17507
18029
  if (await fileExists(script)) {
17508
18030
  console.log("");
17509
18031
  console.log("Live preview:");
@@ -17534,11 +18056,11 @@ async function writeDefaultConfigIfMissing(installRoot) {
17534
18056
  // src/commands/install-statusline.ts
17535
18057
  function getPackageStatuslineSource() {
17536
18058
  const here = dirname11(fileURLToPath4(import.meta.url));
17537
- return resolve33(here, "..", "statusline", "statusline.sh");
18059
+ return resolve35(here, "..", "statusline", "statusline.sh");
17538
18060
  }
17539
18061
  async function readSettingsJson(settingsPath) {
17540
18062
  if (!await fileExists(settingsPath)) return {};
17541
- const raw = await readFile22(settingsPath, "utf-8");
18063
+ const raw = await readFile24(settingsPath, "utf-8");
17542
18064
  if (raw.trim() === "") return {};
17543
18065
  try {
17544
18066
  const parsed = JSON.parse(raw);
@@ -17618,12 +18140,12 @@ async function installScript(sourceScript, destScript, link) {
17618
18140
  }
17619
18141
  async function installStatuslineCommand(options = {}) {
17620
18142
  const mode = options.mode ?? "ask";
17621
- const settingsPath = options.settingsPath ?? resolve33(homedir5(), ".claude", "settings.json");
18143
+ const settingsPath = options.settingsPath ?? resolve35(homedir5(), ".claude", "settings.json");
17622
18144
  const installRoot = options.installRoot ?? syntaurRoot();
17623
18145
  const sourceScript = options.sourceScript ?? getPackageStatuslineSource();
17624
- const destScript = resolve33(installRoot, "statusline.sh");
17625
- const confPath = resolve33(installRoot, "statusline.conf");
17626
- const backupPath = resolve33(installRoot, "statusline.backup.json");
18146
+ const destScript = resolve35(installRoot, "statusline.sh");
18147
+ const confPath = resolve35(installRoot, "statusline.conf");
18148
+ const backupPath = resolve35(installRoot, "statusline.backup.json");
17627
18149
  if (!await fileExists(sourceScript)) {
17628
18150
  throw new Error(
17629
18151
  `Statusline source script not found at ${sourceScript}. Try re-installing syntaur (npm install -g syntaur) or pass --source-script explicitly.`
@@ -17659,7 +18181,7 @@ async function installStatuslineCommand(options = {}) {
17659
18181
  if (parsed) {
17660
18182
  wrapTarget = parsed;
17661
18183
  } else {
17662
- const wrapperPath = resolve33(installRoot, "statusline-wrapped.sh");
18184
+ const wrapperPath = resolve35(installRoot, "statusline-wrapped.sh");
17663
18185
  const wrapperBody = `#!/usr/bin/env bash
17664
18186
  # Auto-generated by syntaur install-statusline.
17665
18187
  # Executes the previously configured statusLine command.
@@ -17714,19 +18236,19 @@ async function chmodExec(path) {
17714
18236
  }
17715
18237
  }
17716
18238
  async function uninstallStatuslineCommand(options = {}) {
17717
- const settingsPath = options.settingsPath ?? resolve33(homedir5(), ".claude", "settings.json");
18239
+ const settingsPath = options.settingsPath ?? resolve35(homedir5(), ".claude", "settings.json");
17718
18240
  const installRoot = options.installRoot ?? syntaurRoot();
17719
- const destScript = resolve33(installRoot, "statusline.sh");
17720
- const confPath = resolve33(installRoot, "statusline.conf");
17721
- const backupPath = resolve33(installRoot, "statusline.backup.json");
17722
- const wrapperPath = resolve33(installRoot, "statusline-wrapped.sh");
18241
+ const destScript = resolve35(installRoot, "statusline.sh");
18242
+ const confPath = resolve35(installRoot, "statusline.conf");
18243
+ const backupPath = resolve35(installRoot, "statusline.backup.json");
18244
+ const wrapperPath = resolve35(installRoot, "statusline-wrapped.sh");
17723
18245
  const settings = await readSettingsJson(settingsPath);
17724
18246
  const existing = extractExistingCommand(settings);
17725
18247
  const ourCommand = `bash ${destScript}`;
17726
18248
  let restored = null;
17727
18249
  if (await fileExists(backupPath)) {
17728
18250
  try {
17729
- const raw = await readFile22(backupPath, "utf-8");
18251
+ const raw = await readFile24(backupPath, "utf-8");
17730
18252
  const parsed = JSON.parse(raw);
17731
18253
  const prev = parsed?.previousStatusLine;
17732
18254
  if (prev && typeof prev === "object" && typeof prev.command === "string") {
@@ -17747,7 +18269,7 @@ async function uninstallStatuslineCommand(options = {}) {
17747
18269
  await writeSettingsJson(settingsPath, settings);
17748
18270
  }
17749
18271
  if (!options.keepScript) {
17750
- const configPath = resolve33(installRoot, "statusline.config.json");
18272
+ const configPath = resolve35(installRoot, "statusline.config.json");
17751
18273
  for (const path of [destScript, confPath, backupPath, wrapperPath, configPath]) {
17752
18274
  try {
17753
18275
  await rm5(path, { force: true });
@@ -18004,7 +18526,7 @@ async function setupCommand(options) {
18004
18526
  }
18005
18527
 
18006
18528
  // src/commands/uninstall.ts
18007
- import { resolve as resolve34 } from "path";
18529
+ import { resolve as resolve36 } from "path";
18008
18530
  init_paths();
18009
18531
  function expandTargets(options) {
18010
18532
  if (options.all) {
@@ -18084,7 +18606,7 @@ async function uninstallCommand(options) {
18084
18606
  const configuredProjectDir = await getConfiguredProjectDir();
18085
18607
  await removeSyntaurData();
18086
18608
  console.log(`Removed ${syntaurRoot()}`);
18087
- if (configuredProjectDir && resolve34(configuredProjectDir) !== resolve34(syntaurRoot(), "projects")) {
18609
+ if (configuredProjectDir && resolve36(configuredProjectDir) !== resolve36(syntaurRoot(), "projects")) {
18088
18610
  console.warn(
18089
18611
  `Warning: config.md pointed to an external project directory (${configuredProjectDir}). That directory was not removed automatically.`
18090
18612
  );
@@ -18103,7 +18625,7 @@ init_slug();
18103
18625
  init_cursor_rules();
18104
18626
  init_codex_agents();
18105
18627
  init_opencode_config();
18106
- import { resolve as resolve35 } from "path";
18628
+ import { resolve as resolve37 } from "path";
18107
18629
  var SUPPORTED_FRAMEWORKS = ["cursor", "codex", "opencode"];
18108
18630
  async function setupAdapterCommand(framework, options) {
18109
18631
  if (!SUPPORTED_FRAMEWORKS.includes(framework)) {
@@ -18129,19 +18651,19 @@ async function setupAdapterCommand(framework, options) {
18129
18651
  }
18130
18652
  const config = await readConfig();
18131
18653
  const baseDir = options.dir ? expandHome(options.dir) : config.defaultProjectDir;
18132
- const projectDir = resolve35(baseDir, options.project);
18133
- const assignmentDir = resolve35(
18654
+ const projectDir = resolve37(baseDir, options.project);
18655
+ const assignmentDir = resolve37(
18134
18656
  projectDir,
18135
18657
  "assignments",
18136
18658
  options.assignment
18137
18659
  );
18138
- const projectMdPath = resolve35(projectDir, "project.md");
18660
+ const projectMdPath = resolve37(projectDir, "project.md");
18139
18661
  if (!await fileExists(projectDir) || !await fileExists(projectMdPath)) {
18140
18662
  throw new Error(
18141
18663
  `Project "${options.project}" not found at ${projectDir}.`
18142
18664
  );
18143
18665
  }
18144
- const assignmentMdPath2 = resolve35(assignmentDir, "assignment.md");
18666
+ const assignmentMdPath2 = resolve37(assignmentDir, "assignment.md");
18145
18667
  if (!await fileExists(assignmentDir) || !await fileExists(assignmentMdPath2)) {
18146
18668
  throw new Error(
18147
18669
  `Assignment "${options.assignment}" not found at ${assignmentDir}.`
@@ -18169,15 +18691,15 @@ async function setupAdapterCommand(framework, options) {
18169
18691
  }
18170
18692
  }
18171
18693
  if (framework === "cursor") {
18172
- const protocolPath = resolve35(cwd, ".cursor", "rules", "syntaur-protocol.mdc");
18173
- const assignmentPath = resolve35(cwd, ".cursor", "rules", "syntaur-assignment.mdc");
18694
+ const protocolPath = resolve37(cwd, ".cursor", "rules", "syntaur-protocol.mdc");
18695
+ const assignmentPath = resolve37(cwd, ".cursor", "rules", "syntaur-assignment.mdc");
18174
18696
  await writeAdapterFile(protocolPath, renderCursorProtocol());
18175
18697
  await writeAdapterFile(assignmentPath, renderCursorAssignment(rendererParams));
18176
18698
  } else if (framework === "codex" || framework === "opencode") {
18177
- const agentsPath = resolve35(cwd, "AGENTS.md");
18699
+ const agentsPath = resolve37(cwd, "AGENTS.md");
18178
18700
  await writeAdapterFile(agentsPath, renderCodexAgents(rendererParams));
18179
18701
  if (framework === "opencode") {
18180
- const configPath = resolve35(cwd, "opencode.json");
18702
+ const configPath = resolve37(cwd, "opencode.json");
18181
18703
  await writeAdapterFile(configPath, renderOpenCodeConfig({ projectDir }));
18182
18704
  }
18183
18705
  }
@@ -18202,7 +18724,7 @@ async function setupAdapterCommand(framework, options) {
18202
18724
  init_paths();
18203
18725
  init_fs();
18204
18726
  init_config2();
18205
- import { resolve as resolve36 } from "path";
18727
+ import { resolve as resolve38 } from "path";
18206
18728
  init_session_db();
18207
18729
  init_agent_sessions();
18208
18730
  async function trackSessionCommand(options) {
@@ -18217,7 +18739,7 @@ async function trackSessionCommand(options) {
18217
18739
  if (options.project) {
18218
18740
  const config = await readConfig();
18219
18741
  const baseDir = options.dir ? expandHome(options.dir) : config.defaultProjectDir;
18220
- const projectDir = resolve36(baseDir, options.project);
18742
+ const projectDir = resolve38(baseDir, options.project);
18221
18743
  if (!await fileExists(projectDir)) {
18222
18744
  throw new Error(
18223
18745
  `Project "${options.project}" not found at ${projectDir}.`
@@ -18257,6 +18779,7 @@ init_config2();
18257
18779
  init_paths();
18258
18780
 
18259
18781
  // src/launch/url.ts
18782
+ init_terminal_schema();
18260
18783
  var OpenUrlError = class extends Error {
18261
18784
  code;
18262
18785
  constructor(code, message) {
@@ -18308,6 +18831,24 @@ function parseOpenUrl(input4) {
18308
18831
  "URL has both `assignment` and `session` query params \u2014 only one is allowed"
18309
18832
  );
18310
18833
  }
18834
+ const terminalVals = url.searchParams.getAll("terminal");
18835
+ if (terminalVals.length > 1) {
18836
+ throw new OpenUrlError(
18837
+ "duplicate-param",
18838
+ "URL has more than one `terminal` query param"
18839
+ );
18840
+ }
18841
+ let terminal;
18842
+ if (terminalVals.length === 1 && terminalVals[0].trim() !== "") {
18843
+ const candidate = terminalVals[0];
18844
+ if (!TERMINAL_CHOICES.includes(candidate)) {
18845
+ throw new OpenUrlError(
18846
+ "bad-terminal",
18847
+ `\`terminal\` query param must be one of: ${TERMINAL_CHOICES.join(", ")}`
18848
+ );
18849
+ }
18850
+ terminal = candidate;
18851
+ }
18311
18852
  if (assignmentVals.length === 1) {
18312
18853
  const id = assignmentVals[0];
18313
18854
  if (id.trim() === "") {
@@ -18316,7 +18857,7 @@ function parseOpenUrl(input4) {
18316
18857
  "`assignment` query param is empty"
18317
18858
  );
18318
18859
  }
18319
- return { kind: "assignment", id };
18860
+ return { kind: "assignment", id, ...terminal ? { terminal } : {} };
18320
18861
  }
18321
18862
  if (sessionVals.length === 1) {
18322
18863
  const id = sessionVals[0];
@@ -18341,7 +18882,7 @@ function parseOpenUrl(input4) {
18341
18882
  }
18342
18883
  mode = raw;
18343
18884
  }
18344
- return { kind: "session", id, mode };
18885
+ return { kind: "session", id, mode, ...terminal ? { terminal } : {} };
18345
18886
  }
18346
18887
  throw new OpenUrlError(
18347
18888
  "missing-id",
@@ -18355,11 +18896,11 @@ init_assignment_resolver();
18355
18896
  init_api();
18356
18897
  init_launch();
18357
18898
  init_agent_sessions();
18358
- import { isAbsolute as isAbsolute5 } from "path";
18899
+ import { isAbsolute as isAbsolute6 } from "path";
18359
18900
 
18360
18901
  // src/launch/argv.ts
18361
18902
  init_launch();
18362
- import { isAbsolute as isAbsolute4 } from "path";
18903
+ import { isAbsolute as isAbsolute5 } from "path";
18363
18904
  var buildFreshArgv = buildAgentArgv;
18364
18905
  function buildSessionArgv(agent, sessionId, mode, env = process.env) {
18365
18906
  const invocation = agent[mode];
@@ -18378,7 +18919,7 @@ function buildSessionArgv(agent, sessionId, mode, env = process.env) {
18378
18919
  const requested = env.SHELL;
18379
18920
  let shell = requested;
18380
18921
  let warning = null;
18381
- if (!shell || !isAbsolute4(shell)) {
18922
+ if (!shell || !isAbsolute5(shell)) {
18382
18923
  warning = `syntaur: $SHELL ${requested ? `("${requested}") is not absolute` : "is unset"} \u2014 falling back to /bin/sh for shell-alias resolution`;
18383
18924
  shell = "/bin/sh";
18384
18925
  }
@@ -18414,7 +18955,7 @@ function pickAgent(config) {
18414
18955
  return agents.find((a) => a.default) ?? agents[0];
18415
18956
  }
18416
18957
  async function resolveLaunchPlan(input4) {
18417
- const terminal = getTerminal(input4.config);
18958
+ const terminal = input4.terminalOverride ?? getTerminal(input4.config);
18418
18959
  if (input4.kind === "assignment") {
18419
18960
  return resolveAssignmentPlan(input4, terminal);
18420
18961
  }
@@ -18520,10 +19061,10 @@ async function resolveSessionPlan(input4, terminal) {
18520
19061
  };
18521
19062
  }
18522
19063
  function pickCwd(input4) {
18523
- if (input4.worktreePath && isAbsolute5(input4.worktreePath)) {
19064
+ if (input4.worktreePath && isAbsolute6(input4.worktreePath)) {
18524
19065
  return { cwd: input4.worktreePath, fallbackWarning: null };
18525
19066
  }
18526
- const workspaceDir = input4.repository && isAbsolute5(input4.repository) ? input4.repository : input4.fallbackPath;
19067
+ const workspaceDir = input4.repository && isAbsolute6(input4.repository) ? input4.repository : input4.fallbackPath;
18527
19068
  const fallbackWarning = formatFallbackCwdWarning({
18528
19069
  assignmentSlug: input4.assignmentSlug,
18529
19070
  workspaceDir,
@@ -18535,7 +19076,7 @@ function pickCwd(input4) {
18535
19076
 
18536
19077
  // src/launch/execute.ts
18537
19078
  init_launch();
18538
- import { spawn as spawn3 } from "child_process";
19079
+ import { spawn as spawn4 } from "child_process";
18539
19080
  var TerminalNotFoundError = class extends Error {
18540
19081
  terminal;
18541
19082
  remediation;
@@ -18548,7 +19089,7 @@ var TerminalNotFoundError = class extends Error {
18548
19089
  this.name = "TerminalNotFoundError";
18549
19090
  }
18550
19091
  };
18551
- var realSpawn = (command, args, options) => spawn3(command, args, options);
19092
+ var realSpawn = (command, args, options) => spawn4(command, args, options);
18552
19093
  var WRAPPER_COMMANDS = /* @__PURE__ */ new Set(["osascript", "open"]);
18553
19094
  var WRAPPER_EXIT_TIMEOUT_MS = 1500;
18554
19095
  async function executeLaunchPlan(plan, spawnFn = realSpawn) {
@@ -18575,7 +19116,7 @@ async function executeLaunchPlan(plan, spawnFn = realSpawn) {
18575
19116
  `Spawn failed: ${msg}. Verify the terminal is installed and on PATH.`
18576
19117
  );
18577
19118
  }
18578
- await new Promise((resolve70, reject) => {
19119
+ await new Promise((resolve72, reject) => {
18579
19120
  let settled = false;
18580
19121
  let stderr = "";
18581
19122
  const finishOk = () => {
@@ -18585,7 +19126,7 @@ async function executeLaunchPlan(plan, spawnFn = realSpawn) {
18585
19126
  child.unref();
18586
19127
  } catch {
18587
19128
  }
18588
- resolve70();
19129
+ resolve72();
18589
19130
  };
18590
19131
  const finishErr = (remediation) => {
18591
19132
  if (settled) return;
@@ -18658,19 +19199,19 @@ function buildTerminalInvocation(plan) {
18658
19199
  command: "osascript",
18659
19200
  args: [
18660
19201
  "-e",
18661
- 'tell application "Ghostty"',
19202
+ 'tell application "Ghostty" to activate',
18662
19203
  "-e",
18663
- "activate",
19204
+ "delay 0.3",
18664
19205
  "-e",
18665
- "set newWin to (new window)",
19206
+ 'tell application "System Events"',
18666
19207
  "-e",
18667
- "delay 0.2",
19208
+ 'keystroke "n" using command down',
18668
19209
  "-e",
18669
- "set t to terminal 1 of selected tab of newWin",
19210
+ "delay 0.4",
18670
19211
  "-e",
18671
- `input text ${appleScriptString(cdAndRun)} to t`,
19212
+ `keystroke ${appleScriptString(cdAndRun)}`,
18672
19213
  "-e",
18673
- 'send key "enter" to t',
19214
+ "key code 36",
18674
19215
  "-e",
18675
19216
  "end tell"
18676
19217
  ]
@@ -18714,7 +19255,7 @@ function appleScriptString(value) {
18714
19255
  init_paths();
18715
19256
  init_fs();
18716
19257
  import { fileURLToPath as fileURLToPath5 } from "url";
18717
- import { dirname as dirname12, resolve as resolve38, join as join4 } from "path";
19258
+ import { dirname as dirname12, resolve as resolve40, join as join4 } from "path";
18718
19259
  import { realpathSync, readFileSync, mkdirSync } from "fs";
18719
19260
  var NPX_PATTERNS = [
18720
19261
  { kind: "npm", re: /\/_npx\/([^/]+)\/node_modules(?:\/|$)/ },
@@ -18740,7 +19281,7 @@ function normalizeSlashes(p) {
18740
19281
  }
18741
19282
  function detectInstallKind(scriptUrl, opts = {}) {
18742
19283
  const realpath3 = opts.realpath ?? realpathSync.native;
18743
- const readFile49 = opts.readFile ?? ((p) => readFileSync(p, "utf-8"));
19284
+ const readFile50 = opts.readFile ?? ((p) => readFileSync(p, "utf-8"));
18744
19285
  const ua = opts.envUserAgent !== void 0 ? opts.envUserAgent : process.env.npm_config_user_agent ?? "";
18745
19286
  const resolved = resolveScriptPath(scriptUrl, realpath3);
18746
19287
  if (resolved === null) {
@@ -18761,7 +19302,7 @@ function detectInstallKind(scriptUrl, opts = {}) {
18761
19302
  const pkgJsonPath = join4(dir, "package.json");
18762
19303
  let raw;
18763
19304
  try {
18764
- raw = readFile49(pkgJsonPath);
19305
+ raw = readFile50(pkgJsonPath);
18765
19306
  } catch {
18766
19307
  const parent2 = dirname12(dir);
18767
19308
  if (parent2 === dir) break;
@@ -18793,7 +19334,7 @@ function extractNpxHash(scriptUrl, opts = {}) {
18793
19334
  return null;
18794
19335
  }
18795
19336
  function nudgeStampDir() {
18796
- return resolve38(syntaurRoot(), "npx-handler-nudge");
19337
+ return resolve40(syntaurRoot(), "npx-handler-nudge");
18797
19338
  }
18798
19339
  function sanitizeHash(hash) {
18799
19340
  return hash.replace(/[^A-Za-z0-9_-]/g, "_") || "_";
@@ -18858,7 +19399,8 @@ async function urlCommand(input4, options = {}) {
18858
19399
  mode: parsed.kind === "session" ? parsed.mode : void 0,
18859
19400
  config,
18860
19401
  projectsDir: projectsDir2,
18861
- assignmentsDir: assignmentsDir()
19402
+ assignmentsDir: assignmentsDir(),
19403
+ terminalOverride: parsed.terminal
18862
19404
  });
18863
19405
  if (plan.fallbackWarning) {
18864
19406
  console.error(plan.fallbackWarning);
@@ -18933,9 +19475,8 @@ function formatInstallUrlHandlerError(err2) {
18933
19475
  init_config2();
18934
19476
  init_paths();
18935
19477
  init_fs();
18936
- import { spawnSync as spawnSync3 } from "child_process";
18937
- import { resolve as resolve40, isAbsolute as isAbsolute6 } from "path";
18938
- import { readFile as readFile24 } from "fs/promises";
19478
+ import { resolve as resolve42, isAbsolute as isAbsolute7 } from "path";
19479
+ import { readFile as readFile25 } from "fs/promises";
18939
19480
  import { select, confirm as confirm2, input as input3 } from "@inquirer/prompts";
18940
19481
  async function browseCommand(options) {
18941
19482
  const config = await readConfig();
@@ -19004,7 +19545,7 @@ async function pickAgent2(agents) {
19004
19545
  return picked;
19005
19546
  }
19006
19547
  async function ensureWorktree(opts) {
19007
- const assignmentPath = resolve40(
19548
+ const assignmentPath = resolve42(
19008
19549
  opts.projectsDir,
19009
19550
  opts.projectSlug,
19010
19551
  "assignments",
@@ -19014,7 +19555,7 @@ async function ensureWorktree(opts) {
19014
19555
  if (!await fileExists(assignmentPath)) {
19015
19556
  return void 0;
19016
19557
  }
19017
- const content = await readFile24(assignmentPath, "utf-8");
19558
+ const content = await readFile25(assignmentPath, "utf-8");
19018
19559
  const { parseAssignmentFrontmatter: parseAssignmentFrontmatter2 } = await Promise.resolve().then(() => (init_frontmatter(), frontmatter_exports));
19019
19560
  const fm = parseAssignmentFrontmatter2(content);
19020
19561
  const { workspace } = fm;
@@ -19081,44 +19622,10 @@ async function ensureWorktree(opts) {
19081
19622
  worktreePath
19082
19623
  });
19083
19624
  }
19084
- function computeWorktreeDefaults(opts) {
19085
- const repository = opts.existing.repository ?? detectCurrentGitRoot();
19086
- const branch = opts.projectSlug ? `syntaur/${opts.projectSlug}/${opts.assignmentSlug}` : `syntaur/${opts.assignmentSlug}`;
19087
- const parentBranch = opts.existing.parentBranch ?? detectCurrentBranch() ?? "main";
19088
- const worktreeBase = repository ? resolve40(repository, ".worktrees", branch) : resolve40(
19089
- syntaurRoot(),
19090
- "worktrees",
19091
- opts.projectSlug || "standalone",
19092
- opts.assignmentSlug
19093
- );
19094
- return {
19095
- ...repository ? { repository } : {},
19096
- branch,
19097
- parentBranch,
19098
- worktreePath: worktreeBase
19099
- };
19100
- }
19101
- function detectCurrentGitRoot() {
19102
- const result = spawnSync3("git", ["rev-parse", "--show-toplevel"], {
19103
- encoding: "utf-8"
19104
- });
19105
- if (result.status !== 0) return void 0;
19106
- const out = result.stdout.trim();
19107
- return out.length > 0 ? out : void 0;
19108
- }
19109
- function detectCurrentBranch() {
19110
- const result = spawnSync3("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
19111
- encoding: "utf-8"
19112
- });
19113
- if (result.status !== 0) return void 0;
19114
- const out = result.stdout.trim();
19115
- if (!out || out === "HEAD") return void 0;
19116
- return out;
19117
- }
19118
19625
  async function runCreate(opts) {
19119
19626
  const { createWorktreeAndRecord: createWorktreeAndRecord2, GitWorktreeError: GitWorktreeError2 } = await Promise.resolve().then(() => (init_git_worktree(), git_worktree_exports));
19120
19627
  const expandedWorktree = expandHome(opts.worktreePath);
19121
- const absWorktree = isAbsolute6(expandedWorktree) ? expandedWorktree : resolve40(expandedWorktree);
19628
+ const absWorktree = isAbsolute7(expandedWorktree) ? expandedWorktree : resolve42(expandedWorktree);
19122
19629
  try {
19123
19630
  await createWorktreeAndRecord2({
19124
19631
  assignmentPath: opts.assignmentPath,
@@ -19147,7 +19654,7 @@ init_paths();
19147
19654
  init_fs();
19148
19655
  init_playbook();
19149
19656
  init_playbooks();
19150
- import { resolve as resolve41 } from "path";
19657
+ import { resolve as resolve43 } from "path";
19151
19658
  async function createPlaybookCommand(name, options) {
19152
19659
  if (!name.trim()) {
19153
19660
  throw new Error("Playbook name cannot be empty.");
@@ -19160,7 +19667,7 @@ async function createPlaybookCommand(name, options) {
19160
19667
  }
19161
19668
  const dir = playbooksDir();
19162
19669
  await ensureDir(dir);
19163
- const filePath = resolve41(dir, `${slug}.md`);
19670
+ const filePath = resolve43(dir, `${slug}.md`);
19164
19671
  if (await fileExists(filePath)) {
19165
19672
  throw new Error(
19166
19673
  `Playbook "${slug}" already exists at ${filePath}
@@ -19182,8 +19689,8 @@ init_paths();
19182
19689
  init_fs();
19183
19690
  init_parser();
19184
19691
  init_config2();
19185
- import { readdir as readdir14, readFile as readFile25 } from "fs/promises";
19186
- import { resolve as resolve42 } from "path";
19692
+ import { readdir as readdir15, readFile as readFile26 } from "fs/promises";
19693
+ import { resolve as resolve44 } from "path";
19187
19694
  async function listPlaybooksCommand(options = {}) {
19188
19695
  const dir = playbooksDir();
19189
19696
  if (!await fileExists(dir)) {
@@ -19192,14 +19699,14 @@ async function listPlaybooksCommand(options = {}) {
19192
19699
  }
19193
19700
  const config = await readConfig();
19194
19701
  const disabledSet = new Set(config.playbooks.disabled);
19195
- const entries = await readdir14(dir, { withFileTypes: true });
19702
+ const entries = await readdir15(dir, { withFileTypes: true });
19196
19703
  const mdFiles = entries.filter(
19197
19704
  (e) => e.isFile() && e.name.endsWith(".md") && !e.name.startsWith("_") && e.name !== "manifest.md"
19198
19705
  );
19199
19706
  const rows = [];
19200
19707
  for (const entry of mdFiles) {
19201
- const filePath = resolve42(dir, entry.name);
19202
- const raw = await readFile25(filePath, "utf-8");
19708
+ const filePath = resolve44(dir, entry.name);
19709
+ const raw = await readFile26(filePath, "utf-8");
19203
19710
  const parsed = parsePlaybook(raw);
19204
19711
  const slug = parsed.slug || entry.name.replace(/\.md$/, "");
19205
19712
  const disabled = disabledSet.has(slug);
@@ -19322,8 +19829,8 @@ init_fs();
19322
19829
  init_config2();
19323
19830
  init_slug();
19324
19831
  import { Command } from "commander";
19325
- import { readFile as readFile26 } from "fs/promises";
19326
- import { resolve as resolve43 } from "path";
19832
+ import { readFile as readFile27 } from "fs/promises";
19833
+ import { resolve as resolve45 } from "path";
19327
19834
  var WORKSPACE_REGEX3 = /^[a-z0-9_][a-z0-9-]*$/;
19328
19835
  async function resolveScope(options) {
19329
19836
  const flagCount = [Boolean(options.project), Boolean(options.workspace), Boolean(options.global)].filter(Boolean).length;
@@ -19335,7 +19842,7 @@ async function resolveScope(options) {
19335
19842
  throw new Error(`Invalid project slug: "${options.project}".`);
19336
19843
  }
19337
19844
  const config = await readConfig();
19338
- const projectMd = resolve43(config.defaultProjectDir, options.project, "project.md");
19845
+ const projectMd = resolve45(config.defaultProjectDir, options.project, "project.md");
19339
19846
  if (!await fileExists(projectMd)) {
19340
19847
  throw new Error(`Project "${options.project}" not found.`);
19341
19848
  }
@@ -19656,10 +20163,10 @@ todoCommand.command("archive").description("Archive completed todos and their lo
19656
20163
  (e) => e.itemIds.every((id) => completedIds.has(id))
19657
20164
  );
19658
20165
  const archFile = archivePath(todosPath, workspace, checklist.archiveInterval);
19659
- await ensureDir(resolve43(todosPath, "archive"));
20166
+ await ensureDir(resolve45(todosPath, "archive"));
19660
20167
  let archContent = "";
19661
20168
  if (await fileExists(archFile)) {
19662
- archContent = await readFile26(archFile, "utf-8");
20169
+ archContent = await readFile27(archFile, "utf-8");
19663
20170
  archContent = archContent.trimEnd() + "\n\n";
19664
20171
  } else {
19665
20172
  archContent = `---
@@ -19831,12 +20338,12 @@ function describeScope(scope) {
19831
20338
  }
19832
20339
  async function injectPromotedTodos(assignmentDir, todos, scopeLabel) {
19833
20340
  const { resolve: resolvePath2 } = await import("path");
19834
- const { readFile: readFile49 } = await import("fs/promises");
20341
+ const { readFile: readFile50 } = await import("fs/promises");
19835
20342
  const { writeFileForce: writeFileForce2 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
19836
20343
  const { appendTodosToAssignmentBody: appendTodosToAssignmentBody2, touchAssignmentUpdated: touchAssignmentUpdated2 } = await Promise.resolve().then(() => (init_assignment_todos(), assignment_todos_exports));
19837
20344
  const { nowTimestamp: nowTimestamp3 } = await Promise.resolve().then(() => (init_timestamp(), timestamp_exports));
19838
20345
  const assignmentMdPath2 = resolvePath2(assignmentDir, "assignment.md");
19839
- let content = await readFile49(assignmentMdPath2, "utf-8");
20346
+ let content = await readFile50(assignmentMdPath2, "utf-8");
19840
20347
  content = appendTodosToAssignmentBody2(
19841
20348
  content,
19842
20349
  todos.map((t) => ({
@@ -19881,13 +20388,13 @@ todoCommand.command("plan").description("Create or open a plan directory for a t
19881
20388
  }
19882
20389
  const planDir = todoPlanDir(todosPath, workspace, id);
19883
20390
  await ensureDir(planDir);
19884
- const { readdir: readdir24 } = await import("fs/promises");
19885
- const existingFiles = (await readdir24(planDir).catch(() => [])).filter(
20391
+ const { readdir: readdir25 } = await import("fs/promises");
20392
+ const existingFiles = (await readdir25(planDir).catch(() => [])).filter(
19886
20393
  (f) => /^plan(?:-v\d+)?\.md$/.test(f)
19887
20394
  );
19888
20395
  let target;
19889
20396
  if (existingFiles.length === 0) {
19890
- target = resolve43(planDir, "plan.md");
20397
+ target = resolve45(planDir, "plan.md");
19891
20398
  } else {
19892
20399
  const versions = /* @__PURE__ */ new Set();
19893
20400
  for (const f of existingFiles) {
@@ -19897,7 +20404,7 @@ todoCommand.command("plan").description("Create or open a plan directory for a t
19897
20404
  }
19898
20405
  let n = 2;
19899
20406
  while (versions.has(n)) n++;
19900
- target = resolve43(planDir, `plan-v${n}.md`);
20407
+ target = resolve45(planDir, `plan-v${n}.md`);
19901
20408
  }
19902
20409
  if (!await fileExists(target)) {
19903
20410
  const stub = `---
@@ -20086,12 +20593,12 @@ backupCommand.command("config").description("Show or update backup configuration
20086
20593
 
20087
20594
  // src/commands/doctor.ts
20088
20595
  import { Command as Command3 } from "commander";
20089
- import { readFile as readFile34 } from "fs/promises";
20090
- import { isAbsolute as isAbsolute9, resolve as resolve54 } from "path";
20596
+ import { readFile as readFile35 } from "fs/promises";
20597
+ import { isAbsolute as isAbsolute10, resolve as resolve56 } from "path";
20091
20598
 
20092
20599
  // src/utils/doctor/index.ts
20093
20600
  import { fileURLToPath as fileURLToPath8 } from "url";
20094
- import { readFile as readFile33 } from "fs/promises";
20601
+ import { readFile as readFile34 } from "fs/promises";
20095
20602
  import { dirname as dirname16, join as join8 } from "path";
20096
20603
 
20097
20604
  // src/utils/doctor/context.ts
@@ -20099,11 +20606,11 @@ init_config2();
20099
20606
  init_paths();
20100
20607
  init_fs();
20101
20608
  import Database3 from "better-sqlite3";
20102
- import { resolve as resolve44 } from "path";
20609
+ import { resolve as resolve46 } from "path";
20103
20610
  async function buildCheckContext(cwd = process.cwd()) {
20104
20611
  const config = await readConfig();
20105
20612
  const root = syntaurRoot();
20106
- const dbPath = resolve44(root, "syntaur.db");
20613
+ const dbPath = resolve46(root, "syntaur.db");
20107
20614
  let db4 = null;
20108
20615
  let dbError = null;
20109
20616
  if (await fileExists(dbPath)) {
@@ -20137,8 +20644,8 @@ function closeCheckContext(ctx) {
20137
20644
  // src/utils/doctor/checks/env.ts
20138
20645
  init_fs();
20139
20646
  init_paths();
20140
- import { resolve as resolve45, isAbsolute as isAbsolute7 } from "path";
20141
- import { readFile as readFile27, stat as stat3 } from "fs/promises";
20647
+ import { resolve as resolve47, isAbsolute as isAbsolute8 } from "path";
20648
+ import { readFile as readFile28, stat as stat3 } from "fs/promises";
20142
20649
  import { fileURLToPath as fileURLToPath7 } from "url";
20143
20650
  import { dirname as dirname14, join as join6 } from "path";
20144
20651
  var CATEGORY = "env";
@@ -20178,7 +20685,7 @@ var configValid = {
20178
20685
  category: CATEGORY,
20179
20686
  title: "~/.syntaur/config.md is valid",
20180
20687
  async run(ctx) {
20181
- const configPath = resolve45(ctx.syntaurRoot, "config.md");
20688
+ const configPath = resolve47(ctx.syntaurRoot, "config.md");
20182
20689
  if (!await fileExists(configPath)) {
20183
20690
  return {
20184
20691
  id: this.id,
@@ -20195,7 +20702,7 @@ var configValid = {
20195
20702
  autoFixable: false
20196
20703
  };
20197
20704
  }
20198
- const content = await readFile27(configPath, "utf-8");
20705
+ const content = await readFile28(configPath, "utf-8");
20199
20706
  const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
20200
20707
  if (!fmMatch || fmMatch[1].trim() === "") {
20201
20708
  return {
@@ -20232,7 +20739,7 @@ var configValid = {
20232
20739
  };
20233
20740
  }
20234
20741
  const expanded = expandHome(rawProjectDir);
20235
- if (!isAbsolute7(expanded)) {
20742
+ if (!isAbsolute8(expanded)) {
20236
20743
  return {
20237
20744
  id: this.id,
20238
20745
  category: this.category,
@@ -20475,7 +20982,7 @@ async function readLocalPkg() {
20475
20982
  for (let i = 0; i < 6; i++) {
20476
20983
  const candidate = join6(dir, "package.json");
20477
20984
  try {
20478
- const text = await readFile27(candidate, "utf-8");
20985
+ const text = await readFile28(candidate, "utf-8");
20479
20986
  return JSON.parse(text);
20480
20987
  } catch {
20481
20988
  dir = dirname14(dir);
@@ -20527,8 +21034,8 @@ function versionGte(a, b) {
20527
21034
 
20528
21035
  // src/utils/doctor/checks/structure.ts
20529
21036
  init_fs();
20530
- import { resolve as resolve46 } from "path";
20531
- import { readdir as readdir15, stat as stat4 } from "fs/promises";
21037
+ import { resolve as resolve48 } from "path";
21038
+ import { readdir as readdir16, stat as stat4 } from "fs/promises";
20532
21039
  var CATEGORY2 = "structure";
20533
21040
  var KNOWN_TOP_LEVEL = /* @__PURE__ */ new Set([
20534
21041
  "projects",
@@ -20547,7 +21054,7 @@ var projectsDir = {
20547
21054
  category: CATEGORY2,
20548
21055
  title: "projects/ directory exists",
20549
21056
  async run(ctx) {
20550
- const p = resolve46(ctx.syntaurRoot, "projects");
21057
+ const p = resolve48(ctx.syntaurRoot, "projects");
20551
21058
  if (!await fileExists(p)) {
20552
21059
  return {
20553
21060
  id: this.id,
@@ -20572,7 +21079,7 @@ var playbooksDir2 = {
20572
21079
  category: CATEGORY2,
20573
21080
  title: "playbooks/ directory exists",
20574
21081
  async run(ctx) {
20575
- const p = resolve46(ctx.syntaurRoot, "playbooks");
21082
+ const p = resolve48(ctx.syntaurRoot, "playbooks");
20576
21083
  if (!await fileExists(p)) {
20577
21084
  return {
20578
21085
  id: this.id,
@@ -20597,7 +21104,7 @@ var todosDirValid = {
20597
21104
  category: CATEGORY2,
20598
21105
  title: "todos/ directory is readable (if present)",
20599
21106
  async run(ctx) {
20600
- const p = resolve46(ctx.syntaurRoot, "todos");
21107
+ const p = resolve48(ctx.syntaurRoot, "todos");
20601
21108
  if (!await fileExists(p)) {
20602
21109
  return {
20603
21110
  id: this.id,
@@ -20628,7 +21135,7 @@ var serversDirValid = {
20628
21135
  category: CATEGORY2,
20629
21136
  title: "servers/ directory is readable (if present)",
20630
21137
  async run(ctx) {
20631
- const p = resolve46(ctx.syntaurRoot, "servers");
21138
+ const p = resolve48(ctx.syntaurRoot, "servers");
20632
21139
  if (!await fileExists(p)) {
20633
21140
  return {
20634
21141
  id: this.id,
@@ -20659,7 +21166,7 @@ var knownFilesRecognized = {
20659
21166
  category: CATEGORY2,
20660
21167
  title: "No unexpected top-level entries under ~/.syntaur/",
20661
21168
  async run(ctx) {
20662
- const entries = await readdir15(ctx.syntaurRoot, { withFileTypes: true });
21169
+ const entries = await readdir16(ctx.syntaurRoot, { withFileTypes: true });
20663
21170
  const unexpected = [];
20664
21171
  for (const e of entries) {
20665
21172
  if (e.name.startsWith(".")) continue;
@@ -20673,7 +21180,7 @@ var knownFilesRecognized = {
20673
21180
  title: this.title,
20674
21181
  status: "warn",
20675
21182
  detail: `unexpected top-level entries: ${unexpected.join(", ")}`,
20676
- affected: unexpected.map((n) => resolve46(ctx.syntaurRoot, n)),
21183
+ affected: unexpected.map((n) => resolve48(ctx.syntaurRoot, n)),
20677
21184
  remediation: {
20678
21185
  kind: "manual",
20679
21186
  suggestion: "Review these entries \u2014 they may be leftover state from older versions",
@@ -20702,8 +21209,8 @@ function pass2(check) {
20702
21209
 
20703
21210
  // src/utils/doctor/checks/project.ts
20704
21211
  init_fs();
20705
- import { resolve as resolve47 } from "path";
20706
- import { readdir as readdir16, stat as stat5 } from "fs/promises";
21212
+ import { resolve as resolve49 } from "path";
21213
+ import { readdir as readdir17, stat as stat5 } from "fs/promises";
20707
21214
  var CATEGORY3 = "project";
20708
21215
  var REQUIRED_PROJECT_FILES = [
20709
21216
  "project.md",
@@ -20727,15 +21234,15 @@ var PROJECT_MARKERS = ["project.md", "manifest.md", "assignments"];
20727
21234
  async function listProjects2(ctx) {
20728
21235
  const dir = ctx.config.defaultProjectDir;
20729
21236
  if (!await fileExists(dir)) return [];
20730
- const entries = await readdir16(dir, { withFileTypes: true });
21237
+ const entries = await readdir17(dir, { withFileTypes: true });
20731
21238
  const result = [];
20732
21239
  for (const e of entries) {
20733
21240
  if (!e.isDirectory()) continue;
20734
21241
  if (e.name.startsWith(".") || e.name.startsWith("_")) continue;
20735
- const projectDir = resolve47(dir, e.name);
21242
+ const projectDir = resolve49(dir, e.name);
20736
21243
  let looksLikeProject = false;
20737
21244
  for (const marker of PROJECT_MARKERS) {
20738
- if (await fileExists(resolve47(projectDir, marker))) {
21245
+ if (await fileExists(resolve49(projectDir, marker))) {
20739
21246
  looksLikeProject = true;
20740
21247
  break;
20741
21248
  }
@@ -20754,7 +21261,7 @@ var requiredFiles = {
20754
21261
  for (const projectDir of projects) {
20755
21262
  const missing = [];
20756
21263
  for (const rel of REQUIRED_PROJECT_FILES) {
20757
- const p = resolve47(projectDir, rel);
21264
+ const p = resolve49(projectDir, rel);
20758
21265
  if (!await fileExists(p)) missing.push(rel);
20759
21266
  }
20760
21267
  if (missing.length === 0) continue;
@@ -20764,7 +21271,7 @@ var requiredFiles = {
20764
21271
  title: this.title,
20765
21272
  status: "error",
20766
21273
  detail: `project at ${projectDir} is missing: ${missing.join(", ")}`,
20767
- affected: missing.map((m) => resolve47(projectDir, m)),
21274
+ affected: missing.map((m) => resolve49(projectDir, m)),
20768
21275
  remediation: {
20769
21276
  kind: "manual",
20770
21277
  suggestion: "Recreate the missing scaffold files from templates",
@@ -20787,7 +21294,7 @@ var manifestStale = {
20787
21294
  const projects = await listProjects2(ctx);
20788
21295
  const results = [];
20789
21296
  for (const projectDir of projects) {
20790
- const manifestPath = resolve47(projectDir, "manifest.md");
21297
+ const manifestPath = resolve49(projectDir, "manifest.md");
20791
21298
  if (!await fileExists(manifestPath)) continue;
20792
21299
  const manifestMtime = (await stat5(manifestPath)).mtimeMs;
20793
21300
  const newestAssignment = await newestAssignmentMtime(projectDir);
@@ -20821,7 +21328,7 @@ var orphanFiles = {
20821
21328
  const projects = await listProjects2(ctx);
20822
21329
  const results = [];
20823
21330
  for (const projectDir of projects) {
20824
- const entries = await readdir16(projectDir, { withFileTypes: true });
21331
+ const entries = await readdir17(projectDir, { withFileTypes: true });
20825
21332
  const orphans = [];
20826
21333
  for (const e of entries) {
20827
21334
  if (e.name.startsWith(".")) continue;
@@ -20836,7 +21343,7 @@ var orphanFiles = {
20836
21343
  title: this.title,
20837
21344
  status: "warn",
20838
21345
  detail: `project at ${projectDir} has unexpected entries: ${orphans.join(", ")}`,
20839
- affected: orphans.map((o) => resolve47(projectDir, o)),
21346
+ affected: orphans.map((o) => resolve49(projectDir, o)),
20840
21347
  autoFixable: false
20841
21348
  });
20842
21349
  }
@@ -20846,18 +21353,18 @@ var orphanFiles = {
20846
21353
  };
20847
21354
  var projectChecks = [requiredFiles, manifestStale, orphanFiles];
20848
21355
  async function newestAssignmentMtime(projectDir) {
20849
- const assignmentsRoot = resolve47(projectDir, "assignments");
21356
+ const assignmentsRoot = resolve49(projectDir, "assignments");
20850
21357
  if (!await fileExists(assignmentsRoot)) return 0;
20851
21358
  let newest = 0;
20852
21359
  let entries;
20853
21360
  try {
20854
- entries = await readdir16(assignmentsRoot, { withFileTypes: true });
21361
+ entries = await readdir17(assignmentsRoot, { withFileTypes: true });
20855
21362
  } catch {
20856
21363
  return 0;
20857
21364
  }
20858
21365
  for (const e of entries) {
20859
21366
  if (!e.isDirectory()) continue;
20860
- const assignmentMd = resolve47(assignmentsRoot, e.name, "assignment.md");
21367
+ const assignmentMd = resolve49(assignmentsRoot, e.name, "assignment.md");
20861
21368
  try {
20862
21369
  const s = await stat5(assignmentMd);
20863
21370
  if (s.mtimeMs > newest) newest = s.mtimeMs;
@@ -20881,8 +21388,8 @@ init_fs();
20881
21388
  init_parser();
20882
21389
  init_types();
20883
21390
  init_paths();
20884
- import { resolve as resolve48 } from "path";
20885
- import { readdir as readdir17, readFile as readFile28 } from "fs/promises";
21391
+ import { resolve as resolve50 } from "path";
21392
+ import { readdir as readdir18, readFile as readFile29 } from "fs/promises";
20886
21393
  var CATEGORY4 = "assignment";
20887
21394
  var STATUSES_REQUIRING_HANDOFF = /* @__PURE__ */ new Set(["review", "completed"]);
20888
21395
  var PRE_WORKSPACE_STATUSES = /* @__PURE__ */ new Set([
@@ -20916,20 +21423,20 @@ async function listAssignments(ctx) {
20916
21423
  const result = { withAssignmentMd: [], orphanFolders: [] };
20917
21424
  const projectsDir2 = ctx.config.defaultProjectDir;
20918
21425
  if (await fileExists(projectsDir2)) {
20919
- const projects = await readdir17(projectsDir2, { withFileTypes: true });
21426
+ const projects = await readdir18(projectsDir2, { withFileTypes: true });
20920
21427
  for (const m of projects) {
20921
21428
  if (!m.isDirectory()) continue;
20922
21429
  if (m.name.startsWith(".") || m.name.startsWith("_")) continue;
20923
- const assignmentsDir2 = resolve48(projectsDir2, m.name, "assignments");
21430
+ const assignmentsDir2 = resolve50(projectsDir2, m.name, "assignments");
20924
21431
  if (!await fileExists(assignmentsDir2)) continue;
20925
- const entries = await readdir17(assignmentsDir2, { withFileTypes: true });
21432
+ const entries = await readdir18(assignmentsDir2, { withFileTypes: true });
20926
21433
  for (const a of entries) {
20927
21434
  if (!a.isDirectory()) continue;
20928
21435
  if (a.name.startsWith(".") || a.name.startsWith("_")) continue;
20929
- const assignmentDir = resolve48(assignmentsDir2, a.name);
20930
- const assignmentMd = resolve48(assignmentDir, "assignment.md");
21436
+ const assignmentDir = resolve50(assignmentsDir2, a.name);
21437
+ const assignmentMd = resolve50(assignmentDir, "assignment.md");
20931
21438
  const entry = {
20932
- projectDir: resolve48(projectsDir2, m.name),
21439
+ projectDir: resolve50(projectsDir2, m.name),
20933
21440
  projectSlug: m.name,
20934
21441
  assignmentDir,
20935
21442
  assignmentSlug: a.name,
@@ -20945,12 +21452,12 @@ async function listAssignments(ctx) {
20945
21452
  }
20946
21453
  const standaloneRoot = assignmentsDir();
20947
21454
  if (await fileExists(standaloneRoot)) {
20948
- const entries = await readdir17(standaloneRoot, { withFileTypes: true });
21455
+ const entries = await readdir18(standaloneRoot, { withFileTypes: true });
20949
21456
  for (const a of entries) {
20950
21457
  if (!a.isDirectory()) continue;
20951
21458
  if (a.name.startsWith(".") || a.name.startsWith("_")) continue;
20952
- const assignmentDir = resolve48(standaloneRoot, a.name);
20953
- const assignmentMd = resolve48(assignmentDir, "assignment.md");
21459
+ const assignmentDir = resolve50(standaloneRoot, a.name);
21460
+ const assignmentMd = resolve50(assignmentDir, "assignment.md");
20954
21461
  const entry = {
20955
21462
  projectDir: standaloneRoot,
20956
21463
  projectSlug: null,
@@ -21028,7 +21535,7 @@ var invalidStatus = {
21028
21535
  const allowed = configuredStatuses(ctx);
21029
21536
  const results = [];
21030
21537
  for (const a of withAssignmentMd) {
21031
- const path = resolve48(a.assignmentDir, "assignment.md");
21538
+ const path = resolve50(a.assignmentDir, "assignment.md");
21032
21539
  const parsed = await parseSafe2(path);
21033
21540
  if (!parsed) continue;
21034
21541
  if (!allowed.has(parsed.status)) {
@@ -21061,7 +21568,7 @@ var workspaceMissing = {
21061
21568
  const terminal = terminalStatuses(ctx);
21062
21569
  const results = [];
21063
21570
  for (const a of withAssignmentMd) {
21064
- const path = resolve48(a.assignmentDir, "assignment.md");
21571
+ const path = resolve50(a.assignmentDir, "assignment.md");
21065
21572
  const parsed = await parseSafe2(path);
21066
21573
  if (!parsed) continue;
21067
21574
  if (terminal.has(parsed.status)) continue;
@@ -21108,12 +21615,12 @@ var requiredFilesByStatus = {
21108
21615
  const { withAssignmentMd } = await listAssignments(ctx);
21109
21616
  const results = [];
21110
21617
  for (const a of withAssignmentMd) {
21111
- const assignmentPath = resolve48(a.assignmentDir, "assignment.md");
21618
+ const assignmentPath = resolve50(a.assignmentDir, "assignment.md");
21112
21619
  const parsed = await parseSafe2(assignmentPath);
21113
21620
  if (!parsed) continue;
21114
21621
  const missing = [];
21115
21622
  if (STATUSES_REQUIRING_HANDOFF.has(parsed.status)) {
21116
- const handoffPath = resolve48(a.assignmentDir, "handoff.md");
21623
+ const handoffPath = resolve50(a.assignmentDir, "handoff.md");
21117
21624
  if (!await fileExists(handoffPath)) missing.push("handoff.md");
21118
21625
  }
21119
21626
  if (missing.length === 0) continue;
@@ -21123,7 +21630,7 @@ var requiredFilesByStatus = {
21123
21630
  title: this.title,
21124
21631
  status: "warn",
21125
21632
  detail: `${a.projectSlug}/${a.assignmentSlug} (status: ${parsed.status}) is missing ${missing.join(", ")}`,
21126
- affected: missing.map((m) => resolve48(a.assignmentDir, m)),
21633
+ affected: missing.map((m) => resolve50(a.assignmentDir, m)),
21127
21634
  remediation: {
21128
21635
  kind: "manual",
21129
21636
  suggestion: `Create the missing ${missing.join(" and ")} files for this assignment`,
@@ -21146,7 +21653,7 @@ var companionFilesScaffolded = {
21146
21653
  for (const a of withAssignmentMd) {
21147
21654
  const missing = [];
21148
21655
  for (const filename of ["progress.md", "comments.md"]) {
21149
- if (!await fileExists(resolve48(a.assignmentDir, filename))) {
21656
+ if (!await fileExists(resolve50(a.assignmentDir, filename))) {
21150
21657
  missing.push(filename);
21151
21658
  }
21152
21659
  }
@@ -21158,7 +21665,7 @@ var companionFilesScaffolded = {
21158
21665
  title: this.title,
21159
21666
  status: "warn",
21160
21667
  detail: `${label} is missing ${missing.join(" and ")} (pre-v2.0 assignment \u2014 not required, but scaffolding them keeps the dashboard and CLIs consistent)`,
21161
- affected: missing.map((m) => resolve48(a.assignmentDir, m)),
21668
+ affected: missing.map((m) => resolve50(a.assignmentDir, m)),
21162
21669
  remediation: {
21163
21670
  kind: "manual",
21164
21671
  suggestion: `Create ${missing.join(" and ")} with the renderProgress/renderComments templates, or re-scaffold via the CLI`,
@@ -21191,7 +21698,7 @@ var typeDefinition = {
21191
21698
  const { withAssignmentMd } = await listAssignments(ctx);
21192
21699
  const results = [];
21193
21700
  for (const a of withAssignmentMd) {
21194
- const path = resolve48(a.assignmentDir, "assignment.md");
21701
+ const path = resolve50(a.assignmentDir, "assignment.md");
21195
21702
  const parsed = await parseSafe2(path);
21196
21703
  if (!parsed) continue;
21197
21704
  if (!parsed.type) continue;
@@ -21225,7 +21732,7 @@ var projectFrontmatterMatchesContainer = {
21225
21732
  const { withAssignmentMd } = await listAssignments(ctx);
21226
21733
  const results = [];
21227
21734
  for (const a of withAssignmentMd) {
21228
- const path = resolve48(a.assignmentDir, "assignment.md");
21735
+ const path = resolve50(a.assignmentDir, "assignment.md");
21229
21736
  const parsed = await parseSafe2(path);
21230
21737
  if (!parsed) continue;
21231
21738
  if (a.standalone) {
@@ -21276,13 +21783,13 @@ var draftMissingObjective = {
21276
21783
  const { withAssignmentMd } = await listAssignments(ctx);
21277
21784
  const results = [];
21278
21785
  for (const a of withAssignmentMd) {
21279
- const path = resolve48(a.assignmentDir, "assignment.md");
21786
+ const path = resolve50(a.assignmentDir, "assignment.md");
21280
21787
  const parsed = await parseSafe2(path);
21281
21788
  if (!parsed) continue;
21282
21789
  if (parsed.status !== "draft") continue;
21283
21790
  let raw;
21284
21791
  try {
21285
- raw = await readFile28(path, "utf-8");
21792
+ raw = await readFile29(path, "utf-8");
21286
21793
  } catch {
21287
21794
  continue;
21288
21795
  }
@@ -21315,16 +21822,16 @@ var readyToImplementMissingPlan = {
21315
21822
  const { withAssignmentMd } = await listAssignments(ctx);
21316
21823
  const results = [];
21317
21824
  for (const a of withAssignmentMd) {
21318
- const path = resolve48(a.assignmentDir, "assignment.md");
21825
+ const path = resolve50(a.assignmentDir, "assignment.md");
21319
21826
  const parsed = await parseSafe2(path);
21320
21827
  if (!parsed) continue;
21321
21828
  if (parsed.status !== "ready_to_implement") continue;
21322
- const entries = await readdir17(a.assignmentDir).catch(() => []);
21829
+ const entries = await readdir18(a.assignmentDir).catch(() => []);
21323
21830
  const planFiles = entries.filter((f) => /^plan(?:-v\d+)?\.md$/i.test(f));
21324
21831
  let hasPlanContent = false;
21325
21832
  for (const f of planFiles) {
21326
21833
  try {
21327
- const c2 = await readFile28(resolve48(a.assignmentDir, f), "utf-8");
21834
+ const c2 = await readFile29(resolve50(a.assignmentDir, f), "utf-8");
21328
21835
  if (c2.trim().length > 0) {
21329
21836
  hasPlanContent = true;
21330
21837
  break;
@@ -21340,7 +21847,7 @@ var readyToImplementMissingPlan = {
21340
21847
  title: this.title,
21341
21848
  status: "warn",
21342
21849
  detail: `${label} (status: ready_to_implement) has no plan.md or plan-v<N>.md`,
21343
- affected: [resolve48(a.assignmentDir, "plan.md")],
21850
+ affected: [resolve50(a.assignmentDir, "plan.md")],
21344
21851
  remediation: {
21345
21852
  kind: "manual",
21346
21853
  suggestion: `Write a plan with '/plan-assignment' (or 'syntaur plan'), then re-mark ready_to_implement`,
@@ -21367,7 +21874,7 @@ var assignmentChecks = [
21367
21874
  ];
21368
21875
  async function parseSafe2(path) {
21369
21876
  try {
21370
- const content = await readFile28(path, "utf-8");
21877
+ const content = await readFile29(path, "utf-8");
21371
21878
  return parseAssignmentFull(content);
21372
21879
  } catch {
21373
21880
  return null;
@@ -21386,7 +21893,7 @@ function pass4(check, detail) {
21386
21893
 
21387
21894
  // src/utils/doctor/checks/dashboard.ts
21388
21895
  init_fs();
21389
- import { resolve as resolve49 } from "path";
21896
+ import { resolve as resolve51 } from "path";
21390
21897
  var CATEGORY5 = "dashboard";
21391
21898
  var dbReachable = {
21392
21899
  id: "dashboard.db-reachable",
@@ -21400,7 +21907,7 @@ var dbReachable = {
21400
21907
  title: this.title,
21401
21908
  status: "error",
21402
21909
  detail: `could not open syntaur.db: ${ctx.dbError ?? "unknown error"}`,
21403
- affected: [resolve49(ctx.syntaurRoot, "syntaur.db")],
21910
+ affected: [resolve51(ctx.syntaurRoot, "syntaur.db")],
21404
21911
  remediation: {
21405
21912
  kind: "manual",
21406
21913
  suggestion: "Start the dashboard once (`syntaur dashboard`) to initialize the DB, or restore it from backup",
@@ -21418,7 +21925,7 @@ var dbReachable = {
21418
21925
  title: this.title,
21419
21926
  status: "error",
21420
21927
  detail: 'syntaur.db is missing the expected "sessions" table',
21421
- affected: [resolve49(ctx.syntaurRoot, "syntaur.db")],
21928
+ affected: [resolve51(ctx.syntaurRoot, "syntaur.db")],
21422
21929
  autoFixable: false
21423
21930
  };
21424
21931
  }
@@ -21430,7 +21937,7 @@ var dbReachable = {
21430
21937
  title: this.title,
21431
21938
  status: "error",
21432
21939
  detail: `syntaur.db query failed: ${err2 instanceof Error ? err2.message : String(err2)}`,
21433
- affected: [resolve49(ctx.syntaurRoot, "syntaur.db")],
21940
+ affected: [resolve51(ctx.syntaurRoot, "syntaur.db")],
21434
21941
  autoFixable: false
21435
21942
  };
21436
21943
  }
@@ -21456,7 +21963,7 @@ var ghostSessions = {
21456
21963
  const results = [];
21457
21964
  for (const row of rows) {
21458
21965
  if (!row.project_slug) continue;
21459
- const projectPath = resolve49(projectsDir2, row.project_slug, "project.md");
21966
+ const projectPath = resolve51(projectsDir2, row.project_slug, "project.md");
21460
21967
  if (!await fileExists(projectPath)) {
21461
21968
  results.push({
21462
21969
  id: this.id,
@@ -21475,7 +21982,7 @@ var ghostSessions = {
21475
21982
  continue;
21476
21983
  }
21477
21984
  if (row.assignment_slug) {
21478
- const assignmentPath = resolve49(
21985
+ const assignmentPath = resolve51(
21479
21986
  projectsDir2,
21480
21987
  row.project_slug,
21481
21988
  "assignments",
@@ -21527,8 +22034,8 @@ function skipped(check, reason) {
21527
22034
 
21528
22035
  // src/utils/doctor/checks/integrations.ts
21529
22036
  init_fs();
21530
- import { resolve as resolve50, dirname as dirname15, basename as basename5 } from "path";
21531
- import { readdir as readdir18, readFile as readFile29 } from "fs/promises";
22037
+ import { resolve as resolve52, dirname as dirname15, basename as basename5 } from "path";
22038
+ import { readdir as readdir19, readFile as readFile30 } from "fs/promises";
21532
22039
  import { homedir as homedir7 } from "os";
21533
22040
  var CATEGORY6 = "integrations";
21534
22041
  var claudePluginLinked = {
@@ -21591,7 +22098,7 @@ var backupConfigured = {
21591
22098
  if (ctx.config.backup?.repo) return pass6(this);
21592
22099
  const projectsDir2 = ctx.config.defaultProjectDir;
21593
22100
  if (!await fileExists(projectsDir2)) return skipped2(this, "no projects dir");
21594
- const entries = await readdir18(projectsDir2, { withFileTypes: true });
22101
+ const entries = await readdir19(projectsDir2, { withFileTypes: true });
21595
22102
  const hasProjects = entries.some((e) => e.isDirectory() && !e.name.startsWith(".") && !e.name.startsWith("_"));
21596
22103
  if (!hasProjects) return skipped2(this, "no projects yet");
21597
22104
  return {
@@ -21610,10 +22117,10 @@ var backupConfigured = {
21610
22117
  }
21611
22118
  };
21612
22119
  async function readKnownMarketplaces() {
21613
- const path = resolve50(homedir7(), ".claude", "plugins", "known_marketplaces.json");
22120
+ const path = resolve52(homedir7(), ".claude", "plugins", "known_marketplaces.json");
21614
22121
  if (!await fileExists(path)) return {};
21615
22122
  try {
21616
- const raw = await readFile29(path, "utf-8");
22123
+ const raw = await readFile30(path, "utf-8");
21617
22124
  return JSON.parse(raw);
21618
22125
  } catch {
21619
22126
  return {};
@@ -21647,7 +22154,7 @@ var claudeMarketplaceRegistered = {
21647
22154
  };
21648
22155
  }
21649
22156
  const marketplaceRoot = dirname15(pluginsParent);
21650
- const marketplaceManifest = resolve50(marketplaceRoot, ".claude-plugin", "marketplace.json");
22157
+ const marketplaceManifest = resolve52(marketplaceRoot, ".claude-plugin", "marketplace.json");
21651
22158
  if (!await fileExists(marketplaceManifest)) {
21652
22159
  return {
21653
22160
  id: this.id,
@@ -21666,7 +22173,7 @@ var claudeMarketplaceRegistered = {
21666
22173
  }
21667
22174
  let parsed = {};
21668
22175
  try {
21669
- parsed = JSON.parse(await readFile29(marketplaceManifest, "utf-8"));
22176
+ parsed = JSON.parse(await readFile30(marketplaceManifest, "utf-8"));
21670
22177
  } catch {
21671
22178
  return {
21672
22179
  id: this.id,
@@ -21698,7 +22205,7 @@ var claudeMarketplaceRegistered = {
21698
22205
  title: this.title,
21699
22206
  status: "error",
21700
22207
  detail: issues.join("; "),
21701
- affected: [marketplaceManifest, resolve50(homedir7(), ".claude", "plugins", "known_marketplaces.json")],
22208
+ affected: [marketplaceManifest, resolve52(homedir7(), ".claude", "plugins", "known_marketplaces.json")],
21702
22209
  remediation: {
21703
22210
  kind: "manual",
21704
22211
  suggestion: "Re-run install-plugin to ensure both files are in sync.",
@@ -21738,8 +22245,8 @@ function skipped2(check, reason) {
21738
22245
  init_fs();
21739
22246
  init_parser();
21740
22247
  init_types();
21741
- import { resolve as resolve51 } from "path";
21742
- import { readFile as readFile30 } from "fs/promises";
22248
+ import { resolve as resolve53 } from "path";
22249
+ import { readFile as readFile31 } from "fs/promises";
21743
22250
  var CATEGORY7 = "workspace";
21744
22251
  var ASSIGNMENT_FIELDS = ["projectSlug", "assignmentSlug", "projectDir", "assignmentDir"];
21745
22252
  function hasAnyAssignmentField(ctx) {
@@ -21751,12 +22258,12 @@ function isStandaloneSession(ctx) {
21751
22258
  return !hasAnyAssignmentField(ctx) && typeof ctx.sessionId === "string" && ctx.sessionId.length > 0;
21752
22259
  }
21753
22260
  async function loadContext(ctx) {
21754
- const path = resolve51(ctx.cwd, ".syntaur", "context.json");
22261
+ const path = resolve53(ctx.cwd, ".syntaur", "context.json");
21755
22262
  if (!await fileExists(path)) {
21756
22263
  return { data: null, path, exists: false, parseError: null };
21757
22264
  }
21758
22265
  try {
21759
- const raw = await readFile30(path, "utf-8");
22266
+ const raw = await readFile31(path, "utf-8");
21760
22267
  return { data: JSON.parse(raw), path, exists: true, parseError: null };
21761
22268
  } catch (err2) {
21762
22269
  return {
@@ -21831,7 +22338,7 @@ var contextAssignmentResolves = {
21831
22338
  if (!exists) return skipped3(this, "no context to resolve");
21832
22339
  if (isStandaloneSession(data)) return skipped3(this, "standalone session context \u2014 no assignment to resolve");
21833
22340
  if (!data?.assignmentDir) return skipped3(this, "context has no assignmentDir");
21834
- const assignmentMd = resolve51(data.assignmentDir, "assignment.md");
22341
+ const assignmentMd = resolve53(data.assignmentDir, "assignment.md");
21835
22342
  if (!await fileExists(assignmentMd)) {
21836
22343
  return {
21837
22344
  id: this.id,
@@ -21860,10 +22367,10 @@ var contextTerminal = {
21860
22367
  if (!exists) return skipped3(this, "no context to check");
21861
22368
  if (isStandaloneSession(data)) return skipped3(this, "standalone session context \u2014 no assignment to check");
21862
22369
  if (!data?.assignmentDir) return skipped3(this, "context has no assignmentDir");
21863
- const assignmentMd = resolve51(data.assignmentDir, "assignment.md");
22370
+ const assignmentMd = resolve53(data.assignmentDir, "assignment.md");
21864
22371
  if (!await fileExists(assignmentMd)) return skipped3(this, "assignment file missing");
21865
22372
  try {
21866
- const content = await readFile30(assignmentMd, "utf-8");
22373
+ const content = await readFile31(assignmentMd, "utf-8");
21867
22374
  const parsed = parseAssignmentFull(content);
21868
22375
  const terminal = terminalStatuses2(ctx);
21869
22376
  if (terminal.has(parsed.status)) {
@@ -21917,9 +22424,9 @@ function skipped3(check, reason) {
21917
22424
 
21918
22425
  // src/utils/doctor/checks/agents.ts
21919
22426
  init_config2();
21920
- import { isAbsolute as isAbsolute8 } from "path";
22427
+ import { isAbsolute as isAbsolute9 } from "path";
21921
22428
  import { access as access2, constants as fsConstants } from "fs/promises";
21922
- import { spawnSync as spawnSync4 } from "child_process";
22429
+ import { spawnSync as spawnSync7 } from "child_process";
21923
22430
  var CATEGORY8 = "agents";
21924
22431
  var agentsResolvable = {
21925
22432
  id: "agents.commands-resolvable",
@@ -21963,7 +22470,7 @@ async function checkAgent(agent) {
21963
22470
  autoFixable: false
21964
22471
  };
21965
22472
  }
21966
- if (isAbsolute8(agent.command)) {
22473
+ if (isAbsolute9(agent.command)) {
21967
22474
  try {
21968
22475
  await access2(agent.command, fsConstants.X_OK);
21969
22476
  return { ...base, status: "pass", autoFixable: false };
@@ -21983,7 +22490,7 @@ async function checkAgent(agent) {
21983
22490
  };
21984
22491
  }
21985
22492
  }
21986
- const result = spawnSync4("which", [agent.command], { encoding: "utf-8" });
22493
+ const result = spawnSync7("which", [agent.command], { encoding: "utf-8" });
21987
22494
  if (result.status === 0 && result.stdout.trim().length > 0) {
21988
22495
  return {
21989
22496
  ...base,
@@ -22008,26 +22515,16 @@ var agentChecks = [agentsResolvable];
22008
22515
 
22009
22516
  // src/utils/doctor/checks/terminal.ts
22010
22517
  init_config2();
22518
+ import { spawnSync as spawnSync8 } from "child_process";
22519
+ import { readFile as readFile32 } from "fs/promises";
22520
+ import { resolve as resolve54 } from "path";
22011
22521
  init_paths();
22012
22522
  init_fs();
22013
- import { spawnSync as spawnSync5 } from "child_process";
22014
- import { readFile as readFile31 } from "fs/promises";
22015
- import { resolve as resolve52 } from "path";
22016
22523
  var CATEGORY9 = "terminal";
22017
- var APP_BUNDLE_IDS = {
22018
- "terminal-app": "com.apple.Terminal",
22019
- iterm: "com.googlecode.iterm2",
22020
- ghostty: "com.mitchellh.ghostty",
22021
- warp: "dev.warp.Warp-Stable"
22022
- };
22023
- var CLI_NAMES = {
22024
- alacritty: "alacritty",
22025
- kitty: "kitty"
22026
- };
22027
22524
  async function readRawTerminalKey() {
22028
- const configPath = resolve52(syntaurRoot(), "config.md");
22525
+ const configPath = resolve54(syntaurRoot(), "config.md");
22029
22526
  if (!await fileExists(configPath)) return null;
22030
- const content = await readFile31(configPath, "utf-8");
22527
+ const content = await readFile32(configPath, "utf-8");
22031
22528
  const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
22032
22529
  if (!fmMatch) return null;
22033
22530
  const line = fmMatch[1].split("\n").find((l) => /^terminal:\s*/.test(l));
@@ -22086,68 +22583,41 @@ var terminalInstalled = {
22086
22583
  const terminal = getTerminal(ctx.config);
22087
22584
  const bundleId = APP_BUNDLE_IDS[terminal];
22088
22585
  const cliName = CLI_NAMES[terminal];
22089
- if (bundleId) {
22090
- const result = spawnSync5(
22091
- "mdfind",
22092
- [`kMDItemCFBundleIdentifier == '${bundleId}'`],
22093
- { encoding: "utf-8" }
22094
- );
22095
- if (result.status === 0 && result.stdout.trim().length > 0) {
22096
- return {
22097
- id: this.id,
22098
- category: this.category,
22099
- title: this.title,
22100
- status: "pass",
22101
- detail: `found ${terminal} at ${result.stdout.trim().split("\n")[0]}`,
22102
- autoFixable: false
22103
- };
22104
- }
22586
+ const probe = probeTerminalInstalled(terminal);
22587
+ if (probe.reason === "no-probe-available") {
22105
22588
  return {
22106
22589
  id: this.id,
22107
22590
  category: this.category,
22108
22591
  title: this.title,
22109
- status: "warn",
22110
- detail: `${terminal} (bundle id ${bundleId}) not found via Spotlight`,
22111
- remediation: {
22112
- kind: "manual",
22113
- suggestion: `Install ${terminal} or change \`terminal:\` in ~/.syntaur/config.md to a different choice`,
22114
- command: null
22115
- },
22592
+ status: "skipped",
22593
+ detail: `no install check defined for ${terminal}`,
22116
22594
  autoFixable: false
22117
22595
  };
22118
22596
  }
22119
- if (cliName) {
22120
- const result = spawnSync5("which", [cliName], { encoding: "utf-8" });
22121
- if (result.status === 0 && result.stdout.trim().length > 0) {
22122
- return {
22123
- id: this.id,
22124
- category: this.category,
22125
- title: this.title,
22126
- status: "pass",
22127
- detail: `resolved ${cliName} \u2192 ${result.stdout.trim()}`,
22128
- autoFixable: false
22129
- };
22130
- }
22597
+ if (probe.ok) {
22598
+ const detail2 = bundleId ? `found ${terminal} at ${probe.foundPath}` : `resolved ${cliName} \u2192 ${probe.foundPath}`;
22131
22599
  return {
22132
22600
  id: this.id,
22133
22601
  category: this.category,
22134
22602
  title: this.title,
22135
- status: "warn",
22136
- detail: `${cliName} not found on PATH`,
22137
- remediation: {
22138
- kind: "manual",
22139
- suggestion: `Install ${cliName} or change \`terminal:\` in ~/.syntaur/config.md to a different choice`,
22140
- command: null
22141
- },
22603
+ status: "pass",
22604
+ detail: detail2,
22142
22605
  autoFixable: false
22143
22606
  };
22144
22607
  }
22608
+ const detail = bundleId ? `${terminal} (bundle id ${bundleId}) not found via Spotlight` : `${cliName} not found on PATH`;
22609
+ const suggestion = bundleId ? `Install ${terminal} or change \`terminal:\` in ~/.syntaur/config.md to a different choice` : `Install ${cliName} or change \`terminal:\` in ~/.syntaur/config.md to a different choice`;
22145
22610
  return {
22146
22611
  id: this.id,
22147
22612
  category: this.category,
22148
22613
  title: this.title,
22149
- status: "skipped",
22150
- detail: `no install check defined for ${terminal}`,
22614
+ status: "warn",
22615
+ detail,
22616
+ remediation: {
22617
+ kind: "manual",
22618
+ suggestion,
22619
+ command: null
22620
+ },
22151
22621
  autoFixable: false
22152
22622
  };
22153
22623
  }
@@ -22167,7 +22637,7 @@ var kittyRemoteControl = {
22167
22637
  autoFixable: false
22168
22638
  };
22169
22639
  }
22170
- const result = spawnSync5("kitty", ["@", "ls"], {
22640
+ const result = spawnSync8("kitty", ["@", "ls"], {
22171
22641
  encoding: "utf-8",
22172
22642
  timeout: 2e3
22173
22643
  });
@@ -22204,13 +22674,13 @@ var terminalChecks = [
22204
22674
 
22205
22675
  // src/utils/doctor/checks/skills.ts
22206
22676
  init_fs();
22207
- import { resolve as resolve53, join as join7 } from "path";
22208
- import { readdir as readdir19, readFile as readFile32, lstat as lstat4 } from "fs/promises";
22677
+ import { resolve as resolve55, join as join7 } from "path";
22678
+ import { readdir as readdir20, readFile as readFile33, lstat as lstat4 } from "fs/promises";
22209
22679
  import { homedir as homedir8 } from "os";
22210
22680
  var CATEGORY10 = "skills";
22211
22681
  var skillTargets = [
22212
- { agent: "claude", dir: resolve53(homedir8(), ".claude", "skills"), label: "~/.claude/skills" },
22213
- { agent: "codex", dir: resolve53(homedir8(), ".codex", "skills"), label: "~/.codex/skills" }
22682
+ { agent: "claude", dir: resolve55(homedir8(), ".claude", "skills"), label: "~/.claude/skills" },
22683
+ { agent: "codex", dir: resolve55(homedir8(), ".codex", "skills"), label: "~/.codex/skills" }
22214
22684
  ];
22215
22685
  var skillsDedupCheck = {
22216
22686
  id: "skills.dedup",
@@ -22225,7 +22695,7 @@ var skillsDedupCheck = {
22225
22695
  const present = [];
22226
22696
  let entries;
22227
22697
  try {
22228
- entries = await readdir19(dir, { withFileTypes: true });
22698
+ entries = await readdir20(dir, { withFileTypes: true });
22229
22699
  } catch {
22230
22700
  continue;
22231
22701
  }
@@ -22234,7 +22704,7 @@ var skillsDedupCheck = {
22234
22704
  if (!KNOWN_SKILLS.includes(entry.name)) continue;
22235
22705
  const skillMd = join7(dir, entry.name, "SKILL.md");
22236
22706
  if (!await fileExists(skillMd)) continue;
22237
- const content = await readFile32(skillMd, "utf-8").catch(() => "");
22707
+ const content = await readFile33(skillMd, "utf-8").catch(() => "");
22238
22708
  const match = content.match(/^name:\s*(\S+)\s*$/m);
22239
22709
  if (!match || match[1] !== entry.name) continue;
22240
22710
  let isSymlink2 = false;
@@ -22380,7 +22850,7 @@ async function readVersion() {
22380
22850
  let dir = dirname16(here);
22381
22851
  for (let i = 0; i < 6; i++) {
22382
22852
  try {
22383
- const raw = await readFile33(join8(dir, "package.json"), "utf-8");
22853
+ const raw = await readFile34(join8(dir, "package.json"), "utf-8");
22384
22854
  const parsed = JSON.parse(raw);
22385
22855
  return typeof parsed.version === "string" ? parsed.version : null;
22386
22856
  } catch {
@@ -22477,7 +22947,7 @@ var REQUIRED_WORKSPACE_FIELDS = [
22477
22947
  ];
22478
22948
  var ISO_DATE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z$/;
22479
22949
  async function validateAssignmentFile(inputPath, cwd = process.cwd()) {
22480
- const absolute = isAbsolute9(inputPath) ? inputPath : resolve54(cwd, inputPath);
22950
+ const absolute = isAbsolute10(inputPath) ? inputPath : resolve56(cwd, inputPath);
22481
22951
  const errors = [];
22482
22952
  const warnings = [];
22483
22953
  if (!await fileExists(absolute)) {
@@ -22490,7 +22960,7 @@ async function validateAssignmentFile(inputPath, cwd = process.cwd()) {
22490
22960
  }
22491
22961
  let content;
22492
22962
  try {
22493
- content = await readFile34(absolute, "utf-8");
22963
+ content = await readFile35(absolute, "utf-8");
22494
22964
  } catch (err2) {
22495
22965
  return {
22496
22966
  ok: false,
@@ -22814,8 +23284,8 @@ init_uuid();
22814
23284
  init_timestamp();
22815
23285
  init_assignment_resolver();
22816
23286
  init_templates();
22817
- import { resolve as resolve55 } from "path";
22818
- import { readFile as readFile35 } from "fs/promises";
23287
+ import { resolve as resolve57 } from "path";
23288
+ import { readFile as readFile36 } from "fs/promises";
22819
23289
  function shortId() {
22820
23290
  return generateId().split("-")[0];
22821
23291
  }
@@ -22845,7 +23315,7 @@ async function commentCommand(target, text, options = {}) {
22845
23315
  if (!isValidSlug(target)) {
22846
23316
  throw new Error(`Invalid assignment slug "${target}".`);
22847
23317
  }
22848
- assignmentDir = resolve55(baseDir, options.project, "assignments", target);
23318
+ assignmentDir = resolve57(baseDir, options.project, "assignments", target);
22849
23319
  assignmentRef = target;
22850
23320
  } else {
22851
23321
  const resolved = await resolveAssignmentById(baseDir, assignmentsDir(), target);
@@ -22855,13 +23325,13 @@ async function commentCommand(target, text, options = {}) {
22855
23325
  assignmentDir = resolved.assignmentDir;
22856
23326
  assignmentRef = resolved.standalone ? resolved.id : resolved.assignmentSlug;
22857
23327
  }
22858
- const commentsPath = resolve55(assignmentDir, "comments.md");
23328
+ const commentsPath = resolve57(assignmentDir, "comments.md");
22859
23329
  const timestamp = nowTimestamp();
22860
23330
  const author = options.author ?? process.env.USER ?? "unknown";
22861
23331
  let currentContent;
22862
23332
  let currentCount = 0;
22863
23333
  if (await fileExists(commentsPath)) {
22864
- currentContent = await readFile35(commentsPath, "utf-8");
23334
+ currentContent = await readFile36(commentsPath, "utf-8");
22865
23335
  const countMatch = currentContent.match(/^entryCount:\s*(\d+)/m);
22866
23336
  if (countMatch) currentCount = parseInt(countMatch[1], 10);
22867
23337
  } else {
@@ -22895,7 +23365,7 @@ ${entry}`;
22895
23365
  }
22896
23366
 
22897
23367
  // src/commands/capture.ts
22898
- import { resolve as resolve59, relative as relative4, dirname as dirname17 } from "path";
23368
+ import { resolve as resolve61, relative as relative4, dirname as dirname17 } from "path";
22899
23369
  import { copyFile as copyFile3, mkdir as mkdir8, realpath as realpath2, rm as rm10, stat as stat8, writeFile as writeFile11 } from "fs/promises";
22900
23370
  import { existsSync as existsSync3 } from "fs";
22901
23371
 
@@ -22906,15 +23376,15 @@ init_config2();
22906
23376
  init_slug();
22907
23377
  init_assignment_resolver();
22908
23378
  init_parser();
22909
- import { resolve as resolve56 } from "path";
22910
- import { readFile as readFile36 } from "fs/promises";
23379
+ import { resolve as resolve58 } from "path";
23380
+ import { readFile as readFile37 } from "fs/promises";
22911
23381
  var AssignmentTargetError = class extends Error {
22912
23382
  };
22913
23383
  async function readAssignmentFrontmatterId(assignmentDir) {
22914
- const path = resolve56(assignmentDir, "assignment.md");
23384
+ const path = resolve58(assignmentDir, "assignment.md");
22915
23385
  if (!await fileExists(path)) return null;
22916
23386
  try {
22917
- const content = await readFile36(path, "utf-8");
23387
+ const content = await readFile37(path, "utf-8");
22918
23388
  const [fm] = extractFrontmatter(content);
22919
23389
  return getField(fm, "id");
22920
23390
  } catch {
@@ -22922,10 +23392,10 @@ async function readAssignmentFrontmatterId(assignmentDir) {
22922
23392
  }
22923
23393
  }
22924
23394
  async function readContextJson(cwd) {
22925
- const path = resolve56(cwd, ".syntaur", "context.json");
23395
+ const path = resolve58(cwd, ".syntaur", "context.json");
22926
23396
  if (!await fileExists(path)) return null;
22927
23397
  try {
22928
- const raw = await readFile36(path, "utf-8");
23398
+ const raw = await readFile37(path, "utf-8");
22929
23399
  return JSON.parse(raw);
22930
23400
  } catch {
22931
23401
  return null;
@@ -22946,15 +23416,15 @@ async function resolveAssignmentTarget(input4, opts = {}) {
22946
23416
  if (!isValidSlug(input4)) {
22947
23417
  throw new AssignmentTargetError(`Invalid assignment slug "${input4}".`);
22948
23418
  }
22949
- const projectDir = resolve56(baseDir, opts.project);
22950
- const projectMdPath = resolve56(projectDir, "project.md");
23419
+ const projectDir = resolve58(baseDir, opts.project);
23420
+ const projectMdPath = resolve58(projectDir, "project.md");
22951
23421
  if (!await fileExists(projectDir) || !await fileExists(projectMdPath)) {
22952
23422
  throw new AssignmentTargetError(
22953
23423
  `Project "${opts.project}" not found at ${projectDir}.`
22954
23424
  );
22955
23425
  }
22956
- const assignmentDir = resolve56(projectDir, "assignments", input4);
22957
- const assignmentMdPath2 = resolve56(assignmentDir, "assignment.md");
23426
+ const assignmentDir = resolve58(projectDir, "assignments", input4);
23427
+ const assignmentMdPath2 = resolve58(assignmentDir, "assignment.md");
22958
23428
  if (!await fileExists(assignmentMdPath2)) {
22959
23429
  throw new AssignmentTargetError(
22960
23430
  `Assignment "${input4}" not found in project "${opts.project}".`
@@ -22988,7 +23458,7 @@ async function resolveAssignmentTarget(input4, opts = {}) {
22988
23458
  }
22989
23459
  if (ctx.assignmentDir) {
22990
23460
  const dir = expandHome(ctx.assignmentDir);
22991
- const assignmentMdPath2 = resolve56(dir, "assignment.md");
23461
+ const assignmentMdPath2 = resolve58(dir, "assignment.md");
22992
23462
  if (!await fileExists(assignmentMdPath2)) {
22993
23463
  throw new AssignmentTargetError(
22994
23464
  `.syntaur/context.json points to a missing assignment dir: ${dir}.`
@@ -23017,8 +23487,8 @@ async function resolveAssignmentTarget(input4, opts = {}) {
23017
23487
  `.syntaur/context.json contains invalid slugs: project="${ctx.projectSlug}" assignment="${ctx.assignmentSlug}".`
23018
23488
  );
23019
23489
  }
23020
- const assignmentDir = resolve56(baseDir, ctx.projectSlug, "assignments", ctx.assignmentSlug);
23021
- const assignmentMdPath2 = resolve56(assignmentDir, "assignment.md");
23490
+ const assignmentDir = resolve58(baseDir, ctx.projectSlug, "assignments", ctx.assignmentSlug);
23491
+ const assignmentMdPath2 = resolve58(assignmentDir, "assignment.md");
23022
23492
  if (!await fileExists(assignmentMdPath2)) {
23023
23493
  throw new AssignmentTargetError(
23024
23494
  `.syntaur/context.json points to a missing assignment: ${assignmentDir}.`
@@ -23153,7 +23623,7 @@ async function captureScreenshot(mode) {
23153
23623
 
23154
23624
  // src/utils/asciinema.ts
23155
23625
  import { spawn as spawn6 } from "child_process";
23156
- import { mkdtemp as mkdtemp3, readFile as readFile37, rm as rm7 } from "fs/promises";
23626
+ import { mkdtemp as mkdtemp3, readFile as readFile38, rm as rm7 } from "fs/promises";
23157
23627
  import { tmpdir as tmpdir4 } from "os";
23158
23628
  import { join as join10 } from "path";
23159
23629
  var SAFE_RE = /^[A-Za-z0-9_@%+=:,./-]+$/;
@@ -23216,7 +23686,7 @@ async function captureAsciinema(opts) {
23216
23686
  }
23217
23687
  throw err2;
23218
23688
  }
23219
- const text = await readFile37(castPath, "utf8").catch(() => null);
23689
+ const text = await readFile38(castPath, "utf8").catch(() => null);
23220
23690
  if (text === null) {
23221
23691
  throw new Error(
23222
23692
  `asciinema produced no cast file at ${castPath} (exit ${exitCode}). Try running 'asciinema rec ${castPath}' directly to diagnose.`
@@ -23244,9 +23714,9 @@ async function captureAsciinema(opts) {
23244
23714
  // src/utils/recording.ts
23245
23715
  init_paths();
23246
23716
  import { spawn as spawn7 } from "child_process";
23247
- import { mkdir as mkdir7, mkdtemp as mkdtemp4, open as open3, readFile as readFile38, rm as rm8, stat as stat7, unlink as unlink8, writeFile as writeFile10 } from "fs/promises";
23717
+ import { mkdir as mkdir7, mkdtemp as mkdtemp4, open as open3, readFile as readFile39, rm as rm8, stat as stat7, unlink as unlink8, writeFile as writeFile10 } from "fs/promises";
23248
23718
  import { tmpdir as tmpdir5 } from "os";
23249
- import { join as join11, resolve as resolve57 } from "path";
23719
+ import { join as join11, resolve as resolve59 } from "path";
23250
23720
  import { setTimeout as sleep } from "timers/promises";
23251
23721
  function sigintPollIntervalMs() {
23252
23722
  const raw = process.env.SYNTAUR_RECORDING_POLL_INTERVAL_MS;
@@ -23267,13 +23737,13 @@ function sigtermWaitMs() {
23267
23737
  return Number.isFinite(parsed) && parsed >= 0 ? parsed : 1e3;
23268
23738
  }
23269
23739
  function pidfilePath() {
23270
- return resolve57(syntaurRoot(), "recording.pid");
23740
+ return resolve59(syntaurRoot(), "recording.pid");
23271
23741
  }
23272
23742
  function logPath2() {
23273
- return resolve57(syntaurRoot(), "recording.log");
23743
+ return resolve59(syntaurRoot(), "recording.log");
23274
23744
  }
23275
23745
  function sidecarPath() {
23276
- return resolve57(syntaurRoot(), "recording.json");
23746
+ return resolve59(syntaurRoot(), "recording.json");
23277
23747
  }
23278
23748
  function ffmpegArgs(device, fps, mp4Path) {
23279
23749
  return [
@@ -23318,7 +23788,7 @@ async function acquirePidfile(pidfile) {
23318
23788
  } catch (err2) {
23319
23789
  if (err2.code !== "EEXIST") throw err2;
23320
23790
  if (attempt === 1) throw err2;
23321
- const existing = (await readFile38(pidfile, "utf-8").catch(() => "")).trim();
23791
+ const existing = (await readFile39(pidfile, "utf-8").catch(() => "")).trim();
23322
23792
  if (existing.startsWith(STARTING_SENTINEL_PREFIX)) {
23323
23793
  const parentPidRaw = existing.slice(STARTING_SENTINEL_PREFIX.length);
23324
23794
  const parentPid = Number.parseInt(parentPidRaw, 10);
@@ -23420,7 +23890,7 @@ async function startRecording(input4) {
23420
23890
  logHandle = null;
23421
23891
  if (warmupMs > 0) await sleep(warmupMs);
23422
23892
  if (!await isProcessAlive(pid)) {
23423
- const tail = await readFile38(log, "utf-8").then((s) => s.split("\n").slice(-20).join("\n")).catch(() => "");
23893
+ const tail = await readFile39(log, "utf-8").then((s) => s.split("\n").slice(-20).join("\n")).catch(() => "");
23424
23894
  acquiredPid = null;
23425
23895
  throw new Error(
23426
23896
  `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}
@@ -23477,7 +23947,7 @@ ${tail}`
23477
23947
  async function stopRecording() {
23478
23948
  const pidfile = pidfilePath();
23479
23949
  const sidecar = sidecarPath();
23480
- const pidRaw = await readFile38(pidfile, "utf-8").catch(() => null);
23950
+ const pidRaw = await readFile39(pidfile, "utf-8").catch(() => null);
23481
23951
  if (pidRaw === null) {
23482
23952
  throw new Error(
23483
23953
  `No active recording found (no pidfile at ${pidfile}). Did you run --start?`
@@ -23487,7 +23957,7 @@ async function stopRecording() {
23487
23957
  if (!Number.isInteger(pid) || pid <= 0) {
23488
23958
  throw new Error(`Pidfile at ${pidfile} is corrupt (got "${pidRaw}").`);
23489
23959
  }
23490
- const sidecarRaw = await readFile38(sidecar, "utf-8").catch(() => null);
23960
+ const sidecarRaw = await readFile39(sidecar, "utf-8").catch(() => null);
23491
23961
  if (sidecarRaw === null) {
23492
23962
  throw new Error(
23493
23963
  `No recording sidecar at ${sidecar}. The recording state is inconsistent \u2014 delete ${pidfile} and re-run --start.`
@@ -23552,7 +24022,7 @@ async function stopRecording() {
23552
24022
  // src/db/proof-db.ts
23553
24023
  init_paths();
23554
24024
  import Database4 from "better-sqlite3";
23555
- import { resolve as resolve58 } from "path";
24025
+ import { resolve as resolve60 } from "path";
23556
24026
  var db3 = null;
23557
24027
  var PROOF_SCHEMA_VERSION = "1";
23558
24028
  var SCHEMA_SQL3 = `
@@ -23572,7 +24042,7 @@ CREATE TABLE IF NOT EXISTS meta (key TEXT PRIMARY KEY, value TEXT);
23572
24042
  `;
23573
24043
  function initProofDb(dbPath) {
23574
24044
  if (db3) return db3;
23575
- const finalPath = dbPath ?? resolve58(syntaurRoot(), "syntaur.db");
24045
+ const finalPath = dbPath ?? resolve60(syntaurRoot(), "syntaur.db");
23576
24046
  db3 = new Database4(finalPath);
23577
24047
  db3.pragma("journal_mode = WAL");
23578
24048
  db3.exec(SCHEMA_SQL3);
@@ -23620,7 +24090,7 @@ function listArtifactsByAssignment(assignmentId) {
23620
24090
 
23621
24091
  // src/utils/transcribers/elevenlabs.ts
23622
24092
  import { spawn as spawn8 } from "child_process";
23623
- import { mkdtemp as mkdtemp5, readFile as readFile39, rm as rm9 } from "fs/promises";
24093
+ import { mkdtemp as mkdtemp5, readFile as readFile40, rm as rm9 } from "fs/promises";
23624
24094
  import { tmpdir as tmpdir6 } from "os";
23625
24095
  import { join as join12 } from "path";
23626
24096
  var SCRIBE_URL = "https://api.elevenlabs.io/v1/speech-to-text";
@@ -23684,7 +24154,7 @@ async function extractAudio(videoAbsPath, wavOut) {
23684
24154
  throw new TranscribeFfmpegError(`ffmpeg failed (exit ${result.code}): ${tail}`);
23685
24155
  }
23686
24156
  async function callScribe(wavPath, apiKey, opts) {
23687
- const audio = await readFile39(wavPath);
24157
+ const audio = await readFile40(wavPath);
23688
24158
  const form = new FormData();
23689
24159
  form.set("file", new Blob([new Uint8Array(audio)], { type: "audio/wav" }), "audio.wav");
23690
24160
  form.set("model_id", "scribe_v1");
@@ -24000,7 +24470,7 @@ async function captureCommand(target, options = {}) {
24000
24470
  });
24001
24471
  }
24002
24472
  if (options.file) {
24003
- const expanded = options.file.startsWith("~/") ? resolve59(process.env.HOME ?? "", options.file.slice(2)) : resolve59(options.file);
24473
+ const expanded = options.file.startsWith("~/") ? resolve61(process.env.HOME ?? "", options.file.slice(2)) : resolve61(options.file);
24004
24474
  if (!await fileExists(expanded)) {
24005
24475
  throw new Error(`--file does not exist: ${options.file}`);
24006
24476
  }
@@ -24041,7 +24511,7 @@ async function captureCommand(target, options = {}) {
24041
24511
  }
24042
24512
  initProofDb();
24043
24513
  const subdir = criterionIndex === null ? "untagged" : String(criterionIndex);
24044
- const destDir = resolve59(proofDir(resolved.assignmentDir), subdir);
24514
+ const destDir = resolve61(proofDir(resolved.assignmentDir), subdir);
24045
24515
  if (resolvedSource) await mkdir8(destDir, { recursive: true });
24046
24516
  const ext = resolvedSource ? extensionForKind(kind) : null;
24047
24517
  let id = null;
@@ -24050,7 +24520,7 @@ async function captureCommand(target, options = {}) {
24050
24520
  let lastErr = null;
24051
24521
  for (let attempt = 0; attempt < MAX_ID_RETRIES; attempt += 1) {
24052
24522
  const candidate = generateArtifactId();
24053
- const candidateAbsPath = resolvedSource && ext ? resolve59(destDir, `${candidate}.${ext}`) : null;
24523
+ const candidateAbsPath = resolvedSource && ext ? resolve61(destDir, `${candidate}.${ext}`) : null;
24054
24524
  const candidateRel = candidateAbsPath ? relative4(resolved.assignmentDir, candidateAbsPath) : null;
24055
24525
  try {
24056
24526
  insertArtifact({
@@ -24094,7 +24564,7 @@ async function captureCommand(target, options = {}) {
24094
24564
  }
24095
24565
  }
24096
24566
  if (options.transcribe && kind === "video" && absPath && id) {
24097
- const sidecarPath2 = resolve59(destDir, `${id}.transcript.md`);
24567
+ const sidecarPath2 = resolve61(destDir, `${id}.transcript.md`);
24098
24568
  if (existsSync3(sidecarPath2)) {
24099
24569
  console.warn(
24100
24570
  `transcript: ${sidecarPath2} already exists, skipping (delete to re-transcribe)`
@@ -24126,7 +24596,7 @@ async function captureCommand(target, options = {}) {
24126
24596
  const tagSuffix = criterionIndex === null ? "untagged" : `criterion ${criterionIndex}`;
24127
24597
  console.log(`Captured artifact ${id} (${kind}) for ${ref} \u2014 ${tagSuffix}.`);
24128
24598
  if (relativeFilePath) {
24129
- console.log(` file: ${resolve59(resolved.assignmentDir, relativeFilePath)}`);
24599
+ console.log(` file: ${resolve61(resolved.assignmentDir, relativeFilePath)}`);
24130
24600
  }
24131
24601
  } catch (err2) {
24132
24602
  if (options.stop && kind === "video" && shelloutCleanup && resolvedSource) {
@@ -24149,8 +24619,8 @@ async function captureCommand(target, options = {}) {
24149
24619
 
24150
24620
  // src/commands/proof.ts
24151
24621
  import { Command as Command5 } from "commander";
24152
- import { readFile as readFile40, writeFile as writeFile12, rename as rename8, stat as stat9 } from "fs/promises";
24153
- import { resolve as resolve60, relative as relative5, isAbsolute as isAbsolute10, dirname as dirname18 } from "path";
24622
+ import { readFile as readFile41, writeFile as writeFile12, rename as rename8, stat as stat9 } from "fs/promises";
24623
+ import { resolve as resolve62, relative as relative5, isAbsolute as isAbsolute11, dirname as dirname18 } from "path";
24154
24624
  import { randomBytes as randomBytes3 } from "crypto";
24155
24625
 
24156
24626
  // src/utils/acceptance-criteria-parse.ts
@@ -24390,11 +24860,11 @@ function renderProofHtml(params2, inlineFiles = /* @__PURE__ */ new Map(), trans
24390
24860
 
24391
24861
  // src/commands/proof.ts
24392
24862
  async function readAssignmentMeta(assignmentDir) {
24393
- const path = resolve60(assignmentDir, "assignment.md");
24863
+ const path = resolve62(assignmentDir, "assignment.md");
24394
24864
  if (!await fileExists(path)) {
24395
24865
  return { title: "", body: "" };
24396
24866
  }
24397
- const content = await readFile40(path, "utf-8");
24867
+ const content = await readFile41(path, "utf-8");
24398
24868
  const fmMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?/);
24399
24869
  let title = "";
24400
24870
  if (fmMatch) {
@@ -24413,7 +24883,7 @@ async function atomicWrite(destPath, content) {
24413
24883
  }
24414
24884
  function isWithin(root, target) {
24415
24885
  const rel = relative5(root, target);
24416
- return rel === "" || !rel.startsWith("..") && !isAbsolute10(rel);
24886
+ return rel === "" || !rel.startsWith("..") && !isAbsolute11(rel);
24417
24887
  }
24418
24888
  function groupArtifacts(rows, criterionCount) {
24419
24889
  const artifactsByCriterion = /* @__PURE__ */ new Map();
@@ -24441,7 +24911,7 @@ async function loadInlineFiles(rows, assignmentDir) {
24441
24911
  for (const r of rows) {
24442
24912
  if (!r.file_path) continue;
24443
24913
  if (r.kind !== "http" && r.kind !== "text") continue;
24444
- const abs = resolve60(assignmentDir, r.file_path);
24914
+ const abs = resolve62(assignmentDir, r.file_path);
24445
24915
  if (!isWithin(proofRoot, abs)) {
24446
24916
  out.set(r.file_path, null);
24447
24917
  continue;
@@ -24456,7 +24926,7 @@ async function loadInlineFiles(rows, assignmentDir) {
24456
24926
  continue;
24457
24927
  }
24458
24928
  try {
24459
- out.set(r.file_path, await readFile40(abs, "utf-8"));
24929
+ out.set(r.file_path, await readFile41(abs, "utf-8"));
24460
24930
  } catch {
24461
24931
  out.set(r.file_path, null);
24462
24932
  }
@@ -24468,14 +24938,14 @@ async function loadTranscriptSidecars(rows, assignmentDir) {
24468
24938
  const proofRoot = proofDir(assignmentDir);
24469
24939
  for (const r of rows) {
24470
24940
  if (r.kind !== "video" || !r.file_path) continue;
24471
- const videoAbs = resolve60(assignmentDir, r.file_path);
24472
- const sidecar = resolve60(dirname18(videoAbs), `${r.id}.transcript.md`);
24941
+ const videoAbs = resolve62(assignmentDir, r.file_path);
24942
+ const sidecar = resolve62(dirname18(videoAbs), `${r.id}.transcript.md`);
24473
24943
  if (!isWithin(proofRoot, sidecar)) continue;
24474
24944
  if (!await fileExists(sidecar)) continue;
24475
24945
  const st = await stat9(sidecar);
24476
24946
  if (st.size > INLINE_TEXT_LIMIT_BYTES) continue;
24477
24947
  try {
24478
- out.set(r.id, await readFile40(sidecar, "utf-8"));
24948
+ out.set(r.id, await readFile41(sidecar, "utf-8"));
24479
24949
  } catch {
24480
24950
  }
24481
24951
  }
@@ -24509,8 +24979,8 @@ async function proofBuildCommand(target, options = {}) {
24509
24979
  };
24510
24980
  const md = renderProofMarkdown(renderParams);
24511
24981
  const html = renderProofHtml(renderParams, inlineFiles, transcriptSidecars);
24512
- const mdPath = resolve60(resolved.assignmentDir, "proof.md");
24513
- const htmlPath = resolve60(resolved.assignmentDir, "proof.html");
24982
+ const mdPath = resolve62(resolved.assignmentDir, "proof.md");
24983
+ const htmlPath = resolve62(resolved.assignmentDir, "proof.html");
24514
24984
  await atomicWrite(mdPath, md);
24515
24985
  await atomicWrite(htmlPath, html);
24516
24986
  console.log(`Wrote ${htmlPath}`);
@@ -25007,8 +25477,8 @@ init_slug();
25007
25477
  init_timestamp();
25008
25478
  init_assignment_resolver();
25009
25479
  init_assignment_todos();
25010
- import { resolve as resolve61 } from "path";
25011
- import { readFile as readFile41 } from "fs/promises";
25480
+ import { resolve as resolve63 } from "path";
25481
+ import { readFile as readFile42 } from "fs/promises";
25012
25482
  async function requestCommand(target, text, options = {}) {
25013
25483
  if (!text || !text.trim()) {
25014
25484
  throw new Error("Request text cannot be empty.");
@@ -25024,7 +25494,7 @@ async function requestCommand(target, text, options = {}) {
25024
25494
  if (!isValidSlug(target)) {
25025
25495
  throw new Error(`Invalid assignment slug "${target}".`);
25026
25496
  }
25027
- assignmentDir = resolve61(baseDir, options.project, "assignments", target);
25497
+ assignmentDir = resolve63(baseDir, options.project, "assignments", target);
25028
25498
  targetRef = target;
25029
25499
  } else {
25030
25500
  const resolved = await resolveAssignmentById(baseDir, assignmentsDir(), target);
@@ -25034,12 +25504,12 @@ async function requestCommand(target, text, options = {}) {
25034
25504
  assignmentDir = resolved.assignmentDir;
25035
25505
  targetRef = resolved.standalone ? resolved.id : resolved.assignmentSlug;
25036
25506
  }
25037
- const assignmentMdPath2 = resolve61(assignmentDir, "assignment.md");
25507
+ const assignmentMdPath2 = resolve63(assignmentDir, "assignment.md");
25038
25508
  if (!await fileExists(assignmentMdPath2)) {
25039
25509
  throw new Error(`assignment.md not found at ${assignmentMdPath2}`);
25040
25510
  }
25041
25511
  const source = options.from ?? process.env.SYNTAUR_ASSIGNMENT ?? "unknown";
25042
- let content = await readFile41(assignmentMdPath2, "utf-8");
25512
+ let content = await readFile42(assignmentMdPath2, "utf-8");
25043
25513
  content = appendTodosToAssignmentBody(content, [
25044
25514
  { description: `${text.trim()} (from: ${source})` }
25045
25515
  ]);
@@ -25052,13 +25522,13 @@ async function requestCommand(target, text, options = {}) {
25052
25522
  init_fs();
25053
25523
  init_paths();
25054
25524
  import { Command as Command7 } from "commander";
25055
- import { readFile as readFile42, readdir as readdir20 } from "fs/promises";
25056
- import { resolve as resolve62 } from "path";
25525
+ import { readFile as readFile43, readdir as readdir21 } from "fs/promises";
25526
+ import { resolve as resolve64 } from "path";
25057
25527
  async function readContextAssignmentDir(cwd) {
25058
- const path = resolve62(cwd, ".syntaur", "context.json");
25528
+ const path = resolve64(cwd, ".syntaur", "context.json");
25059
25529
  if (!await fileExists(path)) return null;
25060
25530
  try {
25061
- const raw = await readFile42(path, "utf-8");
25531
+ const raw = await readFile43(path, "utf-8");
25062
25532
  const ctx = JSON.parse(raw);
25063
25533
  if (typeof ctx.assignmentDir === "string" && ctx.assignmentDir.length > 0) {
25064
25534
  return ctx.assignmentDir;
@@ -25072,9 +25542,9 @@ async function resolveAssignmentDir(opts) {
25072
25542
  const cwd = opts.cwd ?? process.cwd();
25073
25543
  if (opts.assignment) {
25074
25544
  if (opts.project) {
25075
- return resolve62(defaultProjectDir(), opts.project, "assignments", opts.assignment);
25545
+ return resolve64(defaultProjectDir(), opts.project, "assignments", opts.assignment);
25076
25546
  }
25077
- return resolve62(assignmentsDir(), opts.assignment);
25547
+ return resolve64(assignmentsDir(), opts.assignment);
25078
25548
  }
25079
25549
  const fromCtx = await readContextAssignmentDir(cwd);
25080
25550
  if (fromCtx) return fromCtx;
@@ -25085,7 +25555,7 @@ async function resolveAssignmentDir(opts) {
25085
25555
  var PLAN_PATTERN = /^plan(?:-v(\d+))?\.md$/;
25086
25556
  async function listPlanFiles(assignmentDir) {
25087
25557
  if (!await fileExists(assignmentDir)) return [];
25088
- const entries = await readdir20(assignmentDir, { withFileTypes: true });
25558
+ const entries = await readdir21(assignmentDir, { withFileTypes: true });
25089
25559
  const out = [];
25090
25560
  for (const e of entries) {
25091
25561
  if (!e.isFile()) continue;
@@ -25217,7 +25687,7 @@ async function runPlanVersion(options) {
25217
25687
  if (!await fileExists(assignmentDir)) {
25218
25688
  throw new Error(`Assignment directory does not exist: ${assignmentDir}`);
25219
25689
  }
25220
- const assignmentMdPath2 = resolve62(assignmentDir, "assignment.md");
25690
+ const assignmentMdPath2 = resolve64(assignmentDir, "assignment.md");
25221
25691
  if (!await fileExists(assignmentMdPath2)) {
25222
25692
  throw new Error(`Missing assignment.md at: ${assignmentMdPath2}`);
25223
25693
  }
@@ -25229,15 +25699,15 @@ async function runPlanVersion(options) {
25229
25699
  }
25230
25700
  const current = planFiles[planFiles.length - 1];
25231
25701
  const next = nextPlanFileName(current.version);
25232
- const newPath = resolve62(assignmentDir, next.fileName);
25702
+ const newPath = resolve64(assignmentDir, next.fileName);
25233
25703
  if (await fileExists(newPath) && !options.force) {
25234
25704
  throw new Error(`${next.fileName} already exists. Use --force to overwrite.`);
25235
25705
  }
25236
- const assignmentMd = await readFile42(assignmentMdPath2, "utf-8");
25706
+ const assignmentMd = await readFile43(assignmentMdPath2, "utf-8");
25237
25707
  const slugMatch = assignmentMd.match(/^slug:\s*(.+?)\s*$/m);
25238
25708
  const slug = slugMatch ? slugMatch[1].trim() : assignmentDir.split("/").pop() ?? "";
25239
- const oldPlanPath = resolve62(assignmentDir, current.fileName);
25240
- const oldPlanContent = await readFile42(oldPlanPath, "utf-8");
25709
+ const oldPlanPath = resolve64(assignmentDir, current.fileName);
25710
+ const oldPlanContent = await readFile43(oldPlanPath, "utf-8");
25241
25711
  const oldBody = oldPlanContent.replace(/^---[\s\S]*?\n---\n?/, "");
25242
25712
  const carriedTodos = extractUncheckedTodos(oldBody);
25243
25713
  const stub = buildNewPlanStub({
@@ -25274,26 +25744,26 @@ planCommand.command("version").description(
25274
25744
  // src/commands/session.ts
25275
25745
  init_fs();
25276
25746
  import { Command as Command8 } from "commander";
25277
- import { readFile as readFile43, readdir as readdir21, stat as stat10 } from "fs/promises";
25278
- import { resolve as resolve63 } from "path";
25747
+ import { readFile as readFile44, readdir as readdir22, stat as stat10 } from "fs/promises";
25748
+ import { resolve as resolve65 } from "path";
25279
25749
  async function readContext(cwd) {
25280
- const path = resolve63(cwd, ".syntaur", "context.json");
25750
+ const path = resolve65(cwd, ".syntaur", "context.json");
25281
25751
  if (!await fileExists(path)) return null;
25282
25752
  try {
25283
- const raw = await readFile43(path, "utf-8");
25753
+ const raw = await readFile44(path, "utf-8");
25284
25754
  return JSON.parse(raw);
25285
25755
  } catch {
25286
25756
  return null;
25287
25757
  }
25288
25758
  }
25289
25759
  async function findLatestSessionSummary(assignmentDir) {
25290
- const sessionsRoot = resolve63(assignmentDir, "sessions");
25760
+ const sessionsRoot = resolve65(assignmentDir, "sessions");
25291
25761
  if (!await fileExists(sessionsRoot)) return null;
25292
- const entries = await readdir21(sessionsRoot, { withFileTypes: true });
25762
+ const entries = await readdir22(sessionsRoot, { withFileTypes: true });
25293
25763
  let best = null;
25294
25764
  for (const entry of entries) {
25295
25765
  if (!entry.isDirectory()) continue;
25296
- const summaryPath = resolve63(sessionsRoot, entry.name, "summary.md");
25766
+ const summaryPath = resolve65(sessionsRoot, entry.name, "summary.md");
25297
25767
  if (!await fileExists(summaryPath)) continue;
25298
25768
  const st = await stat10(summaryPath);
25299
25769
  if (best === null || st.mtime.getTime() > best.mtime.getTime()) {
@@ -25303,9 +25773,9 @@ async function findLatestSessionSummary(assignmentDir) {
25303
25773
  return best;
25304
25774
  }
25305
25775
  async function findOpenHandoff(assignmentDir) {
25306
- const handoffPath = resolve63(assignmentDir, "handoff.md");
25776
+ const handoffPath = resolve65(assignmentDir, "handoff.md");
25307
25777
  if (!await fileExists(handoffPath)) return null;
25308
- const content = await readFile43(handoffPath, "utf-8");
25778
+ const content = await readFile44(handoffPath, "utf-8");
25309
25779
  const body = content.replace(/^---[\s\S]*?\n---\n?/, "").trim();
25310
25780
  if (body.length === 0) return null;
25311
25781
  if (/^<!--[\s\S]*-->$/.test(body)) return null;
@@ -25413,13 +25883,13 @@ init_git_worktree();
25413
25883
  init_fs();
25414
25884
  init_paths();
25415
25885
  import { Command as Command9 } from "commander";
25416
- import { readFile as readFile44 } from "fs/promises";
25417
- import { resolve as resolve64 } from "path";
25886
+ import { readFile as readFile45 } from "fs/promises";
25887
+ import { resolve as resolve66 } from "path";
25418
25888
  async function readContext2(cwd) {
25419
- const path = resolve64(cwd, ".syntaur", "context.json");
25889
+ const path = resolve66(cwd, ".syntaur", "context.json");
25420
25890
  if (!await fileExists(path)) return null;
25421
25891
  try {
25422
- return JSON.parse(await readFile44(path, "utf-8"));
25892
+ return JSON.parse(await readFile45(path, "utf-8"));
25423
25893
  } catch {
25424
25894
  return null;
25425
25895
  }
@@ -25427,7 +25897,7 @@ async function readContext2(cwd) {
25427
25897
  async function resolveAssignmentPath2(opts) {
25428
25898
  if (opts.assignment) {
25429
25899
  if (opts.project) {
25430
- return resolve64(
25900
+ return resolve66(
25431
25901
  defaultProjectDir(),
25432
25902
  opts.project,
25433
25903
  "assignments",
@@ -25435,10 +25905,10 @@ async function resolveAssignmentPath2(opts) {
25435
25905
  "assignment.md"
25436
25906
  );
25437
25907
  }
25438
- return resolve64(assignmentsDir(), opts.assignment, "assignment.md");
25908
+ return resolve66(assignmentsDir(), opts.assignment, "assignment.md");
25439
25909
  }
25440
25910
  const ctx = await readContext2(opts.cwd);
25441
- if (ctx?.assignmentDir) return resolve64(ctx.assignmentDir, "assignment.md");
25911
+ if (ctx?.assignmentDir) return resolve66(ctx.assignmentDir, "assignment.md");
25442
25912
  throw new Error(
25443
25913
  "No active assignment. Pass --assignment <slug> [--project <slug>] or run from a workspace with .syntaur/context.json."
25444
25914
  );
@@ -25449,7 +25919,7 @@ async function runWorktreeCreate(options, cwd = process.cwd()) {
25449
25919
  }
25450
25920
  const repository = options.repository ?? cwd;
25451
25921
  const parentBranch = options.parentBranch ?? "main";
25452
- const worktreePath = options.worktreePath ?? resolve64(repository, ".worktrees", options.branch);
25922
+ const worktreePath = options.worktreePath ?? resolve66(repository, ".worktrees", options.branch);
25453
25923
  const assignmentPath = await resolveAssignmentPath2({
25454
25924
  assignment: options.assignment,
25455
25925
  project: options.project,
@@ -25486,13 +25956,13 @@ init_paths();
25486
25956
  init_fs();
25487
25957
  init_slug();
25488
25958
  import { Command as Command10 } from "commander";
25489
- import { resolve as resolve66 } from "path";
25959
+ import { resolve as resolve68 } from "path";
25490
25960
 
25491
25961
  // src/utils/project-indexes.ts
25492
25962
  init_parser();
25493
25963
  init_fs();
25494
- import { readdir as readdir22, readFile as readFile45 } from "fs/promises";
25495
- import { resolve as resolve65 } from "path";
25964
+ import { readdir as readdir23, readFile as readFile46 } from "fs/promises";
25965
+ import { resolve as resolve67 } from "path";
25496
25966
  function nowIso2() {
25497
25967
  return (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
25498
25968
  }
@@ -25501,7 +25971,7 @@ function readProjectSlug(projectDir) {
25501
25971
  }
25502
25972
  async function listSlugFiles(dir) {
25503
25973
  if (!await fileExists(dir)) return [];
25504
- const entries = await readdir22(dir, { withFileTypes: true });
25974
+ const entries = await readdir23(dir, { withFileTypes: true });
25505
25975
  return entries.filter((e) => e.isFile() && e.name.endsWith(".md") && !e.name.startsWith("_")).map((e) => e.name).sort();
25506
25976
  }
25507
25977
  function escapeCell(value) {
@@ -25511,7 +25981,7 @@ function joinList(items) {
25511
25981
  return items.length === 0 ? "\u2014" : items.map(escapeCell).join(", ");
25512
25982
  }
25513
25983
  async function rebuildResourcesIndex(projectDir) {
25514
- const dir = resolve65(projectDir, "resources");
25984
+ const dir = resolve67(projectDir, "resources");
25515
25985
  await ensureDir(dir);
25516
25986
  const files = await listSlugFiles(dir);
25517
25987
  const slug = readProjectSlug(projectDir);
@@ -25527,7 +25997,7 @@ async function rebuildResourcesIndex(projectDir) {
25527
25997
  lines.push("| Name | Category | Source | Related Assignments | Updated |");
25528
25998
  lines.push("|------|----------|--------|---------------------|---------|");
25529
25999
  for (const fileName of files) {
25530
- const content = await readFile45(resolve65(dir, fileName), "utf-8");
26000
+ const content = await readFile46(resolve67(dir, fileName), "utf-8");
25531
26001
  const parsed = parseResource(content);
25532
26002
  const slugBase = fileName.replace(/\.md$/, "");
25533
26003
  const name = parsed.name || slugBase;
@@ -25537,12 +26007,12 @@ async function rebuildResourcesIndex(projectDir) {
25537
26007
  );
25538
26008
  }
25539
26009
  lines.push("");
25540
- const indexPath = resolve65(dir, "_index.md");
26010
+ const indexPath = resolve67(dir, "_index.md");
25541
26011
  await writeFileForce(indexPath, lines.join("\n"));
25542
26012
  return { total: files.length, path: indexPath };
25543
26013
  }
25544
26014
  async function rebuildMemoriesIndex(projectDir) {
25545
- const dir = resolve65(projectDir, "memories");
26015
+ const dir = resolve67(projectDir, "memories");
25546
26016
  await ensureDir(dir);
25547
26017
  const files = await listSlugFiles(dir);
25548
26018
  const slug = readProjectSlug(projectDir);
@@ -25558,7 +26028,7 @@ async function rebuildMemoriesIndex(projectDir) {
25558
26028
  lines.push("| Name | Source | Scope | Source Assignment | Updated |");
25559
26029
  lines.push("|------|--------|-------|-------------------|---------|");
25560
26030
  for (const fileName of files) {
25561
- const content = await readFile45(resolve65(dir, fileName), "utf-8");
26031
+ const content = await readFile46(resolve67(dir, fileName), "utf-8");
25562
26032
  const parsed = parseMemory(content);
25563
26033
  const slugBase = fileName.replace(/\.md$/, "");
25564
26034
  const name = parsed.name || slugBase;
@@ -25568,7 +26038,7 @@ async function rebuildMemoriesIndex(projectDir) {
25568
26038
  );
25569
26039
  }
25570
26040
  lines.push("");
25571
- const indexPath = resolve65(dir, "_index.md");
26041
+ const indexPath = resolve67(dir, "_index.md");
25572
26042
  await writeFileForce(indexPath, lines.join("\n"));
25573
26043
  return { total: files.length, path: indexPath };
25574
26044
  }
@@ -25606,8 +26076,8 @@ async function runResourceAdd(options) {
25606
26076
  if (!isValidSlug(options.project)) {
25607
26077
  throw new Error(`Invalid project slug: "${options.project}".`);
25608
26078
  }
25609
- const projectDir = resolve66(defaultProjectDir(), options.project);
25610
- if (!await fileExists(resolve66(projectDir, "project.md"))) {
26079
+ const projectDir = resolve68(defaultProjectDir(), options.project);
26080
+ if (!await fileExists(resolve68(projectDir, "project.md"))) {
25611
26081
  throw new Error(`Project "${options.project}" not found at ${projectDir}.`);
25612
26082
  }
25613
26083
  if (!options.name) throw new Error("--name is required.");
@@ -25616,7 +26086,7 @@ async function runResourceAdd(options) {
25616
26086
  if (!isValidSlug(slug)) {
25617
26087
  throw new Error(`Invalid resource slug: "${slug}".`);
25618
26088
  }
25619
- const filePath = resolve66(projectDir, "resources", `${slug}.md`);
26089
+ const filePath = resolve68(projectDir, "resources", `${slug}.md`);
25620
26090
  if (await fileExists(filePath) && !options.force) {
25621
26091
  throw new Error(
25622
26092
  `Resource "${slug}" already exists at ${filePath}. Use --force to overwrite.`
@@ -25651,7 +26121,7 @@ init_paths();
25651
26121
  init_fs();
25652
26122
  init_slug();
25653
26123
  import { Command as Command11 } from "commander";
25654
- import { resolve as resolve67 } from "path";
26124
+ import { resolve as resolve69 } from "path";
25655
26125
  function nowIso4() {
25656
26126
  return (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
25657
26127
  }
@@ -25686,8 +26156,8 @@ async function runMemoryAdd(options) {
25686
26156
  if (!isValidSlug(options.project)) {
25687
26157
  throw new Error(`Invalid project slug: "${options.project}".`);
25688
26158
  }
25689
- const projectDir = resolve67(defaultProjectDir(), options.project);
25690
- if (!await fileExists(resolve67(projectDir, "project.md"))) {
26159
+ const projectDir = resolve69(defaultProjectDir(), options.project);
26160
+ if (!await fileExists(resolve69(projectDir, "project.md"))) {
25691
26161
  throw new Error(`Project "${options.project}" not found at ${projectDir}.`);
25692
26162
  }
25693
26163
  if (!options.name) throw new Error("--name is required.");
@@ -25696,7 +26166,7 @@ async function runMemoryAdd(options) {
25696
26166
  if (!isValidSlug(slug)) {
25697
26167
  throw new Error(`Invalid memory slug: "${slug}".`);
25698
26168
  }
25699
- const filePath = resolve67(projectDir, "memories", `${slug}.md`);
26169
+ const filePath = resolve69(projectDir, "memories", `${slug}.md`);
25700
26170
  if (await fileExists(filePath) && !options.force) {
25701
26171
  throw new Error(
25702
26172
  `Memory "${slug}" already exists at ${filePath}. Use --force to overwrite.`
@@ -25733,8 +26203,8 @@ init_paths();
25733
26203
  init_fs();
25734
26204
  init_frontmatter();
25735
26205
  import { Command as Command12 } from "commander";
25736
- import { readFile as readFile46 } from "fs/promises";
25737
- import { resolve as resolve68 } from "path";
26206
+ import { readFile as readFile47 } from "fs/promises";
26207
+ import { resolve as resolve70 } from "path";
25738
26208
  var AGE_PATTERN = /^(\d+)([dhwm])$/i;
25739
26209
  function parseAgeToCutoff(age) {
25740
26210
  const match = age.match(AGE_PATTERN);
@@ -25753,7 +26223,7 @@ function parseAgeToCutoff(age) {
25753
26223
  }
25754
26224
  function assignmentMdPath(item) {
25755
26225
  if (item.projectSlug) {
25756
- return resolve68(
26226
+ return resolve70(
25757
26227
  defaultProjectDir(),
25758
26228
  item.projectSlug,
25759
26229
  "assignments",
@@ -25761,13 +26231,13 @@ function assignmentMdPath(item) {
25761
26231
  "assignment.md"
25762
26232
  );
25763
26233
  }
25764
- return resolve68(assignmentsDir(), item.id, "assignment.md");
26234
+ return resolve70(assignmentsDir(), item.id, "assignment.md");
25765
26235
  }
25766
26236
  async function loadTags(item) {
25767
26237
  const path = assignmentMdPath(item);
25768
26238
  if (!await fileExists(path)) return [];
25769
26239
  try {
25770
- const content = await readFile46(path, "utf-8");
26240
+ const content = await readFile47(path, "utf-8");
25771
26241
  return parseAssignmentFrontmatter(content).tags;
25772
26242
  } catch {
25773
26243
  return [];
@@ -25845,10 +26315,10 @@ var lsCommand = new Command12("ls").description(
25845
26315
 
25846
26316
  // src/cli-default-command.ts
25847
26317
  init_config2();
25848
- import { readdir as readdir23 } from "fs/promises";
26318
+ import { readdir as readdir24 } from "fs/promises";
25849
26319
  async function hasAnyProjectContent(projectsDir2) {
25850
26320
  try {
25851
- const entries = await readdir23(projectsDir2, { withFileTypes: true });
26321
+ const entries = await readdir24(projectsDir2, { withFileTypes: true });
25852
26322
  return entries.some((entry) => entry.isDirectory());
25853
26323
  } catch {
25854
26324
  return false;
@@ -25885,20 +26355,20 @@ async function getDefaultCommandName() {
25885
26355
  init_paths();
25886
26356
  init_fs();
25887
26357
  import { fileURLToPath as fileURLToPath10 } from "url";
25888
- import { readFile as readFile48 } from "fs/promises";
25889
- import { dirname as dirname20, join as join14, resolve as resolve69 } from "path";
26358
+ import { readFile as readFile49 } from "fs/promises";
26359
+ import { dirname as dirname20, join as join14, resolve as resolve71 } from "path";
25890
26360
  import { spawn as spawn9 } from "child_process";
25891
26361
  import { createInterface as createInterface2 } from "readline/promises";
25892
26362
 
25893
26363
  // src/utils/version.ts
25894
26364
  import { fileURLToPath as fileURLToPath9 } from "url";
25895
- import { readFile as readFile47 } from "fs/promises";
26365
+ import { readFile as readFile48 } from "fs/promises";
25896
26366
  import { dirname as dirname19, join as join13 } from "path";
25897
26367
  async function readPackageVersion(scriptUrl) {
25898
26368
  try {
25899
26369
  const scriptPath = fileURLToPath9(scriptUrl);
25900
26370
  const pkgRoot = dirname19(dirname19(scriptPath));
25901
- const raw = await readFile47(join13(pkgRoot, "package.json"), "utf-8");
26371
+ const raw = await readFile48(join13(pkgRoot, "package.json"), "utf-8");
25902
26372
  const parsed = JSON.parse(raw);
25903
26373
  return typeof parsed.version === "string" ? parsed.version : null;
25904
26374
  } catch {
@@ -25907,7 +26377,7 @@ async function readPackageVersion(scriptUrl) {
25907
26377
  }
25908
26378
 
25909
26379
  // src/utils/npx-prompt.ts
25910
- var STATE_FILE = resolve69(syntaurRoot(), "npx-install.json");
26380
+ var STATE_FILE = resolve71(syntaurRoot(), "npx-install.json");
25911
26381
  var META_ARGS2 = /* @__PURE__ */ new Set(["-h", "--help", "-V", "--version", "help"]);
25912
26382
  var GLOBAL_VERSION_TIMEOUT_MS = 2e3;
25913
26383
  function isRunningViaNpx(scriptUrl) {
@@ -25928,7 +26398,7 @@ function isRunningViaNpx(scriptUrl) {
25928
26398
  async function readState() {
25929
26399
  if (!await fileExists(STATE_FILE)) return null;
25930
26400
  try {
25931
- const raw = await readFile48(STATE_FILE, "utf-8");
26401
+ const raw = await readFile49(STATE_FILE, "utf-8");
25932
26402
  return JSON.parse(raw);
25933
26403
  } catch {
25934
26404
  return null;
@@ -25987,7 +26457,7 @@ async function readGlobalVersion() {
25987
26457
  try {
25988
26458
  const manifestPath = join14(rootPath, "syntaur", "package.json");
25989
26459
  if (!await fileExists(manifestPath)) return null;
25990
- const raw = await readFile48(manifestPath, "utf-8");
26460
+ const raw = await readFile49(manifestPath, "utf-8");
25991
26461
  const parsed = JSON.parse(raw);
25992
26462
  return typeof parsed.version === "string" ? parsed.version : null;
25993
26463
  } catch {