syntaur 0.4.5 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/dashboard/dist/assets/{_basePickBy-ZY1FrcKw.js → _basePickBy-Bcut0btZ.js} +1 -1
  2. package/dashboard/dist/assets/{_baseUniq-C7xZB6ea.js → _baseUniq-AQSP2JEk.js} +1 -1
  3. package/dashboard/dist/assets/{arc-CPtDVk1A.js → arc-BLTpY9lc.js} +1 -1
  4. package/dashboard/dist/assets/{architectureDiagram-2XIMDMQ5-Dr5rnxwf.js → architectureDiagram-2XIMDMQ5-CJtwMY_X.js} +1 -1
  5. package/dashboard/dist/assets/{blockDiagram-WCTKOSBZ-SsDboTb2.js → blockDiagram-WCTKOSBZ-Don-O7X7.js} +1 -1
  6. package/dashboard/dist/assets/{c4Diagram-IC4MRINW-CZqjXmV0.js → c4Diagram-IC4MRINW-C_M3yTTB.js} +1 -1
  7. package/dashboard/dist/assets/channel-BfXmPwE5.js +1 -0
  8. package/dashboard/dist/assets/{chunk-4BX2VUAB-BYskd63Z.js → chunk-4BX2VUAB-CGss0jXe.js} +1 -1
  9. package/dashboard/dist/assets/{chunk-55IACEB6-CWLImr1E.js → chunk-55IACEB6-BatoPJga.js} +1 -1
  10. package/dashboard/dist/assets/{chunk-FMBD7UC4-fQXSIXhy.js → chunk-FMBD7UC4-DxH4wO82.js} +1 -1
  11. package/dashboard/dist/assets/{chunk-JSJVCQXG-DJXtEexG.js → chunk-JSJVCQXG-BL3izAFQ.js} +1 -1
  12. package/dashboard/dist/assets/{chunk-KX2RTZJC-C0ivaZ1M.js → chunk-KX2RTZJC-GnqXwnge.js} +1 -1
  13. package/dashboard/dist/assets/{chunk-NQ4KR5QH-DlwLjqC5.js → chunk-NQ4KR5QH-gvCn4QMb.js} +1 -1
  14. package/dashboard/dist/assets/{chunk-QZHKN3VN-DBEYrRqx.js → chunk-QZHKN3VN-CYGWogyi.js} +1 -1
  15. package/dashboard/dist/assets/{chunk-WL4C6EOR-BQyYCp1z.js → chunk-WL4C6EOR-D9mVTQ1F.js} +1 -1
  16. package/dashboard/dist/assets/classDiagram-VBA2DB6C-D7_G1qy0.js +1 -0
  17. package/dashboard/dist/assets/classDiagram-v2-RAHNMMFH-D7_G1qy0.js +1 -0
  18. package/dashboard/dist/assets/clone-BKG-N796.js +1 -0
  19. package/dashboard/dist/assets/{cose-bilkent-S5V4N54A-CAYzelqd.js → cose-bilkent-S5V4N54A-CUWQCKt4.js} +1 -1
  20. package/dashboard/dist/assets/{dagre-KLK3FWXG-xpTJb8E7.js → dagre-KLK3FWXG-CH3ijEvV.js} +1 -1
  21. package/dashboard/dist/assets/{diagram-E7M64L7V-DRDjskHd.js → diagram-E7M64L7V-sq83lpV1.js} +1 -1
  22. package/dashboard/dist/assets/{diagram-IFDJBPK2-B1r1ZXm3.js → diagram-IFDJBPK2-BzQG_rtq.js} +1 -1
  23. package/dashboard/dist/assets/{diagram-P4PSJMXO-BeE6-ZUH.js → diagram-P4PSJMXO-Dg0eZn0q.js} +1 -1
  24. package/dashboard/dist/assets/{erDiagram-INFDFZHY-BG6KKBm1.js → erDiagram-INFDFZHY-4b9eQ0uj.js} +1 -1
  25. package/dashboard/dist/assets/{flowDiagram-PKNHOUZH-CSFv3RpZ.js → flowDiagram-PKNHOUZH-C9fzKcsZ.js} +1 -1
  26. package/dashboard/dist/assets/{ganttDiagram-A5KZAMGK-Off4zK-k.js → ganttDiagram-A5KZAMGK-Bzt6i9SH.js} +1 -1
  27. package/dashboard/dist/assets/{gitGraphDiagram-K3NZZRJ6-Msv_4mYB.js → gitGraphDiagram-K3NZZRJ6-D0wFOagh.js} +1 -1
  28. package/dashboard/dist/assets/{graph-rPPnNEMq.js → graph-EEIGvqDh.js} +1 -1
  29. package/dashboard/dist/assets/index-C7f0ySJE.js +481 -0
  30. package/dashboard/dist/assets/{infoDiagram-LFFYTUFH-CeBirxFd.js → infoDiagram-LFFYTUFH-DLYMsj1D.js} +1 -1
  31. package/dashboard/dist/assets/{ishikawaDiagram-PHBUUO56-BZ_w8PcD.js → ishikawaDiagram-PHBUUO56-DVebKkzl.js} +1 -1
  32. package/dashboard/dist/assets/{journeyDiagram-4ABVD52K-3FqMPEfs.js → journeyDiagram-4ABVD52K-BsmgOWVw.js} +1 -1
  33. package/dashboard/dist/assets/{kanban-definition-K7BYSVSG-BlCmFkEx.js → kanban-definition-K7BYSVSG-BTnHf0ey.js} +1 -1
  34. package/dashboard/dist/assets/{layout-C50nYMhx.js → layout-BbM7HRvv.js} +1 -1
  35. package/dashboard/dist/assets/{linear-D2Cjmh1X.js → linear-C37bJKPO.js} +1 -1
  36. package/dashboard/dist/assets/{mermaid.core-BXFuNYa4.js → mermaid.core-MZ_JgnRL.js} +4 -4
  37. package/dashboard/dist/assets/{mindmap-definition-YRQLILUH-CowIfM07.js → mindmap-definition-YRQLILUH-CgHS4hFo.js} +1 -1
  38. package/dashboard/dist/assets/{pieDiagram-SKSYHLDU-DG5IQcKl.js → pieDiagram-SKSYHLDU-CmAgopJe.js} +1 -1
  39. package/dashboard/dist/assets/{quadrantDiagram-337W2JSQ-DKD46CSX.js → quadrantDiagram-337W2JSQ-BvzYUPR6.js} +1 -1
  40. package/dashboard/dist/assets/{requirementDiagram-Z7DCOOCP-Dd-qX6Ul.js → requirementDiagram-Z7DCOOCP-Bs52VP7k.js} +1 -1
  41. package/dashboard/dist/assets/{sankeyDiagram-WA2Y5GQK-CDAylULt.js → sankeyDiagram-WA2Y5GQK-aXvGPR1o.js} +1 -1
  42. package/dashboard/dist/assets/{sequenceDiagram-2WXFIKYE-D-5Jq5jy.js → sequenceDiagram-2WXFIKYE-CzgcfU6K.js} +1 -1
  43. package/dashboard/dist/assets/{stateDiagram-RAJIS63D-BmBOa8i0.js → stateDiagram-RAJIS63D-BXBJf9Hq.js} +1 -1
  44. package/dashboard/dist/assets/stateDiagram-v2-FVOUBMTO-QqOtsuOs.js +1 -0
  45. package/dashboard/dist/assets/{timeline-definition-YZTLITO2-C9Sdg7du.js → timeline-definition-YZTLITO2-BsXp26Ai.js} +1 -1
  46. package/dashboard/dist/assets/{treemap-KZPCXAKY-D-PDxUdK.js → treemap-KZPCXAKY-C3WbDii1.js} +1 -1
  47. package/dashboard/dist/assets/{vennDiagram-LZ73GAT5-CBO5f6MT.js → vennDiagram-LZ73GAT5-B28LMHWd.js} +1 -1
  48. package/dashboard/dist/assets/{xychartDiagram-JWTSCODW-4xZrquqP.js → xychartDiagram-JWTSCODW-C3Xwz8mS.js} +1 -1
  49. package/dashboard/dist/index.html +1 -1
  50. package/dist/dashboard/server.js +726 -115
  51. package/dist/dashboard/server.js.map +1 -1
  52. package/dist/index.js +1019 -381
  53. package/dist/index.js.map +1 -1
  54. package/package.json +1 -1
  55. package/dashboard/dist/assets/channel-ejDeCb7i.js +0 -1
  56. package/dashboard/dist/assets/classDiagram-VBA2DB6C-BW_esmsF.js +0 -1
  57. package/dashboard/dist/assets/classDiagram-v2-RAHNMMFH-BW_esmsF.js +0 -1
  58. package/dashboard/dist/assets/clone-y13L00zF.js +0 -1
  59. package/dashboard/dist/assets/index-D_uE8gHg.js +0 -481
  60. package/dashboard/dist/assets/stateDiagram-v2-FVOUBMTO-Dsk6o9iT.js +0 -1
package/dist/index.js CHANGED
@@ -39,6 +39,9 @@ function playbooksDir() {
39
39
  function todosDir() {
40
40
  return resolve(syntaurRoot(), "todos");
41
41
  }
42
+ function projectTodosDir(projectsDir2, projectSlug) {
43
+ return resolve(projectsDir2, projectSlug, "todos");
44
+ }
42
45
  var init_paths = __esm({
43
46
  "src/utils/paths.ts"() {
44
47
  "use strict";
@@ -91,10 +94,10 @@ var init_fs = __esm({
91
94
  });
92
95
 
93
96
  // src/templates/config.ts
94
- function renderConfig(params) {
97
+ function renderConfig(params2) {
95
98
  return `---
96
99
  version: "1.0"
97
- defaultProjectDir: ${params.defaultProjectDir}
100
+ defaultProjectDir: ${params2.defaultProjectDir}
98
101
  onboarding:
99
102
  completed: false
100
103
  agentDefaults:
@@ -4786,8 +4789,8 @@ __export(launch_exports, {
4786
4789
  launchAgent: () => launchAgent
4787
4790
  });
4788
4791
  import { spawn as spawn2 } from "child_process";
4789
- import { mkdir as mkdir4, writeFile as writeFile9 } from "fs/promises";
4790
- import { resolve as resolve31 } from "path";
4792
+ import { mkdir as mkdir5, writeFile as writeFile9 } from "fs/promises";
4793
+ import { resolve as resolve32 } from "path";
4791
4794
  async function launchAgent(options) {
4792
4795
  const { projectsDir: projectsDir2, projectSlug, assignmentSlug, agent } = options;
4793
4796
  const command = AGENT_COMMANDS[agent];
@@ -4797,10 +4800,10 @@ async function launchAgent(options) {
4797
4800
  process.exit(1);
4798
4801
  }
4799
4802
  const workspaceDir = detail.workspace.worktreePath ?? (detail.workspace.repository?.startsWith("/") ? detail.workspace.repository : null) ?? process.cwd();
4800
- const projectDir = resolve31(projectsDir2, projectSlug);
4801
- const assignmentDir = resolve31(projectDir, "assignments", assignmentSlug);
4802
- const contextDir = resolve31(workspaceDir, ".syntaur");
4803
- await mkdir4(contextDir, { recursive: true });
4803
+ const projectDir = resolve32(projectsDir2, projectSlug);
4804
+ const assignmentDir = resolve32(projectDir, "assignments", assignmentSlug);
4805
+ const contextDir = resolve32(workspaceDir, ".syntaur");
4806
+ await mkdir5(contextDir, { recursive: true });
4804
4807
  const context = {
4805
4808
  projectSlug,
4806
4809
  assignmentSlug,
@@ -4812,7 +4815,7 @@ async function launchAgent(options) {
4812
4815
  grabbedAt: (/* @__PURE__ */ new Date()).toISOString()
4813
4816
  };
4814
4817
  await writeFile9(
4815
- resolve31(contextDir, "context.json"),
4818
+ resolve32(contextDir, "context.json"),
4816
4819
  JSON.stringify(context, null, 2) + "\n"
4817
4820
  );
4818
4821
  return new Promise((resolvePromise, reject) => {
@@ -4943,14 +4946,14 @@ init_config2();
4943
4946
  init_config();
4944
4947
 
4945
4948
  // src/templates/manifest.ts
4946
- function renderManifest(params) {
4949
+ function renderManifest(params2) {
4947
4950
  return `---
4948
4951
  version: "2.0"
4949
- project: ${params.slug}
4950
- generated: "${params.timestamp}"
4952
+ project: ${params2.slug}
4953
+ generated: "${params2.timestamp}"
4951
4954
  ---
4952
4955
 
4953
- # Project: ${params.slug}
4956
+ # Project: ${params2.slug}
4954
4957
 
4955
4958
  ## Overview
4956
4959
  - [Project Overview](./project.md)
@@ -4977,24 +4980,24 @@ function escapeYamlString(value) {
4977
4980
  }
4978
4981
 
4979
4982
  // src/templates/project.ts
4980
- function renderProject(params) {
4981
- const safeTitle = escapeYamlString(params.title);
4982
- const workspaceLine = params.workspace ? `
4983
- workspace: ${params.workspace}` : "";
4983
+ function renderProject(params2) {
4984
+ const safeTitle = escapeYamlString(params2.title);
4985
+ const workspaceLine = params2.workspace ? `
4986
+ workspace: ${params2.workspace}` : "";
4984
4987
  return `---
4985
- id: ${params.id}
4986
- slug: ${params.slug}
4988
+ id: ${params2.id}
4989
+ slug: ${params2.slug}
4987
4990
  title: ${safeTitle}
4988
4991
  archived: false
4989
4992
  archivedAt: null
4990
4993
  archivedReason: null
4991
- created: "${params.timestamp}"
4992
- updated: "${params.timestamp}"
4994
+ created: "${params2.timestamp}"
4995
+ updated: "${params2.timestamp}"
4993
4996
  externalIds: []
4994
4997
  tags: []${workspaceLine}
4995
4998
  ---
4996
4999
 
4997
- # ${params.title}
5000
+ # ${params2.title}
4998
5001
 
4999
5002
  ## Overview
5000
5003
 
@@ -5007,17 +5010,17 @@ tags: []${workspaceLine}
5007
5010
  }
5008
5011
 
5009
5012
  // src/templates/assignment.ts
5010
- function renderAssignment(params) {
5011
- const safeTitle = escapeYamlString(params.title);
5012
- const dependsOnYaml = params.dependsOn.length === 0 ? "dependsOn: []" : `dependsOn:
5013
- - ${params.dependsOn.join("\n - ")}`;
5014
- const linksYaml = params.links.length === 0 ? "links: []" : `links:
5015
- - ${params.links.join("\n - ")}`;
5016
- const projectYaml = `project: ${params.project == null ? "null" : params.project}`;
5017
- const workspaceGroupLine = params.workspaceGroup ? `
5018
- workspaceGroup: ${params.workspaceGroup}` : "";
5019
- const typeYaml = `type: ${params.type ?? "feature"}`;
5020
- const todosSection = params.includeTodos ? `## Todos
5013
+ function renderAssignment(params2) {
5014
+ const safeTitle = escapeYamlString(params2.title);
5015
+ const dependsOnYaml = params2.dependsOn.length === 0 ? "dependsOn: []" : `dependsOn:
5016
+ - ${params2.dependsOn.join("\n - ")}`;
5017
+ const linksYaml = params2.links.length === 0 ? "links: []" : `links:
5018
+ - ${params2.links.join("\n - ")}`;
5019
+ const projectYaml = `project: ${params2.project == null ? "null" : params2.project}`;
5020
+ const workspaceGroupLine = params2.workspaceGroup ? `
5021
+ workspaceGroup: ${params2.workspaceGroup}` : "";
5022
+ const typeYaml = `type: ${params2.type ?? "feature"}`;
5023
+ const todosSection = params2.includeTodos ? `## Todos
5021
5024
 
5022
5025
  <!--
5023
5026
  Checklist of work items for this assignment. Items may be simple tasks
@@ -5029,15 +5032,15 @@ Never delete superseded todos \u2014 preserve the history.
5029
5032
 
5030
5033
  ` : "";
5031
5034
  return `---
5032
- id: ${params.id}
5033
- slug: ${params.slug}
5035
+ id: ${params2.id}
5036
+ slug: ${params2.slug}
5034
5037
  title: ${safeTitle}
5035
5038
  ${projectYaml}${workspaceGroupLine}
5036
5039
  ${typeYaml}
5037
5040
  status: pending
5038
- priority: ${params.priority}
5039
- created: "${params.timestamp}"
5040
- updated: "${params.timestamp}"
5041
+ priority: ${params2.priority}
5042
+ created: "${params2.timestamp}"
5043
+ updated: "${params2.timestamp}"
5041
5044
  assignee: null
5042
5045
  externalIds: []
5043
5046
  ${dependsOnYaml}
@@ -5051,7 +5054,7 @@ workspace:
5051
5054
  tags: []
5052
5055
  ---
5053
5056
 
5054
- # ${params.title}
5057
+ # ${params2.title}
5055
5058
 
5056
5059
  ## Objective
5057
5060
 
@@ -5078,10 +5081,10 @@ ${todosSection}## Context
5078
5081
  }
5079
5082
 
5080
5083
  // src/templates/scratchpad.ts
5081
- function renderScratchpad(params) {
5084
+ function renderScratchpad(params2) {
5082
5085
  return `---
5083
- assignment: ${params.assignmentSlug}
5084
- updated: "${params.timestamp}"
5086
+ assignment: ${params2.assignmentSlug}
5087
+ updated: "${params2.timestamp}"
5085
5088
  ---
5086
5089
 
5087
5090
  # Scratchpad
@@ -5091,10 +5094,10 @@ No working notes yet.
5091
5094
  }
5092
5095
 
5093
5096
  // src/templates/handoff.ts
5094
- function renderHandoff(params) {
5097
+ function renderHandoff(params2) {
5095
5098
  return `---
5096
- assignment: ${params.assignmentSlug}
5097
- updated: "${params.timestamp}"
5099
+ assignment: ${params2.assignmentSlug}
5100
+ updated: "${params2.timestamp}"
5098
5101
  handoffCount: 0
5099
5102
  ---
5100
5103
 
@@ -5105,12 +5108,12 @@ No handoffs recorded yet.
5105
5108
  }
5106
5109
 
5107
5110
  // src/templates/progress.ts
5108
- function renderProgress(params) {
5111
+ function renderProgress(params2) {
5109
5112
  return `---
5110
- assignment: ${params.assignment}
5113
+ assignment: ${params2.assignment}
5111
5114
  entryCount: 0
5112
- generated: "${params.timestamp}"
5113
- updated: "${params.timestamp}"
5115
+ generated: "${params2.timestamp}"
5116
+ updated: "${params2.timestamp}"
5114
5117
  ---
5115
5118
 
5116
5119
  # Progress
@@ -5120,12 +5123,12 @@ No progress yet.
5120
5123
  }
5121
5124
 
5122
5125
  // src/templates/comments.ts
5123
- function renderComments(params) {
5126
+ function renderComments(params2) {
5124
5127
  return `---
5125
- assignment: ${params.assignment}
5128
+ assignment: ${params2.assignment}
5126
5129
  entryCount: 0
5127
- generated: "${params.timestamp}"
5128
- updated: "${params.timestamp}"
5130
+ generated: "${params2.timestamp}"
5131
+ updated: "${params2.timestamp}"
5129
5132
  ---
5130
5133
 
5131
5134
  # Comments
@@ -5153,10 +5156,10 @@ function formatCommentEntry(comment) {
5153
5156
  }
5154
5157
 
5155
5158
  // src/templates/decision-record.ts
5156
- function renderDecisionRecord(params) {
5159
+ function renderDecisionRecord(params2) {
5157
5160
  return `---
5158
- assignment: ${params.assignmentSlug}
5159
- updated: "${params.timestamp}"
5161
+ assignment: ${params2.assignmentSlug}
5162
+ updated: "${params2.timestamp}"
5160
5163
  decisionCount: 0
5161
5164
  ---
5162
5165
 
@@ -5167,10 +5170,10 @@ No decisions recorded yet.
5167
5170
  }
5168
5171
 
5169
5172
  // src/templates/index-stubs.ts
5170
- function renderIndexAssignments(params) {
5173
+ function renderIndexAssignments(params2) {
5171
5174
  return `---
5172
- project: ${params.slug}
5173
- generated: "${params.timestamp}"
5175
+ project: ${params2.slug}
5176
+ generated: "${params2.timestamp}"
5174
5177
  total: 0
5175
5178
  by_status:
5176
5179
  pending: 0
@@ -5187,10 +5190,10 @@ by_status:
5187
5190
  |------|-------|--------|----------|----------|--------------|---------|
5188
5191
  `;
5189
5192
  }
5190
- function renderIndexPlans(params) {
5193
+ function renderIndexPlans(params2) {
5191
5194
  return `---
5192
- project: ${params.slug}
5193
- generated: "${params.timestamp}"
5195
+ project: ${params2.slug}
5196
+ generated: "${params2.timestamp}"
5194
5197
  ---
5195
5198
 
5196
5199
  # Plans
@@ -5199,10 +5202,10 @@ generated: "${params.timestamp}"
5199
5202
  |------------|-------------|---------|
5200
5203
  `;
5201
5204
  }
5202
- function renderIndexDecisions(params) {
5205
+ function renderIndexDecisions(params2) {
5203
5206
  return `---
5204
- project: ${params.slug}
5205
- generated: "${params.timestamp}"
5207
+ project: ${params2.slug}
5208
+ generated: "${params2.timestamp}"
5206
5209
  ---
5207
5210
 
5208
5211
  # Decision Records
@@ -5211,10 +5214,10 @@ generated: "${params.timestamp}"
5211
5214
  |------------|-------|-----------------|---------------|---------|
5212
5215
  `;
5213
5216
  }
5214
- function renderStatus(params) {
5217
+ function renderStatus(params2) {
5215
5218
  return `---
5216
- project: ${params.slug}
5217
- generated: "${params.timestamp}"
5219
+ project: ${params2.slug}
5220
+ generated: "${params2.timestamp}"
5218
5221
  status: pending
5219
5222
  progress:
5220
5223
  total: 0
@@ -5230,7 +5233,7 @@ needsAttention:
5230
5233
  openQuestions: 0
5231
5234
  ---
5232
5235
 
5233
- # Project Status: ${params.title}
5236
+ # Project Status: ${params2.title}
5234
5237
 
5235
5238
  **Status:** pending
5236
5239
  **Progress:** 0/0 assignments complete
@@ -5250,10 +5253,10 @@ No dependencies yet.
5250
5253
  - **0 unanswered** questions
5251
5254
  `;
5252
5255
  }
5253
- function renderResourcesIndex(params) {
5256
+ function renderResourcesIndex(params2) {
5254
5257
  return `---
5255
- project: ${params.slug}
5256
- generated: "${params.timestamp}"
5258
+ project: ${params2.slug}
5259
+ generated: "${params2.timestamp}"
5257
5260
  total: 0
5258
5261
  ---
5259
5262
 
@@ -5263,10 +5266,10 @@ total: 0
5263
5266
  |------|----------|--------|---------------------|---------|
5264
5267
  `;
5265
5268
  }
5266
- function renderMemoriesIndex(params) {
5269
+ function renderMemoriesIndex(params2) {
5267
5270
  return `---
5268
- project: ${params.slug}
5269
- generated: "${params.timestamp}"
5271
+ project: ${params2.slug}
5272
+ generated: "${params2.timestamp}"
5270
5273
  total: 0
5271
5274
  ---
5272
5275
 
@@ -5278,19 +5281,19 @@ total: 0
5278
5281
  }
5279
5282
 
5280
5283
  // src/templates/playbook.ts
5281
- function renderPlaybook(params) {
5282
- const whenToUse = params.whenToUse ? escapeYamlString(params.whenToUse) : "null";
5284
+ function renderPlaybook(params2) {
5285
+ const whenToUse = params2.whenToUse ? escapeYamlString(params2.whenToUse) : "null";
5283
5286
  return `---
5284
- name: ${escapeYamlString(params.name)}
5285
- slug: ${params.slug}
5286
- description: ${escapeYamlString(params.description)}
5287
+ name: ${escapeYamlString(params2.name)}
5288
+ slug: ${params2.slug}
5289
+ description: ${escapeYamlString(params2.description)}
5287
5290
  when_to_use: ${whenToUse}
5288
- created: "${params.timestamp}"
5289
- updated: "${params.timestamp}"
5291
+ created: "${params2.timestamp}"
5292
+ updated: "${params2.timestamp}"
5290
5293
  tags: []
5291
5294
  ---
5292
5295
 
5293
- # ${params.name}
5296
+ # ${params2.name}
5294
5297
 
5295
5298
  <!-- Write imperative rules and workflows here. Keep it under 50 lines. -->
5296
5299
  `;
@@ -5433,58 +5436,58 @@ Follow the rules in each playbook. They take precedence over default conventions
5433
5436
  - Commit frequently with messages referencing the assignment slug.
5434
5437
  `;
5435
5438
  }
5436
- function renderCursorAssignment(params) {
5439
+ function renderCursorAssignment(params2) {
5437
5440
  return `---
5438
- description: Syntaur assignment context for ${params.projectSlug}/${params.assignmentSlug}
5441
+ description: Syntaur assignment context for ${params2.projectSlug}/${params2.assignmentSlug}
5439
5442
  globs:
5440
5443
  alwaysApply: true
5441
5444
  ---
5442
5445
 
5443
5446
  # Current Assignment Context
5444
5447
 
5445
- - **Project:** ${params.projectSlug}
5446
- - **Assignment:** ${params.assignmentSlug}
5447
- - **Project directory:** ${params.projectDir}
5448
- - **Assignment directory:** ${params.assignmentDir}
5448
+ - **Project:** ${params2.projectSlug}
5449
+ - **Assignment:** ${params2.assignmentSlug}
5450
+ - **Project directory:** ${params2.projectDir}
5451
+ - **Assignment directory:** ${params2.assignmentDir}
5449
5452
 
5450
5453
  ## Reading Order
5451
5454
 
5452
5455
  Before starting work, read these files in order:
5453
- 1. \`${params.projectDir}/project.md\` -- project overview and goals (project-nested assignments only)
5454
- 2. \`${params.assignmentDir}/assignment.md\` -- your assignment details, acceptance criteria, todos, current status. Frontmatter includes \`project: <slug> | null\` (null for standalone) and \`type: <classification> | null\`.
5455
- 3. any \`${params.assignmentDir}/plan*.md\` files linked from active todos in the \`## Todos\` section (may be 0, 1, or many)
5456
- 4. \`${params.assignmentDir}/progress.md\` -- reverse-chron progress log (if present)
5457
- 5. \`${params.assignmentDir}/comments.md\` -- threaded questions/notes/feedback (if present)
5458
- 6. \`${params.assignmentDir}/handoff.md\` -- previous session handoff notes
5456
+ 1. \`${params2.projectDir}/project.md\` -- project overview and goals (project-nested assignments only)
5457
+ 2. \`${params2.assignmentDir}/assignment.md\` -- your assignment details, acceptance criteria, todos, current status. Frontmatter includes \`project: <slug> | null\` (null for standalone) and \`type: <classification> | null\`.
5458
+ 3. any \`${params2.assignmentDir}/plan*.md\` files linked from active todos in the \`## Todos\` section (may be 0, 1, or many)
5459
+ 4. \`${params2.assignmentDir}/progress.md\` -- reverse-chron progress log (if present)
5460
+ 5. \`${params2.assignmentDir}/comments.md\` -- threaded questions/notes/feedback (if present)
5461
+ 6. \`${params2.assignmentDir}/handoff.md\` -- previous session handoff notes
5459
5462
 
5460
5463
  ## Your Writable Files
5461
5464
 
5462
5465
  You may write directly to these files inside your assignment folder:
5463
- - \`${params.assignmentDir}/assignment.md\`
5464
- - \`${params.assignmentDir}/plan*.md\` (0 or more versioned plan files, e.g., \`plan.md\`, \`plan-v2.md\`)
5465
- - \`${params.assignmentDir}/progress.md\` (append timestamped entries, newest first)
5466
- - \`${params.assignmentDir}/scratchpad.md\`
5467
- - \`${params.assignmentDir}/handoff.md\`
5468
- - \`${params.assignmentDir}/decision-record.md\`
5466
+ - \`${params2.assignmentDir}/assignment.md\`
5467
+ - \`${params2.assignmentDir}/plan*.md\` (0 or more versioned plan files, e.g., \`plan.md\`, \`plan-v2.md\`)
5468
+ - \`${params2.assignmentDir}/progress.md\` (append timestamped entries, newest first)
5469
+ - \`${params2.assignmentDir}/scratchpad.md\`
5470
+ - \`${params2.assignmentDir}/handoff.md\`
5471
+ - \`${params2.assignmentDir}/decision-record.md\`
5469
5472
 
5470
- Do NOT edit \`${params.assignmentDir}/comments.md\` directly \u2014 use \`syntaur comment\`. Do NOT edit other assignments' files \u2014 use \`syntaur request\` for cross-assignment todos.
5473
+ Do NOT edit \`${params2.assignmentDir}/comments.md\` directly \u2014 use \`syntaur comment\`. Do NOT edit other assignments' files \u2014 use \`syntaur request\` for cross-assignment todos.
5471
5474
 
5472
5475
  And source code files in your workspace. Read the \`workspace\` field from your assignment's frontmatter to determine the exact boundary. If not set, the current working directory is your workspace.
5473
5476
  `;
5474
5477
  }
5475
5478
 
5476
5479
  // src/templates/codex-agents.ts
5477
- function renderCodexAgents(params) {
5480
+ function renderCodexAgents(params2) {
5478
5481
  return `# Syntaur Protocol -- Agent Instructions
5479
5482
 
5480
5483
  This project uses the Syntaur protocol for multi-agent project coordination.
5481
5484
 
5482
5485
  ## Current Assignment
5483
5486
 
5484
- - **Project:** ${params.projectSlug}
5485
- - **Assignment:** ${params.assignmentSlug}
5486
- - **Project directory:** ${params.projectDir}
5487
- - **Assignment directory:** ${params.assignmentDir}
5487
+ - **Project:** ${params2.projectSlug}
5488
+ - **Assignment:** ${params2.assignmentSlug}
5489
+ - **Project directory:** ${params2.projectDir}
5490
+ - **Assignment directory:** ${params2.assignmentDir}
5488
5491
 
5489
5492
  ## Preferred Workflow
5490
5493
 
@@ -5504,13 +5507,13 @@ If the plugin is unavailable, follow the same workflow manually with the \`synta
5504
5507
  ## Reading Order
5505
5508
 
5506
5509
  Before starting work, read these files in order:
5507
- 1. \`${params.projectDir}/manifest.md\` -- root navigation entry point (project-nested assignments only)
5508
- 2. \`${params.projectDir}/project.md\` -- project overview and goals (project-nested assignments only)
5509
- 3. \`${params.assignmentDir}/assignment.md\` -- your assignment details, acceptance criteria, todos, current status. Frontmatter now includes \`project: <slug> | null\` (null for standalone) and \`type: <classification> | null\`.
5510
- 4. any \`${params.assignmentDir}/plan*.md\` files linked from active todos in the \`## Todos\` section (may be 0, 1, or many)
5511
- 5. \`${params.assignmentDir}/progress.md\` -- reverse-chron progress log (if present)
5512
- 6. \`${params.assignmentDir}/comments.md\` -- threaded questions/notes/feedback (if present)
5513
- 7. \`${params.assignmentDir}/handoff.md\` -- previous session handoff notes
5510
+ 1. \`${params2.projectDir}/manifest.md\` -- root navigation entry point (project-nested assignments only)
5511
+ 2. \`${params2.projectDir}/project.md\` -- project overview and goals (project-nested assignments only)
5512
+ 3. \`${params2.assignmentDir}/assignment.md\` -- your assignment details, acceptance criteria, todos, current status. Frontmatter now includes \`project: <slug> | null\` (null for standalone) and \`type: <classification> | null\`.
5513
+ 4. any \`${params2.assignmentDir}/plan*.md\` files linked from active todos in the \`## Todos\` section (may be 0, 1, or many)
5514
+ 5. \`${params2.assignmentDir}/progress.md\` -- reverse-chron progress log (if present)
5515
+ 6. \`${params2.assignmentDir}/comments.md\` -- threaded questions/notes/feedback (if present)
5516
+ 7. \`${params2.assignmentDir}/handoff.md\` -- previous session handoff notes
5514
5517
 
5515
5518
  ## Context File
5516
5519
 
@@ -5562,10 +5565,10 @@ Before starting work, read these files in order:
5562
5565
  ### Files you may WRITE:
5563
5566
  1. **Your assignment folder** -- only the assignment you are currently working on:
5564
5567
  - \`assignment.md\`, \`plan*.md\` (0 or more versioned plan files), \`progress.md\`, \`scratchpad.md\`, \`handoff.md\`, \`decision-record.md\`
5565
- - Path: \`${params.assignmentDir}/\`
5568
+ - Path: \`${params2.assignmentDir}/\`
5566
5569
  2. **Shared resources and memories** at the project level:
5567
- - \`${params.projectDir}/resources/<slug>.md\`
5568
- - \`${params.projectDir}/memories/<slug>.md\`
5570
+ - \`${params2.projectDir}/resources/<slug>.md\`
5571
+ - \`${params2.projectDir}/memories/<slug>.md\`
5569
5572
  3. **Your workspace** -- source code files in the current working directory (the directory where this AGENTS.md lives). If your assignment's frontmatter specifies a \`workspace\` field, read it at runtime to determine the exact boundary.
5570
5573
 
5571
5574
  > **Note:** Workspace boundaries are resolved by the agent at runtime by reading \`assignment.md\` frontmatter. If no \`workspace\` field is set, treat the current working directory as your workspace.
@@ -5610,15 +5613,15 @@ Before starting work, read these files in order:
5610
5613
  ## Lifecycle Commands
5611
5614
 
5612
5615
  Use the \`syntaur\` CLI for state transitions and coordination:
5613
- - \`syntaur assign ${params.assignmentSlug} --agent <name> --project ${params.projectSlug}\` -- set assignee
5614
- - \`syntaur start ${params.assignmentSlug} --project ${params.projectSlug}\` -- pending -> in_progress
5615
- - \`syntaur review ${params.assignmentSlug} --project ${params.projectSlug}\` -- in_progress -> review
5616
- - \`syntaur complete ${params.assignmentSlug} --project ${params.projectSlug}\` -- in_progress/review -> completed
5617
- - \`syntaur block ${params.assignmentSlug} --project ${params.projectSlug} --reason <text>\` -- block
5618
- - \`syntaur unblock ${params.assignmentSlug} --project ${params.projectSlug}\` -- unblock
5619
- - \`syntaur fail ${params.assignmentSlug} --project ${params.projectSlug}\` -- mark as failed
5620
- - \`syntaur comment ${params.assignmentSlug} "body" --type question|note|feedback [--reply-to <id>]\` -- append to \`comments.md\` (use for all Q&A; questions support resolve toggle)
5621
- - \`syntaur request ${params.assignmentSlug} <target-slug-or-uuid> "text"\` -- append a todo to another assignment's \`## Todos\` annotated \`(from: ${params.assignmentSlug})\`
5616
+ - \`syntaur assign ${params2.assignmentSlug} --agent <name> --project ${params2.projectSlug}\` -- set assignee
5617
+ - \`syntaur start ${params2.assignmentSlug} --project ${params2.projectSlug}\` -- pending -> in_progress
5618
+ - \`syntaur review ${params2.assignmentSlug} --project ${params2.projectSlug}\` -- in_progress -> review
5619
+ - \`syntaur complete ${params2.assignmentSlug} --project ${params2.projectSlug}\` -- in_progress/review -> completed
5620
+ - \`syntaur block ${params2.assignmentSlug} --project ${params2.projectSlug} --reason <text>\` -- block
5621
+ - \`syntaur unblock ${params2.assignmentSlug} --project ${params2.projectSlug}\` -- unblock
5622
+ - \`syntaur fail ${params2.assignmentSlug} --project ${params2.projectSlug}\` -- mark as failed
5623
+ - \`syntaur comment ${params2.assignmentSlug} "body" --type question|note|feedback [--reply-to <id>]\` -- append to \`comments.md\` (use for all Q&A; questions support resolve toggle)
5624
+ - \`syntaur request ${params2.assignmentSlug} <target-slug-or-uuid> "text"\` -- append a todo to another assignment's \`## Todos\` annotated \`(from: ${params2.assignmentSlug})\`
5622
5625
 
5623
5626
  ## Troubleshooting
5624
5627
 
@@ -5649,11 +5652,11 @@ Read each linked playbook and follow the rules in its body section. The \`when_t
5649
5652
  }
5650
5653
 
5651
5654
  // src/templates/opencode-config.ts
5652
- function renderOpenCodeConfig(params) {
5655
+ function renderOpenCodeConfig(params2) {
5653
5656
  const config = {
5654
5657
  instructions: [
5655
5658
  `Read AGENTS.md in this directory for Syntaur protocol (v2.0) instructions.`,
5656
- `Read ${params.projectDir}/project.md for project overview (project-nested assignments only).`,
5659
+ `Read ${params2.projectDir}/project.md for project overview (project-nested assignments only).`,
5657
5660
  `Append timestamped progress entries to the assignment's progress.md (not to assignment.md).`,
5658
5661
  `Use 'syntaur comment <slug-or-uuid> "body" --type question|note|feedback' to append to comments.md \u2014 never edit it directly.`,
5659
5662
  `Use 'syntaur request <source> <target> "text"' to append a todo to another assignment's ## Todos.`,
@@ -5949,7 +5952,7 @@ Use --slug to specify a different slug.`
5949
5952
  init_config2();
5950
5953
  import { spawn } from "child_process";
5951
5954
  import { createServer as createNetServer } from "net";
5952
- import { resolve as resolve21, dirname as dirname4 } from "path";
5955
+ import { resolve as resolve22, dirname as dirname4 } from "path";
5953
5956
  import { fileURLToPath as fileURLToPath2 } from "url";
5954
5957
 
5955
5958
  // src/dashboard/server.ts
@@ -5958,7 +5961,7 @@ init_api();
5958
5961
  init_assignment_resolver();
5959
5962
  import express from "express";
5960
5963
  import { createServer } from "http";
5961
- import { resolve as resolve20 } from "path";
5964
+ import { resolve as resolve21 } from "path";
5962
5965
  import { writeFile as writeFile5, unlink as unlink4 } from "fs/promises";
5963
5966
  import { WebSocketServer, WebSocket } from "ws";
5964
5967
 
@@ -6121,8 +6124,8 @@ async function migrateFromMarkdown(projectsDir2) {
6121
6124
  return allSessions.length;
6122
6125
  }
6123
6126
  async function parseMarkdownSessionsIndex(filePath, projectSlug) {
6124
- const { readFile: readFile29 } = await import("fs/promises");
6125
- const raw = await readFile29(filePath, "utf-8");
6127
+ const { readFile: readFile30 } = await import("fs/promises");
6128
+ const raw = await readFile30(filePath, "utf-8");
6126
6129
  const sessions = [];
6127
6130
  const lines = raw.split("\n");
6128
6131
  let inTable = false;
@@ -6309,18 +6312,25 @@ function createWatcher(options) {
6309
6312
  if (parts.length === 0) return;
6310
6313
  const projectSlug = parts[0];
6311
6314
  let assignmentSlug;
6315
+ let isProjectTodos = false;
6312
6316
  if (parts.length >= 3 && parts[1] === "assignments") {
6313
6317
  assignmentSlug = parts[2];
6318
+ } else if (parts.length >= 2 && parts[1] === "todos") {
6319
+ isProjectTodos = true;
6314
6320
  }
6315
- const debounceKey = assignmentSlug ? `${projectSlug}/${assignmentSlug}` : projectSlug;
6321
+ const debounceKey = isProjectTodos ? `todos:${projectSlug}` : assignmentSlug ? `${projectSlug}/${assignmentSlug}` : projectSlug;
6316
6322
  const existing = pendingEvents.get(debounceKey);
6317
6323
  if (existing) clearTimeout(existing);
6318
- const messageType = assignmentSlug ? "assignment-updated" : "project-updated";
6324
+ const messageType = isProjectTodos ? "todos-updated" : assignmentSlug ? "assignment-updated" : "project-updated";
6319
6325
  pendingEvents.set(
6320
6326
  debounceKey,
6321
6327
  setTimeout(() => {
6322
6328
  pendingEvents.delete(debounceKey);
6323
- const message = {
6329
+ const message = isProjectTodos ? {
6330
+ type: "todos-updated",
6331
+ projectSlug,
6332
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
6333
+ } : {
6324
6334
  type: messageType,
6325
6335
  projectSlug,
6326
6336
  assignmentSlug,
@@ -8411,14 +8421,17 @@ function getWorkspaceParam(value) {
8411
8421
  return value ?? "";
8412
8422
  }
8413
8423
  var writeLocks = /* @__PURE__ */ new Map();
8414
- function withLock(workspace, fn) {
8415
- const prev = writeLocks.get(workspace) ?? Promise.resolve();
8424
+ function withLock(lockKey, fn) {
8425
+ const prev = writeLocks.get(lockKey) ?? Promise.resolve();
8416
8426
  const next = prev.then(fn);
8417
- writeLocks.set(workspace, next.then(() => {
8427
+ writeLocks.set(lockKey, next.then(() => {
8418
8428
  }, () => {
8419
8429
  }));
8420
8430
  return next;
8421
8431
  }
8432
+ function wsLock(workspace, fn) {
8433
+ return withLock(`ws:${workspace}`, fn);
8434
+ }
8422
8435
  function createTodosRouter(todosDir2, broadcast) {
8423
8436
  const router = Router5();
8424
8437
  function broadcastUpdate() {
@@ -8477,7 +8490,7 @@ function createTodosRouter(todosDir2, broadcast) {
8477
8490
  res.status(400).json({ error: "description is required" });
8478
8491
  return;
8479
8492
  }
8480
- const item = await withLock(workspace, async () => {
8493
+ const item = await wsLock(workspace, async () => {
8481
8494
  const checklist = await readChecklist(todosDir2, workspace);
8482
8495
  const existingIds = new Set(checklist.items.map((i) => i.id));
8483
8496
  const id = generateUniqueId(existingIds);
@@ -8506,7 +8519,7 @@ function createTodosRouter(todosDir2, broadcast) {
8506
8519
  res.status(400).json({ error: "ids must be an array of strings" });
8507
8520
  return;
8508
8521
  }
8509
- const items = await withLock(workspace, async () => {
8522
+ const items = await wsLock(workspace, async () => {
8510
8523
  const checklist = await readChecklist(todosDir2, workspace);
8511
8524
  const itemMap = new Map(checklist.items.map((i) => [i.id, i]));
8512
8525
  const reordered = [];
@@ -8541,8 +8554,8 @@ function createTodosRouter(todosDir2, broadcast) {
8541
8554
  router.post("/:workspace/archive", async (req, res) => {
8542
8555
  try {
8543
8556
  const { archivePath: archivePath2 } = await Promise.resolve().then(() => (init_parser2(), parser_exports));
8544
- const { resolve: resolve45 } = await import("path");
8545
- const { readFile: readFile29 } = await import("fs/promises");
8557
+ const { resolve: resolve46 } = await import("path");
8558
+ const { readFile: readFile30 } = await import("fs/promises");
8546
8559
  const { writeFileForce: writeFileForce2 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
8547
8560
  const workspace = getWorkspaceParam(req.params.workspace);
8548
8561
  const checklist = await readChecklist(todosDir2, workspace);
@@ -8558,10 +8571,10 @@ function createTodosRouter(todosDir2, broadcast) {
8558
8571
  (e) => e.itemIds.every((id) => completedIds.has(id))
8559
8572
  );
8560
8573
  const archFile = archivePath2(todosDir2, workspace, checklist.archiveInterval);
8561
- await ensureDir(resolve45(todosDir2, "archive"));
8574
+ await ensureDir(resolve46(todosDir2, "archive"));
8562
8575
  let archContent = "";
8563
8576
  if (await fileExists(archFile)) {
8564
- archContent = await readFile29(archFile, "utf-8");
8577
+ archContent = await readFile30(archFile, "utf-8");
8565
8578
  archContent = archContent.trimEnd() + "\n\n";
8566
8579
  } else {
8567
8580
  archContent = `---
@@ -8630,7 +8643,7 @@ workspace: ${workspace}
8630
8643
  router.patch("/:workspace/:id", async (req, res) => {
8631
8644
  try {
8632
8645
  const workspace = getWorkspaceParam(req.params.workspace);
8633
- const result = await withLock(workspace, async () => {
8646
+ const result = await wsLock(workspace, async () => {
8634
8647
  const checklist = await readChecklist(todosDir2, workspace);
8635
8648
  const item = checklist.items.find((i) => i.id === req.params.id);
8636
8649
  if (!item) return null;
@@ -8652,7 +8665,7 @@ workspace: ${workspace}
8652
8665
  router.delete("/:workspace/:id", async (req, res) => {
8653
8666
  try {
8654
8667
  const workspace = getWorkspaceParam(req.params.workspace);
8655
- const deleted = await withLock(workspace, async () => {
8668
+ const deleted = await wsLock(workspace, async () => {
8656
8669
  const checklist = await readChecklist(todosDir2, workspace);
8657
8670
  const idx = checklist.items.findIndex((i) => i.id === req.params.id);
8658
8671
  if (idx === -1) return false;
@@ -8673,7 +8686,7 @@ workspace: ${workspace}
8673
8686
  router.post("/:workspace/:id/start", async (req, res) => {
8674
8687
  try {
8675
8688
  const workspace = getWorkspaceParam(req.params.workspace);
8676
- const result = await withLock(workspace, async () => {
8689
+ const result = await wsLock(workspace, async () => {
8677
8690
  const checklist = await readChecklist(todosDir2, workspace);
8678
8691
  const item = checklist.items.find((i) => i.id === req.params.id);
8679
8692
  if (!item) return { error: "not_found" };
@@ -8700,7 +8713,7 @@ workspace: ${workspace}
8700
8713
  router.post("/:workspace/:id/complete", async (req, res) => {
8701
8714
  try {
8702
8715
  const workspace = getWorkspaceParam(req.params.workspace);
8703
- const result = await withLock(workspace, async () => {
8716
+ const result = await wsLock(workspace, async () => {
8704
8717
  const checklist = await readChecklist(todosDir2, workspace);
8705
8718
  const item = checklist.items.find((i) => i.id === req.params.id);
8706
8719
  if (!item) return null;
@@ -8734,7 +8747,7 @@ workspace: ${workspace}
8734
8747
  try {
8735
8748
  const reason = req.body.reason || null;
8736
8749
  const workspace = getWorkspaceParam(req.params.workspace);
8737
- const result = await withLock(workspace, async () => {
8750
+ const result = await wsLock(workspace, async () => {
8738
8751
  const checklist = await readChecklist(todosDir2, workspace);
8739
8752
  const item = checklist.items.find((i) => i.id === req.params.id);
8740
8753
  if (!item) return null;
@@ -8767,7 +8780,7 @@ workspace: ${workspace}
8767
8780
  router.post("/:workspace/:id/reopen", async (req, res) => {
8768
8781
  try {
8769
8782
  const workspace = getWorkspaceParam(req.params.workspace);
8770
- const result = await withLock(workspace, async () => {
8783
+ const result = await wsLock(workspace, async () => {
8771
8784
  const checklist = await readChecklist(todosDir2, workspace);
8772
8785
  const item = checklist.items.find((i) => i.id === req.params.id);
8773
8786
  if (!item) return null;
@@ -8789,7 +8802,7 @@ workspace: ${workspace}
8789
8802
  router.post("/:workspace/:id/unblock", async (req, res) => {
8790
8803
  try {
8791
8804
  const workspace = getWorkspaceParam(req.params.workspace);
8792
- const result = await withLock(workspace, async () => {
8805
+ const result = await wsLock(workspace, async () => {
8793
8806
  const checklist = await readChecklist(todosDir2, workspace);
8794
8807
  const item = checklist.items.find((i) => i.id === req.params.id);
8795
8808
  if (!item) return null;
@@ -8811,9 +8824,606 @@ workspace: ${workspace}
8811
8824
  return router;
8812
8825
  }
8813
8826
 
8827
+ // src/dashboard/api-project-todos.ts
8828
+ init_parser2();
8829
+ init_fs();
8830
+ init_paths();
8831
+ import { Router as Router6 } from "express";
8832
+ import { mkdir as mkdir2, readFile as readFile14 } from "fs/promises";
8833
+ import { resolve as resolve19 } from "path";
8834
+ var writeLocks2 = /* @__PURE__ */ new Map();
8835
+ function projLock(slug, fn) {
8836
+ const key = `proj:${slug}`;
8837
+ const prev = writeLocks2.get(key) ?? Promise.resolve();
8838
+ const next = prev.then(fn);
8839
+ writeLocks2.set(key, next.then(() => {
8840
+ }, () => {
8841
+ }));
8842
+ return next;
8843
+ }
8844
+ function getProjectIdParam(value) {
8845
+ if (Array.isArray(value)) return value[0] ?? "";
8846
+ return value ?? "";
8847
+ }
8848
+ function params(req) {
8849
+ return req.params;
8850
+ }
8851
+ async function projectExists(projectsDir2, slug) {
8852
+ return fileExists(resolve19(projectsDir2, slug, "project.md"));
8853
+ }
8854
+ async function ensureProjectTodosDir(projectsDir2, slug) {
8855
+ const todosDir2 = projectTodosDir(projectsDir2, slug);
8856
+ try {
8857
+ await mkdir2(todosDir2, { recursive: false });
8858
+ } catch (err2) {
8859
+ const code = err2.code;
8860
+ if (code === "EEXIST") return;
8861
+ if (code === "ENOENT") {
8862
+ const e = new Error("PROJECT_GONE");
8863
+ e.code = "PROJECT_GONE";
8864
+ throw e;
8865
+ }
8866
+ throw err2;
8867
+ }
8868
+ try {
8869
+ await mkdir2(resolve19(todosDir2, "archive"), { recursive: false });
8870
+ } catch (err2) {
8871
+ const code = err2.code;
8872
+ if (code === "EEXIST") return;
8873
+ if (code === "ENOENT") {
8874
+ const e = new Error("PROJECT_GONE");
8875
+ e.code = "PROJECT_GONE";
8876
+ throw e;
8877
+ }
8878
+ throw err2;
8879
+ }
8880
+ }
8881
+ function notFound(res, slug) {
8882
+ res.status(404).json({ error: `Project "${slug}" not found` });
8883
+ }
8884
+ function createProjectTodosRouter(projectsDir2, broadcast) {
8885
+ const router = Router6({ mergeParams: true });
8886
+ function broadcastUpdate(projectSlug) {
8887
+ broadcast({ type: "todos-updated", projectSlug, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
8888
+ }
8889
+ function validateProjectId(req, res, next) {
8890
+ const slug = getProjectIdParam(params(req).projectId);
8891
+ if (!slug || !isValidSlug(slug)) {
8892
+ res.status(400).json({ error: `Invalid project slug: "${slug}"` });
8893
+ return;
8894
+ }
8895
+ next();
8896
+ }
8897
+ router.use(validateProjectId);
8898
+ router.get("/", async (req, res) => {
8899
+ try {
8900
+ const slug = getProjectIdParam(params(req).projectId);
8901
+ if (!await projectExists(projectsDir2, slug)) {
8902
+ notFound(res, slug);
8903
+ return;
8904
+ }
8905
+ const todosDir2 = projectTodosDir(projectsDir2, slug);
8906
+ const checklist = await readChecklist(todosDir2, slug);
8907
+ res.json({
8908
+ workspace: checklist.workspace,
8909
+ archiveInterval: checklist.archiveInterval,
8910
+ items: checklist.items,
8911
+ counts: computeCounts(checklist.items)
8912
+ });
8913
+ } catch (error) {
8914
+ res.status(500).json({ error: error instanceof Error ? error.message : "Failed to get todos" });
8915
+ }
8916
+ });
8917
+ router.post("/", async (req, res) => {
8918
+ try {
8919
+ const slug = getProjectIdParam(params(req).projectId);
8920
+ const { description, tags } = req.body;
8921
+ if (!description || typeof description !== "string") {
8922
+ res.status(400).json({ error: "description is required" });
8923
+ return;
8924
+ }
8925
+ if (!await projectExists(projectsDir2, slug)) {
8926
+ notFound(res, slug);
8927
+ return;
8928
+ }
8929
+ const item = await projLock(slug, async () => {
8930
+ if (!await projectExists(projectsDir2, slug)) return null;
8931
+ await ensureProjectTodosDir(projectsDir2, slug);
8932
+ const todosDir2 = projectTodosDir(projectsDir2, slug);
8933
+ const checklist = await readChecklist(todosDir2, slug);
8934
+ const existingIds = new Set(checklist.items.map((i) => i.id));
8935
+ const id = generateUniqueId(existingIds);
8936
+ const newItem = {
8937
+ id,
8938
+ description,
8939
+ status: "open",
8940
+ tags: Array.isArray(tags) ? tags : [],
8941
+ session: null
8942
+ };
8943
+ checklist.workspace = slug;
8944
+ checklist.items.push(newItem);
8945
+ await writeChecklist(todosDir2, checklist);
8946
+ return newItem;
8947
+ });
8948
+ if (!item) {
8949
+ notFound(res, slug);
8950
+ return;
8951
+ }
8952
+ broadcastUpdate(slug);
8953
+ res.status(201).json(item);
8954
+ } catch (error) {
8955
+ if (error.code === "PROJECT_GONE") {
8956
+ notFound(res, getProjectIdParam(params(req).projectId));
8957
+ return;
8958
+ }
8959
+ res.status(500).json({ error: error instanceof Error ? error.message : "Failed to add todo" });
8960
+ }
8961
+ });
8962
+ router.post("/reorder", async (req, res) => {
8963
+ try {
8964
+ const slug = getProjectIdParam(params(req).projectId);
8965
+ const { ids } = req.body;
8966
+ if (!Array.isArray(ids) || !ids.every((id) => typeof id === "string")) {
8967
+ res.status(400).json({ error: "ids must be an array of strings" });
8968
+ return;
8969
+ }
8970
+ if (!await projectExists(projectsDir2, slug)) {
8971
+ notFound(res, slug);
8972
+ return;
8973
+ }
8974
+ const items = await projLock(slug, async () => {
8975
+ if (!await projectExists(projectsDir2, slug)) return null;
8976
+ await ensureProjectTodosDir(projectsDir2, slug);
8977
+ const todosDir2 = projectTodosDir(projectsDir2, slug);
8978
+ const checklist = await readChecklist(todosDir2, slug);
8979
+ const itemMap = new Map(checklist.items.map((i) => [i.id, i]));
8980
+ const reordered = [];
8981
+ for (const id of ids) {
8982
+ const item = itemMap.get(id);
8983
+ if (item) {
8984
+ reordered.push(item);
8985
+ itemMap.delete(id);
8986
+ }
8987
+ }
8988
+ for (const item of itemMap.values()) reordered.push(item);
8989
+ checklist.workspace = slug;
8990
+ checklist.items = reordered;
8991
+ await writeChecklist(todosDir2, checklist);
8992
+ return reordered;
8993
+ });
8994
+ if (!items) {
8995
+ notFound(res, slug);
8996
+ return;
8997
+ }
8998
+ broadcastUpdate(slug);
8999
+ res.json({ items });
9000
+ } catch (error) {
9001
+ if (error.code === "PROJECT_GONE") {
9002
+ notFound(res, getProjectIdParam(params(req).projectId));
9003
+ return;
9004
+ }
9005
+ res.status(500).json({ error: error instanceof Error ? error.message : "Failed to reorder todos" });
9006
+ }
9007
+ });
9008
+ router.get("/log", async (req, res) => {
9009
+ try {
9010
+ const slug = getProjectIdParam(params(req).projectId);
9011
+ if (!await projectExists(projectsDir2, slug)) {
9012
+ notFound(res, slug);
9013
+ return;
9014
+ }
9015
+ const todosDir2 = projectTodosDir(projectsDir2, slug);
9016
+ const log = await readLog(todosDir2, slug);
9017
+ res.json(log);
9018
+ } catch (error) {
9019
+ res.status(500).json({ error: error instanceof Error ? error.message : "Failed to get log" });
9020
+ }
9021
+ });
9022
+ router.post("/archive", async (req, res) => {
9023
+ try {
9024
+ const slug = getProjectIdParam(params(req).projectId);
9025
+ if (!await projectExists(projectsDir2, slug)) {
9026
+ notFound(res, slug);
9027
+ return;
9028
+ }
9029
+ const todosDir2 = projectTodosDir(projectsDir2, slug);
9030
+ await ensureProjectTodosDir(projectsDir2, slug);
9031
+ const checklist = await readChecklist(todosDir2, slug);
9032
+ const log = await readLog(todosDir2, slug);
9033
+ const completedIds = new Set(
9034
+ checklist.items.filter((i) => i.status === "completed").map((i) => i.id)
9035
+ );
9036
+ if (completedIds.size === 0) {
9037
+ res.json({ archived: 0, message: "No completed items to archive" });
9038
+ return;
9039
+ }
9040
+ const toArchive = log.entries.filter(
9041
+ (e) => e.itemIds.every((id) => completedIds.has(id))
9042
+ );
9043
+ const archFile = archivePath(todosDir2, slug, checklist.archiveInterval);
9044
+ let archContent = "";
9045
+ if (await fileExists(archFile)) {
9046
+ archContent = await readFile14(archFile, "utf-8");
9047
+ archContent = archContent.trimEnd() + "\n\n";
9048
+ } else {
9049
+ archContent = `---
9050
+ workspace: ${slug}
9051
+ ---
9052
+
9053
+ # Archive
9054
+
9055
+ `;
9056
+ }
9057
+ const completedItems = checklist.items.filter((i) => completedIds.has(i.id));
9058
+ for (const item of completedItems) {
9059
+ archContent += `- [x] ${item.description} ${item.tags.map((t) => `#${t}`).join(" ")} [t:${item.id}]
9060
+ `;
9061
+ }
9062
+ archContent += "\n";
9063
+ for (const entry of toArchive) {
9064
+ archContent += `### ${entry.timestamp} \u2014 ${entry.itemIds.map((i) => `t:${i}`).join(", ")}
9065
+ `;
9066
+ if (entry.items) archContent += `**Items:** ${entry.items}
9067
+ `;
9068
+ if (entry.session) archContent += `**Session:** ${entry.session}
9069
+ `;
9070
+ if (entry.branch) archContent += `**Branch:** ${entry.branch}
9071
+ `;
9072
+ if (entry.summary) archContent += `**Summary:** ${entry.summary}
9073
+ `;
9074
+ if (entry.blockers) archContent += `**Blockers:** ${entry.blockers}
9075
+ `;
9076
+ archContent += "\n";
9077
+ }
9078
+ await writeFileForce(archFile, archContent);
9079
+ checklist.workspace = slug;
9080
+ checklist.items = checklist.items.filter((i) => !completedIds.has(i.id));
9081
+ await writeChecklist(todosDir2, checklist);
9082
+ broadcastUpdate(slug);
9083
+ res.json({ archived: completedIds.size, logEntries: toArchive.length });
9084
+ } catch (error) {
9085
+ if (error.code === "PROJECT_GONE") {
9086
+ notFound(res, getProjectIdParam(params(req).projectId));
9087
+ return;
9088
+ }
9089
+ res.status(500).json({ error: error instanceof Error ? error.message : "Failed to archive" });
9090
+ }
9091
+ });
9092
+ router.get("/log/:id", async (req, res) => {
9093
+ try {
9094
+ const slug = getProjectIdParam(params(req).projectId);
9095
+ if (!await projectExists(projectsDir2, slug)) {
9096
+ notFound(res, slug);
9097
+ return;
9098
+ }
9099
+ const todosDir2 = projectTodosDir(projectsDir2, slug);
9100
+ const log = await readLog(todosDir2, slug);
9101
+ const entries = log.entries.filter((e) => e.itemIds.includes(params(req).id ?? ""));
9102
+ res.json({ workspace: log.workspace, entries });
9103
+ } catch (error) {
9104
+ res.status(500).json({ error: error instanceof Error ? error.message : "Failed to get log" });
9105
+ }
9106
+ });
9107
+ router.get("/:id", async (req, res) => {
9108
+ try {
9109
+ const slug = getProjectIdParam(params(req).projectId);
9110
+ if (!await projectExists(projectsDir2, slug)) {
9111
+ notFound(res, slug);
9112
+ return;
9113
+ }
9114
+ const todosDir2 = projectTodosDir(projectsDir2, slug);
9115
+ const checklist = await readChecklist(todosDir2, slug);
9116
+ const item = checklist.items.find((i) => i.id === (params(req).id ?? ""));
9117
+ if (!item) {
9118
+ res.status(404).json({ error: `Todo "${params(req).id ?? ""}" not found` });
9119
+ return;
9120
+ }
9121
+ const log = await readLog(todosDir2, slug);
9122
+ const logEntries = log.entries.filter((e) => e.itemIds.includes(params(req).id ?? ""));
9123
+ res.json({ ...item, log: logEntries });
9124
+ } catch (error) {
9125
+ res.status(500).json({ error: error instanceof Error ? error.message : "Failed to get todo" });
9126
+ }
9127
+ });
9128
+ router.patch("/:id", async (req, res) => {
9129
+ try {
9130
+ const slug = getProjectIdParam(params(req).projectId);
9131
+ if (!await projectExists(projectsDir2, slug)) {
9132
+ notFound(res, slug);
9133
+ return;
9134
+ }
9135
+ const result = await projLock(slug, async () => {
9136
+ if (!await projectExists(projectsDir2, slug)) return "gone";
9137
+ await ensureProjectTodosDir(projectsDir2, slug);
9138
+ const todosDir2 = projectTodosDir(projectsDir2, slug);
9139
+ const checklist = await readChecklist(todosDir2, slug);
9140
+ const item = checklist.items.find((i) => i.id === (params(req).id ?? ""));
9141
+ if (!item) return null;
9142
+ if (req.body.description !== void 0) item.description = req.body.description;
9143
+ if (Array.isArray(req.body.tags)) item.tags = req.body.tags;
9144
+ checklist.workspace = slug;
9145
+ await writeChecklist(todosDir2, checklist);
9146
+ return { ...item };
9147
+ });
9148
+ if (result === "gone") {
9149
+ notFound(res, slug);
9150
+ return;
9151
+ }
9152
+ if (!result) {
9153
+ res.status(404).json({ error: `Todo "${params(req).id ?? ""}" not found` });
9154
+ return;
9155
+ }
9156
+ broadcastUpdate(slug);
9157
+ res.json(result);
9158
+ } catch (error) {
9159
+ if (error.code === "PROJECT_GONE") {
9160
+ notFound(res, getProjectIdParam(params(req).projectId));
9161
+ return;
9162
+ }
9163
+ res.status(500).json({ error: error instanceof Error ? error.message : "Failed to update todo" });
9164
+ }
9165
+ });
9166
+ router.delete("/:id", async (req, res) => {
9167
+ try {
9168
+ const slug = getProjectIdParam(params(req).projectId);
9169
+ if (!await projectExists(projectsDir2, slug)) {
9170
+ notFound(res, slug);
9171
+ return;
9172
+ }
9173
+ const deleted = await projLock(slug, async () => {
9174
+ if (!await projectExists(projectsDir2, slug)) return "gone";
9175
+ await ensureProjectTodosDir(projectsDir2, slug);
9176
+ const todosDir2 = projectTodosDir(projectsDir2, slug);
9177
+ const checklist = await readChecklist(todosDir2, slug);
9178
+ const idx = checklist.items.findIndex((i) => i.id === (params(req).id ?? ""));
9179
+ if (idx === -1) return false;
9180
+ checklist.items.splice(idx, 1);
9181
+ checklist.workspace = slug;
9182
+ await writeChecklist(todosDir2, checklist);
9183
+ return true;
9184
+ });
9185
+ if (deleted === "gone") {
9186
+ notFound(res, slug);
9187
+ return;
9188
+ }
9189
+ if (!deleted) {
9190
+ res.status(404).json({ error: `Todo "${params(req).id ?? ""}" not found` });
9191
+ return;
9192
+ }
9193
+ broadcastUpdate(slug);
9194
+ res.json({ deleted: params(req).id ?? "" });
9195
+ } catch (error) {
9196
+ if (error.code === "PROJECT_GONE") {
9197
+ notFound(res, getProjectIdParam(params(req).projectId));
9198
+ return;
9199
+ }
9200
+ res.status(500).json({ error: error instanceof Error ? error.message : "Failed to delete todo" });
9201
+ }
9202
+ });
9203
+ router.post("/:id/start", async (req, res) => {
9204
+ try {
9205
+ const slug = getProjectIdParam(params(req).projectId);
9206
+ if (!await projectExists(projectsDir2, slug)) {
9207
+ notFound(res, slug);
9208
+ return;
9209
+ }
9210
+ const result = await projLock(slug, async () => {
9211
+ if (!await projectExists(projectsDir2, slug)) return { error: "gone" };
9212
+ await ensureProjectTodosDir(projectsDir2, slug);
9213
+ const todosDir2 = projectTodosDir(projectsDir2, slug);
9214
+ const checklist = await readChecklist(todosDir2, slug);
9215
+ const item = checklist.items.find((i) => i.id === (params(req).id ?? ""));
9216
+ if (!item) return { error: "not_found" };
9217
+ if (item.status === "in_progress") return { error: "conflict", session: item.session };
9218
+ item.status = "in_progress";
9219
+ item.session = req.body.session || null;
9220
+ checklist.workspace = slug;
9221
+ await writeChecklist(todosDir2, checklist);
9222
+ return { item: { ...item } };
9223
+ });
9224
+ if ("error" in result) {
9225
+ if (result.error === "gone") {
9226
+ notFound(res, slug);
9227
+ return;
9228
+ }
9229
+ if (result.error === "not_found") {
9230
+ res.status(404).json({ error: `Todo "${params(req).id ?? ""}" not found` });
9231
+ return;
9232
+ }
9233
+ res.status(409).json({ error: `Todo is already in progress (session: ${result.session})` });
9234
+ return;
9235
+ }
9236
+ broadcastUpdate(slug);
9237
+ res.json(result.item);
9238
+ } catch (error) {
9239
+ if (error.code === "PROJECT_GONE") {
9240
+ notFound(res, getProjectIdParam(params(req).projectId));
9241
+ return;
9242
+ }
9243
+ res.status(500).json({ error: error instanceof Error ? error.message : "Failed to start todo" });
9244
+ }
9245
+ });
9246
+ router.post("/:id/complete", async (req, res) => {
9247
+ try {
9248
+ const slug = getProjectIdParam(params(req).projectId);
9249
+ if (!await projectExists(projectsDir2, slug)) {
9250
+ notFound(res, slug);
9251
+ return;
9252
+ }
9253
+ const result = await projLock(slug, async () => {
9254
+ if (!await projectExists(projectsDir2, slug)) return "gone";
9255
+ await ensureProjectTodosDir(projectsDir2, slug);
9256
+ const todosDir2 = projectTodosDir(projectsDir2, slug);
9257
+ const checklist = await readChecklist(todosDir2, slug);
9258
+ const item = checklist.items.find((i) => i.id === (params(req).id ?? ""));
9259
+ if (!item) return null;
9260
+ item.status = "completed";
9261
+ item.session = null;
9262
+ checklist.workspace = slug;
9263
+ await writeChecklist(todosDir2, checklist);
9264
+ const entry = {
9265
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
9266
+ itemIds: [item.id],
9267
+ items: item.description,
9268
+ session: req.body.session || null,
9269
+ branch: req.body.branch || null,
9270
+ summary: req.body.summary || "Completed.",
9271
+ blockers: null,
9272
+ status: null
9273
+ };
9274
+ await appendLogEntry2(todosDir2, slug, entry);
9275
+ return { ...item };
9276
+ });
9277
+ if (result === "gone") {
9278
+ notFound(res, slug);
9279
+ return;
9280
+ }
9281
+ if (!result) {
9282
+ res.status(404).json({ error: `Todo "${params(req).id ?? ""}" not found` });
9283
+ return;
9284
+ }
9285
+ broadcastUpdate(slug);
9286
+ res.json(result);
9287
+ } catch (error) {
9288
+ if (error.code === "PROJECT_GONE") {
9289
+ notFound(res, getProjectIdParam(params(req).projectId));
9290
+ return;
9291
+ }
9292
+ res.status(500).json({ error: error instanceof Error ? error.message : "Failed to complete todo" });
9293
+ }
9294
+ });
9295
+ router.post("/:id/block", async (req, res) => {
9296
+ try {
9297
+ const slug = getProjectIdParam(params(req).projectId);
9298
+ const reason = req.body.reason || null;
9299
+ if (!await projectExists(projectsDir2, slug)) {
9300
+ notFound(res, slug);
9301
+ return;
9302
+ }
9303
+ const result = await projLock(slug, async () => {
9304
+ if (!await projectExists(projectsDir2, slug)) return "gone";
9305
+ await ensureProjectTodosDir(projectsDir2, slug);
9306
+ const todosDir2 = projectTodosDir(projectsDir2, slug);
9307
+ const checklist = await readChecklist(todosDir2, slug);
9308
+ const item = checklist.items.find((i) => i.id === (params(req).id ?? ""));
9309
+ if (!item) return null;
9310
+ item.status = "blocked";
9311
+ item.session = null;
9312
+ checklist.workspace = slug;
9313
+ await writeChecklist(todosDir2, checklist);
9314
+ const entry = {
9315
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
9316
+ itemIds: [item.id],
9317
+ items: item.description,
9318
+ session: req.body.session || null,
9319
+ branch: null,
9320
+ summary: reason || "Blocked.",
9321
+ blockers: reason,
9322
+ status: "blocked"
9323
+ };
9324
+ await appendLogEntry2(todosDir2, slug, entry);
9325
+ return { ...item };
9326
+ });
9327
+ if (result === "gone") {
9328
+ notFound(res, slug);
9329
+ return;
9330
+ }
9331
+ if (!result) {
9332
+ res.status(404).json({ error: `Todo "${params(req).id ?? ""}" not found` });
9333
+ return;
9334
+ }
9335
+ broadcastUpdate(slug);
9336
+ res.json(result);
9337
+ } catch (error) {
9338
+ if (error.code === "PROJECT_GONE") {
9339
+ notFound(res, getProjectIdParam(params(req).projectId));
9340
+ return;
9341
+ }
9342
+ res.status(500).json({ error: error instanceof Error ? error.message : "Failed to block todo" });
9343
+ }
9344
+ });
9345
+ router.post("/:id/reopen", async (req, res) => {
9346
+ try {
9347
+ const slug = getProjectIdParam(params(req).projectId);
9348
+ if (!await projectExists(projectsDir2, slug)) {
9349
+ notFound(res, slug);
9350
+ return;
9351
+ }
9352
+ const result = await projLock(slug, async () => {
9353
+ if (!await projectExists(projectsDir2, slug)) return "gone";
9354
+ await ensureProjectTodosDir(projectsDir2, slug);
9355
+ const todosDir2 = projectTodosDir(projectsDir2, slug);
9356
+ const checklist = await readChecklist(todosDir2, slug);
9357
+ const item = checklist.items.find((i) => i.id === (params(req).id ?? ""));
9358
+ if (!item) return null;
9359
+ item.status = "open";
9360
+ item.session = null;
9361
+ checklist.workspace = slug;
9362
+ await writeChecklist(todosDir2, checklist);
9363
+ return { ...item };
9364
+ });
9365
+ if (result === "gone") {
9366
+ notFound(res, slug);
9367
+ return;
9368
+ }
9369
+ if (!result) {
9370
+ res.status(404).json({ error: `Todo "${params(req).id ?? ""}" not found` });
9371
+ return;
9372
+ }
9373
+ broadcastUpdate(slug);
9374
+ res.json(result);
9375
+ } catch (error) {
9376
+ if (error.code === "PROJECT_GONE") {
9377
+ notFound(res, getProjectIdParam(params(req).projectId));
9378
+ return;
9379
+ }
9380
+ res.status(500).json({ error: error instanceof Error ? error.message : "Failed to reopen todo" });
9381
+ }
9382
+ });
9383
+ router.post("/:id/unblock", async (req, res) => {
9384
+ try {
9385
+ const slug = getProjectIdParam(params(req).projectId);
9386
+ if (!await projectExists(projectsDir2, slug)) {
9387
+ notFound(res, slug);
9388
+ return;
9389
+ }
9390
+ const result = await projLock(slug, async () => {
9391
+ if (!await projectExists(projectsDir2, slug)) return "gone";
9392
+ await ensureProjectTodosDir(projectsDir2, slug);
9393
+ const todosDir2 = projectTodosDir(projectsDir2, slug);
9394
+ const checklist = await readChecklist(todosDir2, slug);
9395
+ const item = checklist.items.find((i) => i.id === (params(req).id ?? ""));
9396
+ if (!item) return null;
9397
+ item.status = "open";
9398
+ item.session = null;
9399
+ checklist.workspace = slug;
9400
+ await writeChecklist(todosDir2, checklist);
9401
+ return { ...item };
9402
+ });
9403
+ if (result === "gone") {
9404
+ notFound(res, slug);
9405
+ return;
9406
+ }
9407
+ if (!result) {
9408
+ res.status(404).json({ error: `Todo "${params(req).id ?? ""}" not found` });
9409
+ return;
9410
+ }
9411
+ broadcastUpdate(slug);
9412
+ res.json(result);
9413
+ } catch (error) {
9414
+ if (error.code === "PROJECT_GONE") {
9415
+ notFound(res, getProjectIdParam(params(req).projectId));
9416
+ return;
9417
+ }
9418
+ res.status(500).json({ error: error instanceof Error ? error.message : "Failed to unblock todo" });
9419
+ }
9420
+ });
9421
+ return router;
9422
+ }
9423
+
8814
9424
  // src/dashboard/api-backup.ts
8815
9425
  init_config2();
8816
- import { Router as Router6 } from "express";
9426
+ import { Router as Router7 } from "express";
8817
9427
 
8818
9428
  // src/utils/github-backup.ts
8819
9429
  init_paths();
@@ -8821,8 +9431,8 @@ init_fs();
8821
9431
  init_config2();
8822
9432
  import { execFile as execFile2 } from "child_process";
8823
9433
  import { promisify as promisify2 } from "util";
8824
- import { cp, mkdtemp, rm as rm2, readFile as readFile14, writeFile as writeFile4, unlink as unlink3, stat, open, rename as rename3 } from "fs/promises";
8825
- import { resolve as resolve19, join as join2 } from "path";
9434
+ import { cp, mkdtemp, rm as rm2, readFile as readFile15, writeFile as writeFile4, unlink as unlink3, stat, open, rename as rename3 } from "fs/promises";
9435
+ import { resolve as resolve20, join as join2 } from "path";
8826
9436
  import { tmpdir } from "os";
8827
9437
  var exec2 = promisify2(execFile2);
8828
9438
  var VALID_CATEGORIES = ["projects", "playbooks", "todos", "servers", "config"];
@@ -8862,7 +9472,7 @@ async function resolveCategoryPath(category) {
8862
9472
  case "servers":
8863
9473
  return { sourcePath: serversDir(), repoPath: "servers", isFile: false };
8864
9474
  case "config":
8865
- return { sourcePath: resolve19(syntaurRoot(), "config.md"), repoPath: "config.md", isFile: true };
9475
+ return { sourcePath: resolve20(syntaurRoot(), "config.md"), repoPath: "config.md", isFile: true };
8866
9476
  }
8867
9477
  }
8868
9478
  async function checkGitInstalled() {
@@ -8873,7 +9483,7 @@ async function checkGitInstalled() {
8873
9483
  }
8874
9484
  }
8875
9485
  async function acquireLock() {
8876
- const lockPath = resolve19(syntaurRoot(), LOCK_FILE_NAME);
9486
+ const lockPath = resolve20(syntaurRoot(), LOCK_FILE_NAME);
8877
9487
  await ensureDir(syntaurRoot());
8878
9488
  try {
8879
9489
  const handle = await open(lockPath, "wx");
@@ -8882,7 +9492,7 @@ async function acquireLock() {
8882
9492
  return lockPath;
8883
9493
  } catch (err2) {
8884
9494
  if (err2.code === "EEXIST") {
8885
- const pid = await readFile14(lockPath, "utf-8").catch(() => "");
9495
+ const pid = await readFile15(lockPath, "utf-8").catch(() => "");
8886
9496
  throw new Error(
8887
9497
  `Backup operation already in progress (lock file at ${lockPath}, pid ${pid.trim() || "unknown"}). If stale, delete the file and retry.`
8888
9498
  );
@@ -8920,7 +9530,7 @@ async function copyRecursive(src, dest) {
8920
9530
  await ensureDir(dest);
8921
9531
  await cp(src, dest, { recursive: true, force: true });
8922
9532
  } else {
8923
- await ensureDir(resolve19(dest, ".."));
9533
+ await ensureDir(resolve20(dest, ".."));
8924
9534
  await cp(src, dest, { force: true });
8925
9535
  }
8926
9536
  }
@@ -8929,7 +9539,7 @@ function resolveCategoriesStrict(csv) {
8929
9539
  return parseCategoriesStrict(parts);
8930
9540
  }
8931
9541
  async function readSanitizedConfig(configPath) {
8932
- const content = await readFile14(configPath, "utf-8");
9542
+ const content = await readFile15(configPath, "utf-8");
8933
9543
  return content.replace(/^(\s*lastBackup:\s*).*$/m, "$1null").replace(/^(\s*lastRestore:\s*).*$/m, "$1null");
8934
9544
  }
8935
9545
  async function backupToGithub(overrides) {
@@ -8968,7 +9578,7 @@ async function backupToGithub(overrides) {
8968
9578
  }
8969
9579
  if (category === "config") {
8970
9580
  const sanitized = await readSanitizedConfig(sourcePath);
8971
- await ensureDir(resolve19(destPath, ".."));
9581
+ await ensureDir(resolve20(destPath, ".."));
8972
9582
  await writeFile4(destPath, sanitized, "utf-8");
8973
9583
  } else {
8974
9584
  await copyRecursive(sourcePath, destPath);
@@ -9022,7 +9632,7 @@ async function backupToGithub(overrides) {
9022
9632
  }
9023
9633
  async function safeRestoreCategory(localPath, repoSrcPath, isFile) {
9024
9634
  if (isFile) {
9025
- await ensureDir(resolve19(localPath, ".."));
9635
+ await ensureDir(resolve20(localPath, ".."));
9026
9636
  await cp(repoSrcPath, localPath, { force: true });
9027
9637
  return;
9028
9638
  }
@@ -9123,7 +9733,7 @@ async function restoreFromGithub(overrides) {
9123
9733
  }
9124
9734
  async function getBackupStatus() {
9125
9735
  const config = await readConfig();
9126
- const lockPath = resolve19(syntaurRoot(), LOCK_FILE_NAME);
9736
+ const lockPath = resolve20(syntaurRoot(), LOCK_FILE_NAME);
9127
9737
  const locked = await fileExists(lockPath);
9128
9738
  return {
9129
9739
  repo: config.backup?.repo ?? null,
@@ -9136,7 +9746,7 @@ async function getBackupStatus() {
9136
9746
 
9137
9747
  // src/dashboard/api-backup.ts
9138
9748
  function createBackupRouter() {
9139
- const router = Router6();
9749
+ const router = Router7();
9140
9750
  router.get("/", async (_req, res) => {
9141
9751
  try {
9142
9752
  const status = await getBackupStatus();
@@ -9460,7 +10070,7 @@ function createDashboardServer(options) {
9460
10070
  (async () => {
9461
10071
  try {
9462
10072
  const configResult = await migrateLegacyConfig(
9463
- resolve20(syntaurRoot(), "config.md")
10073
+ resolve21(syntaurRoot(), "config.md")
9464
10074
  );
9465
10075
  const projectResult = await migrateLegacyProjectFiles(projectsDir2);
9466
10076
  const summary = summarizeMigration(projectResult, configResult);
@@ -9684,15 +10294,16 @@ function createDashboardServer(options) {
9684
10294
  app.use("/api/agent-sessions", createAgentSessionsRouter(projectsDir2, broadcast, assignmentsDir2));
9685
10295
  app.use("/api/playbooks", createPlaybooksRouter(playbooksDir3));
9686
10296
  app.use("/api/todos", createTodosRouter(todosDir2, broadcast));
10297
+ app.use("/api/projects/:projectId/todos", createProjectTodosRouter(projectsDir2, broadcast));
9687
10298
  app.use("/api/backup", createBackupRouter());
9688
10299
  if (serveStaticUi && dashboardDistPath) {
9689
- app.use("/assets", express.static(resolve20(dashboardDistPath, "assets")));
10300
+ app.use("/assets", express.static(resolve21(dashboardDistPath, "assets")));
9690
10301
  app.get("{*path}", async (req, res) => {
9691
10302
  if (req.path.startsWith("/api") || req.path === "/ws" || req.path.startsWith("/assets")) {
9692
10303
  res.status(404).json({ error: "Not Found" });
9693
10304
  return;
9694
10305
  }
9695
- const indexPath = resolve20(dashboardDistPath, "index.html");
10306
+ const indexPath = resolve21(dashboardDistPath, "index.html");
9696
10307
  if (!await fileExists(indexPath)) {
9697
10308
  res.status(503).send(
9698
10309
  'Dashboard not built. Run "npm run build:dashboard" first.'
@@ -9730,7 +10341,7 @@ function createDashboardServer(options) {
9730
10341
  }
9731
10342
  });
9732
10343
  server.listen(port, () => {
9733
- const portFile = resolve20(syntaurRoot(), "dashboard-port");
10344
+ const portFile = resolve21(syntaurRoot(), "dashboard-port");
9734
10345
  writeFile5(portFile, String(port), "utf-8").catch(() => {
9735
10346
  });
9736
10347
  resolvePromise();
@@ -9747,7 +10358,7 @@ function createDashboardServer(options) {
9747
10358
  client.terminate();
9748
10359
  }
9749
10360
  clients.clear();
9750
- const portFile = resolve20(syntaurRoot(), "dashboard-port");
10361
+ const portFile = resolve21(syntaurRoot(), "dashboard-port");
9751
10362
  await unlink4(portFile).catch(() => {
9752
10363
  });
9753
10364
  server.closeAllConnections?.();
@@ -9827,8 +10438,8 @@ async function dashboardCommand(options) {
9827
10438
  port = availablePort;
9828
10439
  }
9829
10440
  const thisFile = fileURLToPath2(import.meta.url);
9830
- const packageRoot = resolve21(dirname4(thisFile), "..");
9831
- const dashboardDist = resolve21(packageRoot, "dashboard", "dist");
10441
+ const packageRoot = resolve22(dirname4(thisFile), "..");
10442
+ const dashboardDist = resolve22(packageRoot, "dashboard", "dist");
9832
10443
  const server = createDashboardServer({
9833
10444
  port,
9834
10445
  projectsDir: projectsDir2,
@@ -9842,8 +10453,8 @@ async function dashboardCommand(options) {
9842
10453
  await server.start();
9843
10454
  let viteProcess = null;
9844
10455
  if (mode === "dev") {
9845
- const dashboardDir = resolve21(packageRoot, "dashboard");
9846
- const viteBin = resolve21(dashboardDir, "node_modules", ".bin", "vite");
10456
+ const dashboardDir = resolve22(packageRoot, "dashboard");
10457
+ const viteBin = resolve22(dashboardDir, "node_modules", ".bin", "vite");
9847
10458
  if (!await fileExists(viteBin)) {
9848
10459
  console.error(
9849
10460
  'Vite not found. Run "npm ci --prefix dashboard" first, or use the default bundled dashboard mode.'
@@ -9915,7 +10526,7 @@ async function dashboardCommand(options) {
9915
10526
  init_paths();
9916
10527
  init_fs();
9917
10528
  init_config2();
9918
- import { resolve as resolve22 } from "path";
10529
+ import { resolve as resolve23 } from "path";
9919
10530
  init_lifecycle();
9920
10531
  init_assignment_resolver();
9921
10532
  async function runTransition(assignment, command, options = {}) {
@@ -9928,8 +10539,8 @@ async function runTransition(assignment, command, options = {}) {
9928
10539
  if (!isValidSlug(assignment)) {
9929
10540
  throw new Error(`Invalid assignment slug "${assignment}".`);
9930
10541
  }
9931
- const projectDir = resolve22(baseDir, options.project);
9932
- const projectMdPath = resolve22(projectDir, "project.md");
10542
+ const projectDir = resolve23(baseDir, options.project);
10543
+ const projectMdPath = resolve23(projectDir, "project.md");
9933
10544
  if (!await fileExists(projectDir) || !await fileExists(projectMdPath)) {
9934
10545
  throw new Error(`Project "${options.project}" not found at ${projectDir}.`);
9935
10546
  }
@@ -9960,8 +10571,8 @@ async function runAssign(assignment, agent, options = {}) {
9960
10571
  if (!isValidSlug(assignment)) {
9961
10572
  throw new Error(`Invalid assignment slug "${assignment}".`);
9962
10573
  }
9963
- const projectDir = resolve22(baseDir, options.project);
9964
- const projectMdPath = resolve22(projectDir, "project.md");
10574
+ const projectDir = resolve23(baseDir, options.project);
10575
+ const projectMdPath = resolve23(projectDir, "project.md");
9965
10576
  if (!await fileExists(projectDir) || !await fileExists(projectMdPath)) {
9966
10577
  throw new Error(`Project "${options.project}" not found at ${projectDir}.`);
9967
10578
  }
@@ -10047,7 +10658,7 @@ import {
10047
10658
  readdir as readdir10,
10048
10659
  symlink,
10049
10660
  lstat,
10050
- readFile as readFile15,
10661
+ readFile as readFile16,
10051
10662
  readlink,
10052
10663
  rm as rm3,
10053
10664
  unlink as unlink5,
@@ -10055,20 +10666,20 @@ import {
10055
10666
  } from "fs/promises";
10056
10667
  import { existsSync } from "fs";
10057
10668
  import { homedir as homedir2 } from "os";
10058
- import { basename, dirname as dirname6, isAbsolute as isAbsolute2, relative as relative2, resolve as resolve24 } from "path";
10669
+ import { basename, dirname as dirname6, isAbsolute as isAbsolute2, relative as relative2, resolve as resolve25 } from "path";
10059
10670
 
10060
10671
  // src/utils/package-root.ts
10061
10672
  init_fs();
10062
- import { dirname as dirname5, resolve as resolve23 } from "path";
10673
+ import { dirname as dirname5, resolve as resolve24 } from "path";
10063
10674
  import { fileURLToPath as fileURLToPath3 } from "url";
10064
10675
  async function findPackageRoot(expectedRelativePath) {
10065
10676
  let currentDir = dirname5(fileURLToPath3(import.meta.url));
10066
10677
  while (true) {
10067
- const candidate = resolve23(currentDir, expectedRelativePath);
10678
+ const candidate = resolve24(currentDir, expectedRelativePath);
10068
10679
  if (await fileExists(candidate)) {
10069
10680
  return currentDir;
10070
10681
  }
10071
- const parentDir = resolve23(currentDir, "..");
10682
+ const parentDir = resolve24(currentDir, "..");
10072
10683
  if (parentDir === currentDir) {
10073
10684
  throw new Error(
10074
10685
  `Could not locate package root containing ${expectedRelativePath}.`
@@ -10089,25 +10700,25 @@ function getPluginManifestRelativePath(pluginKind) {
10089
10700
  }
10090
10701
  function getDefaultPluginTargetDir(pluginKind) {
10091
10702
  const home = homedir2();
10092
- return pluginKind === "claude" ? resolve24(home, ".claude", "plugins", "syntaur") : resolve24(home, "plugins", "syntaur");
10703
+ return pluginKind === "claude" ? resolve25(home, ".claude", "plugins", "syntaur") : resolve25(home, "plugins", "syntaur");
10093
10704
  }
10094
10705
  function getDefaultMarketplacePath() {
10095
- return resolve24(homedir2(), ".agents", "plugins", "marketplace.json");
10706
+ return resolve25(homedir2(), ".agents", "plugins", "marketplace.json");
10096
10707
  }
10097
10708
  function getClaudeMarketplacesRoot() {
10098
- return resolve24(homedir2(), ".claude", "plugins", "marketplaces");
10709
+ return resolve25(homedir2(), ".claude", "plugins", "marketplaces");
10099
10710
  }
10100
10711
  function getClaudeKnownMarketplacesPath() {
10101
- return resolve24(homedir2(), ".claude", "plugins", "known_marketplaces.json");
10712
+ return resolve25(homedir2(), ".claude", "plugins", "known_marketplaces.json");
10102
10713
  }
10103
10714
  function getClaudeInstalledPluginsPath() {
10104
- return resolve24(homedir2(), ".claude", "plugins", "installed_plugins.json");
10715
+ return resolve25(homedir2(), ".claude", "plugins", "installed_plugins.json");
10105
10716
  }
10106
10717
  function getInstallMarkerPath(targetDir) {
10107
- return resolve24(targetDir, INSTALL_MARKER_FILENAME);
10718
+ return resolve25(targetDir, INSTALL_MARKER_FILENAME);
10108
10719
  }
10109
10720
  async function readPackageManifest(packageRoot) {
10110
- const raw = await readFile15(resolve24(packageRoot, "package.json"), "utf-8");
10721
+ const raw = await readFile16(resolve25(packageRoot, "package.json"), "utf-8");
10111
10722
  return JSON.parse(raw);
10112
10723
  }
10113
10724
  async function readJsonFileIfExists(pathValue) {
@@ -10115,7 +10726,7 @@ async function readJsonFileIfExists(pathValue) {
10115
10726
  return null;
10116
10727
  }
10117
10728
  try {
10118
- const raw = await readFile15(pathValue, "utf-8");
10729
+ const raw = await readFile16(pathValue, "utf-8");
10119
10730
  return JSON.parse(raw);
10120
10731
  } catch {
10121
10732
  return null;
@@ -10123,15 +10734,15 @@ async function readJsonFileIfExists(pathValue) {
10123
10734
  }
10124
10735
  async function readClaudePluginManifest(pluginDir) {
10125
10736
  return await readJsonFileIfExists(
10126
- resolve24(pluginDir, ".claude-plugin", "plugin.json")
10737
+ resolve25(pluginDir, ".claude-plugin", "plugin.json")
10127
10738
  ) ?? {};
10128
10739
  }
10129
10740
  async function readPluginManifestName(targetDir, pluginKind) {
10130
- const manifestPath = resolve24(targetDir, getPluginManifestRelativePath(pluginKind));
10741
+ const manifestPath = resolve25(targetDir, getPluginManifestRelativePath(pluginKind));
10131
10742
  if (!await fileExists(manifestPath)) {
10132
10743
  return void 0;
10133
10744
  }
10134
- const raw = await readFile15(manifestPath, "utf-8");
10745
+ const raw = await readFile16(manifestPath, "utf-8");
10135
10746
  const parsed = JSON.parse(raw);
10136
10747
  return parsed.name;
10137
10748
  }
@@ -10141,7 +10752,7 @@ async function readInstallMetadata(targetDir) {
10141
10752
  return null;
10142
10753
  }
10143
10754
  try {
10144
- const raw = await readFile15(markerPath, "utf-8");
10755
+ const raw = await readFile16(markerPath, "utf-8");
10145
10756
  return JSON.parse(raw);
10146
10757
  } catch {
10147
10758
  return null;
@@ -10154,7 +10765,7 @@ async function getInstallStatus(targetDir, pluginKind) {
10154
10765
  const info = await lstat(targetDir);
10155
10766
  if (info.isSymbolicLink()) {
10156
10767
  const symlinkTarget = await readlink(targetDir);
10157
- const resolvedTarget = resolve24(dirname6(targetDir), symlinkTarget);
10768
+ const resolvedTarget = resolve25(dirname6(targetDir), symlinkTarget);
10158
10769
  const manifestName2 = await readPluginManifestName(resolvedTarget, pluginKind);
10159
10770
  return {
10160
10771
  exists: true,
@@ -10200,7 +10811,7 @@ async function installLink(paths) {
10200
10811
  await ensureDir(dirname6(paths.targetDir));
10201
10812
  await rm3(paths.targetDir, { recursive: true, force: true });
10202
10813
  await ensureDir(dirname6(paths.targetDir));
10203
- await symlink(resolve24(paths.sourceDir), paths.targetDir, "dir");
10814
+ await symlink(resolve25(paths.sourceDir), paths.targetDir, "dir");
10204
10815
  }
10205
10816
  async function removeInstallMarker(targetDir) {
10206
10817
  const markerPath = getInstallMarkerPath(targetDir);
@@ -10214,13 +10825,13 @@ function normalizeAbsoluteInstallPath(pathValue, label) {
10214
10825
  if (!isAbsolute2(expanded)) {
10215
10826
  throw new Error(`${label} must be an absolute path.`);
10216
10827
  }
10217
- return resolve24(expanded);
10828
+ return resolve25(expanded);
10218
10829
  }
10219
10830
  async function resolvePluginPaths(pluginKind, targetDir) {
10220
10831
  const packageRoot = await findPackageRoot(getPluginRelativePath(pluginKind));
10221
10832
  return {
10222
10833
  packageRoot,
10223
- sourceDir: resolve24(packageRoot, getPluginRelativePath(pluginKind)),
10834
+ sourceDir: resolve25(packageRoot, getPluginRelativePath(pluginKind)),
10224
10835
  targetDir: targetDir ?? getDefaultPluginTargetDir(pluginKind)
10225
10836
  };
10226
10837
  }
@@ -10324,8 +10935,8 @@ async function listClaudeMarketplaceCandidates() {
10324
10935
  if (!entry.isDirectory()) {
10325
10936
  continue;
10326
10937
  }
10327
- const candidateRoot = resolve24(rootDir, entry.name);
10328
- const manifestPath = resolve24(candidateRoot, ".claude-plugin", "marketplace.json");
10938
+ const candidateRoot = resolve25(rootDir, entry.name);
10939
+ const manifestPath = resolve25(candidateRoot, ".claude-plugin", "marketplace.json");
10329
10940
  if (!await fileExists(manifestPath)) {
10330
10941
  continue;
10331
10942
  }
@@ -10352,11 +10963,11 @@ async function listClaudeMarketplaceCandidates() {
10352
10963
  if (!installLocation) {
10353
10964
  continue;
10354
10965
  }
10355
- const candidateRoot = resolve24(expandHome(installLocation));
10966
+ const candidateRoot = resolve25(expandHome(installLocation));
10356
10967
  if (seen.has(candidateRoot)) {
10357
10968
  continue;
10358
10969
  }
10359
- const manifestPath = resolve24(candidateRoot, ".claude-plugin", "marketplace.json");
10970
+ const manifestPath = resolve25(candidateRoot, ".claude-plugin", "marketplace.json");
10360
10971
  if (!await fileExists(manifestPath)) {
10361
10972
  continue;
10362
10973
  }
@@ -10384,7 +10995,7 @@ async function getPreferredClaudeMarketplace() {
10384
10995
  name: candidate.name,
10385
10996
  rootDir: candidate.rootDir,
10386
10997
  manifestPath: candidate.manifestPath,
10387
- targetDir: resolve24(candidate.rootDir, "plugins", "syntaur")
10998
+ targetDir: resolve25(candidate.rootDir, "plugins", "syntaur")
10388
10999
  };
10389
11000
  }
10390
11001
  async function registerKnownClaudeMarketplace(name, rootDir) {
@@ -10411,9 +11022,9 @@ async function ensureClaudeUserMarketplace() {
10411
11022
  if (existing) {
10412
11023
  return existing;
10413
11024
  }
10414
- const rootDir = resolve24(getClaudeMarketplacesRoot(), "user-plugins");
10415
- const manifestPath = resolve24(rootDir, ".claude-plugin", "marketplace.json");
10416
- await ensureDir(resolve24(rootDir, "plugins"));
11025
+ const rootDir = resolve25(getClaudeMarketplacesRoot(), "user-plugins");
11026
+ const manifestPath = resolve25(rootDir, ".claude-plugin", "marketplace.json");
11027
+ await ensureDir(resolve25(rootDir, "plugins"));
10417
11028
  if (!await fileExists(manifestPath)) {
10418
11029
  const scaffold = {
10419
11030
  plugins: []
@@ -10432,7 +11043,7 @@ async function ensureClaudeUserMarketplace() {
10432
11043
  name: "user-plugins",
10433
11044
  rootDir,
10434
11045
  manifestPath,
10435
- targetDir: resolve24(rootDir, "plugins", "syntaur")
11046
+ targetDir: resolve25(rootDir, "plugins", "syntaur")
10436
11047
  };
10437
11048
  }
10438
11049
  async function detectClaudeMarketplaceForTarget(targetDir) {
@@ -10442,7 +11053,7 @@ async function detectClaudeMarketplaceForTarget(targetDir) {
10442
11053
  return null;
10443
11054
  }
10444
11055
  const rootDir = dirname6(pluginsDir);
10445
- const manifestPath = resolve24(rootDir, ".claude-plugin", "marketplace.json");
11056
+ const manifestPath = resolve25(rootDir, ".claude-plugin", "marketplace.json");
10446
11057
  if (!await fileExists(manifestPath)) {
10447
11058
  return null;
10448
11059
  }
@@ -10458,7 +11069,7 @@ async function detectClaudeMarketplaceForTarget(targetDir) {
10458
11069
  async function findManagedClaudeMarketplacePluginDir() {
10459
11070
  const marketplaces = await listClaudeMarketplaceCandidates();
10460
11071
  for (const marketplace of marketplaces) {
10461
- const targetDir = resolve24(marketplace.rootDir, "plugins", "syntaur");
11072
+ const targetDir = resolve25(marketplace.rootDir, "plugins", "syntaur");
10462
11073
  const status = await getInstallStatus(targetDir, "claude");
10463
11074
  if (status.exists && status.managed) {
10464
11075
  return targetDir;
@@ -10583,7 +11194,7 @@ async function installManagedPlugin(options) {
10583
11194
  `${paths.targetDir} already exists and is not a Syntaur-managed install. Remove it manually before installing Syntaur there.`
10584
11195
  );
10585
11196
  }
10586
- if (desiredMode === "link" && existing.exists && existing.installMode === "link" && existing.symlinkTarget === resolve24(paths.sourceDir) && !force) {
11197
+ if (desiredMode === "link" && existing.exists && existing.installMode === "link" && existing.symlinkTarget === resolve25(paths.sourceDir) && !force) {
10587
11198
  return {
10588
11199
  targetDir: paths.targetDir,
10589
11200
  sourceDir: paths.sourceDir,
@@ -10635,7 +11246,7 @@ async function readMarketplaceFile(marketplacePath) {
10635
11246
  plugins: []
10636
11247
  };
10637
11248
  }
10638
- const raw = await readFile15(marketplacePath, "utf-8");
11249
+ const raw = await readFile16(marketplacePath, "utf-8");
10639
11250
  const parsed = JSON.parse(raw);
10640
11251
  return {
10641
11252
  name: parsed.name ?? "local",
@@ -10796,13 +11407,13 @@ async function recommendMarketplacePath() {
10796
11407
  return configuredOrManaged ?? getDefaultMarketplacePath();
10797
11408
  }
10798
11409
  async function isSyntaurDataInstalled() {
10799
- return fileExists(resolve24(syntaurRoot(), "config.md"));
11410
+ return fileExists(resolve25(syntaurRoot(), "config.md"));
10800
11411
  }
10801
11412
  async function removeSyntaurData() {
10802
11413
  await rm3(syntaurRoot(), { recursive: true, force: true });
10803
11414
  }
10804
11415
  async function getConfiguredProjectDir() {
10805
- if (!await fileExists(resolve24(syntaurRoot(), "config.md"))) {
11416
+ if (!await fileExists(resolve25(syntaurRoot(), "config.md"))) {
10806
11417
  return null;
10807
11418
  }
10808
11419
  return (await readConfig()).defaultProjectDir;
@@ -10871,8 +11482,8 @@ async function textPrompt(question, defaultValue) {
10871
11482
 
10872
11483
  // src/utils/install-skills.ts
10873
11484
  init_fs();
10874
- import { readFile as readFile16, readdir as readdir11, mkdir as mkdir2, copyFile, rm as rm4 } from "fs/promises";
10875
- import { dirname as dirname7, resolve as resolve25, relative as relative3, join as join3 } from "path";
11485
+ import { readFile as readFile17, readdir as readdir11, mkdir as mkdir3, copyFile, rm as rm4 } from "fs/promises";
11486
+ import { dirname as dirname7, resolve as resolve26, relative as relative3, join as join3 } from "path";
10876
11487
  import { fileURLToPath as fileURLToPath4 } from "url";
10877
11488
  import { homedir as homedir3 } from "os";
10878
11489
  var REQUIRED_SKILLS = [
@@ -10885,11 +11496,11 @@ var REQUIRED_SKILLS = [
10885
11496
  ];
10886
11497
  function getVendoredSkillsDir() {
10887
11498
  const here = dirname7(fileURLToPath4(import.meta.url));
10888
- return resolve25(here, "..", "vendor", "syntaur-skills", "skills");
11499
+ return resolve26(here, "..", "vendor", "syntaur-skills", "skills");
10889
11500
  }
10890
11501
  function defaultSkillTargetDir(target) {
10891
- if (target === "claude") return resolve25(homedir3(), ".claude", "skills");
10892
- return resolve25(homedir3(), ".codex", "skills");
11502
+ if (target === "claude") return resolve26(homedir3(), ".claude", "skills");
11503
+ return resolve26(homedir3(), ".codex", "skills");
10893
11504
  }
10894
11505
  async function walkFiles(root) {
10895
11506
  const out = [];
@@ -10909,7 +11520,7 @@ async function walkFiles(root) {
10909
11520
  }
10910
11521
  async function filesEqual(a, b) {
10911
11522
  try {
10912
- const [ba, bb] = await Promise.all([readFile16(a), readFile16(b)]);
11523
+ const [ba, bb] = await Promise.all([readFile17(a), readFile17(b)]);
10913
11524
  if (ba.length !== bb.length) return false;
10914
11525
  return ba.equals(bb);
10915
11526
  } catch {
@@ -10917,7 +11528,7 @@ async function filesEqual(a, b) {
10917
11528
  }
10918
11529
  }
10919
11530
  async function copyDir(srcDir, destDir) {
10920
- await mkdir2(destDir, { recursive: true });
11531
+ await mkdir3(destDir, { recursive: true });
10921
11532
  const entries = await readdir11(srcDir, { withFileTypes: true });
10922
11533
  for (const entry of entries) {
10923
11534
  const src = join3(srcDir, entry.name);
@@ -10951,7 +11562,7 @@ async function installSkills(options) {
10951
11562
  );
10952
11563
  }
10953
11564
  const results = [];
10954
- await mkdir2(targetRoot, { recursive: true });
11565
+ await mkdir3(targetRoot, { recursive: true });
10955
11566
  for (const skill of REQUIRED_SKILLS) {
10956
11567
  const srcDir = join3(source, skill);
10957
11568
  const destDir = join3(targetRoot, skill);
@@ -11000,7 +11611,7 @@ async function uninstallSkills(options) {
11000
11611
  if (!await fileExists(destDir)) continue;
11001
11612
  const skillMd = join3(destDir, "SKILL.md");
11002
11613
  if (!await fileExists(skillMd)) continue;
11003
- const content = await readFile16(skillMd, "utf-8").catch(() => "");
11614
+ const content = await readFile17(skillMd, "utf-8").catch(() => "");
11004
11615
  const match = content.match(/^name:\s*(\S+)\s*$/m);
11005
11616
  if (!match || match[1] !== skill) continue;
11006
11617
  await rm4(destDir, { recursive: true, force: true });
@@ -11137,16 +11748,16 @@ async function installPluginCommand(options) {
11137
11748
  // src/commands/install-statusline.ts
11138
11749
  init_paths();
11139
11750
  init_fs();
11140
- import { readFile as readFile18, writeFile as writeFile8, copyFile as copyFile2, rm as rm5, stat as stat3, symlink as symlink2, unlink as unlink6, lstat as lstat2 } from "fs/promises";
11141
- import { resolve as resolve27, dirname as dirname9 } from "path";
11751
+ 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";
11752
+ import { resolve as resolve28, dirname as dirname9 } from "path";
11142
11753
  import { homedir as homedir4 } from "os";
11143
11754
  import { fileURLToPath as fileURLToPath5 } from "url";
11144
11755
 
11145
11756
  // src/commands/configure-statusline.ts
11146
11757
  init_paths();
11147
11758
  init_fs();
11148
- import { readFile as readFile17, writeFile as writeFile7 } from "fs/promises";
11149
- import { resolve as resolve26, dirname as dirname8 } from "path";
11759
+ import { readFile as readFile18, writeFile as writeFile7 } from "fs/promises";
11760
+ import { resolve as resolve27, dirname as dirname8 } from "path";
11150
11761
  import { spawnSync } from "child_process";
11151
11762
  import { checkbox, input as input2, confirm } from "@inquirer/prompts";
11152
11763
  var AVAILABLE_SEGMENTS = [
@@ -11167,12 +11778,12 @@ var PRESETS = {
11167
11778
  tracker: { segments: ["git", "assignment", "external", "session"], separator: " \xB7 " }
11168
11779
  };
11169
11780
  function getConfigPath(installRoot) {
11170
- return resolve26(installRoot, "statusline.config.json");
11781
+ return resolve27(installRoot, "statusline.config.json");
11171
11782
  }
11172
11783
  async function readConfig2(path) {
11173
11784
  if (!await fileExists(path)) return null;
11174
11785
  try {
11175
- const raw = await readFile17(path, "utf-8");
11786
+ const raw = await readFile18(path, "utf-8");
11176
11787
  const parsed = JSON.parse(raw);
11177
11788
  if (!parsed || typeof parsed !== "object") return null;
11178
11789
  const segments = Array.isArray(parsed.segments) ? parsed.segments.filter(isSegmentName) : [];
@@ -11325,7 +11936,7 @@ async function configureStatuslineCommand(options = {}) {
11325
11936
  console.log(` segments: ${config.segments.join(", ")}`);
11326
11937
  console.log(` separator: ${JSON.stringify(config.separator)}`);
11327
11938
  if (config.wrap) console.log(` wrap: ${config.wrap}`);
11328
- const script = options.statuslineScript ?? resolve26(installRoot, "statusline.sh");
11939
+ const script = options.statuslineScript ?? resolve27(installRoot, "statusline.sh");
11329
11940
  if (await fileExists(script)) {
11330
11941
  console.log("");
11331
11942
  console.log("Live preview:");
@@ -11356,11 +11967,11 @@ async function writeDefaultConfigIfMissing(installRoot) {
11356
11967
  // src/commands/install-statusline.ts
11357
11968
  function getPackageStatuslineSource() {
11358
11969
  const here = dirname9(fileURLToPath5(import.meta.url));
11359
- return resolve27(here, "..", "statusline", "statusline.sh");
11970
+ return resolve28(here, "..", "statusline", "statusline.sh");
11360
11971
  }
11361
11972
  async function readSettingsJson(settingsPath) {
11362
11973
  if (!await fileExists(settingsPath)) return {};
11363
- const raw = await readFile18(settingsPath, "utf-8");
11974
+ const raw = await readFile19(settingsPath, "utf-8");
11364
11975
  if (raw.trim() === "") return {};
11365
11976
  try {
11366
11977
  const parsed = JSON.parse(raw);
@@ -11440,12 +12051,12 @@ async function installScript(sourceScript, destScript, link) {
11440
12051
  }
11441
12052
  async function installStatuslineCommand(options = {}) {
11442
12053
  const mode = options.mode ?? "ask";
11443
- const settingsPath = options.settingsPath ?? resolve27(homedir4(), ".claude", "settings.json");
12054
+ const settingsPath = options.settingsPath ?? resolve28(homedir4(), ".claude", "settings.json");
11444
12055
  const installRoot = options.installRoot ?? syntaurRoot();
11445
12056
  const sourceScript = options.sourceScript ?? getPackageStatuslineSource();
11446
- const destScript = resolve27(installRoot, "statusline.sh");
11447
- const confPath = resolve27(installRoot, "statusline.conf");
11448
- const backupPath = resolve27(installRoot, "statusline.backup.json");
12057
+ const destScript = resolve28(installRoot, "statusline.sh");
12058
+ const confPath = resolve28(installRoot, "statusline.conf");
12059
+ const backupPath = resolve28(installRoot, "statusline.backup.json");
11449
12060
  if (!await fileExists(sourceScript)) {
11450
12061
  throw new Error(
11451
12062
  `Statusline source script not found at ${sourceScript}. Try re-installing syntaur (npm install -g syntaur) or pass --source-script explicitly.`
@@ -11481,7 +12092,7 @@ async function installStatuslineCommand(options = {}) {
11481
12092
  if (parsed) {
11482
12093
  wrapTarget = parsed;
11483
12094
  } else {
11484
- const wrapperPath = resolve27(installRoot, "statusline-wrapped.sh");
12095
+ const wrapperPath = resolve28(installRoot, "statusline-wrapped.sh");
11485
12096
  const wrapperBody = `#!/usr/bin/env bash
11486
12097
  # Auto-generated by syntaur install-statusline.
11487
12098
  # Executes the previously configured statusLine command.
@@ -11536,19 +12147,19 @@ async function chmodExec(path) {
11536
12147
  }
11537
12148
  }
11538
12149
  async function uninstallStatuslineCommand(options = {}) {
11539
- const settingsPath = options.settingsPath ?? resolve27(homedir4(), ".claude", "settings.json");
12150
+ const settingsPath = options.settingsPath ?? resolve28(homedir4(), ".claude", "settings.json");
11540
12151
  const installRoot = options.installRoot ?? syntaurRoot();
11541
- const destScript = resolve27(installRoot, "statusline.sh");
11542
- const confPath = resolve27(installRoot, "statusline.conf");
11543
- const backupPath = resolve27(installRoot, "statusline.backup.json");
11544
- const wrapperPath = resolve27(installRoot, "statusline-wrapped.sh");
12152
+ const destScript = resolve28(installRoot, "statusline.sh");
12153
+ const confPath = resolve28(installRoot, "statusline.conf");
12154
+ const backupPath = resolve28(installRoot, "statusline.backup.json");
12155
+ const wrapperPath = resolve28(installRoot, "statusline-wrapped.sh");
11545
12156
  const settings = await readSettingsJson(settingsPath);
11546
12157
  const existing = extractExistingCommand(settings);
11547
12158
  const ourCommand = `bash ${destScript}`;
11548
12159
  let restored = null;
11549
12160
  if (await fileExists(backupPath)) {
11550
12161
  try {
11551
- const raw = await readFile18(backupPath, "utf-8");
12162
+ const raw = await readFile19(backupPath, "utf-8");
11552
12163
  const parsed = JSON.parse(raw);
11553
12164
  const prev = parsed?.previousStatusLine;
11554
12165
  if (prev && typeof prev === "object" && typeof prev.command === "string") {
@@ -11569,7 +12180,7 @@ async function uninstallStatuslineCommand(options = {}) {
11569
12180
  await writeSettingsJson(settingsPath, settings);
11570
12181
  }
11571
12182
  if (!options.keepScript) {
11572
- const configPath = resolve27(installRoot, "statusline.config.json");
12183
+ const configPath = resolve28(installRoot, "statusline.config.json");
11573
12184
  for (const path of [destScript, confPath, backupPath, wrapperPath, configPath]) {
11574
12185
  try {
11575
12186
  await rm5(path, { force: true });
@@ -11824,7 +12435,7 @@ async function setupCommand(options) {
11824
12435
  }
11825
12436
 
11826
12437
  // src/commands/uninstall.ts
11827
- import { resolve as resolve28 } from "path";
12438
+ import { resolve as resolve29 } from "path";
11828
12439
  init_paths();
11829
12440
  function expandTargets(options) {
11830
12441
  if (options.all) {
@@ -11904,7 +12515,7 @@ async function uninstallCommand(options) {
11904
12515
  const configuredProjectDir = await getConfiguredProjectDir();
11905
12516
  await removeSyntaurData();
11906
12517
  console.log(`Removed ${syntaurRoot()}`);
11907
- if (configuredProjectDir && resolve28(configuredProjectDir) !== resolve28(syntaurRoot(), "projects")) {
12518
+ if (configuredProjectDir && resolve29(configuredProjectDir) !== resolve29(syntaurRoot(), "projects")) {
11908
12519
  console.warn(
11909
12520
  `Warning: config.md pointed to an external project directory (${configuredProjectDir}). That directory was not removed automatically.`
11910
12521
  );
@@ -11919,7 +12530,7 @@ async function uninstallCommand(options) {
11919
12530
  init_paths();
11920
12531
  init_fs();
11921
12532
  init_config2();
11922
- import { resolve as resolve29 } from "path";
12533
+ import { resolve as resolve30 } from "path";
11923
12534
  var SUPPORTED_FRAMEWORKS = ["cursor", "codex", "opencode"];
11924
12535
  async function setupAdapterCommand(framework, options) {
11925
12536
  if (!SUPPORTED_FRAMEWORKS.includes(framework)) {
@@ -11945,19 +12556,19 @@ async function setupAdapterCommand(framework, options) {
11945
12556
  }
11946
12557
  const config = await readConfig();
11947
12558
  const baseDir = options.dir ? expandHome(options.dir) : config.defaultProjectDir;
11948
- const projectDir = resolve29(baseDir, options.project);
11949
- const assignmentDir = resolve29(
12559
+ const projectDir = resolve30(baseDir, options.project);
12560
+ const assignmentDir = resolve30(
11950
12561
  projectDir,
11951
12562
  "assignments",
11952
12563
  options.assignment
11953
12564
  );
11954
- const projectMdPath = resolve29(projectDir, "project.md");
12565
+ const projectMdPath = resolve30(projectDir, "project.md");
11955
12566
  if (!await fileExists(projectDir) || !await fileExists(projectMdPath)) {
11956
12567
  throw new Error(
11957
12568
  `Project "${options.project}" not found at ${projectDir}.`
11958
12569
  );
11959
12570
  }
11960
- const assignmentMdPath = resolve29(assignmentDir, "assignment.md");
12571
+ const assignmentMdPath = resolve30(assignmentDir, "assignment.md");
11961
12572
  if (!await fileExists(assignmentDir) || !await fileExists(assignmentMdPath)) {
11962
12573
  throw new Error(
11963
12574
  `Assignment "${options.assignment}" not found at ${assignmentDir}.`
@@ -11985,15 +12596,15 @@ async function setupAdapterCommand(framework, options) {
11985
12596
  }
11986
12597
  }
11987
12598
  if (framework === "cursor") {
11988
- const protocolPath = resolve29(cwd, ".cursor", "rules", "syntaur-protocol.mdc");
11989
- const assignmentPath = resolve29(cwd, ".cursor", "rules", "syntaur-assignment.mdc");
12599
+ const protocolPath = resolve30(cwd, ".cursor", "rules", "syntaur-protocol.mdc");
12600
+ const assignmentPath = resolve30(cwd, ".cursor", "rules", "syntaur-assignment.mdc");
11990
12601
  await writeAdapterFile(protocolPath, renderCursorProtocol());
11991
12602
  await writeAdapterFile(assignmentPath, renderCursorAssignment(rendererParams));
11992
12603
  } else if (framework === "codex" || framework === "opencode") {
11993
- const agentsPath = resolve29(cwd, "AGENTS.md");
12604
+ const agentsPath = resolve30(cwd, "AGENTS.md");
11994
12605
  await writeAdapterFile(agentsPath, renderCodexAgents(rendererParams));
11995
12606
  if (framework === "opencode") {
11996
- const configPath = resolve29(cwd, "opencode.json");
12607
+ const configPath = resolve30(cwd, "opencode.json");
11997
12608
  await writeAdapterFile(configPath, renderOpenCodeConfig({ projectDir }));
11998
12609
  }
11999
12610
  }
@@ -12018,7 +12629,7 @@ async function setupAdapterCommand(framework, options) {
12018
12629
  init_paths();
12019
12630
  init_fs();
12020
12631
  init_config2();
12021
- import { resolve as resolve30 } from "path";
12632
+ import { resolve as resolve31 } from "path";
12022
12633
  async function trackSessionCommand(options) {
12023
12634
  if (!options.agent) {
12024
12635
  throw new Error("--agent <name> is required.");
@@ -12031,7 +12642,7 @@ async function trackSessionCommand(options) {
12031
12642
  if (options.project) {
12032
12643
  const config = await readConfig();
12033
12644
  const baseDir = options.dir ? expandHome(options.dir) : config.defaultProjectDir;
12034
- const projectDir = resolve30(baseDir, options.project);
12645
+ const projectDir = resolve31(baseDir, options.project);
12035
12646
  if (!await fileExists(projectDir)) {
12036
12647
  throw new Error(
12037
12648
  `Project "${options.project}" not found at ${projectDir}.`
@@ -12086,7 +12697,7 @@ async function browseCommand(options) {
12086
12697
  }
12087
12698
 
12088
12699
  // src/commands/create-playbook.ts
12089
- import { resolve as resolve32 } from "path";
12700
+ import { resolve as resolve33 } from "path";
12090
12701
  init_timestamp();
12091
12702
  init_paths();
12092
12703
  init_fs();
@@ -12103,7 +12714,7 @@ async function createPlaybookCommand(name, options) {
12103
12714
  }
12104
12715
  const dir = playbooksDir();
12105
12716
  await ensureDir(dir);
12106
- const filePath = resolve32(dir, `${slug}.md`);
12717
+ const filePath = resolve33(dir, `${slug}.md`);
12107
12718
  if (await fileExists(filePath)) {
12108
12719
  throw new Error(
12109
12720
  `Playbook "${slug}" already exists at ${filePath}
@@ -12125,8 +12736,8 @@ init_paths();
12125
12736
  init_fs();
12126
12737
  init_parser();
12127
12738
  init_config2();
12128
- import { readdir as readdir12, readFile as readFile19 } from "fs/promises";
12129
- import { resolve as resolve33 } from "path";
12739
+ import { readdir as readdir12, readFile as readFile20 } from "fs/promises";
12740
+ import { resolve as resolve34 } from "path";
12130
12741
  async function listPlaybooksCommand(options = {}) {
12131
12742
  const dir = playbooksDir();
12132
12743
  if (!await fileExists(dir)) {
@@ -12141,8 +12752,8 @@ async function listPlaybooksCommand(options = {}) {
12141
12752
  );
12142
12753
  const rows = [];
12143
12754
  for (const entry of mdFiles) {
12144
- const filePath = resolve33(dir, entry.name);
12145
- const raw = await readFile19(filePath, "utf-8");
12755
+ const filePath = resolve34(dir, entry.name);
12756
+ const raw = await readFile20(filePath, "utf-8");
12146
12757
  const parsed = parsePlaybook(raw);
12147
12758
  const slug = parsed.slug || entry.name.replace(/\.md$/, "");
12148
12759
  const disabled = disabledSet.has(slug);
@@ -12222,28 +12833,44 @@ async function disablePlaybookCommand(slug) {
12222
12833
  init_paths();
12223
12834
  init_parser2();
12224
12835
  init_fs();
12836
+ init_config2();
12225
12837
  import { Command } from "commander";
12226
- import { readFile as readFile20 } from "fs/promises";
12227
- import { resolve as resolve34 } from "path";
12838
+ import { readFile as readFile21 } from "fs/promises";
12839
+ import { resolve as resolve35 } from "path";
12228
12840
  var WORKSPACE_REGEX2 = /^[a-z0-9_][a-z0-9-]*$/;
12229
- function resolveWorkspace(options) {
12230
- if (options.global) return "_global";
12841
+ async function resolveScope(options) {
12842
+ const flagCount = [Boolean(options.project), Boolean(options.workspace), Boolean(options.global)].filter(Boolean).length;
12843
+ if (flagCount > 1) {
12844
+ throw new Error("Use at most one of --project, --workspace, --global.");
12845
+ }
12846
+ if (options.project) {
12847
+ if (!isValidSlug(options.project)) {
12848
+ throw new Error(`Invalid project slug: "${options.project}".`);
12849
+ }
12850
+ const config = await readConfig();
12851
+ const projectMd = resolve35(config.defaultProjectDir, options.project, "project.md");
12852
+ if (!await fileExists(projectMd)) {
12853
+ throw new Error(`Project "${options.project}" not found.`);
12854
+ }
12855
+ return { kind: "project", id: options.project, todosPath: projectTodosDir(config.defaultProjectDir, options.project) };
12856
+ }
12231
12857
  if (options.workspace) {
12232
12858
  if (!WORKSPACE_REGEX2.test(options.workspace)) {
12233
12859
  throw new Error(`Invalid workspace name: "${options.workspace}". Use lowercase letters, numbers, hyphens, and underscores.`);
12234
12860
  }
12235
- return options.workspace;
12861
+ return { kind: "workspace", id: options.workspace, todosPath: todosDir() };
12236
12862
  }
12237
- return "_global";
12863
+ return { kind: "workspace", id: "_global", todosPath: todosDir() };
12238
12864
  }
12239
12865
  function nowISO() {
12240
12866
  return (/* @__PURE__ */ new Date()).toISOString();
12241
12867
  }
12242
12868
  var todoCommand = new Command("todo").description("Manage quick todos");
12243
- todoCommand.command("add").description("Add a new todo item").argument("<description>", "Todo description").option("--tags <tags>", "Comma-separated tags").option("--workspace <slug>", "Workspace slug").option("--global", "Use global todos").action(async (description, options) => {
12869
+ todoCommand.command("add").description("Add a new todo item").argument("<description>", "Todo description").option("--tags <tags>", "Comma-separated tags").option("--workspace <slug>", "Workspace slug").option("--project <slug>", "Project slug (mutually exclusive with --workspace/--global)").option("--global", "Use global todos").action(async (description, options) => {
12244
12870
  try {
12245
- const todosPath = todosDir();
12246
- const workspace = resolveWorkspace(options);
12871
+ const scope = await resolveScope(options);
12872
+ const todosPath = scope.todosPath;
12873
+ const workspace = scope.id;
12247
12874
  const checklist = await readChecklist(todosPath, workspace);
12248
12875
  const existingIds = new Set(checklist.items.map((i) => i.id));
12249
12876
  const id = generateUniqueId(existingIds);
@@ -12257,10 +12884,11 @@ todoCommand.command("add").description("Add a new todo item").argument("<descrip
12257
12884
  process.exit(1);
12258
12885
  }
12259
12886
  });
12260
- todoCommand.command("list").description("List todo items").option("--tag <tag>", "Filter by tag").option("--status <status>", "Filter by status (open|in_progress|completed|blocked)").option("--workspace <slug>", "Workspace slug").option("--global", "Use global todos").action(async (options) => {
12887
+ todoCommand.command("list").description("List todo items").option("--tag <tag>", "Filter by tag").option("--status <status>", "Filter by status (open|in_progress|completed|blocked)").option("--workspace <slug>", "Workspace slug").option("--project <slug>", "Project slug (mutually exclusive with --workspace/--global)").option("--global", "Use global todos").action(async (options) => {
12261
12888
  try {
12262
- const todosPath = todosDir();
12263
- const workspace = resolveWorkspace(options);
12889
+ const scope = await resolveScope(options);
12890
+ const todosPath = scope.todosPath;
12891
+ const workspace = scope.id;
12264
12892
  const checklist = await readChecklist(todosPath, workspace);
12265
12893
  let items = checklist.items;
12266
12894
  if (options.tag) {
@@ -12295,10 +12923,11 @@ ${counts.total} items: ${counts.open} open, ${counts.in_progress} active, ${coun
12295
12923
  function findItem(items, id) {
12296
12924
  return items.find((i) => i.id === id);
12297
12925
  }
12298
- todoCommand.command("start").description("Mark a todo as in-progress").argument("<id>", "Todo short ID (e.g. a3f1)").option("--session <session>", "Session ID").option("--workspace <slug>", "Workspace slug").option("--global", "Use global todos").action(async (id, options) => {
12926
+ todoCommand.command("start").description("Mark a todo as in-progress").argument("<id>", "Todo short ID (e.g. a3f1)").option("--session <session>", "Session ID").option("--workspace <slug>", "Workspace slug").option("--project <slug>", "Project slug (mutually exclusive with --workspace/--global)").option("--global", "Use global todos").action(async (id, options) => {
12299
12927
  try {
12300
- const todosPath = todosDir();
12301
- const workspace = resolveWorkspace(options);
12928
+ const scope = await resolveScope(options);
12929
+ const todosPath = scope.todosPath;
12930
+ const workspace = scope.id;
12302
12931
  const checklist = await readChecklist(todosPath, workspace);
12303
12932
  const item = findItem(checklist.items, id);
12304
12933
  if (!item) {
@@ -12318,10 +12947,11 @@ todoCommand.command("start").description("Mark a todo as in-progress").argument(
12318
12947
  process.exit(1);
12319
12948
  }
12320
12949
  });
12321
- todoCommand.command("complete").description("Mark a todo as completed").argument("<id>", "Todo short ID").option("--summary <summary>", "Completion summary").option("--branch <branch>", "Git branch name").option("--session <session>", "Session ID").option("--workspace <slug>", "Workspace slug").option("--global", "Use global todos").action(async (id, options) => {
12950
+ todoCommand.command("complete").description("Mark a todo as completed").argument("<id>", "Todo short ID").option("--summary <summary>", "Completion summary").option("--branch <branch>", "Git branch name").option("--session <session>", "Session ID").option("--workspace <slug>", "Workspace slug").option("--project <slug>", "Project slug (mutually exclusive with --workspace/--global)").option("--global", "Use global todos").action(async (id, options) => {
12322
12951
  try {
12323
- const todosPath = todosDir();
12324
- const workspace = resolveWorkspace(options);
12952
+ const scope = await resolveScope(options);
12953
+ const todosPath = scope.todosPath;
12954
+ const workspace = scope.id;
12325
12955
  const checklist = await readChecklist(todosPath, workspace);
12326
12956
  const item = findItem(checklist.items, id);
12327
12957
  if (!item) {
@@ -12348,10 +12978,11 @@ todoCommand.command("complete").description("Mark a todo as completed").argument
12348
12978
  process.exit(1);
12349
12979
  }
12350
12980
  });
12351
- todoCommand.command("block").description("Mark a todo as blocked").argument("<id>", "Todo short ID").requiredOption("--reason <reason>", "Blocking reason").option("--session <session>", "Session ID").option("--workspace <slug>", "Workspace slug").option("--global", "Use global todos").action(async (id, options) => {
12981
+ todoCommand.command("block").description("Mark a todo as blocked").argument("<id>", "Todo short ID").requiredOption("--reason <reason>", "Blocking reason").option("--session <session>", "Session ID").option("--workspace <slug>", "Workspace slug").option("--project <slug>", "Project slug (mutually exclusive with --workspace/--global)").option("--global", "Use global todos").action(async (id, options) => {
12352
12982
  try {
12353
- const todosPath = todosDir();
12354
- const workspace = resolveWorkspace(options);
12983
+ const scope = await resolveScope(options);
12984
+ const todosPath = scope.todosPath;
12985
+ const workspace = scope.id;
12355
12986
  const checklist = await readChecklist(todosPath, workspace);
12356
12987
  const item = findItem(checklist.items, id);
12357
12988
  if (!item) {
@@ -12378,10 +13009,11 @@ todoCommand.command("block").description("Mark a todo as blocked").argument("<id
12378
13009
  process.exit(1);
12379
13010
  }
12380
13011
  });
12381
- todoCommand.command("unblock").description("Return a blocked todo to open").argument("<id>", "Todo short ID").option("--workspace <slug>", "Workspace slug").option("--global", "Use global todos").action(async (id, options) => {
13012
+ todoCommand.command("unblock").description("Return a blocked todo to open").argument("<id>", "Todo short ID").option("--workspace <slug>", "Workspace slug").option("--project <slug>", "Project slug (mutually exclusive with --workspace/--global)").option("--global", "Use global todos").action(async (id, options) => {
12382
13013
  try {
12383
- const todosPath = todosDir();
12384
- const workspace = resolveWorkspace(options);
13014
+ const scope = await resolveScope(options);
13015
+ const todosPath = scope.todosPath;
13016
+ const workspace = scope.id;
12385
13017
  const checklist = await readChecklist(todosPath, workspace);
12386
13018
  const item = findItem(checklist.items, id);
12387
13019
  if (!item) {
@@ -12397,10 +13029,11 @@ todoCommand.command("unblock").description("Return a blocked todo to open").argu
12397
13029
  process.exit(1);
12398
13030
  }
12399
13031
  });
12400
- todoCommand.command("delete").description("Delete a todo item (no log entry)").argument("<id>", "Todo short ID").option("--workspace <slug>", "Workspace slug").option("--global", "Use global todos").action(async (id, options) => {
13032
+ todoCommand.command("delete").description("Delete a todo item (no log entry)").argument("<id>", "Todo short ID").option("--workspace <slug>", "Workspace slug").option("--project <slug>", "Project slug (mutually exclusive with --workspace/--global)").option("--global", "Use global todos").action(async (id, options) => {
12401
13033
  try {
12402
- const todosPath = todosDir();
12403
- const workspace = resolveWorkspace(options);
13034
+ const scope = await resolveScope(options);
13035
+ const todosPath = scope.todosPath;
13036
+ const workspace = scope.id;
12404
13037
  const checklist = await readChecklist(todosPath, workspace);
12405
13038
  const idx = checklist.items.findIndex((i) => i.id === id);
12406
13039
  if (idx === -1) {
@@ -12416,10 +13049,11 @@ todoCommand.command("delete").description("Delete a todo item (no log entry)").a
12416
13049
  process.exit(1);
12417
13050
  }
12418
13051
  });
12419
- todoCommand.command("edit").description("Update a todo description").argument("<id>", "Todo short ID").argument("<description>", "New description").option("--workspace <slug>", "Workspace slug").option("--global", "Use global todos").action(async (id, description, options) => {
13052
+ todoCommand.command("edit").description("Update a todo description").argument("<id>", "Todo short ID").argument("<description>", "New description").option("--workspace <slug>", "Workspace slug").option("--project <slug>", "Project slug (mutually exclusive with --workspace/--global)").option("--global", "Use global todos").action(async (id, description, options) => {
12420
13053
  try {
12421
- const todosPath = todosDir();
12422
- const workspace = resolveWorkspace(options);
13054
+ const scope = await resolveScope(options);
13055
+ const todosPath = scope.todosPath;
13056
+ const workspace = scope.id;
12423
13057
  const checklist = await readChecklist(todosPath, workspace);
12424
13058
  const item = findItem(checklist.items, id);
12425
13059
  if (!item) {
@@ -12434,10 +13068,11 @@ todoCommand.command("edit").description("Update a todo description").argument("<
12434
13068
  process.exit(1);
12435
13069
  }
12436
13070
  });
12437
- todoCommand.command("tag").description("Modify tags on a todo").argument("<id>", "Todo short ID").option("--add <tags>", "Tags to add (comma-separated)").option("--remove <tags>", "Tags to remove (comma-separated)").option("--workspace <slug>", "Workspace slug").option("--global", "Use global todos").action(async (id, options) => {
13071
+ todoCommand.command("tag").description("Modify tags on a todo").argument("<id>", "Todo short ID").option("--add <tags>", "Tags to add (comma-separated)").option("--remove <tags>", "Tags to remove (comma-separated)").option("--workspace <slug>", "Workspace slug").option("--project <slug>", "Project slug (mutually exclusive with --workspace/--global)").option("--global", "Use global todos").action(async (id, options) => {
12438
13072
  try {
12439
- const todosPath = todosDir();
12440
- const workspace = resolveWorkspace(options);
13073
+ const scope = await resolveScope(options);
13074
+ const todosPath = scope.todosPath;
13075
+ const workspace = scope.id;
12441
13076
  const checklist = await readChecklist(todosPath, workspace);
12442
13077
  const item = findItem(checklist.items, id);
12443
13078
  if (!item) {
@@ -12461,10 +13096,11 @@ todoCommand.command("tag").description("Modify tags on a todo").argument("<id>",
12461
13096
  process.exit(1);
12462
13097
  }
12463
13098
  });
12464
- todoCommand.command("log").description("Show log entries").argument("[id]", "Optional todo short ID to filter").option("--workspace <slug>", "Workspace slug").option("--global", "Use global todos").action(async (id, options) => {
13099
+ todoCommand.command("log").description("Show log entries").argument("[id]", "Optional todo short ID to filter").option("--workspace <slug>", "Workspace slug").option("--project <slug>", "Project slug (mutually exclusive with --workspace/--global)").option("--global", "Use global todos").action(async (id, options) => {
12465
13100
  try {
12466
- const todosPath = todosDir();
12467
- const workspace = resolveWorkspace(options);
13101
+ const scope = await resolveScope(options);
13102
+ const todosPath = scope.todosPath;
13103
+ const workspace = scope.id;
12468
13104
  const log = await readLog(todosPath, workspace);
12469
13105
  let entries = log.entries;
12470
13106
  if (id) {
@@ -12488,10 +13124,11 @@ ${entry.timestamp} \u2014 ${entry.itemIds.map((i) => `t:${i}`).join(", ")}`);
12488
13124
  process.exit(1);
12489
13125
  }
12490
13126
  });
12491
- todoCommand.command("archive").description("Archive completed todos and their log entries").option("--workspace <slug>", "Workspace slug").option("--global", "Use global todos").action(async (options) => {
13127
+ todoCommand.command("archive").description("Archive completed todos and their log entries").option("--workspace <slug>", "Workspace slug").option("--project <slug>", "Project slug (mutually exclusive with --workspace/--global)").option("--global", "Use global todos").action(async (options) => {
12492
13128
  try {
12493
- const todosPath = todosDir();
12494
- const workspace = resolveWorkspace(options);
13129
+ const scope = await resolveScope(options);
13130
+ const todosPath = scope.todosPath;
13131
+ const workspace = scope.id;
12495
13132
  const checklist = await readChecklist(todosPath, workspace);
12496
13133
  const log = await readLog(todosPath, workspace);
12497
13134
  const completedIds = new Set(
@@ -12505,10 +13142,10 @@ todoCommand.command("archive").description("Archive completed todos and their lo
12505
13142
  (e) => e.itemIds.every((id) => completedIds.has(id))
12506
13143
  );
12507
13144
  const archFile = archivePath(todosPath, workspace, checklist.archiveInterval);
12508
- await ensureDir(resolve34(todosPath, "archive"));
13145
+ await ensureDir(resolve35(todosPath, "archive"));
12509
13146
  let archContent = "";
12510
13147
  if (await fileExists(archFile)) {
12511
- archContent = await readFile20(archFile, "utf-8");
13148
+ archContent = await readFile21(archFile, "utf-8");
12512
13149
  archContent = archContent.trimEnd() + "\n\n";
12513
13150
  } else {
12514
13151
  archContent = `---
@@ -12583,10 +13220,11 @@ workspace: ${workspace}
12583
13220
  process.exit(1);
12584
13221
  }
12585
13222
  });
12586
- todoCommand.command("promote").description("Promote a todo to a full assignment").argument("<id>", "Todo short ID").requiredOption("--project <slug>", "Target project slug").option("--workspace <slug>", "Workspace slug").option("--global", "Use global todos").action(async (id, options) => {
13223
+ todoCommand.command("promote").description("Promote a todo to a full assignment").argument("<id>", "Todo short ID").requiredOption("--to-project <slug>", "Target project slug for the new assignment").option("--workspace <slug>", "Source workspace slug").option("--project <slug>", "Source project slug (mutually exclusive with --workspace/--global)").option("--global", "Use global todos").action(async (id, options) => {
12587
13224
  try {
12588
- const todosPath = todosDir();
12589
- const workspace = resolveWorkspace(options);
13225
+ const scope = await resolveScope(options);
13226
+ const todosPath = scope.todosPath;
13227
+ const workspace = scope.id;
12590
13228
  const checklist = await readChecklist(todosPath, workspace);
12591
13229
  const item = findItem(checklist.items, id);
12592
13230
  if (!item) {
@@ -12602,13 +13240,13 @@ todoCommand.command("promote").description("Promote a todo to a full assignment"
12602
13240
  items: item.description,
12603
13241
  session: null,
12604
13242
  branch: null,
12605
- summary: `Promoted to assignment in project: ${options.project}`,
13243
+ summary: `Promoted to assignment in project: ${options.toProject}`,
12606
13244
  blockers: null,
12607
13245
  status: null
12608
13246
  };
12609
13247
  await appendLogEntry2(todosPath, workspace, entry);
12610
- console.log(`Promoted [t:${id}] to assignment in project "${options.project}".`);
12611
- console.log(`Run: syntaur create-assignment --project ${options.project} "${item.description}"`);
13248
+ console.log(`Promoted [t:${id}] to assignment in project "${options.toProject}".`);
13249
+ console.log(`Run: syntaur create-assignment --project ${options.toProject} "${item.description}"`);
12612
13250
  } catch (error) {
12613
13251
  console.error("Error:", error instanceof Error ? error.message : String(error));
12614
13252
  process.exit(1);
@@ -12697,7 +13335,7 @@ import { Command as Command3 } from "commander";
12697
13335
 
12698
13336
  // src/utils/doctor/index.ts
12699
13337
  import { fileURLToPath as fileURLToPath7 } from "url";
12700
- import { readFile as readFile24 } from "fs/promises";
13338
+ import { readFile as readFile25 } from "fs/promises";
12701
13339
  import { dirname as dirname11, join as join5 } from "path";
12702
13340
 
12703
13341
  // src/utils/doctor/context.ts
@@ -12705,11 +13343,11 @@ init_config2();
12705
13343
  init_paths();
12706
13344
  init_fs();
12707
13345
  import Database2 from "better-sqlite3";
12708
- import { resolve as resolve35 } from "path";
13346
+ import { resolve as resolve36 } from "path";
12709
13347
  async function buildCheckContext(cwd = process.cwd()) {
12710
13348
  const config = await readConfig();
12711
13349
  const root = syntaurRoot();
12712
- const dbPath = resolve35(root, "syntaur.db");
13350
+ const dbPath = resolve36(root, "syntaur.db");
12713
13351
  let db2 = null;
12714
13352
  let dbError = null;
12715
13353
  if (await fileExists(dbPath)) {
@@ -12743,8 +13381,8 @@ function closeCheckContext(ctx) {
12743
13381
  // src/utils/doctor/checks/env.ts
12744
13382
  init_fs();
12745
13383
  init_paths();
12746
- import { resolve as resolve36, isAbsolute as isAbsolute3 } from "path";
12747
- import { readFile as readFile21, stat as stat4 } from "fs/promises";
13384
+ import { resolve as resolve37, isAbsolute as isAbsolute3 } from "path";
13385
+ import { readFile as readFile22, stat as stat4 } from "fs/promises";
12748
13386
  import { fileURLToPath as fileURLToPath6 } from "url";
12749
13387
  import { dirname as dirname10, join as join4 } from "path";
12750
13388
  var CATEGORY = "env";
@@ -12784,7 +13422,7 @@ var configValid = {
12784
13422
  category: CATEGORY,
12785
13423
  title: "~/.syntaur/config.md is valid",
12786
13424
  async run(ctx) {
12787
- const configPath = resolve36(ctx.syntaurRoot, "config.md");
13425
+ const configPath = resolve37(ctx.syntaurRoot, "config.md");
12788
13426
  if (!await fileExists(configPath)) {
12789
13427
  return {
12790
13428
  id: this.id,
@@ -12801,7 +13439,7 @@ var configValid = {
12801
13439
  autoFixable: false
12802
13440
  };
12803
13441
  }
12804
- const content = await readFile21(configPath, "utf-8");
13442
+ const content = await readFile22(configPath, "utf-8");
12805
13443
  const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
12806
13444
  if (!fmMatch || fmMatch[1].trim() === "") {
12807
13445
  return {
@@ -13081,7 +13719,7 @@ async function readLocalPkg() {
13081
13719
  for (let i = 0; i < 6; i++) {
13082
13720
  const candidate = join4(dir, "package.json");
13083
13721
  try {
13084
- const text = await readFile21(candidate, "utf-8");
13722
+ const text = await readFile22(candidate, "utf-8");
13085
13723
  return JSON.parse(text);
13086
13724
  } catch {
13087
13725
  dir = dirname10(dir);
@@ -13133,7 +13771,7 @@ function versionGte(a, b) {
13133
13771
 
13134
13772
  // src/utils/doctor/checks/structure.ts
13135
13773
  init_fs();
13136
- import { resolve as resolve37 } from "path";
13774
+ import { resolve as resolve38 } from "path";
13137
13775
  import { readdir as readdir13, stat as stat5 } from "fs/promises";
13138
13776
  var CATEGORY2 = "structure";
13139
13777
  var KNOWN_TOP_LEVEL = /* @__PURE__ */ new Set([
@@ -13153,7 +13791,7 @@ var projectsDir = {
13153
13791
  category: CATEGORY2,
13154
13792
  title: "projects/ directory exists",
13155
13793
  async run(ctx) {
13156
- const p = resolve37(ctx.syntaurRoot, "projects");
13794
+ const p = resolve38(ctx.syntaurRoot, "projects");
13157
13795
  if (!await fileExists(p)) {
13158
13796
  return {
13159
13797
  id: this.id,
@@ -13178,7 +13816,7 @@ var playbooksDir2 = {
13178
13816
  category: CATEGORY2,
13179
13817
  title: "playbooks/ directory exists",
13180
13818
  async run(ctx) {
13181
- const p = resolve37(ctx.syntaurRoot, "playbooks");
13819
+ const p = resolve38(ctx.syntaurRoot, "playbooks");
13182
13820
  if (!await fileExists(p)) {
13183
13821
  return {
13184
13822
  id: this.id,
@@ -13203,7 +13841,7 @@ var todosDirValid = {
13203
13841
  category: CATEGORY2,
13204
13842
  title: "todos/ directory is readable (if present)",
13205
13843
  async run(ctx) {
13206
- const p = resolve37(ctx.syntaurRoot, "todos");
13844
+ const p = resolve38(ctx.syntaurRoot, "todos");
13207
13845
  if (!await fileExists(p)) {
13208
13846
  return {
13209
13847
  id: this.id,
@@ -13234,7 +13872,7 @@ var serversDirValid = {
13234
13872
  category: CATEGORY2,
13235
13873
  title: "servers/ directory is readable (if present)",
13236
13874
  async run(ctx) {
13237
- const p = resolve37(ctx.syntaurRoot, "servers");
13875
+ const p = resolve38(ctx.syntaurRoot, "servers");
13238
13876
  if (!await fileExists(p)) {
13239
13877
  return {
13240
13878
  id: this.id,
@@ -13279,7 +13917,7 @@ var knownFilesRecognized = {
13279
13917
  title: this.title,
13280
13918
  status: "warn",
13281
13919
  detail: `unexpected top-level entries: ${unexpected.join(", ")}`,
13282
- affected: unexpected.map((n) => resolve37(ctx.syntaurRoot, n)),
13920
+ affected: unexpected.map((n) => resolve38(ctx.syntaurRoot, n)),
13283
13921
  remediation: {
13284
13922
  kind: "manual",
13285
13923
  suggestion: "Review these entries \u2014 they may be leftover state from older versions",
@@ -13308,7 +13946,7 @@ function pass2(check) {
13308
13946
 
13309
13947
  // src/utils/doctor/checks/project.ts
13310
13948
  init_fs();
13311
- import { resolve as resolve38 } from "path";
13949
+ import { resolve as resolve39 } from "path";
13312
13950
  import { readdir as readdir14, stat as stat6 } from "fs/promises";
13313
13951
  var CATEGORY3 = "project";
13314
13952
  var REQUIRED_PROJECT_FILES = [
@@ -13338,10 +13976,10 @@ async function listProjects2(ctx) {
13338
13976
  for (const e of entries) {
13339
13977
  if (!e.isDirectory()) continue;
13340
13978
  if (e.name.startsWith(".") || e.name.startsWith("_")) continue;
13341
- const projectDir = resolve38(dir, e.name);
13979
+ const projectDir = resolve39(dir, e.name);
13342
13980
  let looksLikeProject = false;
13343
13981
  for (const marker of PROJECT_MARKERS) {
13344
- if (await fileExists(resolve38(projectDir, marker))) {
13982
+ if (await fileExists(resolve39(projectDir, marker))) {
13345
13983
  looksLikeProject = true;
13346
13984
  break;
13347
13985
  }
@@ -13360,7 +13998,7 @@ var requiredFiles = {
13360
13998
  for (const projectDir of projects) {
13361
13999
  const missing = [];
13362
14000
  for (const rel of REQUIRED_PROJECT_FILES) {
13363
- const p = resolve38(projectDir, rel);
14001
+ const p = resolve39(projectDir, rel);
13364
14002
  if (!await fileExists(p)) missing.push(rel);
13365
14003
  }
13366
14004
  if (missing.length === 0) continue;
@@ -13370,7 +14008,7 @@ var requiredFiles = {
13370
14008
  title: this.title,
13371
14009
  status: "error",
13372
14010
  detail: `project at ${projectDir} is missing: ${missing.join(", ")}`,
13373
- affected: missing.map((m) => resolve38(projectDir, m)),
14011
+ affected: missing.map((m) => resolve39(projectDir, m)),
13374
14012
  remediation: {
13375
14013
  kind: "manual",
13376
14014
  suggestion: "Recreate the missing scaffold files from templates",
@@ -13393,7 +14031,7 @@ var manifestStale = {
13393
14031
  const projects = await listProjects2(ctx);
13394
14032
  const results = [];
13395
14033
  for (const projectDir of projects) {
13396
- const manifestPath = resolve38(projectDir, "manifest.md");
14034
+ const manifestPath = resolve39(projectDir, "manifest.md");
13397
14035
  if (!await fileExists(manifestPath)) continue;
13398
14036
  const manifestMtime = (await stat6(manifestPath)).mtimeMs;
13399
14037
  const newestAssignment = await newestAssignmentMtime(projectDir);
@@ -13442,7 +14080,7 @@ var orphanFiles = {
13442
14080
  title: this.title,
13443
14081
  status: "warn",
13444
14082
  detail: `project at ${projectDir} has unexpected entries: ${orphans.join(", ")}`,
13445
- affected: orphans.map((o) => resolve38(projectDir, o)),
14083
+ affected: orphans.map((o) => resolve39(projectDir, o)),
13446
14084
  autoFixable: false
13447
14085
  });
13448
14086
  }
@@ -13452,7 +14090,7 @@ var orphanFiles = {
13452
14090
  };
13453
14091
  var projectChecks = [requiredFiles, manifestStale, orphanFiles];
13454
14092
  async function newestAssignmentMtime(projectDir) {
13455
- const assignmentsRoot = resolve38(projectDir, "assignments");
14093
+ const assignmentsRoot = resolve39(projectDir, "assignments");
13456
14094
  if (!await fileExists(assignmentsRoot)) return 0;
13457
14095
  let newest = 0;
13458
14096
  let entries;
@@ -13463,7 +14101,7 @@ async function newestAssignmentMtime(projectDir) {
13463
14101
  }
13464
14102
  for (const e of entries) {
13465
14103
  if (!e.isDirectory()) continue;
13466
- const assignmentMd = resolve38(assignmentsRoot, e.name, "assignment.md");
14104
+ const assignmentMd = resolve39(assignmentsRoot, e.name, "assignment.md");
13467
14105
  try {
13468
14106
  const s = await stat6(assignmentMd);
13469
14107
  if (s.mtimeMs > newest) newest = s.mtimeMs;
@@ -13487,8 +14125,8 @@ init_fs();
13487
14125
  init_parser();
13488
14126
  init_types();
13489
14127
  init_paths();
13490
- import { resolve as resolve39 } from "path";
13491
- import { readdir as readdir15, readFile as readFile22 } from "fs/promises";
14128
+ import { resolve as resolve40 } from "path";
14129
+ import { readdir as readdir15, readFile as readFile23 } from "fs/promises";
13492
14130
  var CATEGORY4 = "assignment";
13493
14131
  var STATUSES_REQUIRING_HANDOFF = /* @__PURE__ */ new Set(["review", "completed"]);
13494
14132
  async function listAssignments(ctx) {
@@ -13499,16 +14137,16 @@ async function listAssignments(ctx) {
13499
14137
  for (const m of projects) {
13500
14138
  if (!m.isDirectory()) continue;
13501
14139
  if (m.name.startsWith(".") || m.name.startsWith("_")) continue;
13502
- const assignmentsDir2 = resolve39(projectsDir2, m.name, "assignments");
14140
+ const assignmentsDir2 = resolve40(projectsDir2, m.name, "assignments");
13503
14141
  if (!await fileExists(assignmentsDir2)) continue;
13504
14142
  const entries = await readdir15(assignmentsDir2, { withFileTypes: true });
13505
14143
  for (const a of entries) {
13506
14144
  if (!a.isDirectory()) continue;
13507
14145
  if (a.name.startsWith(".") || a.name.startsWith("_")) continue;
13508
- const assignmentDir = resolve39(assignmentsDir2, a.name);
13509
- const assignmentMd = resolve39(assignmentDir, "assignment.md");
14146
+ const assignmentDir = resolve40(assignmentsDir2, a.name);
14147
+ const assignmentMd = resolve40(assignmentDir, "assignment.md");
13510
14148
  const entry = {
13511
- projectDir: resolve39(projectsDir2, m.name),
14149
+ projectDir: resolve40(projectsDir2, m.name),
13512
14150
  projectSlug: m.name,
13513
14151
  assignmentDir,
13514
14152
  assignmentSlug: a.name,
@@ -13528,8 +14166,8 @@ async function listAssignments(ctx) {
13528
14166
  for (const a of entries) {
13529
14167
  if (!a.isDirectory()) continue;
13530
14168
  if (a.name.startsWith(".") || a.name.startsWith("_")) continue;
13531
- const assignmentDir = resolve39(standaloneRoot, a.name);
13532
- const assignmentMd = resolve39(assignmentDir, "assignment.md");
14169
+ const assignmentDir = resolve40(standaloneRoot, a.name);
14170
+ const assignmentMd = resolve40(assignmentDir, "assignment.md");
13533
14171
  const entry = {
13534
14172
  projectDir: standaloneRoot,
13535
14173
  projectSlug: null,
@@ -13607,7 +14245,7 @@ var invalidStatus = {
13607
14245
  const allowed = configuredStatuses(ctx);
13608
14246
  const results = [];
13609
14247
  for (const a of withAssignmentMd) {
13610
- const path = resolve39(a.assignmentDir, "assignment.md");
14248
+ const path = resolve40(a.assignmentDir, "assignment.md");
13611
14249
  const parsed = await parseSafe(path);
13612
14250
  if (!parsed) continue;
13613
14251
  if (!allowed.has(parsed.status)) {
@@ -13640,7 +14278,7 @@ var workspaceMissing = {
13640
14278
  const terminal = terminalStatuses(ctx);
13641
14279
  const results = [];
13642
14280
  for (const a of withAssignmentMd) {
13643
- const path = resolve39(a.assignmentDir, "assignment.md");
14281
+ const path = resolve40(a.assignmentDir, "assignment.md");
13644
14282
  const parsed = await parseSafe(path);
13645
14283
  if (!parsed) continue;
13646
14284
  if (terminal.has(parsed.status)) continue;
@@ -13687,12 +14325,12 @@ var requiredFilesByStatus = {
13687
14325
  const { withAssignmentMd } = await listAssignments(ctx);
13688
14326
  const results = [];
13689
14327
  for (const a of withAssignmentMd) {
13690
- const assignmentPath = resolve39(a.assignmentDir, "assignment.md");
14328
+ const assignmentPath = resolve40(a.assignmentDir, "assignment.md");
13691
14329
  const parsed = await parseSafe(assignmentPath);
13692
14330
  if (!parsed) continue;
13693
14331
  const missing = [];
13694
14332
  if (STATUSES_REQUIRING_HANDOFF.has(parsed.status)) {
13695
- const handoffPath = resolve39(a.assignmentDir, "handoff.md");
14333
+ const handoffPath = resolve40(a.assignmentDir, "handoff.md");
13696
14334
  if (!await fileExists(handoffPath)) missing.push("handoff.md");
13697
14335
  }
13698
14336
  if (missing.length === 0) continue;
@@ -13702,7 +14340,7 @@ var requiredFilesByStatus = {
13702
14340
  title: this.title,
13703
14341
  status: "warn",
13704
14342
  detail: `${a.projectSlug}/${a.assignmentSlug} (status: ${parsed.status}) is missing ${missing.join(", ")}`,
13705
- affected: missing.map((m) => resolve39(a.assignmentDir, m)),
14343
+ affected: missing.map((m) => resolve40(a.assignmentDir, m)),
13706
14344
  remediation: {
13707
14345
  kind: "manual",
13708
14346
  suggestion: `Create the missing ${missing.join(" and ")} files for this assignment`,
@@ -13725,7 +14363,7 @@ var companionFilesScaffolded = {
13725
14363
  for (const a of withAssignmentMd) {
13726
14364
  const missing = [];
13727
14365
  for (const filename of ["progress.md", "comments.md"]) {
13728
- if (!await fileExists(resolve39(a.assignmentDir, filename))) {
14366
+ if (!await fileExists(resolve40(a.assignmentDir, filename))) {
13729
14367
  missing.push(filename);
13730
14368
  }
13731
14369
  }
@@ -13737,7 +14375,7 @@ var companionFilesScaffolded = {
13737
14375
  title: this.title,
13738
14376
  status: "warn",
13739
14377
  detail: `${label} is missing ${missing.join(" and ")} (pre-v2.0 assignment \u2014 not required, but scaffolding them keeps the dashboard and CLIs consistent)`,
13740
- affected: missing.map((m) => resolve39(a.assignmentDir, m)),
14378
+ affected: missing.map((m) => resolve40(a.assignmentDir, m)),
13741
14379
  remediation: {
13742
14380
  kind: "manual",
13743
14381
  suggestion: `Create ${missing.join(" and ")} with the renderProgress/renderComments templates, or re-scaffold via the CLI`,
@@ -13770,7 +14408,7 @@ var typeDefinition = {
13770
14408
  const { withAssignmentMd } = await listAssignments(ctx);
13771
14409
  const results = [];
13772
14410
  for (const a of withAssignmentMd) {
13773
- const path = resolve39(a.assignmentDir, "assignment.md");
14411
+ const path = resolve40(a.assignmentDir, "assignment.md");
13774
14412
  const parsed = await parseSafe(path);
13775
14413
  if (!parsed) continue;
13776
14414
  if (!parsed.type) continue;
@@ -13804,7 +14442,7 @@ var projectFrontmatterMatchesContainer = {
13804
14442
  const { withAssignmentMd } = await listAssignments(ctx);
13805
14443
  const results = [];
13806
14444
  for (const a of withAssignmentMd) {
13807
- const path = resolve39(a.assignmentDir, "assignment.md");
14445
+ const path = resolve40(a.assignmentDir, "assignment.md");
13808
14446
  const parsed = await parseSafe(path);
13809
14447
  if (!parsed) continue;
13810
14448
  if (a.standalone) {
@@ -13859,7 +14497,7 @@ var assignmentChecks = [
13859
14497
  ];
13860
14498
  async function parseSafe(path) {
13861
14499
  try {
13862
- const content = await readFile22(path, "utf-8");
14500
+ const content = await readFile23(path, "utf-8");
13863
14501
  return parseAssignmentFull(content);
13864
14502
  } catch {
13865
14503
  return null;
@@ -13878,7 +14516,7 @@ function pass4(check, detail) {
13878
14516
 
13879
14517
  // src/utils/doctor/checks/dashboard.ts
13880
14518
  init_fs();
13881
- import { resolve as resolve40 } from "path";
14519
+ import { resolve as resolve41 } from "path";
13882
14520
  var CATEGORY5 = "dashboard";
13883
14521
  var dbReachable = {
13884
14522
  id: "dashboard.db-reachable",
@@ -13892,7 +14530,7 @@ var dbReachable = {
13892
14530
  title: this.title,
13893
14531
  status: "error",
13894
14532
  detail: `could not open syntaur.db: ${ctx.dbError ?? "unknown error"}`,
13895
- affected: [resolve40(ctx.syntaurRoot, "syntaur.db")],
14533
+ affected: [resolve41(ctx.syntaurRoot, "syntaur.db")],
13896
14534
  remediation: {
13897
14535
  kind: "manual",
13898
14536
  suggestion: "Start the dashboard once (`syntaur dashboard`) to initialize the DB, or restore it from backup",
@@ -13910,7 +14548,7 @@ var dbReachable = {
13910
14548
  title: this.title,
13911
14549
  status: "error",
13912
14550
  detail: 'syntaur.db is missing the expected "sessions" table',
13913
- affected: [resolve40(ctx.syntaurRoot, "syntaur.db")],
14551
+ affected: [resolve41(ctx.syntaurRoot, "syntaur.db")],
13914
14552
  autoFixable: false
13915
14553
  };
13916
14554
  }
@@ -13922,7 +14560,7 @@ var dbReachable = {
13922
14560
  title: this.title,
13923
14561
  status: "error",
13924
14562
  detail: `syntaur.db query failed: ${err2 instanceof Error ? err2.message : String(err2)}`,
13925
- affected: [resolve40(ctx.syntaurRoot, "syntaur.db")],
14563
+ affected: [resolve41(ctx.syntaurRoot, "syntaur.db")],
13926
14564
  autoFixable: false
13927
14565
  };
13928
14566
  }
@@ -13948,7 +14586,7 @@ var ghostSessions = {
13948
14586
  const results = [];
13949
14587
  for (const row of rows) {
13950
14588
  if (!row.project_slug) continue;
13951
- const projectPath = resolve40(projectsDir2, row.project_slug, "project.md");
14589
+ const projectPath = resolve41(projectsDir2, row.project_slug, "project.md");
13952
14590
  if (!await fileExists(projectPath)) {
13953
14591
  results.push({
13954
14592
  id: this.id,
@@ -13967,7 +14605,7 @@ var ghostSessions = {
13967
14605
  continue;
13968
14606
  }
13969
14607
  if (row.assignment_slug) {
13970
- const assignmentPath = resolve40(
14608
+ const assignmentPath = resolve41(
13971
14609
  projectsDir2,
13972
14610
  row.project_slug,
13973
14611
  "assignments",
@@ -14124,8 +14762,8 @@ function skipped2(check, reason) {
14124
14762
  init_fs();
14125
14763
  init_parser();
14126
14764
  init_types();
14127
- import { resolve as resolve41 } from "path";
14128
- import { readFile as readFile23 } from "fs/promises";
14765
+ import { resolve as resolve42 } from "path";
14766
+ import { readFile as readFile24 } from "fs/promises";
14129
14767
  var CATEGORY7 = "workspace";
14130
14768
  var ASSIGNMENT_FIELDS = ["projectSlug", "assignmentSlug", "projectDir", "assignmentDir"];
14131
14769
  function hasAnyAssignmentField(ctx) {
@@ -14137,12 +14775,12 @@ function isStandaloneSession(ctx) {
14137
14775
  return !hasAnyAssignmentField(ctx) && typeof ctx.sessionId === "string" && ctx.sessionId.length > 0;
14138
14776
  }
14139
14777
  async function loadContext(ctx) {
14140
- const path = resolve41(ctx.cwd, ".syntaur", "context.json");
14778
+ const path = resolve42(ctx.cwd, ".syntaur", "context.json");
14141
14779
  if (!await fileExists(path)) {
14142
14780
  return { data: null, path, exists: false, parseError: null };
14143
14781
  }
14144
14782
  try {
14145
- const raw = await readFile23(path, "utf-8");
14783
+ const raw = await readFile24(path, "utf-8");
14146
14784
  return { data: JSON.parse(raw), path, exists: true, parseError: null };
14147
14785
  } catch (err2) {
14148
14786
  return {
@@ -14217,7 +14855,7 @@ var contextAssignmentResolves = {
14217
14855
  if (!exists) return skipped3(this, "no context to resolve");
14218
14856
  if (isStandaloneSession(data)) return skipped3(this, "standalone session context \u2014 no assignment to resolve");
14219
14857
  if (!data?.assignmentDir) return skipped3(this, "context has no assignmentDir");
14220
- const assignmentMd = resolve41(data.assignmentDir, "assignment.md");
14858
+ const assignmentMd = resolve42(data.assignmentDir, "assignment.md");
14221
14859
  if (!await fileExists(assignmentMd)) {
14222
14860
  return {
14223
14861
  id: this.id,
@@ -14246,10 +14884,10 @@ var contextTerminal = {
14246
14884
  if (!exists) return skipped3(this, "no context to check");
14247
14885
  if (isStandaloneSession(data)) return skipped3(this, "standalone session context \u2014 no assignment to check");
14248
14886
  if (!data?.assignmentDir) return skipped3(this, "context has no assignmentDir");
14249
- const assignmentMd = resolve41(data.assignmentDir, "assignment.md");
14887
+ const assignmentMd = resolve42(data.assignmentDir, "assignment.md");
14250
14888
  if (!await fileExists(assignmentMd)) return skipped3(this, "assignment file missing");
14251
14889
  try {
14252
- const content = await readFile23(assignmentMd, "utf-8");
14890
+ const content = await readFile24(assignmentMd, "utf-8");
14253
14891
  const parsed = parseAssignmentFull(content);
14254
14892
  const terminal = terminalStatuses2(ctx);
14255
14893
  if (terminal.has(parsed.status)) {
@@ -14396,7 +15034,7 @@ async function readVersion() {
14396
15034
  let dir = dirname11(here);
14397
15035
  for (let i = 0; i < 6; i++) {
14398
15036
  try {
14399
- const raw = await readFile24(join5(dir, "package.json"), "utf-8");
15037
+ const raw = await readFile25(join5(dir, "package.json"), "utf-8");
14400
15038
  const parsed = JSON.parse(raw);
14401
15039
  return typeof parsed.version === "string" ? parsed.version : null;
14402
15040
  } catch {
@@ -14533,8 +15171,8 @@ var doctorCommand = new Command3("doctor").description("Diagnose Syntaur state a
14533
15171
  init_paths();
14534
15172
  init_fs();
14535
15173
  init_config2();
14536
- import { resolve as resolve42 } from "path";
14537
- import { readFile as readFile25 } from "fs/promises";
15174
+ import { resolve as resolve43 } from "path";
15175
+ import { readFile as readFile26 } from "fs/promises";
14538
15176
  init_timestamp();
14539
15177
  init_assignment_resolver();
14540
15178
  function shortId() {
@@ -14566,7 +15204,7 @@ async function commentCommand(target, text, options = {}) {
14566
15204
  if (!isValidSlug(target)) {
14567
15205
  throw new Error(`Invalid assignment slug "${target}".`);
14568
15206
  }
14569
- assignmentDir = resolve42(baseDir, options.project, "assignments", target);
15207
+ assignmentDir = resolve43(baseDir, options.project, "assignments", target);
14570
15208
  assignmentRef = target;
14571
15209
  } else {
14572
15210
  const resolved = await resolveAssignmentById(baseDir, assignmentsDir(), target);
@@ -14576,13 +15214,13 @@ async function commentCommand(target, text, options = {}) {
14576
15214
  assignmentDir = resolved.assignmentDir;
14577
15215
  assignmentRef = resolved.standalone ? resolved.id : resolved.assignmentSlug;
14578
15216
  }
14579
- const commentsPath = resolve42(assignmentDir, "comments.md");
15217
+ const commentsPath = resolve43(assignmentDir, "comments.md");
14580
15218
  const timestamp = nowTimestamp();
14581
15219
  const author = options.author ?? process.env.USER ?? "unknown";
14582
15220
  let currentContent;
14583
15221
  let currentCount = 0;
14584
15222
  if (await fileExists(commentsPath)) {
14585
- currentContent = await readFile25(commentsPath, "utf-8");
15223
+ currentContent = await readFile26(commentsPath, "utf-8");
14586
15224
  const countMatch = currentContent.match(/^entryCount:\s*(\d+)/m);
14587
15225
  if (countMatch) currentCount = parseInt(countMatch[1], 10);
14588
15226
  } else {
@@ -14619,8 +15257,8 @@ ${entry}`;
14619
15257
  init_paths();
14620
15258
  init_fs();
14621
15259
  init_config2();
14622
- import { resolve as resolve43 } from "path";
14623
- import { readFile as readFile26 } from "fs/promises";
15260
+ import { resolve as resolve44 } from "path";
15261
+ import { readFile as readFile27 } from "fs/promises";
14624
15262
  init_timestamp();
14625
15263
  init_assignment_resolver();
14626
15264
  function setTopLevelField3(content, key, value) {
@@ -14645,7 +15283,7 @@ async function requestCommand(target, text, options = {}) {
14645
15283
  if (!isValidSlug(target)) {
14646
15284
  throw new Error(`Invalid assignment slug "${target}".`);
14647
15285
  }
14648
- assignmentDir = resolve43(baseDir, options.project, "assignments", target);
15286
+ assignmentDir = resolve44(baseDir, options.project, "assignments", target);
14649
15287
  targetRef = target;
14650
15288
  } else {
14651
15289
  const resolved = await resolveAssignmentById(baseDir, assignmentsDir(), target);
@@ -14655,12 +15293,12 @@ async function requestCommand(target, text, options = {}) {
14655
15293
  assignmentDir = resolved.assignmentDir;
14656
15294
  targetRef = resolved.standalone ? resolved.id : resolved.assignmentSlug;
14657
15295
  }
14658
- const assignmentMdPath = resolve43(assignmentDir, "assignment.md");
15296
+ const assignmentMdPath = resolve44(assignmentDir, "assignment.md");
14659
15297
  if (!await fileExists(assignmentMdPath)) {
14660
15298
  throw new Error(`assignment.md not found at ${assignmentMdPath}`);
14661
15299
  }
14662
15300
  const source = options.from ?? process.env.SYNTAUR_ASSIGNMENT ?? "unknown";
14663
- let content = await readFile26(assignmentMdPath, "utf-8");
15301
+ let content = await readFile27(assignmentMdPath, "utf-8");
14664
15302
  const todoLine = `- [ ] ${text.trim()} (from: ${source})`;
14665
15303
  const todosHeading = /^## Todos\s*$/m;
14666
15304
  if (todosHeading.test(content)) {
@@ -14728,20 +15366,20 @@ async function getDefaultCommandName() {
14728
15366
  init_paths();
14729
15367
  init_fs();
14730
15368
  import { fileURLToPath as fileURLToPath9 } from "url";
14731
- import { readFile as readFile28 } from "fs/promises";
14732
- import { dirname as dirname13, join as join7, resolve as resolve44 } from "path";
15369
+ import { readFile as readFile29 } from "fs/promises";
15370
+ import { dirname as dirname13, join as join7, resolve as resolve45 } from "path";
14733
15371
  import { spawn as spawn3 } from "child_process";
14734
15372
  import { createInterface as createInterface2 } from "readline/promises";
14735
15373
 
14736
15374
  // src/utils/version.ts
14737
15375
  import { fileURLToPath as fileURLToPath8 } from "url";
14738
- import { readFile as readFile27 } from "fs/promises";
15376
+ import { readFile as readFile28 } from "fs/promises";
14739
15377
  import { dirname as dirname12, join as join6 } from "path";
14740
15378
  async function readPackageVersion(scriptUrl) {
14741
15379
  try {
14742
15380
  const scriptPath = fileURLToPath8(scriptUrl);
14743
15381
  const pkgRoot = dirname12(dirname12(scriptPath));
14744
- const raw = await readFile27(join6(pkgRoot, "package.json"), "utf-8");
15382
+ const raw = await readFile28(join6(pkgRoot, "package.json"), "utf-8");
14745
15383
  const parsed = JSON.parse(raw);
14746
15384
  return typeof parsed.version === "string" ? parsed.version : null;
14747
15385
  } catch {
@@ -14750,7 +15388,7 @@ async function readPackageVersion(scriptUrl) {
14750
15388
  }
14751
15389
 
14752
15390
  // src/utils/npx-prompt.ts
14753
- var STATE_FILE = resolve44(syntaurRoot(), "npx-install.json");
15391
+ var STATE_FILE = resolve45(syntaurRoot(), "npx-install.json");
14754
15392
  var META_ARGS = /* @__PURE__ */ new Set(["-h", "--help", "-V", "--version", "help"]);
14755
15393
  var GLOBAL_VERSION_TIMEOUT_MS = 2e3;
14756
15394
  function isRunningViaNpx(scriptUrl) {
@@ -14771,7 +15409,7 @@ function isRunningViaNpx(scriptUrl) {
14771
15409
  async function readState() {
14772
15410
  if (!await fileExists(STATE_FILE)) return null;
14773
15411
  try {
14774
- const raw = await readFile28(STATE_FILE, "utf-8");
15412
+ const raw = await readFile29(STATE_FILE, "utf-8");
14775
15413
  return JSON.parse(raw);
14776
15414
  } catch {
14777
15415
  return null;
@@ -14830,7 +15468,7 @@ async function readGlobalVersion() {
14830
15468
  try {
14831
15469
  const manifestPath = join7(rootPath, "syntaur", "package.json");
14832
15470
  if (!await fileExists(manifestPath)) return null;
14833
- const raw = await readFile28(manifestPath, "utf-8");
15471
+ const raw = await readFile29(manifestPath, "utf-8");
14834
15472
  const parsed = JSON.parse(raw);
14835
15473
  return typeof parsed.version === "string" ? parsed.version : null;
14836
15474
  } catch {