syntaur 0.18.0 → 0.20.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-Cx0q44iE.js → _basePickBy-CFyquBjL.js} +1 -1
- package/dashboard/dist/assets/{_baseUniq-CfFbkKif.js → _baseUniq-S9hlXGZI.js} +1 -1
- package/dashboard/dist/assets/{arc-XluBomWz.js → arc-DNRWeoWH.js} +1 -1
- package/dashboard/dist/assets/{architectureDiagram-2XIMDMQ5-CihGybG6.js → architectureDiagram-2XIMDMQ5-BujnKgOd.js} +1 -1
- package/dashboard/dist/assets/{blockDiagram-WCTKOSBZ-BPUKO5fj.js → blockDiagram-WCTKOSBZ-DOaDmG_o.js} +1 -1
- package/dashboard/dist/assets/{c4Diagram-IC4MRINW-DKVq_j1f.js → c4Diagram-IC4MRINW-BeBwVhQx.js} +1 -1
- package/dashboard/dist/assets/channel-yUe8n53Y.js +1 -0
- package/dashboard/dist/assets/{chunk-4BX2VUAB-DAaE8oN2.js → chunk-4BX2VUAB-CtzM4Epf.js} +1 -1
- package/dashboard/dist/assets/{chunk-55IACEB6-BZoVpy5G.js → chunk-55IACEB6-D2cd1TFn.js} +1 -1
- package/dashboard/dist/assets/{chunk-FMBD7UC4-Cuo9M0jI.js → chunk-FMBD7UC4-DAqRATzJ.js} +1 -1
- package/dashboard/dist/assets/{chunk-JSJVCQXG-DGzypY5S.js → chunk-JSJVCQXG-CIEi981s.js} +1 -1
- package/dashboard/dist/assets/{chunk-KX2RTZJC-DfxYbrKI.js → chunk-KX2RTZJC-BMv4Xwdm.js} +1 -1
- package/dashboard/dist/assets/{chunk-NQ4KR5QH-BEbwpe5N.js → chunk-NQ4KR5QH-BKTm5ctg.js} +1 -1
- package/dashboard/dist/assets/{chunk-QZHKN3VN-DDjrkJqg.js → chunk-QZHKN3VN-DjmLZcpF.js} +1 -1
- package/dashboard/dist/assets/{chunk-WL4C6EOR-BSMHQ3p4.js → chunk-WL4C6EOR-CphX8dKS.js} +1 -1
- package/dashboard/dist/assets/classDiagram-VBA2DB6C-C0f25bKl.js +1 -0
- package/dashboard/dist/assets/classDiagram-v2-RAHNMMFH-C0f25bKl.js +1 -0
- package/dashboard/dist/assets/clone-D-aj2eAK.js +1 -0
- package/dashboard/dist/assets/{cose-bilkent-S5V4N54A-BgUyyImN.js → cose-bilkent-S5V4N54A-BZ-8TY36.js} +1 -1
- package/dashboard/dist/assets/{dagre-KLK3FWXG-zAe7pq6F.js → dagre-KLK3FWXG-Bx5whsTp.js} +1 -1
- package/dashboard/dist/assets/{diagram-E7M64L7V-B1bVb4ll.js → diagram-E7M64L7V-BVOQiv2e.js} +1 -1
- package/dashboard/dist/assets/{diagram-IFDJBPK2-DZCxJIkJ.js → diagram-IFDJBPK2-D7bQBDL_.js} +1 -1
- package/dashboard/dist/assets/{diagram-P4PSJMXO-BuPgL5q1.js → diagram-P4PSJMXO-CwO6aafO.js} +1 -1
- package/dashboard/dist/assets/{erDiagram-INFDFZHY-l3YvnHT9.js → erDiagram-INFDFZHY-DCNz82wG.js} +1 -1
- package/dashboard/dist/assets/{flowDiagram-PKNHOUZH-CLOLQLdW.js → flowDiagram-PKNHOUZH-Dq6yNscM.js} +1 -1
- package/dashboard/dist/assets/{ganttDiagram-A5KZAMGK-D1yLx2Ap.js → ganttDiagram-A5KZAMGK-B2xtuK0X.js} +1 -1
- package/dashboard/dist/assets/{gitGraphDiagram-K3NZZRJ6-uC4rY3UQ.js → gitGraphDiagram-K3NZZRJ6-D8mNkQd3.js} +1 -1
- package/dashboard/dist/assets/{graph-DvUnLFOz.js → graph-DMbUUtaR.js} +1 -1
- package/dashboard/dist/assets/index-BlikGvSd.js +550 -0
- package/dashboard/dist/assets/index-CsyJIOqR.css +1 -0
- package/dashboard/dist/assets/{infoDiagram-LFFYTUFH-B6jLLOUk.js → infoDiagram-LFFYTUFH-BsrDYNxL.js} +1 -1
- package/dashboard/dist/assets/{ishikawaDiagram-PHBUUO56-CDvhubqL.js → ishikawaDiagram-PHBUUO56-DlUJjWQM.js} +1 -1
- package/dashboard/dist/assets/{journeyDiagram-4ABVD52K-BOtMcoBj.js → journeyDiagram-4ABVD52K-DBbVdCE_.js} +1 -1
- package/dashboard/dist/assets/{kanban-definition-K7BYSVSG-DC05q9p4.js → kanban-definition-K7BYSVSG-Cuv3IHJ_.js} +1 -1
- package/dashboard/dist/assets/{layout-B4jiZlj6.js → layout-CgL4rycu.js} +1 -1
- package/dashboard/dist/assets/{linear-DpQXVSyZ.js → linear-CgnhFMi4.js} +1 -1
- package/dashboard/dist/assets/{mermaid.core-aNcWKq-4.js → mermaid.core-C3eCFd8P.js} +4 -4
- package/dashboard/dist/assets/{mindmap-definition-YRQLILUH-Dv4qo7wQ.js → mindmap-definition-YRQLILUH-Bsm6cGkL.js} +1 -1
- package/dashboard/dist/assets/{pieDiagram-SKSYHLDU-ByRY4ahm.js → pieDiagram-SKSYHLDU-DBRLfMtc.js} +1 -1
- package/dashboard/dist/assets/{quadrantDiagram-337W2JSQ-Dt5_Xjwp.js → quadrantDiagram-337W2JSQ-CES4NNws.js} +1 -1
- package/dashboard/dist/assets/{requirementDiagram-Z7DCOOCP-CSRV6VcE.js → requirementDiagram-Z7DCOOCP-B0cUyFW2.js} +1 -1
- package/dashboard/dist/assets/{sankeyDiagram-WA2Y5GQK-DA8gxGEq.js → sankeyDiagram-WA2Y5GQK-LVaVg6wF.js} +1 -1
- package/dashboard/dist/assets/{sequenceDiagram-2WXFIKYE-CVZr9aKv.js → sequenceDiagram-2WXFIKYE-BISSySYI.js} +1 -1
- package/dashboard/dist/assets/{stateDiagram-RAJIS63D-yIU4X0e8.js → stateDiagram-RAJIS63D-rdMNHAXE.js} +1 -1
- package/dashboard/dist/assets/stateDiagram-v2-FVOUBMTO-ti7j33kD.js +1 -0
- package/dashboard/dist/assets/{timeline-definition-YZTLITO2-DnfvRVEJ.js → timeline-definition-YZTLITO2-fVRyntnZ.js} +1 -1
- package/dashboard/dist/assets/{treemap-KZPCXAKY-p5sbmBwK.js → treemap-KZPCXAKY-ECgEamZF.js} +1 -1
- package/dashboard/dist/assets/{vennDiagram-LZ73GAT5-Do2Vp3nv.js → vennDiagram-LZ73GAT5-DkL5XCcJ.js} +1 -1
- package/dashboard/dist/assets/{xychartDiagram-JWTSCODW-BrfAGQWt.js → xychartDiagram-JWTSCODW-DSQCD2gS.js} +1 -1
- package/dashboard/dist/index.html +2 -2
- package/dist/dashboard/server.js +328 -123
- package/dist/dashboard/server.js.map +1 -1
- package/dist/index.js +795 -488
- package/dist/index.js.map +1 -1
- package/dist/launch/index.js +75 -10
- package/dist/launch/index.js.map +1 -1
- package/package.json +1 -1
- package/dashboard/dist/assets/channel-mNClnLVi.js +0 -1
- package/dashboard/dist/assets/classDiagram-VBA2DB6C-Dv6vdcHm.js +0 -1
- package/dashboard/dist/assets/classDiagram-v2-RAHNMMFH-Dv6vdcHm.js +0 -1
- package/dashboard/dist/assets/clone-CRLXHkrd.js +0 -1
- package/dashboard/dist/assets/index-BoVXMp5e.js +0 -535
- package/dashboard/dist/assets/index-XappA8I7.css +0 -1
- package/dashboard/dist/assets/stateDiagram-v2-FVOUBMTO-DMBx3hAg.js +0 -1
package/dist/dashboard/server.js
CHANGED
|
@@ -284,10 +284,14 @@ function parseAssignmentFrontmatter(fileContent) {
|
|
|
284
284
|
links: parseLinks(frontmatter),
|
|
285
285
|
blockedReason: getField2("blockedReason"),
|
|
286
286
|
workspace: parseWorkspace(frontmatter),
|
|
287
|
-
tags: parseTags(frontmatter)
|
|
287
|
+
tags: parseTags(frontmatter),
|
|
288
|
+
archived: getField2("archived") === "true",
|
|
289
|
+
archivedAt: getField2("archivedAt"),
|
|
290
|
+
archivedReason: getField2("archivedReason")
|
|
288
291
|
};
|
|
289
292
|
}
|
|
290
293
|
function formatYamlValue(value) {
|
|
294
|
+
if (typeof value === "boolean") return value ? "true" : "false";
|
|
291
295
|
if (value === null) return "null";
|
|
292
296
|
if (/^\d{4}-\d{2}-\d{2}T/.test(value)) {
|
|
293
297
|
return `"${value}"`;
|
|
@@ -306,6 +310,12 @@ function updateAssignmentFile(fileContent, updates) {
|
|
|
306
310
|
const fieldRegex = new RegExp(`^(${key}:)\\s*.*$`, "m");
|
|
307
311
|
if (fieldRegex.test(result)) {
|
|
308
312
|
result = result.replace(fieldRegex, `$1 ${formatted}`);
|
|
313
|
+
} else {
|
|
314
|
+
const closeIdx = result.indexOf("\n---", 4);
|
|
315
|
+
if (closeIdx !== -1) {
|
|
316
|
+
result = `${result.slice(0, closeIdx)}
|
|
317
|
+
${key}: ${formatted}${result.slice(closeIdx)}`;
|
|
318
|
+
}
|
|
309
319
|
}
|
|
310
320
|
}
|
|
311
321
|
return result;
|
|
@@ -582,6 +592,9 @@ function parseAssignmentFull(fileContent) {
|
|
|
582
592
|
},
|
|
583
593
|
externalIds: parseExternalIds2(fm),
|
|
584
594
|
tags: parseListField(fm, "tags"),
|
|
595
|
+
archived: getField(fm, "archived") === "true",
|
|
596
|
+
archivedAt: getField(fm, "archivedAt"),
|
|
597
|
+
archivedReason: getField(fm, "archivedReason"),
|
|
585
598
|
created: getField(fm, "created") ?? "",
|
|
586
599
|
updated: getField(fm, "updated") ?? "",
|
|
587
600
|
body
|
|
@@ -1456,6 +1469,53 @@ async function migrateLegacyProjectFiles(projectsDir) {
|
|
|
1456
1469
|
}
|
|
1457
1470
|
return result;
|
|
1458
1471
|
}
|
|
1472
|
+
function setFrontmatterField(content, key, value) {
|
|
1473
|
+
const formatted = value === null ? "null" : typeof value === "boolean" ? String(value) : value;
|
|
1474
|
+
const fieldRegex = new RegExp(`^(${key}:)\\s*.*$`, "m");
|
|
1475
|
+
if (fieldRegex.test(content)) {
|
|
1476
|
+
return content.replace(fieldRegex, `$1 ${formatted}`);
|
|
1477
|
+
}
|
|
1478
|
+
const closingIdx = content.indexOf("\n---", 4);
|
|
1479
|
+
if (closingIdx === -1) return content;
|
|
1480
|
+
return `${content.slice(0, closingIdx)}
|
|
1481
|
+
${key}: ${formatted}${content.slice(closingIdx)}`;
|
|
1482
|
+
}
|
|
1483
|
+
function readFrontmatterField(content, key) {
|
|
1484
|
+
const match = content.match(new RegExp(`^${key}:\\s*(.*)$`, "m"));
|
|
1485
|
+
if (!match) return null;
|
|
1486
|
+
const raw = match[1].trim().replace(/^['"]|['"]$/g, "");
|
|
1487
|
+
return raw === "" || raw === "null" ? null : raw;
|
|
1488
|
+
}
|
|
1489
|
+
async function migrateLegacyArchivedProjects(projectsDir) {
|
|
1490
|
+
const result = { reconciled: [] };
|
|
1491
|
+
if (!await fileExists(projectsDir)) return result;
|
|
1492
|
+
let entries;
|
|
1493
|
+
try {
|
|
1494
|
+
entries = await readdir2(projectsDir, { withFileTypes: true });
|
|
1495
|
+
} catch {
|
|
1496
|
+
return result;
|
|
1497
|
+
}
|
|
1498
|
+
for (const entry of entries) {
|
|
1499
|
+
if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
|
|
1500
|
+
const projectMd = resolve5(projectsDir, entry.name, "project.md");
|
|
1501
|
+
try {
|
|
1502
|
+
if (!await fileExists(projectMd)) continue;
|
|
1503
|
+
const content = await readFile3(projectMd, "utf-8");
|
|
1504
|
+
if (readFrontmatterField(content, "statusOverride") !== "archived") continue;
|
|
1505
|
+
let next = setFrontmatterField(content, "archived", true);
|
|
1506
|
+
if (readFrontmatterField(content, "archivedAt") === null) {
|
|
1507
|
+
next = setFrontmatterField(next, "archivedAt", nowTimestamp());
|
|
1508
|
+
}
|
|
1509
|
+
next = setFrontmatterField(next, "statusOverride", null);
|
|
1510
|
+
next = setFrontmatterField(next, "updated", nowTimestamp());
|
|
1511
|
+
await writeFile2(projectMd, next, "utf-8");
|
|
1512
|
+
result.reconciled.push(entry.name);
|
|
1513
|
+
} catch {
|
|
1514
|
+
continue;
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1517
|
+
return result;
|
|
1518
|
+
}
|
|
1459
1519
|
async function migrateLegacyConfig(configPath) {
|
|
1460
1520
|
const result = {
|
|
1461
1521
|
renamedField: false,
|
|
@@ -1549,6 +1609,7 @@ var init_fs_migration = __esm({
|
|
|
1549
1609
|
"src/utils/fs-migration.ts"() {
|
|
1550
1610
|
"use strict";
|
|
1551
1611
|
init_fs();
|
|
1612
|
+
init_timestamp();
|
|
1552
1613
|
}
|
|
1553
1614
|
});
|
|
1554
1615
|
|
|
@@ -2997,7 +3058,7 @@ import { readdir as readdir3, readFile as readFile5, unlink } from "fs/promises"
|
|
|
2997
3058
|
function escapeRegExp(value) {
|
|
2998
3059
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2999
3060
|
}
|
|
3000
|
-
function
|
|
3061
|
+
function setFrontmatterField2(content, key, value) {
|
|
3001
3062
|
const regex = new RegExp(`^(${escapeRegExp(key)}:)\\s*.*$`, "m");
|
|
3002
3063
|
if (regex.test(content)) {
|
|
3003
3064
|
return content.replace(regex, `$1 ${value}`);
|
|
@@ -3146,8 +3207,8 @@ async function renamePlaybook(playbooksDir2, oldSlug, newSlug) {
|
|
|
3146
3207
|
}
|
|
3147
3208
|
}
|
|
3148
3209
|
const raw = await readFile5(oldPath, "utf-8");
|
|
3149
|
-
let next =
|
|
3150
|
-
next =
|
|
3210
|
+
let next = setFrontmatterField2(raw, "slug", newSlug);
|
|
3211
|
+
next = setFrontmatterField2(next, "updated", `"${nowTimestamp()}"`);
|
|
3151
3212
|
await writeFileForce(newPath, next);
|
|
3152
3213
|
if (!renamedInPlace) {
|
|
3153
3214
|
await unlink(oldPath);
|
|
@@ -4764,6 +4825,12 @@ function setUpdatedField(content, value) {
|
|
|
4764
4825
|
function escapeRegExp2(value) {
|
|
4765
4826
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
4766
4827
|
}
|
|
4828
|
+
function isProjectArchived(p) {
|
|
4829
|
+
return p.archived === true;
|
|
4830
|
+
}
|
|
4831
|
+
function activeAssignments(items) {
|
|
4832
|
+
return items.filter((item) => item.archived !== true);
|
|
4833
|
+
}
|
|
4767
4834
|
function createTraces() {
|
|
4768
4835
|
return { entries: [], subPhases: /* @__PURE__ */ new Map() };
|
|
4769
4836
|
}
|
|
@@ -4901,7 +4968,7 @@ function clearStatusConfigCache() {
|
|
|
4901
4968
|
}
|
|
4902
4969
|
async function listProjects(projectsDir) {
|
|
4903
4970
|
const projectRecords = await listProjectRecords(projectsDir);
|
|
4904
|
-
return projectRecords.map((record) => record.summary);
|
|
4971
|
+
return projectRecords.filter((record) => !isProjectArchived(record.summary)).map((record) => record.summary);
|
|
4905
4972
|
}
|
|
4906
4973
|
async function readWorkspaceRegistry(projectsDir) {
|
|
4907
4974
|
const registryPath = resolve13(dirname2(projectsDir), "workspaces.json");
|
|
@@ -5033,7 +5100,9 @@ async function getOverview(projectsDir, serversDir2, assignmentsDir2, options =
|
|
|
5033
5100
|
"list-standalone-records",
|
|
5034
5101
|
() => listStandaloneRecords(assignmentsDir2)
|
|
5035
5102
|
);
|
|
5036
|
-
const
|
|
5103
|
+
const activeProjectRecords = projectRecords.filter((record) => !isProjectArchived(record.summary));
|
|
5104
|
+
const activeStandaloneRecords = standaloneRecords.filter((sr) => sr.record.archived !== true);
|
|
5105
|
+
const recentActivity = buildRecentActivity(activeProjectRecords, activeStandaloneRecords);
|
|
5037
5106
|
const staleLimit = clamp(
|
|
5038
5107
|
Number.isFinite(options.staleLimit) ? Number(options.staleLimit) : STALE_LIMIT_DEFAULT,
|
|
5039
5108
|
1,
|
|
@@ -5092,56 +5161,111 @@ async function getOverview(projectsDir, serversDir2, assignmentsDir2, options =
|
|
|
5092
5161
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5093
5162
|
firstRun: projectRecords.length === 0 && standaloneRecords.length === 0,
|
|
5094
5163
|
stats: {
|
|
5095
|
-
activeProjects:
|
|
5096
|
-
inProgressAssignments:
|
|
5164
|
+
activeProjects: activeProjectRecords.filter((record) => record.summary.status === "active").length,
|
|
5165
|
+
inProgressAssignments: activeProjectRecords.reduce(
|
|
5097
5166
|
(total, record) => total + (record.summary.progress["in_progress"] ?? 0),
|
|
5098
5167
|
0
|
|
5099
5168
|
),
|
|
5100
|
-
blockedAssignments:
|
|
5169
|
+
blockedAssignments: activeProjectRecords.reduce(
|
|
5101
5170
|
(total, record) => total + (record.summary.progress["blocked"] ?? 0),
|
|
5102
5171
|
0
|
|
5103
5172
|
),
|
|
5104
|
-
reviewAssignments:
|
|
5173
|
+
reviewAssignments: activeProjectRecords.reduce(
|
|
5105
5174
|
(total, record) => total + (record.summary.progress["review"] ?? 0),
|
|
5106
5175
|
0
|
|
5107
5176
|
),
|
|
5108
|
-
failedAssignments:
|
|
5177
|
+
failedAssignments: activeProjectRecords.reduce(
|
|
5109
5178
|
(total, record) => total + (record.summary.progress["failed"] ?? 0),
|
|
5110
5179
|
0
|
|
5111
5180
|
),
|
|
5112
|
-
staleAssignments:
|
|
5113
|
-
(total, record) => total + record.assignments.filter((assignment) => isStale(assignment.updated)).length,
|
|
5181
|
+
staleAssignments: activeProjectRecords.reduce(
|
|
5182
|
+
(total, record) => total + activeAssignments(record.assignments).filter((assignment) => isStale(assignment.updated)).length,
|
|
5114
5183
|
0
|
|
5115
|
-
) +
|
|
5184
|
+
) + activeStandaloneRecords.filter((sr) => isStale(sr.record.updated)).length
|
|
5116
5185
|
},
|
|
5117
5186
|
hero,
|
|
5118
5187
|
segments,
|
|
5119
5188
|
recentSessions,
|
|
5120
|
-
recentProjects:
|
|
5189
|
+
recentProjects: activeProjectRecords.map((record) => record.summary).sort((left, right) => compareTimestamps(right.updated, left.updated)).slice(0, RECENT_PROJECTS_LIMIT),
|
|
5121
5190
|
recentActivity: recentActivity.slice(0, RECENT_ACTIVITY_LIMIT),
|
|
5122
5191
|
serverStats
|
|
5123
5192
|
};
|
|
5124
5193
|
}
|
|
5125
|
-
async function listAssignmentsBoard(projectsDir, assignmentsDir2) {
|
|
5194
|
+
async function listAssignmentsBoard(projectsDir, assignmentsDir2, options = {}) {
|
|
5195
|
+
const mode = options.archived ?? "exclude";
|
|
5126
5196
|
const projectRecords = await listProjectRecords(projectsDir);
|
|
5127
5197
|
const projectItems = await Promise.all(
|
|
5128
|
-
projectRecords.flatMap(
|
|
5129
|
-
|
|
5130
|
-
|
|
5198
|
+
projectRecords.flatMap(async (record) => {
|
|
5199
|
+
if (mode === "only") {
|
|
5200
|
+
return Promise.all(
|
|
5201
|
+
record.assignments.filter((assignment) => assignment.archived === true).map(async (assignment) => toAssignmentBoardItem(projectsDir, record, assignment))
|
|
5202
|
+
);
|
|
5203
|
+
}
|
|
5204
|
+
if (isProjectArchived(record.summary)) return [];
|
|
5205
|
+
return Promise.all(
|
|
5206
|
+
activeAssignments(record.assignments).map(
|
|
5131
5207
|
async (assignment) => toAssignmentBoardItem(projectsDir, record, assignment)
|
|
5132
5208
|
)
|
|
5133
|
-
)
|
|
5134
|
-
)
|
|
5209
|
+
);
|
|
5210
|
+
})
|
|
5135
5211
|
);
|
|
5136
5212
|
const standaloneRecords = await listStandaloneRecords(assignmentsDir2);
|
|
5213
|
+
const filteredStandalone = mode === "only" ? standaloneRecords.filter((sr) => sr.record.archived === true) : standaloneRecords.filter((sr) => sr.record.archived !== true);
|
|
5137
5214
|
const standaloneItems = await Promise.all(
|
|
5138
|
-
|
|
5215
|
+
filteredStandalone.map(async (sr) => toStandaloneBoardItem(sr))
|
|
5139
5216
|
);
|
|
5140
5217
|
return {
|
|
5141
5218
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5142
5219
|
assignments: [...projectItems.flat(), ...standaloneItems].sort((left, right) => compareTimestamps(right.updated, left.updated))
|
|
5143
5220
|
};
|
|
5144
5221
|
}
|
|
5222
|
+
function toArchivedAssignmentItem(assignment, projectSlug, projectTitle) {
|
|
5223
|
+
return {
|
|
5224
|
+
id: assignment.id,
|
|
5225
|
+
slug: assignment.slug,
|
|
5226
|
+
title: assignment.title,
|
|
5227
|
+
status: assignment.status,
|
|
5228
|
+
type: assignment.type,
|
|
5229
|
+
priority: assignment.priority,
|
|
5230
|
+
projectSlug,
|
|
5231
|
+
projectTitle,
|
|
5232
|
+
archived: assignment.archived,
|
|
5233
|
+
archivedAt: assignment.archivedAt,
|
|
5234
|
+
archivedReason: assignment.archivedReason,
|
|
5235
|
+
updated: assignment.updated
|
|
5236
|
+
};
|
|
5237
|
+
}
|
|
5238
|
+
async function listArchived(projectsDir, assignmentsDir2) {
|
|
5239
|
+
const projectRecords = await listProjectRecords(projectsDir);
|
|
5240
|
+
const standaloneRecords = await listStandaloneRecords(assignmentsDir2);
|
|
5241
|
+
const projects = projectRecords.filter((record) => isProjectArchived(record.summary)).map((record) => ({
|
|
5242
|
+
slug: record.summary.slug,
|
|
5243
|
+
title: record.summary.title,
|
|
5244
|
+
archivedAt: record.summary.archivedAt,
|
|
5245
|
+
archivedReason: record.summary.archivedReason,
|
|
5246
|
+
assignments: record.assignments.map(
|
|
5247
|
+
(assignment) => toArchivedAssignmentItem(assignment, record.summary.slug, record.summary.title)
|
|
5248
|
+
).sort((left, right) => compareTimestamps(right.updated, left.updated))
|
|
5249
|
+
})).sort((left, right) => compareTimestamps(right.archivedAt ?? "", left.archivedAt ?? ""));
|
|
5250
|
+
const individuallyArchived = [];
|
|
5251
|
+
for (const record of projectRecords) {
|
|
5252
|
+
if (isProjectArchived(record.summary)) continue;
|
|
5253
|
+
for (const assignment of record.assignments) {
|
|
5254
|
+
if (assignment.archived === true) {
|
|
5255
|
+
individuallyArchived.push(
|
|
5256
|
+
toArchivedAssignmentItem(assignment, record.summary.slug, record.summary.title)
|
|
5257
|
+
);
|
|
5258
|
+
}
|
|
5259
|
+
}
|
|
5260
|
+
}
|
|
5261
|
+
for (const sr of standaloneRecords) {
|
|
5262
|
+
if (sr.record.archived === true) {
|
|
5263
|
+
individuallyArchived.push(toArchivedAssignmentItem(sr.record, null, null));
|
|
5264
|
+
}
|
|
5265
|
+
}
|
|
5266
|
+
individuallyArchived.sort((left, right) => compareTimestamps(right.updated, left.updated));
|
|
5267
|
+
return { projects, assignments: individuallyArchived };
|
|
5268
|
+
}
|
|
5145
5269
|
async function toStandaloneBoardItem(sr) {
|
|
5146
5270
|
return {
|
|
5147
5271
|
...toAssignmentSummary(sr.record),
|
|
@@ -5236,7 +5360,7 @@ async function getProjectDetail(projectsDir, slug) {
|
|
|
5236
5360
|
const dependencyGraph = await loadDependencyGraph(projectPath, assignments);
|
|
5237
5361
|
const resources = await listResources(projectPath);
|
|
5238
5362
|
const memories = await listMemories(projectPath);
|
|
5239
|
-
const updated = getProjectActivityTimestamp(project.updated, assignments);
|
|
5363
|
+
const updated = getProjectActivityTimestamp(project.updated, activeAssignments(assignments));
|
|
5240
5364
|
return {
|
|
5241
5365
|
slug: project.slug || slug,
|
|
5242
5366
|
title: project.title,
|
|
@@ -5357,6 +5481,9 @@ async function getAssignmentDetail(projectsDir, projectSlug, assignmentSlug) {
|
|
|
5357
5481
|
projectWorkspace,
|
|
5358
5482
|
externalIds: assignment.externalIds,
|
|
5359
5483
|
tags: assignment.tags,
|
|
5484
|
+
archived: assignment.archived,
|
|
5485
|
+
archivedAt: assignment.archivedAt,
|
|
5486
|
+
archivedReason: assignment.archivedReason,
|
|
5360
5487
|
created: assignment.created,
|
|
5361
5488
|
updated: assignment.updated,
|
|
5362
5489
|
body: assignment.body,
|
|
@@ -5607,6 +5734,9 @@ async function buildStandaloneAssignmentDetail(resolved) {
|
|
|
5607
5734
|
projectWorkspace: assignment.workspaceGroup,
|
|
5608
5735
|
externalIds: assignment.externalIds,
|
|
5609
5736
|
tags: assignment.tags,
|
|
5737
|
+
archived: assignment.archived,
|
|
5738
|
+
archivedAt: assignment.archivedAt,
|
|
5739
|
+
archivedReason: assignment.archivedReason,
|
|
5610
5740
|
created: assignment.created,
|
|
5611
5741
|
updated: assignment.updated,
|
|
5612
5742
|
body: assignment.body,
|
|
@@ -5636,6 +5766,7 @@ async function computeProjectRecords(projectsDir, traces) {
|
|
|
5636
5766
|
if (!migratedProjectsDirs.has(projectsDir)) {
|
|
5637
5767
|
migratedProjectsDirs.add(projectsDir);
|
|
5638
5768
|
await migrateLegacyProjectFiles(projectsDir);
|
|
5769
|
+
await migrateLegacyArchivedProjects(projectsDir);
|
|
5639
5770
|
}
|
|
5640
5771
|
const entries = await readdir7(projectsDir, { withFileTypes: true });
|
|
5641
5772
|
const projectDirs = entries.filter((entry) => entry.isDirectory() && !entry.name.startsWith("."));
|
|
@@ -5656,7 +5787,7 @@ async function computeProjectRecords(projectsDir, traces) {
|
|
|
5656
5787
|
const t2 = traces ? performance.now() : 0;
|
|
5657
5788
|
const rollup = await buildProjectRollup(projectPath, project, assignments, traces);
|
|
5658
5789
|
if (traces) accumulatePhase(traces, "build-rollup", performance.now() - t2);
|
|
5659
|
-
const updated = getProjectActivityTimestamp(project.updated, assignments);
|
|
5790
|
+
const updated = getProjectActivityTimestamp(project.updated, activeAssignments(assignments));
|
|
5660
5791
|
const t3 = traces ? performance.now() : 0;
|
|
5661
5792
|
const dependencyGraph = await loadDependencyGraph(projectPath, assignments);
|
|
5662
5793
|
if (traces) accumulatePhase(traces, "load-dep-graph", performance.now() - t3);
|
|
@@ -5866,9 +5997,10 @@ async function loadDependencyGraph(projectPath, assignments) {
|
|
|
5866
5997
|
return buildDependencyGraph(assignments);
|
|
5867
5998
|
}
|
|
5868
5999
|
async function buildProjectRollup(projectPath, project, assignments, traces) {
|
|
5869
|
-
const
|
|
6000
|
+
const active = activeAssignments(assignments);
|
|
6001
|
+
const progress = { total: active.length };
|
|
5870
6002
|
const perAssignment = await Promise.all(
|
|
5871
|
-
|
|
6003
|
+
active.map(async (assignment) => {
|
|
5872
6004
|
const t0 = traces ? performance.now() : 0;
|
|
5873
6005
|
const openQuestions2 = await countOpenQuestions(projectPath, assignment.slug);
|
|
5874
6006
|
if (traces) accumulatePhase(traces, "count-open-questions", performance.now() - t0);
|
|
@@ -5916,7 +6048,12 @@ function toAssignmentSummary(assignment) {
|
|
|
5916
6048
|
assignee: assignment.assignee,
|
|
5917
6049
|
dependsOn: assignment.dependsOn,
|
|
5918
6050
|
links: assignment.links,
|
|
5919
|
-
|
|
6051
|
+
tags: assignment.tags,
|
|
6052
|
+
created: assignment.created,
|
|
6053
|
+
updated: assignment.updated,
|
|
6054
|
+
archived: assignment.archived,
|
|
6055
|
+
archivedAt: assignment.archivedAt,
|
|
6056
|
+
archivedReason: assignment.archivedReason
|
|
5920
6057
|
};
|
|
5921
6058
|
}
|
|
5922
6059
|
async function toAssignmentBoardItem(projectsDir, projectRecord, assignment) {
|
|
@@ -6054,12 +6191,14 @@ async function buildOverviewSegmentBuckets(projectsDir, projectRecords, standalo
|
|
|
6054
6191
|
const buckets = emptyBuckets();
|
|
6055
6192
|
const newestPool = [];
|
|
6056
6193
|
for (const record of projectRecords) {
|
|
6194
|
+
if (isProjectArchived(record.summary)) continue;
|
|
6057
6195
|
const depMap = /* @__PURE__ */ new Map();
|
|
6058
6196
|
for (const a of record.assignments) {
|
|
6059
6197
|
depMap.set(a.slug, a.status);
|
|
6060
6198
|
}
|
|
6199
|
+
const visibleAssignments = activeAssignments(record.assignments);
|
|
6061
6200
|
const resolvedTransitions = await Promise.all(
|
|
6062
|
-
|
|
6201
|
+
visibleAssignments.map(async (assignment) => {
|
|
6063
6202
|
const t0 = traces ? performance.now() : 0;
|
|
6064
6203
|
const availableTransitions = await getAvailableTransitions(
|
|
6065
6204
|
projectsDir,
|
|
@@ -6128,7 +6267,7 @@ async function buildOverviewSegmentBuckets(projectsDir, projectRecords, standalo
|
|
|
6128
6267
|
}
|
|
6129
6268
|
}
|
|
6130
6269
|
const resolvedStandaloneTransitions = await Promise.all(
|
|
6131
|
-
standaloneRecords.map(async (sr) => {
|
|
6270
|
+
standaloneRecords.filter((sr) => sr.record.archived !== true).map(async (sr) => {
|
|
6132
6271
|
const t0 = traces ? performance.now() : 0;
|
|
6133
6272
|
const availableTransitions = await getStandaloneAvailableTransitions(sr.record);
|
|
6134
6273
|
if (traces) accumulatePhase(traces, "get-available-transitions", performance.now() - t0);
|
|
@@ -6254,7 +6393,7 @@ function buildRecentActivity(projectRecords, standaloneRecords = []) {
|
|
|
6254
6393
|
assignmentSlug: null,
|
|
6255
6394
|
summary: `Project status is ${record.summary.status}.`
|
|
6256
6395
|
});
|
|
6257
|
-
for (const assignment of record.assignments) {
|
|
6396
|
+
for (const assignment of activeAssignments(record.assignments)) {
|
|
6258
6397
|
activity.push({
|
|
6259
6398
|
id: `assignment:${record.summary.slug}:${assignment.slug}`,
|
|
6260
6399
|
type: "assignment",
|
|
@@ -6926,6 +7065,7 @@ var SORT_FIELDS = [
|
|
|
6926
7065
|
"priority",
|
|
6927
7066
|
"assignee",
|
|
6928
7067
|
"dependencies",
|
|
7068
|
+
"created",
|
|
6929
7069
|
"updated"
|
|
6930
7070
|
];
|
|
6931
7071
|
var SORT_DIRECTIONS = ["asc", "desc"];
|
|
@@ -6939,6 +7079,37 @@ var GROUPINGS = [
|
|
|
6939
7079
|
"project"
|
|
6940
7080
|
];
|
|
6941
7081
|
var ACTIVITIES = ["all", "stale", "fresh"];
|
|
7082
|
+
var DATE_RANGE_FIELDS = ["created", "updated"];
|
|
7083
|
+
var DATE_RANGE_PRESETS = [
|
|
7084
|
+
"last_24h",
|
|
7085
|
+
"last_7d",
|
|
7086
|
+
"last_30d",
|
|
7087
|
+
"last_90d",
|
|
7088
|
+
"older_7d",
|
|
7089
|
+
"older_30d"
|
|
7090
|
+
];
|
|
7091
|
+
var YMD_RE = /^\d{4}-\d{2}-\d{2}$/;
|
|
7092
|
+
function isDateRangeField(v) {
|
|
7093
|
+
return typeof v === "string" && DATE_RANGE_FIELDS.includes(v);
|
|
7094
|
+
}
|
|
7095
|
+
function isDateRangePreset(v) {
|
|
7096
|
+
return typeof v === "string" && DATE_RANGE_PRESETS.includes(v);
|
|
7097
|
+
}
|
|
7098
|
+
function isDateRange(v) {
|
|
7099
|
+
if (!v || typeof v !== "object" || Array.isArray(v)) return false;
|
|
7100
|
+
const r = v;
|
|
7101
|
+
if (!isDateRangeField(r.field)) return false;
|
|
7102
|
+
for (const key of Object.keys(r)) {
|
|
7103
|
+
if (key !== "field" && key !== "preset" && key !== "from" && key !== "to") return false;
|
|
7104
|
+
}
|
|
7105
|
+
const hasPreset = r.preset !== void 0;
|
|
7106
|
+
const hasAbsolute = r.from !== void 0 || r.to !== void 0;
|
|
7107
|
+
if (hasPreset && hasAbsolute) return false;
|
|
7108
|
+
if (hasPreset && !isDateRangePreset(r.preset)) return false;
|
|
7109
|
+
if (r.from !== void 0 && (typeof r.from !== "string" || !YMD_RE.test(r.from))) return false;
|
|
7110
|
+
if (r.to !== void 0 && (typeof r.to !== "string" || !YMD_RE.test(r.to))) return false;
|
|
7111
|
+
return true;
|
|
7112
|
+
}
|
|
6942
7113
|
function toFilterValues(v) {
|
|
6943
7114
|
const arr = v === void 0 ? [] : Array.isArray(v) ? v : [v];
|
|
6944
7115
|
const out = [];
|
|
@@ -7114,7 +7285,7 @@ function isViewPrefsDefaults(file) {
|
|
|
7114
7285
|
if (g.density !== d.density) return false;
|
|
7115
7286
|
if (g.grouping !== d.grouping) return false;
|
|
7116
7287
|
const gf = g.filters;
|
|
7117
|
-
for (const key of ["status", "type", "priority", "assignee", "project"]) {
|
|
7288
|
+
for (const key of ["status", "type", "priority", "assignee", "project", "tags"]) {
|
|
7118
7289
|
if (toFilterValues(gf[key]).length > 0) return false;
|
|
7119
7290
|
}
|
|
7120
7291
|
if (gf.activity !== void 0 && gf.activity !== "all") return false;
|
|
@@ -7131,6 +7302,7 @@ var TABLE_COLUMN_IDS = [
|
|
|
7131
7302
|
"priority",
|
|
7132
7303
|
"assignee",
|
|
7133
7304
|
"dependencies",
|
|
7305
|
+
"created",
|
|
7134
7306
|
"updated"
|
|
7135
7307
|
];
|
|
7136
7308
|
function isTableColumnId(value) {
|
|
@@ -7206,10 +7378,12 @@ function isTableColumnVisibility(value) {
|
|
|
7206
7378
|
function isViewFilters(value) {
|
|
7207
7379
|
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
7208
7380
|
const obj = value;
|
|
7209
|
-
for (const key of ["status", "type", "priority", "assignee", "project"]) {
|
|
7381
|
+
for (const key of ["status", "type", "priority", "assignee", "project", "tags"]) {
|
|
7210
7382
|
if (obj[key] !== void 0 && !isFilterValue(obj[key])) return false;
|
|
7211
7383
|
}
|
|
7212
7384
|
if (obj.activity !== void 0 && !isActivity(obj.activity)) return false;
|
|
7385
|
+
if (obj.dateRange !== void 0 && !isDateRange(obj.dateRange)) return false;
|
|
7386
|
+
if (obj.search !== void 0 && typeof obj.search !== "string") return false;
|
|
7213
7387
|
return true;
|
|
7214
7388
|
}
|
|
7215
7389
|
function isSavedViewConfig(value) {
|
|
@@ -8076,6 +8250,9 @@ workspace:
|
|
|
8076
8250
|
branch: null
|
|
8077
8251
|
parentBranch: null
|
|
8078
8252
|
tags: []
|
|
8253
|
+
archived: false
|
|
8254
|
+
archivedAt: null
|
|
8255
|
+
archivedReason: null
|
|
8079
8256
|
---
|
|
8080
8257
|
|
|
8081
8258
|
# ${params2.title}
|
|
@@ -8422,6 +8599,13 @@ ${key}: ${formatted}${content.slice(closingIdx)}`;
|
|
|
8422
8599
|
function escapeRegExp3(value) {
|
|
8423
8600
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
8424
8601
|
}
|
|
8602
|
+
function applyArchiveFields(content, archived, reason) {
|
|
8603
|
+
let next = setTopLevelField(content, "archived", archived);
|
|
8604
|
+
next = setTopLevelField(next, "archivedAt", archived ? nowTimestamp() : null);
|
|
8605
|
+
next = setTopLevelField(next, "archivedReason", archived ? reason : null);
|
|
8606
|
+
next = setTopLevelField(next, "updated", nowTimestamp());
|
|
8607
|
+
return next;
|
|
8608
|
+
}
|
|
8425
8609
|
function appendLogEntry2(existingContent, countField, nextCount, heading, body, emptyPlaceholder) {
|
|
8426
8610
|
const timestamp = nowTimestamp();
|
|
8427
8611
|
let next = setTopLevelField(existingContent, "updated", timestamp);
|
|
@@ -9715,7 +9899,7 @@ ${entry}`;
|
|
|
9715
9899
|
}
|
|
9716
9900
|
const { status } = req.body || {};
|
|
9717
9901
|
const config = await getStatusConfig();
|
|
9718
|
-
const validStatuses = ["active",
|
|
9902
|
+
const validStatuses = ["active", ...config.statuses.map((s) => s.id)];
|
|
9719
9903
|
if (status !== null && (typeof status !== "string" || !validStatuses.includes(status))) {
|
|
9720
9904
|
res.status(400).json({ error: `Invalid status. Must be one of: ${validStatuses.join(", ")}, or null to clear.` });
|
|
9721
9905
|
return;
|
|
@@ -9767,6 +9951,106 @@ ${entry}`;
|
|
|
9767
9951
|
res.status(500).json({ error: `Failed to override status: ${error.message}` });
|
|
9768
9952
|
}
|
|
9769
9953
|
});
|
|
9954
|
+
function archiveReason(body) {
|
|
9955
|
+
const reason = body?.reason;
|
|
9956
|
+
return typeof reason === "string" && reason.trim() ? reason : null;
|
|
9957
|
+
}
|
|
9958
|
+
router.post("/api/projects/:slug/archive", async (req, res) => {
|
|
9959
|
+
try {
|
|
9960
|
+
const projectSlug = getParam(req.params.slug);
|
|
9961
|
+
const projectPath = resolve18(projectsDir, projectSlug, "project.md");
|
|
9962
|
+
if (!await fileExists(projectPath)) {
|
|
9963
|
+
res.status(404).json({ error: `Project "${projectSlug}" not found` });
|
|
9964
|
+
return;
|
|
9965
|
+
}
|
|
9966
|
+
const content = await readFile14(projectPath, "utf-8");
|
|
9967
|
+
await writeFileForce(projectPath, applyArchiveFields(content, true, archiveReason(req.body)));
|
|
9968
|
+
const project = await getProjectDetail(projectsDir, projectSlug);
|
|
9969
|
+
res.json({ project });
|
|
9970
|
+
} catch (error) {
|
|
9971
|
+
console.error("Error archiving project:", error);
|
|
9972
|
+
res.status(500).json({ error: `Failed to archive project: ${error.message}` });
|
|
9973
|
+
}
|
|
9974
|
+
});
|
|
9975
|
+
router.post("/api/projects/:slug/unarchive", async (req, res) => {
|
|
9976
|
+
try {
|
|
9977
|
+
const projectSlug = getParam(req.params.slug);
|
|
9978
|
+
const projectPath = resolve18(projectsDir, projectSlug, "project.md");
|
|
9979
|
+
if (!await fileExists(projectPath)) {
|
|
9980
|
+
res.status(404).json({ error: `Project "${projectSlug}" not found` });
|
|
9981
|
+
return;
|
|
9982
|
+
}
|
|
9983
|
+
const content = await readFile14(projectPath, "utf-8");
|
|
9984
|
+
await writeFileForce(projectPath, applyArchiveFields(content, false, null));
|
|
9985
|
+
const project = await getProjectDetail(projectsDir, projectSlug);
|
|
9986
|
+
res.json({ project });
|
|
9987
|
+
} catch (error) {
|
|
9988
|
+
console.error("Error restoring project:", error);
|
|
9989
|
+
res.status(500).json({ error: `Failed to restore project: ${error.message}` });
|
|
9990
|
+
}
|
|
9991
|
+
});
|
|
9992
|
+
async function handleAssignmentArchive(req, res, archived) {
|
|
9993
|
+
const projectSlug = getParam(req.params.slug);
|
|
9994
|
+
const assignmentSlug = getParam(req.params.aslug);
|
|
9995
|
+
const assignmentPath = resolve18(projectsDir, projectSlug, "assignments", assignmentSlug, "assignment.md");
|
|
9996
|
+
if (!await fileExists(assignmentPath)) {
|
|
9997
|
+
res.status(404).json({ error: "Assignment not found" });
|
|
9998
|
+
return;
|
|
9999
|
+
}
|
|
10000
|
+
const content = await readFile14(assignmentPath, "utf-8");
|
|
10001
|
+
await writeFileForce(assignmentPath, applyArchiveFields(content, archived, archived ? archiveReason(req.body) : null));
|
|
10002
|
+
const assignment = await getAssignmentDetail(projectsDir, projectSlug, assignmentSlug);
|
|
10003
|
+
res.json({ assignment });
|
|
10004
|
+
}
|
|
10005
|
+
router.post("/api/projects/:slug/assignments/:aslug/archive", async (req, res) => {
|
|
10006
|
+
try {
|
|
10007
|
+
await handleAssignmentArchive(req, res, true);
|
|
10008
|
+
} catch (error) {
|
|
10009
|
+
console.error("Error archiving assignment:", error);
|
|
10010
|
+
res.status(500).json({ error: `Failed to archive assignment: ${error.message}` });
|
|
10011
|
+
}
|
|
10012
|
+
});
|
|
10013
|
+
router.post("/api/projects/:slug/assignments/:aslug/unarchive", async (req, res) => {
|
|
10014
|
+
try {
|
|
10015
|
+
await handleAssignmentArchive(req, res, false);
|
|
10016
|
+
} catch (error) {
|
|
10017
|
+
console.error("Error restoring assignment:", error);
|
|
10018
|
+
res.status(500).json({ error: `Failed to restore assignment: ${error.message}` });
|
|
10019
|
+
}
|
|
10020
|
+
});
|
|
10021
|
+
async function handleStandaloneArchive(req, res, archived) {
|
|
10022
|
+
if (!assignmentsDir2) {
|
|
10023
|
+
res.status(501).json({ error: "Standalone assignments not configured on this server" });
|
|
10024
|
+
return;
|
|
10025
|
+
}
|
|
10026
|
+
const id = getParam(req.params.id);
|
|
10027
|
+
const resolved = await resolveAssignmentById(projectsDir, assignmentsDir2, id);
|
|
10028
|
+
if (!resolved) {
|
|
10029
|
+
res.status(404).json({ error: `Assignment "${id}" not found` });
|
|
10030
|
+
return;
|
|
10031
|
+
}
|
|
10032
|
+
const assignmentPath = resolve18(resolved.assignmentDir, "assignment.md");
|
|
10033
|
+
const content = await readFile14(assignmentPath, "utf-8");
|
|
10034
|
+
await writeFileForce(assignmentPath, applyArchiveFields(content, archived, archived ? archiveReason(req.body) : null));
|
|
10035
|
+
const assignment = await getAssignmentDetailById(projectsDir, assignmentsDir2, id);
|
|
10036
|
+
res.json({ assignment });
|
|
10037
|
+
}
|
|
10038
|
+
router.post("/api/assignments/:id/archive", async (req, res) => {
|
|
10039
|
+
try {
|
|
10040
|
+
await handleStandaloneArchive(req, res, true);
|
|
10041
|
+
} catch (error) {
|
|
10042
|
+
console.error("Error archiving assignment:", error);
|
|
10043
|
+
res.status(500).json({ error: `Failed to archive assignment: ${error.message}` });
|
|
10044
|
+
}
|
|
10045
|
+
});
|
|
10046
|
+
router.post("/api/assignments/:id/unarchive", async (req, res) => {
|
|
10047
|
+
try {
|
|
10048
|
+
await handleStandaloneArchive(req, res, false);
|
|
10049
|
+
} catch (error) {
|
|
10050
|
+
console.error("Error restoring assignment:", error);
|
|
10051
|
+
res.status(500).json({ error: `Failed to restore assignment: ${error.message}` });
|
|
10052
|
+
}
|
|
10053
|
+
});
|
|
9770
10054
|
router.patch("/api/projects/:slug/assignments/:aslug/assignee", async (req, res) => {
|
|
9771
10055
|
try {
|
|
9772
10056
|
const projectSlug = getParam(req.params.slug);
|
|
@@ -10416,84 +10700,6 @@ ${entry}`;
|
|
|
10416
10700
|
res.status(500).json({ error: `Failed to update title: ${error.message}` });
|
|
10417
10701
|
}
|
|
10418
10702
|
});
|
|
10419
|
-
router.post("/api/assignments/bulk-status-override", async (req, res) => {
|
|
10420
|
-
try {
|
|
10421
|
-
const body = req.body;
|
|
10422
|
-
if (!body || typeof body !== "object" || !Array.isArray(body.items)) {
|
|
10423
|
-
res.status(400).json({ error: "Request body must include `items` (array)." });
|
|
10424
|
-
return;
|
|
10425
|
-
}
|
|
10426
|
-
const items = body.items;
|
|
10427
|
-
if (items.length === 0) {
|
|
10428
|
-
res.status(400).json({ error: "`items` must contain at least one entry." });
|
|
10429
|
-
return;
|
|
10430
|
-
}
|
|
10431
|
-
if (items.length > 200) {
|
|
10432
|
-
res.status(400).json({ error: "`items` is capped at 200 entries per call." });
|
|
10433
|
-
return;
|
|
10434
|
-
}
|
|
10435
|
-
const config = await getStatusConfig();
|
|
10436
|
-
const validStatuses = new Set(config.statuses.map((s) => s.id));
|
|
10437
|
-
const timestamp = nowTimestamp();
|
|
10438
|
-
const results = [];
|
|
10439
|
-
let succeeded = 0;
|
|
10440
|
-
let failed = 0;
|
|
10441
|
-
for (let index = 0; index < items.length; index += 1) {
|
|
10442
|
-
const raw = items[index];
|
|
10443
|
-
const itemKey = buildBulkItemKey(raw, index);
|
|
10444
|
-
try {
|
|
10445
|
-
if (!raw || typeof raw !== "object") {
|
|
10446
|
-
throw new Error("item must be an object");
|
|
10447
|
-
}
|
|
10448
|
-
const item = raw;
|
|
10449
|
-
const status = typeof item.status === "string" ? item.status : null;
|
|
10450
|
-
if (!status || !validStatuses.has(status)) {
|
|
10451
|
-
throw new Error(`invalid status "${status ?? ""}"`);
|
|
10452
|
-
}
|
|
10453
|
-
let assignmentPath = null;
|
|
10454
|
-
if (typeof item.id === "string" && item.id.trim()) {
|
|
10455
|
-
if (!assignmentsDir2) {
|
|
10456
|
-
throw new Error("standalone assignments are not configured on this server");
|
|
10457
|
-
}
|
|
10458
|
-
const resolved = await resolveAssignmentById(projectsDir, assignmentsDir2, item.id);
|
|
10459
|
-
if (!resolved) {
|
|
10460
|
-
throw new Error(`assignment "${item.id}" not found`);
|
|
10461
|
-
}
|
|
10462
|
-
assignmentPath = resolve18(resolved.assignmentDir, "assignment.md");
|
|
10463
|
-
} else if (typeof item.projectSlug === "string" && typeof item.assignmentSlug === "string" && item.projectSlug && item.assignmentSlug) {
|
|
10464
|
-
assignmentPath = resolve18(
|
|
10465
|
-
projectsDir,
|
|
10466
|
-
item.projectSlug,
|
|
10467
|
-
"assignments",
|
|
10468
|
-
item.assignmentSlug,
|
|
10469
|
-
"assignment.md"
|
|
10470
|
-
);
|
|
10471
|
-
} else {
|
|
10472
|
-
throw new Error("must supply either `id` or both `projectSlug` and `assignmentSlug`");
|
|
10473
|
-
}
|
|
10474
|
-
if (!await fileExists(assignmentPath)) {
|
|
10475
|
-
throw new Error("assignment file not found");
|
|
10476
|
-
}
|
|
10477
|
-
let content = await readFile14(assignmentPath, "utf-8");
|
|
10478
|
-
content = setTopLevelField(content, "status", status);
|
|
10479
|
-
content = setTopLevelField(content, "updated", timestamp);
|
|
10480
|
-
if (status !== "blocked") {
|
|
10481
|
-
content = setTopLevelField(content, "blockedReason", null);
|
|
10482
|
-
}
|
|
10483
|
-
await writeFileForce(assignmentPath, content);
|
|
10484
|
-
results.push({ key: itemKey, ok: true });
|
|
10485
|
-
succeeded += 1;
|
|
10486
|
-
} catch (error) {
|
|
10487
|
-
results.push({ key: itemKey, ok: false, error: error.message });
|
|
10488
|
-
failed += 1;
|
|
10489
|
-
}
|
|
10490
|
-
}
|
|
10491
|
-
res.json({ results, succeeded, failed });
|
|
10492
|
-
} catch (error) {
|
|
10493
|
-
console.error("Error in bulk-status-override:", error);
|
|
10494
|
-
res.status(500).json({ error: `Bulk status override failed: ${error.message}` });
|
|
10495
|
-
}
|
|
10496
|
-
});
|
|
10497
10703
|
router.patch("/api/assignments/:id/acceptance-criteria/:index", async (req, res) => {
|
|
10498
10704
|
try {
|
|
10499
10705
|
if (!assignmentsDir2) {
|
|
@@ -10607,16 +10813,6 @@ function validateTitleBody(body) {
|
|
|
10607
10813
|
}
|
|
10608
10814
|
return { ok: true, value: trimmed };
|
|
10609
10815
|
}
|
|
10610
|
-
function buildBulkItemKey(raw, index) {
|
|
10611
|
-
if (raw && typeof raw === "object") {
|
|
10612
|
-
const item = raw;
|
|
10613
|
-
if (typeof item.id === "string" && item.id.trim()) return item.id;
|
|
10614
|
-
if (typeof item.projectSlug === "string" && typeof item.assignmentSlug === "string") {
|
|
10615
|
-
return `${item.projectSlug}/${item.assignmentSlug}`;
|
|
10616
|
-
}
|
|
10617
|
-
}
|
|
10618
|
-
return `#${index}`;
|
|
10619
|
-
}
|
|
10620
10816
|
async function appendCommentTo(assignmentDir, assignmentRef, req, res, reloadDetail) {
|
|
10621
10817
|
const commentsPath = resolve18(assignmentDir, "comments.md");
|
|
10622
10818
|
const { body, author, type, replyTo } = req.body || {};
|
|
@@ -15812,7 +16008,7 @@ function createDashboardServer(options) {
|
|
|
15812
16008
|
}
|
|
15813
16009
|
});
|
|
15814
16010
|
const VIEW_PREFS_LOCK = "vp:global";
|
|
15815
|
-
const FILTER_KEYS = /* @__PURE__ */ new Set(["status", "type", "priority", "assignee", "project", "activity"]);
|
|
16011
|
+
const FILTER_KEYS = /* @__PURE__ */ new Set(["status", "type", "priority", "assignee", "project", "tags", "activity"]);
|
|
15816
16012
|
const GLOBAL_KEYS = /* @__PURE__ */ new Set(["defaultView", "sortField", "sortDirection", "density", "grouping", "filters"]);
|
|
15817
16013
|
const SCOPE_KEYS = /* @__PURE__ */ new Set(["defaultView", "sortField", "sortDirection", "grouping", "filters"]);
|
|
15818
16014
|
const ROOT_KEYS = /* @__PURE__ */ new Set(["global", "projects"]);
|
|
@@ -15831,7 +16027,7 @@ function createDashboardServer(options) {
|
|
|
15831
16027
|
const unknown = unknownKey(obj, FILTER_KEYS, "filters");
|
|
15832
16028
|
if (unknown) return { ok: false, error: unknown };
|
|
15833
16029
|
const out = {};
|
|
15834
|
-
for (const key of ["status", "type", "priority", "assignee", "project"]) {
|
|
16030
|
+
for (const key of ["status", "type", "priority", "assignee", "project", "tags"]) {
|
|
15835
16031
|
if (obj[key] !== void 0) {
|
|
15836
16032
|
if (!isFilterValue(obj[key])) {
|
|
15837
16033
|
return { ok: false, error: `filters.${key} must be a non-empty string or array of non-empty strings` };
|
|
@@ -16056,6 +16252,15 @@ function createDashboardServer(options) {
|
|
|
16056
16252
|
res.status(500).json({ error: "Failed to list assignments" });
|
|
16057
16253
|
}
|
|
16058
16254
|
});
|
|
16255
|
+
app.get("/api/archived", async (_req, res) => {
|
|
16256
|
+
try {
|
|
16257
|
+
const result = await listArchived(projectsDir, assignmentsDir2);
|
|
16258
|
+
res.json(result);
|
|
16259
|
+
} catch (error) {
|
|
16260
|
+
console.error("Error listing archived content:", error);
|
|
16261
|
+
res.status(500).json({ error: "Failed to list archived content" });
|
|
16262
|
+
}
|
|
16263
|
+
});
|
|
16059
16264
|
app.get("/api/projects/:slug", async (req, res) => {
|
|
16060
16265
|
try {
|
|
16061
16266
|
const detail = await getProjectDetail(projectsDir, req.params.slug);
|