syntaur 0.6.0 → 0.7.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/README.md +2 -2
- package/dashboard/dist/assets/{_basePickBy-BQIP1Ca7.js → _basePickBy-DTYUlCEg.js} +1 -1
- package/dashboard/dist/assets/{_baseUniq-BnBWRwT7.js → _baseUniq-C0Y4HRd5.js} +1 -1
- package/dashboard/dist/assets/{arc-BYWL4eq0.js → arc-BFx2eqN9.js} +1 -1
- package/dashboard/dist/assets/{architectureDiagram-2XIMDMQ5-CD_SWPSa.js → architectureDiagram-2XIMDMQ5-Erol1JD6.js} +1 -1
- package/dashboard/dist/assets/{blockDiagram-WCTKOSBZ-BS1ZbFBU.js → blockDiagram-WCTKOSBZ-kSkh6VkS.js} +1 -1
- package/dashboard/dist/assets/{c4Diagram-IC4MRINW-D99yg-l2.js → c4Diagram-IC4MRINW-C04oKzvX.js} +1 -1
- package/dashboard/dist/assets/channel-C82tBKZ7.js +1 -0
- package/dashboard/dist/assets/{chunk-4BX2VUAB-BkN9IORC.js → chunk-4BX2VUAB-C3t0tXt-.js} +1 -1
- package/dashboard/dist/assets/{chunk-55IACEB6-BQPHWefV.js → chunk-55IACEB6-2cnyEL0b.js} +1 -1
- package/dashboard/dist/assets/{chunk-FMBD7UC4-CNcExMdx.js → chunk-FMBD7UC4-DIY9MTNi.js} +1 -1
- package/dashboard/dist/assets/{chunk-JSJVCQXG-LXBmftkC.js → chunk-JSJVCQXG-Cw8fpqpE.js} +1 -1
- package/dashboard/dist/assets/{chunk-KX2RTZJC-Tqi7zNqq.js → chunk-KX2RTZJC-BAhd66XV.js} +1 -1
- package/dashboard/dist/assets/{chunk-NQ4KR5QH-DkMbx-rW.js → chunk-NQ4KR5QH-RzXwoxk3.js} +1 -1
- package/dashboard/dist/assets/{chunk-QZHKN3VN-BlrRCfkJ.js → chunk-QZHKN3VN-Dgri4sGz.js} +1 -1
- package/dashboard/dist/assets/{chunk-WL4C6EOR-of3XBzMu.js → chunk-WL4C6EOR-DYLj9JRa.js} +1 -1
- package/dashboard/dist/assets/classDiagram-VBA2DB6C-STOZ51tg.js +1 -0
- package/dashboard/dist/assets/classDiagram-v2-RAHNMMFH-STOZ51tg.js +1 -0
- package/dashboard/dist/assets/clone-TzhWk-Bj.js +1 -0
- package/dashboard/dist/assets/{cose-bilkent-S5V4N54A-BlIiyO76.js → cose-bilkent-S5V4N54A-DfY_Fnfu.js} +1 -1
- package/dashboard/dist/assets/{dagre-KLK3FWXG-CYQjSI9N.js → dagre-KLK3FWXG-CyTKIVSK.js} +1 -1
- package/dashboard/dist/assets/{diagram-E7M64L7V-BZHzTKct.js → diagram-E7M64L7V-Krub7Xxo.js} +1 -1
- package/dashboard/dist/assets/{diagram-IFDJBPK2-kMP3WqBV.js → diagram-IFDJBPK2-giUl9uHz.js} +1 -1
- package/dashboard/dist/assets/{diagram-P4PSJMXO-BWSHyFOv.js → diagram-P4PSJMXO-oAtnO3C9.js} +1 -1
- package/dashboard/dist/assets/{erDiagram-INFDFZHY-B5HrvsPP.js → erDiagram-INFDFZHY-eYaVjXqo.js} +1 -1
- package/dashboard/dist/assets/{flowDiagram-PKNHOUZH-Dm4ewP7w.js → flowDiagram-PKNHOUZH-or5S0_Sb.js} +1 -1
- package/dashboard/dist/assets/{ganttDiagram-A5KZAMGK-DB3k27zu.js → ganttDiagram-A5KZAMGK-C9R1lsme.js} +1 -1
- package/dashboard/dist/assets/{gitGraphDiagram-K3NZZRJ6-G7y6Ey-m.js → gitGraphDiagram-K3NZZRJ6-BQwsDzvp.js} +1 -1
- package/dashboard/dist/assets/{graph-CaM4i6vq.js → graph-EQOX1wg8.js} +1 -1
- package/dashboard/dist/assets/index-Cy7yjuqO.js +500 -0
- package/dashboard/dist/assets/index-u80fISp0.css +1 -0
- package/dashboard/dist/assets/{infoDiagram-LFFYTUFH-JNTUbTjg.js → infoDiagram-LFFYTUFH-BjLlQWxk.js} +1 -1
- package/dashboard/dist/assets/{ishikawaDiagram-PHBUUO56-BZJt1ht8.js → ishikawaDiagram-PHBUUO56-BNjydh4j.js} +1 -1
- package/dashboard/dist/assets/{journeyDiagram-4ABVD52K-DPcqvl9A.js → journeyDiagram-4ABVD52K-DNzE7TgQ.js} +1 -1
- package/dashboard/dist/assets/{kanban-definition-K7BYSVSG-D1D7AuOV.js → kanban-definition-K7BYSVSG-FbNvGx6i.js} +1 -1
- package/dashboard/dist/assets/{layout-BTOh3EDT.js → layout-B2yZvlWs.js} +1 -1
- package/dashboard/dist/assets/{linear-MbCpC_Cg.js → linear-p68yY_14.js} +1 -1
- package/dashboard/dist/assets/{mermaid.core-CYbhqlNy.js → mermaid.core-D558akcW.js} +4 -4
- package/dashboard/dist/assets/{mindmap-definition-YRQLILUH-CwYCISFH.js → mindmap-definition-YRQLILUH-CZPgesSK.js} +1 -1
- package/dashboard/dist/assets/{pieDiagram-SKSYHLDU-5qfZ73SG.js → pieDiagram-SKSYHLDU-CdXMWspp.js} +1 -1
- package/dashboard/dist/assets/{quadrantDiagram-337W2JSQ-WI8y1sQ_.js → quadrantDiagram-337W2JSQ-D7tq22ZY.js} +1 -1
- package/dashboard/dist/assets/{requirementDiagram-Z7DCOOCP-BFlD0ZTS.js → requirementDiagram-Z7DCOOCP-ByZxUSmd.js} +1 -1
- package/dashboard/dist/assets/{sankeyDiagram-WA2Y5GQK-Bdckv1Se.js → sankeyDiagram-WA2Y5GQK-CZon9rRY.js} +1 -1
- package/dashboard/dist/assets/{sequenceDiagram-2WXFIKYE-DgzxKAlZ.js → sequenceDiagram-2WXFIKYE-nbELB6rb.js} +1 -1
- package/dashboard/dist/assets/{stateDiagram-RAJIS63D-DO4OXahC.js → stateDiagram-RAJIS63D-D_OPKr5B.js} +1 -1
- package/dashboard/dist/assets/stateDiagram-v2-FVOUBMTO-4pLM5B3m.js +1 -0
- package/dashboard/dist/assets/{timeline-definition-YZTLITO2-BBB01JWw.js → timeline-definition-YZTLITO2-Cxuvk1D2.js} +1 -1
- package/dashboard/dist/assets/{treemap-KZPCXAKY-Dr0jb8op.js → treemap-KZPCXAKY-C0CRpL92.js} +1 -1
- package/dashboard/dist/assets/{vennDiagram-LZ73GAT5-D40KFl2o.js → vennDiagram-LZ73GAT5-DijCj6M3.js} +1 -1
- package/dashboard/dist/assets/{xychartDiagram-JWTSCODW-DBUmWQfT.js → xychartDiagram-JWTSCODW-CdpE0oRi.js} +1 -1
- package/dashboard/dist/index.html +2 -2
- package/dist/dashboard/server.js +429 -39
- package/dist/dashboard/server.js.map +1 -1
- package/dist/index.js +658 -134
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/platforms/claude-code/README.md +3 -3
- package/platforms/claude-code/agents/syntaur-expert.md +12 -4
- package/platforms/claude-code/commands/save-session-summary/save-session-summary.md +24 -0
- package/platforms/claude-code/hooks/hooks.json +10 -0
- package/platforms/claude-code/hooks/session-start.sh +26 -1
- package/platforms/claude-code/references/file-ownership.md +2 -1
- package/platforms/claude-code/references/protocol-summary.md +6 -1
- package/platforms/claude-code/skills/track-session/SKILL.md +86 -0
- package/platforms/codex/README.md +2 -2
- package/platforms/codex/agents/syntaur-operator.md +6 -4
- package/platforms/codex/commands/save-session-summary.md +23 -0
- package/platforms/codex/references/file-ownership.md +2 -1
- package/platforms/codex/references/protocol-summary.md +6 -1
- package/vendor/syntaur-skills/skills/complete-assignment/SKILL.md +2 -0
- package/vendor/syntaur-skills/skills/grab-assignment/SKILL.md +7 -2
- package/vendor/syntaur-skills/skills/plan-assignment/SKILL.md +3 -1
- package/vendor/syntaur-skills/skills/save-session-summary/SKILL.md +113 -0
- package/vendor/syntaur-skills/skills/syntaur-protocol/SKILL.md +23 -4
- package/vendor/syntaur-skills/skills/syntaur-protocol/references/file-ownership.md +2 -1
- package/vendor/syntaur-skills/skills/syntaur-protocol/references/protocol-summary.md +6 -1
- package/dashboard/dist/assets/channel-Df6VrFK5.js +0 -1
- package/dashboard/dist/assets/classDiagram-VBA2DB6C-CyfzumTY.js +0 -1
- package/dashboard/dist/assets/classDiagram-v2-RAHNMMFH-CyfzumTY.js +0 -1
- package/dashboard/dist/assets/clone-CMs4Aqrx.js +0 -1
- package/dashboard/dist/assets/index-B4QMu-Oq.css +0 -1
- package/dashboard/dist/assets/index-BBWZjPBC.js +0 -495
- package/dashboard/dist/assets/stateDiagram-v2-FVOUBMTO-o8bgX-J3.js +0 -1
package/dist/dashboard/server.js
CHANGED
|
@@ -4771,8 +4771,9 @@ ${todosSection}## Context
|
|
|
4771
4771
|
- [Progress](./progress.md)
|
|
4772
4772
|
- [Comments](./comments.md)
|
|
4773
4773
|
- [Scratchpad](./scratchpad.md)
|
|
4774
|
-
- [Handoff](./handoff.md)
|
|
4774
|
+
- [Handoff](./handoff.md) \u2014 cross-ticket outbound
|
|
4775
4775
|
- [Decision Record](./decision-record.md)
|
|
4776
|
+
- [Sessions](./sessions/) \u2014 per-session continuity summaries (one \`<session-id>/summary.md\` per session)
|
|
4776
4777
|
`;
|
|
4777
4778
|
}
|
|
4778
4779
|
var init_assignment = __esm({
|
|
@@ -4826,6 +4827,13 @@ var init_handoff = __esm({
|
|
|
4826
4827
|
}
|
|
4827
4828
|
});
|
|
4828
4829
|
|
|
4830
|
+
// src/templates/session-summary.ts
|
|
4831
|
+
var init_session_summary = __esm({
|
|
4832
|
+
"src/templates/session-summary.ts"() {
|
|
4833
|
+
"use strict";
|
|
4834
|
+
}
|
|
4835
|
+
});
|
|
4836
|
+
|
|
4829
4837
|
// src/templates/progress.ts
|
|
4830
4838
|
function renderProgress(params2) {
|
|
4831
4839
|
return `---
|
|
@@ -5076,6 +5084,7 @@ var init_templates = __esm({
|
|
|
5076
5084
|
init_plan();
|
|
5077
5085
|
init_scratchpad();
|
|
5078
5086
|
init_handoff();
|
|
5087
|
+
init_session_summary();
|
|
5079
5088
|
init_progress();
|
|
5080
5089
|
init_comments();
|
|
5081
5090
|
init_decision_record();
|
|
@@ -8172,37 +8181,62 @@ init_fs_migration();
|
|
|
8172
8181
|
// src/dashboard/api-todos.ts
|
|
8173
8182
|
init_parser2();
|
|
8174
8183
|
init_fs();
|
|
8184
|
+
init_paths();
|
|
8175
8185
|
import { Router as Router5 } from "express";
|
|
8176
8186
|
import { readdir as readdir8 } from "fs/promises";
|
|
8177
|
-
|
|
8178
|
-
|
|
8179
|
-
|
|
8180
|
-
|
|
8181
|
-
}
|
|
8182
|
-
return value ?? "";
|
|
8183
|
-
}
|
|
8187
|
+
import { resolve as resolvePath, dirname as dirname3 } from "path";
|
|
8188
|
+
import { rename as rename3, mkdir as mkdir2 } from "fs/promises";
|
|
8189
|
+
|
|
8190
|
+
// src/dashboard/todos-locks.ts
|
|
8184
8191
|
var writeLocks = /* @__PURE__ */ new Map();
|
|
8185
8192
|
function withLock(lockKey, fn) {
|
|
8186
8193
|
const prev = writeLocks.get(lockKey) ?? Promise.resolve();
|
|
8187
8194
|
const next = prev.then(fn);
|
|
8188
|
-
writeLocks.set(
|
|
8189
|
-
|
|
8190
|
-
|
|
8195
|
+
writeLocks.set(
|
|
8196
|
+
lockKey,
|
|
8197
|
+
next.then(
|
|
8198
|
+
() => {
|
|
8199
|
+
},
|
|
8200
|
+
() => {
|
|
8201
|
+
}
|
|
8202
|
+
)
|
|
8203
|
+
);
|
|
8191
8204
|
return next;
|
|
8192
8205
|
}
|
|
8193
8206
|
function wsLock(workspace, fn) {
|
|
8194
8207
|
return withLock(`ws:${workspace}`, fn);
|
|
8195
8208
|
}
|
|
8209
|
+
function projLock(slug, fn) {
|
|
8210
|
+
return withLock(`proj:${slug}`, fn);
|
|
8211
|
+
}
|
|
8212
|
+
function withTwoLocks(keyA, keyB, fn) {
|
|
8213
|
+
if (keyA === keyB) return withLock(keyA, fn);
|
|
8214
|
+
const [first, second] = keyA < keyB ? [keyA, keyB] : [keyB, keyA];
|
|
8215
|
+
return withLock(first, () => withLock(second, fn));
|
|
8216
|
+
}
|
|
8217
|
+
|
|
8218
|
+
// src/dashboard/api-todos.ts
|
|
8219
|
+
init_slug();
|
|
8220
|
+
var WORKSPACE_REGEX = /^[a-z0-9_][a-z0-9-]*$/;
|
|
8221
|
+
function getWorkspaceParam(value) {
|
|
8222
|
+
if (Array.isArray(value)) {
|
|
8223
|
+
return value[0] ?? "";
|
|
8224
|
+
}
|
|
8225
|
+
return value ?? "";
|
|
8226
|
+
}
|
|
8196
8227
|
function touchItem(item) {
|
|
8197
8228
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
8198
8229
|
if (item.createdAt === null) item.createdAt = now;
|
|
8199
8230
|
item.updatedAt = now;
|
|
8200
8231
|
}
|
|
8201
|
-
function createTodosRouter(todosDir2, broadcast) {
|
|
8232
|
+
function createTodosRouter(todosDir2, broadcast, projectsDir) {
|
|
8202
8233
|
const router = Router5();
|
|
8203
8234
|
function broadcastUpdate() {
|
|
8204
8235
|
broadcast({ type: "todos-updated", timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
8205
8236
|
}
|
|
8237
|
+
function broadcastProject(slug) {
|
|
8238
|
+
broadcast({ type: "todos-updated", projectSlug: slug, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
8239
|
+
}
|
|
8206
8240
|
function validateWorkspace(req, res, next) {
|
|
8207
8241
|
const workspace = getWorkspaceParam(req.params.workspace);
|
|
8208
8242
|
if (workspace && !WORKSPACE_REGEX.test(workspace)) {
|
|
@@ -8601,7 +8635,7 @@ workspace: ${workspace}
|
|
|
8601
8635
|
items.push(item);
|
|
8602
8636
|
}
|
|
8603
8637
|
const scopeLabel = workspace === "_global" ? "_global" : `workspace:${workspace}`;
|
|
8604
|
-
const { resolve:
|
|
8638
|
+
const { resolve: resolvePath2 } = await import("path");
|
|
8605
8639
|
const { readConfig: readConfig2 } = await Promise.resolve().then(() => (init_config2(), config_exports));
|
|
8606
8640
|
const { assignmentsDir: assignmentsDirFn } = await Promise.resolve().then(() => (init_paths(), paths_exports));
|
|
8607
8641
|
const { fileExists: fileExists2, writeFileForce: writeFileForce2 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
|
|
@@ -8631,18 +8665,18 @@ workspace: ${workspace}
|
|
|
8631
8665
|
const parts = tg.split("/");
|
|
8632
8666
|
if (parts.length !== 2) return { error: `Invalid target.assignment "${tg}"` };
|
|
8633
8667
|
const config = await readConfig2();
|
|
8634
|
-
assignmentDir =
|
|
8668
|
+
assignmentDir = resolvePath2(config.defaultProjectDir, parts[0], "assignments", parts[1]);
|
|
8635
8669
|
assignmentRef = `${parts[0]}/${parts[1]}`;
|
|
8636
8670
|
} else if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(tg)) {
|
|
8637
|
-
assignmentDir =
|
|
8671
|
+
assignmentDir = resolvePath2(assignmentsDirFn(), tg);
|
|
8638
8672
|
assignmentRef = tg;
|
|
8639
8673
|
} else {
|
|
8640
8674
|
return { error: `Invalid target.assignment "${tg}"` };
|
|
8641
8675
|
}
|
|
8642
|
-
const assignmentMdPath2 =
|
|
8676
|
+
const assignmentMdPath2 = resolvePath2(assignmentDir, "assignment.md");
|
|
8643
8677
|
if (!await fileExists2(assignmentMdPath2)) return { error: `Target assignment not found: ${assignmentMdPath2}` };
|
|
8644
8678
|
}
|
|
8645
|
-
const assignmentMdPath =
|
|
8679
|
+
const assignmentMdPath = resolvePath2(assignmentDir, "assignment.md");
|
|
8646
8680
|
let content = await readFile15(assignmentMdPath, "utf-8");
|
|
8647
8681
|
content = appendTodosToAssignmentBody2(
|
|
8648
8682
|
content,
|
|
@@ -8686,6 +8720,115 @@ workspace: ${workspace}
|
|
|
8686
8720
|
res.status(500).json({ error: error instanceof Error ? error.message : "Failed to promote todos" });
|
|
8687
8721
|
}
|
|
8688
8722
|
});
|
|
8723
|
+
router.post("/:workspace/:id/move", async (req, res) => {
|
|
8724
|
+
try {
|
|
8725
|
+
const sourceWs = getWorkspaceParam(req.params.workspace);
|
|
8726
|
+
const id = req.params.id;
|
|
8727
|
+
const to = req.body?.to;
|
|
8728
|
+
if (!to || typeof to !== "object") {
|
|
8729
|
+
res.status(400).json({ error: "body.to is required" });
|
|
8730
|
+
return;
|
|
8731
|
+
}
|
|
8732
|
+
const targetCount = [Boolean(to.workspace), Boolean(to.project), Boolean(to.global)].filter(Boolean).length;
|
|
8733
|
+
if (targetCount !== 1) {
|
|
8734
|
+
res.status(400).json({ error: "body.to must specify exactly one of workspace, project, or global" });
|
|
8735
|
+
return;
|
|
8736
|
+
}
|
|
8737
|
+
if (to.project && !isValidSlug(to.project)) {
|
|
8738
|
+
res.status(400).json({ error: `Invalid target project slug: "${to.project}"` });
|
|
8739
|
+
return;
|
|
8740
|
+
}
|
|
8741
|
+
if (to.workspace && !WORKSPACE_REGEX.test(to.workspace)) {
|
|
8742
|
+
res.status(400).json({ error: `Invalid target workspace name: "${to.workspace}"` });
|
|
8743
|
+
return;
|
|
8744
|
+
}
|
|
8745
|
+
let target;
|
|
8746
|
+
if (to.global) {
|
|
8747
|
+
target = { kind: "workspace", id: "_global", todosPath: todosDir2, lockKey: "ws:_global" };
|
|
8748
|
+
} else if (to.workspace) {
|
|
8749
|
+
target = { kind: "workspace", id: to.workspace, todosPath: todosDir2, lockKey: `ws:${to.workspace}` };
|
|
8750
|
+
} else {
|
|
8751
|
+
if (!projectsDir) {
|
|
8752
|
+
res.status(500).json({ error: "Server not configured with projectsDir; cannot move to project scope" });
|
|
8753
|
+
return;
|
|
8754
|
+
}
|
|
8755
|
+
const slug = to.project;
|
|
8756
|
+
const projectMd = resolvePath(projectsDir, slug, "project.md");
|
|
8757
|
+
if (!await fileExists(projectMd)) {
|
|
8758
|
+
res.status(404).json({ error: `Target project "${slug}" not found` });
|
|
8759
|
+
return;
|
|
8760
|
+
}
|
|
8761
|
+
target = {
|
|
8762
|
+
kind: "project",
|
|
8763
|
+
id: slug,
|
|
8764
|
+
todosPath: projectTodosDir(projectsDir, slug),
|
|
8765
|
+
lockKey: `proj:${slug}`
|
|
8766
|
+
};
|
|
8767
|
+
}
|
|
8768
|
+
const sourceLockKey = `ws:${sourceWs}`;
|
|
8769
|
+
if (sourceLockKey === target.lockKey) {
|
|
8770
|
+
res.status(400).json({ error: "cannot move to the same scope" });
|
|
8771
|
+
return;
|
|
8772
|
+
}
|
|
8773
|
+
const result = await withTwoLocks(sourceLockKey, target.lockKey, async () => {
|
|
8774
|
+
const sourceChecklist = await readChecklist(todosDir2, sourceWs);
|
|
8775
|
+
const targetChecklist = await readChecklist(target.todosPath, target.id);
|
|
8776
|
+
const idx = sourceChecklist.items.findIndex((i) => i.id === id);
|
|
8777
|
+
if (idx === -1) return { status: 404, error: `Todo "${id}" not found` };
|
|
8778
|
+
if (targetChecklist.items.some((i) => i.id === id)) {
|
|
8779
|
+
return { status: 409, error: "id already exists in target" };
|
|
8780
|
+
}
|
|
8781
|
+
const item = sourceChecklist.items[idx];
|
|
8782
|
+
if (item.planDir) {
|
|
8783
|
+
const newPlanDir = todoPlanDir(target.todosPath, target.id, id);
|
|
8784
|
+
if (await fileExists(newPlanDir)) {
|
|
8785
|
+
return { status: 409, error: "plan dir already exists in target" };
|
|
8786
|
+
}
|
|
8787
|
+
await mkdir2(dirname3(newPlanDir), { recursive: true });
|
|
8788
|
+
await rename3(item.planDir, newPlanDir);
|
|
8789
|
+
item.planDir = newPlanDir;
|
|
8790
|
+
}
|
|
8791
|
+
sourceChecklist.items.splice(idx, 1);
|
|
8792
|
+
targetChecklist.items.push(item);
|
|
8793
|
+
await writeChecklist(todosDir2, sourceChecklist);
|
|
8794
|
+
await writeChecklist(target.todosPath, targetChecklist);
|
|
8795
|
+
const sourceLabel = sourceWs === "_global" ? "_global" : `workspace:${sourceWs}`;
|
|
8796
|
+
const targetLabel = target.kind === "project" ? `project:${target.id}` : target.id === "_global" ? "_global" : `workspace:${target.id}`;
|
|
8797
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
8798
|
+
await appendLogEntry2(todosDir2, sourceWs, {
|
|
8799
|
+
timestamp: ts,
|
|
8800
|
+
itemIds: [id],
|
|
8801
|
+
items: item.description,
|
|
8802
|
+
session: null,
|
|
8803
|
+
branch: item.branch || null,
|
|
8804
|
+
summary: `Moved to ${targetLabel}`,
|
|
8805
|
+
blockers: null,
|
|
8806
|
+
status: null
|
|
8807
|
+
});
|
|
8808
|
+
await appendLogEntry2(target.todosPath, target.id, {
|
|
8809
|
+
timestamp: ts,
|
|
8810
|
+
itemIds: [id],
|
|
8811
|
+
items: item.description,
|
|
8812
|
+
session: null,
|
|
8813
|
+
branch: item.branch || null,
|
|
8814
|
+
summary: `Moved from ${sourceLabel}`,
|
|
8815
|
+
blockers: null,
|
|
8816
|
+
status: null
|
|
8817
|
+
});
|
|
8818
|
+
return { status: 200, item };
|
|
8819
|
+
});
|
|
8820
|
+
if (result.status !== 200) {
|
|
8821
|
+
res.status(result.status).json({ error: result.error });
|
|
8822
|
+
return;
|
|
8823
|
+
}
|
|
8824
|
+
broadcastUpdate();
|
|
8825
|
+
if (target.kind === "project") broadcastProject(target.id);
|
|
8826
|
+
else broadcastUpdate();
|
|
8827
|
+
res.json({ moved: id, to: target });
|
|
8828
|
+
} catch (error) {
|
|
8829
|
+
res.status(500).json({ error: error instanceof Error ? error.message : "Failed to move todo" });
|
|
8830
|
+
}
|
|
8831
|
+
});
|
|
8689
8832
|
router.post("/:workspace/:id/unblock", async (req, res) => {
|
|
8690
8833
|
try {
|
|
8691
8834
|
const workspace = getWorkspaceParam(req.params.workspace);
|
|
@@ -8718,18 +8861,9 @@ init_fs();
|
|
|
8718
8861
|
init_paths();
|
|
8719
8862
|
init_slug();
|
|
8720
8863
|
import { Router as Router6 } from "express";
|
|
8721
|
-
import { mkdir as
|
|
8722
|
-
import { resolve as resolve17 } from "path";
|
|
8723
|
-
var
|
|
8724
|
-
function projLock(slug, fn) {
|
|
8725
|
-
const key = `proj:${slug}`;
|
|
8726
|
-
const prev = writeLocks2.get(key) ?? Promise.resolve();
|
|
8727
|
-
const next = prev.then(fn);
|
|
8728
|
-
writeLocks2.set(key, next.then(() => {
|
|
8729
|
-
}, () => {
|
|
8730
|
-
}));
|
|
8731
|
-
return next;
|
|
8732
|
-
}
|
|
8864
|
+
import { mkdir as mkdir3, readFile as readFile13, rename as rename4 } from "fs/promises";
|
|
8865
|
+
import { resolve as resolve17, dirname as dirname4 } from "path";
|
|
8866
|
+
var WORKSPACE_REGEX2 = /^[a-z0-9_][a-z0-9-]*$/;
|
|
8733
8867
|
function touchItem2(item) {
|
|
8734
8868
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
8735
8869
|
if (item.createdAt === null) item.createdAt = now;
|
|
@@ -8748,7 +8882,7 @@ async function projectExists(projectsDir, slug) {
|
|
|
8748
8882
|
async function ensureProjectTodosDir(projectsDir, slug) {
|
|
8749
8883
|
const todosDir2 = projectTodosDir(projectsDir, slug);
|
|
8750
8884
|
try {
|
|
8751
|
-
await
|
|
8885
|
+
await mkdir3(todosDir2, { recursive: false });
|
|
8752
8886
|
} catch (err) {
|
|
8753
8887
|
const code = err.code;
|
|
8754
8888
|
if (code === "EEXIST") return;
|
|
@@ -8760,7 +8894,7 @@ async function ensureProjectTodosDir(projectsDir, slug) {
|
|
|
8760
8894
|
throw err;
|
|
8761
8895
|
}
|
|
8762
8896
|
try {
|
|
8763
|
-
await
|
|
8897
|
+
await mkdir3(resolve17(todosDir2, "archive"), { recursive: false });
|
|
8764
8898
|
} catch (err) {
|
|
8765
8899
|
const code = err.code;
|
|
8766
8900
|
if (code === "EEXIST") return;
|
|
@@ -8775,11 +8909,14 @@ async function ensureProjectTodosDir(projectsDir, slug) {
|
|
|
8775
8909
|
function notFound(res, slug) {
|
|
8776
8910
|
res.status(404).json({ error: `Project "${slug}" not found` });
|
|
8777
8911
|
}
|
|
8778
|
-
function createProjectTodosRouter(projectsDir, broadcast) {
|
|
8912
|
+
function createProjectTodosRouter(projectsDir, broadcast, workspaceTodosDir) {
|
|
8779
8913
|
const router = Router6({ mergeParams: true });
|
|
8780
8914
|
function broadcastUpdate(projectSlug) {
|
|
8781
8915
|
broadcast({ type: "todos-updated", projectSlug, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
8782
8916
|
}
|
|
8917
|
+
function broadcastWorkspace() {
|
|
8918
|
+
broadcast({ type: "todos-updated", timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
8919
|
+
}
|
|
8783
8920
|
function validateProjectId(req, res, next) {
|
|
8784
8921
|
const slug = getProjectIdParam(params(req).projectId);
|
|
8785
8922
|
if (!slug || !isValidSlug(slug)) {
|
|
@@ -9327,6 +9464,259 @@ workspace: ${slug}
|
|
|
9327
9464
|
res.status(500).json({ error: error instanceof Error ? error.message : "Failed to unblock todo" });
|
|
9328
9465
|
}
|
|
9329
9466
|
});
|
|
9467
|
+
router.post("/promote", async (req, res) => {
|
|
9468
|
+
try {
|
|
9469
|
+
const slug = getProjectIdParam(params(req).projectId);
|
|
9470
|
+
if (!await projectExists(projectsDir, slug)) {
|
|
9471
|
+
notFound(res, slug);
|
|
9472
|
+
return;
|
|
9473
|
+
}
|
|
9474
|
+
const { todoIds, mode, target, title, type, priority, keepSource } = req.body ?? {};
|
|
9475
|
+
if (!Array.isArray(todoIds) || todoIds.length === 0) {
|
|
9476
|
+
res.status(400).json({ error: "todoIds (non-empty array of strings) is required" });
|
|
9477
|
+
return;
|
|
9478
|
+
}
|
|
9479
|
+
if (mode !== "new-assignment" && mode !== "to-assignment") {
|
|
9480
|
+
res.status(400).json({ error: 'mode must be "new-assignment" or "to-assignment"' });
|
|
9481
|
+
return;
|
|
9482
|
+
}
|
|
9483
|
+
const result = await projLock(slug, async () => {
|
|
9484
|
+
if (!await projectExists(projectsDir, slug)) return { gone: true };
|
|
9485
|
+
await ensureProjectTodosDir(projectsDir, slug);
|
|
9486
|
+
const todosDir2 = projectTodosDir(projectsDir, slug);
|
|
9487
|
+
const checklist = await readChecklist(todosDir2, slug);
|
|
9488
|
+
const items = [];
|
|
9489
|
+
for (const id of todoIds) {
|
|
9490
|
+
const item = checklist.items.find((i) => i.id === id);
|
|
9491
|
+
if (!item) return { error: `Todo "${id}" not found` };
|
|
9492
|
+
if (item.status === "completed") return { error: `Todo "${id}" is already completed` };
|
|
9493
|
+
items.push(item);
|
|
9494
|
+
}
|
|
9495
|
+
const scopeLabel = `project:${slug}`;
|
|
9496
|
+
const { assignmentsDir: assignmentsDirFn } = await Promise.resolve().then(() => (init_paths(), paths_exports));
|
|
9497
|
+
const { appendTodosToAssignmentBody: appendTodosToAssignmentBody2, touchAssignmentUpdated: touchAssignmentUpdated2 } = await Promise.resolve().then(() => (init_assignment_todos(), assignment_todos_exports));
|
|
9498
|
+
const { nowTimestamp: nowTimestamp3 } = await Promise.resolve().then(() => (init_timestamp(), timestamp_exports));
|
|
9499
|
+
let assignmentRef;
|
|
9500
|
+
let assignmentDir;
|
|
9501
|
+
if (mode === "new-assignment") {
|
|
9502
|
+
const targetProject = target?.project ?? slug;
|
|
9503
|
+
if (!targetProject) return { error: "target.project is required for new-assignment mode" };
|
|
9504
|
+
if (items.length > 1 && !title) return { error: "title is required when promoting multiple todos" };
|
|
9505
|
+
const { createAssignmentCommand: createAssignmentCommand2 } = await Promise.resolve().then(() => (init_create_assignment(), create_assignment_exports));
|
|
9506
|
+
const created = await createAssignmentCommand2(title || items[0].description, {
|
|
9507
|
+
project: targetProject,
|
|
9508
|
+
type,
|
|
9509
|
+
priority,
|
|
9510
|
+
withTodos: true,
|
|
9511
|
+
silent: true
|
|
9512
|
+
});
|
|
9513
|
+
assignmentDir = created.assignmentDir;
|
|
9514
|
+
assignmentRef = `${created.projectSlug}/${created.slug}`;
|
|
9515
|
+
} else {
|
|
9516
|
+
const tg = target?.assignment || "";
|
|
9517
|
+
if (!tg) return { error: "target.assignment is required for to-assignment mode" };
|
|
9518
|
+
if (tg.includes("/")) {
|
|
9519
|
+
const parts = tg.split("/");
|
|
9520
|
+
if (parts.length !== 2) return { error: `Invalid target.assignment "${tg}"` };
|
|
9521
|
+
assignmentDir = resolve17(projectsDir, parts[0], "assignments", parts[1]);
|
|
9522
|
+
assignmentRef = `${parts[0]}/${parts[1]}`;
|
|
9523
|
+
} else if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(tg)) {
|
|
9524
|
+
assignmentDir = resolve17(assignmentsDirFn(), tg);
|
|
9525
|
+
assignmentRef = tg;
|
|
9526
|
+
} else {
|
|
9527
|
+
return { error: `Invalid target.assignment "${tg}"` };
|
|
9528
|
+
}
|
|
9529
|
+
const assignmentMdPath2 = resolve17(assignmentDir, "assignment.md");
|
|
9530
|
+
if (!await fileExists(assignmentMdPath2)) return { error: `Target assignment not found: ${assignmentMdPath2}` };
|
|
9531
|
+
}
|
|
9532
|
+
const assignmentMdPath = resolve17(assignmentDir, "assignment.md");
|
|
9533
|
+
let content = await readFile13(assignmentMdPath, "utf-8");
|
|
9534
|
+
content = appendTodosToAssignmentBody2(
|
|
9535
|
+
content,
|
|
9536
|
+
items.map((it) => ({
|
|
9537
|
+
description: it.description,
|
|
9538
|
+
trace: `promoted from t:${it.id} in ${scopeLabel}`
|
|
9539
|
+
}))
|
|
9540
|
+
);
|
|
9541
|
+
content = touchAssignmentUpdated2(content, nowTimestamp3());
|
|
9542
|
+
await writeFileForce(assignmentMdPath, content);
|
|
9543
|
+
if (!keepSource) {
|
|
9544
|
+
for (const item of items) {
|
|
9545
|
+
item.status = "completed";
|
|
9546
|
+
item.session = null;
|
|
9547
|
+
touchItem2(item);
|
|
9548
|
+
}
|
|
9549
|
+
checklist.workspace = slug;
|
|
9550
|
+
await writeChecklist(todosDir2, checklist);
|
|
9551
|
+
for (const item of items) {
|
|
9552
|
+
const entry = {
|
|
9553
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9554
|
+
itemIds: [item.id],
|
|
9555
|
+
items: item.description,
|
|
9556
|
+
session: null,
|
|
9557
|
+
branch: item.branch || null,
|
|
9558
|
+
summary: `Promoted to assignment ${assignmentRef}`,
|
|
9559
|
+
blockers: null,
|
|
9560
|
+
status: null
|
|
9561
|
+
};
|
|
9562
|
+
await appendLogEntry2(todosDir2, slug, entry);
|
|
9563
|
+
}
|
|
9564
|
+
}
|
|
9565
|
+
return { assignmentRef, assignmentDir, promoted: items.map((i) => i.id) };
|
|
9566
|
+
});
|
|
9567
|
+
if ("gone" in result) {
|
|
9568
|
+
notFound(res, slug);
|
|
9569
|
+
return;
|
|
9570
|
+
}
|
|
9571
|
+
if ("error" in result) {
|
|
9572
|
+
res.status(400).json({ error: result.error });
|
|
9573
|
+
return;
|
|
9574
|
+
}
|
|
9575
|
+
broadcastUpdate(slug);
|
|
9576
|
+
res.json(result);
|
|
9577
|
+
} catch (error) {
|
|
9578
|
+
if (error.code === "PROJECT_GONE") {
|
|
9579
|
+
notFound(res, getProjectIdParam(params(req).projectId));
|
|
9580
|
+
return;
|
|
9581
|
+
}
|
|
9582
|
+
res.status(500).json({ error: error instanceof Error ? error.message : "Failed to promote todos" });
|
|
9583
|
+
}
|
|
9584
|
+
});
|
|
9585
|
+
router.post("/:id/move", async (req, res) => {
|
|
9586
|
+
try {
|
|
9587
|
+
const sourceSlug = getProjectIdParam(params(req).projectId);
|
|
9588
|
+
const id = params(req).id ?? "";
|
|
9589
|
+
if (!await projectExists(projectsDir, sourceSlug)) {
|
|
9590
|
+
notFound(res, sourceSlug);
|
|
9591
|
+
return;
|
|
9592
|
+
}
|
|
9593
|
+
const to = req.body?.to;
|
|
9594
|
+
if (!to || typeof to !== "object") {
|
|
9595
|
+
res.status(400).json({ error: "body.to is required" });
|
|
9596
|
+
return;
|
|
9597
|
+
}
|
|
9598
|
+
const targetCount = [Boolean(to.workspace), Boolean(to.project), Boolean(to.global)].filter(Boolean).length;
|
|
9599
|
+
if (targetCount !== 1) {
|
|
9600
|
+
res.status(400).json({ error: "body.to must specify exactly one of workspace, project, or global" });
|
|
9601
|
+
return;
|
|
9602
|
+
}
|
|
9603
|
+
if (to.project && !isValidSlug(to.project)) {
|
|
9604
|
+
res.status(400).json({ error: `Invalid target project slug: "${to.project}"` });
|
|
9605
|
+
return;
|
|
9606
|
+
}
|
|
9607
|
+
if (to.workspace && !WORKSPACE_REGEX2.test(to.workspace)) {
|
|
9608
|
+
res.status(400).json({ error: `Invalid target workspace name: "${to.workspace}"` });
|
|
9609
|
+
return;
|
|
9610
|
+
}
|
|
9611
|
+
let target;
|
|
9612
|
+
if (to.global) {
|
|
9613
|
+
if (!workspaceTodosDir) {
|
|
9614
|
+
res.status(500).json({ error: "Server not configured with workspaceTodosDir; cannot move to global scope" });
|
|
9615
|
+
return;
|
|
9616
|
+
}
|
|
9617
|
+
target = { kind: "workspace", id: "_global", todosPath: workspaceTodosDir, lockKey: "ws:_global" };
|
|
9618
|
+
} else if (to.workspace) {
|
|
9619
|
+
if (!workspaceTodosDir) {
|
|
9620
|
+
res.status(500).json({ error: "Server not configured with workspaceTodosDir; cannot move to workspace scope" });
|
|
9621
|
+
return;
|
|
9622
|
+
}
|
|
9623
|
+
target = { kind: "workspace", id: to.workspace, todosPath: workspaceTodosDir, lockKey: `ws:${to.workspace}` };
|
|
9624
|
+
} else {
|
|
9625
|
+
const tslug = to.project;
|
|
9626
|
+
if (!await projectExists(projectsDir, tslug)) {
|
|
9627
|
+
res.status(404).json({ error: `Target project "${tslug}" not found` });
|
|
9628
|
+
return;
|
|
9629
|
+
}
|
|
9630
|
+
target = {
|
|
9631
|
+
kind: "project",
|
|
9632
|
+
id: tslug,
|
|
9633
|
+
todosPath: projectTodosDir(projectsDir, tslug),
|
|
9634
|
+
lockKey: `proj:${tslug}`
|
|
9635
|
+
};
|
|
9636
|
+
}
|
|
9637
|
+
const sourceLockKey = `proj:${sourceSlug}`;
|
|
9638
|
+
if (sourceLockKey === target.lockKey) {
|
|
9639
|
+
res.status(400).json({ error: "cannot move to the same scope" });
|
|
9640
|
+
return;
|
|
9641
|
+
}
|
|
9642
|
+
const result = await withTwoLocks(sourceLockKey, target.lockKey, async () => {
|
|
9643
|
+
if (!await projectExists(projectsDir, sourceSlug)) return { status: "gone" };
|
|
9644
|
+
if (target.kind === "project" && !await projectExists(projectsDir, target.id)) {
|
|
9645
|
+
return { status: "targetGone" };
|
|
9646
|
+
}
|
|
9647
|
+
await ensureProjectTodosDir(projectsDir, sourceSlug);
|
|
9648
|
+
const sourceTodosDir = projectTodosDir(projectsDir, sourceSlug);
|
|
9649
|
+
const sourceChecklist = await readChecklist(sourceTodosDir, sourceSlug);
|
|
9650
|
+
const targetChecklist = await readChecklist(target.todosPath, target.id);
|
|
9651
|
+
const idx = sourceChecklist.items.findIndex((i) => i.id === id);
|
|
9652
|
+
if (idx === -1) return { status: 404, error: `Todo "${id}" not found` };
|
|
9653
|
+
if (targetChecklist.items.some((i) => i.id === id)) {
|
|
9654
|
+
return { status: 409, error: "id already exists in target" };
|
|
9655
|
+
}
|
|
9656
|
+
const item = sourceChecklist.items[idx];
|
|
9657
|
+
if (item.planDir) {
|
|
9658
|
+
const newPlanDir = todoPlanDir(target.todosPath, target.id, id);
|
|
9659
|
+
if (await fileExists(newPlanDir)) {
|
|
9660
|
+
return { status: 409, error: "plan dir already exists in target" };
|
|
9661
|
+
}
|
|
9662
|
+
await mkdir3(dirname4(newPlanDir), { recursive: true });
|
|
9663
|
+
await rename4(item.planDir, newPlanDir);
|
|
9664
|
+
item.planDir = newPlanDir;
|
|
9665
|
+
}
|
|
9666
|
+
sourceChecklist.items.splice(idx, 1);
|
|
9667
|
+
targetChecklist.items.push(item);
|
|
9668
|
+
sourceChecklist.workspace = sourceSlug;
|
|
9669
|
+
await writeChecklist(sourceTodosDir, sourceChecklist);
|
|
9670
|
+
await writeChecklist(target.todosPath, targetChecklist);
|
|
9671
|
+
const sourceLabel = `project:${sourceSlug}`;
|
|
9672
|
+
const targetLabel = target.kind === "project" ? `project:${target.id}` : target.id === "_global" ? "_global" : `workspace:${target.id}`;
|
|
9673
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
9674
|
+
await appendLogEntry2(sourceTodosDir, sourceSlug, {
|
|
9675
|
+
timestamp: ts,
|
|
9676
|
+
itemIds: [id],
|
|
9677
|
+
items: item.description,
|
|
9678
|
+
session: null,
|
|
9679
|
+
branch: item.branch || null,
|
|
9680
|
+
summary: `Moved to ${targetLabel}`,
|
|
9681
|
+
blockers: null,
|
|
9682
|
+
status: null
|
|
9683
|
+
});
|
|
9684
|
+
await appendLogEntry2(target.todosPath, target.id, {
|
|
9685
|
+
timestamp: ts,
|
|
9686
|
+
itemIds: [id],
|
|
9687
|
+
items: item.description,
|
|
9688
|
+
session: null,
|
|
9689
|
+
branch: item.branch || null,
|
|
9690
|
+
summary: `Moved from ${sourceLabel}`,
|
|
9691
|
+
blockers: null,
|
|
9692
|
+
status: null
|
|
9693
|
+
});
|
|
9694
|
+
return { status: 200, item };
|
|
9695
|
+
});
|
|
9696
|
+
if (result.status === "gone") {
|
|
9697
|
+
notFound(res, sourceSlug);
|
|
9698
|
+
return;
|
|
9699
|
+
}
|
|
9700
|
+
if (result.status === "targetGone") {
|
|
9701
|
+
res.status(404).json({ error: `Target project "${target.id}" not found` });
|
|
9702
|
+
return;
|
|
9703
|
+
}
|
|
9704
|
+
if (result.status !== 200) {
|
|
9705
|
+
res.status(result.status).json({ error: result.error });
|
|
9706
|
+
return;
|
|
9707
|
+
}
|
|
9708
|
+
broadcastUpdate(sourceSlug);
|
|
9709
|
+
if (target.kind === "project") broadcastUpdate(target.id);
|
|
9710
|
+
else broadcastWorkspace();
|
|
9711
|
+
res.json({ moved: id, to: target });
|
|
9712
|
+
} catch (error) {
|
|
9713
|
+
if (error.code === "PROJECT_GONE") {
|
|
9714
|
+
notFound(res, getProjectIdParam(params(req).projectId));
|
|
9715
|
+
return;
|
|
9716
|
+
}
|
|
9717
|
+
res.status(500).json({ error: error instanceof Error ? error.message : "Failed to move todo" });
|
|
9718
|
+
}
|
|
9719
|
+
});
|
|
9330
9720
|
return router;
|
|
9331
9721
|
}
|
|
9332
9722
|
|
|
@@ -9340,7 +9730,7 @@ init_fs();
|
|
|
9340
9730
|
init_config2();
|
|
9341
9731
|
import { execFile as execFile2 } from "child_process";
|
|
9342
9732
|
import { promisify as promisify2 } from "util";
|
|
9343
|
-
import { cp, mkdtemp, rm as rm2, readFile as readFile14, writeFile as writeFile4, unlink as unlink3, stat, open, rename as
|
|
9733
|
+
import { cp, mkdtemp, rm as rm2, readFile as readFile14, writeFile as writeFile4, unlink as unlink3, stat, open, rename as rename5 } from "fs/promises";
|
|
9344
9734
|
import { resolve as resolve18, join as join2 } from "path";
|
|
9345
9735
|
import { tmpdir } from "os";
|
|
9346
9736
|
var exec2 = promisify2(execFile2);
|
|
@@ -9552,7 +9942,7 @@ async function safeRestoreCategory(localPath, repoSrcPath, isFile) {
|
|
|
9552
9942
|
const localExistsBefore = await fileExists(localPath);
|
|
9553
9943
|
if (backupExistsBefore) {
|
|
9554
9944
|
if (!localExistsBefore) {
|
|
9555
|
-
await
|
|
9945
|
+
await rename5(backupPath, localPath);
|
|
9556
9946
|
} else {
|
|
9557
9947
|
throw new Error(
|
|
9558
9948
|
`Cannot restore "${localPath}": a stale crash-recovery backup exists at ${backupPath} while the current path also exists. Inspect both and remove the one you don't need, then retry.`
|
|
@@ -9564,15 +9954,15 @@ async function safeRestoreCategory(localPath, repoSrcPath, isFile) {
|
|
|
9564
9954
|
await cp(repoSrcPath, stagingPath, { recursive: true, force: true });
|
|
9565
9955
|
const localExists = await fileExists(localPath);
|
|
9566
9956
|
if (localExists) {
|
|
9567
|
-
await
|
|
9957
|
+
await rename5(localPath, backupPath);
|
|
9568
9958
|
localMovedAside = true;
|
|
9569
9959
|
}
|
|
9570
|
-
await
|
|
9960
|
+
await rename5(stagingPath, localPath);
|
|
9571
9961
|
await rm2(backupPath, { recursive: true, force: true }).catch(() => {
|
|
9572
9962
|
});
|
|
9573
9963
|
} catch (err) {
|
|
9574
9964
|
if (localMovedAside && await fileExists(backupPath)) {
|
|
9575
|
-
await
|
|
9965
|
+
await rename5(backupPath, localPath).catch(() => {
|
|
9576
9966
|
});
|
|
9577
9967
|
}
|
|
9578
9968
|
await rm2(stagingPath, { recursive: true, force: true }).catch(() => {
|
|
@@ -10311,8 +10701,8 @@ function createDashboardServer(options) {
|
|
|
10311
10701
|
app.use("/api/servers", createServersRouter(serversDir2, projectsDir, assignmentsDir2));
|
|
10312
10702
|
app.use("/api/agent-sessions", createAgentSessionsRouter(projectsDir, broadcast, assignmentsDir2));
|
|
10313
10703
|
app.use("/api/playbooks", createPlaybooksRouter(playbooksDir2));
|
|
10314
|
-
app.use("/api/todos", createTodosRouter(todosDir2, broadcast));
|
|
10315
|
-
app.use("/api/projects/:projectId/todos", createProjectTodosRouter(projectsDir, broadcast));
|
|
10704
|
+
app.use("/api/todos", createTodosRouter(todosDir2, broadcast, projectsDir));
|
|
10705
|
+
app.use("/api/projects/:projectId/todos", createProjectTodosRouter(projectsDir, broadcast, todosDir2));
|
|
10316
10706
|
app.use("/api/backup", createBackupRouter());
|
|
10317
10707
|
if (serveStaticUi && dashboardDistPath) {
|
|
10318
10708
|
const sendOpts = { dotfiles: "allow" };
|