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.
Files changed (64) hide show
  1. package/dashboard/dist/assets/{_basePickBy-Cx0q44iE.js → _basePickBy-CFyquBjL.js} +1 -1
  2. package/dashboard/dist/assets/{_baseUniq-CfFbkKif.js → _baseUniq-S9hlXGZI.js} +1 -1
  3. package/dashboard/dist/assets/{arc-XluBomWz.js → arc-DNRWeoWH.js} +1 -1
  4. package/dashboard/dist/assets/{architectureDiagram-2XIMDMQ5-CihGybG6.js → architectureDiagram-2XIMDMQ5-BujnKgOd.js} +1 -1
  5. package/dashboard/dist/assets/{blockDiagram-WCTKOSBZ-BPUKO5fj.js → blockDiagram-WCTKOSBZ-DOaDmG_o.js} +1 -1
  6. package/dashboard/dist/assets/{c4Diagram-IC4MRINW-DKVq_j1f.js → c4Diagram-IC4MRINW-BeBwVhQx.js} +1 -1
  7. package/dashboard/dist/assets/channel-yUe8n53Y.js +1 -0
  8. package/dashboard/dist/assets/{chunk-4BX2VUAB-DAaE8oN2.js → chunk-4BX2VUAB-CtzM4Epf.js} +1 -1
  9. package/dashboard/dist/assets/{chunk-55IACEB6-BZoVpy5G.js → chunk-55IACEB6-D2cd1TFn.js} +1 -1
  10. package/dashboard/dist/assets/{chunk-FMBD7UC4-Cuo9M0jI.js → chunk-FMBD7UC4-DAqRATzJ.js} +1 -1
  11. package/dashboard/dist/assets/{chunk-JSJVCQXG-DGzypY5S.js → chunk-JSJVCQXG-CIEi981s.js} +1 -1
  12. package/dashboard/dist/assets/{chunk-KX2RTZJC-DfxYbrKI.js → chunk-KX2RTZJC-BMv4Xwdm.js} +1 -1
  13. package/dashboard/dist/assets/{chunk-NQ4KR5QH-BEbwpe5N.js → chunk-NQ4KR5QH-BKTm5ctg.js} +1 -1
  14. package/dashboard/dist/assets/{chunk-QZHKN3VN-DDjrkJqg.js → chunk-QZHKN3VN-DjmLZcpF.js} +1 -1
  15. package/dashboard/dist/assets/{chunk-WL4C6EOR-BSMHQ3p4.js → chunk-WL4C6EOR-CphX8dKS.js} +1 -1
  16. package/dashboard/dist/assets/classDiagram-VBA2DB6C-C0f25bKl.js +1 -0
  17. package/dashboard/dist/assets/classDiagram-v2-RAHNMMFH-C0f25bKl.js +1 -0
  18. package/dashboard/dist/assets/clone-D-aj2eAK.js +1 -0
  19. package/dashboard/dist/assets/{cose-bilkent-S5V4N54A-BgUyyImN.js → cose-bilkent-S5V4N54A-BZ-8TY36.js} +1 -1
  20. package/dashboard/dist/assets/{dagre-KLK3FWXG-zAe7pq6F.js → dagre-KLK3FWXG-Bx5whsTp.js} +1 -1
  21. package/dashboard/dist/assets/{diagram-E7M64L7V-B1bVb4ll.js → diagram-E7M64L7V-BVOQiv2e.js} +1 -1
  22. package/dashboard/dist/assets/{diagram-IFDJBPK2-DZCxJIkJ.js → diagram-IFDJBPK2-D7bQBDL_.js} +1 -1
  23. package/dashboard/dist/assets/{diagram-P4PSJMXO-BuPgL5q1.js → diagram-P4PSJMXO-CwO6aafO.js} +1 -1
  24. package/dashboard/dist/assets/{erDiagram-INFDFZHY-l3YvnHT9.js → erDiagram-INFDFZHY-DCNz82wG.js} +1 -1
  25. package/dashboard/dist/assets/{flowDiagram-PKNHOUZH-CLOLQLdW.js → flowDiagram-PKNHOUZH-Dq6yNscM.js} +1 -1
  26. package/dashboard/dist/assets/{ganttDiagram-A5KZAMGK-D1yLx2Ap.js → ganttDiagram-A5KZAMGK-B2xtuK0X.js} +1 -1
  27. package/dashboard/dist/assets/{gitGraphDiagram-K3NZZRJ6-uC4rY3UQ.js → gitGraphDiagram-K3NZZRJ6-D8mNkQd3.js} +1 -1
  28. package/dashboard/dist/assets/{graph-DvUnLFOz.js → graph-DMbUUtaR.js} +1 -1
  29. package/dashboard/dist/assets/index-BlikGvSd.js +550 -0
  30. package/dashboard/dist/assets/index-CsyJIOqR.css +1 -0
  31. package/dashboard/dist/assets/{infoDiagram-LFFYTUFH-B6jLLOUk.js → infoDiagram-LFFYTUFH-BsrDYNxL.js} +1 -1
  32. package/dashboard/dist/assets/{ishikawaDiagram-PHBUUO56-CDvhubqL.js → ishikawaDiagram-PHBUUO56-DlUJjWQM.js} +1 -1
  33. package/dashboard/dist/assets/{journeyDiagram-4ABVD52K-BOtMcoBj.js → journeyDiagram-4ABVD52K-DBbVdCE_.js} +1 -1
  34. package/dashboard/dist/assets/{kanban-definition-K7BYSVSG-DC05q9p4.js → kanban-definition-K7BYSVSG-Cuv3IHJ_.js} +1 -1
  35. package/dashboard/dist/assets/{layout-B4jiZlj6.js → layout-CgL4rycu.js} +1 -1
  36. package/dashboard/dist/assets/{linear-DpQXVSyZ.js → linear-CgnhFMi4.js} +1 -1
  37. package/dashboard/dist/assets/{mermaid.core-aNcWKq-4.js → mermaid.core-C3eCFd8P.js} +4 -4
  38. package/dashboard/dist/assets/{mindmap-definition-YRQLILUH-Dv4qo7wQ.js → mindmap-definition-YRQLILUH-Bsm6cGkL.js} +1 -1
  39. package/dashboard/dist/assets/{pieDiagram-SKSYHLDU-ByRY4ahm.js → pieDiagram-SKSYHLDU-DBRLfMtc.js} +1 -1
  40. package/dashboard/dist/assets/{quadrantDiagram-337W2JSQ-Dt5_Xjwp.js → quadrantDiagram-337W2JSQ-CES4NNws.js} +1 -1
  41. package/dashboard/dist/assets/{requirementDiagram-Z7DCOOCP-CSRV6VcE.js → requirementDiagram-Z7DCOOCP-B0cUyFW2.js} +1 -1
  42. package/dashboard/dist/assets/{sankeyDiagram-WA2Y5GQK-DA8gxGEq.js → sankeyDiagram-WA2Y5GQK-LVaVg6wF.js} +1 -1
  43. package/dashboard/dist/assets/{sequenceDiagram-2WXFIKYE-CVZr9aKv.js → sequenceDiagram-2WXFIKYE-BISSySYI.js} +1 -1
  44. package/dashboard/dist/assets/{stateDiagram-RAJIS63D-yIU4X0e8.js → stateDiagram-RAJIS63D-rdMNHAXE.js} +1 -1
  45. package/dashboard/dist/assets/stateDiagram-v2-FVOUBMTO-ti7j33kD.js +1 -0
  46. package/dashboard/dist/assets/{timeline-definition-YZTLITO2-DnfvRVEJ.js → timeline-definition-YZTLITO2-fVRyntnZ.js} +1 -1
  47. package/dashboard/dist/assets/{treemap-KZPCXAKY-p5sbmBwK.js → treemap-KZPCXAKY-ECgEamZF.js} +1 -1
  48. package/dashboard/dist/assets/{vennDiagram-LZ73GAT5-Do2Vp3nv.js → vennDiagram-LZ73GAT5-DkL5XCcJ.js} +1 -1
  49. package/dashboard/dist/assets/{xychartDiagram-JWTSCODW-BrfAGQWt.js → xychartDiagram-JWTSCODW-DSQCD2gS.js} +1 -1
  50. package/dashboard/dist/index.html +2 -2
  51. package/dist/dashboard/server.js +328 -123
  52. package/dist/dashboard/server.js.map +1 -1
  53. package/dist/index.js +795 -488
  54. package/dist/index.js.map +1 -1
  55. package/dist/launch/index.js +75 -10
  56. package/dist/launch/index.js.map +1 -1
  57. package/package.json +1 -1
  58. package/dashboard/dist/assets/channel-mNClnLVi.js +0 -1
  59. package/dashboard/dist/assets/classDiagram-VBA2DB6C-Dv6vdcHm.js +0 -1
  60. package/dashboard/dist/assets/classDiagram-v2-RAHNMMFH-Dv6vdcHm.js +0 -1
  61. package/dashboard/dist/assets/clone-CRLXHkrd.js +0 -1
  62. package/dashboard/dist/assets/index-BoVXMp5e.js +0 -535
  63. package/dashboard/dist/assets/index-XappA8I7.css +0 -1
  64. package/dashboard/dist/assets/stateDiagram-v2-FVOUBMTO-DMBx3hAg.js +0 -1
@@ -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 setFrontmatterField(content, key, value) {
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 = setFrontmatterField(raw, "slug", newSlug);
3150
- next = setFrontmatterField(next, "updated", `"${nowTimestamp()}"`);
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 recentActivity = buildRecentActivity(projectRecords, standaloneRecords);
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: projectRecords.filter((record) => record.summary.status === "active").length,
5096
- inProgressAssignments: projectRecords.reduce(
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: projectRecords.reduce(
5169
+ blockedAssignments: activeProjectRecords.reduce(
5101
5170
  (total, record) => total + (record.summary.progress["blocked"] ?? 0),
5102
5171
  0
5103
5172
  ),
5104
- reviewAssignments: projectRecords.reduce(
5173
+ reviewAssignments: activeProjectRecords.reduce(
5105
5174
  (total, record) => total + (record.summary.progress["review"] ?? 0),
5106
5175
  0
5107
5176
  ),
5108
- failedAssignments: projectRecords.reduce(
5177
+ failedAssignments: activeProjectRecords.reduce(
5109
5178
  (total, record) => total + (record.summary.progress["failed"] ?? 0),
5110
5179
  0
5111
5180
  ),
5112
- staleAssignments: projectRecords.reduce(
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
- ) + standaloneRecords.filter((sr) => isStale(sr.record.updated)).length
5184
+ ) + activeStandaloneRecords.filter((sr) => isStale(sr.record.updated)).length
5116
5185
  },
5117
5186
  hero,
5118
5187
  segments,
5119
5188
  recentSessions,
5120
- recentProjects: projectRecords.map((record) => record.summary).sort((left, right) => compareTimestamps(right.updated, left.updated)).slice(0, RECENT_PROJECTS_LIMIT),
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
- async (record) => Promise.all(
5130
- record.assignments.map(
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
- standaloneRecords.map(async (sr) => toStandaloneBoardItem(sr))
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 progress = { total: assignments.length };
6000
+ const active = activeAssignments(assignments);
6001
+ const progress = { total: active.length };
5870
6002
  const perAssignment = await Promise.all(
5871
- assignments.map(async (assignment) => {
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
- updated: assignment.updated
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
- record.assignments.map(async (assignment) => {
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", "archived", ...config.statuses.map((s) => s.id)];
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);