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
@@ -36,6 +36,9 @@ function playbooksDir() {
36
36
  function todosDir() {
37
37
  return resolve(syntaurRoot(), "todos");
38
38
  }
39
+ function projectTodosDir(projectsDir, projectSlug) {
40
+ return resolve(projectsDir, projectSlug, "todos");
41
+ }
39
42
  var init_paths = __esm({
40
43
  "src/utils/paths.ts"() {
41
44
  "use strict";
@@ -457,10 +460,10 @@ var init_lifecycle = __esm({
457
460
  });
458
461
 
459
462
  // src/templates/config.ts
460
- function renderConfig(params) {
463
+ function renderConfig(params2) {
461
464
  return `---
462
465
  version: "1.0"
463
- defaultProjectDir: ${params.defaultProjectDir}
466
+ defaultProjectDir: ${params2.defaultProjectDir}
464
467
  onboarding:
465
468
  completed: false
466
469
  agentDefaults:
@@ -4162,7 +4165,7 @@ init_api();
4162
4165
  init_assignment_resolver();
4163
4166
  import express from "express";
4164
4167
  import { createServer } from "http";
4165
- import { resolve as resolve17 } from "path";
4168
+ import { resolve as resolve18 } from "path";
4166
4169
  import { writeFile as writeFile5, unlink as unlink4 } from "fs/promises";
4167
4170
  import { WebSocketServer, WebSocket } from "ws";
4168
4171
 
@@ -4325,8 +4328,8 @@ async function migrateFromMarkdown(projectsDir) {
4325
4328
  return allSessions.length;
4326
4329
  }
4327
4330
  async function parseMarkdownSessionsIndex(filePath, projectSlug) {
4328
- const { readFile: readFile14 } = await import("fs/promises");
4329
- const raw = await readFile14(filePath, "utf-8");
4331
+ const { readFile: readFile15 } = await import("fs/promises");
4332
+ const raw = await readFile15(filePath, "utf-8");
4330
4333
  const sessions = [];
4331
4334
  const lines = raw.split("\n");
4332
4335
  let inTable = false;
@@ -4513,18 +4516,25 @@ function createWatcher(options) {
4513
4516
  if (parts.length === 0) return;
4514
4517
  const projectSlug = parts[0];
4515
4518
  let assignmentSlug;
4519
+ let isProjectTodos = false;
4516
4520
  if (parts.length >= 3 && parts[1] === "assignments") {
4517
4521
  assignmentSlug = parts[2];
4522
+ } else if (parts.length >= 2 && parts[1] === "todos") {
4523
+ isProjectTodos = true;
4518
4524
  }
4519
- const debounceKey = assignmentSlug ? `${projectSlug}/${assignmentSlug}` : projectSlug;
4525
+ const debounceKey = isProjectTodos ? `todos:${projectSlug}` : assignmentSlug ? `${projectSlug}/${assignmentSlug}` : projectSlug;
4520
4526
  const existing = pendingEvents.get(debounceKey);
4521
4527
  if (existing) clearTimeout(existing);
4522
- const messageType = assignmentSlug ? "assignment-updated" : "project-updated";
4528
+ const messageType = isProjectTodos ? "todos-updated" : assignmentSlug ? "assignment-updated" : "project-updated";
4523
4529
  pendingEvents.set(
4524
4530
  debounceKey,
4525
4531
  setTimeout(() => {
4526
4532
  pendingEvents.delete(debounceKey);
4527
- const message = {
4533
+ const message = isProjectTodos ? {
4534
+ type: "todos-updated",
4535
+ projectSlug,
4536
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
4537
+ } : {
4528
4538
  type: messageType,
4529
4539
  projectSlug,
4530
4540
  assignmentSlug,
@@ -4754,14 +4764,14 @@ init_assignment_resolver();
4754
4764
  init_config();
4755
4765
 
4756
4766
  // src/templates/manifest.ts
4757
- function renderManifest(params) {
4767
+ function renderManifest(params2) {
4758
4768
  return `---
4759
4769
  version: "2.0"
4760
- project: ${params.slug}
4761
- generated: "${params.timestamp}"
4770
+ project: ${params2.slug}
4771
+ generated: "${params2.timestamp}"
4762
4772
  ---
4763
4773
 
4764
- # Project: ${params.slug}
4774
+ # Project: ${params2.slug}
4765
4775
 
4766
4776
  ## Overview
4767
4777
  - [Project Overview](./project.md)
@@ -4788,24 +4798,24 @@ function escapeYamlString(value) {
4788
4798
  }
4789
4799
 
4790
4800
  // src/templates/project.ts
4791
- function renderProject(params) {
4792
- const safeTitle = escapeYamlString(params.title);
4793
- const workspaceLine = params.workspace ? `
4794
- workspace: ${params.workspace}` : "";
4801
+ function renderProject(params2) {
4802
+ const safeTitle = escapeYamlString(params2.title);
4803
+ const workspaceLine = params2.workspace ? `
4804
+ workspace: ${params2.workspace}` : "";
4795
4805
  return `---
4796
- id: ${params.id}
4797
- slug: ${params.slug}
4806
+ id: ${params2.id}
4807
+ slug: ${params2.slug}
4798
4808
  title: ${safeTitle}
4799
4809
  archived: false
4800
4810
  archivedAt: null
4801
4811
  archivedReason: null
4802
- created: "${params.timestamp}"
4803
- updated: "${params.timestamp}"
4812
+ created: "${params2.timestamp}"
4813
+ updated: "${params2.timestamp}"
4804
4814
  externalIds: []
4805
4815
  tags: []${workspaceLine}
4806
4816
  ---
4807
4817
 
4808
- # ${params.title}
4818
+ # ${params2.title}
4809
4819
 
4810
4820
  ## Overview
4811
4821
 
@@ -4818,17 +4828,17 @@ tags: []${workspaceLine}
4818
4828
  }
4819
4829
 
4820
4830
  // src/templates/assignment.ts
4821
- function renderAssignment(params) {
4822
- const safeTitle = escapeYamlString(params.title);
4823
- const dependsOnYaml = params.dependsOn.length === 0 ? "dependsOn: []" : `dependsOn:
4824
- - ${params.dependsOn.join("\n - ")}`;
4825
- const linksYaml = params.links.length === 0 ? "links: []" : `links:
4826
- - ${params.links.join("\n - ")}`;
4827
- const projectYaml = `project: ${params.project == null ? "null" : params.project}`;
4828
- const workspaceGroupLine = params.workspaceGroup ? `
4829
- workspaceGroup: ${params.workspaceGroup}` : "";
4830
- const typeYaml = `type: ${params.type ?? "feature"}`;
4831
- const todosSection = params.includeTodos ? `## Todos
4831
+ function renderAssignment(params2) {
4832
+ const safeTitle = escapeYamlString(params2.title);
4833
+ const dependsOnYaml = params2.dependsOn.length === 0 ? "dependsOn: []" : `dependsOn:
4834
+ - ${params2.dependsOn.join("\n - ")}`;
4835
+ const linksYaml = params2.links.length === 0 ? "links: []" : `links:
4836
+ - ${params2.links.join("\n - ")}`;
4837
+ const projectYaml = `project: ${params2.project == null ? "null" : params2.project}`;
4838
+ const workspaceGroupLine = params2.workspaceGroup ? `
4839
+ workspaceGroup: ${params2.workspaceGroup}` : "";
4840
+ const typeYaml = `type: ${params2.type ?? "feature"}`;
4841
+ const todosSection = params2.includeTodos ? `## Todos
4832
4842
 
4833
4843
  <!--
4834
4844
  Checklist of work items for this assignment. Items may be simple tasks
@@ -4840,15 +4850,15 @@ Never delete superseded todos \u2014 preserve the history.
4840
4850
 
4841
4851
  ` : "";
4842
4852
  return `---
4843
- id: ${params.id}
4844
- slug: ${params.slug}
4853
+ id: ${params2.id}
4854
+ slug: ${params2.slug}
4845
4855
  title: ${safeTitle}
4846
4856
  ${projectYaml}${workspaceGroupLine}
4847
4857
  ${typeYaml}
4848
4858
  status: pending
4849
- priority: ${params.priority}
4850
- created: "${params.timestamp}"
4851
- updated: "${params.timestamp}"
4859
+ priority: ${params2.priority}
4860
+ created: "${params2.timestamp}"
4861
+ updated: "${params2.timestamp}"
4852
4862
  assignee: null
4853
4863
  externalIds: []
4854
4864
  ${dependsOnYaml}
@@ -4862,7 +4872,7 @@ workspace:
4862
4872
  tags: []
4863
4873
  ---
4864
4874
 
4865
- # ${params.title}
4875
+ # ${params2.title}
4866
4876
 
4867
4877
  ## Objective
4868
4878
 
@@ -4889,10 +4899,10 @@ ${todosSection}## Context
4889
4899
  }
4890
4900
 
4891
4901
  // src/templates/scratchpad.ts
4892
- function renderScratchpad(params) {
4902
+ function renderScratchpad(params2) {
4893
4903
  return `---
4894
- assignment: ${params.assignmentSlug}
4895
- updated: "${params.timestamp}"
4904
+ assignment: ${params2.assignmentSlug}
4905
+ updated: "${params2.timestamp}"
4896
4906
  ---
4897
4907
 
4898
4908
  # Scratchpad
@@ -4902,10 +4912,10 @@ No working notes yet.
4902
4912
  }
4903
4913
 
4904
4914
  // src/templates/handoff.ts
4905
- function renderHandoff(params) {
4915
+ function renderHandoff(params2) {
4906
4916
  return `---
4907
- assignment: ${params.assignmentSlug}
4908
- updated: "${params.timestamp}"
4917
+ assignment: ${params2.assignmentSlug}
4918
+ updated: "${params2.timestamp}"
4909
4919
  handoffCount: 0
4910
4920
  ---
4911
4921
 
@@ -4916,12 +4926,12 @@ No handoffs recorded yet.
4916
4926
  }
4917
4927
 
4918
4928
  // src/templates/progress.ts
4919
- function renderProgress(params) {
4929
+ function renderProgress(params2) {
4920
4930
  return `---
4921
- assignment: ${params.assignment}
4931
+ assignment: ${params2.assignment}
4922
4932
  entryCount: 0
4923
- generated: "${params.timestamp}"
4924
- updated: "${params.timestamp}"
4933
+ generated: "${params2.timestamp}"
4934
+ updated: "${params2.timestamp}"
4925
4935
  ---
4926
4936
 
4927
4937
  # Progress
@@ -4931,12 +4941,12 @@ No progress yet.
4931
4941
  }
4932
4942
 
4933
4943
  // src/templates/comments.ts
4934
- function renderComments(params) {
4944
+ function renderComments(params2) {
4935
4945
  return `---
4936
- assignment: ${params.assignment}
4946
+ assignment: ${params2.assignment}
4937
4947
  entryCount: 0
4938
- generated: "${params.timestamp}"
4939
- updated: "${params.timestamp}"
4948
+ generated: "${params2.timestamp}"
4949
+ updated: "${params2.timestamp}"
4940
4950
  ---
4941
4951
 
4942
4952
  # Comments
@@ -4964,10 +4974,10 @@ function formatCommentEntry(comment) {
4964
4974
  }
4965
4975
 
4966
4976
  // src/templates/decision-record.ts
4967
- function renderDecisionRecord(params) {
4977
+ function renderDecisionRecord(params2) {
4968
4978
  return `---
4969
- assignment: ${params.assignmentSlug}
4970
- updated: "${params.timestamp}"
4979
+ assignment: ${params2.assignmentSlug}
4980
+ updated: "${params2.timestamp}"
4971
4981
  decisionCount: 0
4972
4982
  ---
4973
4983
 
@@ -4978,10 +4988,10 @@ No decisions recorded yet.
4978
4988
  }
4979
4989
 
4980
4990
  // src/templates/index-stubs.ts
4981
- function renderIndexAssignments(params) {
4991
+ function renderIndexAssignments(params2) {
4982
4992
  return `---
4983
- project: ${params.slug}
4984
- generated: "${params.timestamp}"
4993
+ project: ${params2.slug}
4994
+ generated: "${params2.timestamp}"
4985
4995
  total: 0
4986
4996
  by_status:
4987
4997
  pending: 0
@@ -4998,10 +5008,10 @@ by_status:
4998
5008
  |------|-------|--------|----------|----------|--------------|---------|
4999
5009
  `;
5000
5010
  }
5001
- function renderIndexPlans(params) {
5011
+ function renderIndexPlans(params2) {
5002
5012
  return `---
5003
- project: ${params.slug}
5004
- generated: "${params.timestamp}"
5013
+ project: ${params2.slug}
5014
+ generated: "${params2.timestamp}"
5005
5015
  ---
5006
5016
 
5007
5017
  # Plans
@@ -5010,10 +5020,10 @@ generated: "${params.timestamp}"
5010
5020
  |------------|-------------|---------|
5011
5021
  `;
5012
5022
  }
5013
- function renderIndexDecisions(params) {
5023
+ function renderIndexDecisions(params2) {
5014
5024
  return `---
5015
- project: ${params.slug}
5016
- generated: "${params.timestamp}"
5025
+ project: ${params2.slug}
5026
+ generated: "${params2.timestamp}"
5017
5027
  ---
5018
5028
 
5019
5029
  # Decision Records
@@ -5022,10 +5032,10 @@ generated: "${params.timestamp}"
5022
5032
  |------------|-------|-----------------|---------------|---------|
5023
5033
  `;
5024
5034
  }
5025
- function renderStatus(params) {
5035
+ function renderStatus(params2) {
5026
5036
  return `---
5027
- project: ${params.slug}
5028
- generated: "${params.timestamp}"
5037
+ project: ${params2.slug}
5038
+ generated: "${params2.timestamp}"
5029
5039
  status: pending
5030
5040
  progress:
5031
5041
  total: 0
@@ -5041,7 +5051,7 @@ needsAttention:
5041
5051
  openQuestions: 0
5042
5052
  ---
5043
5053
 
5044
- # Project Status: ${params.title}
5054
+ # Project Status: ${params2.title}
5045
5055
 
5046
5056
  **Status:** pending
5047
5057
  **Progress:** 0/0 assignments complete
@@ -5061,10 +5071,10 @@ No dependencies yet.
5061
5071
  - **0 unanswered** questions
5062
5072
  `;
5063
5073
  }
5064
- function renderResourcesIndex(params) {
5074
+ function renderResourcesIndex(params2) {
5065
5075
  return `---
5066
- project: ${params.slug}
5067
- generated: "${params.timestamp}"
5076
+ project: ${params2.slug}
5077
+ generated: "${params2.timestamp}"
5068
5078
  total: 0
5069
5079
  ---
5070
5080
 
@@ -5074,10 +5084,10 @@ total: 0
5074
5084
  |------|----------|--------|---------------------|---------|
5075
5085
  `;
5076
5086
  }
5077
- function renderMemoriesIndex(params) {
5087
+ function renderMemoriesIndex(params2) {
5078
5088
  return `---
5079
- project: ${params.slug}
5080
- generated: "${params.timestamp}"
5089
+ project: ${params2.slug}
5090
+ generated: "${params2.timestamp}"
5081
5091
  total: 0
5082
5092
  ---
5083
5093
 
@@ -5089,19 +5099,19 @@ total: 0
5089
5099
  }
5090
5100
 
5091
5101
  // src/templates/playbook.ts
5092
- function renderPlaybook(params) {
5093
- const whenToUse = params.whenToUse ? escapeYamlString(params.whenToUse) : "null";
5102
+ function renderPlaybook(params2) {
5103
+ const whenToUse = params2.whenToUse ? escapeYamlString(params2.whenToUse) : "null";
5094
5104
  return `---
5095
- name: ${escapeYamlString(params.name)}
5096
- slug: ${params.slug}
5097
- description: ${escapeYamlString(params.description)}
5105
+ name: ${escapeYamlString(params2.name)}
5106
+ slug: ${params2.slug}
5107
+ description: ${escapeYamlString(params2.description)}
5098
5108
  when_to_use: ${whenToUse}
5099
- created: "${params.timestamp}"
5100
- updated: "${params.timestamp}"
5109
+ created: "${params2.timestamp}"
5110
+ updated: "${params2.timestamp}"
5101
5111
  tags: []
5102
5112
  ---
5103
5113
 
5104
- # ${params.name}
5114
+ # ${params2.name}
5105
5115
 
5106
5116
  <!-- Write imperative rules and workflows here. Keep it under 50 lines. -->
5107
5117
  `;
@@ -6987,14 +6997,17 @@ function getWorkspaceParam(value) {
6987
6997
  return value ?? "";
6988
6998
  }
6989
6999
  var writeLocks = /* @__PURE__ */ new Map();
6990
- function withLock(workspace, fn) {
6991
- const prev = writeLocks.get(workspace) ?? Promise.resolve();
7000
+ function withLock(lockKey, fn) {
7001
+ const prev = writeLocks.get(lockKey) ?? Promise.resolve();
6992
7002
  const next = prev.then(fn);
6993
- writeLocks.set(workspace, next.then(() => {
7003
+ writeLocks.set(lockKey, next.then(() => {
6994
7004
  }, () => {
6995
7005
  }));
6996
7006
  return next;
6997
7007
  }
7008
+ function wsLock(workspace, fn) {
7009
+ return withLock(`ws:${workspace}`, fn);
7010
+ }
6998
7011
  function createTodosRouter(todosDir2, broadcast) {
6999
7012
  const router = Router5();
7000
7013
  function broadcastUpdate() {
@@ -7053,7 +7066,7 @@ function createTodosRouter(todosDir2, broadcast) {
7053
7066
  res.status(400).json({ error: "description is required" });
7054
7067
  return;
7055
7068
  }
7056
- const item = await withLock(workspace, async () => {
7069
+ const item = await wsLock(workspace, async () => {
7057
7070
  const checklist = await readChecklist(todosDir2, workspace);
7058
7071
  const existingIds = new Set(checklist.items.map((i) => i.id));
7059
7072
  const id = generateUniqueId(existingIds);
@@ -7082,7 +7095,7 @@ function createTodosRouter(todosDir2, broadcast) {
7082
7095
  res.status(400).json({ error: "ids must be an array of strings" });
7083
7096
  return;
7084
7097
  }
7085
- const items = await withLock(workspace, async () => {
7098
+ const items = await wsLock(workspace, async () => {
7086
7099
  const checklist = await readChecklist(todosDir2, workspace);
7087
7100
  const itemMap = new Map(checklist.items.map((i) => [i.id, i]));
7088
7101
  const reordered = [];
@@ -7117,8 +7130,8 @@ function createTodosRouter(todosDir2, broadcast) {
7117
7130
  router.post("/:workspace/archive", async (req, res) => {
7118
7131
  try {
7119
7132
  const { archivePath: archivePath2 } = await Promise.resolve().then(() => (init_parser2(), parser_exports));
7120
- const { resolve: resolve18 } = await import("path");
7121
- const { readFile: readFile14 } = await import("fs/promises");
7133
+ const { resolve: resolve19 } = await import("path");
7134
+ const { readFile: readFile15 } = await import("fs/promises");
7122
7135
  const { writeFileForce: writeFileForce2 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
7123
7136
  const workspace = getWorkspaceParam(req.params.workspace);
7124
7137
  const checklist = await readChecklist(todosDir2, workspace);
@@ -7134,10 +7147,10 @@ function createTodosRouter(todosDir2, broadcast) {
7134
7147
  (e) => e.itemIds.every((id) => completedIds.has(id))
7135
7148
  );
7136
7149
  const archFile = archivePath2(todosDir2, workspace, checklist.archiveInterval);
7137
- await ensureDir(resolve18(todosDir2, "archive"));
7150
+ await ensureDir(resolve19(todosDir2, "archive"));
7138
7151
  let archContent = "";
7139
7152
  if (await fileExists(archFile)) {
7140
- archContent = await readFile14(archFile, "utf-8");
7153
+ archContent = await readFile15(archFile, "utf-8");
7141
7154
  archContent = archContent.trimEnd() + "\n\n";
7142
7155
  } else {
7143
7156
  archContent = `---
@@ -7206,7 +7219,7 @@ workspace: ${workspace}
7206
7219
  router.patch("/:workspace/:id", async (req, res) => {
7207
7220
  try {
7208
7221
  const workspace = getWorkspaceParam(req.params.workspace);
7209
- const result = await withLock(workspace, async () => {
7222
+ const result = await wsLock(workspace, async () => {
7210
7223
  const checklist = await readChecklist(todosDir2, workspace);
7211
7224
  const item = checklist.items.find((i) => i.id === req.params.id);
7212
7225
  if (!item) return null;
@@ -7228,7 +7241,7 @@ workspace: ${workspace}
7228
7241
  router.delete("/:workspace/:id", async (req, res) => {
7229
7242
  try {
7230
7243
  const workspace = getWorkspaceParam(req.params.workspace);
7231
- const deleted = await withLock(workspace, async () => {
7244
+ const deleted = await wsLock(workspace, async () => {
7232
7245
  const checklist = await readChecklist(todosDir2, workspace);
7233
7246
  const idx = checklist.items.findIndex((i) => i.id === req.params.id);
7234
7247
  if (idx === -1) return false;
@@ -7249,7 +7262,7 @@ workspace: ${workspace}
7249
7262
  router.post("/:workspace/:id/start", async (req, res) => {
7250
7263
  try {
7251
7264
  const workspace = getWorkspaceParam(req.params.workspace);
7252
- const result = await withLock(workspace, async () => {
7265
+ const result = await wsLock(workspace, async () => {
7253
7266
  const checklist = await readChecklist(todosDir2, workspace);
7254
7267
  const item = checklist.items.find((i) => i.id === req.params.id);
7255
7268
  if (!item) return { error: "not_found" };
@@ -7276,7 +7289,7 @@ workspace: ${workspace}
7276
7289
  router.post("/:workspace/:id/complete", async (req, res) => {
7277
7290
  try {
7278
7291
  const workspace = getWorkspaceParam(req.params.workspace);
7279
- const result = await withLock(workspace, async () => {
7292
+ const result = await wsLock(workspace, async () => {
7280
7293
  const checklist = await readChecklist(todosDir2, workspace);
7281
7294
  const item = checklist.items.find((i) => i.id === req.params.id);
7282
7295
  if (!item) return null;
@@ -7310,7 +7323,7 @@ workspace: ${workspace}
7310
7323
  try {
7311
7324
  const reason = req.body.reason || null;
7312
7325
  const workspace = getWorkspaceParam(req.params.workspace);
7313
- const result = await withLock(workspace, async () => {
7326
+ const result = await wsLock(workspace, async () => {
7314
7327
  const checklist = await readChecklist(todosDir2, workspace);
7315
7328
  const item = checklist.items.find((i) => i.id === req.params.id);
7316
7329
  if (!item) return null;
@@ -7343,7 +7356,7 @@ workspace: ${workspace}
7343
7356
  router.post("/:workspace/:id/reopen", async (req, res) => {
7344
7357
  try {
7345
7358
  const workspace = getWorkspaceParam(req.params.workspace);
7346
- const result = await withLock(workspace, async () => {
7359
+ const result = await wsLock(workspace, async () => {
7347
7360
  const checklist = await readChecklist(todosDir2, workspace);
7348
7361
  const item = checklist.items.find((i) => i.id === req.params.id);
7349
7362
  if (!item) return null;
@@ -7365,7 +7378,7 @@ workspace: ${workspace}
7365
7378
  router.post("/:workspace/:id/unblock", async (req, res) => {
7366
7379
  try {
7367
7380
  const workspace = getWorkspaceParam(req.params.workspace);
7368
- const result = await withLock(workspace, async () => {
7381
+ const result = await wsLock(workspace, async () => {
7369
7382
  const checklist = await readChecklist(todosDir2, workspace);
7370
7383
  const item = checklist.items.find((i) => i.id === req.params.id);
7371
7384
  if (!item) return null;
@@ -7387,9 +7400,606 @@ workspace: ${workspace}
7387
7400
  return router;
7388
7401
  }
7389
7402
 
7403
+ // src/dashboard/api-project-todos.ts
7404
+ init_parser2();
7405
+ init_fs();
7406
+ init_paths();
7407
+ import { Router as Router6 } from "express";
7408
+ import { mkdir as mkdir2, readFile as readFile13 } from "fs/promises";
7409
+ import { resolve as resolve16 } from "path";
7410
+ var writeLocks2 = /* @__PURE__ */ new Map();
7411
+ function projLock(slug, fn) {
7412
+ const key = `proj:${slug}`;
7413
+ const prev = writeLocks2.get(key) ?? Promise.resolve();
7414
+ const next = prev.then(fn);
7415
+ writeLocks2.set(key, next.then(() => {
7416
+ }, () => {
7417
+ }));
7418
+ return next;
7419
+ }
7420
+ function getProjectIdParam(value) {
7421
+ if (Array.isArray(value)) return value[0] ?? "";
7422
+ return value ?? "";
7423
+ }
7424
+ function params(req) {
7425
+ return req.params;
7426
+ }
7427
+ async function projectExists(projectsDir, slug) {
7428
+ return fileExists(resolve16(projectsDir, slug, "project.md"));
7429
+ }
7430
+ async function ensureProjectTodosDir(projectsDir, slug) {
7431
+ const todosDir2 = projectTodosDir(projectsDir, slug);
7432
+ try {
7433
+ await mkdir2(todosDir2, { recursive: false });
7434
+ } catch (err) {
7435
+ const code = err.code;
7436
+ if (code === "EEXIST") return;
7437
+ if (code === "ENOENT") {
7438
+ const e = new Error("PROJECT_GONE");
7439
+ e.code = "PROJECT_GONE";
7440
+ throw e;
7441
+ }
7442
+ throw err;
7443
+ }
7444
+ try {
7445
+ await mkdir2(resolve16(todosDir2, "archive"), { recursive: false });
7446
+ } catch (err) {
7447
+ const code = err.code;
7448
+ if (code === "EEXIST") return;
7449
+ if (code === "ENOENT") {
7450
+ const e = new Error("PROJECT_GONE");
7451
+ e.code = "PROJECT_GONE";
7452
+ throw e;
7453
+ }
7454
+ throw err;
7455
+ }
7456
+ }
7457
+ function notFound(res, slug) {
7458
+ res.status(404).json({ error: `Project "${slug}" not found` });
7459
+ }
7460
+ function createProjectTodosRouter(projectsDir, broadcast) {
7461
+ const router = Router6({ mergeParams: true });
7462
+ function broadcastUpdate(projectSlug) {
7463
+ broadcast({ type: "todos-updated", projectSlug, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
7464
+ }
7465
+ function validateProjectId(req, res, next) {
7466
+ const slug = getProjectIdParam(params(req).projectId);
7467
+ if (!slug || !isValidSlug(slug)) {
7468
+ res.status(400).json({ error: `Invalid project slug: "${slug}"` });
7469
+ return;
7470
+ }
7471
+ next();
7472
+ }
7473
+ router.use(validateProjectId);
7474
+ router.get("/", async (req, res) => {
7475
+ try {
7476
+ const slug = getProjectIdParam(params(req).projectId);
7477
+ if (!await projectExists(projectsDir, slug)) {
7478
+ notFound(res, slug);
7479
+ return;
7480
+ }
7481
+ const todosDir2 = projectTodosDir(projectsDir, slug);
7482
+ const checklist = await readChecklist(todosDir2, slug);
7483
+ res.json({
7484
+ workspace: checklist.workspace,
7485
+ archiveInterval: checklist.archiveInterval,
7486
+ items: checklist.items,
7487
+ counts: computeCounts(checklist.items)
7488
+ });
7489
+ } catch (error) {
7490
+ res.status(500).json({ error: error instanceof Error ? error.message : "Failed to get todos" });
7491
+ }
7492
+ });
7493
+ router.post("/", async (req, res) => {
7494
+ try {
7495
+ const slug = getProjectIdParam(params(req).projectId);
7496
+ const { description, tags } = req.body;
7497
+ if (!description || typeof description !== "string") {
7498
+ res.status(400).json({ error: "description is required" });
7499
+ return;
7500
+ }
7501
+ if (!await projectExists(projectsDir, slug)) {
7502
+ notFound(res, slug);
7503
+ return;
7504
+ }
7505
+ const item = await projLock(slug, async () => {
7506
+ if (!await projectExists(projectsDir, slug)) return null;
7507
+ await ensureProjectTodosDir(projectsDir, slug);
7508
+ const todosDir2 = projectTodosDir(projectsDir, slug);
7509
+ const checklist = await readChecklist(todosDir2, slug);
7510
+ const existingIds = new Set(checklist.items.map((i) => i.id));
7511
+ const id = generateUniqueId(existingIds);
7512
+ const newItem = {
7513
+ id,
7514
+ description,
7515
+ status: "open",
7516
+ tags: Array.isArray(tags) ? tags : [],
7517
+ session: null
7518
+ };
7519
+ checklist.workspace = slug;
7520
+ checklist.items.push(newItem);
7521
+ await writeChecklist(todosDir2, checklist);
7522
+ return newItem;
7523
+ });
7524
+ if (!item) {
7525
+ notFound(res, slug);
7526
+ return;
7527
+ }
7528
+ broadcastUpdate(slug);
7529
+ res.status(201).json(item);
7530
+ } catch (error) {
7531
+ if (error.code === "PROJECT_GONE") {
7532
+ notFound(res, getProjectIdParam(params(req).projectId));
7533
+ return;
7534
+ }
7535
+ res.status(500).json({ error: error instanceof Error ? error.message : "Failed to add todo" });
7536
+ }
7537
+ });
7538
+ router.post("/reorder", async (req, res) => {
7539
+ try {
7540
+ const slug = getProjectIdParam(params(req).projectId);
7541
+ const { ids } = req.body;
7542
+ if (!Array.isArray(ids) || !ids.every((id) => typeof id === "string")) {
7543
+ res.status(400).json({ error: "ids must be an array of strings" });
7544
+ return;
7545
+ }
7546
+ if (!await projectExists(projectsDir, slug)) {
7547
+ notFound(res, slug);
7548
+ return;
7549
+ }
7550
+ const items = await projLock(slug, async () => {
7551
+ if (!await projectExists(projectsDir, slug)) return null;
7552
+ await ensureProjectTodosDir(projectsDir, slug);
7553
+ const todosDir2 = projectTodosDir(projectsDir, slug);
7554
+ const checklist = await readChecklist(todosDir2, slug);
7555
+ const itemMap = new Map(checklist.items.map((i) => [i.id, i]));
7556
+ const reordered = [];
7557
+ for (const id of ids) {
7558
+ const item = itemMap.get(id);
7559
+ if (item) {
7560
+ reordered.push(item);
7561
+ itemMap.delete(id);
7562
+ }
7563
+ }
7564
+ for (const item of itemMap.values()) reordered.push(item);
7565
+ checklist.workspace = slug;
7566
+ checklist.items = reordered;
7567
+ await writeChecklist(todosDir2, checklist);
7568
+ return reordered;
7569
+ });
7570
+ if (!items) {
7571
+ notFound(res, slug);
7572
+ return;
7573
+ }
7574
+ broadcastUpdate(slug);
7575
+ res.json({ items });
7576
+ } catch (error) {
7577
+ if (error.code === "PROJECT_GONE") {
7578
+ notFound(res, getProjectIdParam(params(req).projectId));
7579
+ return;
7580
+ }
7581
+ res.status(500).json({ error: error instanceof Error ? error.message : "Failed to reorder todos" });
7582
+ }
7583
+ });
7584
+ router.get("/log", async (req, res) => {
7585
+ try {
7586
+ const slug = getProjectIdParam(params(req).projectId);
7587
+ if (!await projectExists(projectsDir, slug)) {
7588
+ notFound(res, slug);
7589
+ return;
7590
+ }
7591
+ const todosDir2 = projectTodosDir(projectsDir, slug);
7592
+ const log = await readLog(todosDir2, slug);
7593
+ res.json(log);
7594
+ } catch (error) {
7595
+ res.status(500).json({ error: error instanceof Error ? error.message : "Failed to get log" });
7596
+ }
7597
+ });
7598
+ router.post("/archive", async (req, res) => {
7599
+ try {
7600
+ const slug = getProjectIdParam(params(req).projectId);
7601
+ if (!await projectExists(projectsDir, slug)) {
7602
+ notFound(res, slug);
7603
+ return;
7604
+ }
7605
+ const todosDir2 = projectTodosDir(projectsDir, slug);
7606
+ await ensureProjectTodosDir(projectsDir, slug);
7607
+ const checklist = await readChecklist(todosDir2, slug);
7608
+ const log = await readLog(todosDir2, slug);
7609
+ const completedIds = new Set(
7610
+ checklist.items.filter((i) => i.status === "completed").map((i) => i.id)
7611
+ );
7612
+ if (completedIds.size === 0) {
7613
+ res.json({ archived: 0, message: "No completed items to archive" });
7614
+ return;
7615
+ }
7616
+ const toArchive = log.entries.filter(
7617
+ (e) => e.itemIds.every((id) => completedIds.has(id))
7618
+ );
7619
+ const archFile = archivePath(todosDir2, slug, checklist.archiveInterval);
7620
+ let archContent = "";
7621
+ if (await fileExists(archFile)) {
7622
+ archContent = await readFile13(archFile, "utf-8");
7623
+ archContent = archContent.trimEnd() + "\n\n";
7624
+ } else {
7625
+ archContent = `---
7626
+ workspace: ${slug}
7627
+ ---
7628
+
7629
+ # Archive
7630
+
7631
+ `;
7632
+ }
7633
+ const completedItems = checklist.items.filter((i) => completedIds.has(i.id));
7634
+ for (const item of completedItems) {
7635
+ archContent += `- [x] ${item.description} ${item.tags.map((t) => `#${t}`).join(" ")} [t:${item.id}]
7636
+ `;
7637
+ }
7638
+ archContent += "\n";
7639
+ for (const entry of toArchive) {
7640
+ archContent += `### ${entry.timestamp} \u2014 ${entry.itemIds.map((i) => `t:${i}`).join(", ")}
7641
+ `;
7642
+ if (entry.items) archContent += `**Items:** ${entry.items}
7643
+ `;
7644
+ if (entry.session) archContent += `**Session:** ${entry.session}
7645
+ `;
7646
+ if (entry.branch) archContent += `**Branch:** ${entry.branch}
7647
+ `;
7648
+ if (entry.summary) archContent += `**Summary:** ${entry.summary}
7649
+ `;
7650
+ if (entry.blockers) archContent += `**Blockers:** ${entry.blockers}
7651
+ `;
7652
+ archContent += "\n";
7653
+ }
7654
+ await writeFileForce(archFile, archContent);
7655
+ checklist.workspace = slug;
7656
+ checklist.items = checklist.items.filter((i) => !completedIds.has(i.id));
7657
+ await writeChecklist(todosDir2, checklist);
7658
+ broadcastUpdate(slug);
7659
+ res.json({ archived: completedIds.size, logEntries: toArchive.length });
7660
+ } catch (error) {
7661
+ if (error.code === "PROJECT_GONE") {
7662
+ notFound(res, getProjectIdParam(params(req).projectId));
7663
+ return;
7664
+ }
7665
+ res.status(500).json({ error: error instanceof Error ? error.message : "Failed to archive" });
7666
+ }
7667
+ });
7668
+ router.get("/log/:id", async (req, res) => {
7669
+ try {
7670
+ const slug = getProjectIdParam(params(req).projectId);
7671
+ if (!await projectExists(projectsDir, slug)) {
7672
+ notFound(res, slug);
7673
+ return;
7674
+ }
7675
+ const todosDir2 = projectTodosDir(projectsDir, slug);
7676
+ const log = await readLog(todosDir2, slug);
7677
+ const entries = log.entries.filter((e) => e.itemIds.includes(params(req).id ?? ""));
7678
+ res.json({ workspace: log.workspace, entries });
7679
+ } catch (error) {
7680
+ res.status(500).json({ error: error instanceof Error ? error.message : "Failed to get log" });
7681
+ }
7682
+ });
7683
+ router.get("/:id", async (req, res) => {
7684
+ try {
7685
+ const slug = getProjectIdParam(params(req).projectId);
7686
+ if (!await projectExists(projectsDir, slug)) {
7687
+ notFound(res, slug);
7688
+ return;
7689
+ }
7690
+ const todosDir2 = projectTodosDir(projectsDir, slug);
7691
+ const checklist = await readChecklist(todosDir2, slug);
7692
+ const item = checklist.items.find((i) => i.id === (params(req).id ?? ""));
7693
+ if (!item) {
7694
+ res.status(404).json({ error: `Todo "${params(req).id ?? ""}" not found` });
7695
+ return;
7696
+ }
7697
+ const log = await readLog(todosDir2, slug);
7698
+ const logEntries = log.entries.filter((e) => e.itemIds.includes(params(req).id ?? ""));
7699
+ res.json({ ...item, log: logEntries });
7700
+ } catch (error) {
7701
+ res.status(500).json({ error: error instanceof Error ? error.message : "Failed to get todo" });
7702
+ }
7703
+ });
7704
+ router.patch("/:id", async (req, res) => {
7705
+ try {
7706
+ const slug = getProjectIdParam(params(req).projectId);
7707
+ if (!await projectExists(projectsDir, slug)) {
7708
+ notFound(res, slug);
7709
+ return;
7710
+ }
7711
+ const result = await projLock(slug, async () => {
7712
+ if (!await projectExists(projectsDir, slug)) return "gone";
7713
+ await ensureProjectTodosDir(projectsDir, slug);
7714
+ const todosDir2 = projectTodosDir(projectsDir, slug);
7715
+ const checklist = await readChecklist(todosDir2, slug);
7716
+ const item = checklist.items.find((i) => i.id === (params(req).id ?? ""));
7717
+ if (!item) return null;
7718
+ if (req.body.description !== void 0) item.description = req.body.description;
7719
+ if (Array.isArray(req.body.tags)) item.tags = req.body.tags;
7720
+ checklist.workspace = slug;
7721
+ await writeChecklist(todosDir2, checklist);
7722
+ return { ...item };
7723
+ });
7724
+ if (result === "gone") {
7725
+ notFound(res, slug);
7726
+ return;
7727
+ }
7728
+ if (!result) {
7729
+ res.status(404).json({ error: `Todo "${params(req).id ?? ""}" not found` });
7730
+ return;
7731
+ }
7732
+ broadcastUpdate(slug);
7733
+ res.json(result);
7734
+ } catch (error) {
7735
+ if (error.code === "PROJECT_GONE") {
7736
+ notFound(res, getProjectIdParam(params(req).projectId));
7737
+ return;
7738
+ }
7739
+ res.status(500).json({ error: error instanceof Error ? error.message : "Failed to update todo" });
7740
+ }
7741
+ });
7742
+ router.delete("/:id", async (req, res) => {
7743
+ try {
7744
+ const slug = getProjectIdParam(params(req).projectId);
7745
+ if (!await projectExists(projectsDir, slug)) {
7746
+ notFound(res, slug);
7747
+ return;
7748
+ }
7749
+ const deleted = await projLock(slug, async () => {
7750
+ if (!await projectExists(projectsDir, slug)) return "gone";
7751
+ await ensureProjectTodosDir(projectsDir, slug);
7752
+ const todosDir2 = projectTodosDir(projectsDir, slug);
7753
+ const checklist = await readChecklist(todosDir2, slug);
7754
+ const idx = checklist.items.findIndex((i) => i.id === (params(req).id ?? ""));
7755
+ if (idx === -1) return false;
7756
+ checklist.items.splice(idx, 1);
7757
+ checklist.workspace = slug;
7758
+ await writeChecklist(todosDir2, checklist);
7759
+ return true;
7760
+ });
7761
+ if (deleted === "gone") {
7762
+ notFound(res, slug);
7763
+ return;
7764
+ }
7765
+ if (!deleted) {
7766
+ res.status(404).json({ error: `Todo "${params(req).id ?? ""}" not found` });
7767
+ return;
7768
+ }
7769
+ broadcastUpdate(slug);
7770
+ res.json({ deleted: params(req).id ?? "" });
7771
+ } catch (error) {
7772
+ if (error.code === "PROJECT_GONE") {
7773
+ notFound(res, getProjectIdParam(params(req).projectId));
7774
+ return;
7775
+ }
7776
+ res.status(500).json({ error: error instanceof Error ? error.message : "Failed to delete todo" });
7777
+ }
7778
+ });
7779
+ router.post("/:id/start", async (req, res) => {
7780
+ try {
7781
+ const slug = getProjectIdParam(params(req).projectId);
7782
+ if (!await projectExists(projectsDir, slug)) {
7783
+ notFound(res, slug);
7784
+ return;
7785
+ }
7786
+ const result = await projLock(slug, async () => {
7787
+ if (!await projectExists(projectsDir, slug)) return { error: "gone" };
7788
+ await ensureProjectTodosDir(projectsDir, slug);
7789
+ const todosDir2 = projectTodosDir(projectsDir, slug);
7790
+ const checklist = await readChecklist(todosDir2, slug);
7791
+ const item = checklist.items.find((i) => i.id === (params(req).id ?? ""));
7792
+ if (!item) return { error: "not_found" };
7793
+ if (item.status === "in_progress") return { error: "conflict", session: item.session };
7794
+ item.status = "in_progress";
7795
+ item.session = req.body.session || null;
7796
+ checklist.workspace = slug;
7797
+ await writeChecklist(todosDir2, checklist);
7798
+ return { item: { ...item } };
7799
+ });
7800
+ if ("error" in result) {
7801
+ if (result.error === "gone") {
7802
+ notFound(res, slug);
7803
+ return;
7804
+ }
7805
+ if (result.error === "not_found") {
7806
+ res.status(404).json({ error: `Todo "${params(req).id ?? ""}" not found` });
7807
+ return;
7808
+ }
7809
+ res.status(409).json({ error: `Todo is already in progress (session: ${result.session})` });
7810
+ return;
7811
+ }
7812
+ broadcastUpdate(slug);
7813
+ res.json(result.item);
7814
+ } catch (error) {
7815
+ if (error.code === "PROJECT_GONE") {
7816
+ notFound(res, getProjectIdParam(params(req).projectId));
7817
+ return;
7818
+ }
7819
+ res.status(500).json({ error: error instanceof Error ? error.message : "Failed to start todo" });
7820
+ }
7821
+ });
7822
+ router.post("/:id/complete", async (req, res) => {
7823
+ try {
7824
+ const slug = getProjectIdParam(params(req).projectId);
7825
+ if (!await projectExists(projectsDir, slug)) {
7826
+ notFound(res, slug);
7827
+ return;
7828
+ }
7829
+ const result = await projLock(slug, async () => {
7830
+ if (!await projectExists(projectsDir, slug)) return "gone";
7831
+ await ensureProjectTodosDir(projectsDir, slug);
7832
+ const todosDir2 = projectTodosDir(projectsDir, slug);
7833
+ const checklist = await readChecklist(todosDir2, slug);
7834
+ const item = checklist.items.find((i) => i.id === (params(req).id ?? ""));
7835
+ if (!item) return null;
7836
+ item.status = "completed";
7837
+ item.session = null;
7838
+ checklist.workspace = slug;
7839
+ await writeChecklist(todosDir2, checklist);
7840
+ const entry = {
7841
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
7842
+ itemIds: [item.id],
7843
+ items: item.description,
7844
+ session: req.body.session || null,
7845
+ branch: req.body.branch || null,
7846
+ summary: req.body.summary || "Completed.",
7847
+ blockers: null,
7848
+ status: null
7849
+ };
7850
+ await appendLogEntry2(todosDir2, slug, entry);
7851
+ return { ...item };
7852
+ });
7853
+ if (result === "gone") {
7854
+ notFound(res, slug);
7855
+ return;
7856
+ }
7857
+ if (!result) {
7858
+ res.status(404).json({ error: `Todo "${params(req).id ?? ""}" not found` });
7859
+ return;
7860
+ }
7861
+ broadcastUpdate(slug);
7862
+ res.json(result);
7863
+ } catch (error) {
7864
+ if (error.code === "PROJECT_GONE") {
7865
+ notFound(res, getProjectIdParam(params(req).projectId));
7866
+ return;
7867
+ }
7868
+ res.status(500).json({ error: error instanceof Error ? error.message : "Failed to complete todo" });
7869
+ }
7870
+ });
7871
+ router.post("/:id/block", async (req, res) => {
7872
+ try {
7873
+ const slug = getProjectIdParam(params(req).projectId);
7874
+ const reason = req.body.reason || null;
7875
+ if (!await projectExists(projectsDir, slug)) {
7876
+ notFound(res, slug);
7877
+ return;
7878
+ }
7879
+ const result = await projLock(slug, async () => {
7880
+ if (!await projectExists(projectsDir, slug)) return "gone";
7881
+ await ensureProjectTodosDir(projectsDir, slug);
7882
+ const todosDir2 = projectTodosDir(projectsDir, slug);
7883
+ const checklist = await readChecklist(todosDir2, slug);
7884
+ const item = checklist.items.find((i) => i.id === (params(req).id ?? ""));
7885
+ if (!item) return null;
7886
+ item.status = "blocked";
7887
+ item.session = null;
7888
+ checklist.workspace = slug;
7889
+ await writeChecklist(todosDir2, checklist);
7890
+ const entry = {
7891
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
7892
+ itemIds: [item.id],
7893
+ items: item.description,
7894
+ session: req.body.session || null,
7895
+ branch: null,
7896
+ summary: reason || "Blocked.",
7897
+ blockers: reason,
7898
+ status: "blocked"
7899
+ };
7900
+ await appendLogEntry2(todosDir2, slug, entry);
7901
+ return { ...item };
7902
+ });
7903
+ if (result === "gone") {
7904
+ notFound(res, slug);
7905
+ return;
7906
+ }
7907
+ if (!result) {
7908
+ res.status(404).json({ error: `Todo "${params(req).id ?? ""}" not found` });
7909
+ return;
7910
+ }
7911
+ broadcastUpdate(slug);
7912
+ res.json(result);
7913
+ } catch (error) {
7914
+ if (error.code === "PROJECT_GONE") {
7915
+ notFound(res, getProjectIdParam(params(req).projectId));
7916
+ return;
7917
+ }
7918
+ res.status(500).json({ error: error instanceof Error ? error.message : "Failed to block todo" });
7919
+ }
7920
+ });
7921
+ router.post("/:id/reopen", async (req, res) => {
7922
+ try {
7923
+ const slug = getProjectIdParam(params(req).projectId);
7924
+ if (!await projectExists(projectsDir, slug)) {
7925
+ notFound(res, slug);
7926
+ return;
7927
+ }
7928
+ const result = await projLock(slug, async () => {
7929
+ if (!await projectExists(projectsDir, slug)) return "gone";
7930
+ await ensureProjectTodosDir(projectsDir, slug);
7931
+ const todosDir2 = projectTodosDir(projectsDir, slug);
7932
+ const checklist = await readChecklist(todosDir2, slug);
7933
+ const item = checklist.items.find((i) => i.id === (params(req).id ?? ""));
7934
+ if (!item) return null;
7935
+ item.status = "open";
7936
+ item.session = null;
7937
+ checklist.workspace = slug;
7938
+ await writeChecklist(todosDir2, checklist);
7939
+ return { ...item };
7940
+ });
7941
+ if (result === "gone") {
7942
+ notFound(res, slug);
7943
+ return;
7944
+ }
7945
+ if (!result) {
7946
+ res.status(404).json({ error: `Todo "${params(req).id ?? ""}" not found` });
7947
+ return;
7948
+ }
7949
+ broadcastUpdate(slug);
7950
+ res.json(result);
7951
+ } catch (error) {
7952
+ if (error.code === "PROJECT_GONE") {
7953
+ notFound(res, getProjectIdParam(params(req).projectId));
7954
+ return;
7955
+ }
7956
+ res.status(500).json({ error: error instanceof Error ? error.message : "Failed to reopen todo" });
7957
+ }
7958
+ });
7959
+ router.post("/:id/unblock", async (req, res) => {
7960
+ try {
7961
+ const slug = getProjectIdParam(params(req).projectId);
7962
+ if (!await projectExists(projectsDir, slug)) {
7963
+ notFound(res, slug);
7964
+ return;
7965
+ }
7966
+ const result = await projLock(slug, async () => {
7967
+ if (!await projectExists(projectsDir, slug)) return "gone";
7968
+ await ensureProjectTodosDir(projectsDir, slug);
7969
+ const todosDir2 = projectTodosDir(projectsDir, slug);
7970
+ const checklist = await readChecklist(todosDir2, slug);
7971
+ const item = checklist.items.find((i) => i.id === (params(req).id ?? ""));
7972
+ if (!item) return null;
7973
+ item.status = "open";
7974
+ item.session = null;
7975
+ checklist.workspace = slug;
7976
+ await writeChecklist(todosDir2, checklist);
7977
+ return { ...item };
7978
+ });
7979
+ if (result === "gone") {
7980
+ notFound(res, slug);
7981
+ return;
7982
+ }
7983
+ if (!result) {
7984
+ res.status(404).json({ error: `Todo "${params(req).id ?? ""}" not found` });
7985
+ return;
7986
+ }
7987
+ broadcastUpdate(slug);
7988
+ res.json(result);
7989
+ } catch (error) {
7990
+ if (error.code === "PROJECT_GONE") {
7991
+ notFound(res, getProjectIdParam(params(req).projectId));
7992
+ return;
7993
+ }
7994
+ res.status(500).json({ error: error instanceof Error ? error.message : "Failed to unblock todo" });
7995
+ }
7996
+ });
7997
+ return router;
7998
+ }
7999
+
7390
8000
  // src/dashboard/api-backup.ts
7391
8001
  init_config2();
7392
- import { Router as Router6 } from "express";
8002
+ import { Router as Router7 } from "express";
7393
8003
 
7394
8004
  // src/utils/github-backup.ts
7395
8005
  init_paths();
@@ -7397,8 +8007,8 @@ init_fs();
7397
8007
  init_config2();
7398
8008
  import { execFile as execFile2 } from "child_process";
7399
8009
  import { promisify as promisify2 } from "util";
7400
- import { cp, mkdtemp, rm as rm2, readFile as readFile13, writeFile as writeFile4, unlink as unlink3, stat, open, rename as rename3 } from "fs/promises";
7401
- import { resolve as resolve16, join as join2 } from "path";
8010
+ import { cp, mkdtemp, rm as rm2, readFile as readFile14, writeFile as writeFile4, unlink as unlink3, stat, open, rename as rename3 } from "fs/promises";
8011
+ import { resolve as resolve17, join as join2 } from "path";
7402
8012
  import { tmpdir } from "os";
7403
8013
  var exec2 = promisify2(execFile2);
7404
8014
  var VALID_CATEGORIES = ["projects", "playbooks", "todos", "servers", "config"];
@@ -7438,7 +8048,7 @@ async function resolveCategoryPath(category) {
7438
8048
  case "servers":
7439
8049
  return { sourcePath: serversDir(), repoPath: "servers", isFile: false };
7440
8050
  case "config":
7441
- return { sourcePath: resolve16(syntaurRoot(), "config.md"), repoPath: "config.md", isFile: true };
8051
+ return { sourcePath: resolve17(syntaurRoot(), "config.md"), repoPath: "config.md", isFile: true };
7442
8052
  }
7443
8053
  }
7444
8054
  async function checkGitInstalled() {
@@ -7449,7 +8059,7 @@ async function checkGitInstalled() {
7449
8059
  }
7450
8060
  }
7451
8061
  async function acquireLock() {
7452
- const lockPath = resolve16(syntaurRoot(), LOCK_FILE_NAME);
8062
+ const lockPath = resolve17(syntaurRoot(), LOCK_FILE_NAME);
7453
8063
  await ensureDir(syntaurRoot());
7454
8064
  try {
7455
8065
  const handle = await open(lockPath, "wx");
@@ -7458,7 +8068,7 @@ async function acquireLock() {
7458
8068
  return lockPath;
7459
8069
  } catch (err) {
7460
8070
  if (err.code === "EEXIST") {
7461
- const pid = await readFile13(lockPath, "utf-8").catch(() => "");
8071
+ const pid = await readFile14(lockPath, "utf-8").catch(() => "");
7462
8072
  throw new Error(
7463
8073
  `Backup operation already in progress (lock file at ${lockPath}, pid ${pid.trim() || "unknown"}). If stale, delete the file and retry.`
7464
8074
  );
@@ -7496,7 +8106,7 @@ async function copyRecursive(src, dest) {
7496
8106
  await ensureDir(dest);
7497
8107
  await cp(src, dest, { recursive: true, force: true });
7498
8108
  } else {
7499
- await ensureDir(resolve16(dest, ".."));
8109
+ await ensureDir(resolve17(dest, ".."));
7500
8110
  await cp(src, dest, { force: true });
7501
8111
  }
7502
8112
  }
@@ -7505,7 +8115,7 @@ function resolveCategoriesStrict(csv) {
7505
8115
  return parseCategoriesStrict(parts);
7506
8116
  }
7507
8117
  async function readSanitizedConfig(configPath) {
7508
- const content = await readFile13(configPath, "utf-8");
8118
+ const content = await readFile14(configPath, "utf-8");
7509
8119
  return content.replace(/^(\s*lastBackup:\s*).*$/m, "$1null").replace(/^(\s*lastRestore:\s*).*$/m, "$1null");
7510
8120
  }
7511
8121
  async function backupToGithub(overrides) {
@@ -7544,7 +8154,7 @@ async function backupToGithub(overrides) {
7544
8154
  }
7545
8155
  if (category === "config") {
7546
8156
  const sanitized = await readSanitizedConfig(sourcePath);
7547
- await ensureDir(resolve16(destPath, ".."));
8157
+ await ensureDir(resolve17(destPath, ".."));
7548
8158
  await writeFile4(destPath, sanitized, "utf-8");
7549
8159
  } else {
7550
8160
  await copyRecursive(sourcePath, destPath);
@@ -7598,7 +8208,7 @@ async function backupToGithub(overrides) {
7598
8208
  }
7599
8209
  async function safeRestoreCategory(localPath, repoSrcPath, isFile) {
7600
8210
  if (isFile) {
7601
- await ensureDir(resolve16(localPath, ".."));
8211
+ await ensureDir(resolve17(localPath, ".."));
7602
8212
  await cp(repoSrcPath, localPath, { force: true });
7603
8213
  return;
7604
8214
  }
@@ -7699,7 +8309,7 @@ async function restoreFromGithub(overrides) {
7699
8309
  }
7700
8310
  async function getBackupStatus() {
7701
8311
  const config = await readConfig();
7702
- const lockPath = resolve16(syntaurRoot(), LOCK_FILE_NAME);
8312
+ const lockPath = resolve17(syntaurRoot(), LOCK_FILE_NAME);
7703
8313
  const locked = await fileExists(lockPath);
7704
8314
  return {
7705
8315
  repo: config.backup?.repo ?? null,
@@ -7712,7 +8322,7 @@ async function getBackupStatus() {
7712
8322
 
7713
8323
  // src/dashboard/api-backup.ts
7714
8324
  function createBackupRouter() {
7715
- const router = Router6();
8325
+ const router = Router7();
7716
8326
  router.get("/", async (_req, res) => {
7717
8327
  try {
7718
8328
  const status = await getBackupStatus();
@@ -8036,7 +8646,7 @@ function createDashboardServer(options) {
8036
8646
  (async () => {
8037
8647
  try {
8038
8648
  const configResult = await migrateLegacyConfig(
8039
- resolve17(syntaurRoot(), "config.md")
8649
+ resolve18(syntaurRoot(), "config.md")
8040
8650
  );
8041
8651
  const projectResult = await migrateLegacyProjectFiles(projectsDir);
8042
8652
  const summary = summarizeMigration(projectResult, configResult);
@@ -8260,15 +8870,16 @@ function createDashboardServer(options) {
8260
8870
  app.use("/api/agent-sessions", createAgentSessionsRouter(projectsDir, broadcast, assignmentsDir));
8261
8871
  app.use("/api/playbooks", createPlaybooksRouter(playbooksDir2));
8262
8872
  app.use("/api/todos", createTodosRouter(todosDir2, broadcast));
8873
+ app.use("/api/projects/:projectId/todos", createProjectTodosRouter(projectsDir, broadcast));
8263
8874
  app.use("/api/backup", createBackupRouter());
8264
8875
  if (serveStaticUi && dashboardDistPath) {
8265
- app.use("/assets", express.static(resolve17(dashboardDistPath, "assets")));
8876
+ app.use("/assets", express.static(resolve18(dashboardDistPath, "assets")));
8266
8877
  app.get("{*path}", async (req, res) => {
8267
8878
  if (req.path.startsWith("/api") || req.path === "/ws" || req.path.startsWith("/assets")) {
8268
8879
  res.status(404).json({ error: "Not Found" });
8269
8880
  return;
8270
8881
  }
8271
- const indexPath = resolve17(dashboardDistPath, "index.html");
8882
+ const indexPath = resolve18(dashboardDistPath, "index.html");
8272
8883
  if (!await fileExists(indexPath)) {
8273
8884
  res.status(503).send(
8274
8885
  'Dashboard not built. Run "npm run build:dashboard" first.'
@@ -8306,7 +8917,7 @@ function createDashboardServer(options) {
8306
8917
  }
8307
8918
  });
8308
8919
  server.listen(port, () => {
8309
- const portFile = resolve17(syntaurRoot(), "dashboard-port");
8920
+ const portFile = resolve18(syntaurRoot(), "dashboard-port");
8310
8921
  writeFile5(portFile, String(port), "utf-8").catch(() => {
8311
8922
  });
8312
8923
  resolvePromise();
@@ -8323,7 +8934,7 @@ function createDashboardServer(options) {
8323
8934
  client.terminate();
8324
8935
  }
8325
8936
  clients.clear();
8326
- const portFile = resolve17(syntaurRoot(), "dashboard-port");
8937
+ const portFile = resolve18(syntaurRoot(), "dashboard-port");
8327
8938
  await unlink4(portFile).catch(() => {
8328
8939
  });
8329
8940
  server.closeAllConnections?.();