syntaur 0.6.0 → 0.6.1

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 (62) hide show
  1. package/dashboard/dist/assets/{_basePickBy-BQIP1Ca7.js → _basePickBy-DTYUlCEg.js} +1 -1
  2. package/dashboard/dist/assets/{_baseUniq-BnBWRwT7.js → _baseUniq-C0Y4HRd5.js} +1 -1
  3. package/dashboard/dist/assets/{arc-BYWL4eq0.js → arc-BFx2eqN9.js} +1 -1
  4. package/dashboard/dist/assets/{architectureDiagram-2XIMDMQ5-CD_SWPSa.js → architectureDiagram-2XIMDMQ5-Erol1JD6.js} +1 -1
  5. package/dashboard/dist/assets/{blockDiagram-WCTKOSBZ-BS1ZbFBU.js → blockDiagram-WCTKOSBZ-kSkh6VkS.js} +1 -1
  6. package/dashboard/dist/assets/{c4Diagram-IC4MRINW-D99yg-l2.js → c4Diagram-IC4MRINW-C04oKzvX.js} +1 -1
  7. package/dashboard/dist/assets/channel-C82tBKZ7.js +1 -0
  8. package/dashboard/dist/assets/{chunk-4BX2VUAB-BkN9IORC.js → chunk-4BX2VUAB-C3t0tXt-.js} +1 -1
  9. package/dashboard/dist/assets/{chunk-55IACEB6-BQPHWefV.js → chunk-55IACEB6-2cnyEL0b.js} +1 -1
  10. package/dashboard/dist/assets/{chunk-FMBD7UC4-CNcExMdx.js → chunk-FMBD7UC4-DIY9MTNi.js} +1 -1
  11. package/dashboard/dist/assets/{chunk-JSJVCQXG-LXBmftkC.js → chunk-JSJVCQXG-Cw8fpqpE.js} +1 -1
  12. package/dashboard/dist/assets/{chunk-KX2RTZJC-Tqi7zNqq.js → chunk-KX2RTZJC-BAhd66XV.js} +1 -1
  13. package/dashboard/dist/assets/{chunk-NQ4KR5QH-DkMbx-rW.js → chunk-NQ4KR5QH-RzXwoxk3.js} +1 -1
  14. package/dashboard/dist/assets/{chunk-QZHKN3VN-BlrRCfkJ.js → chunk-QZHKN3VN-Dgri4sGz.js} +1 -1
  15. package/dashboard/dist/assets/{chunk-WL4C6EOR-of3XBzMu.js → chunk-WL4C6EOR-DYLj9JRa.js} +1 -1
  16. package/dashboard/dist/assets/classDiagram-VBA2DB6C-STOZ51tg.js +1 -0
  17. package/dashboard/dist/assets/classDiagram-v2-RAHNMMFH-STOZ51tg.js +1 -0
  18. package/dashboard/dist/assets/clone-TzhWk-Bj.js +1 -0
  19. package/dashboard/dist/assets/{cose-bilkent-S5V4N54A-BlIiyO76.js → cose-bilkent-S5V4N54A-DfY_Fnfu.js} +1 -1
  20. package/dashboard/dist/assets/{dagre-KLK3FWXG-CYQjSI9N.js → dagre-KLK3FWXG-CyTKIVSK.js} +1 -1
  21. package/dashboard/dist/assets/{diagram-E7M64L7V-BZHzTKct.js → diagram-E7M64L7V-Krub7Xxo.js} +1 -1
  22. package/dashboard/dist/assets/{diagram-IFDJBPK2-kMP3WqBV.js → diagram-IFDJBPK2-giUl9uHz.js} +1 -1
  23. package/dashboard/dist/assets/{diagram-P4PSJMXO-BWSHyFOv.js → diagram-P4PSJMXO-oAtnO3C9.js} +1 -1
  24. package/dashboard/dist/assets/{erDiagram-INFDFZHY-B5HrvsPP.js → erDiagram-INFDFZHY-eYaVjXqo.js} +1 -1
  25. package/dashboard/dist/assets/{flowDiagram-PKNHOUZH-Dm4ewP7w.js → flowDiagram-PKNHOUZH-or5S0_Sb.js} +1 -1
  26. package/dashboard/dist/assets/{ganttDiagram-A5KZAMGK-DB3k27zu.js → ganttDiagram-A5KZAMGK-C9R1lsme.js} +1 -1
  27. package/dashboard/dist/assets/{gitGraphDiagram-K3NZZRJ6-G7y6Ey-m.js → gitGraphDiagram-K3NZZRJ6-BQwsDzvp.js} +1 -1
  28. package/dashboard/dist/assets/{graph-CaM4i6vq.js → graph-EQOX1wg8.js} +1 -1
  29. package/dashboard/dist/assets/index-Cy7yjuqO.js +500 -0
  30. package/dashboard/dist/assets/index-u80fISp0.css +1 -0
  31. package/dashboard/dist/assets/{infoDiagram-LFFYTUFH-JNTUbTjg.js → infoDiagram-LFFYTUFH-BjLlQWxk.js} +1 -1
  32. package/dashboard/dist/assets/{ishikawaDiagram-PHBUUO56-BZJt1ht8.js → ishikawaDiagram-PHBUUO56-BNjydh4j.js} +1 -1
  33. package/dashboard/dist/assets/{journeyDiagram-4ABVD52K-DPcqvl9A.js → journeyDiagram-4ABVD52K-DNzE7TgQ.js} +1 -1
  34. package/dashboard/dist/assets/{kanban-definition-K7BYSVSG-D1D7AuOV.js → kanban-definition-K7BYSVSG-FbNvGx6i.js} +1 -1
  35. package/dashboard/dist/assets/{layout-BTOh3EDT.js → layout-B2yZvlWs.js} +1 -1
  36. package/dashboard/dist/assets/{linear-MbCpC_Cg.js → linear-p68yY_14.js} +1 -1
  37. package/dashboard/dist/assets/{mermaid.core-CYbhqlNy.js → mermaid.core-D558akcW.js} +4 -4
  38. package/dashboard/dist/assets/{mindmap-definition-YRQLILUH-CwYCISFH.js → mindmap-definition-YRQLILUH-CZPgesSK.js} +1 -1
  39. package/dashboard/dist/assets/{pieDiagram-SKSYHLDU-5qfZ73SG.js → pieDiagram-SKSYHLDU-CdXMWspp.js} +1 -1
  40. package/dashboard/dist/assets/{quadrantDiagram-337W2JSQ-WI8y1sQ_.js → quadrantDiagram-337W2JSQ-D7tq22ZY.js} +1 -1
  41. package/dashboard/dist/assets/{requirementDiagram-Z7DCOOCP-BFlD0ZTS.js → requirementDiagram-Z7DCOOCP-ByZxUSmd.js} +1 -1
  42. package/dashboard/dist/assets/{sankeyDiagram-WA2Y5GQK-Bdckv1Se.js → sankeyDiagram-WA2Y5GQK-CZon9rRY.js} +1 -1
  43. package/dashboard/dist/assets/{sequenceDiagram-2WXFIKYE-DgzxKAlZ.js → sequenceDiagram-2WXFIKYE-nbELB6rb.js} +1 -1
  44. package/dashboard/dist/assets/{stateDiagram-RAJIS63D-DO4OXahC.js → stateDiagram-RAJIS63D-D_OPKr5B.js} +1 -1
  45. package/dashboard/dist/assets/stateDiagram-v2-FVOUBMTO-4pLM5B3m.js +1 -0
  46. package/dashboard/dist/assets/{timeline-definition-YZTLITO2-BBB01JWw.js → timeline-definition-YZTLITO2-Cxuvk1D2.js} +1 -1
  47. package/dashboard/dist/assets/{treemap-KZPCXAKY-Dr0jb8op.js → treemap-KZPCXAKY-C0CRpL92.js} +1 -1
  48. package/dashboard/dist/assets/{vennDiagram-LZ73GAT5-D40KFl2o.js → vennDiagram-LZ73GAT5-DijCj6M3.js} +1 -1
  49. package/dashboard/dist/assets/{xychartDiagram-JWTSCODW-DBUmWQfT.js → xychartDiagram-JWTSCODW-CdpE0oRi.js} +1 -1
  50. package/dashboard/dist/index.html +2 -2
  51. package/dist/dashboard/server.js +419 -38
  52. package/dist/dashboard/server.js.map +1 -1
  53. package/dist/index.js +577 -88
  54. package/dist/index.js.map +1 -1
  55. package/package.json +1 -1
  56. package/dashboard/dist/assets/channel-Df6VrFK5.js +0 -1
  57. package/dashboard/dist/assets/classDiagram-VBA2DB6C-CyfzumTY.js +0 -1
  58. package/dashboard/dist/assets/classDiagram-v2-RAHNMMFH-CyfzumTY.js +0 -1
  59. package/dashboard/dist/assets/clone-CMs4Aqrx.js +0 -1
  60. package/dashboard/dist/assets/index-B4QMu-Oq.css +0 -1
  61. package/dashboard/dist/assets/index-BBWZjPBC.js +0 -495
  62. package/dashboard/dist/assets/stateDiagram-v2-FVOUBMTO-o8bgX-J3.js +0 -1
package/dist/index.js CHANGED
@@ -6684,7 +6684,7 @@ __export(launch_exports, {
6684
6684
  shellQuote: () => shellQuote
6685
6685
  });
6686
6686
  import { spawn as spawn2 } from "child_process";
6687
- import { mkdir as mkdir5, writeFile as writeFile9 } from "fs/promises";
6687
+ import { mkdir as mkdir6, writeFile as writeFile9 } from "fs/promises";
6688
6688
  import { isAbsolute as isAbsolute3, resolve as resolve32 } from "path";
6689
6689
  function formatFallbackCwdWarning(opts) {
6690
6690
  const missing = [];
@@ -6743,7 +6743,7 @@ async function launchAgent(options) {
6743
6743
  if (warning) console.warn(warning);
6744
6744
  }
6745
6745
  const contextDir = resolve32(workspaceDir, ".syntaur");
6746
- await mkdir5(contextDir, { recursive: true });
6746
+ await mkdir6(contextDir, { recursive: true });
6747
6747
  const context = {
6748
6748
  projectSlug,
6749
6749
  assignmentSlug,
@@ -7070,7 +7070,7 @@ init_create_assignment();
7070
7070
  init_config2();
7071
7071
  import { spawn } from "child_process";
7072
7072
  import { createServer as createNetServer } from "net";
7073
- import { resolve as resolve22, dirname as dirname4 } from "path";
7073
+ import { resolve as resolve22, dirname as dirname6 } from "path";
7074
7074
  import { fileURLToPath as fileURLToPath2 } from "url";
7075
7075
 
7076
7076
  // src/dashboard/server.ts
@@ -9536,37 +9536,62 @@ init_fs_migration();
9536
9536
  // src/dashboard/api-todos.ts
9537
9537
  init_parser2();
9538
9538
  init_fs();
9539
+ init_paths();
9539
9540
  import { Router as Router5 } from "express";
9540
9541
  import { readdir as readdir9 } from "fs/promises";
9541
- var WORKSPACE_REGEX = /^[a-z0-9_][a-z0-9-]*$/;
9542
- function getWorkspaceParam(value) {
9543
- if (Array.isArray(value)) {
9544
- return value[0] ?? "";
9545
- }
9546
- return value ?? "";
9547
- }
9542
+ import { resolve as resolvePath, dirname as dirname4 } from "path";
9543
+ import { rename as rename3, mkdir as mkdir2 } from "fs/promises";
9544
+
9545
+ // src/dashboard/todos-locks.ts
9548
9546
  var writeLocks = /* @__PURE__ */ new Map();
9549
9547
  function withLock(lockKey, fn) {
9550
9548
  const prev = writeLocks.get(lockKey) ?? Promise.resolve();
9551
9549
  const next = prev.then(fn);
9552
- writeLocks.set(lockKey, next.then(() => {
9553
- }, () => {
9554
- }));
9550
+ writeLocks.set(
9551
+ lockKey,
9552
+ next.then(
9553
+ () => {
9554
+ },
9555
+ () => {
9556
+ }
9557
+ )
9558
+ );
9555
9559
  return next;
9556
9560
  }
9557
9561
  function wsLock(workspace, fn) {
9558
9562
  return withLock(`ws:${workspace}`, fn);
9559
9563
  }
9564
+ function projLock(slug, fn) {
9565
+ return withLock(`proj:${slug}`, fn);
9566
+ }
9567
+ function withTwoLocks(keyA, keyB, fn) {
9568
+ if (keyA === keyB) return withLock(keyA, fn);
9569
+ const [first, second] = keyA < keyB ? [keyA, keyB] : [keyB, keyA];
9570
+ return withLock(first, () => withLock(second, fn));
9571
+ }
9572
+
9573
+ // src/dashboard/api-todos.ts
9574
+ init_slug();
9575
+ var WORKSPACE_REGEX = /^[a-z0-9_][a-z0-9-]*$/;
9576
+ function getWorkspaceParam(value) {
9577
+ if (Array.isArray(value)) {
9578
+ return value[0] ?? "";
9579
+ }
9580
+ return value ?? "";
9581
+ }
9560
9582
  function touchItem(item) {
9561
9583
  const now = (/* @__PURE__ */ new Date()).toISOString();
9562
9584
  if (item.createdAt === null) item.createdAt = now;
9563
9585
  item.updatedAt = now;
9564
9586
  }
9565
- function createTodosRouter(todosDir2, broadcast) {
9587
+ function createTodosRouter(todosDir2, broadcast, projectsDir2) {
9566
9588
  const router = Router5();
9567
9589
  function broadcastUpdate() {
9568
9590
  broadcast({ type: "todos-updated", timestamp: (/* @__PURE__ */ new Date()).toISOString() });
9569
9591
  }
9592
+ function broadcastProject(slug) {
9593
+ broadcast({ type: "todos-updated", projectSlug: slug, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
9594
+ }
9570
9595
  function validateWorkspace(req, res, next) {
9571
9596
  const workspace = getWorkspaceParam(req.params.workspace);
9572
9597
  if (workspace && !WORKSPACE_REGEX.test(workspace)) {
@@ -9965,7 +9990,7 @@ workspace: ${workspace}
9965
9990
  items.push(item);
9966
9991
  }
9967
9992
  const scopeLabel = workspace === "_global" ? "_global" : `workspace:${workspace}`;
9968
- const { resolve: resolvePath } = await import("path");
9993
+ const { resolve: resolvePath2 } = await import("path");
9969
9994
  const { readConfig: readConfig3 } = await Promise.resolve().then(() => (init_config2(), config_exports));
9970
9995
  const { assignmentsDir: assignmentsDirFn } = await Promise.resolve().then(() => (init_paths(), paths_exports));
9971
9996
  const { fileExists: fileExists2, writeFileForce: writeFileForce2 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
@@ -9995,18 +10020,18 @@ workspace: ${workspace}
9995
10020
  const parts = tg.split("/");
9996
10021
  if (parts.length !== 2) return { error: `Invalid target.assignment "${tg}"` };
9997
10022
  const config = await readConfig3();
9998
- assignmentDir = resolvePath(config.defaultProjectDir, parts[0], "assignments", parts[1]);
10023
+ assignmentDir = resolvePath2(config.defaultProjectDir, parts[0], "assignments", parts[1]);
9999
10024
  assignmentRef = `${parts[0]}/${parts[1]}`;
10000
10025
  } 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)) {
10001
- assignmentDir = resolvePath(assignmentsDirFn(), tg);
10026
+ assignmentDir = resolvePath2(assignmentsDirFn(), tg);
10002
10027
  assignmentRef = tg;
10003
10028
  } else {
10004
10029
  return { error: `Invalid target.assignment "${tg}"` };
10005
10030
  }
10006
- const assignmentMdPath2 = resolvePath(assignmentDir, "assignment.md");
10031
+ const assignmentMdPath2 = resolvePath2(assignmentDir, "assignment.md");
10007
10032
  if (!await fileExists2(assignmentMdPath2)) return { error: `Target assignment not found: ${assignmentMdPath2}` };
10008
10033
  }
10009
- const assignmentMdPath = resolvePath(assignmentDir, "assignment.md");
10034
+ const assignmentMdPath = resolvePath2(assignmentDir, "assignment.md");
10010
10035
  let content = await readFile32(assignmentMdPath, "utf-8");
10011
10036
  content = appendTodosToAssignmentBody2(
10012
10037
  content,
@@ -10050,6 +10075,115 @@ workspace: ${workspace}
10050
10075
  res.status(500).json({ error: error instanceof Error ? error.message : "Failed to promote todos" });
10051
10076
  }
10052
10077
  });
10078
+ router.post("/:workspace/:id/move", async (req, res) => {
10079
+ try {
10080
+ const sourceWs = getWorkspaceParam(req.params.workspace);
10081
+ const id = req.params.id;
10082
+ const to = req.body?.to;
10083
+ if (!to || typeof to !== "object") {
10084
+ res.status(400).json({ error: "body.to is required" });
10085
+ return;
10086
+ }
10087
+ const targetCount = [Boolean(to.workspace), Boolean(to.project), Boolean(to.global)].filter(Boolean).length;
10088
+ if (targetCount !== 1) {
10089
+ res.status(400).json({ error: "body.to must specify exactly one of workspace, project, or global" });
10090
+ return;
10091
+ }
10092
+ if (to.project && !isValidSlug(to.project)) {
10093
+ res.status(400).json({ error: `Invalid target project slug: "${to.project}"` });
10094
+ return;
10095
+ }
10096
+ if (to.workspace && !WORKSPACE_REGEX.test(to.workspace)) {
10097
+ res.status(400).json({ error: `Invalid target workspace name: "${to.workspace}"` });
10098
+ return;
10099
+ }
10100
+ let target;
10101
+ if (to.global) {
10102
+ target = { kind: "workspace", id: "_global", todosPath: todosDir2, lockKey: "ws:_global" };
10103
+ } else if (to.workspace) {
10104
+ target = { kind: "workspace", id: to.workspace, todosPath: todosDir2, lockKey: `ws:${to.workspace}` };
10105
+ } else {
10106
+ if (!projectsDir2) {
10107
+ res.status(500).json({ error: "Server not configured with projectsDir; cannot move to project scope" });
10108
+ return;
10109
+ }
10110
+ const slug = to.project;
10111
+ const projectMd = resolvePath(projectsDir2, slug, "project.md");
10112
+ if (!await fileExists(projectMd)) {
10113
+ res.status(404).json({ error: `Target project "${slug}" not found` });
10114
+ return;
10115
+ }
10116
+ target = {
10117
+ kind: "project",
10118
+ id: slug,
10119
+ todosPath: projectTodosDir(projectsDir2, slug),
10120
+ lockKey: `proj:${slug}`
10121
+ };
10122
+ }
10123
+ const sourceLockKey = `ws:${sourceWs}`;
10124
+ if (sourceLockKey === target.lockKey) {
10125
+ res.status(400).json({ error: "cannot move to the same scope" });
10126
+ return;
10127
+ }
10128
+ const result = await withTwoLocks(sourceLockKey, target.lockKey, async () => {
10129
+ const sourceChecklist = await readChecklist(todosDir2, sourceWs);
10130
+ const targetChecklist = await readChecklist(target.todosPath, target.id);
10131
+ const idx = sourceChecklist.items.findIndex((i) => i.id === id);
10132
+ if (idx === -1) return { status: 404, error: `Todo "${id}" not found` };
10133
+ if (targetChecklist.items.some((i) => i.id === id)) {
10134
+ return { status: 409, error: "id already exists in target" };
10135
+ }
10136
+ const item = sourceChecklist.items[idx];
10137
+ if (item.planDir) {
10138
+ const newPlanDir = todoPlanDir(target.todosPath, target.id, id);
10139
+ if (await fileExists(newPlanDir)) {
10140
+ return { status: 409, error: "plan dir already exists in target" };
10141
+ }
10142
+ await mkdir2(dirname4(newPlanDir), { recursive: true });
10143
+ await rename3(item.planDir, newPlanDir);
10144
+ item.planDir = newPlanDir;
10145
+ }
10146
+ sourceChecklist.items.splice(idx, 1);
10147
+ targetChecklist.items.push(item);
10148
+ await writeChecklist(todosDir2, sourceChecklist);
10149
+ await writeChecklist(target.todosPath, targetChecklist);
10150
+ const sourceLabel = sourceWs === "_global" ? "_global" : `workspace:${sourceWs}`;
10151
+ const targetLabel = target.kind === "project" ? `project:${target.id}` : target.id === "_global" ? "_global" : `workspace:${target.id}`;
10152
+ const ts = (/* @__PURE__ */ new Date()).toISOString();
10153
+ await appendLogEntry2(todosDir2, sourceWs, {
10154
+ timestamp: ts,
10155
+ itemIds: [id],
10156
+ items: item.description,
10157
+ session: null,
10158
+ branch: item.branch || null,
10159
+ summary: `Moved to ${targetLabel}`,
10160
+ blockers: null,
10161
+ status: null
10162
+ });
10163
+ await appendLogEntry2(target.todosPath, target.id, {
10164
+ timestamp: ts,
10165
+ itemIds: [id],
10166
+ items: item.description,
10167
+ session: null,
10168
+ branch: item.branch || null,
10169
+ summary: `Moved from ${sourceLabel}`,
10170
+ blockers: null,
10171
+ status: null
10172
+ });
10173
+ return { status: 200, item };
10174
+ });
10175
+ if (result.status !== 200) {
10176
+ res.status(result.status).json({ error: result.error });
10177
+ return;
10178
+ }
10179
+ broadcastUpdate();
10180
+ if (target.kind === "project") broadcastProject(target.id);
10181
+ else broadcastUpdate();
10182
+ res.json({ moved: id, to: target });
10183
+ } catch (error) {
10184
+ res.status(500).json({ error: error instanceof Error ? error.message : "Failed to move todo" });
10185
+ }
10186
+ });
10053
10187
  router.post("/:workspace/:id/unblock", async (req, res) => {
10054
10188
  try {
10055
10189
  const workspace = getWorkspaceParam(req.params.workspace);
@@ -10082,18 +10216,9 @@ init_fs();
10082
10216
  init_paths();
10083
10217
  init_slug();
10084
10218
  import { Router as Router6 } from "express";
10085
- import { mkdir as mkdir2, readFile as readFile14 } from "fs/promises";
10086
- import { resolve as resolve19 } from "path";
10087
- var writeLocks2 = /* @__PURE__ */ new Map();
10088
- function projLock(slug, fn) {
10089
- const key = `proj:${slug}`;
10090
- const prev = writeLocks2.get(key) ?? Promise.resolve();
10091
- const next = prev.then(fn);
10092
- writeLocks2.set(key, next.then(() => {
10093
- }, () => {
10094
- }));
10095
- return next;
10096
- }
10219
+ import { mkdir as mkdir3, readFile as readFile14, rename as rename4 } from "fs/promises";
10220
+ import { resolve as resolve19, dirname as dirname5 } from "path";
10221
+ var WORKSPACE_REGEX2 = /^[a-z0-9_][a-z0-9-]*$/;
10097
10222
  function touchItem2(item) {
10098
10223
  const now = (/* @__PURE__ */ new Date()).toISOString();
10099
10224
  if (item.createdAt === null) item.createdAt = now;
@@ -10112,7 +10237,7 @@ async function projectExists(projectsDir2, slug) {
10112
10237
  async function ensureProjectTodosDir(projectsDir2, slug) {
10113
10238
  const todosDir2 = projectTodosDir(projectsDir2, slug);
10114
10239
  try {
10115
- await mkdir2(todosDir2, { recursive: false });
10240
+ await mkdir3(todosDir2, { recursive: false });
10116
10241
  } catch (err2) {
10117
10242
  const code = err2.code;
10118
10243
  if (code === "EEXIST") return;
@@ -10124,7 +10249,7 @@ async function ensureProjectTodosDir(projectsDir2, slug) {
10124
10249
  throw err2;
10125
10250
  }
10126
10251
  try {
10127
- await mkdir2(resolve19(todosDir2, "archive"), { recursive: false });
10252
+ await mkdir3(resolve19(todosDir2, "archive"), { recursive: false });
10128
10253
  } catch (err2) {
10129
10254
  const code = err2.code;
10130
10255
  if (code === "EEXIST") return;
@@ -10139,11 +10264,14 @@ async function ensureProjectTodosDir(projectsDir2, slug) {
10139
10264
  function notFound(res, slug) {
10140
10265
  res.status(404).json({ error: `Project "${slug}" not found` });
10141
10266
  }
10142
- function createProjectTodosRouter(projectsDir2, broadcast) {
10267
+ function createProjectTodosRouter(projectsDir2, broadcast, workspaceTodosDir) {
10143
10268
  const router = Router6({ mergeParams: true });
10144
10269
  function broadcastUpdate(projectSlug) {
10145
10270
  broadcast({ type: "todos-updated", projectSlug, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
10146
10271
  }
10272
+ function broadcastWorkspace() {
10273
+ broadcast({ type: "todos-updated", timestamp: (/* @__PURE__ */ new Date()).toISOString() });
10274
+ }
10147
10275
  function validateProjectId(req, res, next) {
10148
10276
  const slug = getProjectIdParam(params(req).projectId);
10149
10277
  if (!slug || !isValidSlug(slug)) {
@@ -10691,6 +10819,259 @@ workspace: ${slug}
10691
10819
  res.status(500).json({ error: error instanceof Error ? error.message : "Failed to unblock todo" });
10692
10820
  }
10693
10821
  });
10822
+ router.post("/promote", async (req, res) => {
10823
+ try {
10824
+ const slug = getProjectIdParam(params(req).projectId);
10825
+ if (!await projectExists(projectsDir2, slug)) {
10826
+ notFound(res, slug);
10827
+ return;
10828
+ }
10829
+ const { todoIds, mode, target, title, type, priority, keepSource } = req.body ?? {};
10830
+ if (!Array.isArray(todoIds) || todoIds.length === 0) {
10831
+ res.status(400).json({ error: "todoIds (non-empty array of strings) is required" });
10832
+ return;
10833
+ }
10834
+ if (mode !== "new-assignment" && mode !== "to-assignment") {
10835
+ res.status(400).json({ error: 'mode must be "new-assignment" or "to-assignment"' });
10836
+ return;
10837
+ }
10838
+ const result = await projLock(slug, async () => {
10839
+ if (!await projectExists(projectsDir2, slug)) return { gone: true };
10840
+ await ensureProjectTodosDir(projectsDir2, slug);
10841
+ const todosDir2 = projectTodosDir(projectsDir2, slug);
10842
+ const checklist = await readChecklist(todosDir2, slug);
10843
+ const items = [];
10844
+ for (const id of todoIds) {
10845
+ const item = checklist.items.find((i) => i.id === id);
10846
+ if (!item) return { error: `Todo "${id}" not found` };
10847
+ if (item.status === "completed") return { error: `Todo "${id}" is already completed` };
10848
+ items.push(item);
10849
+ }
10850
+ const scopeLabel = `project:${slug}`;
10851
+ const { assignmentsDir: assignmentsDirFn } = await Promise.resolve().then(() => (init_paths(), paths_exports));
10852
+ const { appendTodosToAssignmentBody: appendTodosToAssignmentBody2, touchAssignmentUpdated: touchAssignmentUpdated2 } = await Promise.resolve().then(() => (init_assignment_todos(), assignment_todos_exports));
10853
+ const { nowTimestamp: nowTimestamp3 } = await Promise.resolve().then(() => (init_timestamp(), timestamp_exports));
10854
+ let assignmentRef;
10855
+ let assignmentDir;
10856
+ if (mode === "new-assignment") {
10857
+ const targetProject = target?.project ?? slug;
10858
+ if (!targetProject) return { error: "target.project is required for new-assignment mode" };
10859
+ if (items.length > 1 && !title) return { error: "title is required when promoting multiple todos" };
10860
+ const { createAssignmentCommand: createAssignmentCommand2 } = await Promise.resolve().then(() => (init_create_assignment(), create_assignment_exports));
10861
+ const created = await createAssignmentCommand2(title || items[0].description, {
10862
+ project: targetProject,
10863
+ type,
10864
+ priority,
10865
+ withTodos: true,
10866
+ silent: true
10867
+ });
10868
+ assignmentDir = created.assignmentDir;
10869
+ assignmentRef = `${created.projectSlug}/${created.slug}`;
10870
+ } else {
10871
+ const tg = target?.assignment || "";
10872
+ if (!tg) return { error: "target.assignment is required for to-assignment mode" };
10873
+ if (tg.includes("/")) {
10874
+ const parts = tg.split("/");
10875
+ if (parts.length !== 2) return { error: `Invalid target.assignment "${tg}"` };
10876
+ assignmentDir = resolve19(projectsDir2, parts[0], "assignments", parts[1]);
10877
+ assignmentRef = `${parts[0]}/${parts[1]}`;
10878
+ } 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)) {
10879
+ assignmentDir = resolve19(assignmentsDirFn(), tg);
10880
+ assignmentRef = tg;
10881
+ } else {
10882
+ return { error: `Invalid target.assignment "${tg}"` };
10883
+ }
10884
+ const assignmentMdPath2 = resolve19(assignmentDir, "assignment.md");
10885
+ if (!await fileExists(assignmentMdPath2)) return { error: `Target assignment not found: ${assignmentMdPath2}` };
10886
+ }
10887
+ const assignmentMdPath = resolve19(assignmentDir, "assignment.md");
10888
+ let content = await readFile14(assignmentMdPath, "utf-8");
10889
+ content = appendTodosToAssignmentBody2(
10890
+ content,
10891
+ items.map((it) => ({
10892
+ description: it.description,
10893
+ trace: `promoted from t:${it.id} in ${scopeLabel}`
10894
+ }))
10895
+ );
10896
+ content = touchAssignmentUpdated2(content, nowTimestamp3());
10897
+ await writeFileForce(assignmentMdPath, content);
10898
+ if (!keepSource) {
10899
+ for (const item of items) {
10900
+ item.status = "completed";
10901
+ item.session = null;
10902
+ touchItem2(item);
10903
+ }
10904
+ checklist.workspace = slug;
10905
+ await writeChecklist(todosDir2, checklist);
10906
+ for (const item of items) {
10907
+ const entry = {
10908
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
10909
+ itemIds: [item.id],
10910
+ items: item.description,
10911
+ session: null,
10912
+ branch: item.branch || null,
10913
+ summary: `Promoted to assignment ${assignmentRef}`,
10914
+ blockers: null,
10915
+ status: null
10916
+ };
10917
+ await appendLogEntry2(todosDir2, slug, entry);
10918
+ }
10919
+ }
10920
+ return { assignmentRef, assignmentDir, promoted: items.map((i) => i.id) };
10921
+ });
10922
+ if ("gone" in result) {
10923
+ notFound(res, slug);
10924
+ return;
10925
+ }
10926
+ if ("error" in result) {
10927
+ res.status(400).json({ error: result.error });
10928
+ return;
10929
+ }
10930
+ broadcastUpdate(slug);
10931
+ res.json(result);
10932
+ } catch (error) {
10933
+ if (error.code === "PROJECT_GONE") {
10934
+ notFound(res, getProjectIdParam(params(req).projectId));
10935
+ return;
10936
+ }
10937
+ res.status(500).json({ error: error instanceof Error ? error.message : "Failed to promote todos" });
10938
+ }
10939
+ });
10940
+ router.post("/:id/move", async (req, res) => {
10941
+ try {
10942
+ const sourceSlug = getProjectIdParam(params(req).projectId);
10943
+ const id = params(req).id ?? "";
10944
+ if (!await projectExists(projectsDir2, sourceSlug)) {
10945
+ notFound(res, sourceSlug);
10946
+ return;
10947
+ }
10948
+ const to = req.body?.to;
10949
+ if (!to || typeof to !== "object") {
10950
+ res.status(400).json({ error: "body.to is required" });
10951
+ return;
10952
+ }
10953
+ const targetCount = [Boolean(to.workspace), Boolean(to.project), Boolean(to.global)].filter(Boolean).length;
10954
+ if (targetCount !== 1) {
10955
+ res.status(400).json({ error: "body.to must specify exactly one of workspace, project, or global" });
10956
+ return;
10957
+ }
10958
+ if (to.project && !isValidSlug(to.project)) {
10959
+ res.status(400).json({ error: `Invalid target project slug: "${to.project}"` });
10960
+ return;
10961
+ }
10962
+ if (to.workspace && !WORKSPACE_REGEX2.test(to.workspace)) {
10963
+ res.status(400).json({ error: `Invalid target workspace name: "${to.workspace}"` });
10964
+ return;
10965
+ }
10966
+ let target;
10967
+ if (to.global) {
10968
+ if (!workspaceTodosDir) {
10969
+ res.status(500).json({ error: "Server not configured with workspaceTodosDir; cannot move to global scope" });
10970
+ return;
10971
+ }
10972
+ target = { kind: "workspace", id: "_global", todosPath: workspaceTodosDir, lockKey: "ws:_global" };
10973
+ } else if (to.workspace) {
10974
+ if (!workspaceTodosDir) {
10975
+ res.status(500).json({ error: "Server not configured with workspaceTodosDir; cannot move to workspace scope" });
10976
+ return;
10977
+ }
10978
+ target = { kind: "workspace", id: to.workspace, todosPath: workspaceTodosDir, lockKey: `ws:${to.workspace}` };
10979
+ } else {
10980
+ const tslug = to.project;
10981
+ if (!await projectExists(projectsDir2, tslug)) {
10982
+ res.status(404).json({ error: `Target project "${tslug}" not found` });
10983
+ return;
10984
+ }
10985
+ target = {
10986
+ kind: "project",
10987
+ id: tslug,
10988
+ todosPath: projectTodosDir(projectsDir2, tslug),
10989
+ lockKey: `proj:${tslug}`
10990
+ };
10991
+ }
10992
+ const sourceLockKey = `proj:${sourceSlug}`;
10993
+ if (sourceLockKey === target.lockKey) {
10994
+ res.status(400).json({ error: "cannot move to the same scope" });
10995
+ return;
10996
+ }
10997
+ const result = await withTwoLocks(sourceLockKey, target.lockKey, async () => {
10998
+ if (!await projectExists(projectsDir2, sourceSlug)) return { status: "gone" };
10999
+ if (target.kind === "project" && !await projectExists(projectsDir2, target.id)) {
11000
+ return { status: "targetGone" };
11001
+ }
11002
+ await ensureProjectTodosDir(projectsDir2, sourceSlug);
11003
+ const sourceTodosDir = projectTodosDir(projectsDir2, sourceSlug);
11004
+ const sourceChecklist = await readChecklist(sourceTodosDir, sourceSlug);
11005
+ const targetChecklist = await readChecklist(target.todosPath, target.id);
11006
+ const idx = sourceChecklist.items.findIndex((i) => i.id === id);
11007
+ if (idx === -1) return { status: 404, error: `Todo "${id}" not found` };
11008
+ if (targetChecklist.items.some((i) => i.id === id)) {
11009
+ return { status: 409, error: "id already exists in target" };
11010
+ }
11011
+ const item = sourceChecklist.items[idx];
11012
+ if (item.planDir) {
11013
+ const newPlanDir = todoPlanDir(target.todosPath, target.id, id);
11014
+ if (await fileExists(newPlanDir)) {
11015
+ return { status: 409, error: "plan dir already exists in target" };
11016
+ }
11017
+ await mkdir3(dirname5(newPlanDir), { recursive: true });
11018
+ await rename4(item.planDir, newPlanDir);
11019
+ item.planDir = newPlanDir;
11020
+ }
11021
+ sourceChecklist.items.splice(idx, 1);
11022
+ targetChecklist.items.push(item);
11023
+ sourceChecklist.workspace = sourceSlug;
11024
+ await writeChecklist(sourceTodosDir, sourceChecklist);
11025
+ await writeChecklist(target.todosPath, targetChecklist);
11026
+ const sourceLabel = `project:${sourceSlug}`;
11027
+ const targetLabel = target.kind === "project" ? `project:${target.id}` : target.id === "_global" ? "_global" : `workspace:${target.id}`;
11028
+ const ts = (/* @__PURE__ */ new Date()).toISOString();
11029
+ await appendLogEntry2(sourceTodosDir, sourceSlug, {
11030
+ timestamp: ts,
11031
+ itemIds: [id],
11032
+ items: item.description,
11033
+ session: null,
11034
+ branch: item.branch || null,
11035
+ summary: `Moved to ${targetLabel}`,
11036
+ blockers: null,
11037
+ status: null
11038
+ });
11039
+ await appendLogEntry2(target.todosPath, target.id, {
11040
+ timestamp: ts,
11041
+ itemIds: [id],
11042
+ items: item.description,
11043
+ session: null,
11044
+ branch: item.branch || null,
11045
+ summary: `Moved from ${sourceLabel}`,
11046
+ blockers: null,
11047
+ status: null
11048
+ });
11049
+ return { status: 200, item };
11050
+ });
11051
+ if (result.status === "gone") {
11052
+ notFound(res, sourceSlug);
11053
+ return;
11054
+ }
11055
+ if (result.status === "targetGone") {
11056
+ res.status(404).json({ error: `Target project "${target.id}" not found` });
11057
+ return;
11058
+ }
11059
+ if (result.status !== 200) {
11060
+ res.status(result.status).json({ error: result.error });
11061
+ return;
11062
+ }
11063
+ broadcastUpdate(sourceSlug);
11064
+ if (target.kind === "project") broadcastUpdate(target.id);
11065
+ else broadcastWorkspace();
11066
+ res.json({ moved: id, to: target });
11067
+ } catch (error) {
11068
+ if (error.code === "PROJECT_GONE") {
11069
+ notFound(res, getProjectIdParam(params(req).projectId));
11070
+ return;
11071
+ }
11072
+ res.status(500).json({ error: error instanceof Error ? error.message : "Failed to move todo" });
11073
+ }
11074
+ });
10694
11075
  return router;
10695
11076
  }
10696
11077
 
@@ -10704,7 +11085,7 @@ init_fs();
10704
11085
  init_config2();
10705
11086
  import { execFile as execFile2 } from "child_process";
10706
11087
  import { promisify as promisify2 } from "util";
10707
- import { cp, mkdtemp, rm as rm2, readFile as readFile15, writeFile as writeFile4, unlink as unlink3, stat, open, rename as rename3 } from "fs/promises";
11088
+ import { cp, mkdtemp, rm as rm2, readFile as readFile15, writeFile as writeFile4, unlink as unlink3, stat, open, rename as rename5 } from "fs/promises";
10708
11089
  import { resolve as resolve20, join as join2 } from "path";
10709
11090
  import { tmpdir } from "os";
10710
11091
  var exec2 = promisify2(execFile2);
@@ -10916,7 +11297,7 @@ async function safeRestoreCategory(localPath, repoSrcPath, isFile) {
10916
11297
  const localExistsBefore = await fileExists(localPath);
10917
11298
  if (backupExistsBefore) {
10918
11299
  if (!localExistsBefore) {
10919
- await rename3(backupPath, localPath);
11300
+ await rename5(backupPath, localPath);
10920
11301
  } else {
10921
11302
  throw new Error(
10922
11303
  `Cannot restore "${localPath}": a stale crash-recovery backup exists at ${backupPath} while the current path also exists. Inspect both and remove the one you don't need, then retry.`
@@ -10928,15 +11309,15 @@ async function safeRestoreCategory(localPath, repoSrcPath, isFile) {
10928
11309
  await cp(repoSrcPath, stagingPath, { recursive: true, force: true });
10929
11310
  const localExists = await fileExists(localPath);
10930
11311
  if (localExists) {
10931
- await rename3(localPath, backupPath);
11312
+ await rename5(localPath, backupPath);
10932
11313
  localMovedAside = true;
10933
11314
  }
10934
- await rename3(stagingPath, localPath);
11315
+ await rename5(stagingPath, localPath);
10935
11316
  await rm2(backupPath, { recursive: true, force: true }).catch(() => {
10936
11317
  });
10937
11318
  } catch (err2) {
10938
11319
  if (localMovedAside && await fileExists(backupPath)) {
10939
- await rename3(backupPath, localPath).catch(() => {
11320
+ await rename5(backupPath, localPath).catch(() => {
10940
11321
  });
10941
11322
  }
10942
11323
  await rm2(stagingPath, { recursive: true, force: true }).catch(() => {
@@ -11675,8 +12056,8 @@ function createDashboardServer(options) {
11675
12056
  app.use("/api/servers", createServersRouter(serversDir2, projectsDir2, assignmentsDir2));
11676
12057
  app.use("/api/agent-sessions", createAgentSessionsRouter(projectsDir2, broadcast, assignmentsDir2));
11677
12058
  app.use("/api/playbooks", createPlaybooksRouter(playbooksDir3));
11678
- app.use("/api/todos", createTodosRouter(todosDir2, broadcast));
11679
- app.use("/api/projects/:projectId/todos", createProjectTodosRouter(projectsDir2, broadcast));
12059
+ app.use("/api/todos", createTodosRouter(todosDir2, broadcast, projectsDir2));
12060
+ app.use("/api/projects/:projectId/todos", createProjectTodosRouter(projectsDir2, broadcast, todosDir2));
11680
12061
  app.use("/api/backup", createBackupRouter());
11681
12062
  if (serveStaticUi && dashboardDistPath) {
11682
12063
  const sendOpts = { dotfiles: "allow" };
@@ -11822,7 +12203,7 @@ async function dashboardCommand(options) {
11822
12203
  port = availablePort;
11823
12204
  }
11824
12205
  const thisFile = fileURLToPath2(import.meta.url);
11825
- const packageRoot = resolve22(dirname4(thisFile), "..");
12206
+ const packageRoot = resolve22(dirname6(thisFile), "..");
11826
12207
  const dashboardDist = resolve22(packageRoot, "dashboard", "dist");
11827
12208
  const server = createDashboardServer({
11828
12209
  port,
@@ -12051,14 +12432,14 @@ import {
12051
12432
  } from "fs/promises";
12052
12433
  import { existsSync } from "fs";
12053
12434
  import { homedir as homedir2 } from "os";
12054
- import { basename, dirname as dirname6, isAbsolute as isAbsolute2, relative as relative2, resolve as resolve25 } from "path";
12435
+ import { basename, dirname as dirname8, isAbsolute as isAbsolute2, relative as relative2, resolve as resolve25 } from "path";
12055
12436
 
12056
12437
  // src/utils/package-root.ts
12057
12438
  init_fs();
12058
- import { dirname as dirname5, resolve as resolve24 } from "path";
12439
+ import { dirname as dirname7, resolve as resolve24 } from "path";
12059
12440
  import { fileURLToPath as fileURLToPath3 } from "url";
12060
12441
  async function findPackageRoot(expectedRelativePath) {
12061
- let currentDir = dirname5(fileURLToPath3(import.meta.url));
12442
+ let currentDir = dirname7(fileURLToPath3(import.meta.url));
12062
12443
  while (true) {
12063
12444
  const candidate = resolve24(currentDir, expectedRelativePath);
12064
12445
  if (await fileExists(candidate)) {
@@ -12150,7 +12531,7 @@ async function getInstallStatus(targetDir, pluginKind) {
12150
12531
  const info = await lstat(targetDir);
12151
12532
  if (info.isSymbolicLink()) {
12152
12533
  const symlinkTarget = await readlink(targetDir);
12153
- const resolvedTarget = resolve25(dirname6(targetDir), symlinkTarget);
12534
+ const resolvedTarget = resolve25(dirname8(targetDir), symlinkTarget);
12154
12535
  const manifestName2 = await readPluginManifestName(resolvedTarget, pluginKind);
12155
12536
  return {
12156
12537
  exists: true,
@@ -12187,15 +12568,15 @@ async function writeInstallMetadata(targetDir, pluginKind, installMode, packageM
12187
12568
  );
12188
12569
  }
12189
12570
  async function installCopy(paths, pluginKind) {
12190
- await ensureDir(dirname6(paths.targetDir));
12571
+ await ensureDir(dirname8(paths.targetDir));
12191
12572
  await cp2(paths.sourceDir, paths.targetDir, { recursive: true });
12192
12573
  const packageManifest = await readPackageManifest(paths.packageRoot);
12193
12574
  await writeInstallMetadata(paths.targetDir, pluginKind, "copy", packageManifest);
12194
12575
  }
12195
12576
  async function installLink(paths) {
12196
- await ensureDir(dirname6(paths.targetDir));
12577
+ await ensureDir(dirname8(paths.targetDir));
12197
12578
  await rm3(paths.targetDir, { recursive: true, force: true });
12198
- await ensureDir(dirname6(paths.targetDir));
12579
+ await ensureDir(dirname8(paths.targetDir));
12199
12580
  await symlink(resolve25(paths.sourceDir), paths.targetDir, "dir");
12200
12581
  }
12201
12582
  async function removeInstallMarker(targetDir) {
@@ -12250,7 +12631,7 @@ async function readClaudeMarketplaceFile(manifestPath) {
12250
12631
  };
12251
12632
  }
12252
12633
  async function writeClaudeMarketplaceFile(manifestPath, marketplace) {
12253
- await ensureDir(dirname6(manifestPath));
12634
+ await ensureDir(dirname8(manifestPath));
12254
12635
  await writeFile6(manifestPath, `${JSON.stringify(marketplace, null, 2)}
12255
12636
  `, "utf-8");
12256
12637
  }
@@ -12398,7 +12779,7 @@ async function registerKnownClaudeMarketplace(name, rootDir) {
12398
12779
  };
12399
12780
  existing[name].lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
12400
12781
  existing[name].autoUpdate = true;
12401
- await ensureDir(dirname6(manifestPath));
12782
+ await ensureDir(dirname8(manifestPath));
12402
12783
  await writeFile6(manifestPath, `${JSON.stringify(existing, null, 2)}
12403
12784
  `, "utf-8");
12404
12785
  }
@@ -12433,11 +12814,11 @@ async function ensureClaudeUserMarketplace() {
12433
12814
  }
12434
12815
  async function detectClaudeMarketplaceForTarget(targetDir) {
12435
12816
  const normalizedTargetDir = normalizeAbsoluteInstallPath(targetDir, "Claude plugin target");
12436
- const pluginsDir = dirname6(normalizedTargetDir);
12817
+ const pluginsDir = dirname8(normalizedTargetDir);
12437
12818
  if (basename(pluginsDir) !== "plugins") {
12438
12819
  return null;
12439
12820
  }
12440
- const rootDir = dirname6(pluginsDir);
12821
+ const rootDir = dirname8(pluginsDir);
12441
12822
  const manifestPath = resolve25(rootDir, ".claude-plugin", "marketplace.json");
12442
12823
  if (!await fileExists(manifestPath)) {
12443
12824
  return null;
@@ -12603,7 +12984,7 @@ async function installManagedPlugin(options) {
12603
12984
  };
12604
12985
  }
12605
12986
  function buildMarketplaceSourcePath(pluginTargetDir, marketplacePath) {
12606
- const relPath = relative2(dirname6(marketplacePath), pluginTargetDir).replaceAll("\\", "/");
12987
+ const relPath = relative2(dirname8(marketplacePath), pluginTargetDir).replaceAll("\\", "/");
12607
12988
  if (relPath === "") {
12608
12989
  return ".";
12609
12990
  }
@@ -12640,7 +13021,7 @@ async function readMarketplaceFile(marketplacePath) {
12640
13021
  };
12641
13022
  }
12642
13023
  async function writeMarketplaceFile(marketplacePath, marketplace) {
12643
- await ensureDir(dirname6(marketplacePath));
13024
+ await ensureDir(dirname8(marketplacePath));
12644
13025
  await writeFile6(marketplacePath, `${JSON.stringify(marketplace, null, 2)}
12645
13026
  `, "utf-8");
12646
13027
  }
@@ -12867,8 +13248,8 @@ async function textPrompt(question, defaultValue) {
12867
13248
 
12868
13249
  // src/utils/install-skills.ts
12869
13250
  init_fs();
12870
- import { readFile as readFile17, readdir as readdir11, mkdir as mkdir3, copyFile, rm as rm4 } from "fs/promises";
12871
- import { dirname as dirname7, resolve as resolve26, relative as relative3, join as join3 } from "path";
13251
+ import { readFile as readFile17, readdir as readdir11, mkdir as mkdir4, copyFile, rm as rm4 } from "fs/promises";
13252
+ import { dirname as dirname9, resolve as resolve26, relative as relative3, join as join3 } from "path";
12872
13253
  import { fileURLToPath as fileURLToPath4 } from "url";
12873
13254
  import { homedir as homedir3 } from "os";
12874
13255
  var REQUIRED_SKILLS = [
@@ -12880,7 +13261,7 @@ var REQUIRED_SKILLS = [
12880
13261
  "create-project"
12881
13262
  ];
12882
13263
  function getVendoredSkillsDir() {
12883
- const here = dirname7(fileURLToPath4(import.meta.url));
13264
+ const here = dirname9(fileURLToPath4(import.meta.url));
12884
13265
  return resolve26(here, "..", "vendor", "syntaur-skills", "skills");
12885
13266
  }
12886
13267
  function defaultSkillTargetDir(target) {
@@ -12913,7 +13294,7 @@ async function filesEqual(a, b) {
12913
13294
  }
12914
13295
  }
12915
13296
  async function copyDir(srcDir, destDir) {
12916
- await mkdir3(destDir, { recursive: true });
13297
+ await mkdir4(destDir, { recursive: true });
12917
13298
  const entries = await readdir11(srcDir, { withFileTypes: true });
12918
13299
  for (const entry of entries) {
12919
13300
  const src = join3(srcDir, entry.name);
@@ -12947,7 +13328,7 @@ async function installSkills(options) {
12947
13328
  );
12948
13329
  }
12949
13330
  const results = [];
12950
- await mkdir3(targetRoot, { recursive: true });
13331
+ await mkdir4(targetRoot, { recursive: true });
12951
13332
  for (const skill of REQUIRED_SKILLS) {
12952
13333
  const srcDir = join3(source, skill);
12953
13334
  const destDir = join3(targetRoot, skill);
@@ -13134,7 +13515,7 @@ async function installPluginCommand(options) {
13134
13515
  init_paths();
13135
13516
  init_fs();
13136
13517
  import { readFile as readFile19, writeFile as writeFile8, copyFile as copyFile2, rm as rm5, stat as stat3, symlink as symlink2, unlink as unlink6, lstat as lstat2 } from "fs/promises";
13137
- import { resolve as resolve28, dirname as dirname9 } from "path";
13518
+ import { resolve as resolve28, dirname as dirname11 } from "path";
13138
13519
  import { homedir as homedir4 } from "os";
13139
13520
  import { fileURLToPath as fileURLToPath5 } from "url";
13140
13521
 
@@ -13142,7 +13523,7 @@ import { fileURLToPath as fileURLToPath5 } from "url";
13142
13523
  init_paths();
13143
13524
  init_fs();
13144
13525
  import { readFile as readFile18, writeFile as writeFile7 } from "fs/promises";
13145
- import { resolve as resolve27, dirname as dirname8 } from "path";
13526
+ import { resolve as resolve27, dirname as dirname10 } from "path";
13146
13527
  import { spawnSync } from "child_process";
13147
13528
  import { checkbox, input as input2, confirm } from "@inquirer/prompts";
13148
13529
  var AVAILABLE_SEGMENTS = [
@@ -13263,7 +13644,7 @@ function renderPreview(config, statuslineScript, cwd) {
13263
13644
  env: {
13264
13645
  ...process.env,
13265
13646
  // Force the child to pick up the freshly-written config from install root.
13266
- HOME: dirname8(dirname8(statuslineScript))
13647
+ HOME: dirname10(dirname10(statuslineScript))
13267
13648
  }
13268
13649
  });
13269
13650
  if (res.status !== 0) return null;
@@ -13314,7 +13695,7 @@ async function configureStatuslineCommand(options = {}) {
13314
13695
  console.log("(preview mode \u2014 config NOT written)");
13315
13696
  return;
13316
13697
  }
13317
- await ensureDir(dirname8(configPath));
13698
+ await ensureDir(dirname10(configPath));
13318
13699
  await writeFile7(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
13319
13700
  console.log("Wrote statusline config:");
13320
13701
  console.log(` path: ${configPath}`);
@@ -13341,7 +13722,7 @@ async function configureStatuslineCommand(options = {}) {
13341
13722
  async function writeDefaultConfigIfMissing(installRoot) {
13342
13723
  const path = getConfigPath(installRoot);
13343
13724
  if (await fileExists(path)) return;
13344
- await ensureDir(dirname8(path));
13725
+ await ensureDir(dirname10(path));
13345
13726
  const defaultConfig = {
13346
13727
  segments: ["git", "assignment", "session"],
13347
13728
  separator: " \xB7 "
@@ -13351,7 +13732,7 @@ async function writeDefaultConfigIfMissing(installRoot) {
13351
13732
 
13352
13733
  // src/commands/install-statusline.ts
13353
13734
  function getPackageStatuslineSource() {
13354
- const here = dirname9(fileURLToPath5(import.meta.url));
13735
+ const here = dirname11(fileURLToPath5(import.meta.url));
13355
13736
  return resolve28(here, "..", "statusline", "statusline.sh");
13356
13737
  }
13357
13738
  async function readSettingsJson(settingsPath) {
@@ -13368,7 +13749,7 @@ async function readSettingsJson(settingsPath) {
13368
13749
  }
13369
13750
  }
13370
13751
  async function writeSettingsJson(settingsPath, data) {
13371
- await ensureDir(dirname9(settingsPath));
13752
+ await ensureDir(dirname11(settingsPath));
13372
13753
  await writeFile8(settingsPath, JSON.stringify(data, null, 2) + "\n", "utf-8");
13373
13754
  }
13374
13755
  async function resolveMode(mode, existingCommand, ourCommand) {
@@ -13403,7 +13784,7 @@ function extractExistingCommand(settings) {
13403
13784
  };
13404
13785
  }
13405
13786
  async function backupSettings(settingsSnapshot, backupPath) {
13406
- await ensureDir(dirname9(backupPath));
13787
+ await ensureDir(dirname11(backupPath));
13407
13788
  await writeFile8(
13408
13789
  backupPath,
13409
13790
  JSON.stringify(
@@ -13420,7 +13801,7 @@ async function backupSettings(settingsSnapshot, backupPath) {
13420
13801
  );
13421
13802
  }
13422
13803
  async function installScript(sourceScript, destScript, link) {
13423
- await ensureDir(dirname9(destScript));
13804
+ await ensureDir(dirname11(destScript));
13424
13805
  try {
13425
13806
  const s = await lstat2(destScript);
13426
13807
  if (s.isSymbolicLink() || s.isFile()) {
@@ -13488,7 +13869,7 @@ exec ${existingCommand}
13488
13869
  wrapTarget = wrapperPath;
13489
13870
  }
13490
13871
  }
13491
- await ensureDir(dirname9(confPath));
13872
+ await ensureDir(dirname11(confPath));
13492
13873
  await writeFile8(
13493
13874
  confPath,
13494
13875
  wrapTarget ? `# Wrap target \u2014 the command below is invoked with the same stdin; its
@@ -14408,6 +14789,19 @@ async function disablePlaybookCommand(slug) {
14408
14789
  }
14409
14790
  }
14410
14791
 
14792
+ // src/commands/regen-playbook-manifest.ts
14793
+ init_paths();
14794
+ init_playbooks();
14795
+ init_fs();
14796
+ async function regenPlaybookManifestCommand() {
14797
+ const dir = playbooksDir();
14798
+ if (!await fileExists(dir)) {
14799
+ throw new Error(`Playbooks directory not found at ${dir}. Run "syntaur init" first.`);
14800
+ }
14801
+ await rebuildPlaybookManifest(dir);
14802
+ console.log(`Rebuilt playbook manifest at ${dir}/manifest.md`);
14803
+ }
14804
+
14411
14805
  // src/commands/todo.ts
14412
14806
  init_paths();
14413
14807
  init_parser2();
@@ -14417,7 +14811,7 @@ init_slug();
14417
14811
  import { Command } from "commander";
14418
14812
  import { readFile as readFile23 } from "fs/promises";
14419
14813
  import { resolve as resolve36 } from "path";
14420
- var WORKSPACE_REGEX2 = /^[a-z0-9_][a-z0-9-]*$/;
14814
+ var WORKSPACE_REGEX3 = /^[a-z0-9_][a-z0-9-]*$/;
14421
14815
  async function resolveScope(options) {
14422
14816
  const flagCount = [Boolean(options.project), Boolean(options.workspace), Boolean(options.global)].filter(Boolean).length;
14423
14817
  if (flagCount > 1) {
@@ -14435,7 +14829,7 @@ async function resolveScope(options) {
14435
14829
  return { kind: "project", id: options.project, todosPath: projectTodosDir(config.defaultProjectDir, options.project) };
14436
14830
  }
14437
14831
  if (options.workspace) {
14438
- if (!WORKSPACE_REGEX2.test(options.workspace)) {
14832
+ if (!WORKSPACE_REGEX3.test(options.workspace)) {
14439
14833
  throw new Error(`Invalid workspace name: "${options.workspace}". Use lowercase letters, numbers, hyphens, and underscores.`);
14440
14834
  }
14441
14835
  return { kind: "workspace", id: options.workspace, todosPath: todosDir() };
@@ -14886,7 +15280,7 @@ async function promoteTodos(ids, options) {
14886
15280
  return;
14887
15281
  }
14888
15282
  const target = options.toAssignment;
14889
- const { resolve: resolvePath } = await import("path");
15283
+ const { resolve: resolvePath2 } = await import("path");
14890
15284
  const { readConfig: readConfig3 } = await Promise.resolve().then(() => (init_config2(), config_exports));
14891
15285
  const { assignmentsDir: assignmentsDirFn } = await Promise.resolve().then(() => (init_paths(), paths_exports));
14892
15286
  const config = await readConfig3();
@@ -14897,16 +15291,16 @@ async function promoteTodos(ids, options) {
14897
15291
  if (parts.length !== 2 || !isValidSlug(parts[0]) || !isValidSlug(parts[1])) {
14898
15292
  throw new Error(`Invalid --to-assignment target "${target}". Use <project>/<slug> or a bare UUID.`);
14899
15293
  }
14900
- assignmentDir = resolvePath(config.defaultProjectDir, parts[0], "assignments", parts[1]);
15294
+ assignmentDir = resolvePath2(config.defaultProjectDir, parts[0], "assignments", parts[1]);
14901
15295
  displayRef = `${parts[0]}/${parts[1]}`;
14902
15296
  } else if (UUID_REGEX.test(target)) {
14903
- assignmentDir = resolvePath(assignmentsDirFn(), target);
15297
+ assignmentDir = resolvePath2(assignmentsDirFn(), target);
14904
15298
  displayRef = target;
14905
15299
  } else {
14906
15300
  throw new Error(`Invalid --to-assignment target "${target}". Use <project>/<slug> or a bare UUID.`);
14907
15301
  }
14908
15302
  const { fileExists: fileExists2 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
14909
- const assignmentMdPath = resolvePath(assignmentDir, "assignment.md");
15303
+ const assignmentMdPath = resolvePath2(assignmentDir, "assignment.md");
14910
15304
  if (!await fileExists2(assignmentMdPath)) {
14911
15305
  throw new Error(`Target assignment not found: ${assignmentMdPath}`);
14912
15306
  }
@@ -14922,12 +15316,12 @@ function describeScope(scope) {
14922
15316
  return `workspace:${scope.id}`;
14923
15317
  }
14924
15318
  async function injectPromotedTodos(assignmentDir, todos, scopeLabel) {
14925
- const { resolve: resolvePath } = await import("path");
15319
+ const { resolve: resolvePath2 } = await import("path");
14926
15320
  const { readFile: readFile32 } = await import("fs/promises");
14927
15321
  const { writeFileForce: writeFileForce2 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
14928
15322
  const { appendTodosToAssignmentBody: appendTodosToAssignmentBody2, touchAssignmentUpdated: touchAssignmentUpdated2 } = await Promise.resolve().then(() => (init_assignment_todos(), assignment_todos_exports));
14929
15323
  const { nowTimestamp: nowTimestamp3 } = await Promise.resolve().then(() => (init_timestamp(), timestamp_exports));
14930
- const assignmentMdPath = resolvePath(assignmentDir, "assignment.md");
15324
+ const assignmentMdPath = resolvePath2(assignmentDir, "assignment.md");
14931
15325
  let content = await readFile32(assignmentMdPath, "utf-8");
14932
15326
  content = appendTodosToAssignmentBody2(
14933
15327
  content,
@@ -15014,6 +15408,90 @@ ${item.description}
15014
15408
  process.exit(1);
15015
15409
  }
15016
15410
  });
15411
+ todoCommand.command("move").description("Move a todo between scopes (workspace \u2194 project \u2194 global) without converting it").argument("<id>", "Todo short ID").option("--to-workspace <slug>", "Target workspace slug").option("--to-project <slug>", "Target project slug").option("--to-global", "Move to global todos").option("--workspace <slug>", "Source workspace slug").option("--project <slug>", "Source project slug (mutually exclusive with --workspace/--global)").option("--global", "Source: global todos").action(async (id, options) => {
15412
+ try {
15413
+ await moveTodo(id, options);
15414
+ } catch (error) {
15415
+ console.error("Error:", error instanceof Error ? error.message : String(error));
15416
+ process.exit(1);
15417
+ }
15418
+ });
15419
+ async function moveTodo(id, options) {
15420
+ const targetCount = [
15421
+ Boolean(options.toWorkspace),
15422
+ Boolean(options.toProject),
15423
+ Boolean(options.toGlobal)
15424
+ ].filter(Boolean).length;
15425
+ if (targetCount !== 1) {
15426
+ throw new Error("Specify exactly one of --to-workspace <slug>, --to-project <slug>, --to-global.");
15427
+ }
15428
+ const sourceScope = await resolveScope({
15429
+ project: options.project,
15430
+ workspace: options.workspace,
15431
+ global: options.global
15432
+ });
15433
+ const targetScope = await resolveScope({
15434
+ project: options.toProject,
15435
+ workspace: options.toWorkspace,
15436
+ global: options.toGlobal
15437
+ });
15438
+ if (sourceScope.kind === targetScope.kind && sourceScope.id === targetScope.id) {
15439
+ throw new Error("Source and target scopes are the same; nothing to move.");
15440
+ }
15441
+ const sourceChecklist = await readChecklist(sourceScope.todosPath, sourceScope.id);
15442
+ const targetChecklist = sourceScope.todosPath === targetScope.todosPath && sourceScope.id === targetScope.id ? sourceChecklist : await readChecklist(targetScope.todosPath, targetScope.id);
15443
+ const idx = sourceChecklist.items.findIndex((i) => i.id === id);
15444
+ if (idx === -1) {
15445
+ throw new Error(`Todo [t:${id}] not found in scope ${describeScope(sourceScope)}.`);
15446
+ }
15447
+ const item = sourceChecklist.items[idx];
15448
+ if (targetChecklist.items.some((i) => i.id === id)) {
15449
+ throw new Error(`Todo id [t:${id}] already exists in target scope ${describeScope(targetScope)}; refusing to move (collision).`);
15450
+ }
15451
+ if (item.planDir) {
15452
+ const newPlanDir = todoPlanDir(targetScope.todosPath, targetScope.id, id);
15453
+ if (await fileExists(newPlanDir)) {
15454
+ throw new Error(`Plan directory already exists at target: ${newPlanDir}; refusing to move.`);
15455
+ }
15456
+ const { rename: rename6, mkdir: mkdir7 } = await import("fs/promises");
15457
+ const { dirname: dirname16 } = await import("path");
15458
+ await mkdir7(dirname16(newPlanDir), { recursive: true });
15459
+ await rename6(item.planDir, newPlanDir);
15460
+ item.planDir = newPlanDir;
15461
+ }
15462
+ sourceChecklist.items.splice(idx, 1);
15463
+ targetChecklist.items.push(item);
15464
+ await writeChecklist(sourceScope.todosPath, sourceChecklist);
15465
+ if (targetChecklist !== sourceChecklist) {
15466
+ await writeChecklist(targetScope.todosPath, targetChecklist);
15467
+ }
15468
+ const sourceLabel = describeScope(sourceScope);
15469
+ const targetLabel = describeScope(targetScope);
15470
+ const ts = nowISO();
15471
+ const sourceEntry = {
15472
+ timestamp: ts,
15473
+ itemIds: [id],
15474
+ items: item.description,
15475
+ session: null,
15476
+ branch: item.branch || null,
15477
+ summary: `Moved to ${targetLabel}`,
15478
+ blockers: null,
15479
+ status: null
15480
+ };
15481
+ const targetEntry = {
15482
+ timestamp: ts,
15483
+ itemIds: [id],
15484
+ items: item.description,
15485
+ session: null,
15486
+ branch: item.branch || null,
15487
+ summary: `Moved from ${sourceLabel}`,
15488
+ blockers: null,
15489
+ status: null
15490
+ };
15491
+ await appendLogEntry2(sourceScope.todosPath, sourceScope.id, sourceEntry);
15492
+ await appendLogEntry2(targetScope.todosPath, targetScope.id, targetEntry);
15493
+ console.log(`Moved [t:${id}] from ${sourceLabel} to ${targetLabel}`);
15494
+ }
15017
15495
 
15018
15496
  // src/commands/backup.ts
15019
15497
  init_config2();
@@ -15098,7 +15576,7 @@ import { Command as Command3 } from "commander";
15098
15576
  // src/utils/doctor/index.ts
15099
15577
  import { fileURLToPath as fileURLToPath7 } from "url";
15100
15578
  import { readFile as readFile27 } from "fs/promises";
15101
- import { dirname as dirname11, join as join5 } from "path";
15579
+ import { dirname as dirname13, join as join5 } from "path";
15102
15580
 
15103
15581
  // src/utils/doctor/context.ts
15104
15582
  init_config2();
@@ -15146,7 +15624,7 @@ init_paths();
15146
15624
  import { resolve as resolve38, isAbsolute as isAbsolute5 } from "path";
15147
15625
  import { readFile as readFile24, stat as stat4 } from "fs/promises";
15148
15626
  import { fileURLToPath as fileURLToPath6 } from "url";
15149
- import { dirname as dirname10, join as join4 } from "path";
15627
+ import { dirname as dirname12, join as join4 } from "path";
15150
15628
  var CATEGORY = "env";
15151
15629
  var syntaurRootExists = {
15152
15630
  id: "env.syntaur-root-exists",
@@ -15477,14 +15955,14 @@ async function readLocalVersion() {
15477
15955
  async function readLocalPkg() {
15478
15956
  try {
15479
15957
  const here = fileURLToPath6(import.meta.url);
15480
- let dir = dirname10(here);
15958
+ let dir = dirname12(here);
15481
15959
  for (let i = 0; i < 6; i++) {
15482
15960
  const candidate = join4(dir, "package.json");
15483
15961
  try {
15484
15962
  const text = await readFile24(candidate, "utf-8");
15485
15963
  return JSON.parse(text);
15486
15964
  } catch {
15487
- dir = dirname10(dir);
15965
+ dir = dirname12(dir);
15488
15966
  }
15489
15967
  }
15490
15968
  return null;
@@ -16885,14 +17363,14 @@ async function finalize(checks) {
16885
17363
  async function readVersion() {
16886
17364
  try {
16887
17365
  const here = fileURLToPath7(import.meta.url);
16888
- let dir = dirname11(here);
17366
+ let dir = dirname13(here);
16889
17367
  for (let i = 0; i < 6; i++) {
16890
17368
  try {
16891
17369
  const raw = await readFile27(join5(dir, "package.json"), "utf-8");
16892
17370
  const parsed = JSON.parse(raw);
16893
17371
  return typeof parsed.version === "string" ? parsed.version : null;
16894
17372
  } catch {
16895
- dir = dirname11(dir);
17373
+ dir = dirname13(dir);
16896
17374
  }
16897
17375
  }
16898
17376
  return null;
@@ -17408,18 +17886,18 @@ init_paths();
17408
17886
  init_fs();
17409
17887
  import { fileURLToPath as fileURLToPath9 } from "url";
17410
17888
  import { readFile as readFile31 } from "fs/promises";
17411
- import { dirname as dirname13, join as join7, resolve as resolve46 } from "path";
17889
+ import { dirname as dirname15, join as join7, resolve as resolve46 } from "path";
17412
17890
  import { spawn as spawn4 } from "child_process";
17413
17891
  import { createInterface as createInterface2 } from "readline/promises";
17414
17892
 
17415
17893
  // src/utils/version.ts
17416
17894
  import { fileURLToPath as fileURLToPath8 } from "url";
17417
17895
  import { readFile as readFile30 } from "fs/promises";
17418
- import { dirname as dirname12, join as join6 } from "path";
17896
+ import { dirname as dirname14, join as join6 } from "path";
17419
17897
  async function readPackageVersion(scriptUrl) {
17420
17898
  try {
17421
17899
  const scriptPath = fileURLToPath8(scriptUrl);
17422
- const pkgRoot = dirname12(dirname12(scriptPath));
17900
+ const pkgRoot = dirname14(dirname14(scriptPath));
17423
17901
  const raw = await readFile30(join6(pkgRoot, "package.json"), "utf-8");
17424
17902
  const parsed = JSON.parse(raw);
17425
17903
  return typeof parsed.version === "string" ? parsed.version : null;
@@ -17461,7 +17939,7 @@ async function writeState(state) {
17461
17939
  `);
17462
17940
  }
17463
17941
  async function resolveNpmBin() {
17464
- const nodeDir = dirname13(process.execPath);
17942
+ const nodeDir = dirname15(process.execPath);
17465
17943
  const isWin = process.platform === "win32";
17466
17944
  const npmName = isWin ? "npm.cmd" : "npm";
17467
17945
  const nearNode = join7(nodeDir, npmName);
@@ -17974,6 +18452,17 @@ program.command("disable-playbook").description("Disable a playbook so agents no
17974
18452
  process.exit(1);
17975
18453
  }
17976
18454
  });
18455
+ program.command("regen-playbook-manifest").description("Rebuild ~/.syntaur/playbooks/manifest.md from current playbook files").action(async () => {
18456
+ try {
18457
+ await regenPlaybookManifestCommand();
18458
+ } catch (error) {
18459
+ console.error(
18460
+ "Error:",
18461
+ error instanceof Error ? error.message : String(error)
18462
+ );
18463
+ process.exit(1);
18464
+ }
18465
+ });
17977
18466
  program.addCommand(todoCommand);
17978
18467
  program.addCommand(backupCommand);
17979
18468
  program.addCommand(doctorCommand);