syntaur 0.3.0 → 0.4.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 (41) hide show
  1. package/README.md +26 -3
  2. package/dist/dashboard/server.js +497 -315
  3. package/dist/dashboard/server.js.map +1 -1
  4. package/dist/index.js +1240 -550
  5. package/dist/index.js.map +1 -1
  6. package/package.json +8 -2
  7. package/platforms/claude-code/.claude-plugin/plugin.json +1 -1
  8. package/platforms/claude-code/agents/syntaur-expert.md +35 -20
  9. package/platforms/claude-code/commands/complete-assignment/complete-assignment.md +20 -0
  10. package/platforms/claude-code/commands/create-assignment/create-assignment.md +20 -0
  11. package/platforms/claude-code/commands/create-project/create-project.md +20 -0
  12. package/platforms/claude-code/commands/grab-assignment/grab-assignment.md +20 -0
  13. package/platforms/claude-code/commands/plan-assignment/plan-assignment.md +20 -0
  14. package/platforms/claude-code/commands/track-session/track-session.md +43 -18
  15. package/platforms/claude-code/hooks/hooks.json +11 -0
  16. package/platforms/claude-code/hooks/session-cleanup.sh +13 -23
  17. package/platforms/claude-code/hooks/session-start.sh +80 -0
  18. package/platforms/codex/.codex-plugin/plugin.json +1 -1
  19. package/platforms/codex/agents/syntaur-operator.md +6 -4
  20. package/platforms/codex/scripts/resolve-session.sh +49 -0
  21. package/statusline/statusline.sh +133 -0
  22. package/vendor/syntaur-skills/LICENSE +21 -0
  23. package/vendor/syntaur-skills/README.md +43 -0
  24. package/vendor/syntaur-skills/skills/complete-assignment/SKILL.md +146 -0
  25. package/vendor/syntaur-skills/skills/create-assignment/SKILL.md +72 -0
  26. package/vendor/syntaur-skills/skills/create-project/SKILL.md +56 -0
  27. package/vendor/syntaur-skills/skills/grab-assignment/SKILL.md +158 -0
  28. package/vendor/syntaur-skills/skills/plan-assignment/SKILL.md +137 -0
  29. package/vendor/syntaur-skills/skills/syntaur-protocol/SKILL.md +119 -0
  30. package/vendor/syntaur-skills/skills/syntaur-protocol/references/file-ownership.md +67 -0
  31. package/vendor/syntaur-skills/skills/syntaur-protocol/references/protocol-summary.md +82 -0
  32. package/platforms/claude-code/skills/complete-assignment/SKILL.md +0 -155
  33. package/platforms/claude-code/skills/create-assignment/SKILL.md +0 -67
  34. package/platforms/claude-code/skills/grab-assignment/SKILL.md +0 -187
  35. package/platforms/claude-code/skills/plan-assignment/SKILL.md +0 -148
  36. package/platforms/claude-code/skills/syntaur-protocol/SKILL.md +0 -86
  37. package/platforms/codex/skills/complete-assignment/SKILL.md +0 -64
  38. package/platforms/codex/skills/create-assignment/SKILL.md +0 -49
  39. package/platforms/codex/skills/grab-assignment/SKILL.md +0 -71
  40. package/platforms/codex/skills/plan-assignment/SKILL.md +0 -57
  41. package/platforms/codex/skills/syntaur-protocol/SKILL.md +0 -102
package/dist/index.js CHANGED
@@ -168,9 +168,10 @@ function parseListField(frontmatter, fieldName) {
168
168
  }
169
169
  function parseProject(fileContent) {
170
170
  const [fm, body] = extractFrontmatter(fileContent);
171
+ const slug = getField(fm, "slug") ?? getField(fm, "mission") ?? "";
171
172
  return {
172
173
  id: getField(fm, "id") ?? "",
173
- slug: getField(fm, "slug") ?? "",
174
+ slug,
174
175
  title: getField(fm, "title") ?? "",
175
176
  archived: getField(fm, "archived") === "true",
176
177
  archivedAt: getField(fm, "archivedAt"),
@@ -412,9 +413,144 @@ var init_timestamp = __esm({
412
413
  }
413
414
  });
414
415
 
416
+ // src/utils/fs-migration.ts
417
+ import { readdir as readdir3, readFile as readFile3, rename as rename2, writeFile as writeFile2 } from "fs/promises";
418
+ import { resolve as resolve4 } from "path";
419
+ async function migrateLegacyProjectFiles(projectsDir2) {
420
+ const result = {
421
+ renamedProjectFiles: [],
422
+ legacyExtras: []
423
+ };
424
+ if (!await fileExists(projectsDir2)) return result;
425
+ let entries;
426
+ try {
427
+ entries = await readdir3(projectsDir2, { withFileTypes: true });
428
+ } catch {
429
+ return result;
430
+ }
431
+ for (const entry of entries) {
432
+ if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
433
+ const projectDir = resolve4(projectsDir2, entry.name);
434
+ const legacy = resolve4(projectDir, "mission.md");
435
+ const target = resolve4(projectDir, "project.md");
436
+ try {
437
+ if (await fileExists(legacy) && !await fileExists(target)) {
438
+ await rename2(legacy, target);
439
+ result.renamedProjectFiles.push(`${entry.name}/mission.md`);
440
+ }
441
+ } catch {
442
+ continue;
443
+ }
444
+ for (const stale of ["agent.md", "claude.md"]) {
445
+ try {
446
+ if (await fileExists(resolve4(projectDir, stale))) {
447
+ result.legacyExtras.push(`${entry.name}/${stale}`);
448
+ }
449
+ } catch {
450
+ }
451
+ }
452
+ }
453
+ return result;
454
+ }
455
+ async function migrateLegacyConfig(configPath) {
456
+ const result = {
457
+ renamedField: false,
458
+ renamedDir: false,
459
+ resolvedProjectsDir: null
460
+ };
461
+ if (!await fileExists(configPath)) return result;
462
+ let content;
463
+ try {
464
+ content = await readFile3(configPath, "utf-8");
465
+ } catch {
466
+ return result;
467
+ }
468
+ const fmMatch = content.match(/^---\n([\s\S]*?)\n---\n?/);
469
+ if (!fmMatch) return result;
470
+ const fmBlock = fmMatch[1];
471
+ const afterFm = content.slice(fmMatch[0].length);
472
+ const missionLineRe = /^(\s*)defaultMissionDir\s*:\s*(.*)$/m;
473
+ const missionLineMatch = fmBlock.match(missionLineRe);
474
+ const hasProjectLine = /^\s*defaultProjectDir\s*:/m.test(fmBlock);
475
+ let newFmBlock = fmBlock;
476
+ let missionValue = null;
477
+ if (missionLineMatch) {
478
+ missionValue = missionLineMatch[2].trim();
479
+ if (!hasProjectLine) {
480
+ newFmBlock = fmBlock.replace(
481
+ missionLineRe,
482
+ `$1defaultProjectDir: ${missionValue}`
483
+ );
484
+ result.renamedField = true;
485
+ } else {
486
+ newFmBlock = fmBlock.replace(missionLineRe, "").replace(/\n{2,}/g, "\n");
487
+ result.renamedField = true;
488
+ }
489
+ }
490
+ const projectLineRe = /^\s*defaultProjectDir\s*:\s*(.*)$/m;
491
+ const projectLineMatch = newFmBlock.match(projectLineRe);
492
+ const projectsDirRaw = projectLineMatch ? projectLineMatch[1].trim().replace(/^['"]|['"]$/g, "") : missionValue;
493
+ const expand = (p) => p.startsWith("~") ? resolve4(process.env.HOME ?? "/", p.slice(p.startsWith("~/") ? 2 : 1)) : p;
494
+ let resolvedProjectsDir = projectsDirRaw ? expand(projectsDirRaw) : null;
495
+ if (resolvedProjectsDir && resolvedProjectsDir.endsWith("/missions")) {
496
+ const siblingProjectsDir = resolvedProjectsDir.replace(/\/missions$/, "/projects");
497
+ if (await fileExists(resolvedProjectsDir) && !await fileExists(siblingProjectsDir)) {
498
+ try {
499
+ await rename2(resolvedProjectsDir, siblingProjectsDir);
500
+ const newValue = projectsDirRaw.endsWith("/missions") ? projectsDirRaw.replace(/\/missions$/, "/projects") : siblingProjectsDir;
501
+ newFmBlock = newFmBlock.replace(
502
+ projectLineRe,
503
+ `defaultProjectDir: ${newValue}`
504
+ );
505
+ resolvedProjectsDir = siblingProjectsDir;
506
+ result.renamedDir = true;
507
+ } catch {
508
+ }
509
+ }
510
+ }
511
+ result.resolvedProjectsDir = resolvedProjectsDir;
512
+ if (result.renamedField || result.renamedDir) {
513
+ const newContent = `---
514
+ ${newFmBlock.replace(/\n+$/, "")}
515
+ ---
516
+ ${afterFm.startsWith("\n") ? afterFm.slice(1) : afterFm}`;
517
+ try {
518
+ await writeFile2(configPath, newContent, "utf-8");
519
+ } catch {
520
+ result.renamedField = false;
521
+ result.renamedDir = false;
522
+ }
523
+ }
524
+ return result;
525
+ }
526
+ function summarizeMigration(project, config) {
527
+ const parts = [];
528
+ if (project.renamedProjectFiles.length > 0) {
529
+ const firstThree = project.renamedProjectFiles.map((p) => p.split("/")[0]).slice(0, 3).join(", ");
530
+ const more = project.renamedProjectFiles.length > 3 ? ` and ${project.renamedProjectFiles.length - 3} more` : "";
531
+ parts.push(
532
+ `renamed mission.md \u2192 project.md in ${project.renamedProjectFiles.length} project${project.renamedProjectFiles.length === 1 ? "" : "s"} (${firstThree}${more})`
533
+ );
534
+ }
535
+ if (config?.renamedField) parts.push("updated config defaultMissionDir \u2192 defaultProjectDir");
536
+ if (config?.renamedDir) parts.push("renamed projects directory on disk");
537
+ if (project.legacyExtras.length > 0) {
538
+ parts.push(
539
+ `${project.legacyExtras.length} legacy agent.md / claude.md file${project.legacyExtras.length === 1 ? "" : "s"} left in place (no longer read)`
540
+ );
541
+ }
542
+ return parts.length ? `[syntaur] legacy migration: ${parts.join("; ")}` : "";
543
+ }
544
+ var init_fs_migration = __esm({
545
+ "src/utils/fs-migration.ts"() {
546
+ "use strict";
547
+ init_fs();
548
+ }
549
+ });
550
+
415
551
  // src/utils/config.ts
416
- import { readFile as readFile3 } from "fs/promises";
417
- import { resolve as resolve4, isAbsolute } from "path";
552
+ import { readFile as readFile4 } from "fs/promises";
553
+ import { resolve as resolve5, isAbsolute } from "path";
418
554
  function parseFrontmatter(content) {
419
555
  const match = content.match(/^---\n([\s\S]*?)\n---/);
420
556
  if (!match) return {};
@@ -620,10 +756,10 @@ function parseOptionalAbsolutePath(value, fieldName) {
620
756
  );
621
757
  return null;
622
758
  }
623
- return resolve4(expanded);
759
+ return resolve5(expanded);
624
760
  }
625
761
  async function writeStatusConfig(statuses) {
626
- const configPath = resolve4(syntaurRoot(), "config.md");
762
+ const configPath = resolve5(syntaurRoot(), "config.md");
627
763
  const statusBlock = serializeStatusConfig(statuses);
628
764
  if (!await fileExists(configPath)) {
629
765
  const content = `---
@@ -635,7 +771,7 @@ ${statusBlock}
635
771
  await writeFileForce(configPath, content);
636
772
  return;
637
773
  }
638
- const existing = await readFile3(configPath, "utf-8");
774
+ const existing = await readFile4(configPath, "utf-8");
639
775
  const fmMatch = existing.match(/^(---\n)([\s\S]*?)\n(---)/);
640
776
  if (!fmMatch) {
641
777
  const content = `---
@@ -677,9 +813,9 @@ ${statusBlock}
677
813
  await writeFileForce(configPath, newContent);
678
814
  }
679
815
  async function deleteStatusConfig() {
680
- const configPath = resolve4(syntaurRoot(), "config.md");
816
+ const configPath = resolve5(syntaurRoot(), "config.md");
681
817
  if (!await fileExists(configPath)) return;
682
- const existing = await readFile3(configPath, "utf-8");
818
+ const existing = await readFile4(configPath, "utf-8");
683
819
  const fmMatch = existing.match(/^(---\n)([\s\S]*?)\n(---)/);
684
820
  if (!fmMatch) return;
685
821
  const fmBlock = fmMatch[2];
@@ -691,13 +827,13 @@ ${cleanedFm}
691
827
  await writeFileForce(configPath, newContent);
692
828
  }
693
829
  async function updateIntegrationConfig(integrations) {
694
- const configPath = resolve4(syntaurRoot(), "config.md");
830
+ const configPath = resolve5(syntaurRoot(), "config.md");
695
831
  const nextIntegrations = {
696
832
  ...(await readConfig()).integrations,
697
833
  ...integrations
698
834
  };
699
835
  const integrationBlock = serializeIntegrationConfig(nextIntegrations);
700
- const existing = await fileExists(configPath) ? await readFile3(configPath, "utf-8") : renderConfig({ defaultProjectDir: defaultProjectDir() });
836
+ const existing = await fileExists(configPath) ? await readFile4(configPath, "utf-8") : renderConfig({ defaultProjectDir: defaultProjectDir() });
701
837
  const fmMatch = existing.match(/^(---\n)([\s\S]*?)\n(---)/);
702
838
  if (!fmMatch) {
703
839
  const content = `---
@@ -721,13 +857,13 @@ ${normalizedFm}
721
857
  await writeFileForce(configPath, newContent);
722
858
  }
723
859
  async function updateOnboardingConfig(onboarding) {
724
- const configPath = resolve4(syntaurRoot(), "config.md");
860
+ const configPath = resolve5(syntaurRoot(), "config.md");
725
861
  const nextOnboarding = {
726
862
  ...(await readConfig()).onboarding,
727
863
  ...onboarding
728
864
  };
729
865
  const onboardingBlock = serializeOnboardingConfig(nextOnboarding);
730
- const existing = await fileExists(configPath) ? await readFile3(configPath, "utf-8") : renderConfig({ defaultProjectDir: defaultProjectDir() });
866
+ const existing = await fileExists(configPath) ? await readFile4(configPath, "utf-8") : renderConfig({ defaultProjectDir: defaultProjectDir() });
731
867
  const fmMatch = existing.match(/^(---\n)([\s\S]*?)\n(---)/);
732
868
  if (!fmMatch) {
733
869
  const content = `---
@@ -751,7 +887,7 @@ ${normalizedFm}
751
887
  await writeFileForce(configPath, newContent);
752
888
  }
753
889
  async function updateBackupConfig(backup) {
754
- const configPath = resolve4(syntaurRoot(), "config.md");
890
+ const configPath = resolve5(syntaurRoot(), "config.md");
755
891
  const current = (await readConfig()).backup;
756
892
  const nextBackup = {
757
893
  repo: current?.repo ?? null,
@@ -761,7 +897,7 @@ async function updateBackupConfig(backup) {
761
897
  ...backup
762
898
  };
763
899
  const backupBlock = serializeBackupConfig(nextBackup);
764
- const existing = await fileExists(configPath) ? await readFile3(configPath, "utf-8") : renderConfig({ defaultProjectDir: defaultProjectDir() });
900
+ const existing = await fileExists(configPath) ? await readFile4(configPath, "utf-8") : renderConfig({ defaultProjectDir: defaultProjectDir() });
765
901
  const fmMatch = existing.match(/^(---\n)([\s\S]*?)\n(---)/);
766
902
  if (!fmMatch) {
767
903
  const content = `---
@@ -785,11 +921,15 @@ ${normalizedFm}
785
921
  await writeFileForce(configPath, newContent);
786
922
  }
787
923
  async function readConfig() {
788
- const configPath = resolve4(syntaurRoot(), "config.md");
924
+ const configPath = resolve5(syntaurRoot(), "config.md");
789
925
  if (!await fileExists(configPath)) {
790
926
  return { ...DEFAULT_CONFIG };
791
927
  }
792
- const content = await readFile3(configPath, "utf-8");
928
+ if (!migratedConfigPaths.has(configPath)) {
929
+ migratedConfigPaths.add(configPath);
930
+ await migrateLegacyConfig(configPath);
931
+ }
932
+ const content = await readFile4(configPath, "utf-8");
793
933
  const fm = parseFrontmatter(content);
794
934
  if (Object.keys(fm).length === 0) {
795
935
  console.warn("Warning: ~/.syntaur/config.md has malformed frontmatter, using defaults");
@@ -836,13 +976,14 @@ async function readConfig() {
836
976
  types: null
837
977
  };
838
978
  }
839
- var DEFAULT_CONFIG;
979
+ var DEFAULT_CONFIG, migratedConfigPaths;
840
980
  var init_config2 = __esm({
841
981
  "src/utils/config.ts"() {
842
982
  "use strict";
843
983
  init_paths();
844
984
  init_fs();
845
985
  init_config();
986
+ init_fs_migration();
846
987
  DEFAULT_CONFIG = {
847
988
  version: "2.0",
848
989
  defaultProjectDir: defaultProjectDir(),
@@ -862,6 +1003,7 @@ var init_config2 = __esm({
862
1003
  statuses: null,
863
1004
  types: null
864
1005
  };
1006
+ migratedConfigPaths = /* @__PURE__ */ new Set();
865
1007
  }
866
1008
  });
867
1009
 
@@ -1090,16 +1232,16 @@ var init_frontmatter = __esm({
1090
1232
  });
1091
1233
 
1092
1234
  // src/lifecycle/transitions.ts
1093
- import { resolve as resolve7 } from "path";
1094
- import { readFile as readFile4 } from "fs/promises";
1235
+ import { resolve as resolve8 } from "path";
1236
+ import { readFile as readFile5 } from "fs/promises";
1095
1237
  function resolveAssignmentPath(projectDir, assignmentSlug) {
1096
- return resolve7(projectDir, "assignments", assignmentSlug, "assignment.md");
1238
+ return resolve8(projectDir, "assignments", assignmentSlug, "assignment.md");
1097
1239
  }
1098
1240
  async function readAssignment(filePath) {
1099
1241
  if (!await fileExists(filePath)) {
1100
1242
  throw new Error(`Assignment file not found: ${filePath}`);
1101
1243
  }
1102
- const content = await readFile4(filePath, "utf-8");
1244
+ const content = await readFile5(filePath, "utf-8");
1103
1245
  const frontmatter = parseAssignmentFrontmatter(content);
1104
1246
  return { content, frontmatter };
1105
1247
  }
@@ -1112,7 +1254,7 @@ async function checkDependencies(projectDir, dependsOn, terminalStatuses3) {
1112
1254
  unmet.push(`${depSlug} (file not found)`);
1113
1255
  continue;
1114
1256
  }
1115
- const depContent = await readFile4(depPath, "utf-8");
1257
+ const depContent = await readFile5(depPath, "utf-8");
1116
1258
  const depFrontmatter = parseAssignmentFrontmatter(depContent);
1117
1259
  if (!terminals.has(depFrontmatter.status)) {
1118
1260
  unmet.push(`${depSlug} (status: ${depFrontmatter.status})`);
@@ -1177,7 +1319,7 @@ async function executeAssign(projectDir, assignmentSlug, agent) {
1177
1319
  };
1178
1320
  }
1179
1321
  async function executeTransitionByDir(assignmentDir, command, options = {}) {
1180
- const filePath = resolve7(assignmentDir, "assignment.md");
1322
+ const filePath = resolve8(assignmentDir, "assignment.md");
1181
1323
  const { content, frontmatter } = await readAssignment(filePath);
1182
1324
  const targetStatus = getTargetStatus(frontmatter.status, command, options.transitionTable);
1183
1325
  if (!targetStatus) {
@@ -1189,7 +1331,7 @@ async function executeTransitionByDir(assignmentDir, command, options = {}) {
1189
1331
  }
1190
1332
  const warnings = [];
1191
1333
  if (command === "start" && !options.standalone && frontmatter.dependsOn.length > 0) {
1192
- const projectDir = resolve7(assignmentDir, "..", "..");
1334
+ const projectDir = resolve8(assignmentDir, "..", "..");
1193
1335
  const depCheck = await checkDependencies(
1194
1336
  projectDir,
1195
1337
  frontmatter.dependsOn,
@@ -1223,7 +1365,7 @@ async function executeTransitionByDir(assignmentDir, command, options = {}) {
1223
1365
  };
1224
1366
  }
1225
1367
  async function executeAssignByDir(assignmentDir, agent) {
1226
- const filePath = resolve7(assignmentDir, "assignment.md");
1368
+ const filePath = resolve8(assignmentDir, "assignment.md");
1227
1369
  const { content, frontmatter } = await readAssignment(filePath);
1228
1370
  const updates = {
1229
1371
  assignee: agent,
@@ -1259,13 +1401,13 @@ var init_lifecycle = __esm({
1259
1401
  });
1260
1402
 
1261
1403
  // src/utils/assignment-resolver.ts
1262
- import { resolve as resolve8 } from "path";
1263
- import { readdir as readdir3, readFile as readFile5 } from "fs/promises";
1404
+ import { resolve as resolve9 } from "path";
1405
+ import { readdir as readdir4, readFile as readFile6 } from "fs/promises";
1264
1406
  async function resolveAssignmentById(projectsDir2, assignmentsDir2, id) {
1265
1407
  let standaloneMatch = null;
1266
1408
  let projectMatch = null;
1267
- const standaloneDir = resolve8(assignmentsDir2, id);
1268
- const standalonePath = resolve8(standaloneDir, "assignment.md");
1409
+ const standaloneDir = resolve9(assignmentsDir2, id);
1410
+ const standalonePath = resolve9(standaloneDir, "assignment.md");
1269
1411
  if (await fileExists(standalonePath)) {
1270
1412
  standaloneMatch = {
1271
1413
  assignmentDir: standaloneDir,
@@ -1277,24 +1419,24 @@ async function resolveAssignmentById(projectsDir2, assignmentsDir2, id) {
1277
1419
  }
1278
1420
  if (await fileExists(projectsDir2)) {
1279
1421
  try {
1280
- const projects = await readdir3(projectsDir2, { withFileTypes: true });
1422
+ const projects = await readdir4(projectsDir2, { withFileTypes: true });
1281
1423
  for (const p of projects) {
1282
1424
  if (!p.isDirectory()) continue;
1283
1425
  if (p.name.startsWith(".") || p.name.startsWith("_")) continue;
1284
- const assignmentsPath = resolve8(projectsDir2, p.name, "assignments");
1426
+ const assignmentsPath = resolve9(projectsDir2, p.name, "assignments");
1285
1427
  if (!await fileExists(assignmentsPath)) continue;
1286
- const entries = await readdir3(assignmentsPath, { withFileTypes: true });
1428
+ const entries = await readdir4(assignmentsPath, { withFileTypes: true });
1287
1429
  for (const a of entries) {
1288
1430
  if (!a.isDirectory()) continue;
1289
- const aPath = resolve8(assignmentsPath, a.name, "assignment.md");
1431
+ const aPath = resolve9(assignmentsPath, a.name, "assignment.md");
1290
1432
  if (!await fileExists(aPath)) continue;
1291
1433
  try {
1292
- const content = await readFile5(aPath, "utf-8");
1434
+ const content = await readFile6(aPath, "utf-8");
1293
1435
  const [fm] = extractFrontmatter(content);
1294
1436
  const fileId = getField(fm, "id");
1295
1437
  if (fileId === id) {
1296
1438
  projectMatch = {
1297
- assignmentDir: resolve8(assignmentsPath, a.name),
1439
+ assignmentDir: resolve9(assignmentsPath, a.name),
1298
1440
  projectSlug: p.name,
1299
1441
  assignmentSlug: a.name,
1300
1442
  id,
@@ -1658,8 +1800,8 @@ var init_help = __esm({
1658
1800
  // --- Session & server tracking (index 17) ---
1659
1801
  {
1660
1802
  command: "syntaur track-session",
1661
- description: "Register an agent session, optionally linked to a project and assignment.",
1662
- example: "syntaur track-session --agent claude --project ui-overhaul --assignment implement-overview"
1803
+ description: "Register an agent session. Requires --session-id from the agent runtime (real, not generated). Pass --transcript-path for the rollout/transcript file. --project and --assignment are optional.",
1804
+ example: "syntaur track-session --agent claude --session-id <real-id> --transcript-path <path> --project ui-overhaul --assignment implement-overview"
1663
1805
  },
1664
1806
  // --- Browsing & playbooks (indices 18-20) ---
1665
1807
  {
@@ -1742,8 +1884,8 @@ var init_help = __esm({
1742
1884
  });
1743
1885
 
1744
1886
  // src/dashboard/servers.ts
1745
- import { readdir as readdir4, readFile as readFile6, unlink } from "fs/promises";
1746
- import { resolve as resolve9 } from "path";
1887
+ import { readdir as readdir5, readFile as readFile7, unlink } from "fs/promises";
1888
+ import { resolve as resolve10 } from "path";
1747
1889
  function sanitizeSessionName(name) {
1748
1890
  return name.replace(/[^a-zA-Z0-9_-]/g, "-");
1749
1891
  }
@@ -1791,18 +1933,18 @@ async function registerSession(dir, rawName) {
1791
1933
  lastRefreshed: now,
1792
1934
  overrides: {}
1793
1935
  });
1794
- await writeFileForce(resolve9(dir, `${name}.md`), content);
1936
+ await writeFileForce(resolve10(dir, `${name}.md`), content);
1795
1937
  return name;
1796
1938
  }
1797
1939
  async function listSessionFiles(dir) {
1798
1940
  if (!await fileExists(dir)) return [];
1799
- const entries = await readdir4(dir);
1941
+ const entries = await readdir5(dir);
1800
1942
  return entries.filter((f) => f.endsWith(".md")).map((f) => f.replace(/\.md$/, ""));
1801
1943
  }
1802
1944
  async function readSessionFile(dir, name) {
1803
- const filePath = resolve9(dir, `${sanitizeSessionName(name)}.md`);
1945
+ const filePath = resolve10(dir, `${sanitizeSessionName(name)}.md`);
1804
1946
  if (!await fileExists(filePath)) return null;
1805
- const raw = await readFile6(filePath, "utf-8");
1947
+ const raw = await readFile7(filePath, "utf-8");
1806
1948
  const [frontmatter] = extractFrontmatter(raw);
1807
1949
  if (!frontmatter) return null;
1808
1950
  const session = getField(frontmatter, "session") ?? name;
@@ -1842,7 +1984,7 @@ async function readSessionFile(dir, name) {
1842
1984
  };
1843
1985
  }
1844
1986
  async function removeSession(dir, name) {
1845
- const filePath = resolve9(dir, `${sanitizeSessionName(name)}.md`);
1987
+ const filePath = resolve10(dir, `${sanitizeSessionName(name)}.md`);
1846
1988
  if (await fileExists(filePath)) {
1847
1989
  await unlink(filePath);
1848
1990
  }
@@ -1851,7 +1993,7 @@ async function updateLastRefreshed(dir, name) {
1851
1993
  const data = await readSessionFile(dir, name);
1852
1994
  if (!data) return;
1853
1995
  const content = buildSessionContent({ ...data, lastRefreshed: nowTimestamp2() });
1854
- await writeFileForce(resolve9(dir, `${sanitizeSessionName(name)}.md`), content);
1996
+ await writeFileForce(resolve10(dir, `${sanitizeSessionName(name)}.md`), content);
1855
1997
  }
1856
1998
  async function setOverride(dir, sessionName, windowIndex, paneIndex, assignment) {
1857
1999
  const data = await readSessionFile(dir, sessionName);
@@ -1863,7 +2005,7 @@ async function setOverride(dir, sessionName, windowIndex, paneIndex, assignment)
1863
2005
  delete data.overrides[key];
1864
2006
  }
1865
2007
  const content = buildSessionContent({ ...data });
1866
- await writeFileForce(resolve9(dir, `${sanitizeSessionName(sessionName)}.md`), content);
2008
+ await writeFileForce(resolve10(dir, `${sanitizeSessionName(sessionName)}.md`), content);
1867
2009
  }
1868
2010
  async function registerAutoSession(dir, rawName, opts) {
1869
2011
  const name = sanitizeSessionName(rawName);
@@ -1880,7 +2022,7 @@ async function registerAutoSession(dir, rawName, opts) {
1880
2022
  ports: opts.ports,
1881
2023
  cwd: opts.cwd
1882
2024
  });
1883
- await writeFileForce(resolve9(dir, `${name}.md`), content);
2025
+ await writeFileForce(resolve10(dir, `${name}.md`), content);
1884
2026
  return name;
1885
2027
  }
1886
2028
  var init_servers = __esm({
@@ -1912,8 +2054,8 @@ __export(scanner_exports, {
1912
2054
  });
1913
2055
  import { execFile } from "child_process";
1914
2056
  import { promisify } from "util";
1915
- import { resolve as resolve10 } from "path";
1916
- import { realpath, readdir as readdir5, readFile as readFile7 } from "fs/promises";
2057
+ import { resolve as resolve11 } from "path";
2058
+ import { realpath, readdir as readdir6, readFile as readFile8 } from "fs/promises";
1917
2059
  function clearScanCache() {
1918
2060
  cache = null;
1919
2061
  }
@@ -2008,8 +2150,8 @@ async function getGitInfo(cwd) {
2008
2150
  let isWorktree = false;
2009
2151
  if (commonDir && gitDir && commonDir !== gitDir) {
2010
2152
  try {
2011
- const resolvedCommon = await realpath(resolve10(cwd, commonDir));
2012
- const resolvedGit = await realpath(resolve10(cwd, gitDir));
2153
+ const resolvedCommon = await realpath(resolve11(cwd, commonDir));
2154
+ const resolvedGit = await realpath(resolve11(cwd, gitDir));
2013
2155
  isWorktree = resolvedCommon !== resolvedGit;
2014
2156
  } catch {
2015
2157
  isWorktree = false;
@@ -2022,17 +2164,17 @@ async function loadWorkspaceRecords(projectsDir2, assignmentsDir2) {
2022
2164
  try {
2023
2165
  const projects = await listProjects(projectsDir2);
2024
2166
  for (const project of projects) {
2025
- const projectAssignmentsDir = resolve10(projectsDir2, project.slug, "assignments");
2167
+ const projectAssignmentsDir = resolve11(projectsDir2, project.slug, "assignments");
2026
2168
  let slugs;
2027
2169
  try {
2028
- slugs = await readdir5(projectAssignmentsDir);
2170
+ slugs = await readdir6(projectAssignmentsDir);
2029
2171
  } catch {
2030
2172
  continue;
2031
2173
  }
2032
2174
  for (const aslug of slugs) {
2033
- const aFile = resolve10(projectAssignmentsDir, aslug, "assignment.md");
2175
+ const aFile = resolve11(projectAssignmentsDir, aslug, "assignment.md");
2034
2176
  try {
2035
- const raw = await readFile7(aFile, "utf-8");
2177
+ const raw = await readFile8(aFile, "utf-8");
2036
2178
  const [fm] = extractFrontmatter(raw);
2037
2179
  if (!fm) continue;
2038
2180
  records.push({
@@ -2051,12 +2193,12 @@ async function loadWorkspaceRecords(projectsDir2, assignmentsDir2) {
2051
2193
  }
2052
2194
  if (assignmentsDir2) {
2053
2195
  try {
2054
- const entries = await readdir5(assignmentsDir2);
2196
+ const entries = await readdir6(assignmentsDir2);
2055
2197
  for (const id of entries) {
2056
2198
  if (id.startsWith(".") || id.startsWith("_")) continue;
2057
- const aFile = resolve10(assignmentsDir2, id, "assignment.md");
2199
+ const aFile = resolve11(assignmentsDir2, id, "assignment.md");
2058
2200
  try {
2059
- const raw = await readFile7(aFile, "utf-8");
2201
+ const raw = await readFile8(aFile, "utf-8");
2060
2202
  const [fm] = extractFrontmatter(raw);
2061
2203
  if (!fm) continue;
2062
2204
  records.push({
@@ -2304,20 +2446,20 @@ var init_scanner = __esm({
2304
2446
  });
2305
2447
 
2306
2448
  // src/dashboard/api.ts
2307
- import { readdir as readdir6, readFile as readFile8, writeFile as writeFile2 } from "fs/promises";
2308
- import { resolve as resolve11, dirname as dirname3 } from "path";
2449
+ import { readdir as readdir7, readFile as readFile9, writeFile as writeFile3 } from "fs/promises";
2450
+ import { resolve as resolve12, dirname as dirname3 } from "path";
2309
2451
  async function listStandaloneRecords(assignmentsDir2) {
2310
2452
  if (!assignmentsDir2) return [];
2311
2453
  if (!await fileExists(assignmentsDir2)) return [];
2312
- const entries = await readdir6(assignmentsDir2, { withFileTypes: true });
2454
+ const entries = await readdir7(assignmentsDir2, { withFileTypes: true });
2313
2455
  const records = [];
2314
2456
  for (const entry of entries) {
2315
2457
  if (!entry.isDirectory() || entry.name.startsWith(".") || entry.name.startsWith("_")) continue;
2316
- const assignmentDir = resolve11(assignmentsDir2, entry.name);
2317
- const assignmentMdPath = resolve11(assignmentDir, "assignment.md");
2458
+ const assignmentDir = resolve12(assignmentsDir2, entry.name);
2459
+ const assignmentMdPath = resolve12(assignmentDir, "assignment.md");
2318
2460
  if (!await fileExists(assignmentMdPath)) continue;
2319
2461
  try {
2320
- const content = await readFile8(assignmentMdPath, "utf-8");
2462
+ const content = await readFile9(assignmentMdPath, "utf-8");
2321
2463
  const record = parseAssignmentFull(content);
2322
2464
  records.push({ assignmentDir, id: entry.name, record });
2323
2465
  } catch {
@@ -2386,9 +2528,9 @@ async function listProjects(projectsDir2) {
2386
2528
  return projectRecords.map((record) => record.summary);
2387
2529
  }
2388
2530
  async function readWorkspaceRegistry(projectsDir2) {
2389
- const registryPath = resolve11(dirname3(projectsDir2), "workspaces.json");
2531
+ const registryPath = resolve12(dirname3(projectsDir2), "workspaces.json");
2390
2532
  try {
2391
- const raw = await readFile8(registryPath, "utf-8");
2533
+ const raw = await readFile9(registryPath, "utf-8");
2392
2534
  const parsed = JSON.parse(raw);
2393
2535
  return Array.isArray(parsed) ? parsed.filter((w) => typeof w === "string") : [];
2394
2536
  } catch {
@@ -2396,8 +2538,8 @@ async function readWorkspaceRegistry(projectsDir2) {
2396
2538
  }
2397
2539
  }
2398
2540
  async function writeWorkspaceRegistry(projectsDir2, workspaces) {
2399
- const registryPath = resolve11(dirname3(projectsDir2), "workspaces.json");
2400
- await writeFile2(registryPath, JSON.stringify(workspaces, null, 2) + "\n", "utf-8");
2541
+ const registryPath = resolve12(dirname3(projectsDir2), "workspaces.json");
2542
+ await writeFile3(registryPath, JSON.stringify(workspaces, null, 2) + "\n", "utf-8");
2401
2543
  }
2402
2544
  async function listWorkspaces(projectsDir2) {
2403
2545
  const [projectRecords, registered] = await Promise.all([
@@ -2596,7 +2738,7 @@ async function getEditableDocument(projectsDir2, documentType, projectSlug, assi
2596
2738
  if (!filePath || !await fileExists(filePath)) {
2597
2739
  return null;
2598
2740
  }
2599
- const content = await readFile8(filePath, "utf-8");
2741
+ const content = await readFile9(filePath, "utf-8");
2600
2742
  const title = getEditableDocumentTitle(documentType, projectSlug, assignmentSlug);
2601
2743
  return {
2602
2744
  documentType,
@@ -2620,9 +2762,9 @@ async function getEditableDocumentById(projectsDir2, assignmentsDir2, documentTy
2620
2762
  }
2621
2763
  const fileName = documentType === "assignment" ? "assignment.md" : documentType === "plan" ? "plan.md" : documentType === "scratchpad" ? "scratchpad.md" : documentType === "handoff" ? "handoff.md" : documentType === "decision-record" ? "decision-record.md" : null;
2622
2764
  if (!fileName) return null;
2623
- const filePath = resolve11(resolved.assignmentDir, fileName);
2765
+ const filePath = resolve12(resolved.assignmentDir, fileName);
2624
2766
  if (!await fileExists(filePath)) return null;
2625
- const content = await readFile8(filePath, "utf-8");
2767
+ const content = await readFile9(filePath, "utf-8");
2626
2768
  const label = resolved.id;
2627
2769
  const title = documentType === "assignment" ? `Edit Assignment: ${label}` : documentType === "plan" ? `Edit Plan: ${label}` : documentType === "scratchpad" ? `Edit Scratchpad: ${label}` : documentType === "handoff" ? `Append Handoff: ${label}` : `Append Decision: ${label}`;
2628
2770
  return {
@@ -2636,12 +2778,12 @@ async function getEditableDocumentById(projectsDir2, assignmentsDir2, documentTy
2636
2778
  };
2637
2779
  }
2638
2780
  async function getProjectDetail(projectsDir2, slug) {
2639
- const projectPath = resolve11(projectsDir2, slug);
2640
- const projectMdPath = resolve11(projectPath, "project.md");
2781
+ const projectPath = resolve12(projectsDir2, slug);
2782
+ const projectMdPath = resolve12(projectPath, "project.md");
2641
2783
  if (!await fileExists(projectMdPath)) {
2642
2784
  return null;
2643
2785
  }
2644
- const projectContent = await readFile8(projectMdPath, "utf-8");
2786
+ const projectContent = await readFile9(projectMdPath, "utf-8");
2645
2787
  const project = parseProject(projectContent);
2646
2788
  const assignments = await listAssignmentRecords(projectPath);
2647
2789
  const rollup = await buildProjectRollup(projectPath, project, assignments);
@@ -2671,17 +2813,17 @@ async function getProjectDetail(projectsDir2, slug) {
2671
2813
  };
2672
2814
  }
2673
2815
  async function getAssignmentDetail(projectsDir2, projectSlug, assignmentSlug) {
2674
- const assignmentDir = resolve11(projectsDir2, projectSlug, "assignments", assignmentSlug);
2675
- const assignmentMdPath = resolve11(assignmentDir, "assignment.md");
2816
+ const assignmentDir = resolve12(projectsDir2, projectSlug, "assignments", assignmentSlug);
2817
+ const assignmentMdPath = resolve12(assignmentDir, "assignment.md");
2676
2818
  if (!await fileExists(assignmentMdPath)) {
2677
2819
  return null;
2678
2820
  }
2679
- const assignmentContent = await readFile8(assignmentMdPath, "utf-8");
2821
+ const assignmentContent = await readFile9(assignmentMdPath, "utf-8");
2680
2822
  const assignment = parseAssignmentFull(assignmentContent);
2681
2823
  let plan = null;
2682
- const planPath = resolve11(assignmentDir, "plan.md");
2824
+ const planPath = resolve12(assignmentDir, "plan.md");
2683
2825
  if (await fileExists(planPath)) {
2684
- const planContent = await readFile8(planPath, "utf-8");
2826
+ const planContent = await readFile9(planPath, "utf-8");
2685
2827
  const parsed = parsePlan(planContent);
2686
2828
  plan = {
2687
2829
  status: parsed.status,
@@ -2690,9 +2832,9 @@ async function getAssignmentDetail(projectsDir2, projectSlug, assignmentSlug) {
2690
2832
  };
2691
2833
  }
2692
2834
  let scratchpad = null;
2693
- const scratchpadPath = resolve11(assignmentDir, "scratchpad.md");
2835
+ const scratchpadPath = resolve12(assignmentDir, "scratchpad.md");
2694
2836
  if (await fileExists(scratchpadPath)) {
2695
- const scratchpadContent = await readFile8(scratchpadPath, "utf-8");
2837
+ const scratchpadContent = await readFile9(scratchpadPath, "utf-8");
2696
2838
  const parsed = parseScratchpad(scratchpadContent);
2697
2839
  scratchpad = {
2698
2840
  updated: parsed.updated,
@@ -2700,9 +2842,9 @@ async function getAssignmentDetail(projectsDir2, projectSlug, assignmentSlug) {
2700
2842
  };
2701
2843
  }
2702
2844
  let handoff = null;
2703
- const handoffPath = resolve11(assignmentDir, "handoff.md");
2845
+ const handoffPath = resolve12(assignmentDir, "handoff.md");
2704
2846
  if (await fileExists(handoffPath)) {
2705
- const handoffContent = await readFile8(handoffPath, "utf-8");
2847
+ const handoffContent = await readFile9(handoffPath, "utf-8");
2706
2848
  const parsed = parseHandoff(handoffContent);
2707
2849
  handoff = {
2708
2850
  updated: parsed.updated,
@@ -2711,9 +2853,9 @@ async function getAssignmentDetail(projectsDir2, projectSlug, assignmentSlug) {
2711
2853
  };
2712
2854
  }
2713
2855
  let decisionRecord = null;
2714
- const decisionRecordPath = resolve11(assignmentDir, "decision-record.md");
2856
+ const decisionRecordPath = resolve12(assignmentDir, "decision-record.md");
2715
2857
  if (await fileExists(decisionRecordPath)) {
2716
- const decisionRecordContent = await readFile8(decisionRecordPath, "utf-8");
2858
+ const decisionRecordContent = await readFile9(decisionRecordPath, "utf-8");
2717
2859
  const parsed = parseDecisionRecord(decisionRecordContent);
2718
2860
  decisionRecord = {
2719
2861
  updated: parsed.updated,
@@ -2722,9 +2864,9 @@ async function getAssignmentDetail(projectsDir2, projectSlug, assignmentSlug) {
2722
2864
  };
2723
2865
  }
2724
2866
  let progress = null;
2725
- const progressPath = resolve11(assignmentDir, "progress.md");
2867
+ const progressPath = resolve12(assignmentDir, "progress.md");
2726
2868
  if (await fileExists(progressPath)) {
2727
- const progressContent = await readFile8(progressPath, "utf-8");
2869
+ const progressContent = await readFile9(progressPath, "utf-8");
2728
2870
  const parsed = parseProgress(progressContent);
2729
2871
  progress = {
2730
2872
  updated: parsed.updated,
@@ -2733,9 +2875,9 @@ async function getAssignmentDetail(projectsDir2, projectSlug, assignmentSlug) {
2733
2875
  };
2734
2876
  }
2735
2877
  let comments = null;
2736
- const commentsPath = resolve11(assignmentDir, "comments.md");
2878
+ const commentsPath = resolve12(assignmentDir, "comments.md");
2737
2879
  if (await fileExists(commentsPath)) {
2738
- const commentsContent = await readFile8(commentsPath, "utf-8");
2880
+ const commentsContent = await readFile9(commentsPath, "utf-8");
2739
2881
  const parsed = parseComments(commentsContent);
2740
2882
  comments = {
2741
2883
  updated: parsed.updated,
@@ -2849,7 +2991,7 @@ async function computeReferencedBy(target, projectsDir2, assignmentsDir2) {
2849
2991
  slug: a.slug,
2850
2992
  title: a.title,
2851
2993
  projectSlug: rec.summary.slug,
2852
- assignmentDir: resolve11(rec.projectPath, "assignments", a.slug)
2994
+ assignmentDir: resolve12(rec.projectPath, "assignments", a.slug)
2853
2995
  });
2854
2996
  }
2855
2997
  }
@@ -2882,17 +3024,17 @@ async function computeReferencedBy(target, projectsDir2, assignmentsDir2) {
2882
3024
  }
2883
3025
  async function countMentionsInAssignment(sourceDir, target) {
2884
3026
  const bodies = [];
2885
- const assignmentMd = resolve11(sourceDir, "assignment.md");
3027
+ const assignmentMd = resolve12(sourceDir, "assignment.md");
2886
3028
  if (await fileExists(assignmentMd)) {
2887
- const content = await readFile8(assignmentMd, "utf-8");
3029
+ const content = await readFile9(assignmentMd, "utf-8");
2888
3030
  const todosMatch = content.match(/^## Todos\s*$([\s\S]*?)(?=^## |$(?![\r\n]))/m);
2889
3031
  if (todosMatch) bodies.push(todosMatch[1]);
2890
3032
  }
2891
3033
  for (const filename of ["progress.md", "comments.md", "handoff.md"]) {
2892
- const path = resolve11(sourceDir, filename);
3034
+ const path = resolve12(sourceDir, filename);
2893
3035
  if (await fileExists(path)) {
2894
3036
  try {
2895
- bodies.push(await readFile8(path, "utf-8"));
3037
+ bodies.push(await readFile9(path, "utf-8"));
2896
3038
  } catch {
2897
3039
  }
2898
3040
  }
@@ -2950,44 +3092,44 @@ async function getAssignmentDetailById(projectsDir2, assignmentsDir2, id) {
2950
3092
  }
2951
3093
  async function buildStandaloneAssignmentDetail(resolved) {
2952
3094
  const assignmentDir = resolved.assignmentDir;
2953
- const assignmentMdPath = resolve11(assignmentDir, "assignment.md");
3095
+ const assignmentMdPath = resolve12(assignmentDir, "assignment.md");
2954
3096
  if (!await fileExists(assignmentMdPath)) return null;
2955
- const assignmentContent = await readFile8(assignmentMdPath, "utf-8");
3097
+ const assignmentContent = await readFile9(assignmentMdPath, "utf-8");
2956
3098
  const assignment = parseAssignmentFull(assignmentContent);
2957
3099
  let plan = null;
2958
- const planPath = resolve11(assignmentDir, "plan.md");
3100
+ const planPath = resolve12(assignmentDir, "plan.md");
2959
3101
  if (await fileExists(planPath)) {
2960
- const parsed = parsePlan(await readFile8(planPath, "utf-8"));
3102
+ const parsed = parsePlan(await readFile9(planPath, "utf-8"));
2961
3103
  plan = { status: parsed.status, updated: parsed.updated, body: parsed.body };
2962
3104
  }
2963
3105
  let scratchpad = null;
2964
- const scratchpadPath = resolve11(assignmentDir, "scratchpad.md");
3106
+ const scratchpadPath = resolve12(assignmentDir, "scratchpad.md");
2965
3107
  if (await fileExists(scratchpadPath)) {
2966
- const parsed = parseScratchpad(await readFile8(scratchpadPath, "utf-8"));
3108
+ const parsed = parseScratchpad(await readFile9(scratchpadPath, "utf-8"));
2967
3109
  scratchpad = { updated: parsed.updated, body: parsed.body };
2968
3110
  }
2969
3111
  let handoff = null;
2970
- const handoffPath = resolve11(assignmentDir, "handoff.md");
3112
+ const handoffPath = resolve12(assignmentDir, "handoff.md");
2971
3113
  if (await fileExists(handoffPath)) {
2972
- const parsed = parseHandoff(await readFile8(handoffPath, "utf-8"));
3114
+ const parsed = parseHandoff(await readFile9(handoffPath, "utf-8"));
2973
3115
  handoff = { updated: parsed.updated, handoffCount: parsed.handoffCount, body: parsed.body };
2974
3116
  }
2975
3117
  let decisionRecord = null;
2976
- const decisionRecordPath = resolve11(assignmentDir, "decision-record.md");
3118
+ const decisionRecordPath = resolve12(assignmentDir, "decision-record.md");
2977
3119
  if (await fileExists(decisionRecordPath)) {
2978
- const parsed = parseDecisionRecord(await readFile8(decisionRecordPath, "utf-8"));
3120
+ const parsed = parseDecisionRecord(await readFile9(decisionRecordPath, "utf-8"));
2979
3121
  decisionRecord = { updated: parsed.updated, decisionCount: parsed.decisionCount, body: parsed.body };
2980
3122
  }
2981
3123
  let progress = null;
2982
- const progressPath = resolve11(assignmentDir, "progress.md");
3124
+ const progressPath = resolve12(assignmentDir, "progress.md");
2983
3125
  if (await fileExists(progressPath)) {
2984
- const parsed = parseProgress(await readFile8(progressPath, "utf-8"));
3126
+ const parsed = parseProgress(await readFile9(progressPath, "utf-8"));
2985
3127
  progress = { updated: parsed.updated, entryCount: parsed.entryCount, entries: parsed.entries };
2986
3128
  }
2987
3129
  let comments = null;
2988
- const commentsPath = resolve11(assignmentDir, "comments.md");
3130
+ const commentsPath = resolve12(assignmentDir, "comments.md");
2989
3131
  if (await fileExists(commentsPath)) {
2990
- const parsed = parseComments(await readFile8(commentsPath, "utf-8"));
3132
+ const parsed = parseComments(await readFile9(commentsPath, "utf-8"));
2991
3133
  comments = { updated: parsed.updated, entryCount: parsed.entryCount, entries: parsed.entries };
2992
3134
  }
2993
3135
  const detail = {
@@ -3025,16 +3167,20 @@ async function listProjectRecords(projectsDir2) {
3025
3167
  if (!await fileExists(projectsDir2)) {
3026
3168
  return [];
3027
3169
  }
3028
- const entries = await readdir6(projectsDir2, { withFileTypes: true });
3170
+ if (!migratedProjectsDirs.has(projectsDir2)) {
3171
+ migratedProjectsDirs.add(projectsDir2);
3172
+ await migrateLegacyProjectFiles(projectsDir2);
3173
+ }
3174
+ const entries = await readdir7(projectsDir2, { withFileTypes: true });
3029
3175
  const projectDirs = entries.filter((entry) => entry.isDirectory() && !entry.name.startsWith("."));
3030
3176
  const records = [];
3031
3177
  for (const entry of projectDirs) {
3032
- const projectPath = resolve11(projectsDir2, entry.name);
3033
- const projectMdPath = resolve11(projectPath, "project.md");
3178
+ const projectPath = resolve12(projectsDir2, entry.name);
3179
+ const projectMdPath = resolve12(projectPath, "project.md");
3034
3180
  if (!await fileExists(projectMdPath)) {
3035
3181
  continue;
3036
3182
  }
3037
- const projectContent = await readFile8(projectMdPath, "utf-8");
3183
+ const projectContent = await readFile9(projectMdPath, "utf-8");
3038
3184
  const project = parseProject(projectContent);
3039
3185
  const assignments = await listAssignmentRecords(projectPath);
3040
3186
  const rollup = await buildProjectRollup(projectPath, project, assignments);
@@ -3065,39 +3211,39 @@ async function listProjectRecords(projectsDir2) {
3065
3211
  return records;
3066
3212
  }
3067
3213
  async function listAssignmentRecords(projectPath) {
3068
- const assignmentsDir2 = resolve11(projectPath, "assignments");
3214
+ const assignmentsDir2 = resolve12(projectPath, "assignments");
3069
3215
  if (!await fileExists(assignmentsDir2)) {
3070
3216
  return [];
3071
3217
  }
3072
- const entries = await readdir6(assignmentsDir2, { withFileTypes: true });
3218
+ const entries = await readdir7(assignmentsDir2, { withFileTypes: true });
3073
3219
  const records = [];
3074
3220
  for (const entry of entries) {
3075
3221
  if (!entry.isDirectory()) {
3076
3222
  continue;
3077
3223
  }
3078
- const assignmentMd = resolve11(assignmentsDir2, entry.name, "assignment.md");
3224
+ const assignmentMd = resolve12(assignmentsDir2, entry.name, "assignment.md");
3079
3225
  if (!await fileExists(assignmentMd)) {
3080
3226
  continue;
3081
3227
  }
3082
- const content = await readFile8(assignmentMd, "utf-8");
3228
+ const content = await readFile9(assignmentMd, "utf-8");
3083
3229
  records.push(parseAssignmentFull(content));
3084
3230
  }
3085
3231
  records.sort((left, right) => compareTimestamps(right.updated, left.updated));
3086
3232
  return records;
3087
3233
  }
3088
3234
  async function listResources(projectPath) {
3089
- const resourcesDir = resolve11(projectPath, "resources");
3235
+ const resourcesDir = resolve12(projectPath, "resources");
3090
3236
  if (!await fileExists(resourcesDir)) {
3091
3237
  return [];
3092
3238
  }
3093
- const entries = await readdir6(resourcesDir, { withFileTypes: true });
3239
+ const entries = await readdir7(resourcesDir, { withFileTypes: true });
3094
3240
  const results = [];
3095
3241
  for (const entry of entries) {
3096
3242
  if (!entry.isFile() || !entry.name.endsWith(".md") || entry.name.startsWith("_")) {
3097
3243
  continue;
3098
3244
  }
3099
- const filePath = resolve11(resourcesDir, entry.name);
3100
- const content = await readFile8(filePath, "utf-8");
3245
+ const filePath = resolve12(resourcesDir, entry.name);
3246
+ const content = await readFile9(filePath, "utf-8");
3101
3247
  const parsed = parseResource(content);
3102
3248
  results.push({
3103
3249
  name: parsed.name,
@@ -3112,18 +3258,18 @@ async function listResources(projectPath) {
3112
3258
  return results;
3113
3259
  }
3114
3260
  async function listMemories(projectPath) {
3115
- const memoriesDir = resolve11(projectPath, "memories");
3261
+ const memoriesDir = resolve12(projectPath, "memories");
3116
3262
  if (!await fileExists(memoriesDir)) {
3117
3263
  return [];
3118
3264
  }
3119
- const entries = await readdir6(memoriesDir, { withFileTypes: true });
3265
+ const entries = await readdir7(memoriesDir, { withFileTypes: true });
3120
3266
  const results = [];
3121
3267
  for (const entry of entries) {
3122
3268
  if (!entry.isFile() || !entry.name.endsWith(".md") || entry.name.startsWith("_")) {
3123
3269
  continue;
3124
3270
  }
3125
- const filePath = resolve11(memoriesDir, entry.name);
3126
- const content = await readFile8(filePath, "utf-8");
3271
+ const filePath = resolve12(memoriesDir, entry.name);
3272
+ const content = await readFile9(filePath, "utf-8");
3127
3273
  const parsed = parseMemory(content);
3128
3274
  results.push({
3129
3275
  name: parsed.name,
@@ -3138,9 +3284,9 @@ async function listMemories(projectPath) {
3138
3284
  return results;
3139
3285
  }
3140
3286
  async function loadDependencyGraph(projectPath, assignments) {
3141
- const statusPath = resolve11(projectPath, "_status.md");
3287
+ const statusPath = resolve12(projectPath, "_status.md");
3142
3288
  if (await fileExists(statusPath)) {
3143
- const statusContent = await readFile8(statusPath, "utf-8");
3289
+ const statusContent = await readFile9(statusPath, "utf-8");
3144
3290
  const parsed = parseStatus(statusContent);
3145
3291
  const derivedGraph = extractMermaidGraph(parsed.body);
3146
3292
  if (derivedGraph) {
@@ -3240,7 +3386,7 @@ async function getAvailableTransitions(projectsDir2, projectSlug, assignmentSlug
3240
3386
  const config = await getStatusConfig();
3241
3387
  const transitionDefs = getTransitionDefinitions(config);
3242
3388
  const actions = [];
3243
- const projectPath = resolve11(projectsDir2, projectSlug);
3389
+ const projectPath = resolve12(projectsDir2, projectSlug);
3244
3390
  for (const definition of transitionDefs) {
3245
3391
  let warning = null;
3246
3392
  if (definition.command === "start" && !assignment.assignee) {
@@ -3270,12 +3416,12 @@ async function getUnmetDependencies(projectPath, dependsOn, terminalStatuses3) {
3270
3416
  const terminals = terminalStatuses3 ?? /* @__PURE__ */ new Set(["completed"]);
3271
3417
  const unmet = [];
3272
3418
  for (const dependency of dependsOn) {
3273
- const dependencyPath = resolve11(projectPath, "assignments", dependency, "assignment.md");
3419
+ const dependencyPath = resolve12(projectPath, "assignments", dependency, "assignment.md");
3274
3420
  if (!await fileExists(dependencyPath)) {
3275
3421
  unmet.push(`${dependency} (missing)`);
3276
3422
  continue;
3277
3423
  }
3278
- const content = await readFile8(dependencyPath, "utf-8");
3424
+ const content = await readFile9(dependencyPath, "utf-8");
3279
3425
  const parsed = parseAssignmentFull(content);
3280
3426
  if (!terminals.has(parsed.status)) {
3281
3427
  unmet.push(`${dependency} (${parsed.status})`);
@@ -3430,7 +3576,7 @@ function isStale(updated) {
3430
3576
  return Date.now() - timestamp > STALE_ASSIGNMENT_MS;
3431
3577
  }
3432
3578
  async function countOpenQuestions(projectPath, assignmentSlug) {
3433
- const commentsPath = resolve11(
3579
+ const commentsPath = resolve12(
3434
3580
  projectPath,
3435
3581
  "assignments",
3436
3582
  assignmentSlug,
@@ -3440,7 +3586,7 @@ async function countOpenQuestions(projectPath, assignmentSlug) {
3440
3586
  return 0;
3441
3587
  }
3442
3588
  try {
3443
- const content = await readFile8(commentsPath, "utf-8");
3589
+ const content = await readFile9(commentsPath, "utf-8");
3444
3590
  const parsed = parseComments(content);
3445
3591
  return parsed.entries.filter(
3446
3592
  (e) => e.type === "question" && e.resolved !== true
@@ -3461,17 +3607,17 @@ function getProjectActivityTimestamp(projectUpdated, assignments) {
3461
3607
  function getDocumentPath(projectsDir2, documentType, projectSlug, assignmentSlug) {
3462
3608
  switch (documentType) {
3463
3609
  case "project":
3464
- return resolve11(projectsDir2, projectSlug, "project.md");
3610
+ return resolve12(projectsDir2, projectSlug, "project.md");
3465
3611
  case "assignment":
3466
- return assignmentSlug ? resolve11(projectsDir2, projectSlug, "assignments", assignmentSlug, "assignment.md") : null;
3612
+ return assignmentSlug ? resolve12(projectsDir2, projectSlug, "assignments", assignmentSlug, "assignment.md") : null;
3467
3613
  case "plan":
3468
- return assignmentSlug ? resolve11(projectsDir2, projectSlug, "assignments", assignmentSlug, "plan.md") : null;
3614
+ return assignmentSlug ? resolve12(projectsDir2, projectSlug, "assignments", assignmentSlug, "plan.md") : null;
3469
3615
  case "scratchpad":
3470
- return assignmentSlug ? resolve11(projectsDir2, projectSlug, "assignments", assignmentSlug, "scratchpad.md") : null;
3616
+ return assignmentSlug ? resolve12(projectsDir2, projectSlug, "assignments", assignmentSlug, "scratchpad.md") : null;
3471
3617
  case "handoff":
3472
- return assignmentSlug ? resolve11(projectsDir2, projectSlug, "assignments", assignmentSlug, "handoff.md") : null;
3618
+ return assignmentSlug ? resolve12(projectsDir2, projectSlug, "assignments", assignmentSlug, "handoff.md") : null;
3473
3619
  case "decision-record":
3474
- return assignmentSlug ? resolve11(projectsDir2, projectSlug, "assignments", assignmentSlug, "decision-record.md") : null;
3620
+ return assignmentSlug ? resolve12(projectsDir2, projectSlug, "assignments", assignmentSlug, "decision-record.md") : null;
3475
3621
  default:
3476
3622
  return null;
3477
3623
  }
@@ -3498,12 +3644,12 @@ function getEditableDocumentTitle(documentType, projectSlug, assignmentSlug) {
3498
3644
  }
3499
3645
  async function listPlaybooks(playbooksDir3) {
3500
3646
  if (!await fileExists(playbooksDir3)) return [];
3501
- const entries = await readdir6(playbooksDir3, { withFileTypes: true });
3647
+ const entries = await readdir7(playbooksDir3, { withFileTypes: true });
3502
3648
  const playbooks = [];
3503
3649
  for (const entry of entries) {
3504
3650
  if (!entry.isFile() || !entry.name.endsWith(".md") || entry.name.startsWith("_") || entry.name === "manifest.md") continue;
3505
- const filePath = resolve11(playbooksDir3, entry.name);
3506
- const raw = await readFile8(filePath, "utf-8");
3651
+ const filePath = resolve12(playbooksDir3, entry.name);
3652
+ const raw = await readFile9(filePath, "utf-8");
3507
3653
  const parsed = parsePlaybook(raw);
3508
3654
  const slug = parsed.slug || entry.name.replace(/\.md$/, "");
3509
3655
  playbooks.push({
@@ -3519,9 +3665,9 @@ async function listPlaybooks(playbooksDir3) {
3519
3665
  return playbooks.sort((a, b) => (b.updated || b.created).localeCompare(a.updated || a.created));
3520
3666
  }
3521
3667
  async function getPlaybookDetail(playbooksDir3, slug) {
3522
- const filePath = resolve11(playbooksDir3, `${slug}.md`);
3668
+ const filePath = resolve12(playbooksDir3, `${slug}.md`);
3523
3669
  if (!await fileExists(filePath)) return null;
3524
- const raw = await readFile8(filePath, "utf-8");
3670
+ const raw = await readFile9(filePath, "utf-8");
3525
3671
  const parsed = parsePlaybook(raw);
3526
3672
  return {
3527
3673
  slug: parsed.slug || slug,
@@ -3534,13 +3680,14 @@ async function getPlaybookDetail(playbooksDir3, slug) {
3534
3680
  body: parsed.body
3535
3681
  };
3536
3682
  }
3537
- var STALE_ASSIGNMENT_MS, ATTENTION_PAGE_LIMIT, OVERVIEW_ATTENTION_LIMIT, RECENT_PROJECTS_LIMIT, RECENT_ACTIVITY_LIMIT, DEFAULT_TRANSITION_DEFINITIONS, DEFAULT_STATUS_COLORS, _cachedConfig, REFERENCED_BY_LIMIT, DEFAULT_GRAPH_COLORS;
3683
+ var STALE_ASSIGNMENT_MS, ATTENTION_PAGE_LIMIT, OVERVIEW_ATTENTION_LIMIT, RECENT_PROJECTS_LIMIT, RECENT_ACTIVITY_LIMIT, DEFAULT_TRANSITION_DEFINITIONS, DEFAULT_STATUS_COLORS, _cachedConfig, REFERENCED_BY_LIMIT, migratedProjectsDirs, DEFAULT_GRAPH_COLORS;
3538
3684
  var init_api = __esm({
3539
3685
  "src/dashboard/api.ts"() {
3540
3686
  "use strict";
3541
3687
  init_lifecycle();
3542
3688
  init_fs();
3543
3689
  init_config2();
3690
+ init_fs_migration();
3544
3691
  init_assignment_resolver();
3545
3692
  init_parser();
3546
3693
  init_help();
@@ -3603,6 +3750,7 @@ var init_api = __esm({
3603
3750
  };
3604
3751
  _cachedConfig = null;
3605
3752
  REFERENCED_BY_LIMIT = 50;
3753
+ migratedProjectsDirs = /* @__PURE__ */ new Set();
3606
3754
  DEFAULT_GRAPH_COLORS = {
3607
3755
  completed: "fill:#4ea84f,stroke:#1f6b29,color:#ffffff",
3608
3756
  in_progress: "fill:#1e6fd9,stroke:#0f3f8f,color:#ffffff",
@@ -3635,8 +3783,8 @@ __export(parser_exports, {
3635
3783
  writeChecklist: () => writeChecklist
3636
3784
  });
3637
3785
  import { randomBytes } from "crypto";
3638
- import { readFile as readFile12 } from "fs/promises";
3639
- import { resolve as resolve17 } from "path";
3786
+ import { readFile as readFile13 } from "fs/promises";
3787
+ import { resolve as resolve18 } from "path";
3640
3788
  function generateShortId() {
3641
3789
  return randomBytes(2).toString("hex");
3642
3790
  }
@@ -3796,10 +3944,10 @@ function serializeLogEntry(entry) {
3796
3944
  return lines.join("\n");
3797
3945
  }
3798
3946
  function checklistPath(todosDir2, workspace) {
3799
- return resolve17(todosDir2, `${workspace}.md`);
3947
+ return resolve18(todosDir2, `${workspace}.md`);
3800
3948
  }
3801
3949
  function logPath(todosDir2, workspace) {
3802
- return resolve17(todosDir2, `${workspace}-log.md`);
3950
+ return resolve18(todosDir2, `${workspace}-log.md`);
3803
3951
  }
3804
3952
  function archivePath(todosDir2, workspace, interval, now = /* @__PURE__ */ new Date()) {
3805
3953
  const year = now.getFullYear();
@@ -3823,14 +3971,14 @@ function archivePath(todosDir2, workspace, interval, now = /* @__PURE__ */ new D
3823
3971
  default:
3824
3972
  suffix = `${year}-${month}-${day}`;
3825
3973
  }
3826
- return resolve17(todosDir2, "archive", `${workspace}-${suffix}.md`);
3974
+ return resolve18(todosDir2, "archive", `${workspace}-${suffix}.md`);
3827
3975
  }
3828
3976
  async function readChecklist(todosDir2, workspace) {
3829
3977
  const path = checklistPath(todosDir2, workspace);
3830
3978
  if (!await fileExists(path)) {
3831
3979
  return { workspace, archiveInterval: "weekly", items: [] };
3832
3980
  }
3833
- const content = await readFile12(path, "utf-8");
3981
+ const content = await readFile13(path, "utf-8");
3834
3982
  return parseChecklist(content);
3835
3983
  }
3836
3984
  async function writeChecklist(todosDir2, checklist) {
@@ -3843,7 +3991,7 @@ async function readLog(todosDir2, workspace) {
3843
3991
  if (!await fileExists(path)) {
3844
3992
  return { workspace, entries: [] };
3845
3993
  }
3846
- const content = await readFile12(path, "utf-8");
3994
+ const content = await readFile13(path, "utf-8");
3847
3995
  return parseLog(content);
3848
3996
  }
3849
3997
  async function appendLogEntry2(todosDir2, workspace, entry) {
@@ -3851,7 +3999,7 @@ async function appendLogEntry2(todosDir2, workspace, entry) {
3851
3999
  const path = logPath(todosDir2, workspace);
3852
4000
  let content;
3853
4001
  if (await fileExists(path)) {
3854
- content = await readFile12(path, "utf-8");
4002
+ content = await readFile13(path, "utf-8");
3855
4003
  content = content.trimEnd() + "\n\n" + serializeLogEntry(entry) + "\n";
3856
4004
  } else {
3857
4005
  const fm = `---
@@ -4398,8 +4546,8 @@ __export(launch_exports, {
4398
4546
  launchAgent: () => launchAgent
4399
4547
  });
4400
4548
  import { spawn as spawn2 } from "child_process";
4401
- import { mkdir as mkdir2, writeFile as writeFile6 } from "fs/promises";
4402
- import { resolve as resolve27 } from "path";
4549
+ import { mkdir as mkdir4, writeFile as writeFile8 } from "fs/promises";
4550
+ import { resolve as resolve30 } from "path";
4403
4551
  async function launchAgent(options) {
4404
4552
  const { projectsDir: projectsDir2, projectSlug, assignmentSlug, agent } = options;
4405
4553
  const command = AGENT_COMMANDS[agent];
@@ -4409,10 +4557,10 @@ async function launchAgent(options) {
4409
4557
  process.exit(1);
4410
4558
  }
4411
4559
  const workspaceDir = detail.workspace.worktreePath ?? (detail.workspace.repository?.startsWith("/") ? detail.workspace.repository : null) ?? process.cwd();
4412
- const projectDir = resolve27(projectsDir2, projectSlug);
4413
- const assignmentDir = resolve27(projectDir, "assignments", assignmentSlug);
4414
- const contextDir = resolve27(workspaceDir, ".syntaur");
4415
- await mkdir2(contextDir, { recursive: true });
4560
+ const projectDir = resolve30(projectsDir2, projectSlug);
4561
+ const assignmentDir = resolve30(projectDir, "assignments", assignmentSlug);
4562
+ const contextDir = resolve30(workspaceDir, ".syntaur");
4563
+ await mkdir4(contextDir, { recursive: true });
4416
4564
  const context = {
4417
4565
  projectSlug,
4418
4566
  assignmentSlug,
@@ -4423,8 +4571,8 @@ async function launchAgent(options) {
4423
4571
  branch: detail.workspace.branch ?? null,
4424
4572
  grabbedAt: (/* @__PURE__ */ new Date()).toISOString()
4425
4573
  };
4426
- await writeFile6(
4427
- resolve27(contextDir, "context.json"),
4574
+ await writeFile8(
4575
+ resolve30(contextDir, "context.json"),
4428
4576
  JSON.stringify(context, null, 2) + "\n"
4429
4577
  );
4430
4578
  return new Promise((resolvePromise, reject) => {
@@ -4573,7 +4721,7 @@ async function seedDefaultPlaybooks(playbooksDir3) {
4573
4721
  }
4574
4722
 
4575
4723
  // src/commands/create-project.ts
4576
- import { resolve as resolve5 } from "path";
4724
+ import { resolve as resolve6 } from "path";
4577
4725
 
4578
4726
  // src/utils/slug.ts
4579
4727
  function slugify(title) {
@@ -5331,7 +5479,7 @@ async function createProjectCommand(title, options) {
5331
5479
  }
5332
5480
  const config = await readConfig();
5333
5481
  const baseDir = options.dir ? expandHome(options.dir) : config.defaultProjectDir;
5334
- const projectDir = resolve5(baseDir, slug);
5482
+ const projectDir = resolve6(baseDir, slug);
5335
5483
  if (await fileExists(projectDir)) {
5336
5484
  throw new Error(
5337
5485
  `Project folder already exists: ${projectDir}
@@ -5340,40 +5488,40 @@ Use --slug to specify a different slug.`
5340
5488
  }
5341
5489
  const timestamp = nowTimestamp();
5342
5490
  const id = generateId();
5343
- await ensureDir(resolve5(projectDir, "assignments"));
5344
- await ensureDir(resolve5(projectDir, "resources"));
5345
- await ensureDir(resolve5(projectDir, "memories"));
5491
+ await ensureDir(resolve6(projectDir, "assignments"));
5492
+ await ensureDir(resolve6(projectDir, "resources"));
5493
+ await ensureDir(resolve6(projectDir, "memories"));
5346
5494
  const files = [
5347
5495
  [
5348
- resolve5(projectDir, "manifest.md"),
5496
+ resolve6(projectDir, "manifest.md"),
5349
5497
  renderManifest({ slug, timestamp })
5350
5498
  ],
5351
5499
  [
5352
- resolve5(projectDir, "project.md"),
5500
+ resolve6(projectDir, "project.md"),
5353
5501
  renderProject({ id, slug, title, timestamp, workspace: options.workspace })
5354
5502
  ],
5355
5503
  [
5356
- resolve5(projectDir, "_index-assignments.md"),
5504
+ resolve6(projectDir, "_index-assignments.md"),
5357
5505
  renderIndexAssignments({ slug, title, timestamp })
5358
5506
  ],
5359
5507
  [
5360
- resolve5(projectDir, "_index-plans.md"),
5508
+ resolve6(projectDir, "_index-plans.md"),
5361
5509
  renderIndexPlans({ slug, title, timestamp })
5362
5510
  ],
5363
5511
  [
5364
- resolve5(projectDir, "_index-decisions.md"),
5512
+ resolve6(projectDir, "_index-decisions.md"),
5365
5513
  renderIndexDecisions({ slug, title, timestamp })
5366
5514
  ],
5367
5515
  [
5368
- resolve5(projectDir, "_status.md"),
5516
+ resolve6(projectDir, "_status.md"),
5369
5517
  renderStatus({ slug, title, timestamp })
5370
5518
  ],
5371
5519
  [
5372
- resolve5(projectDir, "resources", "_index.md"),
5520
+ resolve6(projectDir, "resources", "_index.md"),
5373
5521
  renderResourcesIndex({ slug, title, timestamp })
5374
5522
  ],
5375
5523
  [
5376
- resolve5(projectDir, "memories", "_index.md"),
5524
+ resolve6(projectDir, "memories", "_index.md"),
5377
5525
  renderMemoriesIndex({ slug, title, timestamp })
5378
5526
  ]
5379
5527
  ];
@@ -5395,7 +5543,7 @@ Use --slug to specify a different slug.`
5395
5543
  }
5396
5544
 
5397
5545
  // src/commands/create-assignment.ts
5398
- import { resolve as resolve6 } from "path";
5546
+ import { resolve as resolve7 } from "path";
5399
5547
  init_timestamp();
5400
5548
  init_paths();
5401
5549
  init_fs();
@@ -5461,14 +5609,14 @@ async function createAssignmentCommand(title, options) {
5461
5609
  if (options.oneOff) {
5462
5610
  const standaloneRoot = assignmentsDir();
5463
5611
  folderName = id;
5464
- assignmentDir = resolve6(standaloneRoot, folderName);
5612
+ assignmentDir = resolve7(standaloneRoot, folderName);
5465
5613
  projectSlug = null;
5466
5614
  await ensureDir(standaloneRoot);
5467
5615
  } else {
5468
5616
  const baseDir = options.dir ? expandHome(options.dir) : config.defaultProjectDir;
5469
5617
  projectSlug = options.project;
5470
- const projectDir = resolve6(baseDir, projectSlug);
5471
- const projectMdPath = resolve6(projectDir, "project.md");
5618
+ const projectDir = resolve7(baseDir, projectSlug);
5619
+ const projectMdPath = resolve7(projectDir, "project.md");
5472
5620
  if (!await fileExists(projectDir) || !await fileExists(projectMdPath)) {
5473
5621
  throw new Error(
5474
5622
  `Project "${projectSlug}" not found at ${projectDir}.
@@ -5476,9 +5624,9 @@ Run 'syntaur create-project' first or use --one-off.`
5476
5624
  );
5477
5625
  }
5478
5626
  if (dependsOn.length > 0) {
5479
- const depDirBase = resolve6(projectDir, "assignments");
5627
+ const depDirBase = resolve7(projectDir, "assignments");
5480
5628
  for (const dep of dependsOn) {
5481
- const depDir = resolve6(depDirBase, dep);
5629
+ const depDir = resolve7(depDirBase, dep);
5482
5630
  if (!await fileExists(depDir)) {
5483
5631
  console.warn(
5484
5632
  `Warning: dependency "${dep}" does not exist in project "${projectSlug}" yet.`
@@ -5487,7 +5635,7 @@ Run 'syntaur create-project' first or use --one-off.`
5487
5635
  }
5488
5636
  }
5489
5637
  folderName = assignmentSlug;
5490
- assignmentDir = resolve6(projectDir, "assignments", folderName);
5638
+ assignmentDir = resolve7(projectDir, "assignments", folderName);
5491
5639
  }
5492
5640
  if (await fileExists(assignmentDir)) {
5493
5641
  throw new Error(
@@ -5499,7 +5647,7 @@ Use --slug to specify a different slug.`
5499
5647
  const companionAssignmentRef = projectSlug === null ? id : assignmentSlug;
5500
5648
  const files = [
5501
5649
  [
5502
- resolve6(assignmentDir, "assignment.md"),
5650
+ resolve7(assignmentDir, "assignment.md"),
5503
5651
  renderAssignment({
5504
5652
  id,
5505
5653
  slug: assignmentSlug,
@@ -5513,35 +5661,35 @@ Use --slug to specify a different slug.`
5513
5661
  })
5514
5662
  ],
5515
5663
  [
5516
- resolve6(assignmentDir, "scratchpad.md"),
5664
+ resolve7(assignmentDir, "scratchpad.md"),
5517
5665
  renderScratchpad({
5518
5666
  assignmentSlug: companionAssignmentRef,
5519
5667
  timestamp
5520
5668
  })
5521
5669
  ],
5522
5670
  [
5523
- resolve6(assignmentDir, "handoff.md"),
5671
+ resolve7(assignmentDir, "handoff.md"),
5524
5672
  renderHandoff({
5525
5673
  assignmentSlug: companionAssignmentRef,
5526
5674
  timestamp
5527
5675
  })
5528
5676
  ],
5529
5677
  [
5530
- resolve6(assignmentDir, "decision-record.md"),
5678
+ resolve7(assignmentDir, "decision-record.md"),
5531
5679
  renderDecisionRecord({
5532
5680
  assignmentSlug: companionAssignmentRef,
5533
5681
  timestamp
5534
5682
  })
5535
5683
  ],
5536
5684
  [
5537
- resolve6(assignmentDir, "progress.md"),
5685
+ resolve7(assignmentDir, "progress.md"),
5538
5686
  renderProgress({
5539
5687
  assignment: companionAssignmentRef,
5540
5688
  timestamp
5541
5689
  })
5542
5690
  ],
5543
5691
  [
5544
- resolve6(assignmentDir, "comments.md"),
5692
+ resolve7(assignmentDir, "comments.md"),
5545
5693
  renderComments({
5546
5694
  assignment: companionAssignmentRef,
5547
5695
  timestamp
@@ -5589,7 +5737,7 @@ Use --slug to specify a different slug.`
5589
5737
  init_config2();
5590
5738
  import { spawn } from "child_process";
5591
5739
  import { createServer as createNetServer } from "net";
5592
- import { resolve as resolve20, dirname as dirname4 } from "path";
5740
+ import { resolve as resolve21, dirname as dirname4 } from "path";
5593
5741
  import { fileURLToPath as fileURLToPath2 } from "url";
5594
5742
 
5595
5743
  // src/dashboard/server.ts
@@ -5598,21 +5746,21 @@ init_api();
5598
5746
  init_assignment_resolver();
5599
5747
  import express from "express";
5600
5748
  import { createServer } from "http";
5601
- import { resolve as resolve19 } from "path";
5602
- import { writeFile as writeFile4, unlink as unlink4 } from "fs/promises";
5749
+ import { resolve as resolve20 } from "path";
5750
+ import { writeFile as writeFile5, unlink as unlink4 } from "fs/promises";
5603
5751
  import { WebSocketServer, WebSocket } from "ws";
5604
5752
 
5605
5753
  // src/dashboard/agent-sessions.ts
5606
5754
  init_fs();
5607
- import { readFile as readFile9 } from "fs/promises";
5608
- import { resolve as resolve13 } from "path";
5755
+ import { readFile as readFile10 } from "fs/promises";
5756
+ import { resolve as resolve14 } from "path";
5609
5757
 
5610
5758
  // src/dashboard/session-db.ts
5611
5759
  init_paths();
5612
5760
  init_fs();
5613
5761
  import Database from "better-sqlite3";
5614
- import { resolve as resolve12 } from "path";
5615
- import { readdir as readdir7 } from "fs/promises";
5762
+ import { resolve as resolve13 } from "path";
5763
+ import { readdir as readdir8 } from "fs/promises";
5616
5764
  var db = null;
5617
5765
  var SCHEMA_VERSION = "3";
5618
5766
  var SCHEMA_SQL = `
@@ -5630,14 +5778,16 @@ CREATE TABLE IF NOT EXISTS sessions (
5630
5778
  created_at TEXT NOT NULL DEFAULT (datetime('now')),
5631
5779
  updated_at TEXT NOT NULL DEFAULT (datetime('now'))
5632
5780
  );
5633
- CREATE INDEX IF NOT EXISTS idx_sessions_project ON sessions(project_slug);
5634
- CREATE INDEX IF NOT EXISTS idx_sessions_assignment ON sessions(project_slug, assignment_slug);
5635
5781
  CREATE INDEX IF NOT EXISTS idx_sessions_status ON sessions(status);
5636
5782
  CREATE TABLE IF NOT EXISTS meta (key TEXT PRIMARY KEY, value TEXT);
5637
5783
  `;
5784
+ var POST_MIGRATION_INDEXES_SQL = `
5785
+ CREATE INDEX IF NOT EXISTS idx_sessions_project ON sessions(project_slug);
5786
+ CREATE INDEX IF NOT EXISTS idx_sessions_assignment ON sessions(project_slug, assignment_slug);
5787
+ `;
5638
5788
  function initSessionDb(dbPath) {
5639
5789
  if (db) return db;
5640
- const finalPath = dbPath ?? resolve12(syntaurRoot(), "syntaur.db");
5790
+ const finalPath = dbPath ?? resolve13(syntaurRoot(), "syntaur.db");
5641
5791
  db = new Database(finalPath);
5642
5792
  db.pragma("journal_mode = WAL");
5643
5793
  db.exec(SCHEMA_SQL);
@@ -5645,57 +5795,74 @@ function initSessionDb(dbPath) {
5645
5795
  "schema_version",
5646
5796
  SCHEMA_VERSION
5647
5797
  );
5648
- const currentVersion = db.prepare("SELECT value FROM meta WHERE key = 'schema_version'").get();
5649
- if (currentVersion?.value === "1") {
5650
- db.exec(`
5651
- CREATE TABLE sessions_v2 (
5652
- session_id TEXT PRIMARY KEY,
5653
- project_slug TEXT,
5654
- assignment_slug TEXT,
5655
- agent TEXT NOT NULL,
5656
- started TEXT NOT NULL,
5657
- ended TEXT,
5658
- status TEXT NOT NULL DEFAULT 'active',
5659
- path TEXT,
5660
- description TEXT,
5661
- created_at TEXT NOT NULL DEFAULT (datetime('now')),
5662
- updated_at TEXT NOT NULL DEFAULT (datetime('now'))
5663
- );
5664
- INSERT INTO sessions_v2 SELECT session_id, project_slug, assignment_slug, agent, started, ended, status, path, NULL, created_at, updated_at FROM sessions;
5665
- DROP TABLE sessions;
5666
- ALTER TABLE sessions_v2 RENAME TO sessions;
5667
- CREATE INDEX IF NOT EXISTS idx_sessions_project ON sessions(project_slug);
5668
- CREATE INDEX IF NOT EXISTS idx_sessions_assignment ON sessions(project_slug, assignment_slug);
5669
- CREATE INDEX IF NOT EXISTS idx_sessions_status ON sessions(status);
5670
- UPDATE meta SET value = '2' WHERE key = 'schema_version';
5671
- `);
5672
- }
5673
- const versionAfterV1 = db.prepare("SELECT value FROM meta WHERE key = 'schema_version'").get();
5674
- if (versionAfterV1?.value === "2") {
5675
- db.exec(`
5676
- CREATE TABLE sessions_v3 (
5677
- session_id TEXT PRIMARY KEY,
5678
- project_slug TEXT,
5679
- assignment_slug TEXT,
5680
- agent TEXT NOT NULL,
5681
- started TEXT NOT NULL,
5682
- ended TEXT,
5683
- status TEXT NOT NULL DEFAULT 'active',
5684
- path TEXT,
5685
- description TEXT,
5686
- transcript_path TEXT,
5687
- created_at TEXT NOT NULL DEFAULT (datetime('now')),
5688
- updated_at TEXT NOT NULL DEFAULT (datetime('now'))
5689
- );
5690
- INSERT INTO sessions_v3 SELECT session_id, project_slug, assignment_slug, agent, started, ended, status, path, description, NULL, created_at, updated_at FROM sessions;
5691
- DROP TABLE sessions;
5692
- ALTER TABLE sessions_v3 RENAME TO sessions;
5693
- CREATE INDEX IF NOT EXISTS idx_sessions_project ON sessions(project_slug);
5694
- CREATE INDEX IF NOT EXISTS idx_sessions_assignment ON sessions(project_slug, assignment_slug);
5695
- CREATE INDEX IF NOT EXISTS idx_sessions_status ON sessions(status);
5696
- UPDATE meta SET value = '3' WHERE key = 'schema_version';
5697
- `);
5698
- }
5798
+ const database = db;
5799
+ const runMigrations = database.transaction(() => {
5800
+ const vBeforeV2 = database.prepare("SELECT value FROM meta WHERE key = 'schema_version'").get()?.value;
5801
+ if (vBeforeV2 === "1") {
5802
+ database.exec(`
5803
+ CREATE TABLE sessions_v2 (
5804
+ session_id TEXT PRIMARY KEY,
5805
+ project_slug TEXT,
5806
+ assignment_slug TEXT,
5807
+ agent TEXT NOT NULL,
5808
+ started TEXT NOT NULL,
5809
+ ended TEXT,
5810
+ status TEXT NOT NULL DEFAULT 'active',
5811
+ path TEXT,
5812
+ description TEXT,
5813
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
5814
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
5815
+ );
5816
+ INSERT INTO sessions_v2 SELECT session_id, project_slug, assignment_slug, agent, started, ended, status, path, NULL, created_at, updated_at FROM sessions;
5817
+ DROP TABLE sessions;
5818
+ ALTER TABLE sessions_v2 RENAME TO sessions;
5819
+ CREATE INDEX IF NOT EXISTS idx_sessions_project ON sessions(project_slug);
5820
+ CREATE INDEX IF NOT EXISTS idx_sessions_assignment ON sessions(project_slug, assignment_slug);
5821
+ CREATE INDEX IF NOT EXISTS idx_sessions_status ON sessions(status);
5822
+ UPDATE meta SET value = '2' WHERE key = 'schema_version';
5823
+ `);
5824
+ }
5825
+ const vBeforeV3 = database.prepare("SELECT value FROM meta WHERE key = 'schema_version'").get()?.value;
5826
+ if (vBeforeV3 === "2") {
5827
+ const v2Columns = database.prepare("PRAGMA table_info(sessions)").all();
5828
+ const v2ColNames = v2Columns.map((c2) => c2.name);
5829
+ const hasProject = v2ColNames.includes("project_slug");
5830
+ const hasMission = v2ColNames.includes("mission_slug");
5831
+ const projectSlugExpr = hasProject && hasMission ? "COALESCE(project_slug, mission_slug)" : hasProject ? "project_slug" : hasMission ? "mission_slug" : null;
5832
+ if (!projectSlugExpr) {
5833
+ throw new Error(
5834
+ "sessions table has neither project_slug nor mission_slug; cannot migrate from v2 to v3"
5835
+ );
5836
+ }
5837
+ database.exec(`
5838
+ CREATE TABLE sessions_v3 (
5839
+ session_id TEXT PRIMARY KEY,
5840
+ project_slug TEXT,
5841
+ assignment_slug TEXT,
5842
+ agent TEXT NOT NULL,
5843
+ started TEXT NOT NULL,
5844
+ ended TEXT,
5845
+ status TEXT NOT NULL DEFAULT 'active',
5846
+ path TEXT,
5847
+ description TEXT,
5848
+ transcript_path TEXT,
5849
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
5850
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
5851
+ );
5852
+ INSERT INTO sessions_v3
5853
+ SELECT session_id, ${projectSlugExpr}, assignment_slug, agent, started, ended, status, path, description, NULL, created_at, updated_at
5854
+ FROM sessions;
5855
+ DROP TABLE sessions;
5856
+ ALTER TABLE sessions_v3 RENAME TO sessions;
5857
+ CREATE INDEX IF NOT EXISTS idx_sessions_project ON sessions(project_slug);
5858
+ CREATE INDEX IF NOT EXISTS idx_sessions_assignment ON sessions(project_slug, assignment_slug);
5859
+ CREATE INDEX IF NOT EXISTS idx_sessions_status ON sessions(status);
5860
+ UPDATE meta SET value = '3' WHERE key = 'schema_version';
5861
+ `);
5862
+ }
5863
+ });
5864
+ runMigrations.exclusive();
5865
+ db.exec(POST_MIGRATION_INDEXES_SQL);
5699
5866
  return db;
5700
5867
  }
5701
5868
  function getSessionDb() {
@@ -5717,12 +5884,12 @@ async function migrateFromMarkdown(projectsDir2) {
5717
5884
  const count = database.prepare("SELECT COUNT(*) as count FROM sessions").get();
5718
5885
  if (count.count > 0) return 0;
5719
5886
  if (!await fileExists(projectsDir2)) return 0;
5720
- const entries = await readdir7(projectsDir2, { withFileTypes: true });
5887
+ const entries = await readdir8(projectsDir2, { withFileTypes: true });
5721
5888
  const allSessions = [];
5722
5889
  for (const entry of entries) {
5723
5890
  if (!entry.isDirectory()) continue;
5724
- const projectDir = resolve12(projectsDir2, entry.name);
5725
- const indexPath = resolve12(projectDir, "_index-sessions.md");
5891
+ const projectDir = resolve13(projectsDir2, entry.name);
5892
+ const indexPath = resolve13(projectDir, "_index-sessions.md");
5726
5893
  if (!await fileExists(indexPath)) continue;
5727
5894
  const sessions = await parseMarkdownSessionsIndex(indexPath, entry.name);
5728
5895
  allSessions.push(...sessions);
@@ -5742,8 +5909,8 @@ async function migrateFromMarkdown(projectsDir2) {
5742
5909
  return allSessions.length;
5743
5910
  }
5744
5911
  async function parseMarkdownSessionsIndex(filePath, projectSlug) {
5745
- const { readFile: readFile25 } = await import("fs/promises");
5746
- const raw = await readFile25(filePath, "utf-8");
5912
+ const { readFile: readFile28 } = await import("fs/promises");
5913
+ const raw = await readFile28(filePath, "utf-8");
5747
5914
  const sessions = [];
5748
5915
  const lines = raw.split("\n");
5749
5916
  let inTable = false;
@@ -5799,13 +5966,13 @@ async function appendSession(_projectDir, session) {
5799
5966
  INSERT INTO sessions (session_id, project_slug, assignment_slug, agent, started, status, path, description, transcript_path)
5800
5967
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
5801
5968
  ON CONFLICT(session_id) DO UPDATE SET
5802
- project_slug = COALESCE(excluded.project_slug, project_slug),
5803
- assignment_slug = COALESCE(excluded.assignment_slug, assignment_slug),
5969
+ project_slug = COALESCE(NULLIF(excluded.project_slug, ''), project_slug),
5970
+ assignment_slug = COALESCE(NULLIF(excluded.assignment_slug, ''), assignment_slug),
5804
5971
  agent = excluded.agent,
5805
5972
  status = CASE WHEN status IN ('completed','stopped') THEN status ELSE excluded.status END,
5806
- path = COALESCE(excluded.path, path),
5807
- description = COALESCE(excluded.description, description),
5808
- transcript_path = COALESCE(excluded.transcript_path, transcript_path),
5973
+ path = COALESCE(NULLIF(excluded.path, ''), path),
5974
+ description = COALESCE(NULLIF(excluded.description, ''), description),
5975
+ transcript_path = COALESCE(NULLIF(excluded.transcript_path, ''), transcript_path),
5809
5976
  updated_at = datetime('now')
5810
5977
  `).run(
5811
5978
  session.sessionId,
@@ -5855,13 +6022,13 @@ async function deleteSessions(sessionIds) {
5855
6022
  var DONE_ASSIGNMENT_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "review"]);
5856
6023
  async function readAssignmentStatusFromPath(assignmentMdPath) {
5857
6024
  if (!await fileExists(assignmentMdPath)) return null;
5858
- const raw = await readFile9(assignmentMdPath, "utf-8");
6025
+ const raw = await readFile10(assignmentMdPath, "utf-8");
5859
6026
  const match = raw.match(/^status:\s*(.+)$/m);
5860
6027
  return match ? match[1].trim() : null;
5861
6028
  }
5862
6029
  async function readAssignmentStatus(projectDir, assignmentSlug) {
5863
6030
  return readAssignmentStatusFromPath(
5864
- resolve13(projectDir, "assignments", assignmentSlug, "assignment.md")
6031
+ resolve14(projectDir, "assignments", assignmentSlug, "assignment.md")
5865
6032
  );
5866
6033
  }
5867
6034
  async function reconcileActiveSessions(projectsDir2, assignmentsDir2) {
@@ -5879,13 +6046,13 @@ async function reconcileActiveSessions(projectsDir2, assignmentsDir2) {
5879
6046
  seen.add(key);
5880
6047
  if (session.project_slug) {
5881
6048
  const status = await readAssignmentStatus(
5882
- resolve13(projectsDir2, session.project_slug),
6049
+ resolve14(projectsDir2, session.project_slug),
5883
6050
  aslug
5884
6051
  );
5885
6052
  if (status) assignmentStatuses.set(key, status);
5886
6053
  } else if (assignmentsDir2) {
5887
6054
  const status = await readAssignmentStatusFromPath(
5888
- resolve13(assignmentsDir2, aslug, "assignment.md")
6055
+ resolve14(assignmentsDir2, aslug, "assignment.md")
5889
6056
  );
5890
6057
  if (status) assignmentStatuses.set(key, status);
5891
6058
  }
@@ -6099,8 +6266,8 @@ init_config2();
6099
6266
  // src/dashboard/api-write.ts
6100
6267
  init_lifecycle();
6101
6268
  import { Router } from "express";
6102
- import { resolve as resolve14 } from "path";
6103
- import { rm, readFile as readFile10 } from "fs/promises";
6269
+ import { resolve as resolve15 } from "path";
6270
+ import { rm, readFile as readFile11 } from "fs/promises";
6104
6271
  init_timestamp();
6105
6272
  init_fs();
6106
6273
  init_parser();
@@ -6254,7 +6421,7 @@ async function readCurrentDocument(filePath) {
6254
6421
  if (!await fileExists(filePath)) {
6255
6422
  return null;
6256
6423
  }
6257
- return readFile10(filePath, "utf-8");
6424
+ return readFile11(filePath, "utf-8");
6258
6425
  }
6259
6426
  function createWriteRouter(projectsDir2, assignmentsDir2) {
6260
6427
  const router = Router();
@@ -6384,26 +6551,26 @@ function createWriteRouter(projectsDir2, assignmentsDir2) {
6384
6551
  res.status(400).json({ error: `Invalid slug "${slug}". Must be lowercase and hyphen-separated.` });
6385
6552
  return;
6386
6553
  }
6387
- const projectDir = resolve14(projectsDir2, slug);
6554
+ const projectDir = resolve15(projectsDir2, slug);
6388
6555
  if (await fileExists(projectDir)) {
6389
6556
  res.status(409).json({ error: `Project "${slug}" already exists` });
6390
6557
  return;
6391
6558
  }
6392
6559
  const title = fields.title;
6393
6560
  const timestamp = fields.created || nowTimestamp();
6394
- await ensureDir(resolve14(projectDir, "assignments"));
6395
- await ensureDir(resolve14(projectDir, "resources"));
6396
- await ensureDir(resolve14(projectDir, "memories"));
6397
- await writeFileForce(resolve14(projectDir, "project.md"), content);
6561
+ await ensureDir(resolve15(projectDir, "assignments"));
6562
+ await ensureDir(resolve15(projectDir, "resources"));
6563
+ await ensureDir(resolve15(projectDir, "memories"));
6564
+ await writeFileForce(resolve15(projectDir, "project.md"), content);
6398
6565
  try {
6399
6566
  const companions = [
6400
- [resolve14(projectDir, "manifest.md"), renderManifest({ slug, timestamp })],
6401
- [resolve14(projectDir, "_index-assignments.md"), renderIndexAssignments({ slug, title, timestamp })],
6402
- [resolve14(projectDir, "_index-plans.md"), renderIndexPlans({ slug, title, timestamp })],
6403
- [resolve14(projectDir, "_index-decisions.md"), renderIndexDecisions({ slug, title, timestamp })],
6404
- [resolve14(projectDir, "_status.md"), renderStatus({ slug, title, timestamp })],
6405
- [resolve14(projectDir, "resources", "_index.md"), renderResourcesIndex({ slug, title, timestamp })],
6406
- [resolve14(projectDir, "memories", "_index.md"), renderMemoriesIndex({ slug, title, timestamp })]
6567
+ [resolve15(projectDir, "manifest.md"), renderManifest({ slug, timestamp })],
6568
+ [resolve15(projectDir, "_index-assignments.md"), renderIndexAssignments({ slug, title, timestamp })],
6569
+ [resolve15(projectDir, "_index-plans.md"), renderIndexPlans({ slug, title, timestamp })],
6570
+ [resolve15(projectDir, "_index-decisions.md"), renderIndexDecisions({ slug, title, timestamp })],
6571
+ [resolve15(projectDir, "_status.md"), renderStatus({ slug, title, timestamp })],
6572
+ [resolve15(projectDir, "resources", "_index.md"), renderResourcesIndex({ slug, title, timestamp })],
6573
+ [resolve15(projectDir, "memories", "_index.md"), renderMemoriesIndex({ slug, title, timestamp })]
6407
6574
  ];
6408
6575
  for (const [filePath, fileContent] of companions) {
6409
6576
  await writeFileForce(filePath, fileContent);
@@ -6424,8 +6591,8 @@ function createWriteRouter(projectsDir2, assignmentsDir2) {
6424
6591
  router.post("/api/projects/:slug/assignments", async (req, res) => {
6425
6592
  try {
6426
6593
  const projectSlug = getParam(req.params.slug);
6427
- const projectDir = resolve14(projectsDir2, projectSlug);
6428
- const projectMdPath = resolve14(projectDir, "project.md");
6594
+ const projectDir = resolve15(projectsDir2, projectSlug);
6595
+ const projectMdPath = resolve15(projectDir, "project.md");
6429
6596
  if (!await fileExists(projectMdPath)) {
6430
6597
  res.status(404).json({ error: `Project "${projectSlug}" not found` });
6431
6598
  return;
@@ -6455,7 +6622,7 @@ function createWriteRouter(projectsDir2, assignmentsDir2) {
6455
6622
  res.status(400).json({ error: `Invalid priority "${priority}". Must be low, medium, high, or critical.` });
6456
6623
  return;
6457
6624
  }
6458
- const assignmentDir = resolve14(projectDir, "assignments", assignmentSlug);
6625
+ const assignmentDir = resolve15(projectDir, "assignments", assignmentSlug);
6459
6626
  if (await fileExists(assignmentDir)) {
6460
6627
  res.status(409).json({
6461
6628
  error: `Assignment "${assignmentSlug}" already exists in project "${projectSlug}"`
@@ -6464,12 +6631,12 @@ function createWriteRouter(projectsDir2, assignmentsDir2) {
6464
6631
  }
6465
6632
  const timestamp = fields.created || nowTimestamp();
6466
6633
  await ensureDir(assignmentDir);
6467
- await writeFileForce(resolve14(assignmentDir, "assignment.md"), content);
6634
+ await writeFileForce(resolve15(assignmentDir, "assignment.md"), content);
6468
6635
  try {
6469
6636
  const companions = [
6470
- [resolve14(assignmentDir, "scratchpad.md"), renderScratchpad({ assignmentSlug, timestamp })],
6471
- [resolve14(assignmentDir, "handoff.md"), renderHandoff({ assignmentSlug, timestamp })],
6472
- [resolve14(assignmentDir, "decision-record.md"), renderDecisionRecord({ assignmentSlug, timestamp })]
6637
+ [resolve15(assignmentDir, "scratchpad.md"), renderScratchpad({ assignmentSlug, timestamp })],
6638
+ [resolve15(assignmentDir, "handoff.md"), renderHandoff({ assignmentSlug, timestamp })],
6639
+ [resolve15(assignmentDir, "decision-record.md"), renderDecisionRecord({ assignmentSlug, timestamp })]
6473
6640
  ];
6474
6641
  for (const [filePath, fileContent] of companions) {
6475
6642
  await writeFileForce(filePath, fileContent);
@@ -6490,7 +6657,7 @@ function createWriteRouter(projectsDir2, assignmentsDir2) {
6490
6657
  router.patch("/api/projects/:slug", async (req, res) => {
6491
6658
  try {
6492
6659
  const projectSlug = getParam(req.params.slug);
6493
- const projectPath = resolve14(projectsDir2, projectSlug, "project.md");
6660
+ const projectPath = resolve15(projectsDir2, projectSlug, "project.md");
6494
6661
  const currentContent = await readCurrentDocument(projectPath);
6495
6662
  if (!currentContent) {
6496
6663
  res.status(404).json({ error: `Project "${projectSlug}" not found` });
@@ -6523,7 +6690,7 @@ function createWriteRouter(projectsDir2, assignmentsDir2) {
6523
6690
  try {
6524
6691
  const projectSlug = getParam(req.params.slug);
6525
6692
  const assignmentSlug = getParam(req.params.aslug);
6526
- const assignmentPath = resolve14(
6693
+ const assignmentPath = resolve15(
6527
6694
  projectsDir2,
6528
6695
  projectSlug,
6529
6696
  "assignments",
@@ -6566,7 +6733,7 @@ function createWriteRouter(projectsDir2, assignmentsDir2) {
6566
6733
  try {
6567
6734
  const projectSlug = getParam(req.params.slug);
6568
6735
  const assignmentSlug = getParam(req.params.aslug);
6569
- const assignmentPath = resolve14(
6736
+ const assignmentPath = resolve15(
6570
6737
  projectsDir2,
6571
6738
  projectSlug,
6572
6739
  "assignments",
@@ -6602,7 +6769,7 @@ function createWriteRouter(projectsDir2, assignmentsDir2) {
6602
6769
  try {
6603
6770
  const projectSlug = getParam(req.params.slug);
6604
6771
  const assignmentSlug = getParam(req.params.aslug);
6605
- const planPath = resolve14(
6772
+ const planPath = resolve15(
6606
6773
  projectsDir2,
6607
6774
  projectSlug,
6608
6775
  "assignments",
@@ -6640,7 +6807,7 @@ function createWriteRouter(projectsDir2, assignmentsDir2) {
6640
6807
  try {
6641
6808
  const projectSlug = getParam(req.params.slug);
6642
6809
  const assignmentSlug = getParam(req.params.aslug);
6643
- const scratchpadPath = resolve14(
6810
+ const scratchpadPath = resolve15(
6644
6811
  projectsDir2,
6645
6812
  projectSlug,
6646
6813
  "assignments",
@@ -6678,7 +6845,7 @@ function createWriteRouter(projectsDir2, assignmentsDir2) {
6678
6845
  try {
6679
6846
  const projectSlug = getParam(req.params.slug);
6680
6847
  const assignmentSlug = getParam(req.params.aslug);
6681
- const handoffPath = resolve14(
6848
+ const handoffPath = resolve15(
6682
6849
  projectsDir2,
6683
6850
  projectSlug,
6684
6851
  "assignments",
@@ -6716,7 +6883,7 @@ function createWriteRouter(projectsDir2, assignmentsDir2) {
6716
6883
  try {
6717
6884
  const projectSlug = getParam(req.params.slug);
6718
6885
  const assignmentSlug = getParam(req.params.aslug);
6719
- const decisionPath = resolve14(
6886
+ const decisionPath = resolve15(
6720
6887
  projectsDir2,
6721
6888
  projectSlug,
6722
6889
  "assignments",
@@ -6754,7 +6921,7 @@ function createWriteRouter(projectsDir2, assignmentsDir2) {
6754
6921
  try {
6755
6922
  const projectSlug = getParam(req.params.slug);
6756
6923
  const assignmentSlug = getParam(req.params.aslug);
6757
- const commentsPath = resolve14(
6924
+ const commentsPath = resolve15(
6758
6925
  projectsDir2,
6759
6926
  projectSlug,
6760
6927
  "assignments",
@@ -6772,7 +6939,7 @@ function createWriteRouter(projectsDir2, assignmentsDir2) {
6772
6939
  let currentContent;
6773
6940
  let currentCount = 0;
6774
6941
  if (await fileExists(commentsPath)) {
6775
- currentContent = await readFile10(commentsPath, "utf-8");
6942
+ currentContent = await readFile11(commentsPath, "utf-8");
6776
6943
  const countMatch = currentContent.match(/^entryCount:\s*(\d+)/m);
6777
6944
  if (countMatch) currentCount = parseInt(countMatch[1], 10);
6778
6945
  } else {
@@ -6813,7 +6980,7 @@ ${entry}`;
6813
6980
  const projectSlug = getParam(req.params.slug);
6814
6981
  const assignmentSlug = getParam(req.params.aslug);
6815
6982
  const commentId = getParam(req.params.commentId);
6816
- const commentsPath = resolve14(
6983
+ const commentsPath = resolve15(
6817
6984
  projectsDir2,
6818
6985
  projectSlug,
6819
6986
  "assignments",
@@ -6829,7 +6996,7 @@ ${entry}`;
6829
6996
  res.status(400).json({ error: "resolved (boolean) is required" });
6830
6997
  return;
6831
6998
  }
6832
- const content = await readFile10(commentsPath, "utf-8");
6999
+ const content = await readFile11(commentsPath, "utf-8");
6833
7000
  const parsed = parseComments(content);
6834
7001
  const target = parsed.entries.find((e) => e.id === commentId);
6835
7002
  if (!target) {
@@ -6864,7 +7031,7 @@ ${entry}`;
6864
7031
  router.post("/api/projects/:slug/move-workspace", async (req, res) => {
6865
7032
  try {
6866
7033
  const projectSlug = getParam(req.params.slug);
6867
- const projectPath = resolve14(projectsDir2, projectSlug, "project.md");
7034
+ const projectPath = resolve15(projectsDir2, projectSlug, "project.md");
6868
7035
  if (!await fileExists(projectPath)) {
6869
7036
  res.status(404).json({ error: `Project "${projectSlug}" not found` });
6870
7037
  return;
@@ -6874,7 +7041,7 @@ ${entry}`;
6874
7041
  res.status(400).json({ error: "workspace must be a non-empty string or null (for ungrouped)." });
6875
7042
  return;
6876
7043
  }
6877
- let content = await readFile10(projectPath, "utf-8");
7044
+ let content = await readFile11(projectPath, "utf-8");
6878
7045
  content = setTopLevelField(content, "workspace", workspace ?? null);
6879
7046
  content = setTopLevelField(content, "updated", nowTimestamp());
6880
7047
  await writeFileForce(projectPath, content);
@@ -6888,7 +7055,7 @@ ${entry}`;
6888
7055
  router.post("/api/projects/:slug/status-override", async (req, res) => {
6889
7056
  try {
6890
7057
  const projectSlug = getParam(req.params.slug);
6891
- const projectPath = resolve14(projectsDir2, projectSlug, "project.md");
7058
+ const projectPath = resolve15(projectsDir2, projectSlug, "project.md");
6892
7059
  if (!await fileExists(projectPath)) {
6893
7060
  res.status(404).json({ error: `Project "${projectSlug}" not found` });
6894
7061
  return;
@@ -6900,7 +7067,7 @@ ${entry}`;
6900
7067
  res.status(400).json({ error: `Invalid status. Must be one of: ${validStatuses.join(", ")}, or null to clear.` });
6901
7068
  return;
6902
7069
  }
6903
- let content = await readFile10(projectPath, "utf-8");
7070
+ let content = await readFile11(projectPath, "utf-8");
6904
7071
  content = setTopLevelField(content, "statusOverride", status ?? null);
6905
7072
  content = setTopLevelField(content, "updated", nowTimestamp());
6906
7073
  await writeFileForce(projectPath, content);
@@ -6915,7 +7082,7 @@ ${entry}`;
6915
7082
  try {
6916
7083
  const projectSlug = getParam(req.params.slug);
6917
7084
  const assignmentSlug = getParam(req.params.aslug);
6918
- const assignmentPath = resolve14(
7085
+ const assignmentPath = resolve15(
6919
7086
  projectsDir2,
6920
7087
  projectSlug,
6921
7088
  "assignments",
@@ -6933,7 +7100,7 @@ ${entry}`;
6933
7100
  res.status(400).json({ error: `Invalid status. Must be one of: ${validStatuses.join(", ")}.` });
6934
7101
  return;
6935
7102
  }
6936
- let content = await readFile10(assignmentPath, "utf-8");
7103
+ let content = await readFile11(assignmentPath, "utf-8");
6937
7104
  content = setTopLevelField(content, "status", status);
6938
7105
  content = setTopLevelField(content, "updated", nowTimestamp());
6939
7106
  if (status !== "blocked") {
@@ -6958,8 +7125,8 @@ ${entry}`;
6958
7125
  res.status(400).json({ error: `Unsupported transition command "${req.params.command}"` });
6959
7126
  return;
6960
7127
  }
6961
- const projectDir = resolve14(projectsDir2, projectSlug);
6962
- const assignmentPath = resolve14(projectDir, "assignments", assignmentSlug, "assignment.md");
7128
+ const projectDir = resolve15(projectsDir2, projectSlug);
7129
+ const assignmentPath = resolve15(projectDir, "assignments", assignmentSlug, "assignment.md");
6963
7130
  if (!await fileExists(assignmentPath)) {
6964
7131
  res.status(404).json({ error: "Assignment not found" });
6965
7132
  return;
@@ -6985,8 +7152,8 @@ ${entry}`;
6985
7152
  try {
6986
7153
  const projectSlug = getParam(req.params.slug);
6987
7154
  const assignmentSlug = getParam(req.params.aslug);
6988
- const assignmentDir = resolve14(projectsDir2, projectSlug, "assignments", assignmentSlug);
6989
- const assignmentPath = resolve14(assignmentDir, "assignment.md");
7155
+ const assignmentDir = resolve15(projectsDir2, projectSlug, "assignments", assignmentSlug);
7156
+ const assignmentPath = resolve15(assignmentDir, "assignment.md");
6990
7157
  if (!await fileExists(assignmentPath)) {
6991
7158
  res.status(404).json({ error: `Assignment "${assignmentSlug}" not found in project "${projectSlug}"` });
6992
7159
  return;
@@ -7015,7 +7182,7 @@ ${entry}`;
7015
7182
  return;
7016
7183
  }
7017
7184
  const id = generateId();
7018
- const assignmentDir = resolve14(assignmentsDir2, id);
7185
+ const assignmentDir = resolve15(assignmentsDir2, id);
7019
7186
  if (await fileExists(assignmentDir)) {
7020
7187
  res.status(500).json({ error: "UUID collision \u2014 try again" });
7021
7188
  return;
@@ -7035,25 +7202,25 @@ ${entry}`;
7035
7202
  project: null,
7036
7203
  type: typeof type === "string" ? type : void 0
7037
7204
  });
7038
- await writeFileForce(resolve14(assignmentDir, "assignment.md"), assignmentContent);
7205
+ await writeFileForce(resolve15(assignmentDir, "assignment.md"), assignmentContent);
7039
7206
  await writeFileForce(
7040
- resolve14(assignmentDir, "scratchpad.md"),
7207
+ resolve15(assignmentDir, "scratchpad.md"),
7041
7208
  renderScratchpad({ assignmentSlug: id, timestamp })
7042
7209
  );
7043
7210
  await writeFileForce(
7044
- resolve14(assignmentDir, "handoff.md"),
7211
+ resolve15(assignmentDir, "handoff.md"),
7045
7212
  renderHandoff({ assignmentSlug: id, timestamp })
7046
7213
  );
7047
7214
  await writeFileForce(
7048
- resolve14(assignmentDir, "decision-record.md"),
7215
+ resolve15(assignmentDir, "decision-record.md"),
7049
7216
  renderDecisionRecord({ assignmentSlug: id, timestamp })
7050
7217
  );
7051
7218
  await writeFileForce(
7052
- resolve14(assignmentDir, "progress.md"),
7219
+ resolve15(assignmentDir, "progress.md"),
7053
7220
  renderProgress({ assignment: id, timestamp })
7054
7221
  );
7055
7222
  await writeFileForce(
7056
- resolve14(assignmentDir, "comments.md"),
7223
+ resolve15(assignmentDir, "comments.md"),
7057
7224
  renderComments({ assignment: id, timestamp })
7058
7225
  );
7059
7226
  const detail = await getAssignmentDetailById(projectsDir2, assignmentsDir2, id);
@@ -7181,7 +7348,7 @@ ${entry}`;
7181
7348
  res.status(404).json({ error: `Assignment "${id}" not found` });
7182
7349
  return;
7183
7350
  }
7184
- const assignmentPath = resolve14(resolved.assignmentDir, "assignment.md");
7351
+ const assignmentPath = resolve15(resolved.assignmentDir, "assignment.md");
7185
7352
  const currentContent = await readCurrentDocument(assignmentPath);
7186
7353
  if (!currentContent) {
7187
7354
  res.status(404).json({ error: "Assignment not found" });
@@ -7223,7 +7390,7 @@ ${entry}`;
7223
7390
  res.status(404).json({ error: `Assignment "${id}" not found` });
7224
7391
  return;
7225
7392
  }
7226
- const planPath = resolve14(resolved.assignmentDir, "plan.md");
7393
+ const planPath = resolve15(resolved.assignmentDir, "plan.md");
7227
7394
  const currentContent = await readCurrentDocument(planPath);
7228
7395
  if (!currentContent) {
7229
7396
  res.status(404).json({ error: "Plan not found" });
@@ -7257,7 +7424,7 @@ ${entry}`;
7257
7424
  res.status(404).json({ error: `Assignment "${id}" not found` });
7258
7425
  return;
7259
7426
  }
7260
- const scratchpadPath = resolve14(resolved.assignmentDir, "scratchpad.md");
7427
+ const scratchpadPath = resolve15(resolved.assignmentDir, "scratchpad.md");
7261
7428
  const currentContent = await readCurrentDocument(scratchpadPath);
7262
7429
  if (!currentContent) {
7263
7430
  res.status(404).json({ error: "Scratchpad not found" });
@@ -7291,7 +7458,7 @@ ${entry}`;
7291
7458
  res.status(404).json({ error: `Assignment "${id}" not found` });
7292
7459
  return;
7293
7460
  }
7294
- const handoffPath = resolve14(resolved.assignmentDir, "handoff.md");
7461
+ const handoffPath = resolve15(resolved.assignmentDir, "handoff.md");
7295
7462
  const currentContent = await readCurrentDocument(handoffPath);
7296
7463
  if (!currentContent) {
7297
7464
  res.status(404).json({ error: "Handoff log not found" });
@@ -7331,7 +7498,7 @@ ${entry}`;
7331
7498
  res.status(404).json({ error: `Assignment "${id}" not found` });
7332
7499
  return;
7333
7500
  }
7334
- const decisionPath = resolve14(resolved.assignmentDir, "decision-record.md");
7501
+ const decisionPath = resolve15(resolved.assignmentDir, "decision-record.md");
7335
7502
  const currentContent = await readCurrentDocument(decisionPath);
7336
7503
  if (!currentContent) {
7337
7504
  res.status(404).json({ error: "Decision record not found" });
@@ -7371,7 +7538,7 @@ ${entry}`;
7371
7538
  res.status(404).json({ error: `Assignment "${id}" not found` });
7372
7539
  return;
7373
7540
  }
7374
- const assignmentPath = resolve14(resolved.assignmentDir, "assignment.md");
7541
+ const assignmentPath = resolve15(resolved.assignmentDir, "assignment.md");
7375
7542
  if (!await fileExists(assignmentPath)) {
7376
7543
  res.status(404).json({ error: "Assignment not found" });
7377
7544
  return;
@@ -7383,7 +7550,7 @@ ${entry}`;
7383
7550
  res.status(400).json({ error: `Invalid status. Must be one of: ${validStatuses.join(", ")}.` });
7384
7551
  return;
7385
7552
  }
7386
- let content = await readFile10(assignmentPath, "utf-8");
7553
+ let content = await readFile11(assignmentPath, "utf-8");
7387
7554
  content = setTopLevelField(content, "status", status);
7388
7555
  content = setTopLevelField(content, "updated", nowTimestamp());
7389
7556
  if (status !== "blocked") {
@@ -7409,7 +7576,7 @@ ${entry}`;
7409
7576
  res.status(404).json({ error: `Assignment "${id}" not found` });
7410
7577
  return;
7411
7578
  }
7412
- const assignmentPath = resolve14(resolved.assignmentDir, "assignment.md");
7579
+ const assignmentPath = resolve15(resolved.assignmentDir, "assignment.md");
7413
7580
  const currentContent = await readCurrentDocument(assignmentPath);
7414
7581
  if (!currentContent) {
7415
7582
  res.status(404).json({ error: "Assignment not found" });
@@ -7474,7 +7641,7 @@ function slugifyLocal(input2) {
7474
7641
  return input2.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "untitled";
7475
7642
  }
7476
7643
  async function appendCommentTo(assignmentDir, assignmentRef, req, res, reloadDetail) {
7477
- const commentsPath = resolve14(assignmentDir, "comments.md");
7644
+ const commentsPath = resolve15(assignmentDir, "comments.md");
7478
7645
  const { body, author, type, replyTo } = req.body || {};
7479
7646
  if (!body || typeof body !== "string" || !body.trim()) {
7480
7647
  res.status(400).json({ error: "body is required" });
@@ -7486,7 +7653,7 @@ async function appendCommentTo(assignmentDir, assignmentRef, req, res, reloadDet
7486
7653
  let currentContent;
7487
7654
  let currentCount = 0;
7488
7655
  if (await fileExists(commentsPath)) {
7489
- currentContent = await readFile10(commentsPath, "utf-8");
7656
+ currentContent = await readFile11(commentsPath, "utf-8");
7490
7657
  const countMatch = currentContent.match(/^entryCount:\s*(\d+)/m);
7491
7658
  if (countMatch) currentCount = parseInt(countMatch[1], 10);
7492
7659
  } else {
@@ -7516,7 +7683,7 @@ ${entry}`;
7516
7683
  res.status(201).json({ assignment, comment: { id: comment.id } });
7517
7684
  }
7518
7685
  async function toggleCommentResolvedAt(assignmentDir, commentId, req, res, reloadDetail) {
7519
- const commentsPath = resolve14(assignmentDir, "comments.md");
7686
+ const commentsPath = resolve15(assignmentDir, "comments.md");
7520
7687
  if (!await fileExists(commentsPath)) {
7521
7688
  res.status(404).json({ error: "Comments file not found" });
7522
7689
  return;
@@ -7526,7 +7693,7 @@ async function toggleCommentResolvedAt(assignmentDir, commentId, req, res, reloa
7526
7693
  res.status(400).json({ error: "resolved (boolean) is required" });
7527
7694
  return;
7528
7695
  }
7529
- const content = await readFile10(commentsPath, "utf-8");
7696
+ const content = await readFile11(commentsPath, "utf-8");
7530
7697
  const parsed = parseComments(content);
7531
7698
  const target = parsed.entries.find((e) => e.id === commentId);
7532
7699
  if (!target) {
@@ -7671,7 +7838,7 @@ function createServersRouter(serversDir2, projectsDir2, assignmentsDir2) {
7671
7838
 
7672
7839
  // src/dashboard/api-agent-sessions.ts
7673
7840
  import { Router as Router3 } from "express";
7674
- import { resolve as resolve15 } from "path";
7841
+ import { resolve as resolve16 } from "path";
7675
7842
  init_fs();
7676
7843
  function createAgentSessionsRouter(projectsDir2, broadcast, assignmentsDir2) {
7677
7844
  const router = Router3();
@@ -7688,7 +7855,7 @@ function createAgentSessionsRouter(projectsDir2, broadcast, assignmentsDir2) {
7688
7855
  try {
7689
7856
  const { projectSlug } = req.params;
7690
7857
  const assignment = req.query.assignment;
7691
- const projectDir = resolve15(projectsDir2, projectSlug);
7858
+ const projectDir = resolve16(projectsDir2, projectSlug);
7692
7859
  if (!await fileExists(projectDir)) {
7693
7860
  res.status(404).json({ error: `Project "${projectSlug}" not found` });
7694
7861
  return;
@@ -7714,7 +7881,7 @@ function createAgentSessionsRouter(projectsDir2, broadcast, assignmentsDir2) {
7714
7881
  return;
7715
7882
  }
7716
7883
  if (projectSlug) {
7717
- const projectDir = resolve15(projectsDir2, projectSlug);
7884
+ const projectDir = resolve16(projectsDir2, projectSlug);
7718
7885
  if (!await fileExists(projectDir)) {
7719
7886
  res.status(404).json({ error: `Project "${projectSlug}" not found` });
7720
7887
  return;
@@ -7782,8 +7949,8 @@ function createAgentSessionsRouter(projectsDir2, broadcast, assignmentsDir2) {
7782
7949
  init_api();
7783
7950
  init_parser();
7784
7951
  import { Router as Router4 } from "express";
7785
- import { resolve as resolve16 } from "path";
7786
- import { readFile as readFile11, unlink as unlink2 } from "fs/promises";
7952
+ import { resolve as resolve17 } from "path";
7953
+ import { readFile as readFile12, unlink as unlink2 } from "fs/promises";
7787
7954
  init_timestamp();
7788
7955
  init_fs();
7789
7956
  function createPlaybooksRouter(playbooksDir3) {
@@ -7823,12 +7990,12 @@ function createPlaybooksRouter(playbooksDir3) {
7823
7990
  });
7824
7991
  router.get("/:slug/edit", async (req, res) => {
7825
7992
  try {
7826
- const filePath = resolve16(playbooksDir3, `${req.params.slug}.md`);
7993
+ const filePath = resolve17(playbooksDir3, `${req.params.slug}.md`);
7827
7994
  if (!await fileExists(filePath)) {
7828
7995
  res.status(404).json({ error: `Playbook "${req.params.slug}" not found` });
7829
7996
  return;
7830
7997
  }
7831
- const content = await readFile11(filePath, "utf-8");
7998
+ const content = await readFile12(filePath, "utf-8");
7832
7999
  res.json({
7833
8000
  documentType: "playbook",
7834
8001
  title: `Edit Playbook: ${req.params.slug}`,
@@ -7853,7 +8020,7 @@ function createPlaybooksRouter(playbooksDir3) {
7853
8020
  return;
7854
8021
  }
7855
8022
  await ensureDir(playbooksDir3);
7856
- const filePath = resolve16(playbooksDir3, `${slug}.md`);
8023
+ const filePath = resolve17(playbooksDir3, `${slug}.md`);
7857
8024
  if (await fileExists(filePath)) {
7858
8025
  res.status(409).json({ error: `Playbook "${slug}" already exists` });
7859
8026
  return;
@@ -7872,7 +8039,7 @@ function createPlaybooksRouter(playbooksDir3) {
7872
8039
  res.status(400).json({ error: "content is required" });
7873
8040
  return;
7874
8041
  }
7875
- const filePath = resolve16(playbooksDir3, `${req.params.slug}.md`);
8042
+ const filePath = resolve17(playbooksDir3, `${req.params.slug}.md`);
7876
8043
  if (!await fileExists(filePath)) {
7877
8044
  res.status(404).json({ error: `Playbook "${req.params.slug}" not found` });
7878
8045
  return;
@@ -7890,7 +8057,7 @@ function createPlaybooksRouter(playbooksDir3) {
7890
8057
  res.status(403).json({ error: "The playbook manifest cannot be deleted" });
7891
8058
  return;
7892
8059
  }
7893
- const filePath = resolve16(playbooksDir3, `${req.params.slug}.md`);
8060
+ const filePath = resolve17(playbooksDir3, `${req.params.slug}.md`);
7894
8061
  if (!await fileExists(filePath)) {
7895
8062
  res.status(404).json({ error: `Playbook "${req.params.slug}" not found` });
7896
8063
  return;
@@ -7905,11 +8072,14 @@ function createPlaybooksRouter(playbooksDir3) {
7905
8072
  return router;
7906
8073
  }
7907
8074
 
8075
+ // src/dashboard/server.ts
8076
+ init_fs_migration();
8077
+
7908
8078
  // src/dashboard/api-todos.ts
7909
8079
  init_parser2();
7910
8080
  init_fs();
7911
8081
  import { Router as Router5 } from "express";
7912
- import { readdir as readdir8 } from "fs/promises";
8082
+ import { readdir as readdir9 } from "fs/promises";
7913
8083
  var WORKSPACE_REGEX = /^[a-z0-9_][a-z0-9-]*$/;
7914
8084
  function getWorkspaceParam(value) {
7915
8085
  if (Array.isArray(value)) {
@@ -7943,7 +8113,7 @@ function createTodosRouter(todosDir2, broadcast) {
7943
8113
  router.get("/", async (_req, res) => {
7944
8114
  try {
7945
8115
  await ensureDir(todosDir2);
7946
- const files = await readdir8(todosDir2).catch(() => []);
8116
+ const files = await readdir9(todosDir2).catch(() => []);
7947
8117
  const workspaces = [];
7948
8118
  for (const file of files) {
7949
8119
  if (typeof file !== "string") continue;
@@ -8048,8 +8218,8 @@ function createTodosRouter(todosDir2, broadcast) {
8048
8218
  router.post("/:workspace/archive", async (req, res) => {
8049
8219
  try {
8050
8220
  const { archivePath: archivePath2 } = await Promise.resolve().then(() => (init_parser2(), parser_exports));
8051
- const { resolve: resolve41 } = await import("path");
8052
- const { readFile: readFile25 } = await import("fs/promises");
8221
+ const { resolve: resolve44 } = await import("path");
8222
+ const { readFile: readFile28 } = await import("fs/promises");
8053
8223
  const { writeFileForce: writeFileForce2 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
8054
8224
  const workspace = getWorkspaceParam(req.params.workspace);
8055
8225
  const checklist = await readChecklist(todosDir2, workspace);
@@ -8065,10 +8235,10 @@ function createTodosRouter(todosDir2, broadcast) {
8065
8235
  (e) => e.itemIds.every((id) => completedIds.has(id))
8066
8236
  );
8067
8237
  const archFile = archivePath2(todosDir2, workspace, checklist.archiveInterval);
8068
- await ensureDir(resolve41(todosDir2, "archive"));
8238
+ await ensureDir(resolve44(todosDir2, "archive"));
8069
8239
  let archContent = "";
8070
8240
  if (await fileExists(archFile)) {
8071
- archContent = await readFile25(archFile, "utf-8");
8241
+ archContent = await readFile28(archFile, "utf-8");
8072
8242
  archContent = archContent.trimEnd() + "\n\n";
8073
8243
  } else {
8074
8244
  archContent = `---
@@ -8328,8 +8498,8 @@ init_fs();
8328
8498
  init_config2();
8329
8499
  import { execFile as execFile2 } from "child_process";
8330
8500
  import { promisify as promisify2 } from "util";
8331
- import { cp, mkdtemp, rm as rm2, readFile as readFile13, writeFile as writeFile3, unlink as unlink3, stat, open, rename as rename2 } from "fs/promises";
8332
- import { resolve as resolve18, join as join2 } from "path";
8501
+ import { cp, mkdtemp, rm as rm2, readFile as readFile14, writeFile as writeFile4, unlink as unlink3, stat, open, rename as rename3 } from "fs/promises";
8502
+ import { resolve as resolve19, join as join2 } from "path";
8333
8503
  import { tmpdir } from "os";
8334
8504
  var exec2 = promisify2(execFile2);
8335
8505
  var VALID_CATEGORIES = ["projects", "playbooks", "todos", "servers", "config"];
@@ -8369,7 +8539,7 @@ async function resolveCategoryPath(category) {
8369
8539
  case "servers":
8370
8540
  return { sourcePath: serversDir(), repoPath: "servers", isFile: false };
8371
8541
  case "config":
8372
- return { sourcePath: resolve18(syntaurRoot(), "config.md"), repoPath: "config.md", isFile: true };
8542
+ return { sourcePath: resolve19(syntaurRoot(), "config.md"), repoPath: "config.md", isFile: true };
8373
8543
  }
8374
8544
  }
8375
8545
  async function checkGitInstalled() {
@@ -8380,7 +8550,7 @@ async function checkGitInstalled() {
8380
8550
  }
8381
8551
  }
8382
8552
  async function acquireLock() {
8383
- const lockPath = resolve18(syntaurRoot(), LOCK_FILE_NAME);
8553
+ const lockPath = resolve19(syntaurRoot(), LOCK_FILE_NAME);
8384
8554
  await ensureDir(syntaurRoot());
8385
8555
  try {
8386
8556
  const handle = await open(lockPath, "wx");
@@ -8389,7 +8559,7 @@ async function acquireLock() {
8389
8559
  return lockPath;
8390
8560
  } catch (err2) {
8391
8561
  if (err2.code === "EEXIST") {
8392
- const pid = await readFile13(lockPath, "utf-8").catch(() => "");
8562
+ const pid = await readFile14(lockPath, "utf-8").catch(() => "");
8393
8563
  throw new Error(
8394
8564
  `Backup operation already in progress (lock file at ${lockPath}, pid ${pid.trim() || "unknown"}). If stale, delete the file and retry.`
8395
8565
  );
@@ -8427,7 +8597,7 @@ async function copyRecursive(src, dest) {
8427
8597
  await ensureDir(dest);
8428
8598
  await cp(src, dest, { recursive: true, force: true });
8429
8599
  } else {
8430
- await ensureDir(resolve18(dest, ".."));
8600
+ await ensureDir(resolve19(dest, ".."));
8431
8601
  await cp(src, dest, { force: true });
8432
8602
  }
8433
8603
  }
@@ -8436,7 +8606,7 @@ function resolveCategoriesStrict(csv) {
8436
8606
  return parseCategoriesStrict(parts);
8437
8607
  }
8438
8608
  async function readSanitizedConfig(configPath) {
8439
- const content = await readFile13(configPath, "utf-8");
8609
+ const content = await readFile14(configPath, "utf-8");
8440
8610
  return content.replace(/^(\s*lastBackup:\s*).*$/m, "$1null").replace(/^(\s*lastRestore:\s*).*$/m, "$1null");
8441
8611
  }
8442
8612
  async function backupToGithub(overrides) {
@@ -8475,8 +8645,8 @@ async function backupToGithub(overrides) {
8475
8645
  }
8476
8646
  if (category === "config") {
8477
8647
  const sanitized = await readSanitizedConfig(sourcePath);
8478
- await ensureDir(resolve18(destPath, ".."));
8479
- await writeFile3(destPath, sanitized, "utf-8");
8648
+ await ensureDir(resolve19(destPath, ".."));
8649
+ await writeFile4(destPath, sanitized, "utf-8");
8480
8650
  } else {
8481
8651
  await copyRecursive(sourcePath, destPath);
8482
8652
  }
@@ -8529,7 +8699,7 @@ async function backupToGithub(overrides) {
8529
8699
  }
8530
8700
  async function safeRestoreCategory(localPath, repoSrcPath, isFile) {
8531
8701
  if (isFile) {
8532
- await ensureDir(resolve18(localPath, ".."));
8702
+ await ensureDir(resolve19(localPath, ".."));
8533
8703
  await cp(repoSrcPath, localPath, { force: true });
8534
8704
  return;
8535
8705
  }
@@ -8540,7 +8710,7 @@ async function safeRestoreCategory(localPath, repoSrcPath, isFile) {
8540
8710
  const localExistsBefore = await fileExists(localPath);
8541
8711
  if (backupExistsBefore) {
8542
8712
  if (!localExistsBefore) {
8543
- await rename2(backupPath, localPath);
8713
+ await rename3(backupPath, localPath);
8544
8714
  } else {
8545
8715
  throw new Error(
8546
8716
  `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.`
@@ -8552,15 +8722,15 @@ async function safeRestoreCategory(localPath, repoSrcPath, isFile) {
8552
8722
  await cp(repoSrcPath, stagingPath, { recursive: true, force: true });
8553
8723
  const localExists = await fileExists(localPath);
8554
8724
  if (localExists) {
8555
- await rename2(localPath, backupPath);
8725
+ await rename3(localPath, backupPath);
8556
8726
  localMovedAside = true;
8557
8727
  }
8558
- await rename2(stagingPath, localPath);
8728
+ await rename3(stagingPath, localPath);
8559
8729
  await rm2(backupPath, { recursive: true, force: true }).catch(() => {
8560
8730
  });
8561
8731
  } catch (err2) {
8562
8732
  if (localMovedAside && await fileExists(backupPath)) {
8563
- await rename2(backupPath, localPath).catch(() => {
8733
+ await rename3(backupPath, localPath).catch(() => {
8564
8734
  });
8565
8735
  }
8566
8736
  await rm2(stagingPath, { recursive: true, force: true }).catch(() => {
@@ -8630,7 +8800,7 @@ async function restoreFromGithub(overrides) {
8630
8800
  }
8631
8801
  async function getBackupStatus() {
8632
8802
  const config = await readConfig();
8633
- const lockPath = resolve18(syntaurRoot(), LOCK_FILE_NAME);
8803
+ const lockPath = resolve19(syntaurRoot(), LOCK_FILE_NAME);
8634
8804
  const locked = await fileExists(lockPath);
8635
8805
  return {
8636
8806
  repo: config.backup?.repo ?? null,
@@ -8964,6 +9134,18 @@ function createDashboardServer(options) {
8964
9134
  migrateFromMarkdown(projectsDir2).catch((err2) => {
8965
9135
  console.error("Session migration from markdown failed:", err2);
8966
9136
  });
9137
+ (async () => {
9138
+ try {
9139
+ const configResult = await migrateLegacyConfig(
9140
+ resolve20(syntaurRoot(), "config.md")
9141
+ );
9142
+ const projectResult = await migrateLegacyProjectFiles(projectsDir2);
9143
+ const summary = summarizeMigration(projectResult, configResult);
9144
+ if (summary) console.log(summary);
9145
+ } catch (err2) {
9146
+ console.error("Legacy filesystem migration failed:", err2);
9147
+ }
9148
+ })();
8967
9149
  app.use(express.json());
8968
9150
  app.get("/api/overview", async (_req, res) => {
8969
9151
  try {
@@ -9183,7 +9365,7 @@ function createDashboardServer(options) {
9183
9365
  if (serveStaticUi && dashboardDistPath) {
9184
9366
  app.use(express.static(dashboardDistPath));
9185
9367
  app.get("{*path}", async (_req, res) => {
9186
- const indexPath = resolve19(dashboardDistPath, "index.html");
9368
+ const indexPath = resolve20(dashboardDistPath, "index.html");
9187
9369
  if (await fileExists(indexPath)) {
9188
9370
  res.sendFile(indexPath);
9189
9371
  } else {
@@ -9216,8 +9398,8 @@ function createDashboardServer(options) {
9216
9398
  }
9217
9399
  });
9218
9400
  server.listen(port, () => {
9219
- const portFile = resolve19(syntaurRoot(), "dashboard-port");
9220
- writeFile4(portFile, String(port), "utf-8").catch(() => {
9401
+ const portFile = resolve20(syntaurRoot(), "dashboard-port");
9402
+ writeFile5(portFile, String(port), "utf-8").catch(() => {
9221
9403
  });
9222
9404
  resolvePromise();
9223
9405
  });
@@ -9233,7 +9415,7 @@ function createDashboardServer(options) {
9233
9415
  client.terminate();
9234
9416
  }
9235
9417
  clients.clear();
9236
- const portFile = resolve19(syntaurRoot(), "dashboard-port");
9418
+ const portFile = resolve20(syntaurRoot(), "dashboard-port");
9237
9419
  await unlink4(portFile).catch(() => {
9238
9420
  });
9239
9421
  server.closeAllConnections?.();
@@ -9313,8 +9495,8 @@ async function dashboardCommand(options) {
9313
9495
  port = availablePort;
9314
9496
  }
9315
9497
  const thisFile = fileURLToPath2(import.meta.url);
9316
- const packageRoot = resolve20(dirname4(thisFile), "..");
9317
- const dashboardDist = resolve20(packageRoot, "dashboard", "dist");
9498
+ const packageRoot = resolve21(dirname4(thisFile), "..");
9499
+ const dashboardDist = resolve21(packageRoot, "dashboard", "dist");
9318
9500
  const server = createDashboardServer({
9319
9501
  port,
9320
9502
  projectsDir: projectsDir2,
@@ -9328,8 +9510,8 @@ async function dashboardCommand(options) {
9328
9510
  await server.start();
9329
9511
  let viteProcess = null;
9330
9512
  if (mode === "dev") {
9331
- const dashboardDir = resolve20(packageRoot, "dashboard");
9332
- const viteBin = resolve20(dashboardDir, "node_modules", ".bin", "vite");
9513
+ const dashboardDir = resolve21(packageRoot, "dashboard");
9514
+ const viteBin = resolve21(dashboardDir, "node_modules", ".bin", "vite");
9333
9515
  if (!await fileExists(viteBin)) {
9334
9516
  console.error(
9335
9517
  'Vite not found. Run "npm ci --prefix dashboard" first, or use the default bundled dashboard mode.'
@@ -9401,7 +9583,7 @@ async function dashboardCommand(options) {
9401
9583
  init_paths();
9402
9584
  init_fs();
9403
9585
  init_config2();
9404
- import { resolve as resolve21 } from "path";
9586
+ import { resolve as resolve22 } from "path";
9405
9587
  init_lifecycle();
9406
9588
  init_assignment_resolver();
9407
9589
  async function runTransition(assignment, command, options = {}) {
@@ -9414,8 +9596,8 @@ async function runTransition(assignment, command, options = {}) {
9414
9596
  if (!isValidSlug(assignment)) {
9415
9597
  throw new Error(`Invalid assignment slug "${assignment}".`);
9416
9598
  }
9417
- const projectDir = resolve21(baseDir, options.project);
9418
- const projectMdPath = resolve21(projectDir, "project.md");
9599
+ const projectDir = resolve22(baseDir, options.project);
9600
+ const projectMdPath = resolve22(projectDir, "project.md");
9419
9601
  if (!await fileExists(projectDir) || !await fileExists(projectMdPath)) {
9420
9602
  throw new Error(`Project "${options.project}" not found at ${projectDir}.`);
9421
9603
  }
@@ -9446,8 +9628,8 @@ async function runAssign(assignment, agent, options = {}) {
9446
9628
  if (!isValidSlug(assignment)) {
9447
9629
  throw new Error(`Invalid assignment slug "${assignment}".`);
9448
9630
  }
9449
- const projectDir = resolve21(baseDir, options.project);
9450
- const projectMdPath = resolve21(projectDir, "project.md");
9631
+ const projectDir = resolve22(baseDir, options.project);
9632
+ const projectMdPath = resolve22(projectDir, "project.md");
9451
9633
  if (!await fileExists(projectDir) || !await fileExists(projectMdPath)) {
9452
9634
  throw new Error(`Project "${options.project}" not found at ${projectDir}.`);
9453
9635
  }
@@ -9530,31 +9712,31 @@ init_config2();
9530
9712
  init_fs();
9531
9713
  import {
9532
9714
  cp as cp2,
9533
- readdir as readdir9,
9715
+ readdir as readdir10,
9534
9716
  symlink,
9535
9717
  lstat,
9536
- readFile as readFile14,
9718
+ readFile as readFile15,
9537
9719
  readlink,
9538
9720
  rm as rm3,
9539
9721
  unlink as unlink5,
9540
- writeFile as writeFile5
9722
+ writeFile as writeFile6
9541
9723
  } from "fs/promises";
9542
9724
  import { existsSync } from "fs";
9543
9725
  import { homedir as homedir2 } from "os";
9544
- import { basename, dirname as dirname6, isAbsolute as isAbsolute2, relative as relative2, resolve as resolve23 } from "path";
9726
+ import { basename, dirname as dirname6, isAbsolute as isAbsolute2, relative as relative2, resolve as resolve24 } from "path";
9545
9727
 
9546
9728
  // src/utils/package-root.ts
9547
9729
  init_fs();
9548
- import { dirname as dirname5, resolve as resolve22 } from "path";
9730
+ import { dirname as dirname5, resolve as resolve23 } from "path";
9549
9731
  import { fileURLToPath as fileURLToPath3 } from "url";
9550
9732
  async function findPackageRoot(expectedRelativePath) {
9551
9733
  let currentDir = dirname5(fileURLToPath3(import.meta.url));
9552
9734
  while (true) {
9553
- const candidate = resolve22(currentDir, expectedRelativePath);
9735
+ const candidate = resolve23(currentDir, expectedRelativePath);
9554
9736
  if (await fileExists(candidate)) {
9555
9737
  return currentDir;
9556
9738
  }
9557
- const parentDir = resolve22(currentDir, "..");
9739
+ const parentDir = resolve23(currentDir, "..");
9558
9740
  if (parentDir === currentDir) {
9559
9741
  throw new Error(
9560
9742
  `Could not locate package root containing ${expectedRelativePath}.`
@@ -9575,25 +9757,25 @@ function getPluginManifestRelativePath(pluginKind) {
9575
9757
  }
9576
9758
  function getDefaultPluginTargetDir(pluginKind) {
9577
9759
  const home = homedir2();
9578
- return pluginKind === "claude" ? resolve23(home, ".claude", "plugins", "syntaur") : resolve23(home, "plugins", "syntaur");
9760
+ return pluginKind === "claude" ? resolve24(home, ".claude", "plugins", "syntaur") : resolve24(home, "plugins", "syntaur");
9579
9761
  }
9580
9762
  function getDefaultMarketplacePath() {
9581
- return resolve23(homedir2(), ".agents", "plugins", "marketplace.json");
9763
+ return resolve24(homedir2(), ".agents", "plugins", "marketplace.json");
9582
9764
  }
9583
9765
  function getClaudeMarketplacesRoot() {
9584
- return resolve23(homedir2(), ".claude", "plugins", "marketplaces");
9766
+ return resolve24(homedir2(), ".claude", "plugins", "marketplaces");
9585
9767
  }
9586
9768
  function getClaudeKnownMarketplacesPath() {
9587
- return resolve23(homedir2(), ".claude", "plugins", "known_marketplaces.json");
9769
+ return resolve24(homedir2(), ".claude", "plugins", "known_marketplaces.json");
9588
9770
  }
9589
9771
  function getClaudeInstalledPluginsPath() {
9590
- return resolve23(homedir2(), ".claude", "plugins", "installed_plugins.json");
9772
+ return resolve24(homedir2(), ".claude", "plugins", "installed_plugins.json");
9591
9773
  }
9592
9774
  function getInstallMarkerPath(targetDir) {
9593
- return resolve23(targetDir, INSTALL_MARKER_FILENAME);
9775
+ return resolve24(targetDir, INSTALL_MARKER_FILENAME);
9594
9776
  }
9595
9777
  async function readPackageManifest(packageRoot) {
9596
- const raw = await readFile14(resolve23(packageRoot, "package.json"), "utf-8");
9778
+ const raw = await readFile15(resolve24(packageRoot, "package.json"), "utf-8");
9597
9779
  return JSON.parse(raw);
9598
9780
  }
9599
9781
  async function readJsonFileIfExists(pathValue) {
@@ -9601,7 +9783,7 @@ async function readJsonFileIfExists(pathValue) {
9601
9783
  return null;
9602
9784
  }
9603
9785
  try {
9604
- const raw = await readFile14(pathValue, "utf-8");
9786
+ const raw = await readFile15(pathValue, "utf-8");
9605
9787
  return JSON.parse(raw);
9606
9788
  } catch {
9607
9789
  return null;
@@ -9609,15 +9791,15 @@ async function readJsonFileIfExists(pathValue) {
9609
9791
  }
9610
9792
  async function readClaudePluginManifest(pluginDir) {
9611
9793
  return await readJsonFileIfExists(
9612
- resolve23(pluginDir, ".claude-plugin", "plugin.json")
9794
+ resolve24(pluginDir, ".claude-plugin", "plugin.json")
9613
9795
  ) ?? {};
9614
9796
  }
9615
9797
  async function readPluginManifestName(targetDir, pluginKind) {
9616
- const manifestPath = resolve23(targetDir, getPluginManifestRelativePath(pluginKind));
9798
+ const manifestPath = resolve24(targetDir, getPluginManifestRelativePath(pluginKind));
9617
9799
  if (!await fileExists(manifestPath)) {
9618
9800
  return void 0;
9619
9801
  }
9620
- const raw = await readFile14(manifestPath, "utf-8");
9802
+ const raw = await readFile15(manifestPath, "utf-8");
9621
9803
  const parsed = JSON.parse(raw);
9622
9804
  return parsed.name;
9623
9805
  }
@@ -9627,7 +9809,7 @@ async function readInstallMetadata(targetDir) {
9627
9809
  return null;
9628
9810
  }
9629
9811
  try {
9630
- const raw = await readFile14(markerPath, "utf-8");
9812
+ const raw = await readFile15(markerPath, "utf-8");
9631
9813
  return JSON.parse(raw);
9632
9814
  } catch {
9633
9815
  return null;
@@ -9640,7 +9822,7 @@ async function getInstallStatus(targetDir, pluginKind) {
9640
9822
  const info = await lstat(targetDir);
9641
9823
  if (info.isSymbolicLink()) {
9642
9824
  const symlinkTarget = await readlink(targetDir);
9643
- const resolvedTarget = resolve23(dirname6(targetDir), symlinkTarget);
9825
+ const resolvedTarget = resolve24(dirname6(targetDir), symlinkTarget);
9644
9826
  const manifestName2 = await readPluginManifestName(resolvedTarget, pluginKind);
9645
9827
  return {
9646
9828
  exists: true,
@@ -9669,7 +9851,7 @@ async function writeInstallMetadata(targetDir, pluginKind, installMode, packageM
9669
9851
  installMode,
9670
9852
  installedAt: (/* @__PURE__ */ new Date()).toISOString()
9671
9853
  };
9672
- await writeFile5(
9854
+ await writeFile6(
9673
9855
  getInstallMarkerPath(targetDir),
9674
9856
  `${JSON.stringify(metadata, null, 2)}
9675
9857
  `,
@@ -9686,7 +9868,7 @@ async function installLink(paths) {
9686
9868
  await ensureDir(dirname6(paths.targetDir));
9687
9869
  await rm3(paths.targetDir, { recursive: true, force: true });
9688
9870
  await ensureDir(dirname6(paths.targetDir));
9689
- await symlink(resolve23(paths.sourceDir), paths.targetDir, "dir");
9871
+ await symlink(resolve24(paths.sourceDir), paths.targetDir, "dir");
9690
9872
  }
9691
9873
  async function removeInstallMarker(targetDir) {
9692
9874
  const markerPath = getInstallMarkerPath(targetDir);
@@ -9700,13 +9882,13 @@ function normalizeAbsoluteInstallPath(pathValue, label) {
9700
9882
  if (!isAbsolute2(expanded)) {
9701
9883
  throw new Error(`${label} must be an absolute path.`);
9702
9884
  }
9703
- return resolve23(expanded);
9885
+ return resolve24(expanded);
9704
9886
  }
9705
9887
  async function resolvePluginPaths(pluginKind, targetDir) {
9706
9888
  const packageRoot = await findPackageRoot(getPluginRelativePath(pluginKind));
9707
9889
  return {
9708
9890
  packageRoot,
9709
- sourceDir: resolve23(packageRoot, getPluginRelativePath(pluginKind)),
9891
+ sourceDir: resolve24(packageRoot, getPluginRelativePath(pluginKind)),
9710
9892
  targetDir: targetDir ?? getDefaultPluginTargetDir(pluginKind)
9711
9893
  };
9712
9894
  }
@@ -9741,7 +9923,7 @@ async function readClaudeMarketplaceFile(manifestPath) {
9741
9923
  }
9742
9924
  async function writeClaudeMarketplaceFile(manifestPath, marketplace) {
9743
9925
  await ensureDir(dirname6(manifestPath));
9744
- await writeFile5(manifestPath, `${JSON.stringify(marketplace, null, 2)}
9926
+ await writeFile6(manifestPath, `${JSON.stringify(marketplace, null, 2)}
9745
9927
  `, "utf-8");
9746
9928
  }
9747
9929
  function buildClaudeMarketplaceSourcePath(pluginTargetDir, marketplaceRootDir) {
@@ -9802,7 +9984,7 @@ async function listClaudeMarketplaceCandidates() {
9802
9984
  const [knownMarketplaces, activeMarketplaceNames, entries] = await Promise.all([
9803
9985
  readKnownClaudeMarketplaceRecords(),
9804
9986
  readInstalledClaudeMarketplaceNames(),
9805
- readdir9(rootDir, { withFileTypes: true })
9987
+ readdir10(rootDir, { withFileTypes: true })
9806
9988
  ]);
9807
9989
  const candidates = [];
9808
9990
  const seen = /* @__PURE__ */ new Set();
@@ -9810,8 +9992,8 @@ async function listClaudeMarketplaceCandidates() {
9810
9992
  if (!entry.isDirectory()) {
9811
9993
  continue;
9812
9994
  }
9813
- const candidateRoot = resolve23(rootDir, entry.name);
9814
- const manifestPath = resolve23(candidateRoot, ".claude-plugin", "marketplace.json");
9995
+ const candidateRoot = resolve24(rootDir, entry.name);
9996
+ const manifestPath = resolve24(candidateRoot, ".claude-plugin", "marketplace.json");
9815
9997
  if (!await fileExists(manifestPath)) {
9816
9998
  continue;
9817
9999
  }
@@ -9838,11 +10020,11 @@ async function listClaudeMarketplaceCandidates() {
9838
10020
  if (!installLocation) {
9839
10021
  continue;
9840
10022
  }
9841
- const candidateRoot = resolve23(expandHome(installLocation));
10023
+ const candidateRoot = resolve24(expandHome(installLocation));
9842
10024
  if (seen.has(candidateRoot)) {
9843
10025
  continue;
9844
10026
  }
9845
- const manifestPath = resolve23(candidateRoot, ".claude-plugin", "marketplace.json");
10027
+ const manifestPath = resolve24(candidateRoot, ".claude-plugin", "marketplace.json");
9846
10028
  if (!await fileExists(manifestPath)) {
9847
10029
  continue;
9848
10030
  }
@@ -9870,7 +10052,7 @@ async function getPreferredClaudeMarketplace() {
9870
10052
  name: candidate.name,
9871
10053
  rootDir: candidate.rootDir,
9872
10054
  manifestPath: candidate.manifestPath,
9873
- targetDir: resolve23(candidate.rootDir, "plugins", "syntaur")
10055
+ targetDir: resolve24(candidate.rootDir, "plugins", "syntaur")
9874
10056
  };
9875
10057
  }
9876
10058
  async function detectClaudeMarketplaceForTarget(targetDir) {
@@ -9880,7 +10062,7 @@ async function detectClaudeMarketplaceForTarget(targetDir) {
9880
10062
  return null;
9881
10063
  }
9882
10064
  const rootDir = dirname6(pluginsDir);
9883
- const manifestPath = resolve23(rootDir, ".claude-plugin", "marketplace.json");
10065
+ const manifestPath = resolve24(rootDir, ".claude-plugin", "marketplace.json");
9884
10066
  if (!await fileExists(manifestPath)) {
9885
10067
  return null;
9886
10068
  }
@@ -9896,7 +10078,7 @@ async function detectClaudeMarketplaceForTarget(targetDir) {
9896
10078
  async function findManagedClaudeMarketplacePluginDir() {
9897
10079
  const marketplaces = await listClaudeMarketplaceCandidates();
9898
10080
  for (const marketplace of marketplaces) {
9899
- const targetDir = resolve23(marketplace.rootDir, "plugins", "syntaur");
10081
+ const targetDir = resolve24(marketplace.rootDir, "plugins", "syntaur");
9900
10082
  const status = await getInstallStatus(targetDir, "claude");
9901
10083
  if (status.exists && status.managed) {
9902
10084
  return targetDir;
@@ -10021,7 +10203,7 @@ async function installManagedPlugin(options) {
10021
10203
  `${paths.targetDir} already exists and is not a Syntaur-managed install. Remove it manually before installing Syntaur there.`
10022
10204
  );
10023
10205
  }
10024
- if (desiredMode === "link" && existing.exists && existing.installMode === "link" && existing.symlinkTarget === resolve23(paths.sourceDir) && !force) {
10206
+ if (desiredMode === "link" && existing.exists && existing.installMode === "link" && existing.symlinkTarget === resolve24(paths.sourceDir) && !force) {
10025
10207
  return {
10026
10208
  targetDir: paths.targetDir,
10027
10209
  sourceDir: paths.sourceDir,
@@ -10073,7 +10255,7 @@ async function readMarketplaceFile(marketplacePath) {
10073
10255
  plugins: []
10074
10256
  };
10075
10257
  }
10076
- const raw = await readFile14(marketplacePath, "utf-8");
10258
+ const raw = await readFile15(marketplacePath, "utf-8");
10077
10259
  const parsed = JSON.parse(raw);
10078
10260
  return {
10079
10261
  name: parsed.name ?? "local",
@@ -10083,7 +10265,7 @@ async function readMarketplaceFile(marketplacePath) {
10083
10265
  }
10084
10266
  async function writeMarketplaceFile(marketplacePath, marketplace) {
10085
10267
  await ensureDir(dirname6(marketplacePath));
10086
- await writeFile5(marketplacePath, `${JSON.stringify(marketplace, null, 2)}
10268
+ await writeFile6(marketplacePath, `${JSON.stringify(marketplace, null, 2)}
10087
10269
  `, "utf-8");
10088
10270
  }
10089
10271
  async function hasAnySyntaurMarketplaceEntry(marketplacePath) {
@@ -10234,13 +10416,13 @@ async function recommendMarketplacePath() {
10234
10416
  return configuredOrManaged ?? getDefaultMarketplacePath();
10235
10417
  }
10236
10418
  async function isSyntaurDataInstalled() {
10237
- return fileExists(resolve23(syntaurRoot(), "config.md"));
10419
+ return fileExists(resolve24(syntaurRoot(), "config.md"));
10238
10420
  }
10239
10421
  async function removeSyntaurData() {
10240
10422
  await rm3(syntaurRoot(), { recursive: true, force: true });
10241
10423
  }
10242
10424
  async function getConfiguredProjectDir() {
10243
- if (!await fileExists(resolve23(syntaurRoot(), "config.md"))) {
10425
+ if (!await fileExists(resolve24(syntaurRoot(), "config.md"))) {
10244
10426
  return null;
10245
10427
  }
10246
10428
  return (await readConfig()).defaultProjectDir;
@@ -10307,6 +10489,165 @@ async function textPrompt(question, defaultValue) {
10307
10489
  }
10308
10490
  }
10309
10491
 
10492
+ // src/utils/install-skills.ts
10493
+ init_fs();
10494
+ import { readFile as readFile16, readdir as readdir11, mkdir as mkdir2, copyFile, rm as rm4 } from "fs/promises";
10495
+ import { dirname as dirname7, resolve as resolve25, relative as relative3, join as join3 } from "path";
10496
+ import { fileURLToPath as fileURLToPath4 } from "url";
10497
+ import { homedir as homedir3 } from "os";
10498
+ var REQUIRED_SKILLS = [
10499
+ "syntaur-protocol",
10500
+ "grab-assignment",
10501
+ "plan-assignment",
10502
+ "complete-assignment",
10503
+ "create-assignment",
10504
+ "create-project"
10505
+ ];
10506
+ function getVendoredSkillsDir() {
10507
+ const here = dirname7(fileURLToPath4(import.meta.url));
10508
+ return resolve25(here, "..", "vendor", "syntaur-skills", "skills");
10509
+ }
10510
+ function defaultSkillTargetDir(target) {
10511
+ if (target === "claude") return resolve25(homedir3(), ".claude", "skills");
10512
+ return resolve25(homedir3(), ".codex", "skills");
10513
+ }
10514
+ async function walkFiles(root) {
10515
+ const out = [];
10516
+ async function walk(dir) {
10517
+ const entries = await readdir11(dir, { withFileTypes: true });
10518
+ for (const entry of entries) {
10519
+ const full = join3(dir, entry.name);
10520
+ if (entry.isDirectory()) {
10521
+ await walk(full);
10522
+ } else if (entry.isFile()) {
10523
+ out.push(full);
10524
+ }
10525
+ }
10526
+ }
10527
+ await walk(root);
10528
+ return out.sort();
10529
+ }
10530
+ async function filesEqual(a, b) {
10531
+ try {
10532
+ const [ba, bb] = await Promise.all([readFile16(a), readFile16(b)]);
10533
+ if (ba.length !== bb.length) return false;
10534
+ return ba.equals(bb);
10535
+ } catch {
10536
+ return false;
10537
+ }
10538
+ }
10539
+ async function copyDir(srcDir, destDir) {
10540
+ await mkdir2(destDir, { recursive: true });
10541
+ const entries = await readdir11(srcDir, { withFileTypes: true });
10542
+ for (const entry of entries) {
10543
+ const src = join3(srcDir, entry.name);
10544
+ const dest = join3(destDir, entry.name);
10545
+ if (entry.isDirectory()) {
10546
+ await copyDir(src, dest);
10547
+ } else if (entry.isFile()) {
10548
+ await copyFile(src, dest);
10549
+ }
10550
+ }
10551
+ }
10552
+ async function skillMatches(srcDir, destDir) {
10553
+ if (!await fileExists(destDir)) return false;
10554
+ const srcFiles = await walkFiles(srcDir);
10555
+ for (const srcFile of srcFiles) {
10556
+ const rel = relative3(srcDir, srcFile);
10557
+ const destFile = join3(destDir, rel);
10558
+ if (!await filesEqual(srcFile, destFile)) return false;
10559
+ }
10560
+ const destFiles = await walkFiles(destDir);
10561
+ if (destFiles.length !== srcFiles.length) return false;
10562
+ return true;
10563
+ }
10564
+ async function installSkills(options) {
10565
+ const source = options.sourceDir ?? getVendoredSkillsDir();
10566
+ const targetRoot = options.targetDir ?? defaultSkillTargetDir(options.target);
10567
+ const force = options.force ?? false;
10568
+ if (!await fileExists(source)) {
10569
+ throw new Error(
10570
+ `Vendored skills not found at ${source}. Reinstall syntaur: npm install -g syntaur@latest`
10571
+ );
10572
+ }
10573
+ const results = [];
10574
+ await mkdir2(targetRoot, { recursive: true });
10575
+ for (const skill of REQUIRED_SKILLS) {
10576
+ const srcDir = join3(source, skill);
10577
+ const destDir = join3(targetRoot, skill);
10578
+ if (!await fileExists(srcDir)) continue;
10579
+ if (!await fileExists(destDir)) {
10580
+ await copyDir(srcDir, destDir);
10581
+ results.push({
10582
+ skill,
10583
+ status: "installed",
10584
+ targetPath: destDir
10585
+ });
10586
+ continue;
10587
+ }
10588
+ if (await skillMatches(srcDir, destDir)) {
10589
+ results.push({
10590
+ skill,
10591
+ status: "already-current",
10592
+ targetPath: destDir
10593
+ });
10594
+ continue;
10595
+ }
10596
+ if (force) {
10597
+ await rm4(destDir, { recursive: true, force: true });
10598
+ await copyDir(srcDir, destDir);
10599
+ results.push({
10600
+ skill,
10601
+ status: "overwritten",
10602
+ targetPath: destDir
10603
+ });
10604
+ } else {
10605
+ results.push({
10606
+ skill,
10607
+ status: "differs-preserved",
10608
+ targetPath: destDir
10609
+ });
10610
+ }
10611
+ }
10612
+ return results;
10613
+ }
10614
+ async function uninstallSkills(options) {
10615
+ const targetRoot = options.targetDir ?? defaultSkillTargetDir(options.target);
10616
+ if (!await fileExists(targetRoot)) return [];
10617
+ const removed = [];
10618
+ for (const skill of REQUIRED_SKILLS) {
10619
+ const destDir = join3(targetRoot, skill);
10620
+ if (!await fileExists(destDir)) continue;
10621
+ const skillMd = join3(destDir, "SKILL.md");
10622
+ if (!await fileExists(skillMd)) continue;
10623
+ const content = await readFile16(skillMd, "utf-8").catch(() => "");
10624
+ const match = content.match(/^name:\s*(\S+)\s*$/m);
10625
+ if (!match || match[1] !== skill) continue;
10626
+ await rm4(destDir, { recursive: true, force: true });
10627
+ removed.push(destDir);
10628
+ }
10629
+ return removed;
10630
+ }
10631
+ function formatInstallReport(results, target) {
10632
+ const lines = [];
10633
+ lines.push(`Skill install (${target}):`);
10634
+ for (const r of results) {
10635
+ const marker = r.status === "installed" ? "+" : r.status === "overwritten" ? "!" : r.status === "differs-preserved" ? "?" : "=";
10636
+ lines.push(` ${marker} ${r.skill} (${r.status})`);
10637
+ }
10638
+ const diffs = results.filter((r) => r.status === "differs-preserved");
10639
+ if (diffs.length > 0) {
10640
+ lines.push("");
10641
+ lines.push(
10642
+ ` Note: ${diffs.length} skill(s) already exist with different content and were preserved.`
10643
+ );
10644
+ lines.push(
10645
+ " Run with --force-skills to overwrite with the vendored version."
10646
+ );
10647
+ }
10648
+ return lines.join("\n");
10649
+ }
10650
+
10310
10651
  // src/commands/install-plugin.ts
10311
10652
  async function promptForInstallPath(question, recommendedPath) {
10312
10653
  while (true) {
@@ -10389,10 +10730,261 @@ async function installPluginCommand(options) {
10389
10730
  if (currentMarketplace) {
10390
10731
  console.log(` marketplace: ${currentMarketplace.manifestPath}`);
10391
10732
  }
10733
+ if (!options.skipSkills) {
10734
+ try {
10735
+ const skillResults = await installSkills({
10736
+ target: "claude",
10737
+ force: options.forceSkills
10738
+ });
10739
+ console.log("");
10740
+ console.log(formatInstallReport(skillResults, "claude"));
10741
+ } catch (error) {
10742
+ console.warn(
10743
+ `Warning: skill install failed \u2014 ${error instanceof Error ? error.message : String(error)}`
10744
+ );
10745
+ }
10746
+ }
10392
10747
  console.log("\nThe plugin is now available in Claude Code.");
10393
- console.log(" Skills: /grab-assignment, /plan-assignment, /complete-assignment");
10394
- console.log(" Background: syntaur-protocol (auto-invoked)");
10395
- console.log(" Hook: write boundary enforcement (PreToolUse)");
10748
+ console.log(" Slash commands: /grab-assignment, /plan-assignment, /complete-assignment, /create-assignment, /create-project");
10749
+ console.log(" Background: syntaur-protocol skill (auto-invoked)");
10750
+ console.log(" Hook: write boundary enforcement (PreToolUse) + SessionStart/End");
10751
+ }
10752
+
10753
+ // src/commands/install-statusline.ts
10754
+ init_paths();
10755
+ init_fs();
10756
+ import { readFile as readFile17, writeFile as writeFile7, copyFile as copyFile2, rm as rm5, stat as stat3, symlink as symlink2, unlink as unlink6, lstat as lstat2 } from "fs/promises";
10757
+ import { resolve as resolve26, dirname as dirname8 } from "path";
10758
+ import { homedir as homedir4 } from "os";
10759
+ import { fileURLToPath as fileURLToPath5 } from "url";
10760
+ function getPackageStatuslineSource() {
10761
+ const here = dirname8(fileURLToPath5(import.meta.url));
10762
+ return resolve26(here, "..", "statusline", "statusline.sh");
10763
+ }
10764
+ async function readSettingsJson(settingsPath) {
10765
+ if (!await fileExists(settingsPath)) return {};
10766
+ const raw = await readFile17(settingsPath, "utf-8");
10767
+ if (raw.trim() === "") return {};
10768
+ try {
10769
+ const parsed = JSON.parse(raw);
10770
+ return parsed && typeof parsed === "object" ? parsed : {};
10771
+ } catch (error) {
10772
+ throw new Error(
10773
+ `Unable to parse ${settingsPath}: ${error.message}. Fix the JSON and re-run.`
10774
+ );
10775
+ }
10776
+ }
10777
+ async function writeSettingsJson(settingsPath, data) {
10778
+ await ensureDir(dirname8(settingsPath));
10779
+ await writeFile7(settingsPath, JSON.stringify(data, null, 2) + "\n", "utf-8");
10780
+ }
10781
+ async function resolveMode(mode, existingCommand, ourCommand) {
10782
+ if (mode !== "ask") return mode;
10783
+ if (!existingCommand || existingCommand === ourCommand) return "replace";
10784
+ if (!isInteractiveTerminal()) {
10785
+ return "wrap";
10786
+ }
10787
+ console.log(
10788
+ `A Claude Code statusLine is already configured:
10789
+ ${existingCommand}
10790
+ `
10791
+ );
10792
+ const wantWrap = await confirmPrompt(
10793
+ "Compose (wrap) your existing statusline with syntaur segments? [Y to wrap / n to replace]",
10794
+ true
10795
+ );
10796
+ if (wantWrap) return "wrap";
10797
+ const confirmReplace = await confirmPrompt(
10798
+ "Replace your existing statusline with syntaur only?",
10799
+ false
10800
+ );
10801
+ return confirmReplace ? "replace" : "skip";
10802
+ }
10803
+ function extractExistingCommand(settings) {
10804
+ const sl = settings.statusLine;
10805
+ if (!sl || typeof sl !== "object") return void 0;
10806
+ const obj = sl;
10807
+ return {
10808
+ type: typeof obj.type === "string" ? obj.type : void 0,
10809
+ command: typeof obj.command === "string" ? obj.command : void 0
10810
+ };
10811
+ }
10812
+ async function backupSettings(settingsSnapshot, backupPath) {
10813
+ await ensureDir(dirname8(backupPath));
10814
+ await writeFile7(
10815
+ backupPath,
10816
+ JSON.stringify(
10817
+ {
10818
+ version: 1,
10819
+ takenAt: (/* @__PURE__ */ new Date()).toISOString(),
10820
+ settingsPath: settingsSnapshot.settingsPath,
10821
+ previousStatusLine: settingsSnapshot.existingStatusLine ?? null
10822
+ },
10823
+ null,
10824
+ 2
10825
+ ) + "\n",
10826
+ "utf-8"
10827
+ );
10828
+ }
10829
+ async function installScript(sourceScript, destScript, link) {
10830
+ await ensureDir(dirname8(destScript));
10831
+ try {
10832
+ const s = await lstat2(destScript);
10833
+ if (s.isSymbolicLink() || s.isFile()) {
10834
+ await unlink6(destScript);
10835
+ }
10836
+ } catch {
10837
+ }
10838
+ if (link) {
10839
+ await symlink2(sourceScript, destScript);
10840
+ } else {
10841
+ await copyFile2(sourceScript, destScript);
10842
+ }
10843
+ }
10844
+ async function installStatuslineCommand(options = {}) {
10845
+ const mode = options.mode ?? "ask";
10846
+ const settingsPath = options.settingsPath ?? resolve26(homedir4(), ".claude", "settings.json");
10847
+ const installRoot = options.installRoot ?? syntaurRoot();
10848
+ const sourceScript = options.sourceScript ?? getPackageStatuslineSource();
10849
+ const destScript = resolve26(installRoot, "statusline.sh");
10850
+ const confPath = resolve26(installRoot, "statusline.conf");
10851
+ const backupPath = resolve26(installRoot, "statusline.backup.json");
10852
+ if (!await fileExists(sourceScript)) {
10853
+ throw new Error(
10854
+ `Statusline source script not found at ${sourceScript}. Try re-installing syntaur (npm install -g syntaur) or pass --source-script explicitly.`
10855
+ );
10856
+ }
10857
+ await installScript(sourceScript, destScript, Boolean(options.link));
10858
+ const settings = await readSettingsJson(settingsPath);
10859
+ const existingStatusLine = extractExistingCommand(settings);
10860
+ const existingCommand = existingStatusLine?.command;
10861
+ const ourCommand = `bash ${destScript}`;
10862
+ const resolvedMode = await resolveMode(mode, existingCommand, ourCommand);
10863
+ if (resolvedMode === "skip") {
10864
+ console.log("Installed statusline script only:");
10865
+ console.log(` script: ${destScript}`);
10866
+ console.log(` source: ${sourceScript}`);
10867
+ console.log(
10868
+ " (settings.json left unchanged \u2014 run with --mode=replace or --mode=wrap to wire it up)"
10869
+ );
10870
+ return;
10871
+ }
10872
+ await backupSettings(
10873
+ {
10874
+ settingsPath,
10875
+ existingStatusLine,
10876
+ existingCommand
10877
+ },
10878
+ backupPath
10879
+ );
10880
+ let wrapTarget = "";
10881
+ if (resolvedMode === "wrap" && existingCommand && existingCommand !== ourCommand) {
10882
+ const parsed = parseWrapCommand(existingCommand);
10883
+ if (parsed) {
10884
+ wrapTarget = parsed;
10885
+ } else {
10886
+ const wrapperPath = resolve26(installRoot, "statusline-wrapped.sh");
10887
+ const wrapperBody = `#!/usr/bin/env bash
10888
+ # Auto-generated by syntaur install-statusline.
10889
+ # Executes the previously configured statusLine command.
10890
+ exec ${existingCommand}
10891
+ `;
10892
+ await writeFile7(wrapperPath, wrapperBody, "utf-8");
10893
+ await chmodExec(wrapperPath);
10894
+ wrapTarget = wrapperPath;
10895
+ }
10896
+ }
10897
+ await ensureDir(dirname8(confPath));
10898
+ await writeFile7(
10899
+ confPath,
10900
+ wrapTarget ? `# Wrap target \u2014 the command below is invoked with the same stdin; its
10901
+ # stdout becomes the leading segment of the statusline. Remove this
10902
+ # line or comment it out to disable wrapping.
10903
+ ${wrapTarget}
10904
+ ` : `# syntaur statusline config. Add a single path on a non-comment line to
10905
+ # wrap another statusline script with syntaur segments appended.
10906
+ `,
10907
+ "utf-8"
10908
+ );
10909
+ settings.statusLine = {
10910
+ type: "command",
10911
+ command: ourCommand
10912
+ };
10913
+ await writeSettingsJson(settingsPath, settings);
10914
+ console.log("Installed syntaur statusline:");
10915
+ console.log(` script: ${destScript}`);
10916
+ console.log(` source: ${sourceScript}`);
10917
+ console.log(` mode: ${resolvedMode}`);
10918
+ console.log(` settings.json:${settingsPath}`);
10919
+ console.log(` backup: ${backupPath}`);
10920
+ if (wrapTarget) {
10921
+ console.log(` wrap target: ${wrapTarget}`);
10922
+ console.log(` (edit ${confPath} to change or disable wrapping)`);
10923
+ }
10924
+ }
10925
+ function parseWrapCommand(command) {
10926
+ const trimmed = command.trim();
10927
+ const bashMatch = trimmed.match(/^bash\s+(\S+)$/);
10928
+ if (bashMatch) return bashMatch[1];
10929
+ if (/^\S+\.(sh|bash)$/.test(trimmed)) return trimmed;
10930
+ return null;
10931
+ }
10932
+ async function chmodExec(path) {
10933
+ const fs = await import("fs/promises");
10934
+ try {
10935
+ const s = await stat3(path);
10936
+ await fs.chmod(path, s.mode | 73);
10937
+ } catch {
10938
+ }
10939
+ }
10940
+ async function uninstallStatuslineCommand(options = {}) {
10941
+ const settingsPath = options.settingsPath ?? resolve26(homedir4(), ".claude", "settings.json");
10942
+ const installRoot = options.installRoot ?? syntaurRoot();
10943
+ const destScript = resolve26(installRoot, "statusline.sh");
10944
+ const confPath = resolve26(installRoot, "statusline.conf");
10945
+ const backupPath = resolve26(installRoot, "statusline.backup.json");
10946
+ const wrapperPath = resolve26(installRoot, "statusline-wrapped.sh");
10947
+ const settings = await readSettingsJson(settingsPath);
10948
+ const existing = extractExistingCommand(settings);
10949
+ const ourCommand = `bash ${destScript}`;
10950
+ let restored = null;
10951
+ if (await fileExists(backupPath)) {
10952
+ try {
10953
+ const raw = await readFile17(backupPath, "utf-8");
10954
+ const parsed = JSON.parse(raw);
10955
+ const prev = parsed?.previousStatusLine;
10956
+ if (prev && typeof prev === "object" && typeof prev.command === "string") {
10957
+ restored = { command: prev.command };
10958
+ }
10959
+ } catch {
10960
+ }
10961
+ }
10962
+ if (existing?.command === ourCommand) {
10963
+ if (restored) {
10964
+ settings.statusLine = {
10965
+ type: "command",
10966
+ command: restored.command
10967
+ };
10968
+ } else {
10969
+ delete settings.statusLine;
10970
+ }
10971
+ await writeSettingsJson(settingsPath, settings);
10972
+ }
10973
+ if (!options.keepScript) {
10974
+ for (const path of [destScript, confPath, backupPath, wrapperPath]) {
10975
+ try {
10976
+ await rm5(path, { force: true });
10977
+ } catch {
10978
+ }
10979
+ }
10980
+ }
10981
+ console.log("Uninstalled syntaur statusline.");
10982
+ if (restored) {
10983
+ console.log(` Restored previous command: ${restored.command}`);
10984
+ } else {
10985
+ console.log(" Removed statusLine entry from settings.json.");
10986
+ }
10987
+ console.log(` settings.json: ${settingsPath}`);
10396
10988
  }
10397
10989
 
10398
10990
  // src/commands/install-codex-plugin.ts
@@ -10474,14 +11066,61 @@ async function installCodexPluginCommand(options) {
10474
11066
  console.log(` source: ${result.sourceDir}`);
10475
11067
  console.log(` mode: ${result.mode}`);
10476
11068
  console.log(` marketplace: ${marketplace.marketplacePath}`);
11069
+ if (!options.skipSkills) {
11070
+ try {
11071
+ const skillResults = await installSkills({
11072
+ target: "codex",
11073
+ force: options.forceSkills
11074
+ });
11075
+ console.log("");
11076
+ console.log(formatInstallReport(skillResults, "codex"));
11077
+ } catch (error) {
11078
+ console.warn(
11079
+ `Warning: skill install failed \u2014 ${error instanceof Error ? error.message : String(error)}`
11080
+ );
11081
+ }
11082
+ }
10477
11083
  console.log("\nThe plugin is now available to Codex.");
10478
11084
  console.log(
10479
- " Skills: syntaur-protocol, create-project, create-assignment, grab-assignment, plan-assignment, complete-assignment, track-session"
11085
+ " Protocol skills: syntaur-protocol, create-project, create-assignment, grab-assignment, plan-assignment, complete-assignment"
10480
11086
  );
10481
- console.log(" Command: /track-session");
11087
+ console.log(" Codex-specific: track-session skill (rollout path aware)");
10482
11088
  console.log(" Hooks: write boundary enforcement, session cleanup");
10483
11089
  }
10484
11090
 
11091
+ // src/commands/uninstall-skills.ts
11092
+ async function uninstallSkillsCommand(options) {
11093
+ const runClaude = Boolean(options.claude || options.all);
11094
+ const runCodex = Boolean(options.codex || options.all);
11095
+ if (!runClaude && !runCodex) {
11096
+ throw new Error(
11097
+ "Specify --claude, --codex, or --all (use one or more)."
11098
+ );
11099
+ }
11100
+ let totalRemoved = 0;
11101
+ if (runClaude) {
11102
+ const removed = await uninstallSkills({ target: "claude" });
11103
+ totalRemoved += removed.length;
11104
+ console.log(
11105
+ `Removed ${removed.length} Syntaur protocol skill(s) from ~/.claude/skills:`
11106
+ );
11107
+ for (const p of removed) console.log(` - ${p}`);
11108
+ }
11109
+ if (runCodex) {
11110
+ const removed = await uninstallSkills({ target: "codex" });
11111
+ totalRemoved += removed.length;
11112
+ console.log(
11113
+ `Removed ${removed.length} Syntaur protocol skill(s) from ~/.codex/skills:`
11114
+ );
11115
+ for (const p of removed) console.log(` - ${p}`);
11116
+ }
11117
+ if (totalRemoved === 0) {
11118
+ console.log(
11119
+ "No Syntaur protocol skills found to remove. (User-authored skills with matching directory names are preserved.)"
11120
+ );
11121
+ }
11122
+ }
11123
+
10485
11124
  // src/commands/setup.ts
10486
11125
  import { execSync } from "child_process";
10487
11126
  init_config2();
@@ -10586,7 +11225,7 @@ async function setupCommand(options) {
10586
11225
  }
10587
11226
 
10588
11227
  // src/commands/uninstall.ts
10589
- import { resolve as resolve24 } from "path";
11228
+ import { resolve as resolve27 } from "path";
10590
11229
  init_paths();
10591
11230
  function expandTargets(options) {
10592
11231
  if (options.all) {
@@ -10666,7 +11305,7 @@ async function uninstallCommand(options) {
10666
11305
  const configuredProjectDir = await getConfiguredProjectDir();
10667
11306
  await removeSyntaurData();
10668
11307
  console.log(`Removed ${syntaurRoot()}`);
10669
- if (configuredProjectDir && resolve24(configuredProjectDir) !== resolve24(syntaurRoot(), "projects")) {
11308
+ if (configuredProjectDir && resolve27(configuredProjectDir) !== resolve27(syntaurRoot(), "projects")) {
10670
11309
  console.warn(
10671
11310
  `Warning: config.md pointed to an external project directory (${configuredProjectDir}). That directory was not removed automatically.`
10672
11311
  );
@@ -10681,7 +11320,7 @@ async function uninstallCommand(options) {
10681
11320
  init_paths();
10682
11321
  init_fs();
10683
11322
  init_config2();
10684
- import { resolve as resolve25 } from "path";
11323
+ import { resolve as resolve28 } from "path";
10685
11324
  var SUPPORTED_FRAMEWORKS = ["cursor", "codex", "opencode"];
10686
11325
  async function setupAdapterCommand(framework, options) {
10687
11326
  if (!SUPPORTED_FRAMEWORKS.includes(framework)) {
@@ -10707,19 +11346,19 @@ async function setupAdapterCommand(framework, options) {
10707
11346
  }
10708
11347
  const config = await readConfig();
10709
11348
  const baseDir = options.dir ? expandHome(options.dir) : config.defaultProjectDir;
10710
- const projectDir = resolve25(baseDir, options.project);
10711
- const assignmentDir = resolve25(
11349
+ const projectDir = resolve28(baseDir, options.project);
11350
+ const assignmentDir = resolve28(
10712
11351
  projectDir,
10713
11352
  "assignments",
10714
11353
  options.assignment
10715
11354
  );
10716
- const projectMdPath = resolve25(projectDir, "project.md");
11355
+ const projectMdPath = resolve28(projectDir, "project.md");
10717
11356
  if (!await fileExists(projectDir) || !await fileExists(projectMdPath)) {
10718
11357
  throw new Error(
10719
11358
  `Project "${options.project}" not found at ${projectDir}.`
10720
11359
  );
10721
11360
  }
10722
- const assignmentMdPath = resolve25(assignmentDir, "assignment.md");
11361
+ const assignmentMdPath = resolve28(assignmentDir, "assignment.md");
10723
11362
  if (!await fileExists(assignmentDir) || !await fileExists(assignmentMdPath)) {
10724
11363
  throw new Error(
10725
11364
  `Assignment "${options.assignment}" not found at ${assignmentDir}.`
@@ -10747,15 +11386,15 @@ async function setupAdapterCommand(framework, options) {
10747
11386
  }
10748
11387
  }
10749
11388
  if (framework === "cursor") {
10750
- const protocolPath = resolve25(cwd, ".cursor", "rules", "syntaur-protocol.mdc");
10751
- const assignmentPath = resolve25(cwd, ".cursor", "rules", "syntaur-assignment.mdc");
11389
+ const protocolPath = resolve28(cwd, ".cursor", "rules", "syntaur-protocol.mdc");
11390
+ const assignmentPath = resolve28(cwd, ".cursor", "rules", "syntaur-assignment.mdc");
10752
11391
  await writeAdapterFile(protocolPath, renderCursorProtocol());
10753
11392
  await writeAdapterFile(assignmentPath, renderCursorAssignment(rendererParams));
10754
11393
  } else if (framework === "codex" || framework === "opencode") {
10755
- const agentsPath = resolve25(cwd, "AGENTS.md");
11394
+ const agentsPath = resolve28(cwd, "AGENTS.md");
10756
11395
  await writeAdapterFile(agentsPath, renderCodexAgents(rendererParams));
10757
11396
  if (framework === "opencode") {
10758
- const configPath = resolve25(cwd, "opencode.json");
11397
+ const configPath = resolve28(cwd, "opencode.json");
10759
11398
  await writeAdapterFile(configPath, renderOpenCodeConfig({ projectDir }));
10760
11399
  }
10761
11400
  }
@@ -10780,16 +11419,20 @@ async function setupAdapterCommand(framework, options) {
10780
11419
  init_paths();
10781
11420
  init_fs();
10782
11421
  init_config2();
10783
- import { resolve as resolve26 } from "path";
10784
- import { randomUUID as randomUUID2 } from "crypto";
11422
+ import { resolve as resolve29 } from "path";
10785
11423
  async function trackSessionCommand(options) {
10786
11424
  if (!options.agent) {
10787
11425
  throw new Error("--agent <name> is required.");
10788
11426
  }
11427
+ if (!options.sessionId) {
11428
+ throw new Error(
11429
+ "--session-id <id> is required. Pass the real agent-generated session id \u2014 do not synthesize one."
11430
+ );
11431
+ }
10789
11432
  if (options.project) {
10790
11433
  const config = await readConfig();
10791
11434
  const baseDir = options.dir ? expandHome(options.dir) : config.defaultProjectDir;
10792
- const projectDir = resolve26(baseDir, options.project);
11435
+ const projectDir = resolve29(baseDir, options.project);
10793
11436
  if (!await fileExists(projectDir)) {
10794
11437
  throw new Error(
10795
11438
  `Project "${options.project}" not found at ${projectDir}.`
@@ -10797,7 +11440,7 @@ async function trackSessionCommand(options) {
10797
11440
  }
10798
11441
  }
10799
11442
  initSessionDb();
10800
- const sessionId = options.sessionId || randomUUID2();
11443
+ const { sessionId } = options;
10801
11444
  await appendSession("", {
10802
11445
  projectSlug: options.project || null,
10803
11446
  assignmentSlug: options.assignment || null,
@@ -10806,10 +11449,13 @@ async function trackSessionCommand(options) {
10806
11449
  started: (/* @__PURE__ */ new Date()).toISOString(),
10807
11450
  status: "active",
10808
11451
  path: options.path || process.cwd(),
10809
- description: options.description || null
11452
+ description: options.description || null,
11453
+ transcriptPath: options.transcriptPath ?? null
10810
11454
  });
10811
11455
  if (options.project && options.assignment) {
10812
- console.log(`Registered agent session ${sessionId} for ${options.assignment} in ${options.project}.`);
11456
+ console.log(
11457
+ `Registered agent session ${sessionId} for ${options.assignment} in ${options.project}.`
11458
+ );
10813
11459
  } else {
10814
11460
  console.log(`Registered standalone agent session ${sessionId}.`);
10815
11461
  }
@@ -10841,7 +11487,7 @@ async function browseCommand(options) {
10841
11487
  }
10842
11488
 
10843
11489
  // src/commands/create-playbook.ts
10844
- import { resolve as resolve28 } from "path";
11490
+ import { resolve as resolve31 } from "path";
10845
11491
  init_timestamp();
10846
11492
  init_paths();
10847
11493
  init_fs();
@@ -10857,7 +11503,7 @@ async function createPlaybookCommand(name, options) {
10857
11503
  }
10858
11504
  const dir = playbooksDir();
10859
11505
  await ensureDir(dir);
10860
- const filePath = resolve28(dir, `${slug}.md`);
11506
+ const filePath = resolve31(dir, `${slug}.md`);
10861
11507
  if (await fileExists(filePath)) {
10862
11508
  throw new Error(
10863
11509
  `Playbook "${slug}" already exists at ${filePath}
@@ -10878,15 +11524,15 @@ Use --slug to specify a different slug.`
10878
11524
  init_paths();
10879
11525
  init_fs();
10880
11526
  init_parser();
10881
- import { readdir as readdir10, readFile as readFile15 } from "fs/promises";
10882
- import { resolve as resolve29 } from "path";
11527
+ import { readdir as readdir12, readFile as readFile18 } from "fs/promises";
11528
+ import { resolve as resolve32 } from "path";
10883
11529
  async function listPlaybooksCommand() {
10884
11530
  const dir = playbooksDir();
10885
11531
  if (!await fileExists(dir)) {
10886
11532
  console.log('No playbooks directory found. Run "syntaur init" first.');
10887
11533
  return;
10888
11534
  }
10889
- const entries = await readdir10(dir, { withFileTypes: true });
11535
+ const entries = await readdir12(dir, { withFileTypes: true });
10890
11536
  const mdFiles = entries.filter((e) => e.isFile() && e.name.endsWith(".md") && !e.name.startsWith("_") && e.name !== "manifest.md");
10891
11537
  if (mdFiles.length === 0) {
10892
11538
  console.log('No playbooks found. Create one with "syntaur create-playbook <name>".');
@@ -10897,8 +11543,8 @@ async function listPlaybooksCommand() {
10897
11543
  console.log(`${"Slug".padEnd(30)} ${"Name".padEnd(30)} Description`);
10898
11544
  console.log(`${"\u2500".repeat(30)} ${"\u2500".repeat(30)} ${"\u2500".repeat(40)}`);
10899
11545
  for (const entry of mdFiles) {
10900
- const filePath = resolve29(dir, entry.name);
10901
- const raw = await readFile15(filePath, "utf-8");
11546
+ const filePath = resolve32(dir, entry.name);
11547
+ const raw = await readFile18(filePath, "utf-8");
10902
11548
  const parsed = parsePlaybook(raw);
10903
11549
  const slug = parsed.slug || entry.name.replace(/\.md$/, "");
10904
11550
  const name = parsed.name || slug;
@@ -10912,8 +11558,8 @@ init_paths();
10912
11558
  init_parser2();
10913
11559
  init_fs();
10914
11560
  import { Command } from "commander";
10915
- import { readFile as readFile16 } from "fs/promises";
10916
- import { resolve as resolve30 } from "path";
11561
+ import { readFile as readFile19 } from "fs/promises";
11562
+ import { resolve as resolve33 } from "path";
10917
11563
  var WORKSPACE_REGEX2 = /^[a-z0-9_][a-z0-9-]*$/;
10918
11564
  function resolveWorkspace(options) {
10919
11565
  if (options.global) return "_global";
@@ -11194,10 +11840,10 @@ todoCommand.command("archive").description("Archive completed todos and their lo
11194
11840
  (e) => e.itemIds.every((id) => completedIds.has(id))
11195
11841
  );
11196
11842
  const archFile = archivePath(todosPath, workspace, checklist.archiveInterval);
11197
- await ensureDir(resolve30(todosPath, "archive"));
11843
+ await ensureDir(resolve33(todosPath, "archive"));
11198
11844
  let archContent = "";
11199
11845
  if (await fileExists(archFile)) {
11200
- archContent = await readFile16(archFile, "utf-8");
11846
+ archContent = await readFile19(archFile, "utf-8");
11201
11847
  archContent = archContent.trimEnd() + "\n\n";
11202
11848
  } else {
11203
11849
  archContent = `---
@@ -11385,20 +12031,20 @@ backupCommand.command("config").description("Show or update backup configuration
11385
12031
  import { Command as Command3 } from "commander";
11386
12032
 
11387
12033
  // src/utils/doctor/index.ts
11388
- import { fileURLToPath as fileURLToPath5 } from "url";
11389
- import { readFile as readFile20 } from "fs/promises";
11390
- import { dirname as dirname8, join as join4 } from "path";
12034
+ import { fileURLToPath as fileURLToPath7 } from "url";
12035
+ import { readFile as readFile23 } from "fs/promises";
12036
+ import { dirname as dirname10, join as join5 } from "path";
11391
12037
 
11392
12038
  // src/utils/doctor/context.ts
11393
12039
  init_config2();
11394
12040
  init_paths();
11395
12041
  init_fs();
11396
12042
  import Database2 from "better-sqlite3";
11397
- import { resolve as resolve31 } from "path";
12043
+ import { resolve as resolve34 } from "path";
11398
12044
  async function buildCheckContext(cwd = process.cwd()) {
11399
12045
  const config = await readConfig();
11400
12046
  const root = syntaurRoot();
11401
- const dbPath = resolve31(root, "syntaur.db");
12047
+ const dbPath = resolve34(root, "syntaur.db");
11402
12048
  let db2 = null;
11403
12049
  let dbError = null;
11404
12050
  if (await fileExists(dbPath)) {
@@ -11432,10 +12078,10 @@ function closeCheckContext(ctx) {
11432
12078
  // src/utils/doctor/checks/env.ts
11433
12079
  init_fs();
11434
12080
  init_paths();
11435
- import { resolve as resolve32, isAbsolute as isAbsolute3 } from "path";
11436
- import { readFile as readFile17, stat as stat2 } from "fs/promises";
11437
- import { fileURLToPath as fileURLToPath4 } from "url";
11438
- import { dirname as dirname7, join as join3 } from "path";
12081
+ import { resolve as resolve35, isAbsolute as isAbsolute3 } from "path";
12082
+ import { readFile as readFile20, stat as stat4 } from "fs/promises";
12083
+ import { fileURLToPath as fileURLToPath6 } from "url";
12084
+ import { dirname as dirname9, join as join4 } from "path";
11439
12085
  var CATEGORY = "env";
11440
12086
  var syntaurRootExists = {
11441
12087
  id: "env.syntaur-root-exists",
@@ -11443,7 +12089,7 @@ var syntaurRootExists = {
11443
12089
  title: "~/.syntaur/ directory exists",
11444
12090
  async run(ctx) {
11445
12091
  try {
11446
- const s = await stat2(ctx.syntaurRoot);
12092
+ const s = await stat4(ctx.syntaurRoot);
11447
12093
  if (!s.isDirectory()) {
11448
12094
  return err(this, `${ctx.syntaurRoot} exists but is not a directory`, [
11449
12095
  ctx.syntaurRoot
@@ -11473,7 +12119,7 @@ var configValid = {
11473
12119
  category: CATEGORY,
11474
12120
  title: "~/.syntaur/config.md is valid",
11475
12121
  async run(ctx) {
11476
- const configPath = resolve32(ctx.syntaurRoot, "config.md");
12122
+ const configPath = resolve35(ctx.syntaurRoot, "config.md");
11477
12123
  if (!await fileExists(configPath)) {
11478
12124
  return {
11479
12125
  id: this.id,
@@ -11490,7 +12136,7 @@ var configValid = {
11490
12136
  autoFixable: false
11491
12137
  };
11492
12138
  }
11493
- const content = await readFile17(configPath, "utf-8");
12139
+ const content = await readFile20(configPath, "utf-8");
11494
12140
  const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
11495
12141
  if (!fmMatch || fmMatch[1].trim() === "") {
11496
12142
  return {
@@ -11765,15 +12411,15 @@ async function readLocalVersion() {
11765
12411
  }
11766
12412
  async function readLocalPkg() {
11767
12413
  try {
11768
- const here = fileURLToPath4(import.meta.url);
11769
- let dir = dirname7(here);
12414
+ const here = fileURLToPath6(import.meta.url);
12415
+ let dir = dirname9(here);
11770
12416
  for (let i = 0; i < 6; i++) {
11771
- const candidate = join3(dir, "package.json");
12417
+ const candidate = join4(dir, "package.json");
11772
12418
  try {
11773
- const text = await readFile17(candidate, "utf-8");
12419
+ const text = await readFile20(candidate, "utf-8");
11774
12420
  return JSON.parse(text);
11775
12421
  } catch {
11776
- dir = dirname7(dir);
12422
+ dir = dirname9(dir);
11777
12423
  }
11778
12424
  }
11779
12425
  return null;
@@ -11822,8 +12468,8 @@ function versionGte(a, b) {
11822
12468
 
11823
12469
  // src/utils/doctor/checks/structure.ts
11824
12470
  init_fs();
11825
- import { resolve as resolve33 } from "path";
11826
- import { readdir as readdir11, stat as stat3 } from "fs/promises";
12471
+ import { resolve as resolve36 } from "path";
12472
+ import { readdir as readdir13, stat as stat5 } from "fs/promises";
11827
12473
  var CATEGORY2 = "structure";
11828
12474
  var KNOWN_TOP_LEVEL = /* @__PURE__ */ new Set([
11829
12475
  "projects",
@@ -11842,7 +12488,7 @@ var projectsDir = {
11842
12488
  category: CATEGORY2,
11843
12489
  title: "projects/ directory exists",
11844
12490
  async run(ctx) {
11845
- const p = resolve33(ctx.syntaurRoot, "projects");
12491
+ const p = resolve36(ctx.syntaurRoot, "projects");
11846
12492
  if (!await fileExists(p)) {
11847
12493
  return {
11848
12494
  id: this.id,
@@ -11867,7 +12513,7 @@ var playbooksDir2 = {
11867
12513
  category: CATEGORY2,
11868
12514
  title: "playbooks/ directory exists",
11869
12515
  async run(ctx) {
11870
- const p = resolve33(ctx.syntaurRoot, "playbooks");
12516
+ const p = resolve36(ctx.syntaurRoot, "playbooks");
11871
12517
  if (!await fileExists(p)) {
11872
12518
  return {
11873
12519
  id: this.id,
@@ -11892,7 +12538,7 @@ var todosDirValid = {
11892
12538
  category: CATEGORY2,
11893
12539
  title: "todos/ directory is readable (if present)",
11894
12540
  async run(ctx) {
11895
- const p = resolve33(ctx.syntaurRoot, "todos");
12541
+ const p = resolve36(ctx.syntaurRoot, "todos");
11896
12542
  if (!await fileExists(p)) {
11897
12543
  return {
11898
12544
  id: this.id,
@@ -11903,7 +12549,7 @@ var todosDirValid = {
11903
12549
  autoFixable: false
11904
12550
  };
11905
12551
  }
11906
- const s = await stat3(p);
12552
+ const s = await stat5(p);
11907
12553
  if (!s.isDirectory()) {
11908
12554
  return {
11909
12555
  id: this.id,
@@ -11923,7 +12569,7 @@ var serversDirValid = {
11923
12569
  category: CATEGORY2,
11924
12570
  title: "servers/ directory is readable (if present)",
11925
12571
  async run(ctx) {
11926
- const p = resolve33(ctx.syntaurRoot, "servers");
12572
+ const p = resolve36(ctx.syntaurRoot, "servers");
11927
12573
  if (!await fileExists(p)) {
11928
12574
  return {
11929
12575
  id: this.id,
@@ -11934,7 +12580,7 @@ var serversDirValid = {
11934
12580
  autoFixable: false
11935
12581
  };
11936
12582
  }
11937
- const s = await stat3(p);
12583
+ const s = await stat5(p);
11938
12584
  if (!s.isDirectory()) {
11939
12585
  return {
11940
12586
  id: this.id,
@@ -11954,7 +12600,7 @@ var knownFilesRecognized = {
11954
12600
  category: CATEGORY2,
11955
12601
  title: "No unexpected top-level entries under ~/.syntaur/",
11956
12602
  async run(ctx) {
11957
- const entries = await readdir11(ctx.syntaurRoot, { withFileTypes: true });
12603
+ const entries = await readdir13(ctx.syntaurRoot, { withFileTypes: true });
11958
12604
  const unexpected = [];
11959
12605
  for (const e of entries) {
11960
12606
  if (e.name.startsWith(".")) continue;
@@ -11968,7 +12614,7 @@ var knownFilesRecognized = {
11968
12614
  title: this.title,
11969
12615
  status: "warn",
11970
12616
  detail: `unexpected top-level entries: ${unexpected.join(", ")}`,
11971
- affected: unexpected.map((n) => resolve33(ctx.syntaurRoot, n)),
12617
+ affected: unexpected.map((n) => resolve36(ctx.syntaurRoot, n)),
11972
12618
  remediation: {
11973
12619
  kind: "manual",
11974
12620
  suggestion: "Review these entries \u2014 they may be leftover state from older versions",
@@ -11997,8 +12643,8 @@ function pass2(check) {
11997
12643
 
11998
12644
  // src/utils/doctor/checks/project.ts
11999
12645
  init_fs();
12000
- import { resolve as resolve34 } from "path";
12001
- import { readdir as readdir12, stat as stat4 } from "fs/promises";
12646
+ import { resolve as resolve37 } from "path";
12647
+ import { readdir as readdir14, stat as stat6 } from "fs/promises";
12002
12648
  var CATEGORY3 = "project";
12003
12649
  var REQUIRED_PROJECT_FILES = [
12004
12650
  "project.md",
@@ -12022,15 +12668,15 @@ var PROJECT_MARKERS = ["project.md", "manifest.md", "assignments"];
12022
12668
  async function listProjects2(ctx) {
12023
12669
  const dir = ctx.config.defaultProjectDir;
12024
12670
  if (!await fileExists(dir)) return [];
12025
- const entries = await readdir12(dir, { withFileTypes: true });
12671
+ const entries = await readdir14(dir, { withFileTypes: true });
12026
12672
  const result = [];
12027
12673
  for (const e of entries) {
12028
12674
  if (!e.isDirectory()) continue;
12029
12675
  if (e.name.startsWith(".") || e.name.startsWith("_")) continue;
12030
- const projectDir = resolve34(dir, e.name);
12676
+ const projectDir = resolve37(dir, e.name);
12031
12677
  let looksLikeProject = false;
12032
12678
  for (const marker of PROJECT_MARKERS) {
12033
- if (await fileExists(resolve34(projectDir, marker))) {
12679
+ if (await fileExists(resolve37(projectDir, marker))) {
12034
12680
  looksLikeProject = true;
12035
12681
  break;
12036
12682
  }
@@ -12049,7 +12695,7 @@ var requiredFiles = {
12049
12695
  for (const projectDir of projects) {
12050
12696
  const missing = [];
12051
12697
  for (const rel of REQUIRED_PROJECT_FILES) {
12052
- const p = resolve34(projectDir, rel);
12698
+ const p = resolve37(projectDir, rel);
12053
12699
  if (!await fileExists(p)) missing.push(rel);
12054
12700
  }
12055
12701
  if (missing.length === 0) continue;
@@ -12059,7 +12705,7 @@ var requiredFiles = {
12059
12705
  title: this.title,
12060
12706
  status: "error",
12061
12707
  detail: `project at ${projectDir} is missing: ${missing.join(", ")}`,
12062
- affected: missing.map((m) => resolve34(projectDir, m)),
12708
+ affected: missing.map((m) => resolve37(projectDir, m)),
12063
12709
  remediation: {
12064
12710
  kind: "manual",
12065
12711
  suggestion: "Recreate the missing scaffold files from templates",
@@ -12082,9 +12728,9 @@ var manifestStale = {
12082
12728
  const projects = await listProjects2(ctx);
12083
12729
  const results = [];
12084
12730
  for (const projectDir of projects) {
12085
- const manifestPath = resolve34(projectDir, "manifest.md");
12731
+ const manifestPath = resolve37(projectDir, "manifest.md");
12086
12732
  if (!await fileExists(manifestPath)) continue;
12087
- const manifestMtime = (await stat4(manifestPath)).mtimeMs;
12733
+ const manifestMtime = (await stat6(manifestPath)).mtimeMs;
12088
12734
  const newestAssignment = await newestAssignmentMtime(projectDir);
12089
12735
  if (newestAssignment === 0) continue;
12090
12736
  if (newestAssignment > manifestMtime) {
@@ -12116,7 +12762,7 @@ var orphanFiles = {
12116
12762
  const projects = await listProjects2(ctx);
12117
12763
  const results = [];
12118
12764
  for (const projectDir of projects) {
12119
- const entries = await readdir12(projectDir, { withFileTypes: true });
12765
+ const entries = await readdir14(projectDir, { withFileTypes: true });
12120
12766
  const orphans = [];
12121
12767
  for (const e of entries) {
12122
12768
  if (e.name.startsWith(".")) continue;
@@ -12131,7 +12777,7 @@ var orphanFiles = {
12131
12777
  title: this.title,
12132
12778
  status: "warn",
12133
12779
  detail: `project at ${projectDir} has unexpected entries: ${orphans.join(", ")}`,
12134
- affected: orphans.map((o) => resolve34(projectDir, o)),
12780
+ affected: orphans.map((o) => resolve37(projectDir, o)),
12135
12781
  autoFixable: false
12136
12782
  });
12137
12783
  }
@@ -12141,20 +12787,20 @@ var orphanFiles = {
12141
12787
  };
12142
12788
  var projectChecks = [requiredFiles, manifestStale, orphanFiles];
12143
12789
  async function newestAssignmentMtime(projectDir) {
12144
- const assignmentsRoot = resolve34(projectDir, "assignments");
12790
+ const assignmentsRoot = resolve37(projectDir, "assignments");
12145
12791
  if (!await fileExists(assignmentsRoot)) return 0;
12146
12792
  let newest = 0;
12147
12793
  let entries;
12148
12794
  try {
12149
- entries = await readdir12(assignmentsRoot, { withFileTypes: true });
12795
+ entries = await readdir14(assignmentsRoot, { withFileTypes: true });
12150
12796
  } catch {
12151
12797
  return 0;
12152
12798
  }
12153
12799
  for (const e of entries) {
12154
12800
  if (!e.isDirectory()) continue;
12155
- const assignmentMd = resolve34(assignmentsRoot, e.name, "assignment.md");
12801
+ const assignmentMd = resolve37(assignmentsRoot, e.name, "assignment.md");
12156
12802
  try {
12157
- const s = await stat4(assignmentMd);
12803
+ const s = await stat6(assignmentMd);
12158
12804
  if (s.mtimeMs > newest) newest = s.mtimeMs;
12159
12805
  } catch {
12160
12806
  }
@@ -12176,28 +12822,28 @@ init_fs();
12176
12822
  init_parser();
12177
12823
  init_types();
12178
12824
  init_paths();
12179
- import { resolve as resolve35 } from "path";
12180
- import { readdir as readdir13, readFile as readFile18 } from "fs/promises";
12825
+ import { resolve as resolve38 } from "path";
12826
+ import { readdir as readdir15, readFile as readFile21 } from "fs/promises";
12181
12827
  var CATEGORY4 = "assignment";
12182
12828
  var STATUSES_REQUIRING_HANDOFF = /* @__PURE__ */ new Set(["review", "completed"]);
12183
12829
  async function listAssignments(ctx) {
12184
12830
  const result = { withAssignmentMd: [], orphanFolders: [] };
12185
12831
  const projectsDir2 = ctx.config.defaultProjectDir;
12186
12832
  if (await fileExists(projectsDir2)) {
12187
- const projects = await readdir13(projectsDir2, { withFileTypes: true });
12833
+ const projects = await readdir15(projectsDir2, { withFileTypes: true });
12188
12834
  for (const m of projects) {
12189
12835
  if (!m.isDirectory()) continue;
12190
12836
  if (m.name.startsWith(".") || m.name.startsWith("_")) continue;
12191
- const assignmentsDir2 = resolve35(projectsDir2, m.name, "assignments");
12837
+ const assignmentsDir2 = resolve38(projectsDir2, m.name, "assignments");
12192
12838
  if (!await fileExists(assignmentsDir2)) continue;
12193
- const entries = await readdir13(assignmentsDir2, { withFileTypes: true });
12839
+ const entries = await readdir15(assignmentsDir2, { withFileTypes: true });
12194
12840
  for (const a of entries) {
12195
12841
  if (!a.isDirectory()) continue;
12196
12842
  if (a.name.startsWith(".") || a.name.startsWith("_")) continue;
12197
- const assignmentDir = resolve35(assignmentsDir2, a.name);
12198
- const assignmentMd = resolve35(assignmentDir, "assignment.md");
12843
+ const assignmentDir = resolve38(assignmentsDir2, a.name);
12844
+ const assignmentMd = resolve38(assignmentDir, "assignment.md");
12199
12845
  const entry = {
12200
- projectDir: resolve35(projectsDir2, m.name),
12846
+ projectDir: resolve38(projectsDir2, m.name),
12201
12847
  projectSlug: m.name,
12202
12848
  assignmentDir,
12203
12849
  assignmentSlug: a.name,
@@ -12213,12 +12859,12 @@ async function listAssignments(ctx) {
12213
12859
  }
12214
12860
  const standaloneRoot = assignmentsDir();
12215
12861
  if (await fileExists(standaloneRoot)) {
12216
- const entries = await readdir13(standaloneRoot, { withFileTypes: true });
12862
+ const entries = await readdir15(standaloneRoot, { withFileTypes: true });
12217
12863
  for (const a of entries) {
12218
12864
  if (!a.isDirectory()) continue;
12219
12865
  if (a.name.startsWith(".") || a.name.startsWith("_")) continue;
12220
- const assignmentDir = resolve35(standaloneRoot, a.name);
12221
- const assignmentMd = resolve35(assignmentDir, "assignment.md");
12866
+ const assignmentDir = resolve38(standaloneRoot, a.name);
12867
+ const assignmentMd = resolve38(assignmentDir, "assignment.md");
12222
12868
  const entry = {
12223
12869
  projectDir: standaloneRoot,
12224
12870
  projectSlug: null,
@@ -12296,7 +12942,7 @@ var invalidStatus = {
12296
12942
  const allowed = configuredStatuses(ctx);
12297
12943
  const results = [];
12298
12944
  for (const a of withAssignmentMd) {
12299
- const path = resolve35(a.assignmentDir, "assignment.md");
12945
+ const path = resolve38(a.assignmentDir, "assignment.md");
12300
12946
  const parsed = await parseSafe(path);
12301
12947
  if (!parsed) continue;
12302
12948
  if (!allowed.has(parsed.status)) {
@@ -12329,7 +12975,7 @@ var workspaceMissing = {
12329
12975
  const terminal = terminalStatuses(ctx);
12330
12976
  const results = [];
12331
12977
  for (const a of withAssignmentMd) {
12332
- const path = resolve35(a.assignmentDir, "assignment.md");
12978
+ const path = resolve38(a.assignmentDir, "assignment.md");
12333
12979
  const parsed = await parseSafe(path);
12334
12980
  if (!parsed) continue;
12335
12981
  if (terminal.has(parsed.status)) continue;
@@ -12376,12 +13022,12 @@ var requiredFilesByStatus = {
12376
13022
  const { withAssignmentMd } = await listAssignments(ctx);
12377
13023
  const results = [];
12378
13024
  for (const a of withAssignmentMd) {
12379
- const assignmentPath = resolve35(a.assignmentDir, "assignment.md");
13025
+ const assignmentPath = resolve38(a.assignmentDir, "assignment.md");
12380
13026
  const parsed = await parseSafe(assignmentPath);
12381
13027
  if (!parsed) continue;
12382
13028
  const missing = [];
12383
13029
  if (STATUSES_REQUIRING_HANDOFF.has(parsed.status)) {
12384
- const handoffPath = resolve35(a.assignmentDir, "handoff.md");
13030
+ const handoffPath = resolve38(a.assignmentDir, "handoff.md");
12385
13031
  if (!await fileExists(handoffPath)) missing.push("handoff.md");
12386
13032
  }
12387
13033
  if (missing.length === 0) continue;
@@ -12391,7 +13037,7 @@ var requiredFilesByStatus = {
12391
13037
  title: this.title,
12392
13038
  status: "warn",
12393
13039
  detail: `${a.projectSlug}/${a.assignmentSlug} (status: ${parsed.status}) is missing ${missing.join(", ")}`,
12394
- affected: missing.map((m) => resolve35(a.assignmentDir, m)),
13040
+ affected: missing.map((m) => resolve38(a.assignmentDir, m)),
12395
13041
  remediation: {
12396
13042
  kind: "manual",
12397
13043
  suggestion: `Create the missing ${missing.join(" and ")} files for this assignment`,
@@ -12414,7 +13060,7 @@ var companionFilesScaffolded = {
12414
13060
  for (const a of withAssignmentMd) {
12415
13061
  const missing = [];
12416
13062
  for (const filename of ["progress.md", "comments.md"]) {
12417
- if (!await fileExists(resolve35(a.assignmentDir, filename))) {
13063
+ if (!await fileExists(resolve38(a.assignmentDir, filename))) {
12418
13064
  missing.push(filename);
12419
13065
  }
12420
13066
  }
@@ -12426,7 +13072,7 @@ var companionFilesScaffolded = {
12426
13072
  title: this.title,
12427
13073
  status: "warn",
12428
13074
  detail: `${label} is missing ${missing.join(" and ")} (pre-v2.0 assignment \u2014 not required, but scaffolding them keeps the dashboard and CLIs consistent)`,
12429
- affected: missing.map((m) => resolve35(a.assignmentDir, m)),
13075
+ affected: missing.map((m) => resolve38(a.assignmentDir, m)),
12430
13076
  remediation: {
12431
13077
  kind: "manual",
12432
13078
  suggestion: `Create ${missing.join(" and ")} with the renderProgress/renderComments templates, or re-scaffold via the CLI`,
@@ -12459,7 +13105,7 @@ var typeDefinition = {
12459
13105
  const { withAssignmentMd } = await listAssignments(ctx);
12460
13106
  const results = [];
12461
13107
  for (const a of withAssignmentMd) {
12462
- const path = resolve35(a.assignmentDir, "assignment.md");
13108
+ const path = resolve38(a.assignmentDir, "assignment.md");
12463
13109
  const parsed = await parseSafe(path);
12464
13110
  if (!parsed) continue;
12465
13111
  if (!parsed.type) continue;
@@ -12493,7 +13139,7 @@ var projectFrontmatterMatchesContainer = {
12493
13139
  const { withAssignmentMd } = await listAssignments(ctx);
12494
13140
  const results = [];
12495
13141
  for (const a of withAssignmentMd) {
12496
- const path = resolve35(a.assignmentDir, "assignment.md");
13142
+ const path = resolve38(a.assignmentDir, "assignment.md");
12497
13143
  const parsed = await parseSafe(path);
12498
13144
  if (!parsed) continue;
12499
13145
  if (a.standalone) {
@@ -12548,7 +13194,7 @@ var assignmentChecks = [
12548
13194
  ];
12549
13195
  async function parseSafe(path) {
12550
13196
  try {
12551
- const content = await readFile18(path, "utf-8");
13197
+ const content = await readFile21(path, "utf-8");
12552
13198
  return parseAssignmentFull(content);
12553
13199
  } catch {
12554
13200
  return null;
@@ -12567,7 +13213,7 @@ function pass4(check, detail) {
12567
13213
 
12568
13214
  // src/utils/doctor/checks/dashboard.ts
12569
13215
  init_fs();
12570
- import { resolve as resolve36 } from "path";
13216
+ import { resolve as resolve39 } from "path";
12571
13217
  var CATEGORY5 = "dashboard";
12572
13218
  var dbReachable = {
12573
13219
  id: "dashboard.db-reachable",
@@ -12581,7 +13227,7 @@ var dbReachable = {
12581
13227
  title: this.title,
12582
13228
  status: "error",
12583
13229
  detail: `could not open syntaur.db: ${ctx.dbError ?? "unknown error"}`,
12584
- affected: [resolve36(ctx.syntaurRoot, "syntaur.db")],
13230
+ affected: [resolve39(ctx.syntaurRoot, "syntaur.db")],
12585
13231
  remediation: {
12586
13232
  kind: "manual",
12587
13233
  suggestion: "Start the dashboard once (`syntaur dashboard`) to initialize the DB, or restore it from backup",
@@ -12599,7 +13245,7 @@ var dbReachable = {
12599
13245
  title: this.title,
12600
13246
  status: "error",
12601
13247
  detail: 'syntaur.db is missing the expected "sessions" table',
12602
- affected: [resolve36(ctx.syntaurRoot, "syntaur.db")],
13248
+ affected: [resolve39(ctx.syntaurRoot, "syntaur.db")],
12603
13249
  autoFixable: false
12604
13250
  };
12605
13251
  }
@@ -12611,7 +13257,7 @@ var dbReachable = {
12611
13257
  title: this.title,
12612
13258
  status: "error",
12613
13259
  detail: `syntaur.db query failed: ${err2 instanceof Error ? err2.message : String(err2)}`,
12614
- affected: [resolve36(ctx.syntaurRoot, "syntaur.db")],
13260
+ affected: [resolve39(ctx.syntaurRoot, "syntaur.db")],
12615
13261
  autoFixable: false
12616
13262
  };
12617
13263
  }
@@ -12637,7 +13283,7 @@ var ghostSessions = {
12637
13283
  const results = [];
12638
13284
  for (const row of rows) {
12639
13285
  if (!row.project_slug) continue;
12640
- const projectPath = resolve36(projectsDir2, row.project_slug, "project.md");
13286
+ const projectPath = resolve39(projectsDir2, row.project_slug, "project.md");
12641
13287
  if (!await fileExists(projectPath)) {
12642
13288
  results.push({
12643
13289
  id: this.id,
@@ -12656,7 +13302,7 @@ var ghostSessions = {
12656
13302
  continue;
12657
13303
  }
12658
13304
  if (row.assignment_slug) {
12659
- const assignmentPath = resolve36(
13305
+ const assignmentPath = resolve39(
12660
13306
  projectsDir2,
12661
13307
  row.project_slug,
12662
13308
  "assignments",
@@ -12708,7 +13354,7 @@ function skipped(check, reason) {
12708
13354
 
12709
13355
  // src/utils/doctor/checks/integrations.ts
12710
13356
  init_fs();
12711
- import { readdir as readdir14 } from "fs/promises";
13357
+ import { readdir as readdir16 } from "fs/promises";
12712
13358
  var CATEGORY6 = "integrations";
12713
13359
  var claudePluginLinked = {
12714
13360
  id: "integrations.claude-plugin-linked",
@@ -12770,7 +13416,7 @@ var backupConfigured = {
12770
13416
  if (ctx.config.backup?.repo) return pass6(this);
12771
13417
  const projectsDir2 = ctx.config.defaultProjectDir;
12772
13418
  if (!await fileExists(projectsDir2)) return skipped2(this, "no projects dir");
12773
- const entries = await readdir14(projectsDir2, { withFileTypes: true });
13419
+ const entries = await readdir16(projectsDir2, { withFileTypes: true });
12774
13420
  const hasProjects = entries.some((e) => e.isDirectory() && !e.name.startsWith(".") && !e.name.startsWith("_"));
12775
13421
  if (!hasProjects) return skipped2(this, "no projects yet");
12776
13422
  return {
@@ -12813,8 +13459,8 @@ function skipped2(check, reason) {
12813
13459
  init_fs();
12814
13460
  init_parser();
12815
13461
  init_types();
12816
- import { resolve as resolve37 } from "path";
12817
- import { readFile as readFile19 } from "fs/promises";
13462
+ import { resolve as resolve40 } from "path";
13463
+ import { readFile as readFile22 } from "fs/promises";
12818
13464
  var CATEGORY7 = "workspace";
12819
13465
  var ASSIGNMENT_FIELDS = ["projectSlug", "assignmentSlug", "projectDir", "assignmentDir"];
12820
13466
  function hasAnyAssignmentField(ctx) {
@@ -12826,12 +13472,12 @@ function isStandaloneSession(ctx) {
12826
13472
  return !hasAnyAssignmentField(ctx) && typeof ctx.sessionId === "string" && ctx.sessionId.length > 0;
12827
13473
  }
12828
13474
  async function loadContext(ctx) {
12829
- const path = resolve37(ctx.cwd, ".syntaur", "context.json");
13475
+ const path = resolve40(ctx.cwd, ".syntaur", "context.json");
12830
13476
  if (!await fileExists(path)) {
12831
13477
  return { data: null, path, exists: false, parseError: null };
12832
13478
  }
12833
13479
  try {
12834
- const raw = await readFile19(path, "utf-8");
13480
+ const raw = await readFile22(path, "utf-8");
12835
13481
  return { data: JSON.parse(raw), path, exists: true, parseError: null };
12836
13482
  } catch (err2) {
12837
13483
  return {
@@ -12906,7 +13552,7 @@ var contextAssignmentResolves = {
12906
13552
  if (!exists) return skipped3(this, "no context to resolve");
12907
13553
  if (isStandaloneSession(data)) return skipped3(this, "standalone session context \u2014 no assignment to resolve");
12908
13554
  if (!data?.assignmentDir) return skipped3(this, "context has no assignmentDir");
12909
- const assignmentMd = resolve37(data.assignmentDir, "assignment.md");
13555
+ const assignmentMd = resolve40(data.assignmentDir, "assignment.md");
12910
13556
  if (!await fileExists(assignmentMd)) {
12911
13557
  return {
12912
13558
  id: this.id,
@@ -12935,10 +13581,10 @@ var contextTerminal = {
12935
13581
  if (!exists) return skipped3(this, "no context to check");
12936
13582
  if (isStandaloneSession(data)) return skipped3(this, "standalone session context \u2014 no assignment to check");
12937
13583
  if (!data?.assignmentDir) return skipped3(this, "context has no assignmentDir");
12938
- const assignmentMd = resolve37(data.assignmentDir, "assignment.md");
13584
+ const assignmentMd = resolve40(data.assignmentDir, "assignment.md");
12939
13585
  if (!await fileExists(assignmentMd)) return skipped3(this, "assignment file missing");
12940
13586
  try {
12941
- const content = await readFile19(assignmentMd, "utf-8");
13587
+ const content = await readFile22(assignmentMd, "utf-8");
12942
13588
  const parsed = parseAssignmentFull(content);
12943
13589
  const terminal = terminalStatuses2(ctx);
12944
13590
  if (terminal.has(parsed.status)) {
@@ -13081,15 +13727,15 @@ async function finalize(checks) {
13081
13727
  }
13082
13728
  async function readVersion() {
13083
13729
  try {
13084
- const here = fileURLToPath5(import.meta.url);
13085
- let dir = dirname8(here);
13730
+ const here = fileURLToPath7(import.meta.url);
13731
+ let dir = dirname10(here);
13086
13732
  for (let i = 0; i < 6; i++) {
13087
13733
  try {
13088
- const raw = await readFile20(join4(dir, "package.json"), "utf-8");
13734
+ const raw = await readFile23(join5(dir, "package.json"), "utf-8");
13089
13735
  const parsed = JSON.parse(raw);
13090
13736
  return typeof parsed.version === "string" ? parsed.version : null;
13091
13737
  } catch {
13092
- dir = dirname8(dir);
13738
+ dir = dirname10(dir);
13093
13739
  }
13094
13740
  }
13095
13741
  return null;
@@ -13222,8 +13868,8 @@ var doctorCommand = new Command3("doctor").description("Diagnose Syntaur state a
13222
13868
  init_paths();
13223
13869
  init_fs();
13224
13870
  init_config2();
13225
- import { resolve as resolve38 } from "path";
13226
- import { readFile as readFile21 } from "fs/promises";
13871
+ import { resolve as resolve41 } from "path";
13872
+ import { readFile as readFile24 } from "fs/promises";
13227
13873
  init_timestamp();
13228
13874
  init_assignment_resolver();
13229
13875
  function shortId() {
@@ -13255,7 +13901,7 @@ async function commentCommand(target, text, options = {}) {
13255
13901
  if (!isValidSlug(target)) {
13256
13902
  throw new Error(`Invalid assignment slug "${target}".`);
13257
13903
  }
13258
- assignmentDir = resolve38(baseDir, options.project, "assignments", target);
13904
+ assignmentDir = resolve41(baseDir, options.project, "assignments", target);
13259
13905
  assignmentRef = target;
13260
13906
  } else {
13261
13907
  const resolved = await resolveAssignmentById(baseDir, assignmentsDir(), target);
@@ -13265,13 +13911,13 @@ async function commentCommand(target, text, options = {}) {
13265
13911
  assignmentDir = resolved.assignmentDir;
13266
13912
  assignmentRef = resolved.standalone ? resolved.id : resolved.assignmentSlug;
13267
13913
  }
13268
- const commentsPath = resolve38(assignmentDir, "comments.md");
13914
+ const commentsPath = resolve41(assignmentDir, "comments.md");
13269
13915
  const timestamp = nowTimestamp();
13270
13916
  const author = options.author ?? process.env.USER ?? "unknown";
13271
13917
  let currentContent;
13272
13918
  let currentCount = 0;
13273
13919
  if (await fileExists(commentsPath)) {
13274
- currentContent = await readFile21(commentsPath, "utf-8");
13920
+ currentContent = await readFile24(commentsPath, "utf-8");
13275
13921
  const countMatch = currentContent.match(/^entryCount:\s*(\d+)/m);
13276
13922
  if (countMatch) currentCount = parseInt(countMatch[1], 10);
13277
13923
  } else {
@@ -13308,8 +13954,8 @@ ${entry}`;
13308
13954
  init_paths();
13309
13955
  init_fs();
13310
13956
  init_config2();
13311
- import { resolve as resolve39 } from "path";
13312
- import { readFile as readFile22 } from "fs/promises";
13957
+ import { resolve as resolve42 } from "path";
13958
+ import { readFile as readFile25 } from "fs/promises";
13313
13959
  init_timestamp();
13314
13960
  init_assignment_resolver();
13315
13961
  function setTopLevelField3(content, key, value) {
@@ -13334,7 +13980,7 @@ async function requestCommand(target, text, options = {}) {
13334
13980
  if (!isValidSlug(target)) {
13335
13981
  throw new Error(`Invalid assignment slug "${target}".`);
13336
13982
  }
13337
- assignmentDir = resolve39(baseDir, options.project, "assignments", target);
13983
+ assignmentDir = resolve42(baseDir, options.project, "assignments", target);
13338
13984
  targetRef = target;
13339
13985
  } else {
13340
13986
  const resolved = await resolveAssignmentById(baseDir, assignmentsDir(), target);
@@ -13344,12 +13990,12 @@ async function requestCommand(target, text, options = {}) {
13344
13990
  assignmentDir = resolved.assignmentDir;
13345
13991
  targetRef = resolved.standalone ? resolved.id : resolved.assignmentSlug;
13346
13992
  }
13347
- const assignmentMdPath = resolve39(assignmentDir, "assignment.md");
13993
+ const assignmentMdPath = resolve42(assignmentDir, "assignment.md");
13348
13994
  if (!await fileExists(assignmentMdPath)) {
13349
13995
  throw new Error(`assignment.md not found at ${assignmentMdPath}`);
13350
13996
  }
13351
13997
  const source = options.from ?? process.env.SYNTAUR_ASSIGNMENT ?? "unknown";
13352
- let content = await readFile22(assignmentMdPath, "utf-8");
13998
+ let content = await readFile25(assignmentMdPath, "utf-8");
13353
13999
  const todoLine = `- [ ] ${text.trim()} (from: ${source})`;
13354
14000
  const todosHeading = /^## Todos\s*$/m;
13355
14001
  if (todosHeading.test(content)) {
@@ -13377,10 +14023,10 @@ ${todoLine}
13377
14023
 
13378
14024
  // src/cli-default-command.ts
13379
14025
  init_config2();
13380
- import { readdir as readdir15 } from "fs/promises";
14026
+ import { readdir as readdir17 } from "fs/promises";
13381
14027
  async function hasAnyProjectContent(projectsDir2) {
13382
14028
  try {
13383
- const entries = await readdir15(projectsDir2, { withFileTypes: true });
14029
+ const entries = await readdir17(projectsDir2, { withFileTypes: true });
13384
14030
  return entries.some((entry) => entry.isDirectory());
13385
14031
  } catch {
13386
14032
  return false;
@@ -13416,21 +14062,21 @@ async function getDefaultCommandName() {
13416
14062
  // src/utils/npx-prompt.ts
13417
14063
  init_paths();
13418
14064
  init_fs();
13419
- import { fileURLToPath as fileURLToPath7 } from "url";
13420
- import { readFile as readFile24 } from "fs/promises";
13421
- import { dirname as dirname10, join as join6, resolve as resolve40 } from "path";
14065
+ import { fileURLToPath as fileURLToPath9 } from "url";
14066
+ import { readFile as readFile27 } from "fs/promises";
14067
+ import { dirname as dirname12, join as join7, resolve as resolve43 } from "path";
13422
14068
  import { spawn as spawn3 } from "child_process";
13423
14069
  import { createInterface as createInterface2 } from "readline/promises";
13424
14070
 
13425
14071
  // src/utils/version.ts
13426
- import { fileURLToPath as fileURLToPath6 } from "url";
13427
- import { readFile as readFile23 } from "fs/promises";
13428
- import { dirname as dirname9, join as join5 } from "path";
14072
+ import { fileURLToPath as fileURLToPath8 } from "url";
14073
+ import { readFile as readFile26 } from "fs/promises";
14074
+ import { dirname as dirname11, join as join6 } from "path";
13429
14075
  async function readPackageVersion(scriptUrl) {
13430
14076
  try {
13431
- const scriptPath = fileURLToPath6(scriptUrl);
13432
- const pkgRoot = dirname9(dirname9(scriptPath));
13433
- const raw = await readFile23(join5(pkgRoot, "package.json"), "utf-8");
14077
+ const scriptPath = fileURLToPath8(scriptUrl);
14078
+ const pkgRoot = dirname11(dirname11(scriptPath));
14079
+ const raw = await readFile26(join6(pkgRoot, "package.json"), "utf-8");
13434
14080
  const parsed = JSON.parse(raw);
13435
14081
  return typeof parsed.version === "string" ? parsed.version : null;
13436
14082
  } catch {
@@ -13439,13 +14085,13 @@ async function readPackageVersion(scriptUrl) {
13439
14085
  }
13440
14086
 
13441
14087
  // src/utils/npx-prompt.ts
13442
- var STATE_FILE = resolve40(syntaurRoot(), "npx-install.json");
14088
+ var STATE_FILE = resolve43(syntaurRoot(), "npx-install.json");
13443
14089
  var META_ARGS = /* @__PURE__ */ new Set(["-h", "--help", "-V", "--version", "help"]);
13444
14090
  var GLOBAL_VERSION_TIMEOUT_MS = 2e3;
13445
14091
  function isRunningViaNpx(scriptUrl) {
13446
14092
  let scriptPath;
13447
14093
  try {
13448
- scriptPath = fileURLToPath7(scriptUrl);
14094
+ scriptPath = fileURLToPath9(scriptUrl);
13449
14095
  } catch {
13450
14096
  return false;
13451
14097
  }
@@ -13460,7 +14106,7 @@ function isRunningViaNpx(scriptUrl) {
13460
14106
  async function readState() {
13461
14107
  if (!await fileExists(STATE_FILE)) return null;
13462
14108
  try {
13463
- const raw = await readFile24(STATE_FILE, "utf-8");
14109
+ const raw = await readFile27(STATE_FILE, "utf-8");
13464
14110
  return JSON.parse(raw);
13465
14111
  } catch {
13466
14112
  return null;
@@ -13471,10 +14117,10 @@ async function writeState(state) {
13471
14117
  `);
13472
14118
  }
13473
14119
  async function resolveNpmBin() {
13474
- const nodeDir = dirname10(process.execPath);
14120
+ const nodeDir = dirname12(process.execPath);
13475
14121
  const isWin = process.platform === "win32";
13476
14122
  const npmName = isWin ? "npm.cmd" : "npm";
13477
- const nearNode = join6(nodeDir, npmName);
14123
+ const nearNode = join7(nodeDir, npmName);
13478
14124
  if (await fileExists(nearNode)) {
13479
14125
  return { cmd: nearNode, shell: false };
13480
14126
  }
@@ -13517,9 +14163,9 @@ async function readGlobalVersion() {
13517
14163
  });
13518
14164
  if (!rootPath) return null;
13519
14165
  try {
13520
- const manifestPath = join6(rootPath, "syntaur", "package.json");
14166
+ const manifestPath = join7(rootPath, "syntaur", "package.json");
13521
14167
  if (!await fileExists(manifestPath)) return null;
13522
- const raw = await readFile24(manifestPath, "utf-8");
14168
+ const raw = await readFile27(manifestPath, "utf-8");
13523
14169
  const parsed = JSON.parse(raw);
13524
14170
  return typeof parsed.version === "string" ? parsed.version : null;
13525
14171
  } catch {
@@ -13814,7 +14460,7 @@ program.command("setup").description("Initialize Syntaur and optionally install
13814
14460
  process.exit(1);
13815
14461
  }
13816
14462
  });
13817
- program.command("install-plugin").description("Install the Syntaur Claude Code plugin").option("--force", "Overwrite an existing Syntaur-managed install").option("--target-dir <path>", "Install the plugin at a specific directory").option("--link", "Use a symlink instead of copying files (repo-local dev only)").action(async (options) => {
14463
+ program.command("install-plugin").description("Install the Syntaur Claude Code plugin").option("--force", "Overwrite an existing Syntaur-managed install").option("--target-dir <path>", "Install the plugin at a specific directory").option("--link", "Use a symlink instead of copying files (repo-local dev only)").option("--force-skills", "Overwrite user-edited skills in ~/.claude/skills").option("--skip-skills", "Do not install protocol skills into ~/.claude/skills").action(async (options) => {
13818
14464
  try {
13819
14465
  await installPluginCommand({ ...options, promptForTarget: true });
13820
14466
  } catch (error) {
@@ -13825,7 +14471,45 @@ program.command("install-plugin").description("Install the Syntaur Claude Code p
13825
14471
  process.exit(1);
13826
14472
  }
13827
14473
  });
13828
- program.command("install-codex-plugin").description("Install the Syntaur Codex plugin and marketplace entry").option("--force", "Overwrite an existing Syntaur-managed install").option("--target-dir <path>", "Install the plugin at a specific directory").option("--marketplace-path <path>", "Write the marketplace entry to a specific file").option("--link", "Use a symlink instead of copying files (repo-local dev only)").action(async (options) => {
14474
+ program.command("install-statusline").description(
14475
+ "Install the syntaur statusLine for Claude Code. Augments ~/.claude/settings.json; wraps any existing script by default."
14476
+ ).option("--mode <mode>", "replace | wrap | skip | ask (default: ask, wrap in non-TTY)", "ask").option("--link", "Symlink the installed script to the package source (dev mode)").action(async (options) => {
14477
+ try {
14478
+ const rawMode = (options.mode ?? "ask").toLowerCase();
14479
+ const valid = ["replace", "wrap", "skip", "ask"];
14480
+ if (!valid.includes(rawMode)) {
14481
+ throw new Error(
14482
+ `Invalid --mode "${rawMode}". Must be one of: ${valid.join(", ")}.`
14483
+ );
14484
+ }
14485
+ await installStatuslineCommand({
14486
+ mode: rawMode,
14487
+ link: options.link
14488
+ });
14489
+ } catch (error) {
14490
+ console.error("Error:", error instanceof Error ? error.message : String(error));
14491
+ process.exit(1);
14492
+ }
14493
+ });
14494
+ program.command("uninstall-statusline").description(
14495
+ "Remove the syntaur statusLine. Restores the previously configured command from backup if present."
14496
+ ).option("--keep-script", "Leave ~/.syntaur/statusline.sh on disk (only edit settings.json)").action(async (options) => {
14497
+ try {
14498
+ await uninstallStatuslineCommand({ keepScript: options.keepScript });
14499
+ } catch (error) {
14500
+ console.error("Error:", error instanceof Error ? error.message : String(error));
14501
+ process.exit(1);
14502
+ }
14503
+ });
14504
+ program.command("uninstall-skills").description("Remove Syntaur protocol skills from ~/.claude/skills and/or ~/.codex/skills").option("--claude", "Remove from ~/.claude/skills").option("--codex", "Remove from ~/.codex/skills").option("--all", "Remove from both").action(async (options) => {
14505
+ try {
14506
+ await uninstallSkillsCommand(options);
14507
+ } catch (error) {
14508
+ console.error("Error:", error instanceof Error ? error.message : String(error));
14509
+ process.exit(1);
14510
+ }
14511
+ });
14512
+ program.command("install-codex-plugin").description("Install the Syntaur Codex plugin and marketplace entry").option("--force", "Overwrite an existing Syntaur-managed install").option("--target-dir <path>", "Install the plugin at a specific directory").option("--marketplace-path <path>", "Write the marketplace entry to a specific file").option("--link", "Use a symlink instead of copying files (repo-local dev only)").option("--force-skills", "Overwrite user-edited skills in ~/.codex/skills").option("--skip-skills", "Do not install protocol skills into ~/.codex/skills").action(async (options) => {
13829
14513
  try {
13830
14514
  await installCodexPluginCommand({ ...options, promptForTarget: true });
13831
14515
  } catch (error) {
@@ -13858,7 +14542,13 @@ program.command("setup-adapter").description("Generate adapter instruction files
13858
14542
  process.exit(1);
13859
14543
  }
13860
14544
  });
13861
- program.command("track-session").description("Register an agent session (optionally linked to a project/assignment)").option("--project <slug>", "Target project slug").option("--assignment <slug>", "Assignment slug").option("--agent <name>", "Agent name, e.g. claude, codex, cursor (required)").option("--session-id <id>", "Session ID (auto-generated if omitted)").option("--path <path>", "Full path to session on disk (defaults to cwd)").option("--dir <path>", "Override default project directory").option("--description <text>", "Description of what this session is for").action(async (options) => {
14545
+ program.command("track-session").description("Register an agent session (optionally linked to a project/assignment)").option("--project <slug>", "Target project slug").option("--assignment <slug>", "Assignment slug").option("--agent <name>", "Agent name, e.g. claude, codex, cursor (required)").requiredOption(
14546
+ "--session-id <id>",
14547
+ "Session id from the agent runtime (real, not generated). Claude: read from ~/.claude/sessions/<pid>.json or the SessionStart hook payload. Codex: `payload.id` from the first line of the matching ~/.codex/sessions/YYYY/MM/DD/rollout-*.jsonl."
14548
+ ).option(
14549
+ "--transcript-path <path>",
14550
+ "Absolute path to the agent rollout/transcript file (e.g. the Codex rollout jsonl or Claude transcript jsonl)."
14551
+ ).option("--path <path>", "Full path to session on disk (defaults to cwd)").option("--dir <path>", "Override default project directory").option("--description <text>", "Description of what this session is for").action(async (options) => {
13862
14552
  try {
13863
14553
  await trackSessionCommand(options);
13864
14554
  } catch (error) {