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.
- package/dashboard/dist/assets/{_basePickBy-ZY1FrcKw.js → _basePickBy-Bcut0btZ.js} +1 -1
- package/dashboard/dist/assets/{_baseUniq-C7xZB6ea.js → _baseUniq-AQSP2JEk.js} +1 -1
- package/dashboard/dist/assets/{arc-CPtDVk1A.js → arc-BLTpY9lc.js} +1 -1
- package/dashboard/dist/assets/{architectureDiagram-2XIMDMQ5-Dr5rnxwf.js → architectureDiagram-2XIMDMQ5-CJtwMY_X.js} +1 -1
- package/dashboard/dist/assets/{blockDiagram-WCTKOSBZ-SsDboTb2.js → blockDiagram-WCTKOSBZ-Don-O7X7.js} +1 -1
- package/dashboard/dist/assets/{c4Diagram-IC4MRINW-CZqjXmV0.js → c4Diagram-IC4MRINW-C_M3yTTB.js} +1 -1
- package/dashboard/dist/assets/channel-BfXmPwE5.js +1 -0
- package/dashboard/dist/assets/{chunk-4BX2VUAB-BYskd63Z.js → chunk-4BX2VUAB-CGss0jXe.js} +1 -1
- package/dashboard/dist/assets/{chunk-55IACEB6-CWLImr1E.js → chunk-55IACEB6-BatoPJga.js} +1 -1
- package/dashboard/dist/assets/{chunk-FMBD7UC4-fQXSIXhy.js → chunk-FMBD7UC4-DxH4wO82.js} +1 -1
- package/dashboard/dist/assets/{chunk-JSJVCQXG-DJXtEexG.js → chunk-JSJVCQXG-BL3izAFQ.js} +1 -1
- package/dashboard/dist/assets/{chunk-KX2RTZJC-C0ivaZ1M.js → chunk-KX2RTZJC-GnqXwnge.js} +1 -1
- package/dashboard/dist/assets/{chunk-NQ4KR5QH-DlwLjqC5.js → chunk-NQ4KR5QH-gvCn4QMb.js} +1 -1
- package/dashboard/dist/assets/{chunk-QZHKN3VN-DBEYrRqx.js → chunk-QZHKN3VN-CYGWogyi.js} +1 -1
- package/dashboard/dist/assets/{chunk-WL4C6EOR-BQyYCp1z.js → chunk-WL4C6EOR-D9mVTQ1F.js} +1 -1
- package/dashboard/dist/assets/classDiagram-VBA2DB6C-D7_G1qy0.js +1 -0
- package/dashboard/dist/assets/classDiagram-v2-RAHNMMFH-D7_G1qy0.js +1 -0
- package/dashboard/dist/assets/clone-BKG-N796.js +1 -0
- package/dashboard/dist/assets/{cose-bilkent-S5V4N54A-CAYzelqd.js → cose-bilkent-S5V4N54A-CUWQCKt4.js} +1 -1
- package/dashboard/dist/assets/{dagre-KLK3FWXG-xpTJb8E7.js → dagre-KLK3FWXG-CH3ijEvV.js} +1 -1
- package/dashboard/dist/assets/{diagram-E7M64L7V-DRDjskHd.js → diagram-E7M64L7V-sq83lpV1.js} +1 -1
- package/dashboard/dist/assets/{diagram-IFDJBPK2-B1r1ZXm3.js → diagram-IFDJBPK2-BzQG_rtq.js} +1 -1
- package/dashboard/dist/assets/{diagram-P4PSJMXO-BeE6-ZUH.js → diagram-P4PSJMXO-Dg0eZn0q.js} +1 -1
- package/dashboard/dist/assets/{erDiagram-INFDFZHY-BG6KKBm1.js → erDiagram-INFDFZHY-4b9eQ0uj.js} +1 -1
- package/dashboard/dist/assets/{flowDiagram-PKNHOUZH-CSFv3RpZ.js → flowDiagram-PKNHOUZH-C9fzKcsZ.js} +1 -1
- package/dashboard/dist/assets/{ganttDiagram-A5KZAMGK-Off4zK-k.js → ganttDiagram-A5KZAMGK-Bzt6i9SH.js} +1 -1
- package/dashboard/dist/assets/{gitGraphDiagram-K3NZZRJ6-Msv_4mYB.js → gitGraphDiagram-K3NZZRJ6-D0wFOagh.js} +1 -1
- package/dashboard/dist/assets/{graph-rPPnNEMq.js → graph-EEIGvqDh.js} +1 -1
- package/dashboard/dist/assets/index-C7f0ySJE.js +481 -0
- package/dashboard/dist/assets/{infoDiagram-LFFYTUFH-CeBirxFd.js → infoDiagram-LFFYTUFH-DLYMsj1D.js} +1 -1
- package/dashboard/dist/assets/{ishikawaDiagram-PHBUUO56-BZ_w8PcD.js → ishikawaDiagram-PHBUUO56-DVebKkzl.js} +1 -1
- package/dashboard/dist/assets/{journeyDiagram-4ABVD52K-3FqMPEfs.js → journeyDiagram-4ABVD52K-BsmgOWVw.js} +1 -1
- package/dashboard/dist/assets/{kanban-definition-K7BYSVSG-BlCmFkEx.js → kanban-definition-K7BYSVSG-BTnHf0ey.js} +1 -1
- package/dashboard/dist/assets/{layout-C50nYMhx.js → layout-BbM7HRvv.js} +1 -1
- package/dashboard/dist/assets/{linear-D2Cjmh1X.js → linear-C37bJKPO.js} +1 -1
- package/dashboard/dist/assets/{mermaid.core-BXFuNYa4.js → mermaid.core-MZ_JgnRL.js} +4 -4
- package/dashboard/dist/assets/{mindmap-definition-YRQLILUH-CowIfM07.js → mindmap-definition-YRQLILUH-CgHS4hFo.js} +1 -1
- package/dashboard/dist/assets/{pieDiagram-SKSYHLDU-DG5IQcKl.js → pieDiagram-SKSYHLDU-CmAgopJe.js} +1 -1
- package/dashboard/dist/assets/{quadrantDiagram-337W2JSQ-DKD46CSX.js → quadrantDiagram-337W2JSQ-BvzYUPR6.js} +1 -1
- package/dashboard/dist/assets/{requirementDiagram-Z7DCOOCP-Dd-qX6Ul.js → requirementDiagram-Z7DCOOCP-Bs52VP7k.js} +1 -1
- package/dashboard/dist/assets/{sankeyDiagram-WA2Y5GQK-CDAylULt.js → sankeyDiagram-WA2Y5GQK-aXvGPR1o.js} +1 -1
- package/dashboard/dist/assets/{sequenceDiagram-2WXFIKYE-D-5Jq5jy.js → sequenceDiagram-2WXFIKYE-CzgcfU6K.js} +1 -1
- package/dashboard/dist/assets/{stateDiagram-RAJIS63D-BmBOa8i0.js → stateDiagram-RAJIS63D-BXBJf9Hq.js} +1 -1
- package/dashboard/dist/assets/stateDiagram-v2-FVOUBMTO-QqOtsuOs.js +1 -0
- package/dashboard/dist/assets/{timeline-definition-YZTLITO2-C9Sdg7du.js → timeline-definition-YZTLITO2-BsXp26Ai.js} +1 -1
- package/dashboard/dist/assets/{treemap-KZPCXAKY-D-PDxUdK.js → treemap-KZPCXAKY-C3WbDii1.js} +1 -1
- package/dashboard/dist/assets/{vennDiagram-LZ73GAT5-CBO5f6MT.js → vennDiagram-LZ73GAT5-B28LMHWd.js} +1 -1
- package/dashboard/dist/assets/{xychartDiagram-JWTSCODW-4xZrquqP.js → xychartDiagram-JWTSCODW-C3Xwz8mS.js} +1 -1
- package/dashboard/dist/index.html +1 -1
- package/dist/dashboard/server.js +726 -115
- package/dist/dashboard/server.js.map +1 -1
- package/dist/index.js +1019 -381
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dashboard/dist/assets/channel-ejDeCb7i.js +0 -1
- package/dashboard/dist/assets/classDiagram-VBA2DB6C-BW_esmsF.js +0 -1
- package/dashboard/dist/assets/classDiagram-v2-RAHNMMFH-BW_esmsF.js +0 -1
- package/dashboard/dist/assets/clone-y13L00zF.js +0 -1
- package/dashboard/dist/assets/index-D_uE8gHg.js +0 -481
- package/dashboard/dist/assets/stateDiagram-v2-FVOUBMTO-Dsk6o9iT.js +0 -1
package/dist/dashboard/server.js
CHANGED
|
@@ -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(
|
|
463
|
+
function renderConfig(params2) {
|
|
461
464
|
return `---
|
|
462
465
|
version: "1.0"
|
|
463
|
-
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
|
|
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:
|
|
4329
|
-
const raw = await
|
|
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(
|
|
4767
|
+
function renderManifest(params2) {
|
|
4758
4768
|
return `---
|
|
4759
4769
|
version: "2.0"
|
|
4760
|
-
project: ${
|
|
4761
|
-
generated: "${
|
|
4770
|
+
project: ${params2.slug}
|
|
4771
|
+
generated: "${params2.timestamp}"
|
|
4762
4772
|
---
|
|
4763
4773
|
|
|
4764
|
-
# Project: ${
|
|
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(
|
|
4792
|
-
const safeTitle = escapeYamlString(
|
|
4793
|
-
const workspaceLine =
|
|
4794
|
-
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: ${
|
|
4797
|
-
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: "${
|
|
4803
|
-
updated: "${
|
|
4812
|
+
created: "${params2.timestamp}"
|
|
4813
|
+
updated: "${params2.timestamp}"
|
|
4804
4814
|
externalIds: []
|
|
4805
4815
|
tags: []${workspaceLine}
|
|
4806
4816
|
---
|
|
4807
4817
|
|
|
4808
|
-
# ${
|
|
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(
|
|
4822
|
-
const safeTitle = escapeYamlString(
|
|
4823
|
-
const dependsOnYaml =
|
|
4824
|
-
- ${
|
|
4825
|
-
const linksYaml =
|
|
4826
|
-
- ${
|
|
4827
|
-
const projectYaml = `project: ${
|
|
4828
|
-
const workspaceGroupLine =
|
|
4829
|
-
workspaceGroup: ${
|
|
4830
|
-
const typeYaml = `type: ${
|
|
4831
|
-
const todosSection =
|
|
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: ${
|
|
4844
|
-
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: ${
|
|
4850
|
-
created: "${
|
|
4851
|
-
updated: "${
|
|
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
|
-
# ${
|
|
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(
|
|
4902
|
+
function renderScratchpad(params2) {
|
|
4893
4903
|
return `---
|
|
4894
|
-
assignment: ${
|
|
4895
|
-
updated: "${
|
|
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(
|
|
4915
|
+
function renderHandoff(params2) {
|
|
4906
4916
|
return `---
|
|
4907
|
-
assignment: ${
|
|
4908
|
-
updated: "${
|
|
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(
|
|
4929
|
+
function renderProgress(params2) {
|
|
4920
4930
|
return `---
|
|
4921
|
-
assignment: ${
|
|
4931
|
+
assignment: ${params2.assignment}
|
|
4922
4932
|
entryCount: 0
|
|
4923
|
-
generated: "${
|
|
4924
|
-
updated: "${
|
|
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(
|
|
4944
|
+
function renderComments(params2) {
|
|
4935
4945
|
return `---
|
|
4936
|
-
assignment: ${
|
|
4946
|
+
assignment: ${params2.assignment}
|
|
4937
4947
|
entryCount: 0
|
|
4938
|
-
generated: "${
|
|
4939
|
-
updated: "${
|
|
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(
|
|
4977
|
+
function renderDecisionRecord(params2) {
|
|
4968
4978
|
return `---
|
|
4969
|
-
assignment: ${
|
|
4970
|
-
updated: "${
|
|
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(
|
|
4991
|
+
function renderIndexAssignments(params2) {
|
|
4982
4992
|
return `---
|
|
4983
|
-
project: ${
|
|
4984
|
-
generated: "${
|
|
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(
|
|
5011
|
+
function renderIndexPlans(params2) {
|
|
5002
5012
|
return `---
|
|
5003
|
-
project: ${
|
|
5004
|
-
generated: "${
|
|
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(
|
|
5023
|
+
function renderIndexDecisions(params2) {
|
|
5014
5024
|
return `---
|
|
5015
|
-
project: ${
|
|
5016
|
-
generated: "${
|
|
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(
|
|
5035
|
+
function renderStatus(params2) {
|
|
5026
5036
|
return `---
|
|
5027
|
-
project: ${
|
|
5028
|
-
generated: "${
|
|
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: ${
|
|
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(
|
|
5074
|
+
function renderResourcesIndex(params2) {
|
|
5065
5075
|
return `---
|
|
5066
|
-
project: ${
|
|
5067
|
-
generated: "${
|
|
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(
|
|
5087
|
+
function renderMemoriesIndex(params2) {
|
|
5078
5088
|
return `---
|
|
5079
|
-
project: ${
|
|
5080
|
-
generated: "${
|
|
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(
|
|
5093
|
-
const whenToUse =
|
|
5102
|
+
function renderPlaybook(params2) {
|
|
5103
|
+
const whenToUse = params2.whenToUse ? escapeYamlString(params2.whenToUse) : "null";
|
|
5094
5104
|
return `---
|
|
5095
|
-
name: ${escapeYamlString(
|
|
5096
|
-
slug: ${
|
|
5097
|
-
description: ${escapeYamlString(
|
|
5105
|
+
name: ${escapeYamlString(params2.name)}
|
|
5106
|
+
slug: ${params2.slug}
|
|
5107
|
+
description: ${escapeYamlString(params2.description)}
|
|
5098
5108
|
when_to_use: ${whenToUse}
|
|
5099
|
-
created: "${
|
|
5100
|
-
updated: "${
|
|
5109
|
+
created: "${params2.timestamp}"
|
|
5110
|
+
updated: "${params2.timestamp}"
|
|
5101
5111
|
tags: []
|
|
5102
5112
|
---
|
|
5103
5113
|
|
|
5104
|
-
# ${
|
|
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(
|
|
6991
|
-
const prev = writeLocks.get(
|
|
7000
|
+
function withLock(lockKey, fn) {
|
|
7001
|
+
const prev = writeLocks.get(lockKey) ?? Promise.resolve();
|
|
6992
7002
|
const next = prev.then(fn);
|
|
6993
|
-
writeLocks.set(
|
|
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
|
|
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
|
|
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:
|
|
7121
|
-
const { readFile:
|
|
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(
|
|
7150
|
+
await ensureDir(resolve19(todosDir2, "archive"));
|
|
7138
7151
|
let archContent = "";
|
|
7139
7152
|
if (await fileExists(archFile)) {
|
|
7140
|
-
archContent = await
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
7401
|
-
import { resolve as
|
|
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:
|
|
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 =
|
|
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
|
|
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(
|
|
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
|
|
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(
|
|
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(
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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(
|
|
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 =
|
|
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 =
|
|
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 =
|
|
8937
|
+
const portFile = resolve18(syntaurRoot(), "dashboard-port");
|
|
8327
8938
|
await unlink4(portFile).catch(() => {
|
|
8328
8939
|
});
|
|
8329
8940
|
server.closeAllConnections?.();
|