syntaur 0.1.13 → 0.2.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 (128) hide show
  1. package/README.md +5 -0
  2. package/dashboard/dist/assets/{_basePickBy-DXzhD14q.js → _basePickBy-CHKX1r7P.js} +1 -1
  3. package/dashboard/dist/assets/{_baseUniq-gxypqvP5.js → _baseUniq-CTxTc4MS.js} +1 -1
  4. package/dashboard/dist/assets/{arc-Ce7nYKSm.js → arc-BUo5zftd.js} +1 -1
  5. package/dashboard/dist/assets/{architectureDiagram-2XIMDMQ5-zX4f4_Mf.js → architectureDiagram-2XIMDMQ5-CrJLm-P0.js} +1 -1
  6. package/dashboard/dist/assets/{blockDiagram-WCTKOSBZ-auOdy7nH.js → blockDiagram-WCTKOSBZ-BK60lBBJ.js} +1 -1
  7. package/dashboard/dist/assets/{c4Diagram-IC4MRINW-C2kkjPbW.js → c4Diagram-IC4MRINW-C7oJEvA0.js} +1 -1
  8. package/dashboard/dist/assets/channel-DdltvFFH.js +1 -0
  9. package/dashboard/dist/assets/{chunk-4BX2VUAB-B7dfpnbG.js → chunk-4BX2VUAB-CjUPlzHz.js} +1 -1
  10. package/dashboard/dist/assets/{chunk-55IACEB6-r1_jHZYp.js → chunk-55IACEB6-6HmWguiO.js} +1 -1
  11. package/dashboard/dist/assets/{chunk-FMBD7UC4-5mMONjMK.js → chunk-FMBD7UC4-CLuJnd1b.js} +1 -1
  12. package/dashboard/dist/assets/{chunk-JSJVCQXG-CwKj-Es4.js → chunk-JSJVCQXG-B4d62qWV.js} +1 -1
  13. package/dashboard/dist/assets/{chunk-KX2RTZJC-ByoW-HgN.js → chunk-KX2RTZJC-AsEKRPq2.js} +1 -1
  14. package/dashboard/dist/assets/{chunk-NQ4KR5QH-D1olOovd.js → chunk-NQ4KR5QH-DQhHHvwY.js} +1 -1
  15. package/dashboard/dist/assets/{chunk-QZHKN3VN-CB8_FC8w.js → chunk-QZHKN3VN-Ds1TtI3E.js} +1 -1
  16. package/dashboard/dist/assets/{chunk-WL4C6EOR-CFEqRrE1.js → chunk-WL4C6EOR-C7jE3-cR.js} +1 -1
  17. package/dashboard/dist/assets/classDiagram-VBA2DB6C-BHqdFE-8.js +1 -0
  18. package/dashboard/dist/assets/classDiagram-v2-RAHNMMFH-BHqdFE-8.js +1 -0
  19. package/dashboard/dist/assets/clone-CBJOOeOm.js +1 -0
  20. package/dashboard/dist/assets/{cose-bilkent-S5V4N54A-D6dGVXEI.js → cose-bilkent-S5V4N54A-C9ka5v1m.js} +1 -1
  21. package/dashboard/dist/assets/{dagre-KLK3FWXG-Cvg9CgP-.js → dagre-KLK3FWXG-BbgPQBKy.js} +1 -1
  22. package/dashboard/dist/assets/{diagram-E7M64L7V-iCBudhZD.js → diagram-E7M64L7V-DpdeZFD4.js} +1 -1
  23. package/dashboard/dist/assets/{diagram-IFDJBPK2-BGniy7VQ.js → diagram-IFDJBPK2-FlHLQzOV.js} +1 -1
  24. package/dashboard/dist/assets/{diagram-P4PSJMXO-B6Ie044E.js → diagram-P4PSJMXO-B22NkEF_.js} +1 -1
  25. package/dashboard/dist/assets/{erDiagram-INFDFZHY-BHvFRNhJ.js → erDiagram-INFDFZHY-zSqmtDid.js} +1 -1
  26. package/dashboard/dist/assets/{flowDiagram-PKNHOUZH-CN86Zu3Q.js → flowDiagram-PKNHOUZH-BP_0XmVV.js} +1 -1
  27. package/dashboard/dist/assets/{ganttDiagram-A5KZAMGK-D-1fKFjW.js → ganttDiagram-A5KZAMGK-8uRyYgZV.js} +1 -1
  28. package/dashboard/dist/assets/{gitGraphDiagram-K3NZZRJ6-Dtf1A6KL.js → gitGraphDiagram-K3NZZRJ6-JFqg8sv4.js} +1 -1
  29. package/dashboard/dist/assets/{graph-B6H_kXSs.js → graph-a-PAH599.js} +1 -1
  30. package/dashboard/dist/assets/index-CoVCLSh2.css +1 -0
  31. package/dashboard/dist/assets/index-yyAIuzrP.js +471 -0
  32. package/dashboard/dist/assets/{infoDiagram-LFFYTUFH-R9wJj4JF.js → infoDiagram-LFFYTUFH-C3kq7Nbv.js} +1 -1
  33. package/dashboard/dist/assets/{ishikawaDiagram-PHBUUO56-CJmeR-bX.js → ishikawaDiagram-PHBUUO56-Kqi4EZ-n.js} +1 -1
  34. package/dashboard/dist/assets/{journeyDiagram-4ABVD52K-FcUhyu8I.js → journeyDiagram-4ABVD52K-CTfv0Wcr.js} +1 -1
  35. package/dashboard/dist/assets/{kanban-definition-K7BYSVSG-C8UcTIwW.js → kanban-definition-K7BYSVSG-Dmx0lgvR.js} +1 -1
  36. package/dashboard/dist/assets/{layout-DzBy6alw.js → layout-KKRbT2Od.js} +1 -1
  37. package/dashboard/dist/assets/{linear-CZJCNOB9.js → linear-5egaBiw7.js} +1 -1
  38. package/dashboard/dist/assets/{mermaid.core-fMQRe9Gq.js → mermaid.core-C9pF_oFQ.js} +4 -4
  39. package/dashboard/dist/assets/{mindmap-definition-YRQLILUH-BFwp-LS-.js → mindmap-definition-YRQLILUH-C7HXYEXt.js} +1 -1
  40. package/dashboard/dist/assets/{pieDiagram-SKSYHLDU-CQLmPkkd.js → pieDiagram-SKSYHLDU-DkdZm-YP.js} +1 -1
  41. package/dashboard/dist/assets/{quadrantDiagram-337W2JSQ-DAmi-dmD.js → quadrantDiagram-337W2JSQ-DkcRJs5F.js} +1 -1
  42. package/dashboard/dist/assets/{requirementDiagram-Z7DCOOCP-Dcdts4kX.js → requirementDiagram-Z7DCOOCP-BaTDVYTl.js} +1 -1
  43. package/dashboard/dist/assets/{sankeyDiagram-WA2Y5GQK-By8LRvM0.js → sankeyDiagram-WA2Y5GQK-DvPLbGV5.js} +1 -1
  44. package/dashboard/dist/assets/{sequenceDiagram-2WXFIKYE-BsvqgtTz.js → sequenceDiagram-2WXFIKYE-DQoZ2xMK.js} +1 -1
  45. package/dashboard/dist/assets/{stateDiagram-RAJIS63D-DFNOD7cx.js → stateDiagram-RAJIS63D-CS4l0OjM.js} +1 -1
  46. package/dashboard/dist/assets/stateDiagram-v2-FVOUBMTO-DkBtE1WJ.js +1 -0
  47. package/dashboard/dist/assets/{timeline-definition-YZTLITO2-CMcgJGjn.js → timeline-definition-YZTLITO2-aC0iCFCW.js} +1 -1
  48. package/dashboard/dist/assets/{treemap-KZPCXAKY-BWsRNHwq.js → treemap-KZPCXAKY-Ie-PFjgx.js} +1 -1
  49. package/dashboard/dist/assets/{vennDiagram-LZ73GAT5-io7-2Tod.js → vennDiagram-LZ73GAT5-CJN3ExTQ.js} +1 -1
  50. package/dashboard/dist/assets/{xychartDiagram-JWTSCODW-AVnh4fDS.js → xychartDiagram-JWTSCODW-DSiDu1CN.js} +1 -1
  51. package/dashboard/dist/index.html +2 -2
  52. package/dist/dashboard/server.d.ts +1 -1
  53. package/dist/dashboard/server.js +1163 -734
  54. package/dist/dashboard/server.js.map +1 -1
  55. package/dist/index.js +3979 -1372
  56. package/dist/index.js.map +1 -1
  57. package/examples/playbooks/keep-records-updated.md +1 -1
  58. package/examples/playbooks/read-before-plan.md +5 -5
  59. package/examples/{sample-mission → sample-project}/_index-assignments.md +1 -1
  60. package/examples/{sample-mission → sample-project}/_index-decisions.md +1 -1
  61. package/examples/{sample-mission → sample-project}/_index-plans.md +1 -1
  62. package/examples/{sample-mission → sample-project}/_status.md +3 -3
  63. package/examples/{sample-mission → sample-project}/assignments/design-auth-schema/assignment.md +4 -1
  64. package/examples/{sample-mission → sample-project}/assignments/implement-jwt-middleware/assignment.md +4 -1
  65. package/examples/{sample-mission → sample-project}/assignments/write-auth-tests/assignment.md +4 -1
  66. package/examples/{sample-mission → sample-project}/manifest.md +3 -3
  67. package/examples/{sample-mission → sample-project}/memories/_index.md +2 -2
  68. package/examples/{sample-mission → sample-project}/memories/postgres-connection-pooling.md +1 -1
  69. package/examples/{sample-mission → sample-project}/resources/_index.md +1 -1
  70. package/package.json +5 -3
  71. package/platforms/README.md +7 -7
  72. package/platforms/claude-code/README.md +1 -1
  73. package/platforms/claude-code/agents/syntaur-expert.md +57 -57
  74. package/platforms/claude-code/commands/doctor-syntaur/doctor-syntaur.md +112 -0
  75. package/platforms/claude-code/commands/track-session/track-session.md +8 -8
  76. package/platforms/claude-code/hooks/enforce-boundaries.sh +4 -4
  77. package/platforms/claude-code/hooks/hooks.json +1 -1
  78. package/platforms/claude-code/hooks/session-cleanup.sh +5 -5
  79. package/platforms/claude-code/references/file-ownership.md +8 -8
  80. package/platforms/claude-code/references/protocol-summary.md +7 -6
  81. package/platforms/claude-code/skills/complete-assignment/SKILL.md +21 -17
  82. package/platforms/claude-code/skills/create-assignment/SKILL.md +15 -14
  83. package/platforms/claude-code/skills/grab-assignment/SKILL.md +56 -49
  84. package/platforms/claude-code/skills/plan-assignment/SKILL.md +57 -10
  85. package/platforms/claude-code/skills/syntaur-protocol/SKILL.md +21 -17
  86. package/platforms/codex/.codex-plugin/plugin.json +3 -3
  87. package/platforms/codex/README.md +1 -1
  88. package/platforms/codex/adapters/AGENTS.md.template +3 -3
  89. package/platforms/codex/agents/openai.yaml +2 -2
  90. package/platforms/codex/agents/syntaur-operator.md +33 -30
  91. package/platforms/codex/references/file-ownership.md +8 -8
  92. package/platforms/codex/references/protocol-summary.md +11 -6
  93. package/platforms/codex/scripts/enforce-boundaries.sh +2 -2
  94. package/platforms/codex/scripts/session-cleanup.sh +2 -2
  95. package/platforms/codex/skills/complete-assignment/SKILL.md +6 -6
  96. package/platforms/codex/skills/create-assignment/SKILL.md +8 -7
  97. package/platforms/codex/skills/grab-assignment/SKILL.md +30 -20
  98. package/platforms/codex/skills/plan-assignment/SKILL.md +19 -11
  99. package/platforms/codex/skills/syntaur-protocol/SKILL.md +26 -21
  100. package/platforms/cursor/README.md +1 -1
  101. package/platforms/cursor/adapters/syntaur-protocol.mdc +1 -1
  102. package/platforms/opencode/README.md +1 -1
  103. package/platforms/opencode/adapters/opencode.json.template +1 -1
  104. package/dashboard/dist/assets/channel-PMR2DuGi.js +0 -1
  105. package/dashboard/dist/assets/classDiagram-VBA2DB6C-DmESf_RL.js +0 -1
  106. package/dashboard/dist/assets/classDiagram-v2-RAHNMMFH-DmESf_RL.js +0 -1
  107. package/dashboard/dist/assets/clone-WlIeyha4.js +0 -1
  108. package/dashboard/dist/assets/index-BhuXD-Q5.js +0 -445
  109. package/dashboard/dist/assets/index-BnqH-RIk.css +0 -1
  110. package/dashboard/dist/assets/stateDiagram-v2-FVOUBMTO-DVO-Epiz.js +0 -1
  111. package/examples/sample-mission/agent.md +0 -33
  112. package/examples/sample-mission/claude.md +0 -13
  113. package/platforms/claude-code/skills/create-mission/SKILL.md +0 -51
  114. package/platforms/codex/skills/create-mission/SKILL.md +0 -35
  115. /package/examples/{sample-mission → sample-project}/assignments/design-auth-schema/decision-record.md +0 -0
  116. /package/examples/{sample-mission → sample-project}/assignments/design-auth-schema/handoff.md +0 -0
  117. /package/examples/{sample-mission → sample-project}/assignments/design-auth-schema/plan.md +0 -0
  118. /package/examples/{sample-mission → sample-project}/assignments/design-auth-schema/scratchpad.md +0 -0
  119. /package/examples/{sample-mission → sample-project}/assignments/implement-jwt-middleware/decision-record.md +0 -0
  120. /package/examples/{sample-mission → sample-project}/assignments/implement-jwt-middleware/handoff.md +0 -0
  121. /package/examples/{sample-mission → sample-project}/assignments/implement-jwt-middleware/plan.md +0 -0
  122. /package/examples/{sample-mission → sample-project}/assignments/implement-jwt-middleware/scratchpad.md +0 -0
  123. /package/examples/{sample-mission → sample-project}/assignments/write-auth-tests/decision-record.md +0 -0
  124. /package/examples/{sample-mission → sample-project}/assignments/write-auth-tests/handoff.md +0 -0
  125. /package/examples/{sample-mission → sample-project}/assignments/write-auth-tests/plan.md +0 -0
  126. /package/examples/{sample-mission → sample-project}/assignments/write-auth-tests/scratchpad.md +0 -0
  127. /package/examples/{sample-mission/mission.md → sample-project/project.md} +0 -0
  128. /package/examples/{sample-mission → sample-project}/resources/auth-requirements.md +0 -0
@@ -8,6 +8,40 @@ var __export = (target, all) => {
8
8
  __defProp(target, name, { get: all[name], enumerable: true });
9
9
  };
10
10
 
11
+ // src/utils/paths.ts
12
+ import { homedir } from "os";
13
+ import { resolve } from "path";
14
+ function expandHome(p) {
15
+ if (p.startsWith("~/") || p === "~") {
16
+ return resolve(homedir(), p.slice(2));
17
+ }
18
+ return p;
19
+ }
20
+ function syntaurRoot() {
21
+ const override = process.env.SYNTAUR_HOME;
22
+ if (override && override.length > 0) {
23
+ return resolve(expandHome(override));
24
+ }
25
+ return resolve(homedir(), ".syntaur");
26
+ }
27
+ function defaultProjectDir() {
28
+ return resolve(syntaurRoot(), "projects");
29
+ }
30
+ function serversDir() {
31
+ return resolve(syntaurRoot(), "servers");
32
+ }
33
+ function playbooksDir() {
34
+ return resolve(syntaurRoot(), "playbooks");
35
+ }
36
+ function todosDir() {
37
+ return resolve(syntaurRoot(), "todos");
38
+ }
39
+ var init_paths = __esm({
40
+ "src/utils/paths.ts"() {
41
+ "use strict";
42
+ }
43
+ });
44
+
11
45
  // src/lifecycle/types.ts
12
46
  var DEFAULT_STATUSES;
13
47
  var init_types = __esm({
@@ -184,6 +218,8 @@ function parseAssignmentFrontmatter(fileContent) {
184
218
  id: getField2("id") ?? "",
185
219
  slug: getField2("slug") ?? "",
186
220
  title: getField2("title") ?? "",
221
+ project: getField2("project"),
222
+ type: getField2("type"),
187
223
  status: getField2("status") ?? "pending",
188
224
  priority: getField2("priority") ?? "medium",
189
225
  created: getField2("created") ?? "",
@@ -282,10 +318,10 @@ var init_timestamp = __esm({
282
318
  });
283
319
 
284
320
  // src/lifecycle/transitions.ts
285
- import { resolve } from "path";
321
+ import { resolve as resolve2 } from "path";
286
322
  import { readFile } from "fs/promises";
287
- function resolveAssignmentPath(missionDir, assignmentSlug) {
288
- return resolve(missionDir, "assignments", assignmentSlug, "assignment.md");
323
+ function resolveAssignmentPath(projectDir, assignmentSlug) {
324
+ return resolve2(projectDir, "assignments", assignmentSlug, "assignment.md");
289
325
  }
290
326
  async function readAssignment(filePath) {
291
327
  if (!await fileExists(filePath)) {
@@ -295,11 +331,11 @@ async function readAssignment(filePath) {
295
331
  const frontmatter = parseAssignmentFrontmatter(content);
296
332
  return { content, frontmatter };
297
333
  }
298
- async function checkDependencies(missionDir, dependsOn, terminalStatuses) {
334
+ async function checkDependencies(projectDir, dependsOn, terminalStatuses) {
299
335
  const terminals = terminalStatuses ?? /* @__PURE__ */ new Set(["completed"]);
300
336
  const unmet = [];
301
337
  for (const depSlug of dependsOn) {
302
- const depPath = resolveAssignmentPath(missionDir, depSlug);
338
+ const depPath = resolveAssignmentPath(projectDir, depSlug);
303
339
  if (!await fileExists(depPath)) {
304
340
  unmet.push(`${depSlug} (file not found)`);
305
341
  continue;
@@ -312,8 +348,8 @@ async function checkDependencies(missionDir, dependsOn, terminalStatuses) {
312
348
  }
313
349
  return { satisfied: unmet.length === 0, unmet };
314
350
  }
315
- async function executeTransition(missionDir, assignmentSlug, command, options = {}) {
316
- const filePath = resolveAssignmentPath(missionDir, assignmentSlug);
351
+ async function executeTransition(projectDir, assignmentSlug, command, options = {}) {
352
+ const filePath = resolveAssignmentPath(projectDir, assignmentSlug);
317
353
  const { content, frontmatter } = await readAssignment(filePath);
318
354
  const targetStatus = getTargetStatus(frontmatter.status, command, options.transitionTable);
319
355
  if (!targetStatus) {
@@ -325,7 +361,7 @@ async function executeTransition(missionDir, assignmentSlug, command, options =
325
361
  }
326
362
  const warnings = [];
327
363
  if (command === "start" && frontmatter.dependsOn.length > 0) {
328
- const depCheck = await checkDependencies(missionDir, frontmatter.dependsOn, options.terminalStatuses);
364
+ const depCheck = await checkDependencies(projectDir, frontmatter.dependsOn, options.terminalStatuses);
329
365
  if (!depCheck.satisfied) {
330
366
  warnings.push(`Starting with unmet dependencies: ${depCheck.unmet.join(", ")}`);
331
367
  }
@@ -374,28 +410,28 @@ var init_lifecycle = __esm({
374
410
  }
375
411
  });
376
412
 
377
- // src/utils/paths.ts
378
- import { homedir } from "os";
379
- import { resolve as resolve2 } from "path";
380
- function expandHome(p) {
381
- if (p.startsWith("~/") || p === "~") {
382
- return resolve2(homedir(), p.slice(2));
383
- }
384
- return p;
385
- }
386
- function syntaurRoot() {
387
- return resolve2(homedir(), ".syntaur");
388
- }
389
- function defaultMissionDir() {
390
- return resolve2(syntaurRoot(), "missions");
391
- }
392
- var init_paths = __esm({
393
- "src/utils/paths.ts"() {
394
- "use strict";
395
- }
396
- });
397
-
398
413
  // src/templates/config.ts
414
+ function renderConfig(params) {
415
+ return `---
416
+ version: "1.0"
417
+ defaultProjectDir: ${params.defaultProjectDir}
418
+ onboarding:
419
+ completed: false
420
+ agentDefaults:
421
+ trustLevel: medium
422
+ autoApprove: false
423
+ backup:
424
+ repo: null
425
+ categories: projects, playbooks, todos, servers, config
426
+ lastBackup: null
427
+ lastRestore: null
428
+ ---
429
+
430
+ # Syntaur Configuration
431
+
432
+ Global configuration for the Syntaur CLI.
433
+ `;
434
+ }
399
435
  var init_config = __esm({
400
436
  "src/templates/config.ts"() {
401
437
  "use strict";
@@ -549,6 +585,14 @@ function serializeStatusConfig(statuses) {
549
585
  }
550
586
  return lines.join("\n");
551
587
  }
588
+ function serializeBackupConfig(backup) {
589
+ const lines = ["backup:"];
590
+ lines.push(` repo: ${backup.repo ?? "null"}`);
591
+ lines.push(` categories: ${backup.categories}`);
592
+ lines.push(` lastBackup: ${backup.lastBackup ?? "null"}`);
593
+ lines.push(` lastRestore: ${backup.lastRestore ?? "null"}`);
594
+ return lines.join("\n");
595
+ }
552
596
  function stripTopLevelBlock(fmBlock, key) {
553
597
  const blockStart = fmBlock.match(new RegExp(`^${key}:\\s*$`, "m"));
554
598
  if (!blockStart) {
@@ -590,8 +634,8 @@ async function writeStatusConfig(statuses) {
590
634
  const statusBlock = serializeStatusConfig(statuses);
591
635
  if (!await fileExists(configPath)) {
592
636
  const content = `---
593
- version: "1.0"
594
- defaultMissionDir: ~/missions
637
+ version: "2.0"
638
+ defaultProjectDir: ~/projects
595
639
  ${statusBlock}
596
640
  ---
597
641
  `;
@@ -602,7 +646,7 @@ ${statusBlock}
602
646
  const fmMatch = existing.match(/^(---\n)([\s\S]*?)\n(---)/);
603
647
  if (!fmMatch) {
604
648
  const content = `---
605
- version: "1.0"
649
+ version: "2.0"
606
650
  ${statusBlock}
607
651
  ---
608
652
  ${existing}`;
@@ -653,6 +697,40 @@ ${cleanedFm}
653
697
  ---${afterFrontmatter}`;
654
698
  await writeFileForce(configPath, newContent);
655
699
  }
700
+ async function updateBackupConfig(backup) {
701
+ const configPath = resolve3(syntaurRoot(), "config.md");
702
+ const current = (await readConfig()).backup;
703
+ const nextBackup = {
704
+ repo: current?.repo ?? null,
705
+ categories: current?.categories ?? "projects, playbooks, todos, servers, config",
706
+ lastBackup: current?.lastBackup ?? null,
707
+ lastRestore: current?.lastRestore ?? null,
708
+ ...backup
709
+ };
710
+ const backupBlock = serializeBackupConfig(nextBackup);
711
+ const existing = await fileExists(configPath) ? await readFile2(configPath, "utf-8") : renderConfig({ defaultProjectDir: defaultProjectDir() });
712
+ const fmMatch = existing.match(/^(---\n)([\s\S]*?)\n(---)/);
713
+ if (!fmMatch) {
714
+ const content = `---
715
+ version: "2.0"
716
+ defaultProjectDir: ${defaultProjectDir()}
717
+ ${backupBlock}
718
+ ---
719
+ ${existing}`;
720
+ await writeFileForce(configPath, content.replace(/\n\n---/, "\n---"));
721
+ return;
722
+ }
723
+ const fmBlock = fmMatch[2];
724
+ const afterFrontmatter = existing.slice(fmMatch[0].length);
725
+ const cleanedFm = stripTopLevelBlock(fmBlock, "backup");
726
+ const newFm = `${cleanedFm}
727
+ ${backupBlock}`.replace(/^\n+/, "");
728
+ const normalizedFm = newFm.replace(/\n+$/, "");
729
+ const newContent = `---
730
+ ${normalizedFm}
731
+ ---${afterFrontmatter}`;
732
+ await writeFileForce(configPath, newContent);
733
+ }
656
734
  async function readConfig() {
657
735
  const configPath = resolve3(syntaurRoot(), "config.md");
658
736
  if (!await fileExists(configPath)) {
@@ -664,16 +742,16 @@ async function readConfig() {
664
742
  console.warn("Warning: ~/.syntaur/config.md has malformed frontmatter, using defaults");
665
743
  return { ...DEFAULT_CONFIG };
666
744
  }
667
- let missionDir = fm["defaultMissionDir"] ? expandHome(String(fm["defaultMissionDir"])) : DEFAULT_CONFIG.defaultMissionDir;
668
- if (!isAbsolute(missionDir)) {
745
+ let projectDir = fm["defaultProjectDir"] ? expandHome(String(fm["defaultProjectDir"])) : DEFAULT_CONFIG.defaultProjectDir;
746
+ if (!isAbsolute(projectDir)) {
669
747
  console.warn(
670
- `Warning: config.md defaultMissionDir is not an absolute path ("${fm["defaultMissionDir"]}"), using default`
748
+ `Warning: config.md defaultProjectDir is not an absolute path ("${fm["defaultProjectDir"]}"), using default`
671
749
  );
672
- missionDir = DEFAULT_CONFIG.defaultMissionDir;
750
+ projectDir = DEFAULT_CONFIG.defaultProjectDir;
673
751
  }
674
752
  return {
675
753
  version: fm["version"] || DEFAULT_CONFIG.version,
676
- defaultMissionDir: missionDir,
754
+ defaultProjectDir: projectDir,
677
755
  onboarding: {
678
756
  completed: fm["onboarding.completed"] === "true"
679
757
  },
@@ -695,7 +773,14 @@ async function readConfig() {
695
773
  "integrations.codexMarketplacePath"
696
774
  )
697
775
  },
698
- statuses: parseStatusConfig(content)
776
+ backup: fm["backup.repo"] || fm["backup.categories"] ? {
777
+ repo: fm["backup.repo"] && fm["backup.repo"] !== "null" ? fm["backup.repo"] : null,
778
+ categories: fm["backup.categories"] || "projects, playbooks, todos, servers, config",
779
+ lastBackup: fm["backup.lastBackup"] && fm["backup.lastBackup"] !== "null" ? fm["backup.lastBackup"] : null,
780
+ lastRestore: fm["backup.lastRestore"] && fm["backup.lastRestore"] !== "null" ? fm["backup.lastRestore"] : null
781
+ } : null,
782
+ statuses: parseStatusConfig(content),
783
+ types: null
699
784
  };
700
785
  }
701
786
  var DEFAULT_CONFIG;
@@ -706,8 +791,8 @@ var init_config2 = __esm({
706
791
  init_fs();
707
792
  init_config();
708
793
  DEFAULT_CONFIG = {
709
- version: "1.0",
710
- defaultMissionDir: defaultMissionDir(),
794
+ version: "2.0",
795
+ defaultProjectDir: defaultProjectDir(),
711
796
  onboarding: {
712
797
  completed: false
713
798
  },
@@ -720,7 +805,9 @@ var init_config2 = __esm({
720
805
  codexPluginDir: null,
721
806
  codexMarketplacePath: null
722
807
  },
723
- statuses: null
808
+ backup: null,
809
+ statuses: null,
810
+ types: null
724
811
  };
725
812
  }
726
813
  });
@@ -773,7 +860,7 @@ function parseListField(frontmatter, fieldName) {
773
860
  }
774
861
  return results;
775
862
  }
776
- function parseMission(fileContent) {
863
+ function parseProject(fileContent) {
777
864
  const [fm, body] = extractFrontmatter2(fileContent);
778
865
  return {
779
866
  id: getField(fm, "id") ?? "",
@@ -804,13 +891,13 @@ function parseStatus(fileContent) {
804
891
  }
805
892
  }
806
893
  return {
807
- mission: getField(fm, "mission") ?? "",
894
+ project: getField(fm, "project") ?? "",
808
895
  status: getField(fm, "status") ?? "pending",
809
896
  progress,
810
897
  needsAttention: {
811
898
  blockedCount: parseInt(getNestedField(fm, "needsAttention", "blockedCount") ?? "0", 10),
812
899
  failedCount: parseInt(getNestedField(fm, "needsAttention", "failedCount") ?? "0", 10),
813
- unansweredQuestions: parseInt(getNestedField(fm, "needsAttention", "unansweredQuestions") ?? "0", 10)
900
+ openQuestions: parseInt(getNestedField(fm, "needsAttention", "openQuestions") ?? "0", 10)
814
901
  },
815
902
  body
816
903
  };
@@ -852,6 +939,8 @@ function parseAssignmentFull(fileContent) {
852
939
  id: getField(fm, "id") ?? "",
853
940
  slug: getField(fm, "slug") ?? "",
854
941
  title: getField(fm, "title") ?? "",
942
+ project: getField(fm, "project"),
943
+ type: getField(fm, "type"),
855
944
  status: getField(fm, "status") ?? "pending",
856
945
  priority: getField(fm, "priority") ?? "medium",
857
946
  assignee: getField(fm, "assignee"),
@@ -971,17 +1060,17 @@ async function getDashboardHelp() {
971
1060
  return {
972
1061
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
973
1062
  whatIsSyntaur: {
974
- summary: "Syntaur is a local-first, markdown-backed agent work system. The dashboard is a live view over mission folders and files on disk.",
1063
+ summary: "Syntaur is a local-first, markdown-backed agent work system. The dashboard is a live view over project folders and files on disk.",
975
1064
  bullets: [
976
1065
  "Markdown files are the source of truth.",
977
- "The UI reads mission folders, assignment files, and derived indexes from the local filesystem.",
1066
+ "The UI reads project folders, assignment files, and derived indexes from the local filesystem.",
978
1067
  "Derived underscore-prefixed files are projections, not the canonical edit target."
979
1068
  ]
980
1069
  },
981
1070
  coreConcepts: [
982
1071
  {
983
- term: "Mission",
984
- description: "A mission is the higher-level objective. It owns assignments, shared resources, and mission memories."
1072
+ term: "Project",
1073
+ description: "A project is the higher-level objective. It owns assignments, shared resources, and project memories."
985
1074
  },
986
1075
  {
987
1076
  term: "Assignment",
@@ -989,15 +1078,15 @@ async function getDashboardHelp() {
989
1078
  },
990
1079
  {
991
1080
  term: "Resource",
992
- description: "A mission-level shared reference file that provides source material or constraints for the work."
1081
+ description: "A project-level shared reference file that provides source material or constraints for the work."
993
1082
  },
994
1083
  {
995
1084
  term: "Memory",
996
- description: "A mission-level learning or pattern captured during execution so future assignments can reuse it."
1085
+ description: "A project-level learning or pattern captured during execution so future assignments can reuse it."
997
1086
  },
998
1087
  {
999
1088
  term: "Manifest",
1000
- description: "A derived navigation file that points agents at the mission overview, indexes, and agent instructions."
1089
+ description: "A derived navigation file that points agents at the project overview, indexes, and agent instructions."
1001
1090
  },
1002
1091
  {
1003
1092
  term: "Derived file",
@@ -1033,12 +1122,12 @@ async function getDashboardHelp() {
1033
1122
  ownershipRules: [
1034
1123
  {
1035
1124
  label: "Human-authored files",
1036
- files: ["mission.md", "agent.md", "claude.md"],
1037
- description: "These files define mission intent and instructions. The dashboard treats mission status as derived except for the archive fields."
1125
+ files: ["project.md", "agent.md", "claude.md"],
1126
+ description: "These files define project intent and instructions. The dashboard treats project status as derived except for the archive fields."
1038
1127
  },
1039
1128
  {
1040
1129
  label: "Assignment working files",
1041
- files: ["assignment.md", "plan.md", "scratchpad.md"],
1130
+ files: ["assignment.md", "plan*.md (optional, versioned)", "scratchpad.md"],
1042
1131
  description: "These are agent-writable files. The dashboard lets you edit the source markdown while preserving unsupported frontmatter keys."
1043
1132
  },
1044
1133
  {
@@ -1060,13 +1149,13 @@ async function getDashboardHelp() {
1060
1149
  href: "/"
1061
1150
  },
1062
1151
  {
1063
- label: "Missions",
1064
- description: "Browse, search, filter, and sort the mission directory. Create new missions and drill into mission workspaces.",
1065
- href: "/missions"
1152
+ label: "Projects",
1153
+ description: "Browse, search, filter, and sort the project directory. Create new projects and drill into project workspaces.",
1154
+ href: "/projects"
1066
1155
  },
1067
1156
  {
1068
1157
  label: "Assignments",
1069
- description: "Cross-mission kanban board of all assignments. Drag cards between columns to change status, or filter by mission, assignee, or status.",
1158
+ description: "Cross-project kanban board of all assignments. Drag cards between columns to change status, or filter by project, assignee, or status.",
1070
1159
  href: "/assignments"
1071
1160
  },
1072
1161
  {
@@ -1100,14 +1189,14 @@ async function getDashboardHelp() {
1100
1189
  href: "/settings"
1101
1190
  },
1102
1191
  {
1103
- label: "Mission page",
1104
- description: "The mission workspace shows health stats, assignment list, dependency graph, shared resources, and memories.",
1105
- href: "/missions"
1192
+ label: "Project page",
1193
+ description: "The project workspace shows health stats, assignment list, dependency graph, shared resources, and memories.",
1194
+ href: "/projects"
1106
1195
  },
1107
1196
  {
1108
1197
  label: "Assignment page",
1109
1198
  description: "The assignment workspace shows lifecycle actions, plan editor, scratchpad, handoff log, decision records, and agent sessions.",
1110
- href: "/missions"
1199
+ href: "/projects"
1111
1200
  }
1112
1201
  ],
1113
1202
  faq: [
@@ -1144,16 +1233,16 @@ async function getDashboardHelp() {
1144
1233
  answer: "Syntaur tracks tmux sessions to discover running dev servers, their ports, git branches, and linked assignments. Register sessions on the Servers page or let autodiscovery find them. Pane info refreshes automatically."
1145
1234
  }
1146
1235
  ],
1147
- firstMissionChecklist: [
1236
+ firstProjectChecklist: [
1148
1237
  {
1149
- title: "Create the mission",
1150
- detail: "Describe the overall objective in mission.md, then add tags and archive metadata only when needed.",
1238
+ title: "Create the project",
1239
+ detail: "Describe the overall objective in project.md, then add tags and archive metadata only when needed.",
1151
1240
  command: CLI_COMMANDS[1],
1152
- href: "/create/mission"
1241
+ href: "/create/project"
1153
1242
  },
1154
1243
  {
1155
1244
  title: "Create at least one assignment",
1156
- detail: "Break the mission into executable work units with explicit priority and dependencies.",
1245
+ detail: "Break the project into executable work units with explicit priority and dependencies.",
1157
1246
  command: CLI_COMMANDS[2]
1158
1247
  },
1159
1248
  {
@@ -1163,8 +1252,8 @@ async function getDashboardHelp() {
1163
1252
  },
1164
1253
  {
1165
1254
  title: "Use the assignment workspace for execution",
1166
- detail: "Keep the objective in assignment.md, the implementation plan in plan.md, and transient notes in scratchpad.md.",
1167
- href: "/missions"
1255
+ detail: "Keep the objective and todos in assignment.md, implementation plans in optional versioned plan files (plan.md, plan-v2.md, ...), and transient notes in scratchpad.md.",
1256
+ href: "/projects"
1168
1257
  },
1169
1258
  {
1170
1259
  title: "Record handoffs and decisions without rewriting history",
@@ -1178,14 +1267,14 @@ async function getDashboardHelp() {
1178
1267
  ],
1179
1268
  links: [
1180
1269
  { label: "Overview", href: "/" },
1181
- { label: "Mission Directory", href: "/missions" },
1270
+ { label: "Project Directory", href: "/projects" },
1182
1271
  { label: "Assignments Board", href: "/assignments" },
1183
1272
  { label: "Attention Queue", href: "/attention" },
1184
1273
  { label: "Servers", href: "/servers" },
1185
1274
  { label: "Agent Sessions", href: "/agent-sessions" },
1186
1275
  { label: "Playbooks", href: "/playbooks" },
1187
1276
  { label: "Settings", href: "/settings" },
1188
- { label: "Create Mission", href: "/create/mission" }
1277
+ { label: "Create Project", href: "/create/project" }
1189
1278
  ]
1190
1279
  };
1191
1280
  }
@@ -1207,60 +1296,60 @@ var init_help = __esm({
1207
1296
  example: "syntaur init"
1208
1297
  },
1209
1298
  {
1210
- command: "syntaur create-mission",
1211
- description: "Create a new mission folder with the required source and derived files.",
1212
- example: 'syntaur create-mission "Ship dashboard overhaul"'
1299
+ command: "syntaur create-project",
1300
+ description: "Create a new project folder with the required source and derived files.",
1301
+ example: 'syntaur create-project "Ship dashboard overhaul"'
1213
1302
  },
1214
1303
  {
1215
1304
  command: "syntaur create-assignment",
1216
- description: "Create a new assignment inside a mission.",
1217
- example: 'syntaur create-assignment "Implement overview API" --mission ui-overhaul'
1305
+ description: "Create a new assignment inside a project.",
1306
+ example: 'syntaur create-assignment "Implement overview API" --project ui-overhaul'
1218
1307
  },
1219
1308
  {
1220
1309
  command: "syntaur assign",
1221
1310
  description: "Set the assignee for an assignment before work begins.",
1222
- example: "syntaur assign implement-overview --mission ui-overhaul --agent codex-1"
1311
+ example: "syntaur assign implement-overview --project ui-overhaul --agent codex-1"
1223
1312
  },
1224
1313
  // --- Lifecycle transitions (indices 5-11) ---
1225
1314
  {
1226
1315
  command: "syntaur start",
1227
1316
  description: "Transition an assignment to in_progress.",
1228
- example: "syntaur start implement-overview --mission ui-overhaul"
1317
+ example: "syntaur start implement-overview --project ui-overhaul"
1229
1318
  },
1230
1319
  {
1231
1320
  command: "syntaur review",
1232
1321
  description: "Move active work into review once implementation is ready for inspection.",
1233
- example: "syntaur review implement-overview --mission ui-overhaul"
1322
+ example: "syntaur review implement-overview --project ui-overhaul"
1234
1323
  },
1235
1324
  {
1236
1325
  command: "syntaur complete",
1237
1326
  description: "Mark an assignment completed after review or direct completion.",
1238
- example: "syntaur complete implement-overview --mission ui-overhaul"
1327
+ example: "syntaur complete implement-overview --project ui-overhaul"
1239
1328
  },
1240
1329
  {
1241
1330
  command: "syntaur block",
1242
1331
  description: "Mark an assignment blocked and record the explicit reason.",
1243
- example: 'syntaur block implement-overview --mission ui-overhaul --reason "Waiting on API spec"'
1332
+ example: 'syntaur block implement-overview --project ui-overhaul --reason "Waiting on API spec"'
1244
1333
  },
1245
1334
  {
1246
1335
  command: "syntaur unblock",
1247
1336
  description: "Move a blocked assignment back to in_progress after the blocker is cleared.",
1248
- example: "syntaur unblock implement-overview --mission ui-overhaul"
1337
+ example: "syntaur unblock implement-overview --project ui-overhaul"
1249
1338
  },
1250
1339
  {
1251
1340
  command: "syntaur fail",
1252
1341
  description: "Mark an assignment failed when it cannot be completed as planned.",
1253
- example: "syntaur fail implement-overview --mission ui-overhaul"
1342
+ example: "syntaur fail implement-overview --project ui-overhaul"
1254
1343
  },
1255
1344
  {
1256
1345
  command: "syntaur reopen",
1257
1346
  description: "Reopen a completed or failed assignment back to in_progress.",
1258
- example: "syntaur reopen implement-overview --mission ui-overhaul"
1347
+ example: "syntaur reopen implement-overview --project ui-overhaul"
1259
1348
  },
1260
1349
  // --- Dashboard (index 12) ---
1261
1350
  {
1262
1351
  command: "syntaur dashboard",
1263
- description: "Start the local dashboard UI over the mission files on disk.",
1352
+ description: "Start the local dashboard UI over the project files on disk.",
1264
1353
  example: "syntaur dashboard --port 4800"
1265
1354
  },
1266
1355
  // --- Plugin & adapter setup (indices 13-16) ---
@@ -1282,18 +1371,18 @@ var init_help = __esm({
1282
1371
  {
1283
1372
  command: "syntaur setup-adapter",
1284
1373
  description: "Generate adapter instruction files for cursor, codex, or opencode in the current directory.",
1285
- example: "syntaur setup-adapter cursor --mission ui-overhaul --assignment implement-overview"
1374
+ example: "syntaur setup-adapter cursor --project ui-overhaul --assignment implement-overview"
1286
1375
  },
1287
1376
  // --- Session & server tracking (index 17) ---
1288
1377
  {
1289
1378
  command: "syntaur track-session",
1290
- description: "Register an agent session, optionally linked to a mission and assignment.",
1291
- example: "syntaur track-session --agent claude --mission ui-overhaul --assignment implement-overview"
1379
+ description: "Register an agent session, optionally linked to a project and assignment.",
1380
+ example: "syntaur track-session --agent claude --project ui-overhaul --assignment implement-overview"
1292
1381
  },
1293
1382
  // --- Browsing & playbooks (indices 18-20) ---
1294
1383
  {
1295
1384
  command: "syntaur browse",
1296
- description: "Interactive TUI browser for missions and assignments.",
1385
+ description: "Interactive TUI browser for projects and assignments.",
1297
1386
  example: "syntaur browse"
1298
1387
  },
1299
1388
  {
@@ -1314,14 +1403,14 @@ var init_help = __esm({
1314
1403
  command: CLI_COMMANDS[0]
1315
1404
  },
1316
1405
  {
1317
- title: "Create a mission",
1318
- detail: "Use a mission for a higher-level objective. Missions group assignments, shared resources, and memories.",
1406
+ title: "Create a project",
1407
+ detail: "Use a project for a higher-level objective. Projects group assignments, shared resources, and memories.",
1319
1408
  command: CLI_COMMANDS[2],
1320
- href: "/create/mission"
1409
+ href: "/create/project"
1321
1410
  },
1322
1411
  {
1323
1412
  title: "Create the first assignment",
1324
- detail: "Assignments are the execution unit. Create one for each concrete chunk of work inside the mission.",
1413
+ detail: "Assignments are the execution unit. Create one for each concrete chunk of work inside the project.",
1325
1414
  command: CLI_COMMANDS[3]
1326
1415
  },
1327
1416
  {
@@ -1336,7 +1425,7 @@ var init_help = __esm({
1336
1425
  },
1337
1426
  {
1338
1427
  title: "Use the dashboard for triage and context",
1339
- detail: "Overview shows the current queue, mission pages show health, assignment pages show the execution surface.",
1428
+ detail: "Overview shows the current queue, project pages show health, assignment pages show the execution surface.",
1340
1429
  command: CLI_COMMANDS[12],
1341
1430
  href: "/"
1342
1431
  }
@@ -1404,7 +1493,7 @@ function buildSessionContent(opts) {
1404
1493
  if (Object.keys(opts.overrides).length > 0) {
1405
1494
  lines.push("overrides:");
1406
1495
  for (const [key, val] of Object.entries(opts.overrides)) {
1407
- lines.push(` "${key}": { mission: "${val.mission}", assignment: "${val.assignment}" }`);
1496
+ lines.push(` "${key}": { project: "${val.project}", assignment: "${val.assignment}" }`);
1408
1497
  }
1409
1498
  }
1410
1499
  lines.push("---", "");
@@ -1441,10 +1530,10 @@ async function readSessionFile(dir, name) {
1441
1530
  const overridesMatch = frontmatter.match(/^overrides:\n((?:\s+".+\n?)*)/m);
1442
1531
  if (overridesMatch) {
1443
1532
  const overrideLines = overridesMatch[1].matchAll(
1444
- /^\s+"([^"]+)":\s*\{\s*mission:\s*"([^"]+)",\s*assignment:\s*"([^"]+)"\s*\}/gm
1533
+ /^\s+"([^"]+)":\s*\{\s*project:\s*"([^"]+)",\s*assignment:\s*"([^"]+)"\s*\}/gm
1445
1534
  );
1446
1535
  for (const m of overrideLines) {
1447
- overrides[m[1]] = { mission: m[2], assignment: m[3] };
1536
+ overrides[m[1]] = { project: m[2], assignment: m[3] };
1448
1537
  }
1449
1538
  }
1450
1539
  const autoField = getField(frontmatter, "auto");
@@ -1646,12 +1735,12 @@ async function getGitInfo(cwd) {
1646
1735
  }
1647
1736
  return { branch: branch || null, worktree: isWorktree };
1648
1737
  }
1649
- async function loadWorkspaceRecords(missionsDir) {
1738
+ async function loadWorkspaceRecords(projectsDir) {
1650
1739
  const records = [];
1651
1740
  try {
1652
- const missions = await listMissions(missionsDir);
1653
- for (const mission of missions) {
1654
- const assignmentsDir = resolve5(missionsDir, mission.slug, "assignments");
1741
+ const projects = await listProjects(projectsDir);
1742
+ for (const project of projects) {
1743
+ const assignmentsDir = resolve5(projectsDir, project.slug, "assignments");
1655
1744
  let slugs;
1656
1745
  try {
1657
1746
  slugs = await readdir2(assignmentsDir);
@@ -1665,7 +1754,7 @@ async function loadWorkspaceRecords(missionsDir) {
1665
1754
  const [fm] = extractFrontmatter2(raw);
1666
1755
  if (!fm) continue;
1667
1756
  records.push({
1668
- missionSlug: mission.slug,
1757
+ projectSlug: project.slug,
1669
1758
  assignmentSlug: aslug,
1670
1759
  assignmentTitle: getField(fm, "title") ?? aslug,
1671
1760
  worktreePath: getNestedField(fm, "workspace", "worktreePath") ?? null,
@@ -1694,14 +1783,14 @@ async function autoLinkPane(cwd, branch, records) {
1694
1783
  if (rec.worktreePath) {
1695
1784
  const normalizedWt = await resolveAndNormalize(rec.worktreePath);
1696
1785
  if (normalizedCwd === normalizedWt) {
1697
- return { mission: rec.missionSlug, slug: rec.assignmentSlug, title: rec.assignmentTitle };
1786
+ return { project: rec.projectSlug, slug: rec.assignmentSlug, title: rec.assignmentTitle };
1698
1787
  }
1699
1788
  }
1700
1789
  }
1701
1790
  if (branch) {
1702
1791
  for (const rec of records) {
1703
1792
  if (rec.branch && rec.branch === branch) {
1704
- return { mission: rec.missionSlug, slug: rec.assignmentSlug, title: rec.assignmentTitle };
1793
+ return { project: rec.projectSlug, slug: rec.assignmentSlug, title: rec.assignmentTitle };
1705
1794
  }
1706
1795
  }
1707
1796
  }
@@ -1771,10 +1860,10 @@ async function scanSession(sessionData, lsofOutput, workspaceRecords) {
1771
1860
  let assignment = null;
1772
1861
  if (override) {
1773
1862
  const rec = workspaceRecords.find(
1774
- (r) => r.missionSlug === override.mission && r.assignmentSlug === override.assignment
1863
+ (r) => r.projectSlug === override.project && r.assignmentSlug === override.assignment
1775
1864
  );
1776
1865
  assignment = {
1777
- mission: override.mission,
1866
+ project: override.project,
1778
1867
  slug: override.assignment,
1779
1868
  title: rec?.assignmentTitle ?? override.assignment
1780
1869
  };
@@ -1833,10 +1922,10 @@ async function scanProcessSession(sessionData, lsofOutput, workspaceRecords) {
1833
1922
  let assignment = null;
1834
1923
  if (override) {
1835
1924
  const rec = workspaceRecords.find(
1836
- (r) => r.missionSlug === override.mission && r.assignmentSlug === override.assignment
1925
+ (r) => r.projectSlug === override.project && r.assignmentSlug === override.assignment
1837
1926
  );
1838
1927
  assignment = {
1839
- mission: override.mission,
1928
+ project: override.project,
1840
1929
  slug: override.assignment,
1841
1930
  title: rec?.assignmentTitle ?? override.assignment
1842
1931
  };
@@ -1863,17 +1952,17 @@ async function scanProcessSession(sessionData, lsofOutput, workspaceRecords) {
1863
1952
  windows: [{ index: 0, name: "process", panes: [pane] }]
1864
1953
  };
1865
1954
  }
1866
- async function scanAllSessions(serversDir, missionsDir, options) {
1955
+ async function scanAllSessions(serversDir2, projectsDir, options) {
1867
1956
  if (!options?.bypassCache && cache && Date.now() < cache.expiry) {
1868
1957
  return cache.data;
1869
1958
  }
1870
1959
  const tmuxAvailable = await checkTmuxAvailable();
1871
- const names = await listSessionFiles(serversDir);
1960
+ const names = await listSessionFiles(serversDir2);
1872
1961
  const lsofOutput = await getLsofOutput();
1873
- const workspaceRecords = await loadWorkspaceRecords(missionsDir);
1962
+ const workspaceRecords = await loadWorkspaceRecords(projectsDir);
1874
1963
  const sessions = [];
1875
1964
  for (const name of names) {
1876
- const data = await readSessionFile(serversDir, name);
1965
+ const data = await readSessionFile(serversDir2, name);
1877
1966
  if (!data) continue;
1878
1967
  if (data.kind === "process") {
1879
1968
  sessions.push(await scanProcessSession(data, lsofOutput, workspaceRecords));
@@ -1885,11 +1974,11 @@ async function scanAllSessions(serversDir, missionsDir, options) {
1885
1974
  cache = { data: result, expiry: Date.now() + CACHE_TTL_MS };
1886
1975
  return result;
1887
1976
  }
1888
- async function scanSingleSession(serversDir, missionsDir, name) {
1889
- const data = await readSessionFile(serversDir, name);
1977
+ async function scanSingleSession(serversDir2, projectsDir, name) {
1978
+ const data = await readSessionFile(serversDir2, name);
1890
1979
  if (!data) return null;
1891
1980
  const lsofOutput = await getLsofOutput();
1892
- const workspaceRecords = await loadWorkspaceRecords(missionsDir);
1981
+ const workspaceRecords = await loadWorkspaceRecords(projectsDir);
1893
1982
  if (data.kind === "process") {
1894
1983
  return scanProcessSession(data, lsofOutput, workspaceRecords);
1895
1984
  }
@@ -1966,12 +2055,12 @@ async function getStatusConfig() {
1966
2055
  function clearStatusConfigCache() {
1967
2056
  _cachedConfig = null;
1968
2057
  }
1969
- async function listMissions(missionsDir) {
1970
- const missionRecords = await listMissionRecords(missionsDir);
1971
- return missionRecords.map((record) => record.summary);
2058
+ async function listProjects(projectsDir) {
2059
+ const projectRecords = await listProjectRecords(projectsDir);
2060
+ return projectRecords.map((record) => record.summary);
1972
2061
  }
1973
- async function readWorkspaceRegistry(missionsDir) {
1974
- const registryPath = resolve6(dirname2(missionsDir), "workspaces.json");
2062
+ async function readWorkspaceRegistry(projectsDir) {
2063
+ const registryPath = resolve6(dirname2(projectsDir), "workspaces.json");
1975
2064
  try {
1976
2065
  const raw = await readFile5(registryPath, "utf-8");
1977
2066
  const parsed = JSON.parse(raw);
@@ -1980,20 +2069,20 @@ async function readWorkspaceRegistry(missionsDir) {
1980
2069
  return [];
1981
2070
  }
1982
2071
  }
1983
- async function writeWorkspaceRegistry(missionsDir, workspaces) {
1984
- const registryPath = resolve6(dirname2(missionsDir), "workspaces.json");
2072
+ async function writeWorkspaceRegistry(projectsDir, workspaces) {
2073
+ const registryPath = resolve6(dirname2(projectsDir), "workspaces.json");
1985
2074
  await writeFile2(registryPath, JSON.stringify(workspaces, null, 2) + "\n", "utf-8");
1986
2075
  }
1987
- async function listWorkspaces(missionsDir) {
1988
- const [missionRecords, registered] = await Promise.all([
1989
- listMissionRecords(missionsDir),
1990
- readWorkspaceRegistry(missionsDir)
2076
+ async function listWorkspaces(projectsDir) {
2077
+ const [projectRecords, registered] = await Promise.all([
2078
+ listProjectRecords(projectsDir),
2079
+ readWorkspaceRegistry(projectsDir)
1991
2080
  ]);
1992
2081
  const workspaceSet = new Set(registered);
1993
2082
  let hasUngrouped = false;
1994
- for (const record of missionRecords) {
1995
- if (record.mission.workspace) {
1996
- workspaceSet.add(record.mission.workspace);
2083
+ for (const record of projectRecords) {
2084
+ if (record.project.workspace) {
2085
+ workspaceSet.add(record.project.workspace);
1997
2086
  } else {
1998
2087
  hasUngrouped = true;
1999
2088
  }
@@ -2001,28 +2090,28 @@ async function listWorkspaces(missionsDir) {
2001
2090
  const workspaces = Array.from(workspaceSet).sort();
2002
2091
  return { workspaces, hasUngrouped };
2003
2092
  }
2004
- async function createWorkspace(missionsDir, name) {
2005
- const registered = await readWorkspaceRegistry(missionsDir);
2093
+ async function createWorkspace(projectsDir, name) {
2094
+ const registered = await readWorkspaceRegistry(projectsDir);
2006
2095
  if (!registered.includes(name)) {
2007
2096
  registered.push(name);
2008
2097
  registered.sort();
2009
- await writeWorkspaceRegistry(missionsDir, registered);
2098
+ await writeWorkspaceRegistry(projectsDir, registered);
2010
2099
  }
2011
2100
  }
2012
- async function deleteWorkspace(missionsDir, name) {
2013
- const registered = await readWorkspaceRegistry(missionsDir);
2101
+ async function deleteWorkspace(projectsDir, name) {
2102
+ const registered = await readWorkspaceRegistry(projectsDir);
2014
2103
  const filtered = registered.filter((w) => w !== name);
2015
- await writeWorkspaceRegistry(missionsDir, filtered);
2104
+ await writeWorkspaceRegistry(projectsDir, filtered);
2016
2105
  }
2017
- async function getOverview(missionsDir, serversDir) {
2018
- const missionRecords = await listMissionRecords(missionsDir);
2019
- const attention = buildAttentionItems(missionRecords);
2020
- const recentActivity = buildRecentActivity(missionRecords);
2106
+ async function getOverview(projectsDir, serversDir2) {
2107
+ const projectRecords = await listProjectRecords(projectsDir);
2108
+ const attention = buildAttentionItems(projectRecords);
2109
+ const recentActivity = buildRecentActivity(projectRecords);
2021
2110
  let serverStats;
2022
- if (serversDir) {
2111
+ if (serversDir2) {
2023
2112
  try {
2024
2113
  const { scanAllSessions: scanAllSessions2 } = await Promise.resolve().then(() => (init_scanner(), scanner_exports));
2025
- const servers = await scanAllSessions2(serversDir, missionsDir);
2114
+ const servers = await scanAllSessions2(serversDir2, projectsDir);
2026
2115
  if (servers.tmuxAvailable) {
2027
2116
  const alive = servers.sessions.filter((s) => s.alive).length;
2028
2117
  const totalPorts = servers.sessions.reduce((sum, s) => sum + s.windows.reduce((ws, w) => ws + w.panes.reduce((ps, p) => ps + p.ports.length, 0), 0), 0);
@@ -2038,50 +2127,50 @@ async function getOverview(missionsDir, serversDir) {
2038
2127
  }
2039
2128
  return {
2040
2129
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
2041
- firstRun: missionRecords.length === 0,
2130
+ firstRun: projectRecords.length === 0,
2042
2131
  stats: {
2043
- activeMissions: missionRecords.filter((record) => record.summary.status === "active").length,
2044
- inProgressAssignments: missionRecords.reduce(
2132
+ activeProjects: projectRecords.filter((record) => record.summary.status === "active").length,
2133
+ inProgressAssignments: projectRecords.reduce(
2045
2134
  (total, record) => total + (record.summary.progress["in_progress"] ?? 0),
2046
2135
  0
2047
2136
  ),
2048
- blockedAssignments: missionRecords.reduce(
2137
+ blockedAssignments: projectRecords.reduce(
2049
2138
  (total, record) => total + (record.summary.progress["blocked"] ?? 0),
2050
2139
  0
2051
2140
  ),
2052
- reviewAssignments: missionRecords.reduce(
2141
+ reviewAssignments: projectRecords.reduce(
2053
2142
  (total, record) => total + (record.summary.progress["review"] ?? 0),
2054
2143
  0
2055
2144
  ),
2056
- failedAssignments: missionRecords.reduce(
2145
+ failedAssignments: projectRecords.reduce(
2057
2146
  (total, record) => total + (record.summary.progress["failed"] ?? 0),
2058
2147
  0
2059
2148
  ),
2060
- staleAssignments: missionRecords.reduce(
2149
+ staleAssignments: projectRecords.reduce(
2061
2150
  (total, record) => total + record.assignments.filter((assignment) => isStale(assignment.updated)).length,
2062
2151
  0
2063
2152
  )
2064
2153
  },
2065
2154
  attention: attention.slice(0, OVERVIEW_ATTENTION_LIMIT),
2066
- recentMissions: missionRecords.map((record) => record.summary).sort((left, right) => compareTimestamps(right.updated, left.updated)).slice(0, RECENT_MISSIONS_LIMIT),
2155
+ recentProjects: projectRecords.map((record) => record.summary).sort((left, right) => compareTimestamps(right.updated, left.updated)).slice(0, RECENT_PROJECTS_LIMIT),
2067
2156
  recentActivity: recentActivity.slice(0, RECENT_ACTIVITY_LIMIT),
2068
2157
  serverStats
2069
2158
  };
2070
2159
  }
2071
- async function getAttention(missionsDir, serversDir) {
2072
- const missionRecords = await listMissionRecords(missionsDir);
2073
- const items = buildAttentionItems(missionRecords);
2074
- if (serversDir) {
2160
+ async function getAttention(projectsDir, serversDir2) {
2161
+ const projectRecords = await listProjectRecords(projectsDir);
2162
+ const items = buildAttentionItems(projectRecords);
2163
+ if (serversDir2) {
2075
2164
  try {
2076
2165
  const { scanAllSessions: scanAllSessions2 } = await Promise.resolve().then(() => (init_scanner(), scanner_exports));
2077
- const servers = await scanAllSessions2(serversDir, missionsDir);
2166
+ const servers = await scanAllSessions2(serversDir2, projectsDir);
2078
2167
  for (const session of servers.sessions) {
2079
2168
  if (!session.alive) {
2080
2169
  items.push({
2081
2170
  id: `server-dead-${session.name}`,
2082
2171
  severity: "low",
2083
- missionSlug: "",
2084
- missionTitle: "",
2172
+ projectSlug: "",
2173
+ projectTitle: "",
2085
2174
  assignmentSlug: "",
2086
2175
  assignmentTitle: `tmux: ${session.name}`,
2087
2176
  status: "failed",
@@ -2118,13 +2207,13 @@ async function getAttention(missionsDir, serversDir) {
2118
2207
  items: pagedItems
2119
2208
  };
2120
2209
  }
2121
- async function listAssignmentsBoard(missionsDir) {
2122
- const missionRecords = await listMissionRecords(missionsDir);
2210
+ async function listAssignmentsBoard(projectsDir) {
2211
+ const projectRecords = await listProjectRecords(projectsDir);
2123
2212
  const assignments = await Promise.all(
2124
- missionRecords.flatMap(
2213
+ projectRecords.flatMap(
2125
2214
  async (record) => Promise.all(
2126
2215
  record.assignments.map(
2127
- async (assignment) => toAssignmentBoardItem(missionsDir, record, assignment)
2216
+ async (assignment) => toAssignmentBoardItem(projectsDir, record, assignment)
2128
2217
  )
2129
2218
  )
2130
2219
  )
@@ -2137,59 +2226,59 @@ async function listAssignmentsBoard(missionsDir) {
2137
2226
  async function getHelp() {
2138
2227
  return getDashboardHelp();
2139
2228
  }
2140
- async function getEditableDocument(missionsDir, documentType, missionSlug, assignmentSlug) {
2141
- const filePath = getDocumentPath(missionsDir, documentType, missionSlug, assignmentSlug);
2229
+ async function getEditableDocument(projectsDir, documentType, projectSlug, assignmentSlug) {
2230
+ const filePath = getDocumentPath(projectsDir, documentType, projectSlug, assignmentSlug);
2142
2231
  if (!filePath || !await fileExists(filePath)) {
2143
2232
  return null;
2144
2233
  }
2145
2234
  const content = await readFile5(filePath, "utf-8");
2146
- const title = getEditableDocumentTitle(documentType, missionSlug, assignmentSlug);
2235
+ const title = getEditableDocumentTitle(documentType, projectSlug, assignmentSlug);
2147
2236
  return {
2148
2237
  documentType,
2149
2238
  title,
2150
2239
  content,
2151
- missionSlug,
2240
+ projectSlug,
2152
2241
  assignmentSlug,
2153
2242
  appendOnly: documentType === "handoff" || documentType === "decision-record"
2154
2243
  };
2155
2244
  }
2156
- async function getMissionDetail(missionsDir, slug) {
2157
- const missionPath = resolve6(missionsDir, slug);
2158
- const missionMdPath = resolve6(missionPath, "mission.md");
2159
- if (!await fileExists(missionMdPath)) {
2245
+ async function getProjectDetail(projectsDir, slug) {
2246
+ const projectPath = resolve6(projectsDir, slug);
2247
+ const projectMdPath = resolve6(projectPath, "project.md");
2248
+ if (!await fileExists(projectMdPath)) {
2160
2249
  return null;
2161
2250
  }
2162
- const missionContent = await readFile5(missionMdPath, "utf-8");
2163
- const mission = parseMission(missionContent);
2164
- const assignments = await listAssignmentRecords(missionPath);
2165
- const rollup = buildMissionRollup(mission, assignments);
2166
- const dependencyGraph = await loadDependencyGraph(missionPath, assignments);
2167
- const resources = await listResources(missionPath);
2168
- const memories = await listMemories(missionPath);
2169
- const updated = getMissionActivityTimestamp(mission.updated, assignments);
2251
+ const projectContent = await readFile5(projectMdPath, "utf-8");
2252
+ const project = parseProject(projectContent);
2253
+ const assignments = await listAssignmentRecords(projectPath);
2254
+ const rollup = buildProjectRollup(project, assignments);
2255
+ const dependencyGraph = await loadDependencyGraph(projectPath, assignments);
2256
+ const resources = await listResources(projectPath);
2257
+ const memories = await listMemories(projectPath);
2258
+ const updated = getProjectActivityTimestamp(project.updated, assignments);
2170
2259
  return {
2171
- slug: mission.slug || slug,
2172
- title: mission.title,
2260
+ slug: project.slug || slug,
2261
+ title: project.title,
2173
2262
  status: rollup.status,
2174
- statusOverride: mission.statusOverride,
2175
- archived: mission.archived,
2176
- archivedAt: mission.archivedAt,
2177
- archivedReason: mission.archivedReason,
2178
- created: mission.created,
2263
+ statusOverride: project.statusOverride,
2264
+ archived: project.archived,
2265
+ archivedAt: project.archivedAt,
2266
+ archivedReason: project.archivedReason,
2267
+ created: project.created,
2179
2268
  updated,
2180
- tags: mission.tags,
2181
- body: mission.body,
2269
+ tags: project.tags,
2270
+ body: project.body,
2182
2271
  progress: rollup.progress,
2183
2272
  needsAttention: rollup.needsAttention,
2184
2273
  assignments: assignments.map(toAssignmentSummary).sort((left, right) => compareTimestamps(right.updated, left.updated)),
2185
2274
  resources,
2186
2275
  memories,
2187
2276
  dependencyGraph,
2188
- workspace: mission.workspace
2277
+ workspace: project.workspace
2189
2278
  };
2190
2279
  }
2191
- async function getAssignmentDetail(missionsDir, missionSlug, assignmentSlug) {
2192
- const assignmentDir = resolve6(missionsDir, missionSlug, "assignments", assignmentSlug);
2280
+ async function getAssignmentDetail(projectsDir, projectSlug, assignmentSlug) {
2281
+ const assignmentDir = resolve6(projectsDir, projectSlug, "assignments", assignmentSlug);
2193
2282
  const assignmentMdPath = resolve6(assignmentDir, "assignment.md");
2194
2283
  if (!await fileExists(assignmentMdPath)) {
2195
2284
  return null;
@@ -2241,7 +2330,7 @@ async function getAssignmentDetail(missionsDir, missionSlug, assignmentSlug) {
2241
2330
  }
2242
2331
  const detail = {
2243
2332
  id: assignment.id,
2244
- missionSlug,
2333
+ projectSlug,
2245
2334
  slug: assignment.slug || assignmentSlug,
2246
2335
  title: assignment.title,
2247
2336
  status: assignment.status,
@@ -2263,16 +2352,16 @@ async function getAssignmentDetail(missionsDir, missionSlug, assignmentSlug) {
2263
2352
  handoff,
2264
2353
  decisionRecord,
2265
2354
  availableTransitions: await getAvailableTransitions(
2266
- missionsDir,
2267
- missionSlug,
2355
+ projectsDir,
2356
+ projectSlug,
2268
2357
  assignmentSlug,
2269
2358
  assignment
2270
2359
  )
2271
2360
  };
2272
- const selfSlug = `${missionSlug}/${detail.slug}`;
2273
- const missionRecords = await listMissionRecords(missionsDir);
2361
+ const selfSlug = `${projectSlug}/${detail.slug}`;
2362
+ const projectRecords = await listProjectRecords(projectsDir);
2274
2363
  const reverseLinks = [];
2275
- for (const mr of missionRecords) {
2364
+ for (const mr of projectRecords) {
2276
2365
  for (const a of mr.assignments) {
2277
2366
  const qualifiedSlug = `${mr.summary.slug}/${a.slug}`;
2278
2367
  if (qualifiedSlug === selfSlug) continue;
@@ -2290,10 +2379,10 @@ async function getAssignmentDetail(missionsDir, missionSlug, assignmentSlug) {
2290
2379
  const dedupedReverseLinks = reverseLinks.filter((l) => !forwardSet.has(l));
2291
2380
  detail.links = forwardLinks;
2292
2381
  detail.reverseLinks = dedupedReverseLinks;
2293
- const allMissionAssignments = /* @__PURE__ */ new Map();
2294
- for (const mr of missionRecords) {
2382
+ const allProjectAssignments = /* @__PURE__ */ new Map();
2383
+ for (const mr of projectRecords) {
2295
2384
  for (const a of mr.assignments) {
2296
- allMissionAssignments.set(`${mr.summary.slug}/${a.slug}`, {
2385
+ allProjectAssignments.set(`${mr.summary.slug}/${a.slug}`, {
2297
2386
  title: a.title,
2298
2387
  status: a.status
2299
2388
  });
@@ -2302,10 +2391,10 @@ async function getAssignmentDetail(missionsDir, missionSlug, assignmentSlug) {
2302
2391
  const enrichedLinks = [];
2303
2392
  for (const linkSlug of forwardLinks) {
2304
2393
  const [ms, as] = linkSlug.split("/");
2305
- const info = allMissionAssignments.get(linkSlug);
2394
+ const info = allProjectAssignments.get(linkSlug);
2306
2395
  enrichedLinks.push({
2307
2396
  slug: linkSlug,
2308
- missionSlug: ms,
2397
+ projectSlug: ms,
2309
2398
  assignmentSlug: as,
2310
2399
  title: info?.title ?? linkSlug,
2311
2400
  status: info?.status ?? "pending",
@@ -2314,10 +2403,10 @@ async function getAssignmentDetail(missionsDir, missionSlug, assignmentSlug) {
2314
2403
  }
2315
2404
  for (const linkSlug of dedupedReverseLinks) {
2316
2405
  const [ms, as] = linkSlug.split("/");
2317
- const info = allMissionAssignments.get(linkSlug);
2406
+ const info = allProjectAssignments.get(linkSlug);
2318
2407
  enrichedLinks.push({
2319
2408
  slug: linkSlug,
2320
- missionSlug: ms,
2409
+ projectSlug: ms,
2321
2410
  assignmentSlug: as,
2322
2411
  title: info?.title ?? linkSlug,
2323
2412
  status: info?.status ?? "pending",
@@ -2327,51 +2416,51 @@ async function getAssignmentDetail(missionsDir, missionSlug, assignmentSlug) {
2327
2416
  detail.enrichedLinks = enrichedLinks;
2328
2417
  return detail;
2329
2418
  }
2330
- async function listMissionRecords(missionsDir) {
2331
- if (!await fileExists(missionsDir)) {
2419
+ async function listProjectRecords(projectsDir) {
2420
+ if (!await fileExists(projectsDir)) {
2332
2421
  return [];
2333
2422
  }
2334
- const entries = await readdir3(missionsDir, { withFileTypes: true });
2335
- const missionDirs = entries.filter((entry) => entry.isDirectory() && !entry.name.startsWith("."));
2423
+ const entries = await readdir3(projectsDir, { withFileTypes: true });
2424
+ const projectDirs = entries.filter((entry) => entry.isDirectory() && !entry.name.startsWith("."));
2336
2425
  const records = [];
2337
- for (const entry of missionDirs) {
2338
- const missionPath = resolve6(missionsDir, entry.name);
2339
- const missionMdPath = resolve6(missionPath, "mission.md");
2340
- if (!await fileExists(missionMdPath)) {
2426
+ for (const entry of projectDirs) {
2427
+ const projectPath = resolve6(projectsDir, entry.name);
2428
+ const projectMdPath = resolve6(projectPath, "project.md");
2429
+ if (!await fileExists(projectMdPath)) {
2341
2430
  continue;
2342
2431
  }
2343
- const missionContent = await readFile5(missionMdPath, "utf-8");
2344
- const mission = parseMission(missionContent);
2345
- const assignments = await listAssignmentRecords(missionPath);
2346
- const rollup = buildMissionRollup(mission, assignments);
2347
- const updated = getMissionActivityTimestamp(mission.updated, assignments);
2432
+ const projectContent = await readFile5(projectMdPath, "utf-8");
2433
+ const project = parseProject(projectContent);
2434
+ const assignments = await listAssignmentRecords(projectPath);
2435
+ const rollup = buildProjectRollup(project, assignments);
2436
+ const updated = getProjectActivityTimestamp(project.updated, assignments);
2348
2437
  records.push({
2349
- missionPath,
2350
- mission,
2438
+ projectPath,
2439
+ project,
2351
2440
  assignments,
2352
- dependencyGraph: await loadDependencyGraph(missionPath, assignments),
2441
+ dependencyGraph: await loadDependencyGraph(projectPath, assignments),
2353
2442
  summary: {
2354
- slug: mission.slug || entry.name,
2355
- title: mission.title,
2443
+ slug: project.slug || entry.name,
2444
+ title: project.title,
2356
2445
  status: rollup.status,
2357
- statusOverride: mission.statusOverride,
2358
- archived: mission.archived,
2359
- archivedAt: mission.archivedAt,
2360
- archivedReason: mission.archivedReason,
2361
- created: mission.created,
2446
+ statusOverride: project.statusOverride,
2447
+ archived: project.archived,
2448
+ archivedAt: project.archivedAt,
2449
+ archivedReason: project.archivedReason,
2450
+ created: project.created,
2362
2451
  updated,
2363
- tags: mission.tags,
2452
+ tags: project.tags,
2364
2453
  progress: rollup.progress,
2365
2454
  needsAttention: rollup.needsAttention,
2366
- workspace: mission.workspace
2455
+ workspace: project.workspace
2367
2456
  }
2368
2457
  });
2369
2458
  }
2370
2459
  records.sort((left, right) => compareTimestamps(right.summary.updated, left.summary.updated));
2371
2460
  return records;
2372
2461
  }
2373
- async function listAssignmentRecords(missionPath) {
2374
- const assignmentsDir = resolve6(missionPath, "assignments");
2462
+ async function listAssignmentRecords(projectPath) {
2463
+ const assignmentsDir = resolve6(projectPath, "assignments");
2375
2464
  if (!await fileExists(assignmentsDir)) {
2376
2465
  return [];
2377
2466
  }
@@ -2391,8 +2480,8 @@ async function listAssignmentRecords(missionPath) {
2391
2480
  records.sort((left, right) => compareTimestamps(right.updated, left.updated));
2392
2481
  return records;
2393
2482
  }
2394
- async function listResources(missionPath) {
2395
- const resourcesDir = resolve6(missionPath, "resources");
2483
+ async function listResources(projectPath) {
2484
+ const resourcesDir = resolve6(projectPath, "resources");
2396
2485
  if (!await fileExists(resourcesDir)) {
2397
2486
  return [];
2398
2487
  }
@@ -2417,8 +2506,8 @@ async function listResources(missionPath) {
2417
2506
  results.sort((left, right) => compareTimestamps(right.updated, left.updated));
2418
2507
  return results;
2419
2508
  }
2420
- async function listMemories(missionPath) {
2421
- const memoriesDir = resolve6(missionPath, "memories");
2509
+ async function listMemories(projectPath) {
2510
+ const memoriesDir = resolve6(projectPath, "memories");
2422
2511
  if (!await fileExists(memoriesDir)) {
2423
2512
  return [];
2424
2513
  }
@@ -2443,8 +2532,8 @@ async function listMemories(missionPath) {
2443
2532
  results.sort((left, right) => compareTimestamps(right.updated, left.updated));
2444
2533
  return results;
2445
2534
  }
2446
- async function loadDependencyGraph(missionPath, assignments) {
2447
- const statusPath = resolve6(missionPath, "_status.md");
2535
+ async function loadDependencyGraph(projectPath, assignments) {
2536
+ const statusPath = resolve6(projectPath, "_status.md");
2448
2537
  if (await fileExists(statusPath)) {
2449
2538
  const statusContent = await readFile5(statusPath, "utf-8");
2450
2539
  const parsed = parseStatus(statusContent);
@@ -2455,23 +2544,23 @@ async function loadDependencyGraph(missionPath, assignments) {
2455
2544
  }
2456
2545
  return buildDependencyGraph(assignments);
2457
2546
  }
2458
- function buildMissionRollup(mission, assignments) {
2547
+ function buildProjectRollup(project, assignments) {
2459
2548
  const progress = { total: assignments.length };
2460
- let unansweredQuestions = 0;
2549
+ let openQuestions = 0;
2461
2550
  for (const assignment of assignments) {
2462
2551
  const s = assignment.status;
2463
2552
  progress[s] = (progress[s] ?? 0) + 1;
2464
- unansweredQuestions += countPendingAnswers(assignment.body);
2553
+ openQuestions += countPendingAnswers(assignment.body);
2465
2554
  }
2466
2555
  const needsAttention = {
2467
2556
  blockedCount: progress["blocked"] ?? 0,
2468
2557
  failedCount: progress["failed"] ?? 0,
2469
- unansweredQuestions
2558
+ openQuestions
2470
2559
  };
2471
2560
  let status = "pending";
2472
- if (mission.statusOverride) {
2473
- status = mission.statusOverride;
2474
- } else if (mission.archived) {
2561
+ if (project.statusOverride) {
2562
+ status = project.statusOverride;
2563
+ } else if (project.archived) {
2475
2564
  status = "archived";
2476
2565
  } else if (progress.total > 0 && (progress["completed"] ?? 0) === progress.total) {
2477
2566
  status = "completed";
@@ -2501,16 +2590,16 @@ function toAssignmentSummary(assignment) {
2501
2590
  updated: assignment.updated
2502
2591
  };
2503
2592
  }
2504
- async function toAssignmentBoardItem(missionsDir, missionRecord, assignment) {
2593
+ async function toAssignmentBoardItem(projectsDir, projectRecord, assignment) {
2505
2594
  return {
2506
2595
  ...toAssignmentSummary(assignment),
2507
- missionSlug: missionRecord.summary.slug,
2508
- missionTitle: missionRecord.summary.title,
2596
+ projectSlug: projectRecord.summary.slug,
2597
+ projectTitle: projectRecord.summary.title,
2509
2598
  blockedReason: assignment.blockedReason,
2510
- missionWorkspace: missionRecord.mission.workspace,
2599
+ projectWorkspace: projectRecord.project.workspace,
2511
2600
  availableTransitions: await getAvailableTransitions(
2512
- missionsDir,
2513
- missionRecord.summary.slug,
2601
+ projectsDir,
2602
+ projectRecord.summary.slug,
2514
2603
  assignment.slug,
2515
2604
  assignment
2516
2605
  )
@@ -2542,18 +2631,18 @@ function buildDependencyGraph(assignments) {
2542
2631
  function findAssignmentStatus(assignments, slug) {
2543
2632
  return assignments.find((assignment) => assignment.slug === slug)?.status ?? "pending";
2544
2633
  }
2545
- async function getAvailableTransitions(missionsDir, missionSlug, assignmentSlug, assignment) {
2634
+ async function getAvailableTransitions(projectsDir, projectSlug, assignmentSlug, assignment) {
2546
2635
  const config = await getStatusConfig();
2547
2636
  const transitionDefs = getTransitionDefinitions(config);
2548
2637
  const actions = [];
2549
- const missionPath = resolve6(missionsDir, missionSlug);
2638
+ const projectPath = resolve6(projectsDir, projectSlug);
2550
2639
  for (const definition of transitionDefs) {
2551
2640
  let warning = null;
2552
2641
  if (definition.command === "start" && !assignment.assignee) {
2553
2642
  warning = "No assignee set \u2014 consider assigning before starting.";
2554
2643
  }
2555
2644
  if (definition.command === "start" && assignment.dependsOn.length > 0) {
2556
- const unmetDependencies = await getUnmetDependencies(missionPath, assignment.dependsOn, config.terminalStatuses);
2645
+ const unmetDependencies = await getUnmetDependencies(projectPath, assignment.dependsOn, config.terminalStatuses);
2557
2646
  if (unmetDependencies.length > 0) {
2558
2647
  warning = `Unmet dependencies: ${unmetDependencies.join(", ")}.`;
2559
2648
  }
@@ -2572,11 +2661,11 @@ async function getAvailableTransitions(missionsDir, missionSlug, assignmentSlug,
2572
2661
  }
2573
2662
  return actions;
2574
2663
  }
2575
- async function getUnmetDependencies(missionPath, dependsOn, terminalStatuses) {
2664
+ async function getUnmetDependencies(projectPath, dependsOn, terminalStatuses) {
2576
2665
  const terminals = terminalStatuses ?? /* @__PURE__ */ new Set(["completed"]);
2577
2666
  const unmet = [];
2578
2667
  for (const dependency of dependsOn) {
2579
- const dependencyPath = resolve6(missionPath, "assignments", dependency, "assignment.md");
2668
+ const dependencyPath = resolve6(projectPath, "assignments", dependency, "assignment.md");
2580
2669
  if (!await fileExists(dependencyPath)) {
2581
2670
  unmet.push(`${dependency} (missing)`);
2582
2671
  continue;
@@ -2589,19 +2678,19 @@ async function getUnmetDependencies(missionPath, dependsOn, terminalStatuses) {
2589
2678
  }
2590
2679
  return unmet;
2591
2680
  }
2592
- function buildAttentionItems(missionRecords) {
2681
+ function buildAttentionItems(projectRecords) {
2593
2682
  const items = [];
2594
- for (const record of missionRecords) {
2683
+ for (const record of projectRecords) {
2595
2684
  for (const assignment of record.assignments) {
2596
2685
  const stale = isStale(assignment.updated);
2597
2686
  const base = {
2598
- missionSlug: record.summary.slug,
2599
- missionTitle: record.summary.title,
2687
+ projectSlug: record.summary.slug,
2688
+ projectTitle: record.summary.title,
2600
2689
  assignmentSlug: assignment.slug,
2601
2690
  assignmentTitle: assignment.title,
2602
2691
  status: assignment.status,
2603
2692
  updated: assignment.updated,
2604
- href: `/missions/${record.summary.slug}/assignments/${assignment.slug}`,
2693
+ href: `/projects/${record.summary.slug}/assignments/${assignment.slug}`,
2605
2694
  blockedReason: assignment.blockedReason,
2606
2695
  stale
2607
2696
  };
@@ -2641,19 +2730,19 @@ function buildAttentionItems(missionRecords) {
2641
2730
  }
2642
2731
  return items.sort(compareAttentionItems);
2643
2732
  }
2644
- function buildRecentActivity(missionRecords) {
2733
+ function buildRecentActivity(projectRecords) {
2645
2734
  const activity = [];
2646
- for (const record of missionRecords) {
2735
+ for (const record of projectRecords) {
2647
2736
  activity.push({
2648
- id: `mission:${record.summary.slug}`,
2649
- type: "mission",
2737
+ id: `project:${record.summary.slug}`,
2738
+ type: "project",
2650
2739
  title: record.summary.title,
2651
2740
  updated: record.summary.updated,
2652
- href: `/missions/${record.summary.slug}`,
2653
- missionSlug: record.summary.slug,
2654
- missionTitle: record.summary.title,
2741
+ href: `/projects/${record.summary.slug}`,
2742
+ projectSlug: record.summary.slug,
2743
+ projectTitle: record.summary.title,
2655
2744
  assignmentSlug: null,
2656
- summary: `Mission status is ${record.summary.status}.`
2745
+ summary: `Project status is ${record.summary.status}.`
2657
2746
  });
2658
2747
  for (const assignment of record.assignments) {
2659
2748
  activity.push({
@@ -2661,9 +2750,9 @@ function buildRecentActivity(missionRecords) {
2661
2750
  type: "assignment",
2662
2751
  title: assignment.title,
2663
2752
  updated: assignment.updated,
2664
- href: `/missions/${record.summary.slug}/assignments/${assignment.slug}`,
2665
- missionSlug: record.summary.slug,
2666
- missionTitle: record.summary.title,
2753
+ href: `/projects/${record.summary.slug}/assignments/${assignment.slug}`,
2754
+ projectSlug: record.summary.slug,
2755
+ projectTitle: record.summary.title,
2667
2756
  assignmentSlug: assignment.slug,
2668
2757
  summary: `Assignment is ${assignment.status} with ${assignment.priority} priority.`
2669
2758
  });
@@ -2698,8 +2787,8 @@ function countPendingAnswers(body) {
2698
2787
  const matches = body.match(/^\*\*A:\*\*\s+pending\s*$/gim);
2699
2788
  return matches ? matches.length : 0;
2700
2789
  }
2701
- function getMissionActivityTimestamp(missionUpdated, assignments) {
2702
- let latest = missionUpdated;
2790
+ function getProjectActivityTimestamp(projectUpdated, assignments) {
2791
+ let latest = projectUpdated;
2703
2792
  for (const assignment of assignments) {
2704
2793
  if (compareTimestamps(assignment.updated, latest) > 0) {
2705
2794
  latest = assignment.updated;
@@ -2707,28 +2796,28 @@ function getMissionActivityTimestamp(missionUpdated, assignments) {
2707
2796
  }
2708
2797
  return latest;
2709
2798
  }
2710
- function getDocumentPath(missionsDir, documentType, missionSlug, assignmentSlug) {
2799
+ function getDocumentPath(projectsDir, documentType, projectSlug, assignmentSlug) {
2711
2800
  switch (documentType) {
2712
- case "mission":
2713
- return resolve6(missionsDir, missionSlug, "mission.md");
2801
+ case "project":
2802
+ return resolve6(projectsDir, projectSlug, "project.md");
2714
2803
  case "assignment":
2715
- return assignmentSlug ? resolve6(missionsDir, missionSlug, "assignments", assignmentSlug, "assignment.md") : null;
2804
+ return assignmentSlug ? resolve6(projectsDir, projectSlug, "assignments", assignmentSlug, "assignment.md") : null;
2716
2805
  case "plan":
2717
- return assignmentSlug ? resolve6(missionsDir, missionSlug, "assignments", assignmentSlug, "plan.md") : null;
2806
+ return assignmentSlug ? resolve6(projectsDir, projectSlug, "assignments", assignmentSlug, "plan.md") : null;
2718
2807
  case "scratchpad":
2719
- return assignmentSlug ? resolve6(missionsDir, missionSlug, "assignments", assignmentSlug, "scratchpad.md") : null;
2808
+ return assignmentSlug ? resolve6(projectsDir, projectSlug, "assignments", assignmentSlug, "scratchpad.md") : null;
2720
2809
  case "handoff":
2721
- return assignmentSlug ? resolve6(missionsDir, missionSlug, "assignments", assignmentSlug, "handoff.md") : null;
2810
+ return assignmentSlug ? resolve6(projectsDir, projectSlug, "assignments", assignmentSlug, "handoff.md") : null;
2722
2811
  case "decision-record":
2723
- return assignmentSlug ? resolve6(missionsDir, missionSlug, "assignments", assignmentSlug, "decision-record.md") : null;
2812
+ return assignmentSlug ? resolve6(projectsDir, projectSlug, "assignments", assignmentSlug, "decision-record.md") : null;
2724
2813
  default:
2725
2814
  return null;
2726
2815
  }
2727
2816
  }
2728
- function getEditableDocumentTitle(documentType, missionSlug, assignmentSlug) {
2817
+ function getEditableDocumentTitle(documentType, projectSlug, assignmentSlug) {
2729
2818
  switch (documentType) {
2730
- case "mission":
2731
- return `Edit Mission: ${missionSlug}`;
2819
+ case "project":
2820
+ return `Edit Project: ${projectSlug}`;
2732
2821
  case "assignment":
2733
2822
  return `Edit Assignment: ${assignmentSlug || "assignment"}`;
2734
2823
  case "plan":
@@ -2740,18 +2829,18 @@ function getEditableDocumentTitle(documentType, missionSlug, assignmentSlug) {
2740
2829
  case "decision-record":
2741
2830
  return `Append Decision: ${assignmentSlug || "assignment"}`;
2742
2831
  case "playbook":
2743
- return `Edit Playbook: ${missionSlug}`;
2832
+ return `Edit Playbook: ${projectSlug}`;
2744
2833
  default:
2745
- return missionSlug;
2834
+ return projectSlug;
2746
2835
  }
2747
2836
  }
2748
- async function listPlaybooks(playbooksDir) {
2749
- if (!await fileExists(playbooksDir)) return [];
2750
- const entries = await readdir3(playbooksDir, { withFileTypes: true });
2837
+ async function listPlaybooks(playbooksDir2) {
2838
+ if (!await fileExists(playbooksDir2)) return [];
2839
+ const entries = await readdir3(playbooksDir2, { withFileTypes: true });
2751
2840
  const playbooks = [];
2752
2841
  for (const entry of entries) {
2753
2842
  if (!entry.isFile() || !entry.name.endsWith(".md") || entry.name.startsWith("_") || entry.name === "manifest.md") continue;
2754
- const filePath = resolve6(playbooksDir, entry.name);
2843
+ const filePath = resolve6(playbooksDir2, entry.name);
2755
2844
  const raw = await readFile5(filePath, "utf-8");
2756
2845
  const parsed = parsePlaybook(raw);
2757
2846
  const slug = parsed.slug || entry.name.replace(/\.md$/, "");
@@ -2767,8 +2856,8 @@ async function listPlaybooks(playbooksDir) {
2767
2856
  }
2768
2857
  return playbooks.sort((a, b) => (b.updated || b.created).localeCompare(a.updated || a.created));
2769
2858
  }
2770
- async function getPlaybookDetail(playbooksDir, slug) {
2771
- const filePath = resolve6(playbooksDir, `${slug}.md`);
2859
+ async function getPlaybookDetail(playbooksDir2, slug) {
2860
+ const filePath = resolve6(playbooksDir2, `${slug}.md`);
2772
2861
  if (!await fileExists(filePath)) return null;
2773
2862
  const raw = await readFile5(filePath, "utf-8");
2774
2863
  const parsed = parsePlaybook(raw);
@@ -2783,7 +2872,7 @@ async function getPlaybookDetail(playbooksDir, slug) {
2783
2872
  body: parsed.body
2784
2873
  };
2785
2874
  }
2786
- var STALE_ASSIGNMENT_MS, ATTENTION_PAGE_LIMIT, OVERVIEW_ATTENTION_LIMIT, RECENT_MISSIONS_LIMIT, RECENT_ACTIVITY_LIMIT, DEFAULT_TRANSITION_DEFINITIONS, DEFAULT_STATUS_COLORS, _cachedConfig, DEFAULT_GRAPH_COLORS;
2875
+ var STALE_ASSIGNMENT_MS, ATTENTION_PAGE_LIMIT, OVERVIEW_ATTENTION_LIMIT, RECENT_PROJECTS_LIMIT, RECENT_ACTIVITY_LIMIT, DEFAULT_TRANSITION_DEFINITIONS, DEFAULT_STATUS_COLORS, _cachedConfig, DEFAULT_GRAPH_COLORS;
2787
2876
  var init_api = __esm({
2788
2877
  "src/dashboard/api.ts"() {
2789
2878
  "use strict";
@@ -2795,7 +2884,7 @@ var init_api = __esm({
2795
2884
  STALE_ASSIGNMENT_MS = 7 * 24 * 60 * 60 * 1e3;
2796
2885
  ATTENTION_PAGE_LIMIT = 50;
2797
2886
  OVERVIEW_ATTENTION_LIMIT = 6;
2798
- RECENT_MISSIONS_LIMIT = 6;
2887
+ RECENT_PROJECTS_LIMIT = 6;
2799
2888
  RECENT_ACTIVITY_LIMIT = 12;
2800
2889
  DEFAULT_TRANSITION_DEFINITIONS = [
2801
2890
  {
@@ -3042,13 +3131,13 @@ function serializeLogEntry(entry) {
3042
3131
  if (entry.status) lines.push(`**Status:** ${entry.status}`);
3043
3132
  return lines.join("\n");
3044
3133
  }
3045
- function checklistPath(todosDir, workspace) {
3046
- return resolve13(todosDir, `${workspace}.md`);
3134
+ function checklistPath(todosDir2, workspace) {
3135
+ return resolve13(todosDir2, `${workspace}.md`);
3047
3136
  }
3048
- function logPath(todosDir, workspace) {
3049
- return resolve13(todosDir, `${workspace}-log.md`);
3137
+ function logPath(todosDir2, workspace) {
3138
+ return resolve13(todosDir2, `${workspace}-log.md`);
3050
3139
  }
3051
- function archivePath(todosDir, workspace, interval, now = /* @__PURE__ */ new Date()) {
3140
+ function archivePath(todosDir2, workspace, interval, now = /* @__PURE__ */ new Date()) {
3052
3141
  const year = now.getFullYear();
3053
3142
  const month = String(now.getMonth() + 1).padStart(2, "0");
3054
3143
  const day = String(now.getDate()).padStart(2, "0");
@@ -3070,32 +3159,32 @@ function archivePath(todosDir, workspace, interval, now = /* @__PURE__ */ new Da
3070
3159
  default:
3071
3160
  suffix = `${year}-${month}-${day}`;
3072
3161
  }
3073
- return resolve13(todosDir, "archive", `${workspace}-${suffix}.md`);
3162
+ return resolve13(todosDir2, "archive", `${workspace}-${suffix}.md`);
3074
3163
  }
3075
- async function readChecklist(todosDir, workspace) {
3076
- const path = checklistPath(todosDir, workspace);
3164
+ async function readChecklist(todosDir2, workspace) {
3165
+ const path = checklistPath(todosDir2, workspace);
3077
3166
  if (!await fileExists(path)) {
3078
3167
  return { workspace, archiveInterval: "weekly", items: [] };
3079
3168
  }
3080
3169
  const content = await readFile10(path, "utf-8");
3081
3170
  return parseChecklist(content);
3082
3171
  }
3083
- async function writeChecklist(todosDir, checklist) {
3084
- await ensureDir(todosDir);
3085
- const path = checklistPath(todosDir, checklist.workspace);
3172
+ async function writeChecklist(todosDir2, checklist) {
3173
+ await ensureDir(todosDir2);
3174
+ const path = checklistPath(todosDir2, checklist.workspace);
3086
3175
  await writeFileForce(path, serializeChecklist(checklist));
3087
3176
  }
3088
- async function readLog(todosDir, workspace) {
3089
- const path = logPath(todosDir, workspace);
3177
+ async function readLog(todosDir2, workspace) {
3178
+ const path = logPath(todosDir2, workspace);
3090
3179
  if (!await fileExists(path)) {
3091
3180
  return { workspace, entries: [] };
3092
3181
  }
3093
3182
  const content = await readFile10(path, "utf-8");
3094
3183
  return parseLog(content);
3095
3184
  }
3096
- async function appendLogEntry2(todosDir, workspace, entry) {
3097
- await ensureDir(todosDir);
3098
- const path = logPath(todosDir, workspace);
3185
+ async function appendLogEntry2(todosDir2, workspace, entry) {
3186
+ await ensureDir(todosDir2);
3187
+ const path = logPath(todosDir2, workspace);
3099
3188
  let content;
3100
3189
  if (await fileExists(path)) {
3101
3190
  content = await readFile10(path, "utf-8");
@@ -3132,46 +3221,46 @@ var init_parser2 = __esm({
3132
3221
  });
3133
3222
 
3134
3223
  // src/dashboard/server.ts
3224
+ init_paths();
3135
3225
  init_api();
3136
3226
  import express from "express";
3137
3227
  import { createServer } from "http";
3138
- import { resolve as resolve14 } from "path";
3139
- import { homedir as homedir2 } from "os";
3140
- import { writeFile as writeFile3, unlink as unlink3 } from "fs/promises";
3228
+ import { resolve as resolve15 } from "path";
3229
+ import { writeFile as writeFile4, unlink as unlink4 } from "fs/promises";
3141
3230
  import { WebSocketServer, WebSocket } from "ws";
3142
3231
 
3143
3232
  // src/dashboard/watcher.ts
3144
3233
  import { watch } from "chokidar";
3145
3234
  import { relative, sep } from "path";
3146
3235
  function createWatcher(options) {
3147
- const { missionsDir, serversDir, playbooksDir, todosDir, onMessage, debounceMs = 300 } = options;
3236
+ const { projectsDir, serversDir: serversDir2, playbooksDir: playbooksDir2, todosDir: todosDir2, onMessage, debounceMs = 300 } = options;
3148
3237
  const pendingEvents = /* @__PURE__ */ new Map();
3149
- const missionsWatcher = watch(missionsDir, {
3238
+ const projectsWatcher = watch(projectsDir, {
3150
3239
  ignoreInitial: true,
3151
3240
  persistent: true,
3152
3241
  depth: 10,
3153
3242
  ignored: /(^|[\/\\])\../
3154
3243
  });
3155
- function handleMissionChange(filePath) {
3156
- const rel = relative(missionsDir, filePath);
3244
+ function handleProjectChange(filePath) {
3245
+ const rel = relative(projectsDir, filePath);
3157
3246
  const parts = rel.split(sep);
3158
3247
  if (parts.length === 0) return;
3159
- const missionSlug = parts[0];
3248
+ const projectSlug = parts[0];
3160
3249
  let assignmentSlug;
3161
3250
  if (parts.length >= 3 && parts[1] === "assignments") {
3162
3251
  assignmentSlug = parts[2];
3163
3252
  }
3164
- const debounceKey = assignmentSlug ? `${missionSlug}/${assignmentSlug}` : missionSlug;
3253
+ const debounceKey = assignmentSlug ? `${projectSlug}/${assignmentSlug}` : projectSlug;
3165
3254
  const existing = pendingEvents.get(debounceKey);
3166
3255
  if (existing) clearTimeout(existing);
3167
- const messageType = assignmentSlug ? "assignment-updated" : "mission-updated";
3256
+ const messageType = assignmentSlug ? "assignment-updated" : "project-updated";
3168
3257
  pendingEvents.set(
3169
3258
  debounceKey,
3170
3259
  setTimeout(() => {
3171
3260
  pendingEvents.delete(debounceKey);
3172
3261
  const message = {
3173
3262
  type: messageType,
3174
- missionSlug,
3263
+ projectSlug,
3175
3264
  assignmentSlug,
3176
3265
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
3177
3266
  };
@@ -3179,11 +3268,11 @@ function createWatcher(options) {
3179
3268
  }, debounceMs)
3180
3269
  );
3181
3270
  }
3182
- missionsWatcher.on("change", handleMissionChange);
3183
- missionsWatcher.on("add", handleMissionChange);
3184
- missionsWatcher.on("unlink", handleMissionChange);
3271
+ projectsWatcher.on("change", handleProjectChange);
3272
+ projectsWatcher.on("add", handleProjectChange);
3273
+ projectsWatcher.on("unlink", handleProjectChange);
3185
3274
  let serversWatcher = null;
3186
- if (serversDir) {
3275
+ if (serversDir2) {
3187
3276
  let handleServerChange2 = function() {
3188
3277
  const debounceKey = "__servers__";
3189
3278
  const existing = pendingEvents.get(debounceKey);
@@ -3201,7 +3290,7 @@ function createWatcher(options) {
3201
3290
  );
3202
3291
  };
3203
3292
  var handleServerChange = handleServerChange2;
3204
- serversWatcher = watch(serversDir, {
3293
+ serversWatcher = watch(serversDir2, {
3205
3294
  ignoreInitial: true,
3206
3295
  persistent: true,
3207
3296
  depth: 1,
@@ -3212,7 +3301,7 @@ function createWatcher(options) {
3212
3301
  serversWatcher.on("unlink", handleServerChange2);
3213
3302
  }
3214
3303
  let playbooksWatcher = null;
3215
- if (playbooksDir) {
3304
+ if (playbooksDir2) {
3216
3305
  let handlePlaybookChange2 = function() {
3217
3306
  const debounceKey = "__playbooks__";
3218
3307
  const existing = pendingEvents.get(debounceKey);
@@ -3230,7 +3319,7 @@ function createWatcher(options) {
3230
3319
  );
3231
3320
  };
3232
3321
  var handlePlaybookChange = handlePlaybookChange2;
3233
- playbooksWatcher = watch(playbooksDir, {
3322
+ playbooksWatcher = watch(playbooksDir2, {
3234
3323
  ignoreInitial: true,
3235
3324
  persistent: true,
3236
3325
  depth: 1,
@@ -3241,7 +3330,7 @@ function createWatcher(options) {
3241
3330
  playbooksWatcher.on("unlink", handlePlaybookChange2);
3242
3331
  }
3243
3332
  let todosWatcher = null;
3244
- if (todosDir) {
3333
+ if (todosDir2) {
3245
3334
  let handleTodoChange2 = function() {
3246
3335
  const debounceKey = "__todos__";
3247
3336
  const existing = pendingEvents.get(debounceKey);
@@ -3259,7 +3348,7 @@ function createWatcher(options) {
3259
3348
  );
3260
3349
  };
3261
3350
  var handleTodoChange = handleTodoChange2;
3262
- todosWatcher = watch(todosDir, {
3351
+ todosWatcher = watch(todosDir2, {
3263
3352
  ignoreInitial: true,
3264
3353
  persistent: true,
3265
3354
  depth: 1,
@@ -3275,7 +3364,7 @@ function createWatcher(options) {
3275
3364
  clearTimeout(timeout);
3276
3365
  });
3277
3366
  pendingEvents.clear();
3278
- await missionsWatcher.close();
3367
+ await projectsWatcher.close();
3279
3368
  if (serversWatcher) await serversWatcher.close();
3280
3369
  if (playbooksWatcher) await playbooksWatcher.close();
3281
3370
  if (todosWatcher) await todosWatcher.close();
@@ -3363,15 +3452,15 @@ init_config();
3363
3452
  // src/templates/manifest.ts
3364
3453
  function renderManifest(params) {
3365
3454
  return `---
3366
- version: "1.0"
3367
- mission: ${params.slug}
3455
+ version: "2.0"
3456
+ project: ${params.slug}
3368
3457
  generated: "${params.timestamp}"
3369
3458
  ---
3370
3459
 
3371
- # Mission: ${params.slug}
3460
+ # Project: ${params.slug}
3372
3461
 
3373
3462
  ## Overview
3374
- - [Mission Overview](./mission.md)
3463
+ - [Project Overview](./project.md)
3375
3464
 
3376
3465
  ## Indexes
3377
3466
  - [Assignments](./_index-assignments.md)
@@ -3380,10 +3469,6 @@ generated: "${params.timestamp}"
3380
3469
  - [Status](./_status.md)
3381
3470
  - [Resources](./resources/_index.md)
3382
3471
  - [Memories](./memories/_index.md)
3383
-
3384
- ## Config
3385
- - [Agent Instructions](./agent.md)
3386
- - [Claude Code Instructions](./claude.md)
3387
3472
  `;
3388
3473
  }
3389
3474
 
@@ -3398,8 +3483,8 @@ function escapeYamlString(value) {
3398
3483
  return `"${escaped}"`;
3399
3484
  }
3400
3485
 
3401
- // src/templates/mission.ts
3402
- function renderMission(params) {
3486
+ // src/templates/project.ts
3487
+ function renderProject(params) {
3403
3488
  const safeTitle = escapeYamlString(params.title);
3404
3489
  const workspaceLine = params.workspace ? `
3405
3490
  workspace: ${params.workspace}` : "";
@@ -3420,7 +3505,7 @@ tags: []${workspaceLine}
3420
3505
 
3421
3506
  ## Overview
3422
3507
 
3423
- <!-- Describe the mission goal, context, and success criteria here. -->
3508
+ <!-- Describe the project goal, context, and success criteria here. -->
3424
3509
 
3425
3510
  ## Notes
3426
3511
 
@@ -3428,43 +3513,6 @@ tags: []${workspaceLine}
3428
3513
  `;
3429
3514
  }
3430
3515
 
3431
- // src/templates/agent.ts
3432
- function renderAgent(params) {
3433
- return `---
3434
- mission: ${params.slug}
3435
- updated: "${params.timestamp}"
3436
- ---
3437
-
3438
- # Agent Instructions
3439
-
3440
- All agents working on this mission must follow these guidelines.
3441
-
3442
- ## Conventions
3443
-
3444
- <!-- Coding conventions, naming standards, architectural patterns. -->
3445
-
3446
- ## Boundaries
3447
-
3448
- <!-- What agents should NOT do. Files/systems that are off-limits. -->
3449
-
3450
- ## Resources
3451
-
3452
- <!-- Links to key resources agents should consult. -->
3453
- `;
3454
- }
3455
-
3456
- // src/templates/claude.ts
3457
- function renderClaude(params) {
3458
- return `# Claude Code Instructions \u2014 ${params.slug}
3459
-
3460
- Read \`agent.md\` first for universal conventions and boundaries.
3461
-
3462
- ## Additional Claude Code Rules
3463
-
3464
- <!-- Add Claude Code-specific rules here. -->
3465
- `;
3466
- }
3467
-
3468
3516
  // src/templates/assignment.ts
3469
3517
  function renderAssignment(params) {
3470
3518
  const safeTitle = escapeYamlString(params.title);
@@ -3472,10 +3520,14 @@ function renderAssignment(params) {
3472
3520
  - ${params.dependsOn.join("\n - ")}`;
3473
3521
  const linksYaml = params.links.length === 0 ? "links: []" : `links:
3474
3522
  - ${params.links.join("\n - ")}`;
3523
+ const projectYaml = `project: ${params.project == null ? "null" : params.project}`;
3524
+ const typeYaml = `type: ${params.type ?? "feature"}`;
3475
3525
  return `---
3476
3526
  id: ${params.id}
3477
3527
  slug: ${params.slug}
3478
3528
  title: ${safeTitle}
3529
+ ${projectYaml}
3530
+ ${typeYaml}
3479
3531
  status: pending
3480
3532
  priority: ${params.priority}
3481
3533
  created: "${params.timestamp}"
@@ -3505,56 +3557,30 @@ tags: []
3505
3557
  - [ ] <!-- criterion 2 -->
3506
3558
  - [ ] <!-- criterion 3 -->
3507
3559
 
3508
- ## Context
3509
-
3510
- <!-- Links to relevant docs, code, or other assignments. -->
3511
-
3512
- ## Questions & Answers
3560
+ ## Todos
3513
3561
 
3514
- No questions yet.
3562
+ <!--
3563
+ Checklist of work items for this assignment. Items may be simple tasks
3564
+ or a markdown link to a plan file (e.g., "- [ ] Execute [plan](./plan.md)").
3565
+ When a plan is superseded by a new one, mark the old todo as:
3566
+ - [x] ~~Execute [old plan](./plan.md)~~ (superseded by plan-v2)
3567
+ Never delete superseded todos \u2014 preserve the history.
3568
+ -->
3515
3569
 
3516
- ## Progress
3570
+ ## Context
3517
3571
 
3518
- No progress yet.
3572
+ <!-- Links to relevant docs, code, or other assignments. -->
3519
3573
 
3520
3574
  ## Links
3521
3575
 
3522
- - [Plan](./plan.md)
3576
+ - [Progress](./progress.md)
3577
+ - [Comments](./comments.md)
3523
3578
  - [Scratchpad](./scratchpad.md)
3524
3579
  - [Handoff](./handoff.md)
3525
3580
  - [Decision Record](./decision-record.md)
3526
3581
  `;
3527
3582
  }
3528
3583
 
3529
- // src/templates/plan.ts
3530
- function renderPlan(params) {
3531
- return `---
3532
- assignment: ${params.assignmentSlug}
3533
- status: draft
3534
- created: "${params.timestamp}"
3535
- updated: "${params.timestamp}"
3536
- ---
3537
-
3538
- # Plan: ${params.title}
3539
-
3540
- ## Approach
3541
-
3542
- <!-- High-level description of how to accomplish the objective. -->
3543
-
3544
- ## Tasks
3545
-
3546
- - [ ] <!-- step 1 -->
3547
- - [ ] <!-- step 2 -->
3548
- - [ ] <!-- step 3 -->
3549
-
3550
- ## Risks & Mitigations
3551
-
3552
- | Risk | Mitigation |
3553
- |------|------------|
3554
- | <!-- risk --> | <!-- mitigation --> |
3555
- `;
3556
- }
3557
-
3558
3584
  // src/templates/scratchpad.ts
3559
3585
  function renderScratchpad(params) {
3560
3586
  return `---
@@ -3599,7 +3625,7 @@ No decisions recorded yet.
3599
3625
  // src/templates/index-stubs.ts
3600
3626
  function renderIndexAssignments(params) {
3601
3627
  return `---
3602
- mission: ${params.slug}
3628
+ project: ${params.slug}
3603
3629
  generated: "${params.timestamp}"
3604
3630
  total: 0
3605
3631
  by_status:
@@ -3619,7 +3645,7 @@ by_status:
3619
3645
  }
3620
3646
  function renderIndexPlans(params) {
3621
3647
  return `---
3622
- mission: ${params.slug}
3648
+ project: ${params.slug}
3623
3649
  generated: "${params.timestamp}"
3624
3650
  ---
3625
3651
 
@@ -3631,7 +3657,7 @@ generated: "${params.timestamp}"
3631
3657
  }
3632
3658
  function renderIndexDecisions(params) {
3633
3659
  return `---
3634
- mission: ${params.slug}
3660
+ project: ${params.slug}
3635
3661
  generated: "${params.timestamp}"
3636
3662
  ---
3637
3663
 
@@ -3643,7 +3669,7 @@ generated: "${params.timestamp}"
3643
3669
  }
3644
3670
  function renderStatus(params) {
3645
3671
  return `---
3646
- mission: ${params.slug}
3672
+ project: ${params.slug}
3647
3673
  generated: "${params.timestamp}"
3648
3674
  status: pending
3649
3675
  progress:
@@ -3657,10 +3683,10 @@ progress:
3657
3683
  needsAttention:
3658
3684
  blockedCount: 0
3659
3685
  failedCount: 0
3660
- unansweredQuestions: 0
3686
+ openQuestions: 0
3661
3687
  ---
3662
3688
 
3663
- # Mission Status: ${params.title}
3689
+ # Project Status: ${params.title}
3664
3690
 
3665
3691
  **Status:** pending
3666
3692
  **Progress:** 0/0 assignments complete
@@ -3682,7 +3708,7 @@ No dependencies yet.
3682
3708
  }
3683
3709
  function renderResourcesIndex(params) {
3684
3710
  return `---
3685
- mission: ${params.slug}
3711
+ project: ${params.slug}
3686
3712
  generated: "${params.timestamp}"
3687
3713
  total: 0
3688
3714
  ---
@@ -3695,7 +3721,7 @@ total: 0
3695
3721
  }
3696
3722
  function renderMemoriesIndex(params) {
3697
3723
  return `---
3698
- mission: ${params.slug}
3724
+ project: ${params.slug}
3699
3725
  generated: "${params.timestamp}"
3700
3726
  total: 0
3701
3727
  ---
@@ -3828,13 +3854,13 @@ async function readCurrentDocument(filePath) {
3828
3854
  }
3829
3855
  return readFile6(filePath, "utf-8");
3830
3856
  }
3831
- function createWriteRouter(missionsDir) {
3857
+ function createWriteRouter(projectsDir) {
3832
3858
  const router = Router();
3833
- router.get("/api/templates/mission", (_req, res) => {
3834
- const content = renderMission({
3859
+ router.get("/api/templates/project", (_req, res) => {
3860
+ const content = renderProject({
3835
3861
  id: generateId(),
3836
- slug: "my-new-mission",
3837
- title: "My New Mission",
3862
+ slug: "my-new-project",
3863
+ title: "My New Project",
3838
3864
  timestamp: nowTimestamp()
3839
3865
  });
3840
3866
  res.json({ content });
@@ -3851,20 +3877,20 @@ function createWriteRouter(missionsDir) {
3851
3877
  });
3852
3878
  res.json({ content });
3853
3879
  });
3854
- router.get("/api/missions/:slug/edit", async (req, res) => {
3880
+ router.get("/api/projects/:slug/edit", async (req, res) => {
3855
3881
  const slug = getParam(req.params.slug);
3856
- const document = await getEditableDocument(missionsDir, "mission", slug);
3882
+ const document = await getEditableDocument(projectsDir, "project", slug);
3857
3883
  if (!document) {
3858
- res.status(404).json({ error: `Mission "${slug}" not found` });
3884
+ res.status(404).json({ error: `Project "${slug}" not found` });
3859
3885
  return;
3860
3886
  }
3861
3887
  res.json(document);
3862
3888
  });
3863
- router.get("/api/missions/:slug/assignments/:aslug/edit", async (req, res) => {
3889
+ router.get("/api/projects/:slug/assignments/:aslug/edit", async (req, res) => {
3864
3890
  const slug = getParam(req.params.slug);
3865
3891
  const assignmentSlug = getParam(req.params.aslug);
3866
3892
  const document = await getEditableDocument(
3867
- missionsDir,
3893
+ projectsDir,
3868
3894
  "assignment",
3869
3895
  slug,
3870
3896
  assignmentSlug
@@ -3875,11 +3901,11 @@ function createWriteRouter(missionsDir) {
3875
3901
  }
3876
3902
  res.json(document);
3877
3903
  });
3878
- router.get("/api/missions/:slug/assignments/:aslug/plan/edit", async (req, res) => {
3904
+ router.get("/api/projects/:slug/assignments/:aslug/plan/edit", async (req, res) => {
3879
3905
  const slug = getParam(req.params.slug);
3880
3906
  const assignmentSlug = getParam(req.params.aslug);
3881
3907
  const document = await getEditableDocument(
3882
- missionsDir,
3908
+ projectsDir,
3883
3909
  "plan",
3884
3910
  slug,
3885
3911
  assignmentSlug
@@ -3890,11 +3916,11 @@ function createWriteRouter(missionsDir) {
3890
3916
  }
3891
3917
  res.json(document);
3892
3918
  });
3893
- router.get("/api/missions/:slug/assignments/:aslug/scratchpad/edit", async (req, res) => {
3919
+ router.get("/api/projects/:slug/assignments/:aslug/scratchpad/edit", async (req, res) => {
3894
3920
  const slug = getParam(req.params.slug);
3895
3921
  const assignmentSlug = getParam(req.params.aslug);
3896
3922
  const document = await getEditableDocument(
3897
- missionsDir,
3923
+ projectsDir,
3898
3924
  "scratchpad",
3899
3925
  slug,
3900
3926
  assignmentSlug
@@ -3905,11 +3931,11 @@ function createWriteRouter(missionsDir) {
3905
3931
  }
3906
3932
  res.json(document);
3907
3933
  });
3908
- router.get("/api/missions/:slug/assignments/:aslug/handoff/edit", async (req, res) => {
3934
+ router.get("/api/projects/:slug/assignments/:aslug/handoff/edit", async (req, res) => {
3909
3935
  const slug = getParam(req.params.slug);
3910
3936
  const assignmentSlug = getParam(req.params.aslug);
3911
3937
  const document = await getEditableDocument(
3912
- missionsDir,
3938
+ projectsDir,
3913
3939
  "handoff",
3914
3940
  slug,
3915
3941
  assignmentSlug
@@ -3920,11 +3946,11 @@ function createWriteRouter(missionsDir) {
3920
3946
  }
3921
3947
  res.json(document);
3922
3948
  });
3923
- router.get("/api/missions/:slug/assignments/:aslug/decision-record/edit", async (req, res) => {
3949
+ router.get("/api/projects/:slug/assignments/:aslug/decision-record/edit", async (req, res) => {
3924
3950
  const slug = getParam(req.params.slug);
3925
3951
  const assignmentSlug = getParam(req.params.aslug);
3926
3952
  const document = await getEditableDocument(
3927
- missionsDir,
3953
+ projectsDir,
3928
3954
  "decision-record",
3929
3955
  slug,
3930
3956
  assignmentSlug
@@ -3935,7 +3961,7 @@ function createWriteRouter(missionsDir) {
3935
3961
  }
3936
3962
  res.json(document);
3937
3963
  });
3938
- router.post("/api/missions", async (req, res) => {
3964
+ router.post("/api/projects", async (req, res) => {
3939
3965
  try {
3940
3966
  const content = requireContent(req, res);
3941
3967
  if (!content) {
@@ -3956,52 +3982,50 @@ function createWriteRouter(missionsDir) {
3956
3982
  res.status(400).json({ error: `Invalid slug "${slug}". Must be lowercase and hyphen-separated.` });
3957
3983
  return;
3958
3984
  }
3959
- const missionDir = resolve7(missionsDir, slug);
3960
- if (await fileExists(missionDir)) {
3961
- res.status(409).json({ error: `Mission "${slug}" already exists` });
3985
+ const projectDir = resolve7(projectsDir, slug);
3986
+ if (await fileExists(projectDir)) {
3987
+ res.status(409).json({ error: `Project "${slug}" already exists` });
3962
3988
  return;
3963
3989
  }
3964
3990
  const title = fields.title;
3965
3991
  const timestamp = fields.created || nowTimestamp();
3966
- await ensureDir(resolve7(missionDir, "assignments"));
3967
- await ensureDir(resolve7(missionDir, "resources"));
3968
- await ensureDir(resolve7(missionDir, "memories"));
3969
- await writeFileForce(resolve7(missionDir, "mission.md"), content);
3992
+ await ensureDir(resolve7(projectDir, "assignments"));
3993
+ await ensureDir(resolve7(projectDir, "resources"));
3994
+ await ensureDir(resolve7(projectDir, "memories"));
3995
+ await writeFileForce(resolve7(projectDir, "project.md"), content);
3970
3996
  try {
3971
3997
  const companions = [
3972
- [resolve7(missionDir, "manifest.md"), renderManifest({ slug, timestamp })],
3973
- [resolve7(missionDir, "agent.md"), renderAgent({ slug, timestamp })],
3974
- [resolve7(missionDir, "claude.md"), renderClaude({ slug })],
3975
- [resolve7(missionDir, "_index-assignments.md"), renderIndexAssignments({ slug, title, timestamp })],
3976
- [resolve7(missionDir, "_index-plans.md"), renderIndexPlans({ slug, title, timestamp })],
3977
- [resolve7(missionDir, "_index-decisions.md"), renderIndexDecisions({ slug, title, timestamp })],
3978
- [resolve7(missionDir, "_status.md"), renderStatus({ slug, title, timestamp })],
3979
- [resolve7(missionDir, "resources", "_index.md"), renderResourcesIndex({ slug, title, timestamp })],
3980
- [resolve7(missionDir, "memories", "_index.md"), renderMemoriesIndex({ slug, title, timestamp })]
3998
+ [resolve7(projectDir, "manifest.md"), renderManifest({ slug, timestamp })],
3999
+ [resolve7(projectDir, "_index-assignments.md"), renderIndexAssignments({ slug, title, timestamp })],
4000
+ [resolve7(projectDir, "_index-plans.md"), renderIndexPlans({ slug, title, timestamp })],
4001
+ [resolve7(projectDir, "_index-decisions.md"), renderIndexDecisions({ slug, title, timestamp })],
4002
+ [resolve7(projectDir, "_status.md"), renderStatus({ slug, title, timestamp })],
4003
+ [resolve7(projectDir, "resources", "_index.md"), renderResourcesIndex({ slug, title, timestamp })],
4004
+ [resolve7(projectDir, "memories", "_index.md"), renderMemoriesIndex({ slug, title, timestamp })]
3981
4005
  ];
3982
4006
  for (const [filePath, fileContent] of companions) {
3983
4007
  await writeFileForce(filePath, fileContent);
3984
4008
  }
3985
4009
  } catch (companionError) {
3986
4010
  try {
3987
- await rm(missionDir, { recursive: true, force: true });
4011
+ await rm(projectDir, { recursive: true, force: true });
3988
4012
  } catch {
3989
4013
  }
3990
4014
  throw companionError;
3991
4015
  }
3992
4016
  res.status(201).json({ slug });
3993
4017
  } catch (error) {
3994
- console.error("Error creating mission:", error);
3995
- res.status(500).json({ error: `Failed to create mission: ${error.message}` });
4018
+ console.error("Error creating project:", error);
4019
+ res.status(500).json({ error: `Failed to create project: ${error.message}` });
3996
4020
  }
3997
4021
  });
3998
- router.post("/api/missions/:slug/assignments", async (req, res) => {
4022
+ router.post("/api/projects/:slug/assignments", async (req, res) => {
3999
4023
  try {
4000
- const missionSlug = getParam(req.params.slug);
4001
- const missionDir = resolve7(missionsDir, missionSlug);
4002
- const missionMdPath = resolve7(missionDir, "mission.md");
4003
- if (!await fileExists(missionMdPath)) {
4004
- res.status(404).json({ error: `Mission "${missionSlug}" not found` });
4024
+ const projectSlug = getParam(req.params.slug);
4025
+ const projectDir = resolve7(projectsDir, projectSlug);
4026
+ const projectMdPath = resolve7(projectDir, "project.md");
4027
+ if (!await fileExists(projectMdPath)) {
4028
+ res.status(404).json({ error: `Project "${projectSlug}" not found` });
4005
4029
  return;
4006
4030
  }
4007
4031
  const content = requireContent(req, res);
@@ -4029,20 +4053,18 @@ function createWriteRouter(missionsDir) {
4029
4053
  res.status(400).json({ error: `Invalid priority "${priority}". Must be low, medium, high, or critical.` });
4030
4054
  return;
4031
4055
  }
4032
- const assignmentDir = resolve7(missionDir, "assignments", assignmentSlug);
4056
+ const assignmentDir = resolve7(projectDir, "assignments", assignmentSlug);
4033
4057
  if (await fileExists(assignmentDir)) {
4034
4058
  res.status(409).json({
4035
- error: `Assignment "${assignmentSlug}" already exists in mission "${missionSlug}"`
4059
+ error: `Assignment "${assignmentSlug}" already exists in project "${projectSlug}"`
4036
4060
  });
4037
4061
  return;
4038
4062
  }
4039
- const title = fields.title;
4040
4063
  const timestamp = fields.created || nowTimestamp();
4041
4064
  await ensureDir(assignmentDir);
4042
4065
  await writeFileForce(resolve7(assignmentDir, "assignment.md"), content);
4043
4066
  try {
4044
4067
  const companions = [
4045
- [resolve7(assignmentDir, "plan.md"), renderPlan({ assignmentSlug, title, timestamp })],
4046
4068
  [resolve7(assignmentDir, "scratchpad.md"), renderScratchpad({ assignmentSlug, timestamp })],
4047
4069
  [resolve7(assignmentDir, "handoff.md"), renderHandoff({ assignmentSlug, timestamp })],
4048
4070
  [resolve7(assignmentDir, "decision-record.md"), renderDecisionRecord({ assignmentSlug, timestamp })]
@@ -4057,51 +4079,51 @@ function createWriteRouter(missionsDir) {
4057
4079
  }
4058
4080
  throw companionError;
4059
4081
  }
4060
- res.status(201).json({ slug: assignmentSlug, missionSlug });
4082
+ res.status(201).json({ slug: assignmentSlug, projectSlug });
4061
4083
  } catch (error) {
4062
4084
  console.error("Error creating assignment:", error);
4063
4085
  res.status(500).json({ error: `Failed to create assignment: ${error.message}` });
4064
4086
  }
4065
4087
  });
4066
- router.patch("/api/missions/:slug", async (req, res) => {
4088
+ router.patch("/api/projects/:slug", async (req, res) => {
4067
4089
  try {
4068
- const missionSlug = getParam(req.params.slug);
4069
- const missionPath = resolve7(missionsDir, missionSlug, "mission.md");
4070
- const currentContent = await readCurrentDocument(missionPath);
4090
+ const projectSlug = getParam(req.params.slug);
4091
+ const projectPath = resolve7(projectsDir, projectSlug, "project.md");
4092
+ const currentContent = await readCurrentDocument(projectPath);
4071
4093
  if (!currentContent) {
4072
- res.status(404).json({ error: `Mission "${missionSlug}" not found` });
4094
+ res.status(404).json({ error: `Project "${projectSlug}" not found` });
4073
4095
  return;
4074
4096
  }
4075
4097
  const nextContentRaw = requireContent(req, res);
4076
4098
  if (!nextContentRaw) {
4077
4099
  return;
4078
4100
  }
4079
- const current = parseMission(currentContent);
4080
- const next = parseMission(nextContentRaw);
4101
+ const current = parseProject(currentContent);
4102
+ const next = parseProject(nextContentRaw);
4081
4103
  if (!next.slug || !next.title) {
4082
- res.status(400).json({ error: "Mission content must include slug and title." });
4104
+ res.status(400).json({ error: "Project content must include slug and title." });
4083
4105
  return;
4084
4106
  }
4085
4107
  if (next.slug !== current.slug) {
4086
- res.status(400).json({ error: "Mission slug cannot be changed once created." });
4108
+ res.status(400).json({ error: "Project slug cannot be changed once created." });
4087
4109
  return;
4088
4110
  }
4089
4111
  const nextContent = setTopLevelField(nextContentRaw, "updated", nowTimestamp());
4090
- await writeFileForce(missionPath, nextContent);
4091
- const mission = await getMissionDetail(missionsDir, missionSlug);
4092
- res.json({ mission, content: nextContent });
4112
+ await writeFileForce(projectPath, nextContent);
4113
+ const project = await getProjectDetail(projectsDir, projectSlug);
4114
+ res.json({ project, content: nextContent });
4093
4115
  } catch (error) {
4094
- console.error("Error updating mission:", error);
4095
- res.status(500).json({ error: `Failed to update mission: ${error.message}` });
4116
+ console.error("Error updating project:", error);
4117
+ res.status(500).json({ error: `Failed to update project: ${error.message}` });
4096
4118
  }
4097
4119
  });
4098
- router.patch("/api/missions/:slug/assignments/:aslug", async (req, res) => {
4120
+ router.patch("/api/projects/:slug/assignments/:aslug", async (req, res) => {
4099
4121
  try {
4100
- const missionSlug = getParam(req.params.slug);
4122
+ const projectSlug = getParam(req.params.slug);
4101
4123
  const assignmentSlug = getParam(req.params.aslug);
4102
4124
  const assignmentPath = resolve7(
4103
- missionsDir,
4104
- missionSlug,
4125
+ projectsDir,
4126
+ projectSlug,
4105
4127
  "assignments",
4106
4128
  assignmentSlug,
4107
4129
  "assignment.md"
@@ -4131,20 +4153,20 @@ function createWriteRouter(missionsDir) {
4131
4153
  }
4132
4154
  nextContent = setTopLevelField(nextContent, "updated", nowTimestamp());
4133
4155
  await writeFileForce(assignmentPath, nextContent);
4134
- const assignment = await getAssignmentDetail(missionsDir, missionSlug, assignmentSlug);
4156
+ const assignment = await getAssignmentDetail(projectsDir, projectSlug, assignmentSlug);
4135
4157
  res.json({ assignment, content: nextContent });
4136
4158
  } catch (error) {
4137
4159
  console.error("Error updating assignment:", error);
4138
4160
  res.status(500).json({ error: `Failed to update assignment: ${error.message}` });
4139
4161
  }
4140
4162
  });
4141
- router.patch("/api/missions/:slug/assignments/:aslug/acceptance-criteria/:index", async (req, res) => {
4163
+ router.patch("/api/projects/:slug/assignments/:aslug/acceptance-criteria/:index", async (req, res) => {
4142
4164
  try {
4143
- const missionSlug = getParam(req.params.slug);
4165
+ const projectSlug = getParam(req.params.slug);
4144
4166
  const assignmentSlug = getParam(req.params.aslug);
4145
4167
  const assignmentPath = resolve7(
4146
- missionsDir,
4147
- missionSlug,
4168
+ projectsDir,
4169
+ projectSlug,
4148
4170
  "assignments",
4149
4171
  assignmentSlug,
4150
4172
  "assignment.md"
@@ -4167,20 +4189,20 @@ function createWriteRouter(missionsDir) {
4167
4189
  }
4168
4190
  const nextContent = setTopLevelField(result.content, "updated", nowTimestamp());
4169
4191
  await writeFileForce(assignmentPath, nextContent);
4170
- const assignment = await getAssignmentDetail(missionsDir, missionSlug, assignmentSlug);
4192
+ const assignment = await getAssignmentDetail(projectsDir, projectSlug, assignmentSlug);
4171
4193
  res.json({ assignment, content: nextContent });
4172
4194
  } catch (error) {
4173
4195
  console.error("Error toggling acceptance criterion:", error);
4174
4196
  res.status(500).json({ error: `Failed to toggle acceptance criterion: ${error.message}` });
4175
4197
  }
4176
4198
  });
4177
- router.patch("/api/missions/:slug/assignments/:aslug/plan", async (req, res) => {
4199
+ router.patch("/api/projects/:slug/assignments/:aslug/plan", async (req, res) => {
4178
4200
  try {
4179
- const missionSlug = getParam(req.params.slug);
4201
+ const projectSlug = getParam(req.params.slug);
4180
4202
  const assignmentSlug = getParam(req.params.aslug);
4181
4203
  const planPath = resolve7(
4182
- missionsDir,
4183
- missionSlug,
4204
+ projectsDir,
4205
+ projectSlug,
4184
4206
  "assignments",
4185
4207
  assignmentSlug,
4186
4208
  "plan.md"
@@ -4205,20 +4227,20 @@ function createWriteRouter(missionsDir) {
4205
4227
  }
4206
4228
  const nextContent = setTopLevelField(nextContentRaw, "updated", nowTimestamp());
4207
4229
  await writeFileForce(planPath, nextContent);
4208
- const assignment = await getAssignmentDetail(missionsDir, missionSlug, assignmentSlug);
4230
+ const assignment = await getAssignmentDetail(projectsDir, projectSlug, assignmentSlug);
4209
4231
  res.json({ assignment, content: nextContent });
4210
4232
  } catch (error) {
4211
4233
  console.error("Error updating plan:", error);
4212
4234
  res.status(500).json({ error: `Failed to update plan: ${error.message}` });
4213
4235
  }
4214
4236
  });
4215
- router.patch("/api/missions/:slug/assignments/:aslug/scratchpad", async (req, res) => {
4237
+ router.patch("/api/projects/:slug/assignments/:aslug/scratchpad", async (req, res) => {
4216
4238
  try {
4217
- const missionSlug = getParam(req.params.slug);
4239
+ const projectSlug = getParam(req.params.slug);
4218
4240
  const assignmentSlug = getParam(req.params.aslug);
4219
4241
  const scratchpadPath = resolve7(
4220
- missionsDir,
4221
- missionSlug,
4242
+ projectsDir,
4243
+ projectSlug,
4222
4244
  "assignments",
4223
4245
  assignmentSlug,
4224
4246
  "scratchpad.md"
@@ -4243,20 +4265,20 @@ function createWriteRouter(missionsDir) {
4243
4265
  }
4244
4266
  const nextContent = setTopLevelField(nextContentRaw, "updated", nowTimestamp());
4245
4267
  await writeFileForce(scratchpadPath, nextContent);
4246
- const assignment = await getAssignmentDetail(missionsDir, missionSlug, assignmentSlug);
4268
+ const assignment = await getAssignmentDetail(projectsDir, projectSlug, assignmentSlug);
4247
4269
  res.json({ assignment, content: nextContent });
4248
4270
  } catch (error) {
4249
4271
  console.error("Error updating scratchpad:", error);
4250
4272
  res.status(500).json({ error: `Failed to update scratchpad: ${error.message}` });
4251
4273
  }
4252
4274
  });
4253
- router.post("/api/missions/:slug/assignments/:aslug/handoff/entries", async (req, res) => {
4275
+ router.post("/api/projects/:slug/assignments/:aslug/handoff/entries", async (req, res) => {
4254
4276
  try {
4255
- const missionSlug = getParam(req.params.slug);
4277
+ const projectSlug = getParam(req.params.slug);
4256
4278
  const assignmentSlug = getParam(req.params.aslug);
4257
4279
  const handoffPath = resolve7(
4258
- missionsDir,
4259
- missionSlug,
4280
+ projectsDir,
4281
+ projectSlug,
4260
4282
  "assignments",
4261
4283
  assignmentSlug,
4262
4284
  "handoff.md"
@@ -4281,20 +4303,20 @@ function createWriteRouter(missionsDir) {
4281
4303
  "No handoffs recorded yet."
4282
4304
  );
4283
4305
  await writeFileForce(handoffPath, nextContent);
4284
- const assignment = await getAssignmentDetail(missionsDir, missionSlug, assignmentSlug);
4306
+ const assignment = await getAssignmentDetail(projectsDir, projectSlug, assignmentSlug);
4285
4307
  res.status(201).json({ assignment, content: nextContent });
4286
4308
  } catch (error) {
4287
4309
  console.error("Error appending handoff entry:", error);
4288
4310
  res.status(500).json({ error: `Failed to append handoff entry: ${error.message}` });
4289
4311
  }
4290
4312
  });
4291
- router.post("/api/missions/:slug/assignments/:aslug/decision-record/entries", async (req, res) => {
4313
+ router.post("/api/projects/:slug/assignments/:aslug/decision-record/entries", async (req, res) => {
4292
4314
  try {
4293
- const missionSlug = getParam(req.params.slug);
4315
+ const projectSlug = getParam(req.params.slug);
4294
4316
  const assignmentSlug = getParam(req.params.aslug);
4295
4317
  const decisionPath = resolve7(
4296
- missionsDir,
4297
- missionSlug,
4318
+ projectsDir,
4319
+ projectSlug,
4298
4320
  "assignments",
4299
4321
  assignmentSlug,
4300
4322
  "decision-record.md"
@@ -4319,19 +4341,19 @@ function createWriteRouter(missionsDir) {
4319
4341
  "No decisions recorded yet."
4320
4342
  );
4321
4343
  await writeFileForce(decisionPath, nextContent);
4322
- const assignment = await getAssignmentDetail(missionsDir, missionSlug, assignmentSlug);
4344
+ const assignment = await getAssignmentDetail(projectsDir, projectSlug, assignmentSlug);
4323
4345
  res.status(201).json({ assignment, content: nextContent });
4324
4346
  } catch (error) {
4325
4347
  console.error("Error appending decision entry:", error);
4326
4348
  res.status(500).json({ error: `Failed to append decision entry: ${error.message}` });
4327
4349
  }
4328
4350
  });
4329
- router.post("/api/missions/:slug/move-workspace", async (req, res) => {
4351
+ router.post("/api/projects/:slug/move-workspace", async (req, res) => {
4330
4352
  try {
4331
- const missionSlug = getParam(req.params.slug);
4332
- const missionPath = resolve7(missionsDir, missionSlug, "mission.md");
4333
- if (!await fileExists(missionPath)) {
4334
- res.status(404).json({ error: `Mission "${missionSlug}" not found` });
4353
+ const projectSlug = getParam(req.params.slug);
4354
+ const projectPath = resolve7(projectsDir, projectSlug, "project.md");
4355
+ if (!await fileExists(projectPath)) {
4356
+ res.status(404).json({ error: `Project "${projectSlug}" not found` });
4335
4357
  return;
4336
4358
  }
4337
4359
  const { workspace } = req.body || {};
@@ -4339,23 +4361,23 @@ function createWriteRouter(missionsDir) {
4339
4361
  res.status(400).json({ error: "workspace must be a non-empty string or null (for ungrouped)." });
4340
4362
  return;
4341
4363
  }
4342
- let content = await readFile6(missionPath, "utf-8");
4364
+ let content = await readFile6(projectPath, "utf-8");
4343
4365
  content = setTopLevelField(content, "workspace", workspace ?? null);
4344
4366
  content = setTopLevelField(content, "updated", nowTimestamp());
4345
- await writeFileForce(missionPath, content);
4346
- const mission = await getMissionDetail(missionsDir, missionSlug);
4347
- res.json({ mission });
4367
+ await writeFileForce(projectPath, content);
4368
+ const project = await getProjectDetail(projectsDir, projectSlug);
4369
+ res.json({ project });
4348
4370
  } catch (error) {
4349
- console.error("Error moving mission workspace:", error);
4371
+ console.error("Error moving project workspace:", error);
4350
4372
  res.status(500).json({ error: `Failed to move workspace: ${error.message}` });
4351
4373
  }
4352
4374
  });
4353
- router.post("/api/missions/:slug/status-override", async (req, res) => {
4375
+ router.post("/api/projects/:slug/status-override", async (req, res) => {
4354
4376
  try {
4355
- const missionSlug = getParam(req.params.slug);
4356
- const missionPath = resolve7(missionsDir, missionSlug, "mission.md");
4357
- if (!await fileExists(missionPath)) {
4358
- res.status(404).json({ error: `Mission "${missionSlug}" not found` });
4377
+ const projectSlug = getParam(req.params.slug);
4378
+ const projectPath = resolve7(projectsDir, projectSlug, "project.md");
4379
+ if (!await fileExists(projectPath)) {
4380
+ res.status(404).json({ error: `Project "${projectSlug}" not found` });
4359
4381
  return;
4360
4382
  }
4361
4383
  const { status } = req.body || {};
@@ -4365,24 +4387,24 @@ function createWriteRouter(missionsDir) {
4365
4387
  res.status(400).json({ error: `Invalid status. Must be one of: ${validStatuses.join(", ")}, or null to clear.` });
4366
4388
  return;
4367
4389
  }
4368
- let content = await readFile6(missionPath, "utf-8");
4390
+ let content = await readFile6(projectPath, "utf-8");
4369
4391
  content = setTopLevelField(content, "statusOverride", status ?? null);
4370
4392
  content = setTopLevelField(content, "updated", nowTimestamp());
4371
- await writeFileForce(missionPath, content);
4372
- const mission = await getMissionDetail(missionsDir, missionSlug);
4373
- res.json({ mission });
4393
+ await writeFileForce(projectPath, content);
4394
+ const project = await getProjectDetail(projectsDir, projectSlug);
4395
+ res.json({ project });
4374
4396
  } catch (error) {
4375
- console.error("Error setting mission status override:", error);
4397
+ console.error("Error setting project status override:", error);
4376
4398
  res.status(500).json({ error: `Failed to set status override: ${error.message}` });
4377
4399
  }
4378
4400
  });
4379
- router.post("/api/missions/:slug/assignments/:aslug/status-override", async (req, res) => {
4401
+ router.post("/api/projects/:slug/assignments/:aslug/status-override", async (req, res) => {
4380
4402
  try {
4381
- const missionSlug = getParam(req.params.slug);
4403
+ const projectSlug = getParam(req.params.slug);
4382
4404
  const assignmentSlug = getParam(req.params.aslug);
4383
4405
  const assignmentPath = resolve7(
4384
- missionsDir,
4385
- missionSlug,
4406
+ projectsDir,
4407
+ projectSlug,
4386
4408
  "assignments",
4387
4409
  assignmentSlug,
4388
4410
  "assignment.md"
@@ -4405,16 +4427,16 @@ function createWriteRouter(missionsDir) {
4405
4427
  content = setTopLevelField(content, "blockedReason", null);
4406
4428
  }
4407
4429
  await writeFileForce(assignmentPath, content);
4408
- const assignment = await getAssignmentDetail(missionsDir, missionSlug, assignmentSlug);
4430
+ const assignment = await getAssignmentDetail(projectsDir, projectSlug, assignmentSlug);
4409
4431
  res.json({ assignment });
4410
4432
  } catch (error) {
4411
4433
  console.error("Error overriding assignment status:", error);
4412
4434
  res.status(500).json({ error: `Failed to override status: ${error.message}` });
4413
4435
  }
4414
4436
  });
4415
- router.post("/api/missions/:slug/assignments/:aslug/transitions/:command", async (req, res) => {
4437
+ router.post("/api/projects/:slug/assignments/:aslug/transitions/:command", async (req, res) => {
4416
4438
  try {
4417
- const missionSlug = getParam(req.params.slug);
4439
+ const projectSlug = getParam(req.params.slug);
4418
4440
  const assignmentSlug = getParam(req.params.aslug);
4419
4441
  const command = req.params.command;
4420
4442
  const config = await getStatusConfig();
@@ -4423,14 +4445,14 @@ function createWriteRouter(missionsDir) {
4423
4445
  res.status(400).json({ error: `Unsupported transition command "${req.params.command}"` });
4424
4446
  return;
4425
4447
  }
4426
- const missionDir = resolve7(missionsDir, missionSlug);
4427
- const assignmentPath = resolve7(missionDir, "assignments", assignmentSlug, "assignment.md");
4448
+ const projectDir = resolve7(projectsDir, projectSlug);
4449
+ const assignmentPath = resolve7(projectDir, "assignments", assignmentSlug, "assignment.md");
4428
4450
  if (!await fileExists(assignmentPath)) {
4429
4451
  res.status(404).json({ error: "Assignment not found" });
4430
4452
  return;
4431
4453
  }
4432
4454
  const { reason } = req.body || {};
4433
- const result = await executeTransition(missionDir, assignmentSlug, command, {
4455
+ const result = await executeTransition(projectDir, assignmentSlug, command, {
4434
4456
  reason: typeof reason === "string" ? reason : void 0,
4435
4457
  transitionTable: config.custom ? config.transitionTable : void 0,
4436
4458
  terminalStatuses: config.custom ? config.terminalStatuses : void 0
@@ -4439,25 +4461,25 @@ function createWriteRouter(missionsDir) {
4439
4461
  res.status(400).json({ error: result.message });
4440
4462
  return;
4441
4463
  }
4442
- const assignment = await getAssignmentDetail(missionsDir, missionSlug, assignmentSlug);
4464
+ const assignment = await getAssignmentDetail(projectsDir, projectSlug, assignmentSlug);
4443
4465
  res.json({ assignment, transition: result });
4444
4466
  } catch (error) {
4445
4467
  console.error("Error running assignment transition:", error);
4446
4468
  res.status(500).json({ error: `Failed to transition assignment: ${error.message}` });
4447
4469
  }
4448
4470
  });
4449
- router.delete("/api/missions/:slug/assignments/:aslug", async (req, res) => {
4471
+ router.delete("/api/projects/:slug/assignments/:aslug", async (req, res) => {
4450
4472
  try {
4451
- const missionSlug = getParam(req.params.slug);
4473
+ const projectSlug = getParam(req.params.slug);
4452
4474
  const assignmentSlug = getParam(req.params.aslug);
4453
- const assignmentDir = resolve7(missionsDir, missionSlug, "assignments", assignmentSlug);
4475
+ const assignmentDir = resolve7(projectsDir, projectSlug, "assignments", assignmentSlug);
4454
4476
  const assignmentPath = resolve7(assignmentDir, "assignment.md");
4455
4477
  if (!await fileExists(assignmentPath)) {
4456
- res.status(404).json({ error: `Assignment "${assignmentSlug}" not found in mission "${missionSlug}"` });
4478
+ res.status(404).json({ error: `Assignment "${assignmentSlug}" not found in project "${projectSlug}"` });
4457
4479
  return;
4458
4480
  }
4459
4481
  await rm(assignmentDir, { recursive: true, force: true });
4460
- res.json({ deleted: assignmentSlug, missionSlug });
4482
+ res.json({ deleted: assignmentSlug, projectSlug });
4461
4483
  } catch (error) {
4462
4484
  console.error("Error deleting assignment:", error);
4463
4485
  res.status(500).json({ error: `Failed to delete assignment: ${error.message}` });
@@ -4470,11 +4492,11 @@ function createWriteRouter(missionsDir) {
4470
4492
  init_servers();
4471
4493
  init_scanner();
4472
4494
  import { Router as Router2 } from "express";
4473
- function createServersRouter(serversDir, missionsDir) {
4495
+ function createServersRouter(serversDir2, projectsDir) {
4474
4496
  const router = Router2();
4475
4497
  router.get("/", async (_req, res) => {
4476
4498
  try {
4477
- const result = await scanAllSessions(serversDir, missionsDir);
4499
+ const result = await scanAllSessions(serversDir2, projectsDir);
4478
4500
  res.json(result);
4479
4501
  } catch (error) {
4480
4502
  res.status(500).json({ error: error instanceof Error ? error.message : "Scan failed" });
@@ -4482,7 +4504,7 @@ function createServersRouter(serversDir, missionsDir) {
4482
4504
  });
4483
4505
  router.get("/:name", async (req, res) => {
4484
4506
  try {
4485
- const session = await scanSingleSession(serversDir, missionsDir, req.params.name);
4507
+ const session = await scanSingleSession(serversDir2, projectsDir, req.params.name);
4486
4508
  if (!session) {
4487
4509
  res.status(404).json({ error: "Session not found" });
4488
4510
  return;
@@ -4500,12 +4522,12 @@ function createServersRouter(serversDir, missionsDir) {
4500
4522
  return;
4501
4523
  }
4502
4524
  const sanitized = sanitizeSessionName(name);
4503
- const existing = await readSessionFile(serversDir, sanitized);
4525
+ const existing = await readSessionFile(serversDir2, sanitized);
4504
4526
  if (existing) {
4505
4527
  res.status(409).json({ error: `Session "${sanitized}" already registered` });
4506
4528
  return;
4507
4529
  }
4508
- await registerSession(serversDir, name);
4530
+ await registerSession(serversDir2, name);
4509
4531
  clearScanCache();
4510
4532
  res.status(201).json({ name: sanitized });
4511
4533
  } catch (error) {
@@ -4514,12 +4536,12 @@ function createServersRouter(serversDir, missionsDir) {
4514
4536
  });
4515
4537
  router.delete("/:name", async (req, res) => {
4516
4538
  try {
4517
- const data = await readSessionFile(serversDir, req.params.name);
4539
+ const data = await readSessionFile(serversDir2, req.params.name);
4518
4540
  if (!data) {
4519
4541
  res.status(404).json({ error: "Session not found" });
4520
4542
  return;
4521
4543
  }
4522
- await removeSession(serversDir, req.params.name);
4544
+ await removeSession(serversDir2, req.params.name);
4523
4545
  clearScanCache();
4524
4546
  res.json({ removed: req.params.name });
4525
4547
  } catch (error) {
@@ -4528,12 +4550,12 @@ function createServersRouter(serversDir, missionsDir) {
4528
4550
  });
4529
4551
  router.post("/refresh", async (_req, res) => {
4530
4552
  try {
4531
- const names = await listSessionFiles(serversDir);
4553
+ const names = await listSessionFiles(serversDir2);
4532
4554
  for (const name of names) {
4533
- await updateLastRefreshed(serversDir, name);
4555
+ await updateLastRefreshed(serversDir2, name);
4534
4556
  }
4535
4557
  clearScanCache();
4536
- const result = await scanAllSessions(serversDir, missionsDir, { bypassCache: true });
4558
+ const result = await scanAllSessions(serversDir2, projectsDir, { bypassCache: true });
4537
4559
  res.json(result);
4538
4560
  } catch (error) {
4539
4561
  res.status(500).json({ error: error instanceof Error ? error.message : "Refresh failed" });
@@ -4541,14 +4563,14 @@ function createServersRouter(serversDir, missionsDir) {
4541
4563
  });
4542
4564
  router.post("/:name/refresh", async (req, res) => {
4543
4565
  try {
4544
- const data = await readSessionFile(serversDir, req.params.name);
4566
+ const data = await readSessionFile(serversDir2, req.params.name);
4545
4567
  if (!data) {
4546
4568
  res.status(404).json({ error: "Session not found" });
4547
4569
  return;
4548
4570
  }
4549
- await updateLastRefreshed(serversDir, req.params.name);
4571
+ await updateLastRefreshed(serversDir2, req.params.name);
4550
4572
  clearScanCache();
4551
- const session = await scanSingleSession(serversDir, missionsDir, req.params.name);
4573
+ const session = await scanSingleSession(serversDir2, projectsDir, req.params.name);
4552
4574
  res.json(session);
4553
4575
  } catch (error) {
4554
4576
  res.status(500).json({ error: error instanceof Error ? error.message : "Refresh failed" });
@@ -4557,15 +4579,15 @@ function createServersRouter(serversDir, missionsDir) {
4557
4579
  router.patch("/:name/panes/:windowIndex/:paneIndex/assignment", async (req, res) => {
4558
4580
  try {
4559
4581
  const { name, windowIndex, paneIndex } = req.params;
4560
- const data = await readSessionFile(serversDir, name);
4582
+ const data = await readSessionFile(serversDir2, name);
4561
4583
  if (!data) {
4562
4584
  res.status(404).json({ error: "Session not found" });
4563
4585
  return;
4564
4586
  }
4565
4587
  const body = req.body;
4566
- if (body === null || body && body.mission && body.assignment) {
4588
+ if (body === null || body && body.project && body.assignment) {
4567
4589
  await setOverride(
4568
- serversDir,
4590
+ serversDir2,
4569
4591
  name,
4570
4592
  parseInt(windowIndex, 10),
4571
4593
  parseInt(paneIndex, 10),
@@ -4574,7 +4596,7 @@ function createServersRouter(serversDir, missionsDir) {
4574
4596
  clearScanCache();
4575
4597
  res.json({ updated: true });
4576
4598
  } else {
4577
- res.status(400).json({ error: "Body must be { mission, assignment } or null" });
4599
+ res.status(400).json({ error: "Body must be { project, assignment } or null" });
4578
4600
  }
4579
4601
  } catch (error) {
4580
4602
  res.status(500).json({ error: error instanceof Error ? error.message : "Update failed" });
@@ -4604,7 +4626,7 @@ var SCHEMA_VERSION = "2";
4604
4626
  var SCHEMA_SQL = `
4605
4627
  CREATE TABLE IF NOT EXISTS sessions (
4606
4628
  session_id TEXT PRIMARY KEY,
4607
- mission_slug TEXT,
4629
+ project_slug TEXT,
4608
4630
  assignment_slug TEXT,
4609
4631
  agent TEXT NOT NULL,
4610
4632
  started TEXT NOT NULL,
@@ -4615,8 +4637,8 @@ CREATE TABLE IF NOT EXISTS sessions (
4615
4637
  created_at TEXT NOT NULL DEFAULT (datetime('now')),
4616
4638
  updated_at TEXT NOT NULL DEFAULT (datetime('now'))
4617
4639
  );
4618
- CREATE INDEX IF NOT EXISTS idx_sessions_mission ON sessions(mission_slug);
4619
- CREATE INDEX IF NOT EXISTS idx_sessions_assignment ON sessions(mission_slug, assignment_slug);
4640
+ CREATE INDEX IF NOT EXISTS idx_sessions_project ON sessions(project_slug);
4641
+ CREATE INDEX IF NOT EXISTS idx_sessions_assignment ON sessions(project_slug, assignment_slug);
4620
4642
  CREATE INDEX IF NOT EXISTS idx_sessions_status ON sessions(status);
4621
4643
  CREATE TABLE IF NOT EXISTS meta (key TEXT PRIMARY KEY, value TEXT);
4622
4644
  `;
@@ -4635,7 +4657,7 @@ function initSessionDb(dbPath) {
4635
4657
  db.exec(`
4636
4658
  CREATE TABLE sessions_v2 (
4637
4659
  session_id TEXT PRIMARY KEY,
4638
- mission_slug TEXT,
4660
+ project_slug TEXT,
4639
4661
  assignment_slug TEXT,
4640
4662
  agent TEXT NOT NULL,
4641
4663
  started TEXT NOT NULL,
@@ -4646,11 +4668,11 @@ function initSessionDb(dbPath) {
4646
4668
  created_at TEXT NOT NULL DEFAULT (datetime('now')),
4647
4669
  updated_at TEXT NOT NULL DEFAULT (datetime('now'))
4648
4670
  );
4649
- INSERT INTO sessions_v2 SELECT session_id, mission_slug, assignment_slug, agent, started, ended, status, path, NULL, created_at, updated_at FROM sessions;
4671
+ INSERT INTO sessions_v2 SELECT session_id, project_slug, assignment_slug, agent, started, ended, status, path, NULL, created_at, updated_at FROM sessions;
4650
4672
  DROP TABLE sessions;
4651
4673
  ALTER TABLE sessions_v2 RENAME TO sessions;
4652
- CREATE INDEX IF NOT EXISTS idx_sessions_mission ON sessions(mission_slug);
4653
- CREATE INDEX IF NOT EXISTS idx_sessions_assignment ON sessions(mission_slug, assignment_slug);
4674
+ CREATE INDEX IF NOT EXISTS idx_sessions_project ON sessions(project_slug);
4675
+ CREATE INDEX IF NOT EXISTS idx_sessions_assignment ON sessions(project_slug, assignment_slug);
4654
4676
  CREATE INDEX IF NOT EXISTS idx_sessions_status ON sessions(status);
4655
4677
  UPDATE meta SET value = '2' WHERE key = 'schema_version';
4656
4678
  `);
@@ -4671,38 +4693,38 @@ function closeSessionDb() {
4671
4693
  db = null;
4672
4694
  }
4673
4695
  }
4674
- async function migrateFromMarkdown(missionsDir) {
4696
+ async function migrateFromMarkdown(projectsDir) {
4675
4697
  const database = getSessionDb();
4676
4698
  const count = database.prepare("SELECT COUNT(*) as count FROM sessions").get();
4677
4699
  if (count.count > 0) return 0;
4678
- if (!await fileExists(missionsDir)) return 0;
4679
- const entries = await readdir4(missionsDir, { withFileTypes: true });
4700
+ if (!await fileExists(projectsDir)) return 0;
4701
+ const entries = await readdir4(projectsDir, { withFileTypes: true });
4680
4702
  const allSessions = [];
4681
4703
  for (const entry of entries) {
4682
4704
  if (!entry.isDirectory()) continue;
4683
- const missionDir = resolve8(missionsDir, entry.name);
4684
- const indexPath = resolve8(missionDir, "_index-sessions.md");
4705
+ const projectDir = resolve8(projectsDir, entry.name);
4706
+ const indexPath = resolve8(projectDir, "_index-sessions.md");
4685
4707
  if (!await fileExists(indexPath)) continue;
4686
4708
  const sessions = await parseMarkdownSessionsIndex(indexPath, entry.name);
4687
4709
  allSessions.push(...sessions);
4688
4710
  }
4689
4711
  if (allSessions.length === 0) return 0;
4690
4712
  const insert = database.prepare(`
4691
- INSERT OR IGNORE INTO sessions (session_id, mission_slug, assignment_slug, agent, started, status, path)
4713
+ INSERT OR IGNORE INTO sessions (session_id, project_slug, assignment_slug, agent, started, status, path)
4692
4714
  VALUES (?, ?, ?, ?, ?, ?, ?)
4693
4715
  `);
4694
4716
  const insertAll = database.transaction((sessions) => {
4695
4717
  for (const s of sessions) {
4696
- insert.run(s.sessionId, s.missionSlug, s.assignmentSlug, s.agent, s.started, s.status, s.path);
4718
+ insert.run(s.sessionId, s.projectSlug, s.assignmentSlug, s.agent, s.started, s.status, s.path);
4697
4719
  }
4698
4720
  });
4699
4721
  insertAll(allSessions);
4700
4722
  console.log(`Migrated ${allSessions.length} sessions from markdown to SQLite.`);
4701
4723
  return allSessions.length;
4702
4724
  }
4703
- async function parseMarkdownSessionsIndex(filePath, missionSlug) {
4704
- const { readFile: readFile11 } = await import("fs/promises");
4705
- const raw = await readFile11(filePath, "utf-8");
4725
+ async function parseMarkdownSessionsIndex(filePath, projectSlug) {
4726
+ const { readFile: readFile12 } = await import("fs/promises");
4727
+ const raw = await readFile12(filePath, "utf-8");
4706
4728
  const sessions = [];
4707
4729
  const lines = raw.split("\n");
4708
4730
  let inTable = false;
@@ -4729,7 +4751,7 @@ async function parseMarkdownSessionsIndex(filePath, missionSlug) {
4729
4751
  started: cells[3],
4730
4752
  status: cells[4] || "active",
4731
4753
  path: cells[5],
4732
- missionSlug
4754
+ projectSlug
4733
4755
  });
4734
4756
  }
4735
4757
  }
@@ -4741,7 +4763,7 @@ async function parseMarkdownSessionsIndex(filePath, missionSlug) {
4741
4763
  function rowToSession(row) {
4742
4764
  return {
4743
4765
  sessionId: row.session_id,
4744
- missionSlug: row.mission_slug ?? null,
4766
+ projectSlug: row.project_slug ?? null,
4745
4767
  assignmentSlug: row.assignment_slug ?? null,
4746
4768
  agent: row.agent,
4747
4769
  started: row.started,
@@ -4751,14 +4773,14 @@ function rowToSession(row) {
4751
4773
  description: row.description ?? null
4752
4774
  };
4753
4775
  }
4754
- async function appendSession(_missionDir, session) {
4776
+ async function appendSession(_projectDir, session) {
4755
4777
  const db2 = getSessionDb();
4756
4778
  db2.prepare(`
4757
- INSERT INTO sessions (session_id, mission_slug, assignment_slug, agent, started, status, path, description)
4779
+ INSERT INTO sessions (session_id, project_slug, assignment_slug, agent, started, status, path, description)
4758
4780
  VALUES (?, ?, ?, ?, ?, ?, ?, ?)
4759
4781
  `).run(
4760
4782
  session.sessionId,
4761
- session.missionSlug ?? null,
4783
+ session.projectSlug ?? null,
4762
4784
  session.assignmentSlug ?? null,
4763
4785
  session.agent,
4764
4786
  session.started,
@@ -4767,7 +4789,7 @@ async function appendSession(_missionDir, session) {
4767
4789
  session.description ?? null
4768
4790
  );
4769
4791
  }
4770
- async function updateSessionStatus(_missionDir, sessionId, status) {
4792
+ async function updateSessionStatus(_projectDir, sessionId, status) {
4771
4793
  const db2 = getSessionDb();
4772
4794
  const isTerminal = status === "completed" || status === "stopped";
4773
4795
  const result = isTerminal ? db2.prepare(
@@ -4777,20 +4799,20 @@ async function updateSessionStatus(_missionDir, sessionId, status) {
4777
4799
  ).run(status, sessionId);
4778
4800
  return result.changes > 0;
4779
4801
  }
4780
- async function listAllSessions(_missionsDir) {
4802
+ async function listAllSessions(_projectsDir) {
4781
4803
  const db2 = getSessionDb();
4782
4804
  const rows = db2.prepare("SELECT * FROM sessions ORDER BY started DESC").all();
4783
4805
  return rows.map(rowToSession);
4784
4806
  }
4785
- async function listMissionSessions(_missionsDir, missionSlug, assignmentSlug) {
4807
+ async function listProjectSessions(_projectsDir, projectSlug, assignmentSlug) {
4786
4808
  const db2 = getSessionDb();
4787
4809
  if (assignmentSlug) {
4788
4810
  const rows2 = db2.prepare(
4789
- "SELECT * FROM sessions WHERE mission_slug = ? AND assignment_slug = ? ORDER BY started DESC"
4790
- ).all(missionSlug, assignmentSlug);
4811
+ "SELECT * FROM sessions WHERE project_slug = ? AND assignment_slug = ? ORDER BY started DESC"
4812
+ ).all(projectSlug, assignmentSlug);
4791
4813
  return rows2.map(rowToSession);
4792
4814
  }
4793
- const rows = db2.prepare("SELECT * FROM sessions WHERE mission_slug = ? ORDER BY started DESC").all(missionSlug);
4815
+ const rows = db2.prepare("SELECT * FROM sessions WHERE project_slug = ? ORDER BY started DESC").all(projectSlug);
4794
4816
  return rows.map(rowToSession);
4795
4817
  }
4796
4818
  async function deleteSessions(sessionIds) {
@@ -4801,34 +4823,34 @@ async function deleteSessions(sessionIds) {
4801
4823
  return result.changes;
4802
4824
  }
4803
4825
  var DONE_ASSIGNMENT_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "review"]);
4804
- async function readAssignmentStatus(missionDir, assignmentSlug) {
4805
- const assignmentPath = resolve9(missionDir, "assignments", assignmentSlug, "assignment.md");
4826
+ async function readAssignmentStatus(projectDir, assignmentSlug) {
4827
+ const assignmentPath = resolve9(projectDir, "assignments", assignmentSlug, "assignment.md");
4806
4828
  if (!await fileExists(assignmentPath)) return null;
4807
4829
  const raw = await readFile7(assignmentPath, "utf-8");
4808
4830
  const match = raw.match(/^status:\s*(.+)$/m);
4809
4831
  return match ? match[1].trim() : null;
4810
4832
  }
4811
- async function reconcileActiveSessions(missionsDir) {
4833
+ async function reconcileActiveSessions(projectsDir) {
4812
4834
  const db2 = getSessionDb();
4813
- const activeSessions = db2.prepare("SELECT * FROM sessions WHERE status = 'active' AND mission_slug IS NOT NULL AND assignment_slug IS NOT NULL").all();
4835
+ const activeSessions = db2.prepare("SELECT * FROM sessions WHERE status = 'active' AND project_slug IS NOT NULL AND assignment_slug IS NOT NULL").all();
4814
4836
  if (activeSessions.length === 0) return 0;
4815
4837
  const toCheck = /* @__PURE__ */ new Map();
4816
4838
  for (const session of activeSessions) {
4817
- const slugs = toCheck.get(session.mission_slug) ?? /* @__PURE__ */ new Set();
4839
+ const slugs = toCheck.get(session.project_slug) ?? /* @__PURE__ */ new Set();
4818
4840
  slugs.add(session.assignment_slug);
4819
- toCheck.set(session.mission_slug, slugs);
4841
+ toCheck.set(session.project_slug, slugs);
4820
4842
  }
4821
4843
  const assignmentStatuses = /* @__PURE__ */ new Map();
4822
- for (const [missionSlug, slugs] of toCheck) {
4823
- const missionDir = resolve9(missionsDir, missionSlug);
4844
+ for (const [projectSlug, slugs] of toCheck) {
4845
+ const projectDir = resolve9(projectsDir, projectSlug);
4824
4846
  for (const slug of slugs) {
4825
- const status = await readAssignmentStatus(missionDir, slug);
4826
- if (status) assignmentStatuses.set(`${missionSlug}/${slug}`, status);
4847
+ const status = await readAssignmentStatus(projectDir, slug);
4848
+ if (status) assignmentStatuses.set(`${projectSlug}/${slug}`, status);
4827
4849
  }
4828
4850
  }
4829
4851
  let totalUpdated = 0;
4830
4852
  for (const session of activeSessions) {
4831
- const key = `${session.mission_slug}/${session.assignment_slug}`;
4853
+ const key = `${session.project_slug}/${session.assignment_slug}`;
4832
4854
  const assignmentStatus = assignmentStatuses.get(key);
4833
4855
  if (!assignmentStatus || !DONE_ASSIGNMENT_STATUSES.has(assignmentStatus)) continue;
4834
4856
  const newStatus = assignmentStatus === "failed" ? "stopped" : "completed";
@@ -4840,28 +4862,28 @@ async function reconcileActiveSessions(missionsDir) {
4840
4862
 
4841
4863
  // src/dashboard/api-agent-sessions.ts
4842
4864
  init_fs();
4843
- function createAgentSessionsRouter(missionsDir, broadcast) {
4865
+ function createAgentSessionsRouter(projectsDir, broadcast) {
4844
4866
  const router = Router3();
4845
4867
  router.get("/", async (_req, res) => {
4846
4868
  try {
4847
- await reconcileActiveSessions(missionsDir);
4848
- const sessions = await listAllSessions(missionsDir);
4869
+ await reconcileActiveSessions(projectsDir);
4870
+ const sessions = await listAllSessions(projectsDir);
4849
4871
  res.json({ sessions, generatedAt: (/* @__PURE__ */ new Date()).toISOString() });
4850
4872
  } catch (error) {
4851
4873
  res.status(500).json({ error: error instanceof Error ? error.message : "Failed to list sessions" });
4852
4874
  }
4853
4875
  });
4854
- router.get("/:missionSlug", async (req, res) => {
4876
+ router.get("/:projectSlug", async (req, res) => {
4855
4877
  try {
4856
- const { missionSlug } = req.params;
4878
+ const { projectSlug } = req.params;
4857
4879
  const assignment = req.query.assignment;
4858
- const missionDir = resolve10(missionsDir, missionSlug);
4859
- if (!await fileExists(missionDir)) {
4860
- res.status(404).json({ error: `Mission "${missionSlug}" not found` });
4880
+ const projectDir = resolve10(projectsDir, projectSlug);
4881
+ if (!await fileExists(projectDir)) {
4882
+ res.status(404).json({ error: `Project "${projectSlug}" not found` });
4861
4883
  return;
4862
4884
  }
4863
- await reconcileActiveSessions(missionsDir);
4864
- const sessions = await listMissionSessions(missionsDir, missionSlug, assignment);
4885
+ await reconcileActiveSessions(projectsDir);
4886
+ const sessions = await listProjectSessions(projectsDir, projectSlug, assignment);
4865
4887
  res.json({ sessions, generatedAt: (/* @__PURE__ */ new Date()).toISOString() });
4866
4888
  } catch (error) {
4867
4889
  res.status(500).json({ error: error instanceof Error ? error.message : "Failed to list sessions" });
@@ -4869,21 +4891,21 @@ function createAgentSessionsRouter(missionsDir, broadcast) {
4869
4891
  });
4870
4892
  router.post("/", async (req, res) => {
4871
4893
  try {
4872
- const { missionSlug, assignmentSlug, agent, sessionId, path, description } = req.body;
4894
+ const { projectSlug, assignmentSlug, agent, sessionId, path, description } = req.body;
4873
4895
  if (!agent) {
4874
4896
  res.status(400).json({ error: "agent is required" });
4875
4897
  return;
4876
4898
  }
4877
- if (missionSlug) {
4878
- const missionDir = resolve10(missionsDir, missionSlug);
4879
- if (!await fileExists(missionDir)) {
4880
- res.status(404).json({ error: `Mission "${missionSlug}" not found` });
4899
+ if (projectSlug) {
4900
+ const projectDir = resolve10(projectsDir, projectSlug);
4901
+ if (!await fileExists(projectDir)) {
4902
+ res.status(404).json({ error: `Project "${projectSlug}" not found` });
4881
4903
  return;
4882
4904
  }
4883
4905
  }
4884
4906
  const id = sessionId || randomUUID2();
4885
4907
  const session = {
4886
- missionSlug: missionSlug || null,
4908
+ projectSlug: projectSlug || null,
4887
4909
  assignmentSlug: assignmentSlug || null,
4888
4910
  agent,
4889
4911
  sessionId: id,
@@ -4954,13 +4976,13 @@ init_parser();
4954
4976
  init_timestamp();
4955
4977
  import { resolve as resolve11 } from "path";
4956
4978
  import { readdir as readdir5, readFile as readFile8 } from "fs/promises";
4957
- async function rebuildPlaybookManifest(playbooksDir) {
4958
- if (!await fileExists(playbooksDir)) return;
4959
- const entries = await readdir5(playbooksDir, { withFileTypes: true });
4979
+ async function rebuildPlaybookManifest(playbooksDir2) {
4980
+ if (!await fileExists(playbooksDir2)) return;
4981
+ const entries = await readdir5(playbooksDir2, { withFileTypes: true });
4960
4982
  const rows = [];
4961
4983
  for (const entry of entries) {
4962
4984
  if (!entry.isFile() || !entry.name.endsWith(".md") || entry.name.startsWith("_") || entry.name === "manifest.md") continue;
4963
- const raw = await readFile8(resolve11(playbooksDir, entry.name), "utf-8");
4985
+ const raw = await readFile8(resolve11(playbooksDir2, entry.name), "utf-8");
4964
4986
  const parsed = parsePlaybook(raw);
4965
4987
  const slug = parsed.slug || entry.name.replace(/\.md$/, "");
4966
4988
  rows.push({
@@ -4990,15 +5012,15 @@ async function rebuildPlaybookManifest(playbooksDir) {
4990
5012
  }
4991
5013
  }
4992
5014
  lines.push("");
4993
- await writeFileForce(resolve11(playbooksDir, "manifest.md"), lines.join("\n"));
5015
+ await writeFileForce(resolve11(playbooksDir2, "manifest.md"), lines.join("\n"));
4994
5016
  }
4995
5017
 
4996
5018
  // src/dashboard/api-playbooks.ts
4997
- function createPlaybooksRouter(playbooksDir) {
5019
+ function createPlaybooksRouter(playbooksDir2) {
4998
5020
  const router = Router4();
4999
5021
  router.get("/", async (_req, res) => {
5000
5022
  try {
5001
- const playbooks = await listPlaybooks(playbooksDir);
5023
+ const playbooks = await listPlaybooks(playbooksDir2);
5002
5024
  res.json({ playbooks, generatedAt: (/* @__PURE__ */ new Date()).toISOString() });
5003
5025
  } catch (error) {
5004
5026
  res.status(500).json({ error: error instanceof Error ? error.message : "Failed to list playbooks" });
@@ -5019,7 +5041,7 @@ function createPlaybooksRouter(playbooksDir) {
5019
5041
  });
5020
5042
  router.get("/:slug", async (req, res) => {
5021
5043
  try {
5022
- const detail = await getPlaybookDetail(playbooksDir, req.params.slug);
5044
+ const detail = await getPlaybookDetail(playbooksDir2, req.params.slug);
5023
5045
  if (!detail) {
5024
5046
  res.status(404).json({ error: `Playbook "${req.params.slug}" not found` });
5025
5047
  return;
@@ -5031,7 +5053,7 @@ function createPlaybooksRouter(playbooksDir) {
5031
5053
  });
5032
5054
  router.get("/:slug/edit", async (req, res) => {
5033
5055
  try {
5034
- const filePath = resolve12(playbooksDir, `${req.params.slug}.md`);
5056
+ const filePath = resolve12(playbooksDir2, `${req.params.slug}.md`);
5035
5057
  if (!await fileExists(filePath)) {
5036
5058
  res.status(404).json({ error: `Playbook "${req.params.slug}" not found` });
5037
5059
  return;
@@ -5060,14 +5082,14 @@ function createPlaybooksRouter(playbooksDir) {
5060
5082
  res.status(400).json({ error: `Invalid or missing slug: "${slug}"` });
5061
5083
  return;
5062
5084
  }
5063
- await ensureDir(playbooksDir);
5064
- const filePath = resolve12(playbooksDir, `${slug}.md`);
5085
+ await ensureDir(playbooksDir2);
5086
+ const filePath = resolve12(playbooksDir2, `${slug}.md`);
5065
5087
  if (await fileExists(filePath)) {
5066
5088
  res.status(409).json({ error: `Playbook "${slug}" already exists` });
5067
5089
  return;
5068
5090
  }
5069
5091
  await writeFileForce(filePath, content);
5070
- await rebuildPlaybookManifest(playbooksDir);
5092
+ await rebuildPlaybookManifest(playbooksDir2);
5071
5093
  res.status(201).json({ slug, path: filePath });
5072
5094
  } catch (error) {
5073
5095
  res.status(500).json({ error: error instanceof Error ? error.message : "Failed to create playbook" });
@@ -5080,13 +5102,13 @@ function createPlaybooksRouter(playbooksDir) {
5080
5102
  res.status(400).json({ error: "content is required" });
5081
5103
  return;
5082
5104
  }
5083
- const filePath = resolve12(playbooksDir, `${req.params.slug}.md`);
5105
+ const filePath = resolve12(playbooksDir2, `${req.params.slug}.md`);
5084
5106
  if (!await fileExists(filePath)) {
5085
5107
  res.status(404).json({ error: `Playbook "${req.params.slug}" not found` });
5086
5108
  return;
5087
5109
  }
5088
5110
  await writeFileForce(filePath, content);
5089
- await rebuildPlaybookManifest(playbooksDir);
5111
+ await rebuildPlaybookManifest(playbooksDir2);
5090
5112
  res.json({ slug: req.params.slug, path: filePath });
5091
5113
  } catch (error) {
5092
5114
  res.status(500).json({ error: error instanceof Error ? error.message : "Failed to update playbook" });
@@ -5098,13 +5120,13 @@ function createPlaybooksRouter(playbooksDir) {
5098
5120
  res.status(403).json({ error: "The playbook manifest cannot be deleted" });
5099
5121
  return;
5100
5122
  }
5101
- const filePath = resolve12(playbooksDir, `${req.params.slug}.md`);
5123
+ const filePath = resolve12(playbooksDir2, `${req.params.slug}.md`);
5102
5124
  if (!await fileExists(filePath)) {
5103
5125
  res.status(404).json({ error: `Playbook "${req.params.slug}" not found` });
5104
5126
  return;
5105
5127
  }
5106
5128
  await unlink2(filePath);
5107
- await rebuildPlaybookManifest(playbooksDir);
5129
+ await rebuildPlaybookManifest(playbooksDir2);
5108
5130
  res.json({ deleted: req.params.slug });
5109
5131
  } catch (error) {
5110
5132
  res.status(500).json({ error: error instanceof Error ? error.message : "Failed to delete playbook" });
@@ -5134,7 +5156,7 @@ function withLock(workspace, fn) {
5134
5156
  }));
5135
5157
  return next;
5136
5158
  }
5137
- function createTodosRouter(todosDir, broadcast) {
5159
+ function createTodosRouter(todosDir2, broadcast) {
5138
5160
  const router = Router5();
5139
5161
  function broadcastUpdate() {
5140
5162
  broadcast({ type: "todos-updated", timestamp: (/* @__PURE__ */ new Date()).toISOString() });
@@ -5150,14 +5172,14 @@ function createTodosRouter(todosDir, broadcast) {
5150
5172
  router.param("workspace", validateWorkspace);
5151
5173
  router.get("/", async (_req, res) => {
5152
5174
  try {
5153
- await ensureDir(todosDir);
5154
- const files = await readdir6(todosDir).catch(() => []);
5175
+ await ensureDir(todosDir2);
5176
+ const files = await readdir6(todosDir2).catch(() => []);
5155
5177
  const workspaces = [];
5156
5178
  for (const file of files) {
5157
5179
  if (typeof file !== "string") continue;
5158
5180
  if (!file.endsWith(".md") || file.endsWith("-log.md")) continue;
5159
5181
  const workspace = file.replace(".md", "");
5160
- const checklist = await readChecklist(todosDir, workspace);
5182
+ const checklist = await readChecklist(todosDir2, workspace);
5161
5183
  workspaces.push({
5162
5184
  workspace: checklist.workspace,
5163
5185
  archiveInterval: checklist.archiveInterval,
@@ -5173,7 +5195,7 @@ function createTodosRouter(todosDir, broadcast) {
5173
5195
  router.get("/:workspace", async (req, res) => {
5174
5196
  try {
5175
5197
  const workspace = getWorkspaceParam(req.params.workspace);
5176
- const checklist = await readChecklist(todosDir, workspace);
5198
+ const checklist = await readChecklist(todosDir2, workspace);
5177
5199
  res.json({
5178
5200
  workspace: checklist.workspace,
5179
5201
  archiveInterval: checklist.archiveInterval,
@@ -5193,7 +5215,7 @@ function createTodosRouter(todosDir, broadcast) {
5193
5215
  return;
5194
5216
  }
5195
5217
  const item = await withLock(workspace, async () => {
5196
- const checklist = await readChecklist(todosDir, workspace);
5218
+ const checklist = await readChecklist(todosDir2, workspace);
5197
5219
  const existingIds = new Set(checklist.items.map((i) => i.id));
5198
5220
  const id = generateUniqueId(existingIds);
5199
5221
  const newItem = {
@@ -5204,7 +5226,7 @@ function createTodosRouter(todosDir, broadcast) {
5204
5226
  session: null
5205
5227
  };
5206
5228
  checklist.items.push(newItem);
5207
- await writeChecklist(todosDir, checklist);
5229
+ await writeChecklist(todosDir2, checklist);
5208
5230
  return newItem;
5209
5231
  });
5210
5232
  broadcastUpdate();
@@ -5222,7 +5244,7 @@ function createTodosRouter(todosDir, broadcast) {
5222
5244
  return;
5223
5245
  }
5224
5246
  const items = await withLock(workspace, async () => {
5225
- const checklist = await readChecklist(todosDir, workspace);
5247
+ const checklist = await readChecklist(todosDir2, workspace);
5226
5248
  const itemMap = new Map(checklist.items.map((i) => [i.id, i]));
5227
5249
  const reordered = [];
5228
5250
  for (const id of ids) {
@@ -5236,7 +5258,7 @@ function createTodosRouter(todosDir, broadcast) {
5236
5258
  reordered.push(item);
5237
5259
  }
5238
5260
  checklist.items = reordered;
5239
- await writeChecklist(todosDir, checklist);
5261
+ await writeChecklist(todosDir2, checklist);
5240
5262
  return reordered;
5241
5263
  });
5242
5264
  broadcastUpdate();
@@ -5247,7 +5269,7 @@ function createTodosRouter(todosDir, broadcast) {
5247
5269
  });
5248
5270
  router.get("/:workspace/log", async (req, res) => {
5249
5271
  try {
5250
- const log = await readLog(todosDir, getWorkspaceParam(req.params.workspace));
5272
+ const log = await readLog(todosDir2, getWorkspaceParam(req.params.workspace));
5251
5273
  res.json(log);
5252
5274
  } catch (error) {
5253
5275
  res.status(500).json({ error: error instanceof Error ? error.message : "Failed to get log" });
@@ -5256,12 +5278,12 @@ function createTodosRouter(todosDir, broadcast) {
5256
5278
  router.post("/:workspace/archive", async (req, res) => {
5257
5279
  try {
5258
5280
  const { archivePath: archivePath2 } = await Promise.resolve().then(() => (init_parser2(), parser_exports));
5259
- const { resolve: resolve15 } = await import("path");
5260
- const { readFile: readFile11 } = await import("fs/promises");
5281
+ const { resolve: resolve16 } = await import("path");
5282
+ const { readFile: readFile12 } = await import("fs/promises");
5261
5283
  const { writeFileForce: writeFileForce2 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
5262
5284
  const workspace = getWorkspaceParam(req.params.workspace);
5263
- const checklist = await readChecklist(todosDir, workspace);
5264
- const log = await readLog(todosDir, workspace);
5285
+ const checklist = await readChecklist(todosDir2, workspace);
5286
+ const log = await readLog(todosDir2, workspace);
5265
5287
  const completedIds = new Set(
5266
5288
  checklist.items.filter((i) => i.status === "completed").map((i) => i.id)
5267
5289
  );
@@ -5272,11 +5294,11 @@ function createTodosRouter(todosDir, broadcast) {
5272
5294
  const toArchive = log.entries.filter(
5273
5295
  (e) => e.itemIds.every((id) => completedIds.has(id))
5274
5296
  );
5275
- const archFile = archivePath2(todosDir, workspace, checklist.archiveInterval);
5276
- await ensureDir(resolve15(todosDir, "archive"));
5297
+ const archFile = archivePath2(todosDir2, workspace, checklist.archiveInterval);
5298
+ await ensureDir(resolve16(todosDir2, "archive"));
5277
5299
  let archContent = "";
5278
5300
  if (await fileExists(archFile)) {
5279
- archContent = await readFile11(archFile, "utf-8");
5301
+ archContent = await readFile12(archFile, "utf-8");
5280
5302
  archContent = archContent.trimEnd() + "\n\n";
5281
5303
  } else {
5282
5304
  archContent = `---
@@ -5310,7 +5332,7 @@ workspace: ${workspace}
5310
5332
  }
5311
5333
  await writeFileForce2(archFile, archContent);
5312
5334
  checklist.items = checklist.items.filter((i) => !completedIds.has(i.id));
5313
- await writeChecklist(todosDir, checklist);
5335
+ await writeChecklist(todosDir2, checklist);
5314
5336
  broadcastUpdate();
5315
5337
  res.json({ archived: completedIds.size, logEntries: toArchive.length });
5316
5338
  } catch (error) {
@@ -5319,7 +5341,7 @@ workspace: ${workspace}
5319
5341
  });
5320
5342
  router.get("/:workspace/log/:id", async (req, res) => {
5321
5343
  try {
5322
- const log = await readLog(todosDir, getWorkspaceParam(req.params.workspace));
5344
+ const log = await readLog(todosDir2, getWorkspaceParam(req.params.workspace));
5323
5345
  const entries = log.entries.filter((e) => e.itemIds.includes(req.params.id));
5324
5346
  res.json({ workspace: log.workspace, entries });
5325
5347
  } catch (error) {
@@ -5329,13 +5351,13 @@ workspace: ${workspace}
5329
5351
  router.get("/:workspace/:id", async (req, res) => {
5330
5352
  try {
5331
5353
  const workspace = getWorkspaceParam(req.params.workspace);
5332
- const checklist = await readChecklist(todosDir, workspace);
5354
+ const checklist = await readChecklist(todosDir2, workspace);
5333
5355
  const item = checklist.items.find((i) => i.id === req.params.id);
5334
5356
  if (!item) {
5335
5357
  res.status(404).json({ error: `Todo "${req.params.id}" not found` });
5336
5358
  return;
5337
5359
  }
5338
- const log = await readLog(todosDir, workspace);
5360
+ const log = await readLog(todosDir2, workspace);
5339
5361
  const logEntries = log.entries.filter((e) => e.itemIds.includes(req.params.id));
5340
5362
  res.json({ ...item, log: logEntries });
5341
5363
  } catch (error) {
@@ -5346,12 +5368,12 @@ workspace: ${workspace}
5346
5368
  try {
5347
5369
  const workspace = getWorkspaceParam(req.params.workspace);
5348
5370
  const result = await withLock(workspace, async () => {
5349
- const checklist = await readChecklist(todosDir, workspace);
5371
+ const checklist = await readChecklist(todosDir2, workspace);
5350
5372
  const item = checklist.items.find((i) => i.id === req.params.id);
5351
5373
  if (!item) return null;
5352
5374
  if (req.body.description !== void 0) item.description = req.body.description;
5353
5375
  if (Array.isArray(req.body.tags)) item.tags = req.body.tags;
5354
- await writeChecklist(todosDir, checklist);
5376
+ await writeChecklist(todosDir2, checklist);
5355
5377
  return { ...item };
5356
5378
  });
5357
5379
  if (!result) {
@@ -5368,11 +5390,11 @@ workspace: ${workspace}
5368
5390
  try {
5369
5391
  const workspace = getWorkspaceParam(req.params.workspace);
5370
5392
  const deleted = await withLock(workspace, async () => {
5371
- const checklist = await readChecklist(todosDir, workspace);
5393
+ const checklist = await readChecklist(todosDir2, workspace);
5372
5394
  const idx = checklist.items.findIndex((i) => i.id === req.params.id);
5373
5395
  if (idx === -1) return false;
5374
5396
  checklist.items.splice(idx, 1);
5375
- await writeChecklist(todosDir, checklist);
5397
+ await writeChecklist(todosDir2, checklist);
5376
5398
  return true;
5377
5399
  });
5378
5400
  if (!deleted) {
@@ -5389,13 +5411,13 @@ workspace: ${workspace}
5389
5411
  try {
5390
5412
  const workspace = getWorkspaceParam(req.params.workspace);
5391
5413
  const result = await withLock(workspace, async () => {
5392
- const checklist = await readChecklist(todosDir, workspace);
5414
+ const checklist = await readChecklist(todosDir2, workspace);
5393
5415
  const item = checklist.items.find((i) => i.id === req.params.id);
5394
5416
  if (!item) return { error: "not_found" };
5395
5417
  if (item.status === "in_progress") return { error: "conflict", session: item.session };
5396
5418
  item.status = "in_progress";
5397
5419
  item.session = req.body.session || null;
5398
- await writeChecklist(todosDir, checklist);
5420
+ await writeChecklist(todosDir2, checklist);
5399
5421
  return { item: { ...item } };
5400
5422
  });
5401
5423
  if ("error" in result) {
@@ -5416,12 +5438,12 @@ workspace: ${workspace}
5416
5438
  try {
5417
5439
  const workspace = getWorkspaceParam(req.params.workspace);
5418
5440
  const result = await withLock(workspace, async () => {
5419
- const checklist = await readChecklist(todosDir, workspace);
5441
+ const checklist = await readChecklist(todosDir2, workspace);
5420
5442
  const item = checklist.items.find((i) => i.id === req.params.id);
5421
5443
  if (!item) return null;
5422
5444
  item.status = "completed";
5423
5445
  item.session = null;
5424
- await writeChecklist(todosDir, checklist);
5446
+ await writeChecklist(todosDir2, checklist);
5425
5447
  const entry = {
5426
5448
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
5427
5449
  itemIds: [item.id],
@@ -5432,7 +5454,7 @@ workspace: ${workspace}
5432
5454
  blockers: null,
5433
5455
  status: null
5434
5456
  };
5435
- await appendLogEntry2(todosDir, workspace, entry);
5457
+ await appendLogEntry2(todosDir2, workspace, entry);
5436
5458
  return { ...item };
5437
5459
  });
5438
5460
  if (!result) {
@@ -5450,12 +5472,12 @@ workspace: ${workspace}
5450
5472
  const reason = req.body.reason || null;
5451
5473
  const workspace = getWorkspaceParam(req.params.workspace);
5452
5474
  const result = await withLock(workspace, async () => {
5453
- const checklist = await readChecklist(todosDir, workspace);
5475
+ const checklist = await readChecklist(todosDir2, workspace);
5454
5476
  const item = checklist.items.find((i) => i.id === req.params.id);
5455
5477
  if (!item) return null;
5456
5478
  item.status = "blocked";
5457
5479
  item.session = null;
5458
- await writeChecklist(todosDir, checklist);
5480
+ await writeChecklist(todosDir2, checklist);
5459
5481
  const entry = {
5460
5482
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
5461
5483
  itemIds: [item.id],
@@ -5466,7 +5488,7 @@ workspace: ${workspace}
5466
5488
  blockers: reason,
5467
5489
  status: "blocked"
5468
5490
  };
5469
- await appendLogEntry2(todosDir, workspace, entry);
5491
+ await appendLogEntry2(todosDir2, workspace, entry);
5470
5492
  return { ...item };
5471
5493
  });
5472
5494
  if (!result) {
@@ -5483,12 +5505,12 @@ workspace: ${workspace}
5483
5505
  try {
5484
5506
  const workspace = getWorkspaceParam(req.params.workspace);
5485
5507
  const result = await withLock(workspace, async () => {
5486
- const checklist = await readChecklist(todosDir, workspace);
5508
+ const checklist = await readChecklist(todosDir2, workspace);
5487
5509
  const item = checklist.items.find((i) => i.id === req.params.id);
5488
5510
  if (!item) return null;
5489
5511
  item.status = "open";
5490
5512
  item.session = null;
5491
- await writeChecklist(todosDir, checklist);
5513
+ await writeChecklist(todosDir2, checklist);
5492
5514
  return { ...item };
5493
5515
  });
5494
5516
  if (!result) {
@@ -5505,12 +5527,12 @@ workspace: ${workspace}
5505
5527
  try {
5506
5528
  const workspace = getWorkspaceParam(req.params.workspace);
5507
5529
  const result = await withLock(workspace, async () => {
5508
- const checklist = await readChecklist(todosDir, workspace);
5530
+ const checklist = await readChecklist(todosDir2, workspace);
5509
5531
  const item = checklist.items.find((i) => i.id === req.params.id);
5510
5532
  if (!item) return null;
5511
5533
  item.status = "open";
5512
5534
  item.session = null;
5513
- await writeChecklist(todosDir, checklist);
5535
+ await writeChecklist(todosDir2, checklist);
5514
5536
  return { ...item };
5515
5537
  });
5516
5538
  if (!result) {
@@ -5526,6 +5548,411 @@ workspace: ${workspace}
5526
5548
  return router;
5527
5549
  }
5528
5550
 
5551
+ // src/dashboard/api-backup.ts
5552
+ init_config2();
5553
+ import { Router as Router6 } from "express";
5554
+
5555
+ // src/utils/github-backup.ts
5556
+ init_paths();
5557
+ init_fs();
5558
+ init_config2();
5559
+ import { execFile as execFile2 } from "child_process";
5560
+ import { promisify as promisify2 } from "util";
5561
+ import { cp, mkdtemp, rm as rm2, readFile as readFile11, writeFile as writeFile3, unlink as unlink3, stat, open, rename as rename2 } from "fs/promises";
5562
+ import { resolve as resolve14, join as join2 } from "path";
5563
+ import { tmpdir } from "os";
5564
+ var exec2 = promisify2(execFile2);
5565
+ var VALID_CATEGORIES = ["projects", "playbooks", "todos", "servers", "config"];
5566
+ var LOCK_FILE_NAME = ".backup-lock";
5567
+ function parseCategoriesStrict(cats) {
5568
+ const unknown = [];
5569
+ const valid = [];
5570
+ for (const cat of cats) {
5571
+ if (VALID_CATEGORIES.includes(cat)) {
5572
+ valid.push(cat);
5573
+ } else {
5574
+ unknown.push(cat);
5575
+ }
5576
+ }
5577
+ if (unknown.length > 0) {
5578
+ throw new Error(
5579
+ `Unknown categor${unknown.length === 1 ? "y" : "ies"}: ${unknown.map((c) => `"${c}"`).join(", ")}. Valid: ${VALID_CATEGORIES.join(", ")}`
5580
+ );
5581
+ }
5582
+ return valid;
5583
+ }
5584
+ function validateRepoUrl(url) {
5585
+ if (!url || typeof url !== "string") return false;
5586
+ const trimmed = url.trim();
5587
+ return trimmed.startsWith("https://") || trimmed.startsWith("git@");
5588
+ }
5589
+ async function resolveCategoryPath(category) {
5590
+ switch (category) {
5591
+ case "projects": {
5592
+ const config = await readConfig();
5593
+ return { sourcePath: config.defaultProjectDir, repoPath: "projects", isFile: false };
5594
+ }
5595
+ case "playbooks":
5596
+ return { sourcePath: playbooksDir(), repoPath: "playbooks", isFile: false };
5597
+ case "todos":
5598
+ return { sourcePath: todosDir(), repoPath: "todos", isFile: false };
5599
+ case "servers":
5600
+ return { sourcePath: serversDir(), repoPath: "servers", isFile: false };
5601
+ case "config":
5602
+ return { sourcePath: resolve14(syntaurRoot(), "config.md"), repoPath: "config.md", isFile: true };
5603
+ }
5604
+ }
5605
+ async function checkGitInstalled() {
5606
+ try {
5607
+ await exec2("git", ["--version"]);
5608
+ } catch {
5609
+ throw new Error("git is not installed or not on PATH. Install git and try again.");
5610
+ }
5611
+ }
5612
+ async function acquireLock() {
5613
+ const lockPath = resolve14(syntaurRoot(), LOCK_FILE_NAME);
5614
+ await ensureDir(syntaurRoot());
5615
+ try {
5616
+ const handle = await open(lockPath, "wx");
5617
+ await handle.write(String(process.pid));
5618
+ await handle.close();
5619
+ return lockPath;
5620
+ } catch (err) {
5621
+ if (err.code === "EEXIST") {
5622
+ const pid = await readFile11(lockPath, "utf-8").catch(() => "");
5623
+ throw new Error(
5624
+ `Backup operation already in progress (lock file at ${lockPath}, pid ${pid.trim() || "unknown"}). If stale, delete the file and retry.`
5625
+ );
5626
+ }
5627
+ throw err;
5628
+ }
5629
+ }
5630
+ async function releaseLock(lockPath) {
5631
+ try {
5632
+ await unlink3(lockPath);
5633
+ } catch {
5634
+ }
5635
+ }
5636
+ async function runGit(args, cwd) {
5637
+ return exec2("git", args, { cwd });
5638
+ }
5639
+ async function cloneOrInit(repoUrl, destDir) {
5640
+ try {
5641
+ await exec2("git", ["clone", "--depth", "1", repoUrl, destDir]);
5642
+ } catch (error) {
5643
+ const message = error instanceof Error ? error.message : String(error);
5644
+ if (message.includes("Repository not found") || message.includes("does not appear to be a git repository")) {
5645
+ throw new Error(`Repository not found or inaccessible: ${repoUrl}. Check URL and credentials.`);
5646
+ }
5647
+ if (message.includes("Authentication failed") || message.includes("could not read Username")) {
5648
+ throw new Error(`Authentication failed for ${repoUrl}. Check SSH keys or credentials.`);
5649
+ }
5650
+ throw new Error(`git clone failed: ${message}`);
5651
+ }
5652
+ }
5653
+ async function copyRecursive(src, dest) {
5654
+ if (!await fileExists(src)) return;
5655
+ const s = await stat(src);
5656
+ if (s.isDirectory()) {
5657
+ await ensureDir(dest);
5658
+ await cp(src, dest, { recursive: true, force: true });
5659
+ } else {
5660
+ await ensureDir(resolve14(dest, ".."));
5661
+ await cp(src, dest, { force: true });
5662
+ }
5663
+ }
5664
+ function resolveCategoriesStrict(csv) {
5665
+ const parts = csv.split(",").map((s) => s.trim()).filter(Boolean);
5666
+ return parseCategoriesStrict(parts);
5667
+ }
5668
+ async function readSanitizedConfig(configPath) {
5669
+ const content = await readFile11(configPath, "utf-8");
5670
+ return content.replace(/^(\s*lastBackup:\s*).*$/m, "$1null").replace(/^(\s*lastRestore:\s*).*$/m, "$1null");
5671
+ }
5672
+ async function backupToGithub(overrides) {
5673
+ await checkGitInstalled();
5674
+ const config = await readConfig();
5675
+ const rawRepo = overrides?.repo ?? config.backup?.repo ?? null;
5676
+ if (!rawRepo) {
5677
+ throw new Error("No backup repo configured. Set it via `syntaur backup config --repo <url>` or the dashboard.");
5678
+ }
5679
+ const repo = rawRepo.trim();
5680
+ if (!validateRepoUrl(repo)) {
5681
+ throw new Error(`Invalid repo URL: "${rawRepo}". Must start with https:// or git@.`);
5682
+ }
5683
+ const categoriesCsv = config.backup?.categories ?? "projects, playbooks, todos, servers, config";
5684
+ const categories = overrides?.categories ?? resolveCategoriesStrict(categoriesCsv);
5685
+ if (categories.length === 0) {
5686
+ throw new Error("No valid backup categories selected.");
5687
+ }
5688
+ const lockPath = await acquireLock();
5689
+ let tmpDir = null;
5690
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
5691
+ try {
5692
+ tmpDir = await mkdtemp(join2(tmpdir(), "syntaur-backup-"));
5693
+ await cloneOrInit(repo, tmpDir);
5694
+ for (const category of categories) {
5695
+ const { sourcePath, repoPath, isFile } = await resolveCategoryPath(category);
5696
+ const destPath = join2(tmpDir, repoPath);
5697
+ if (isFile) {
5698
+ await rm2(destPath, { force: true });
5699
+ } else {
5700
+ await rm2(destPath, { recursive: true, force: true });
5701
+ }
5702
+ if (!await fileExists(sourcePath)) {
5703
+ console.warn(`Category "${category}": no local data at ${sourcePath}; backup will reflect deletion.`);
5704
+ continue;
5705
+ }
5706
+ if (category === "config") {
5707
+ const sanitized = await readSanitizedConfig(sourcePath);
5708
+ await ensureDir(resolve14(destPath, ".."));
5709
+ await writeFile3(destPath, sanitized, "utf-8");
5710
+ } else {
5711
+ await copyRecursive(sourcePath, destPath);
5712
+ }
5713
+ }
5714
+ await runGit(["add", "-A"], tmpDir);
5715
+ const { stdout: status } = await runGit(["status", "--porcelain"], tmpDir);
5716
+ if (!status.trim()) {
5717
+ await updateBackupConfig({ lastBackup: timestamp }).catch(() => {
5718
+ });
5719
+ return {
5720
+ success: true,
5721
+ timestamp,
5722
+ message: "No changes to back up.",
5723
+ committed: false
5724
+ };
5725
+ }
5726
+ try {
5727
+ await runGit(["config", "user.email", "syntaur@local"], tmpDir);
5728
+ await runGit(["config", "user.name", "Syntaur Backup"], tmpDir);
5729
+ } catch {
5730
+ }
5731
+ await runGit(["commit", "-m", `Syntaur backup ${timestamp}`], tmpDir);
5732
+ try {
5733
+ await runGit(["push"], tmpDir);
5734
+ } catch (error) {
5735
+ const message = error instanceof Error ? error.message : String(error);
5736
+ if (message.includes("non-fast-forward") || message.includes("rejected")) {
5737
+ throw new Error("Push rejected (non-fast-forward). Pull and resolve manually, or delete remote contents.");
5738
+ }
5739
+ if (message.includes("Authentication") || message.includes("could not read Username")) {
5740
+ throw new Error("Push authentication failed. Check SSH keys or credentials.");
5741
+ }
5742
+ throw new Error(`git push failed: ${message}`);
5743
+ }
5744
+ await updateBackupConfig({ lastBackup: timestamp }).catch(() => {
5745
+ });
5746
+ return {
5747
+ success: true,
5748
+ timestamp,
5749
+ message: `Backed up ${categories.length} categor${categories.length === 1 ? "y" : "ies"} to ${repo}.`,
5750
+ committed: true
5751
+ };
5752
+ } finally {
5753
+ if (tmpDir) {
5754
+ await rm2(tmpDir, { recursive: true, force: true }).catch(() => {
5755
+ });
5756
+ }
5757
+ await releaseLock(lockPath);
5758
+ }
5759
+ }
5760
+ async function safeRestoreCategory(localPath, repoSrcPath, isFile) {
5761
+ if (isFile) {
5762
+ await ensureDir(resolve14(localPath, ".."));
5763
+ await cp(repoSrcPath, localPath, { force: true });
5764
+ return;
5765
+ }
5766
+ const stagingPath = `${localPath}.syntaur-restore-staging`;
5767
+ const backupPath = `${localPath}.syntaur-restore-backup`;
5768
+ await rm2(stagingPath, { recursive: true, force: true });
5769
+ const backupExistsBefore = await fileExists(backupPath);
5770
+ const localExistsBefore = await fileExists(localPath);
5771
+ if (backupExistsBefore) {
5772
+ if (!localExistsBefore) {
5773
+ await rename2(backupPath, localPath);
5774
+ } else {
5775
+ throw new Error(
5776
+ `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.`
5777
+ );
5778
+ }
5779
+ }
5780
+ let localMovedAside = false;
5781
+ try {
5782
+ await cp(repoSrcPath, stagingPath, { recursive: true, force: true });
5783
+ const localExists = await fileExists(localPath);
5784
+ if (localExists) {
5785
+ await rename2(localPath, backupPath);
5786
+ localMovedAside = true;
5787
+ }
5788
+ await rename2(stagingPath, localPath);
5789
+ await rm2(backupPath, { recursive: true, force: true }).catch(() => {
5790
+ });
5791
+ } catch (err) {
5792
+ if (localMovedAside && await fileExists(backupPath)) {
5793
+ await rename2(backupPath, localPath).catch(() => {
5794
+ });
5795
+ }
5796
+ await rm2(stagingPath, { recursive: true, force: true }).catch(() => {
5797
+ });
5798
+ throw err;
5799
+ }
5800
+ }
5801
+ async function restoreFromGithub(overrides) {
5802
+ await checkGitInstalled();
5803
+ const config = await readConfig();
5804
+ const rawRepo = overrides?.repo ?? config.backup?.repo ?? null;
5805
+ if (!rawRepo) {
5806
+ throw new Error("No backup repo configured.");
5807
+ }
5808
+ const repo = rawRepo.trim();
5809
+ if (!validateRepoUrl(repo)) {
5810
+ throw new Error(`Invalid repo URL: "${rawRepo}".`);
5811
+ }
5812
+ const categoriesCsv = config.backup?.categories ?? "projects, playbooks, todos, servers, config";
5813
+ const categories = overrides?.categories ?? resolveCategoriesStrict(categoriesCsv);
5814
+ if (categories.length === 0) {
5815
+ throw new Error("No valid restore categories selected.");
5816
+ }
5817
+ const lockPath = await acquireLock();
5818
+ let tmpDir = null;
5819
+ const restored = [];
5820
+ const failed = [];
5821
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
5822
+ try {
5823
+ await updateBackupConfig({ lastRestore: timestamp });
5824
+ tmpDir = await mkdtemp(join2(tmpdir(), "syntaur-restore-"));
5825
+ await cloneOrInit(repo, tmpDir);
5826
+ for (const category of categories) {
5827
+ if (category === "config") {
5828
+ console.warn('Skipping "config" on restore (would overwrite local backup settings).');
5829
+ continue;
5830
+ }
5831
+ try {
5832
+ const { sourcePath: localPath, repoPath, isFile } = await resolveCategoryPath(category);
5833
+ const repoSrcPath = join2(tmpDir, repoPath);
5834
+ if (!await fileExists(repoSrcPath)) {
5835
+ console.warn(`Category "${category}" not found in backup repo, skipping.`);
5836
+ continue;
5837
+ }
5838
+ await safeRestoreCategory(localPath, repoSrcPath, isFile);
5839
+ restored.push(category);
5840
+ } catch (error) {
5841
+ const msg = error instanceof Error ? error.message : String(error);
5842
+ console.error(`Failed to restore "${category}": ${msg}`);
5843
+ failed.push(category);
5844
+ }
5845
+ }
5846
+ const success = failed.length === 0;
5847
+ return {
5848
+ success,
5849
+ timestamp,
5850
+ message: success ? `Restored ${restored.length} categor${restored.length === 1 ? "y" : "ies"} from ${repo}.` : `Partial restore: ${restored.length} succeeded, ${failed.length} failed (${failed.join(", ")}).`,
5851
+ committed: false
5852
+ };
5853
+ } finally {
5854
+ if (tmpDir) {
5855
+ await rm2(tmpDir, { recursive: true, force: true }).catch(() => {
5856
+ });
5857
+ }
5858
+ await releaseLock(lockPath);
5859
+ }
5860
+ }
5861
+ async function getBackupStatus() {
5862
+ const config = await readConfig();
5863
+ const lockPath = resolve14(syntaurRoot(), LOCK_FILE_NAME);
5864
+ const locked = await fileExists(lockPath);
5865
+ return {
5866
+ repo: config.backup?.repo ?? null,
5867
+ categories: config.backup?.categories ?? "projects, playbooks, todos, servers, config",
5868
+ lastBackup: config.backup?.lastBackup ?? null,
5869
+ lastRestore: config.backup?.lastRestore ?? null,
5870
+ locked
5871
+ };
5872
+ }
5873
+
5874
+ // src/dashboard/api-backup.ts
5875
+ function createBackupRouter() {
5876
+ const router = Router6();
5877
+ router.get("/", async (_req, res) => {
5878
+ try {
5879
+ const status = await getBackupStatus();
5880
+ res.json(status);
5881
+ } catch (error) {
5882
+ res.status(500).json({ error: error instanceof Error ? error.message : String(error) });
5883
+ }
5884
+ });
5885
+ router.put("/config", async (req, res) => {
5886
+ try {
5887
+ const body = req.body ?? {};
5888
+ const updates = {};
5889
+ if (body.repo !== void 0) {
5890
+ const trimmed = typeof body.repo === "string" ? body.repo.trim() : body.repo;
5891
+ if (trimmed !== null && trimmed !== "" && !validateRepoUrl(trimmed)) {
5892
+ return res.status(400).json({
5893
+ error: `Invalid repo URL. Must start with https:// or git@.`
5894
+ });
5895
+ }
5896
+ updates.repo = trimmed || null;
5897
+ }
5898
+ if (body.categories !== void 0) {
5899
+ let cats;
5900
+ if (Array.isArray(body.categories)) {
5901
+ cats = body.categories.map((s) => String(s).trim()).filter(Boolean);
5902
+ } else if (typeof body.categories === "string") {
5903
+ cats = body.categories.split(",").map((s) => s.trim()).filter(Boolean);
5904
+ } else {
5905
+ return res.status(400).json({ error: "categories must be a string or array" });
5906
+ }
5907
+ if (cats.length === 0) {
5908
+ return res.status(400).json({
5909
+ error: `No categories provided. Valid: ${VALID_CATEGORIES.join(", ")}`
5910
+ });
5911
+ }
5912
+ try {
5913
+ const valid = parseCategoriesStrict(cats);
5914
+ updates.categories = valid.join(", ");
5915
+ } catch (err) {
5916
+ return res.status(400).json({
5917
+ error: err instanceof Error ? err.message : String(err)
5918
+ });
5919
+ }
5920
+ }
5921
+ if (Object.keys(updates).length === 0) {
5922
+ return res.status(400).json({ error: "No fields to update" });
5923
+ }
5924
+ await updateBackupConfig(updates);
5925
+ const status = await getBackupStatus();
5926
+ res.json(status);
5927
+ } catch (error) {
5928
+ res.status(500).json({ error: error instanceof Error ? error.message : String(error) });
5929
+ }
5930
+ });
5931
+ router.post("/push", async (_req, res) => {
5932
+ try {
5933
+ const result = await backupToGithub();
5934
+ res.json(result);
5935
+ } catch (error) {
5936
+ res.status(500).json({
5937
+ success: false,
5938
+ error: error instanceof Error ? error.message : String(error)
5939
+ });
5940
+ }
5941
+ });
5942
+ router.post("/pull", async (_req, res) => {
5943
+ try {
5944
+ const result = await restoreFromGithub();
5945
+ res.json(result);
5946
+ } catch (error) {
5947
+ res.status(500).json({
5948
+ success: false,
5949
+ error: error instanceof Error ? error.message : String(error)
5950
+ });
5951
+ }
5952
+ });
5953
+ return router;
5954
+ }
5955
+
5529
5956
  // src/dashboard/autodiscovery.ts
5530
5957
  init_scanner();
5531
5958
  init_servers();
@@ -5588,7 +6015,7 @@ async function stopAutodiscovery() {
5588
6015
  function runReconcile() {
5589
6016
  if (activeReconcile || !savedOptions) return;
5590
6017
  const opts = savedOptions;
5591
- activeReconcile = reconcile(opts.serversDir, opts.missionsDir, opts.excludePids).catch((err) => {
6018
+ activeReconcile = reconcile(opts.serversDir, opts.projectsDir, opts.excludePids).catch((err) => {
5592
6019
  console.error("[autodiscovery] reconcile failed:", err);
5593
6020
  }).finally(() => {
5594
6021
  activeReconcile = null;
@@ -5599,10 +6026,10 @@ async function listAllTmuxSessions() {
5599
6026
  if (!output) return [];
5600
6027
  return output.split("\n").filter((line) => line.length > 0);
5601
6028
  }
5602
- async function discoverTmuxSessions(serversDir, missionsDir, existingNames) {
6029
+ async function discoverTmuxSessions(serversDir2, projectsDir, existingNames) {
5603
6030
  const tmuxAvailable = await checkTmuxAvailable();
5604
6031
  if (!tmuxAvailable) return false;
5605
- const workspaceRecords = await loadWorkspaceRecords(missionsDir);
6032
+ const workspaceRecords = await loadWorkspaceRecords(projectsDir);
5606
6033
  if (workspaceRecords.length === 0) return false;
5607
6034
  const sessions = await listAllTmuxSessions();
5608
6035
  let changed = false;
@@ -5627,7 +6054,7 @@ async function discoverTmuxSessions(serversDir, missionsDir, existingNames) {
5627
6054
  }
5628
6055
  }
5629
6056
  if (matched) {
5630
- await registerAutoSession(serversDir, sessionName, { kind: "tmux" });
6057
+ await registerAutoSession(serversDir2, sessionName, { kind: "tmux" });
5631
6058
  changed = true;
5632
6059
  }
5633
6060
  }
@@ -5643,8 +6070,8 @@ async function getProcessCwd(pid) {
5643
6070
  }
5644
6071
  return null;
5645
6072
  }
5646
- async function discoverProcesses(serversDir, missionsDir, existingFiles, excludePids) {
5647
- const workspaceRecords = await loadWorkspaceRecords(missionsDir);
6073
+ async function discoverProcesses(serversDir2, projectsDir, existingFiles, excludePids) {
6074
+ const workspaceRecords = await loadWorkspaceRecords(projectsDir);
5648
6075
  if (workspaceRecords.length === 0) return false;
5649
6076
  const lsofOutput = await getLsofOutput();
5650
6077
  if (!lsofOutput) return false;
@@ -5668,7 +6095,7 @@ async function discoverProcesses(serversDir, missionsDir, existingFiles, exclude
5668
6095
  const sanitized = sanitizeSessionName(sessionName);
5669
6096
  if (existingFiles.has(sanitized)) continue;
5670
6097
  const ports = parsePortsForPid(lsofOutput, proc.pid);
5671
- await registerAutoSession(serversDir, sessionName, {
6098
+ await registerAutoSession(serversDir2, sessionName, {
5672
6099
  kind: "process",
5673
6100
  pid: proc.pid,
5674
6101
  ports: ports.length > 0 ? ports : [proc.port],
@@ -5678,7 +6105,7 @@ async function discoverProcesses(serversDir, missionsDir, existingFiles, exclude
5678
6105
  }
5679
6106
  return changed;
5680
6107
  }
5681
- async function cleanupDeadAutoSessions(serversDir, existingFiles) {
6108
+ async function cleanupDeadAutoSessions(serversDir2, existingFiles) {
5682
6109
  let changed = false;
5683
6110
  const removedNames = /* @__PURE__ */ new Set();
5684
6111
  const tmuxAvailable = await checkTmuxAvailable();
@@ -5694,7 +6121,7 @@ async function cleanupDeadAutoSessions(serversDir, existingFiles) {
5694
6121
  continue;
5695
6122
  }
5696
6123
  if (!alive) {
5697
- await removeSession(serversDir, name);
6124
+ await removeSession(serversDir2, name);
5698
6125
  removedNames.add(name);
5699
6126
  changed = true;
5700
6127
  }
@@ -5709,20 +6136,20 @@ async function isProcessAlive(pid) {
5709
6136
  return false;
5710
6137
  }
5711
6138
  }
5712
- async function reconcile(serversDir, missionsDir, excludePids) {
5713
- const names = await listSessionFiles(serversDir);
6139
+ async function reconcile(serversDir2, projectsDir, excludePids) {
6140
+ const names = await listSessionFiles(serversDir2);
5714
6141
  const existingFiles = /* @__PURE__ */ new Map();
5715
6142
  for (const name of names) {
5716
- const data = await readSessionFile(serversDir, name);
6143
+ const data = await readSessionFile(serversDir2, name);
5717
6144
  if (data) existingFiles.set(name, data);
5718
6145
  }
5719
- const { changed: cleanupChanged, removedNames } = await cleanupDeadAutoSessions(serversDir, existingFiles);
6146
+ const { changed: cleanupChanged, removedNames } = await cleanupDeadAutoSessions(serversDir2, existingFiles);
5720
6147
  for (const name of removedNames) {
5721
6148
  existingFiles.delete(name);
5722
6149
  }
5723
6150
  const existingNames = new Set(existingFiles.keys());
5724
- const tmuxChanged = await discoverTmuxSessions(serversDir, missionsDir, existingNames);
5725
- const processChanged = await discoverProcesses(serversDir, missionsDir, existingFiles, excludePids);
6151
+ const tmuxChanged = await discoverTmuxSessions(serversDir2, projectsDir, existingNames);
6152
+ const processChanged = await discoverProcesses(serversDir2, projectsDir, existingFiles, excludePids);
5726
6153
  if (tmuxChanged || processChanged || cleanupChanged) {
5727
6154
  clearScanCache();
5728
6155
  }
@@ -5730,7 +6157,7 @@ async function reconcile(serversDir, missionsDir, excludePids) {
5730
6157
 
5731
6158
  // src/dashboard/server.ts
5732
6159
  function createDashboardServer(options) {
5733
- const { port, missionsDir, serversDir, playbooksDir, todosDir, serveStaticUi, dashboardDistPath } = options;
6160
+ const { port, projectsDir, serversDir: serversDir2, playbooksDir: playbooksDir2, todosDir: todosDir2, serveStaticUi, dashboardDistPath } = options;
5734
6161
  const app = express();
5735
6162
  const server = createServer(app);
5736
6163
  const wss = new WebSocketServer({ noServer: true });
@@ -5764,13 +6191,13 @@ function createDashboardServer(options) {
5764
6191
  }
5765
6192
  }
5766
6193
  initSessionDb();
5767
- migrateFromMarkdown(missionsDir).catch((err) => {
6194
+ migrateFromMarkdown(projectsDir).catch((err) => {
5768
6195
  console.error("Session migration from markdown failed:", err);
5769
6196
  });
5770
6197
  app.use(express.json());
5771
6198
  app.get("/api/overview", async (_req, res) => {
5772
6199
  try {
5773
- const overview = await getOverview(missionsDir, serversDir);
6200
+ const overview = await getOverview(projectsDir, serversDir2);
5774
6201
  res.json(overview);
5775
6202
  } catch (error) {
5776
6203
  console.error("Error getting overview:", error);
@@ -5779,7 +6206,7 @@ function createDashboardServer(options) {
5779
6206
  });
5780
6207
  app.get("/api/attention", async (_req, res) => {
5781
6208
  try {
5782
- const attention = await getAttention(missionsDir, serversDir);
6209
+ const attention = await getAttention(projectsDir, serversDir2);
5783
6210
  res.json(attention);
5784
6211
  } catch (error) {
5785
6212
  console.error("Error getting attention queue:", error);
@@ -5846,26 +6273,26 @@ function createDashboardServer(options) {
5846
6273
  res.status(500).json({ error: "Failed to reset status config" });
5847
6274
  }
5848
6275
  });
5849
- app.get("/api/missions", async (req, res) => {
6276
+ app.get("/api/projects", async (req, res) => {
5850
6277
  try {
5851
- let missions = await listMissions(missionsDir);
6278
+ let projects = await listProjects(projectsDir);
5852
6279
  const workspaceParam = req.query.workspace;
5853
6280
  if (workspaceParam) {
5854
6281
  if (workspaceParam === "_ungrouped") {
5855
- missions = missions.filter((m) => m.workspace === null);
6282
+ projects = projects.filter((m) => m.workspace === null);
5856
6283
  } else {
5857
- missions = missions.filter((m) => m.workspace === workspaceParam);
6284
+ projects = projects.filter((m) => m.workspace === workspaceParam);
5858
6285
  }
5859
6286
  }
5860
- res.json(missions);
6287
+ res.json(projects);
5861
6288
  } catch (error) {
5862
- console.error("Error listing missions:", error);
5863
- res.status(500).json({ error: "Failed to list missions" });
6289
+ console.error("Error listing projects:", error);
6290
+ res.status(500).json({ error: "Failed to list projects" });
5864
6291
  }
5865
6292
  });
5866
6293
  app.get("/api/workspaces", async (_req, res) => {
5867
6294
  try {
5868
- const result = await listWorkspaces(missionsDir);
6295
+ const result = await listWorkspaces(projectsDir);
5869
6296
  res.json(result);
5870
6297
  } catch (error) {
5871
6298
  console.error("Error listing workspaces:", error);
@@ -5879,8 +6306,8 @@ function createDashboardServer(options) {
5879
6306
  res.status(400).json({ error: "Invalid workspace name. Use lowercase letters, numbers, and hyphens." });
5880
6307
  return;
5881
6308
  }
5882
- await createWorkspace(missionsDir, name);
5883
- broadcast({ type: "mission-updated", missionSlug: "", timestamp: (/* @__PURE__ */ new Date()).toISOString() });
6309
+ await createWorkspace(projectsDir, name);
6310
+ broadcast({ type: "project-updated", projectSlug: "", timestamp: (/* @__PURE__ */ new Date()).toISOString() });
5884
6311
  res.json({ name });
5885
6312
  } catch (error) {
5886
6313
  console.error("Error creating workspace:", error);
@@ -5889,8 +6316,8 @@ function createDashboardServer(options) {
5889
6316
  });
5890
6317
  app.delete("/api/workspaces/:name", async (req, res) => {
5891
6318
  try {
5892
- await deleteWorkspace(missionsDir, req.params.name);
5893
- broadcast({ type: "mission-updated", missionSlug: "", timestamp: (/* @__PURE__ */ new Date()).toISOString() });
6319
+ await deleteWorkspace(projectsDir, req.params.name);
6320
+ broadcast({ type: "project-updated", projectSlug: "", timestamp: (/* @__PURE__ */ new Date()).toISOString() });
5894
6321
  res.json({ ok: true });
5895
6322
  } catch (error) {
5896
6323
  console.error("Error deleting workspace:", error);
@@ -5899,13 +6326,13 @@ function createDashboardServer(options) {
5899
6326
  });
5900
6327
  app.get("/api/assignments", async (req, res) => {
5901
6328
  try {
5902
- const result = await listAssignmentsBoard(missionsDir);
6329
+ const result = await listAssignmentsBoard(projectsDir);
5903
6330
  const workspaceParam = req.query.workspace;
5904
6331
  if (workspaceParam) {
5905
6332
  if (workspaceParam === "_ungrouped") {
5906
- result.assignments = result.assignments.filter((a) => a.missionWorkspace === null);
6333
+ result.assignments = result.assignments.filter((a) => a.projectWorkspace === null);
5907
6334
  } else {
5908
- result.assignments = result.assignments.filter((a) => a.missionWorkspace === workspaceParam);
6335
+ result.assignments = result.assignments.filter((a) => a.projectWorkspace === workspaceParam);
5909
6336
  }
5910
6337
  }
5911
6338
  res.json(result);
@@ -5914,29 +6341,29 @@ function createDashboardServer(options) {
5914
6341
  res.status(500).json({ error: "Failed to list assignments" });
5915
6342
  }
5916
6343
  });
5917
- app.get("/api/missions/:slug", async (req, res) => {
6344
+ app.get("/api/projects/:slug", async (req, res) => {
5918
6345
  try {
5919
- const detail = await getMissionDetail(missionsDir, req.params.slug);
6346
+ const detail = await getProjectDetail(projectsDir, req.params.slug);
5920
6347
  if (!detail) {
5921
- res.status(404).json({ error: `Mission "${req.params.slug}" not found` });
6348
+ res.status(404).json({ error: `Project "${req.params.slug}" not found` });
5922
6349
  return;
5923
6350
  }
5924
6351
  res.json(detail);
5925
6352
  } catch (error) {
5926
- console.error("Error getting mission detail:", error);
5927
- res.status(500).json({ error: "Failed to get mission detail" });
6353
+ console.error("Error getting project detail:", error);
6354
+ res.status(500).json({ error: "Failed to get project detail" });
5928
6355
  }
5929
6356
  });
5930
- app.get("/api/missions/:slug/assignments/:aslug", async (req, res) => {
6357
+ app.get("/api/projects/:slug/assignments/:aslug", async (req, res) => {
5931
6358
  try {
5932
6359
  const detail = await getAssignmentDetail(
5933
- missionsDir,
6360
+ projectsDir,
5934
6361
  req.params.slug,
5935
6362
  req.params.aslug
5936
6363
  );
5937
6364
  if (!detail) {
5938
6365
  res.status(404).json({
5939
- error: `Assignment "${req.params.aslug}" not found in mission "${req.params.slug}"`
6366
+ error: `Assignment "${req.params.aslug}" not found in project "${req.params.slug}"`
5940
6367
  });
5941
6368
  return;
5942
6369
  }
@@ -5946,15 +6373,16 @@ function createDashboardServer(options) {
5946
6373
  res.status(500).json({ error: "Failed to get assignment detail" });
5947
6374
  }
5948
6375
  });
5949
- app.use(createWriteRouter(missionsDir));
5950
- app.use("/api/servers", createServersRouter(serversDir, missionsDir));
5951
- app.use("/api/agent-sessions", createAgentSessionsRouter(missionsDir, broadcast));
5952
- app.use("/api/playbooks", createPlaybooksRouter(playbooksDir));
5953
- app.use("/api/todos", createTodosRouter(todosDir, broadcast));
6376
+ app.use(createWriteRouter(projectsDir));
6377
+ app.use("/api/servers", createServersRouter(serversDir2, projectsDir));
6378
+ app.use("/api/agent-sessions", createAgentSessionsRouter(projectsDir, broadcast));
6379
+ app.use("/api/playbooks", createPlaybooksRouter(playbooksDir2));
6380
+ app.use("/api/todos", createTodosRouter(todosDir2, broadcast));
6381
+ app.use("/api/backup", createBackupRouter());
5954
6382
  if (serveStaticUi && dashboardDistPath) {
5955
6383
  app.use(express.static(dashboardDistPath));
5956
6384
  app.get("{*path}", async (_req, res) => {
5957
- const indexPath = resolve14(dashboardDistPath, "index.html");
6385
+ const indexPath = resolve15(dashboardDistPath, "index.html");
5958
6386
  if (await fileExists(indexPath)) {
5959
6387
  res.sendFile(indexPath);
5960
6388
  } else {
@@ -5968,13 +6396,13 @@ function createDashboardServer(options) {
5968
6396
  return {
5969
6397
  async start() {
5970
6398
  watcherHandle = createWatcher({
5971
- missionsDir,
5972
- serversDir,
5973
- playbooksDir,
5974
- todosDir,
6399
+ projectsDir,
6400
+ serversDir: serversDir2,
6401
+ playbooksDir: playbooksDir2,
6402
+ todosDir: todosDir2,
5975
6403
  onMessage: broadcast
5976
6404
  });
5977
- startAutodiscovery({ serversDir, missionsDir, excludePids: /* @__PURE__ */ new Set([process.pid]) });
6405
+ startAutodiscovery({ serversDir: serversDir2, projectsDir, excludePids: /* @__PURE__ */ new Set([process.pid]) });
5978
6406
  return new Promise((resolvePromise, reject) => {
5979
6407
  server.on("error", (err) => {
5980
6408
  if (err.code === "EADDRINUSE") {
@@ -5986,8 +6414,8 @@ function createDashboardServer(options) {
5986
6414
  }
5987
6415
  });
5988
6416
  server.listen(port, () => {
5989
- const portFile = resolve14(homedir2(), ".syntaur", "dashboard-port");
5990
- writeFile3(portFile, String(port), "utf-8").catch(() => {
6417
+ const portFile = resolve15(syntaurRoot(), "dashboard-port");
6418
+ writeFile4(portFile, String(port), "utf-8").catch(() => {
5991
6419
  });
5992
6420
  resolvePromise();
5993
6421
  });
@@ -6000,12 +6428,13 @@ function createDashboardServer(options) {
6000
6428
  }
6001
6429
  closeSessionDb();
6002
6430
  for (const client of clients) {
6003
- client.close();
6431
+ client.terminate();
6004
6432
  }
6005
6433
  clients.clear();
6006
- const portFile = resolve14(homedir2(), ".syntaur", "dashboard-port");
6007
- await unlink3(portFile).catch(() => {
6434
+ const portFile = resolve15(syntaurRoot(), "dashboard-port");
6435
+ await unlink4(portFile).catch(() => {
6008
6436
  });
6437
+ server.closeAllConnections?.();
6009
6438
  return new Promise((resolvePromise) => {
6010
6439
  server.close(() => resolvePromise());
6011
6440
  });