syntaur 0.3.0 → 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dashboard/server.js +497 -315
- package/dist/dashboard/server.js.map +1 -1
- package/dist/index.js +712 -517
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/platforms/claude-code/.claude-plugin/plugin.json +5 -1
- package/platforms/claude-code/agents/syntaur-expert.md +6 -3
- package/platforms/claude-code/commands/track-session/track-session.md +43 -18
- package/platforms/claude-code/hooks/hooks.json +11 -0
- package/platforms/claude-code/hooks/session-cleanup.sh +13 -23
- package/platforms/claude-code/hooks/session-start.sh +80 -0
- package/platforms/claude-code/hooks/statusline.sh +110 -0
- package/platforms/claude-code/skills/grab-assignment/SKILL.md +30 -15
- package/platforms/claude-code/skills/plan-assignment/SKILL.md +16 -8
- package/platforms/codex/.codex-plugin/plugin.json +1 -1
- package/platforms/codex/agents/syntaur-operator.md +6 -4
- package/platforms/codex/scripts/resolve-session.sh +49 -0
- package/platforms/codex/skills/grab-assignment/SKILL.md +7 -5
- package/platforms/codex/skills/plan-assignment/SKILL.md +8 -4
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
|
|
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
|
|
417
|
-
import { resolve as
|
|
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
|
|
759
|
+
return resolve5(expanded);
|
|
624
760
|
}
|
|
625
761
|
async function writeStatusConfig(statuses) {
|
|
626
|
-
const configPath =
|
|
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
|
|
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 =
|
|
816
|
+
const configPath = resolve5(syntaurRoot(), "config.md");
|
|
681
817
|
if (!await fileExists(configPath)) return;
|
|
682
|
-
const existing = await
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
924
|
+
const configPath = resolve5(syntaurRoot(), "config.md");
|
|
789
925
|
if (!await fileExists(configPath)) {
|
|
790
926
|
return { ...DEFAULT_CONFIG };
|
|
791
927
|
}
|
|
792
|
-
|
|
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
|
|
1094
|
-
import { readFile as
|
|
1235
|
+
import { resolve as resolve8 } from "path";
|
|
1236
|
+
import { readFile as readFile5 } from "fs/promises";
|
|
1095
1237
|
function resolveAssignmentPath(projectDir, assignmentSlug) {
|
|
1096
|
-
return
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
1263
|
-
import { readdir as
|
|
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 =
|
|
1268
|
-
const standalonePath =
|
|
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
|
|
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 =
|
|
1426
|
+
const assignmentsPath = resolve9(projectsDir2, p.name, "assignments");
|
|
1285
1427
|
if (!await fileExists(assignmentsPath)) continue;
|
|
1286
|
-
const entries = await
|
|
1428
|
+
const entries = await readdir4(assignmentsPath, { withFileTypes: true });
|
|
1287
1429
|
for (const a of entries) {
|
|
1288
1430
|
if (!a.isDirectory()) continue;
|
|
1289
|
-
const aPath =
|
|
1431
|
+
const aPath = resolve9(assignmentsPath, a.name, "assignment.md");
|
|
1290
1432
|
if (!await fileExists(aPath)) continue;
|
|
1291
1433
|
try {
|
|
1292
|
-
const content = await
|
|
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:
|
|
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,
|
|
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
|
|
1746
|
-
import { resolve as
|
|
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(
|
|
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
|
|
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 =
|
|
1945
|
+
const filePath = resolve10(dir, `${sanitizeSessionName(name)}.md`);
|
|
1804
1946
|
if (!await fileExists(filePath)) return null;
|
|
1805
|
-
const raw = await
|
|
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 =
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
1916
|
-
import { realpath, readdir as
|
|
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(
|
|
2012
|
-
const resolvedGit = await realpath(
|
|
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 =
|
|
2167
|
+
const projectAssignmentsDir = resolve11(projectsDir2, project.slug, "assignments");
|
|
2026
2168
|
let slugs;
|
|
2027
2169
|
try {
|
|
2028
|
-
slugs = await
|
|
2170
|
+
slugs = await readdir6(projectAssignmentsDir);
|
|
2029
2171
|
} catch {
|
|
2030
2172
|
continue;
|
|
2031
2173
|
}
|
|
2032
2174
|
for (const aslug of slugs) {
|
|
2033
|
-
const aFile =
|
|
2175
|
+
const aFile = resolve11(projectAssignmentsDir, aslug, "assignment.md");
|
|
2034
2176
|
try {
|
|
2035
|
-
const raw = await
|
|
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
|
|
2196
|
+
const entries = await readdir6(assignmentsDir2);
|
|
2055
2197
|
for (const id of entries) {
|
|
2056
2198
|
if (id.startsWith(".") || id.startsWith("_")) continue;
|
|
2057
|
-
const aFile =
|
|
2199
|
+
const aFile = resolve11(assignmentsDir2, id, "assignment.md");
|
|
2058
2200
|
try {
|
|
2059
|
-
const raw = await
|
|
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
|
|
2308
|
-
import { resolve as
|
|
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
|
|
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 =
|
|
2317
|
-
const assignmentMdPath =
|
|
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
|
|
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 =
|
|
2531
|
+
const registryPath = resolve12(dirname3(projectsDir2), "workspaces.json");
|
|
2390
2532
|
try {
|
|
2391
|
-
const raw = await
|
|
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 =
|
|
2400
|
-
await
|
|
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
|
|
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 =
|
|
2765
|
+
const filePath = resolve12(resolved.assignmentDir, fileName);
|
|
2624
2766
|
if (!await fileExists(filePath)) return null;
|
|
2625
|
-
const content = await
|
|
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 =
|
|
2640
|
-
const projectMdPath =
|
|
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
|
|
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 =
|
|
2675
|
-
const assignmentMdPath =
|
|
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
|
|
2821
|
+
const assignmentContent = await readFile9(assignmentMdPath, "utf-8");
|
|
2680
2822
|
const assignment = parseAssignmentFull(assignmentContent);
|
|
2681
2823
|
let plan = null;
|
|
2682
|
-
const planPath =
|
|
2824
|
+
const planPath = resolve12(assignmentDir, "plan.md");
|
|
2683
2825
|
if (await fileExists(planPath)) {
|
|
2684
|
-
const planContent = await
|
|
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 =
|
|
2835
|
+
const scratchpadPath = resolve12(assignmentDir, "scratchpad.md");
|
|
2694
2836
|
if (await fileExists(scratchpadPath)) {
|
|
2695
|
-
const scratchpadContent = await
|
|
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 =
|
|
2845
|
+
const handoffPath = resolve12(assignmentDir, "handoff.md");
|
|
2704
2846
|
if (await fileExists(handoffPath)) {
|
|
2705
|
-
const handoffContent = await
|
|
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 =
|
|
2856
|
+
const decisionRecordPath = resolve12(assignmentDir, "decision-record.md");
|
|
2715
2857
|
if (await fileExists(decisionRecordPath)) {
|
|
2716
|
-
const decisionRecordContent = await
|
|
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 =
|
|
2867
|
+
const progressPath = resolve12(assignmentDir, "progress.md");
|
|
2726
2868
|
if (await fileExists(progressPath)) {
|
|
2727
|
-
const progressContent = await
|
|
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 =
|
|
2878
|
+
const commentsPath = resolve12(assignmentDir, "comments.md");
|
|
2737
2879
|
if (await fileExists(commentsPath)) {
|
|
2738
|
-
const commentsContent = await
|
|
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:
|
|
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 =
|
|
3027
|
+
const assignmentMd = resolve12(sourceDir, "assignment.md");
|
|
2886
3028
|
if (await fileExists(assignmentMd)) {
|
|
2887
|
-
const content = await
|
|
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 =
|
|
3034
|
+
const path = resolve12(sourceDir, filename);
|
|
2893
3035
|
if (await fileExists(path)) {
|
|
2894
3036
|
try {
|
|
2895
|
-
bodies.push(await
|
|
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 =
|
|
3095
|
+
const assignmentMdPath = resolve12(assignmentDir, "assignment.md");
|
|
2954
3096
|
if (!await fileExists(assignmentMdPath)) return null;
|
|
2955
|
-
const assignmentContent = await
|
|
3097
|
+
const assignmentContent = await readFile9(assignmentMdPath, "utf-8");
|
|
2956
3098
|
const assignment = parseAssignmentFull(assignmentContent);
|
|
2957
3099
|
let plan = null;
|
|
2958
|
-
const planPath =
|
|
3100
|
+
const planPath = resolve12(assignmentDir, "plan.md");
|
|
2959
3101
|
if (await fileExists(planPath)) {
|
|
2960
|
-
const parsed = parsePlan(await
|
|
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 =
|
|
3106
|
+
const scratchpadPath = resolve12(assignmentDir, "scratchpad.md");
|
|
2965
3107
|
if (await fileExists(scratchpadPath)) {
|
|
2966
|
-
const parsed = parseScratchpad(await
|
|
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 =
|
|
3112
|
+
const handoffPath = resolve12(assignmentDir, "handoff.md");
|
|
2971
3113
|
if (await fileExists(handoffPath)) {
|
|
2972
|
-
const parsed = parseHandoff(await
|
|
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 =
|
|
3118
|
+
const decisionRecordPath = resolve12(assignmentDir, "decision-record.md");
|
|
2977
3119
|
if (await fileExists(decisionRecordPath)) {
|
|
2978
|
-
const parsed = parseDecisionRecord(await
|
|
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 =
|
|
3124
|
+
const progressPath = resolve12(assignmentDir, "progress.md");
|
|
2983
3125
|
if (await fileExists(progressPath)) {
|
|
2984
|
-
const parsed = parseProgress(await
|
|
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 =
|
|
3130
|
+
const commentsPath = resolve12(assignmentDir, "comments.md");
|
|
2989
3131
|
if (await fileExists(commentsPath)) {
|
|
2990
|
-
const parsed = parseComments(await
|
|
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
|
-
|
|
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 =
|
|
3033
|
-
const projectMdPath =
|
|
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
|
|
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 =
|
|
3214
|
+
const assignmentsDir2 = resolve12(projectPath, "assignments");
|
|
3069
3215
|
if (!await fileExists(assignmentsDir2)) {
|
|
3070
3216
|
return [];
|
|
3071
3217
|
}
|
|
3072
|
-
const entries = await
|
|
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 =
|
|
3224
|
+
const assignmentMd = resolve12(assignmentsDir2, entry.name, "assignment.md");
|
|
3079
3225
|
if (!await fileExists(assignmentMd)) {
|
|
3080
3226
|
continue;
|
|
3081
3227
|
}
|
|
3082
|
-
const content = await
|
|
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 =
|
|
3235
|
+
const resourcesDir = resolve12(projectPath, "resources");
|
|
3090
3236
|
if (!await fileExists(resourcesDir)) {
|
|
3091
3237
|
return [];
|
|
3092
3238
|
}
|
|
3093
|
-
const entries = await
|
|
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 =
|
|
3100
|
-
const content = await
|
|
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 =
|
|
3261
|
+
const memoriesDir = resolve12(projectPath, "memories");
|
|
3116
3262
|
if (!await fileExists(memoriesDir)) {
|
|
3117
3263
|
return [];
|
|
3118
3264
|
}
|
|
3119
|
-
const entries = await
|
|
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 =
|
|
3126
|
-
const content = await
|
|
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 =
|
|
3287
|
+
const statusPath = resolve12(projectPath, "_status.md");
|
|
3142
3288
|
if (await fileExists(statusPath)) {
|
|
3143
|
-
const statusContent = await
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
3610
|
+
return resolve12(projectsDir2, projectSlug, "project.md");
|
|
3465
3611
|
case "assignment":
|
|
3466
|
-
return assignmentSlug ?
|
|
3612
|
+
return assignmentSlug ? resolve12(projectsDir2, projectSlug, "assignments", assignmentSlug, "assignment.md") : null;
|
|
3467
3613
|
case "plan":
|
|
3468
|
-
return assignmentSlug ?
|
|
3614
|
+
return assignmentSlug ? resolve12(projectsDir2, projectSlug, "assignments", assignmentSlug, "plan.md") : null;
|
|
3469
3615
|
case "scratchpad":
|
|
3470
|
-
return assignmentSlug ?
|
|
3616
|
+
return assignmentSlug ? resolve12(projectsDir2, projectSlug, "assignments", assignmentSlug, "scratchpad.md") : null;
|
|
3471
3617
|
case "handoff":
|
|
3472
|
-
return assignmentSlug ?
|
|
3618
|
+
return assignmentSlug ? resolve12(projectsDir2, projectSlug, "assignments", assignmentSlug, "handoff.md") : null;
|
|
3473
3619
|
case "decision-record":
|
|
3474
|
-
return assignmentSlug ?
|
|
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
|
|
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 =
|
|
3506
|
-
const raw = await
|
|
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 =
|
|
3668
|
+
const filePath = resolve12(playbooksDir3, `${slug}.md`);
|
|
3523
3669
|
if (!await fileExists(filePath)) return null;
|
|
3524
|
-
const raw = await
|
|
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
|
|
3639
|
-
import { resolve as
|
|
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
|
|
3947
|
+
return resolve18(todosDir2, `${workspace}.md`);
|
|
3800
3948
|
}
|
|
3801
3949
|
function logPath(todosDir2, workspace) {
|
|
3802
|
-
return
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
4402
|
-
import { resolve as
|
|
4549
|
+
import { mkdir as mkdir2, writeFile as writeFile7 } from "fs/promises";
|
|
4550
|
+
import { resolve as resolve28 } 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,9 +4557,9 @@ 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 =
|
|
4413
|
-
const assignmentDir =
|
|
4414
|
-
const contextDir =
|
|
4560
|
+
const projectDir = resolve28(projectsDir2, projectSlug);
|
|
4561
|
+
const assignmentDir = resolve28(projectDir, "assignments", assignmentSlug);
|
|
4562
|
+
const contextDir = resolve28(workspaceDir, ".syntaur");
|
|
4415
4563
|
await mkdir2(contextDir, { recursive: true });
|
|
4416
4564
|
const context = {
|
|
4417
4565
|
projectSlug,
|
|
@@ -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
|
|
4427
|
-
|
|
4574
|
+
await writeFile7(
|
|
4575
|
+
resolve28(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
|
|
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 =
|
|
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(
|
|
5344
|
-
await ensureDir(
|
|
5345
|
-
await ensureDir(
|
|
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
|
-
|
|
5496
|
+
resolve6(projectDir, "manifest.md"),
|
|
5349
5497
|
renderManifest({ slug, timestamp })
|
|
5350
5498
|
],
|
|
5351
5499
|
[
|
|
5352
|
-
|
|
5500
|
+
resolve6(projectDir, "project.md"),
|
|
5353
5501
|
renderProject({ id, slug, title, timestamp, workspace: options.workspace })
|
|
5354
5502
|
],
|
|
5355
5503
|
[
|
|
5356
|
-
|
|
5504
|
+
resolve6(projectDir, "_index-assignments.md"),
|
|
5357
5505
|
renderIndexAssignments({ slug, title, timestamp })
|
|
5358
5506
|
],
|
|
5359
5507
|
[
|
|
5360
|
-
|
|
5508
|
+
resolve6(projectDir, "_index-plans.md"),
|
|
5361
5509
|
renderIndexPlans({ slug, title, timestamp })
|
|
5362
5510
|
],
|
|
5363
5511
|
[
|
|
5364
|
-
|
|
5512
|
+
resolve6(projectDir, "_index-decisions.md"),
|
|
5365
5513
|
renderIndexDecisions({ slug, title, timestamp })
|
|
5366
5514
|
],
|
|
5367
5515
|
[
|
|
5368
|
-
|
|
5516
|
+
resolve6(projectDir, "_status.md"),
|
|
5369
5517
|
renderStatus({ slug, title, timestamp })
|
|
5370
5518
|
],
|
|
5371
5519
|
[
|
|
5372
|
-
|
|
5520
|
+
resolve6(projectDir, "resources", "_index.md"),
|
|
5373
5521
|
renderResourcesIndex({ slug, title, timestamp })
|
|
5374
5522
|
],
|
|
5375
5523
|
[
|
|
5376
|
-
|
|
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
|
|
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 =
|
|
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 =
|
|
5471
|
-
const projectMdPath =
|
|
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 =
|
|
5627
|
+
const depDirBase = resolve7(projectDir, "assignments");
|
|
5480
5628
|
for (const dep of dependsOn) {
|
|
5481
|
-
const depDir =
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
5664
|
+
resolve7(assignmentDir, "scratchpad.md"),
|
|
5517
5665
|
renderScratchpad({
|
|
5518
5666
|
assignmentSlug: companionAssignmentRef,
|
|
5519
5667
|
timestamp
|
|
5520
5668
|
})
|
|
5521
5669
|
],
|
|
5522
5670
|
[
|
|
5523
|
-
|
|
5671
|
+
resolve7(assignmentDir, "handoff.md"),
|
|
5524
5672
|
renderHandoff({
|
|
5525
5673
|
assignmentSlug: companionAssignmentRef,
|
|
5526
5674
|
timestamp
|
|
5527
5675
|
})
|
|
5528
5676
|
],
|
|
5529
5677
|
[
|
|
5530
|
-
|
|
5678
|
+
resolve7(assignmentDir, "decision-record.md"),
|
|
5531
5679
|
renderDecisionRecord({
|
|
5532
5680
|
assignmentSlug: companionAssignmentRef,
|
|
5533
5681
|
timestamp
|
|
5534
5682
|
})
|
|
5535
5683
|
],
|
|
5536
5684
|
[
|
|
5537
|
-
|
|
5685
|
+
resolve7(assignmentDir, "progress.md"),
|
|
5538
5686
|
renderProgress({
|
|
5539
5687
|
assignment: companionAssignmentRef,
|
|
5540
5688
|
timestamp
|
|
5541
5689
|
})
|
|
5542
5690
|
],
|
|
5543
5691
|
[
|
|
5544
|
-
|
|
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
|
|
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
|
|
5602
|
-
import { writeFile as
|
|
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
|
|
5608
|
-
import { resolve as
|
|
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
|
|
5615
|
-
import { readdir as
|
|
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 ??
|
|
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
|
|
5649
|
-
|
|
5650
|
-
|
|
5651
|
-
|
|
5652
|
-
|
|
5653
|
-
|
|
5654
|
-
|
|
5655
|
-
|
|
5656
|
-
|
|
5657
|
-
|
|
5658
|
-
|
|
5659
|
-
|
|
5660
|
-
|
|
5661
|
-
|
|
5662
|
-
|
|
5663
|
-
|
|
5664
|
-
|
|
5665
|
-
|
|
5666
|
-
|
|
5667
|
-
|
|
5668
|
-
|
|
5669
|
-
|
|
5670
|
-
|
|
5671
|
-
|
|
5672
|
-
|
|
5673
|
-
|
|
5674
|
-
|
|
5675
|
-
|
|
5676
|
-
|
|
5677
|
-
|
|
5678
|
-
|
|
5679
|
-
|
|
5680
|
-
|
|
5681
|
-
|
|
5682
|
-
|
|
5683
|
-
|
|
5684
|
-
|
|
5685
|
-
|
|
5686
|
-
|
|
5687
|
-
|
|
5688
|
-
|
|
5689
|
-
|
|
5690
|
-
|
|
5691
|
-
|
|
5692
|
-
|
|
5693
|
-
|
|
5694
|
-
|
|
5695
|
-
|
|
5696
|
-
|
|
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
|
|
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 =
|
|
5725
|
-
const indexPath =
|
|
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:
|
|
5746
|
-
const raw = await
|
|
5912
|
+
const { readFile: readFile26 } = await import("fs/promises");
|
|
5913
|
+
const raw = await readFile26(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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
6103
|
-
import { rm, readFile as
|
|
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
|
|
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 =
|
|
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(
|
|
6395
|
-
await ensureDir(
|
|
6396
|
-
await ensureDir(
|
|
6397
|
-
await writeFileForce(
|
|
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
|
-
[
|
|
6401
|
-
[
|
|
6402
|
-
[
|
|
6403
|
-
[
|
|
6404
|
-
[
|
|
6405
|
-
[
|
|
6406
|
-
[
|
|
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 =
|
|
6428
|
-
const projectMdPath =
|
|
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 =
|
|
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(
|
|
6634
|
+
await writeFileForce(resolve15(assignmentDir, "assignment.md"), content);
|
|
6468
6635
|
try {
|
|
6469
6636
|
const companions = [
|
|
6470
|
-
[
|
|
6471
|
-
[
|
|
6472
|
-
[
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
6962
|
-
const assignmentPath =
|
|
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 =
|
|
6989
|
-
const assignmentPath =
|
|
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 =
|
|
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(
|
|
7205
|
+
await writeFileForce(resolve15(assignmentDir, "assignment.md"), assignmentContent);
|
|
7039
7206
|
await writeFileForce(
|
|
7040
|
-
|
|
7207
|
+
resolve15(assignmentDir, "scratchpad.md"),
|
|
7041
7208
|
renderScratchpad({ assignmentSlug: id, timestamp })
|
|
7042
7209
|
);
|
|
7043
7210
|
await writeFileForce(
|
|
7044
|
-
|
|
7211
|
+
resolve15(assignmentDir, "handoff.md"),
|
|
7045
7212
|
renderHandoff({ assignmentSlug: id, timestamp })
|
|
7046
7213
|
);
|
|
7047
7214
|
await writeFileForce(
|
|
7048
|
-
|
|
7215
|
+
resolve15(assignmentDir, "decision-record.md"),
|
|
7049
7216
|
renderDecisionRecord({ assignmentSlug: id, timestamp })
|
|
7050
7217
|
);
|
|
7051
7218
|
await writeFileForce(
|
|
7052
|
-
|
|
7219
|
+
resolve15(assignmentDir, "progress.md"),
|
|
7053
7220
|
renderProgress({ assignment: id, timestamp })
|
|
7054
7221
|
);
|
|
7055
7222
|
await writeFileForce(
|
|
7056
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
7786
|
-
import { readFile as
|
|
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 =
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
|
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:
|
|
8052
|
-
const { readFile:
|
|
8221
|
+
const { resolve: resolve42 } = await import("path");
|
|
8222
|
+
const { readFile: readFile26 } = 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(
|
|
8238
|
+
await ensureDir(resolve42(todosDir2, "archive"));
|
|
8069
8239
|
let archContent = "";
|
|
8070
8240
|
if (await fileExists(archFile)) {
|
|
8071
|
-
archContent = await
|
|
8241
|
+
archContent = await readFile26(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
|
|
8332
|
-
import { resolve as
|
|
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:
|
|
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 =
|
|
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
|
|
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(
|
|
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
|
|
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(
|
|
8479
|
-
await
|
|
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(
|
|
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
|
|
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
|
|
8725
|
+
await rename3(localPath, backupPath);
|
|
8556
8726
|
localMovedAside = true;
|
|
8557
8727
|
}
|
|
8558
|
-
await
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
9220
|
-
|
|
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 =
|
|
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 =
|
|
9317
|
-
const dashboardDist =
|
|
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 =
|
|
9332
|
-
const viteBin =
|
|
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
|
|
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 =
|
|
9418
|
-
const projectMdPath =
|
|
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 =
|
|
9450
|
-
const projectMdPath =
|
|
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
|
|
9715
|
+
readdir as readdir10,
|
|
9534
9716
|
symlink,
|
|
9535
9717
|
lstat,
|
|
9536
|
-
readFile as
|
|
9718
|
+
readFile as readFile15,
|
|
9537
9719
|
readlink,
|
|
9538
9720
|
rm as rm3,
|
|
9539
9721
|
unlink as unlink5,
|
|
9540
|
-
writeFile as
|
|
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
|
|
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
|
|
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 =
|
|
9735
|
+
const candidate = resolve23(currentDir, expectedRelativePath);
|
|
9554
9736
|
if (await fileExists(candidate)) {
|
|
9555
9737
|
return currentDir;
|
|
9556
9738
|
}
|
|
9557
|
-
const parentDir =
|
|
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" ?
|
|
9760
|
+
return pluginKind === "claude" ? resolve24(home, ".claude", "plugins", "syntaur") : resolve24(home, "plugins", "syntaur");
|
|
9579
9761
|
}
|
|
9580
9762
|
function getDefaultMarketplacePath() {
|
|
9581
|
-
return
|
|
9763
|
+
return resolve24(homedir2(), ".agents", "plugins", "marketplace.json");
|
|
9582
9764
|
}
|
|
9583
9765
|
function getClaudeMarketplacesRoot() {
|
|
9584
|
-
return
|
|
9766
|
+
return resolve24(homedir2(), ".claude", "plugins", "marketplaces");
|
|
9585
9767
|
}
|
|
9586
9768
|
function getClaudeKnownMarketplacesPath() {
|
|
9587
|
-
return
|
|
9769
|
+
return resolve24(homedir2(), ".claude", "plugins", "known_marketplaces.json");
|
|
9588
9770
|
}
|
|
9589
9771
|
function getClaudeInstalledPluginsPath() {
|
|
9590
|
-
return
|
|
9772
|
+
return resolve24(homedir2(), ".claude", "plugins", "installed_plugins.json");
|
|
9591
9773
|
}
|
|
9592
9774
|
function getInstallMarkerPath(targetDir) {
|
|
9593
|
-
return
|
|
9775
|
+
return resolve24(targetDir, INSTALL_MARKER_FILENAME);
|
|
9594
9776
|
}
|
|
9595
9777
|
async function readPackageManifest(packageRoot) {
|
|
9596
|
-
const raw = await
|
|
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
|
|
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
|
-
|
|
9794
|
+
resolve24(pluginDir, ".claude-plugin", "plugin.json")
|
|
9613
9795
|
) ?? {};
|
|
9614
9796
|
}
|
|
9615
9797
|
async function readPluginManifestName(targetDir, pluginKind) {
|
|
9616
|
-
const manifestPath =
|
|
9798
|
+
const manifestPath = resolve24(targetDir, getPluginManifestRelativePath(pluginKind));
|
|
9617
9799
|
if (!await fileExists(manifestPath)) {
|
|
9618
9800
|
return void 0;
|
|
9619
9801
|
}
|
|
9620
|
-
const raw = await
|
|
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
|
|
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 =
|
|
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
|
|
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(
|
|
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
|
|
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:
|
|
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
|
|
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
|
-
|
|
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 =
|
|
9814
|
-
const manifestPath =
|
|
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 =
|
|
10023
|
+
const candidateRoot = resolve24(expandHome(installLocation));
|
|
9842
10024
|
if (seen.has(candidateRoot)) {
|
|
9843
10025
|
continue;
|
|
9844
10026
|
}
|
|
9845
|
-
const manifestPath =
|
|
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:
|
|
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 =
|
|
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 =
|
|
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 ===
|
|
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
|
|
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
|
|
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(
|
|
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(
|
|
10425
|
+
if (!await fileExists(resolve24(syntaurRoot(), "config.md"))) {
|
|
10244
10426
|
return null;
|
|
10245
10427
|
}
|
|
10246
10428
|
return (await readConfig()).defaultProjectDir;
|
|
@@ -10586,7 +10768,7 @@ async function setupCommand(options) {
|
|
|
10586
10768
|
}
|
|
10587
10769
|
|
|
10588
10770
|
// src/commands/uninstall.ts
|
|
10589
|
-
import { resolve as
|
|
10771
|
+
import { resolve as resolve25 } from "path";
|
|
10590
10772
|
init_paths();
|
|
10591
10773
|
function expandTargets(options) {
|
|
10592
10774
|
if (options.all) {
|
|
@@ -10666,7 +10848,7 @@ async function uninstallCommand(options) {
|
|
|
10666
10848
|
const configuredProjectDir = await getConfiguredProjectDir();
|
|
10667
10849
|
await removeSyntaurData();
|
|
10668
10850
|
console.log(`Removed ${syntaurRoot()}`);
|
|
10669
|
-
if (configuredProjectDir &&
|
|
10851
|
+
if (configuredProjectDir && resolve25(configuredProjectDir) !== resolve25(syntaurRoot(), "projects")) {
|
|
10670
10852
|
console.warn(
|
|
10671
10853
|
`Warning: config.md pointed to an external project directory (${configuredProjectDir}). That directory was not removed automatically.`
|
|
10672
10854
|
);
|
|
@@ -10681,7 +10863,7 @@ async function uninstallCommand(options) {
|
|
|
10681
10863
|
init_paths();
|
|
10682
10864
|
init_fs();
|
|
10683
10865
|
init_config2();
|
|
10684
|
-
import { resolve as
|
|
10866
|
+
import { resolve as resolve26 } from "path";
|
|
10685
10867
|
var SUPPORTED_FRAMEWORKS = ["cursor", "codex", "opencode"];
|
|
10686
10868
|
async function setupAdapterCommand(framework, options) {
|
|
10687
10869
|
if (!SUPPORTED_FRAMEWORKS.includes(framework)) {
|
|
@@ -10707,19 +10889,19 @@ async function setupAdapterCommand(framework, options) {
|
|
|
10707
10889
|
}
|
|
10708
10890
|
const config = await readConfig();
|
|
10709
10891
|
const baseDir = options.dir ? expandHome(options.dir) : config.defaultProjectDir;
|
|
10710
|
-
const projectDir =
|
|
10711
|
-
const assignmentDir =
|
|
10892
|
+
const projectDir = resolve26(baseDir, options.project);
|
|
10893
|
+
const assignmentDir = resolve26(
|
|
10712
10894
|
projectDir,
|
|
10713
10895
|
"assignments",
|
|
10714
10896
|
options.assignment
|
|
10715
10897
|
);
|
|
10716
|
-
const projectMdPath =
|
|
10898
|
+
const projectMdPath = resolve26(projectDir, "project.md");
|
|
10717
10899
|
if (!await fileExists(projectDir) || !await fileExists(projectMdPath)) {
|
|
10718
10900
|
throw new Error(
|
|
10719
10901
|
`Project "${options.project}" not found at ${projectDir}.`
|
|
10720
10902
|
);
|
|
10721
10903
|
}
|
|
10722
|
-
const assignmentMdPath =
|
|
10904
|
+
const assignmentMdPath = resolve26(assignmentDir, "assignment.md");
|
|
10723
10905
|
if (!await fileExists(assignmentDir) || !await fileExists(assignmentMdPath)) {
|
|
10724
10906
|
throw new Error(
|
|
10725
10907
|
`Assignment "${options.assignment}" not found at ${assignmentDir}.`
|
|
@@ -10747,15 +10929,15 @@ async function setupAdapterCommand(framework, options) {
|
|
|
10747
10929
|
}
|
|
10748
10930
|
}
|
|
10749
10931
|
if (framework === "cursor") {
|
|
10750
|
-
const protocolPath =
|
|
10751
|
-
const assignmentPath =
|
|
10932
|
+
const protocolPath = resolve26(cwd, ".cursor", "rules", "syntaur-protocol.mdc");
|
|
10933
|
+
const assignmentPath = resolve26(cwd, ".cursor", "rules", "syntaur-assignment.mdc");
|
|
10752
10934
|
await writeAdapterFile(protocolPath, renderCursorProtocol());
|
|
10753
10935
|
await writeAdapterFile(assignmentPath, renderCursorAssignment(rendererParams));
|
|
10754
10936
|
} else if (framework === "codex" || framework === "opencode") {
|
|
10755
|
-
const agentsPath =
|
|
10937
|
+
const agentsPath = resolve26(cwd, "AGENTS.md");
|
|
10756
10938
|
await writeAdapterFile(agentsPath, renderCodexAgents(rendererParams));
|
|
10757
10939
|
if (framework === "opencode") {
|
|
10758
|
-
const configPath =
|
|
10940
|
+
const configPath = resolve26(cwd, "opencode.json");
|
|
10759
10941
|
await writeAdapterFile(configPath, renderOpenCodeConfig({ projectDir }));
|
|
10760
10942
|
}
|
|
10761
10943
|
}
|
|
@@ -10780,16 +10962,20 @@ async function setupAdapterCommand(framework, options) {
|
|
|
10780
10962
|
init_paths();
|
|
10781
10963
|
init_fs();
|
|
10782
10964
|
init_config2();
|
|
10783
|
-
import { resolve as
|
|
10784
|
-
import { randomUUID as randomUUID2 } from "crypto";
|
|
10965
|
+
import { resolve as resolve27 } from "path";
|
|
10785
10966
|
async function trackSessionCommand(options) {
|
|
10786
10967
|
if (!options.agent) {
|
|
10787
10968
|
throw new Error("--agent <name> is required.");
|
|
10788
10969
|
}
|
|
10970
|
+
if (!options.sessionId) {
|
|
10971
|
+
throw new Error(
|
|
10972
|
+
"--session-id <id> is required. Pass the real agent-generated session id \u2014 do not synthesize one."
|
|
10973
|
+
);
|
|
10974
|
+
}
|
|
10789
10975
|
if (options.project) {
|
|
10790
10976
|
const config = await readConfig();
|
|
10791
10977
|
const baseDir = options.dir ? expandHome(options.dir) : config.defaultProjectDir;
|
|
10792
|
-
const projectDir =
|
|
10978
|
+
const projectDir = resolve27(baseDir, options.project);
|
|
10793
10979
|
if (!await fileExists(projectDir)) {
|
|
10794
10980
|
throw new Error(
|
|
10795
10981
|
`Project "${options.project}" not found at ${projectDir}.`
|
|
@@ -10797,7 +10983,7 @@ async function trackSessionCommand(options) {
|
|
|
10797
10983
|
}
|
|
10798
10984
|
}
|
|
10799
10985
|
initSessionDb();
|
|
10800
|
-
const sessionId = options
|
|
10986
|
+
const { sessionId } = options;
|
|
10801
10987
|
await appendSession("", {
|
|
10802
10988
|
projectSlug: options.project || null,
|
|
10803
10989
|
assignmentSlug: options.assignment || null,
|
|
@@ -10806,10 +10992,13 @@ async function trackSessionCommand(options) {
|
|
|
10806
10992
|
started: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10807
10993
|
status: "active",
|
|
10808
10994
|
path: options.path || process.cwd(),
|
|
10809
|
-
description: options.description || null
|
|
10995
|
+
description: options.description || null,
|
|
10996
|
+
transcriptPath: options.transcriptPath ?? null
|
|
10810
10997
|
});
|
|
10811
10998
|
if (options.project && options.assignment) {
|
|
10812
|
-
console.log(
|
|
10999
|
+
console.log(
|
|
11000
|
+
`Registered agent session ${sessionId} for ${options.assignment} in ${options.project}.`
|
|
11001
|
+
);
|
|
10813
11002
|
} else {
|
|
10814
11003
|
console.log(`Registered standalone agent session ${sessionId}.`);
|
|
10815
11004
|
}
|
|
@@ -10841,7 +11030,7 @@ async function browseCommand(options) {
|
|
|
10841
11030
|
}
|
|
10842
11031
|
|
|
10843
11032
|
// src/commands/create-playbook.ts
|
|
10844
|
-
import { resolve as
|
|
11033
|
+
import { resolve as resolve29 } from "path";
|
|
10845
11034
|
init_timestamp();
|
|
10846
11035
|
init_paths();
|
|
10847
11036
|
init_fs();
|
|
@@ -10857,7 +11046,7 @@ async function createPlaybookCommand(name, options) {
|
|
|
10857
11046
|
}
|
|
10858
11047
|
const dir = playbooksDir();
|
|
10859
11048
|
await ensureDir(dir);
|
|
10860
|
-
const filePath =
|
|
11049
|
+
const filePath = resolve29(dir, `${slug}.md`);
|
|
10861
11050
|
if (await fileExists(filePath)) {
|
|
10862
11051
|
throw new Error(
|
|
10863
11052
|
`Playbook "${slug}" already exists at ${filePath}
|
|
@@ -10878,15 +11067,15 @@ Use --slug to specify a different slug.`
|
|
|
10878
11067
|
init_paths();
|
|
10879
11068
|
init_fs();
|
|
10880
11069
|
init_parser();
|
|
10881
|
-
import { readdir as
|
|
10882
|
-
import { resolve as
|
|
11070
|
+
import { readdir as readdir11, readFile as readFile16 } from "fs/promises";
|
|
11071
|
+
import { resolve as resolve30 } from "path";
|
|
10883
11072
|
async function listPlaybooksCommand() {
|
|
10884
11073
|
const dir = playbooksDir();
|
|
10885
11074
|
if (!await fileExists(dir)) {
|
|
10886
11075
|
console.log('No playbooks directory found. Run "syntaur init" first.');
|
|
10887
11076
|
return;
|
|
10888
11077
|
}
|
|
10889
|
-
const entries = await
|
|
11078
|
+
const entries = await readdir11(dir, { withFileTypes: true });
|
|
10890
11079
|
const mdFiles = entries.filter((e) => e.isFile() && e.name.endsWith(".md") && !e.name.startsWith("_") && e.name !== "manifest.md");
|
|
10891
11080
|
if (mdFiles.length === 0) {
|
|
10892
11081
|
console.log('No playbooks found. Create one with "syntaur create-playbook <name>".');
|
|
@@ -10897,8 +11086,8 @@ async function listPlaybooksCommand() {
|
|
|
10897
11086
|
console.log(`${"Slug".padEnd(30)} ${"Name".padEnd(30)} Description`);
|
|
10898
11087
|
console.log(`${"\u2500".repeat(30)} ${"\u2500".repeat(30)} ${"\u2500".repeat(40)}`);
|
|
10899
11088
|
for (const entry of mdFiles) {
|
|
10900
|
-
const filePath =
|
|
10901
|
-
const raw = await
|
|
11089
|
+
const filePath = resolve30(dir, entry.name);
|
|
11090
|
+
const raw = await readFile16(filePath, "utf-8");
|
|
10902
11091
|
const parsed = parsePlaybook(raw);
|
|
10903
11092
|
const slug = parsed.slug || entry.name.replace(/\.md$/, "");
|
|
10904
11093
|
const name = parsed.name || slug;
|
|
@@ -10912,8 +11101,8 @@ init_paths();
|
|
|
10912
11101
|
init_parser2();
|
|
10913
11102
|
init_fs();
|
|
10914
11103
|
import { Command } from "commander";
|
|
10915
|
-
import { readFile as
|
|
10916
|
-
import { resolve as
|
|
11104
|
+
import { readFile as readFile17 } from "fs/promises";
|
|
11105
|
+
import { resolve as resolve31 } from "path";
|
|
10917
11106
|
var WORKSPACE_REGEX2 = /^[a-z0-9_][a-z0-9-]*$/;
|
|
10918
11107
|
function resolveWorkspace(options) {
|
|
10919
11108
|
if (options.global) return "_global";
|
|
@@ -11194,10 +11383,10 @@ todoCommand.command("archive").description("Archive completed todos and their lo
|
|
|
11194
11383
|
(e) => e.itemIds.every((id) => completedIds.has(id))
|
|
11195
11384
|
);
|
|
11196
11385
|
const archFile = archivePath(todosPath, workspace, checklist.archiveInterval);
|
|
11197
|
-
await ensureDir(
|
|
11386
|
+
await ensureDir(resolve31(todosPath, "archive"));
|
|
11198
11387
|
let archContent = "";
|
|
11199
11388
|
if (await fileExists(archFile)) {
|
|
11200
|
-
archContent = await
|
|
11389
|
+
archContent = await readFile17(archFile, "utf-8");
|
|
11201
11390
|
archContent = archContent.trimEnd() + "\n\n";
|
|
11202
11391
|
} else {
|
|
11203
11392
|
archContent = `---
|
|
@@ -11386,7 +11575,7 @@ import { Command as Command3 } from "commander";
|
|
|
11386
11575
|
|
|
11387
11576
|
// src/utils/doctor/index.ts
|
|
11388
11577
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
11389
|
-
import { readFile as
|
|
11578
|
+
import { readFile as readFile21 } from "fs/promises";
|
|
11390
11579
|
import { dirname as dirname8, join as join4 } from "path";
|
|
11391
11580
|
|
|
11392
11581
|
// src/utils/doctor/context.ts
|
|
@@ -11394,11 +11583,11 @@ init_config2();
|
|
|
11394
11583
|
init_paths();
|
|
11395
11584
|
init_fs();
|
|
11396
11585
|
import Database2 from "better-sqlite3";
|
|
11397
|
-
import { resolve as
|
|
11586
|
+
import { resolve as resolve32 } from "path";
|
|
11398
11587
|
async function buildCheckContext(cwd = process.cwd()) {
|
|
11399
11588
|
const config = await readConfig();
|
|
11400
11589
|
const root = syntaurRoot();
|
|
11401
|
-
const dbPath =
|
|
11590
|
+
const dbPath = resolve32(root, "syntaur.db");
|
|
11402
11591
|
let db2 = null;
|
|
11403
11592
|
let dbError = null;
|
|
11404
11593
|
if (await fileExists(dbPath)) {
|
|
@@ -11432,8 +11621,8 @@ function closeCheckContext(ctx) {
|
|
|
11432
11621
|
// src/utils/doctor/checks/env.ts
|
|
11433
11622
|
init_fs();
|
|
11434
11623
|
init_paths();
|
|
11435
|
-
import { resolve as
|
|
11436
|
-
import { readFile as
|
|
11624
|
+
import { resolve as resolve33, isAbsolute as isAbsolute3 } from "path";
|
|
11625
|
+
import { readFile as readFile18, stat as stat2 } from "fs/promises";
|
|
11437
11626
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
11438
11627
|
import { dirname as dirname7, join as join3 } from "path";
|
|
11439
11628
|
var CATEGORY = "env";
|
|
@@ -11473,7 +11662,7 @@ var configValid = {
|
|
|
11473
11662
|
category: CATEGORY,
|
|
11474
11663
|
title: "~/.syntaur/config.md is valid",
|
|
11475
11664
|
async run(ctx) {
|
|
11476
|
-
const configPath =
|
|
11665
|
+
const configPath = resolve33(ctx.syntaurRoot, "config.md");
|
|
11477
11666
|
if (!await fileExists(configPath)) {
|
|
11478
11667
|
return {
|
|
11479
11668
|
id: this.id,
|
|
@@ -11490,7 +11679,7 @@ var configValid = {
|
|
|
11490
11679
|
autoFixable: false
|
|
11491
11680
|
};
|
|
11492
11681
|
}
|
|
11493
|
-
const content = await
|
|
11682
|
+
const content = await readFile18(configPath, "utf-8");
|
|
11494
11683
|
const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
11495
11684
|
if (!fmMatch || fmMatch[1].trim() === "") {
|
|
11496
11685
|
return {
|
|
@@ -11770,7 +11959,7 @@ async function readLocalPkg() {
|
|
|
11770
11959
|
for (let i = 0; i < 6; i++) {
|
|
11771
11960
|
const candidate = join3(dir, "package.json");
|
|
11772
11961
|
try {
|
|
11773
|
-
const text = await
|
|
11962
|
+
const text = await readFile18(candidate, "utf-8");
|
|
11774
11963
|
return JSON.parse(text);
|
|
11775
11964
|
} catch {
|
|
11776
11965
|
dir = dirname7(dir);
|
|
@@ -11822,8 +12011,8 @@ function versionGte(a, b) {
|
|
|
11822
12011
|
|
|
11823
12012
|
// src/utils/doctor/checks/structure.ts
|
|
11824
12013
|
init_fs();
|
|
11825
|
-
import { resolve as
|
|
11826
|
-
import { readdir as
|
|
12014
|
+
import { resolve as resolve34 } from "path";
|
|
12015
|
+
import { readdir as readdir12, stat as stat3 } from "fs/promises";
|
|
11827
12016
|
var CATEGORY2 = "structure";
|
|
11828
12017
|
var KNOWN_TOP_LEVEL = /* @__PURE__ */ new Set([
|
|
11829
12018
|
"projects",
|
|
@@ -11842,7 +12031,7 @@ var projectsDir = {
|
|
|
11842
12031
|
category: CATEGORY2,
|
|
11843
12032
|
title: "projects/ directory exists",
|
|
11844
12033
|
async run(ctx) {
|
|
11845
|
-
const p =
|
|
12034
|
+
const p = resolve34(ctx.syntaurRoot, "projects");
|
|
11846
12035
|
if (!await fileExists(p)) {
|
|
11847
12036
|
return {
|
|
11848
12037
|
id: this.id,
|
|
@@ -11867,7 +12056,7 @@ var playbooksDir2 = {
|
|
|
11867
12056
|
category: CATEGORY2,
|
|
11868
12057
|
title: "playbooks/ directory exists",
|
|
11869
12058
|
async run(ctx) {
|
|
11870
|
-
const p =
|
|
12059
|
+
const p = resolve34(ctx.syntaurRoot, "playbooks");
|
|
11871
12060
|
if (!await fileExists(p)) {
|
|
11872
12061
|
return {
|
|
11873
12062
|
id: this.id,
|
|
@@ -11892,7 +12081,7 @@ var todosDirValid = {
|
|
|
11892
12081
|
category: CATEGORY2,
|
|
11893
12082
|
title: "todos/ directory is readable (if present)",
|
|
11894
12083
|
async run(ctx) {
|
|
11895
|
-
const p =
|
|
12084
|
+
const p = resolve34(ctx.syntaurRoot, "todos");
|
|
11896
12085
|
if (!await fileExists(p)) {
|
|
11897
12086
|
return {
|
|
11898
12087
|
id: this.id,
|
|
@@ -11923,7 +12112,7 @@ var serversDirValid = {
|
|
|
11923
12112
|
category: CATEGORY2,
|
|
11924
12113
|
title: "servers/ directory is readable (if present)",
|
|
11925
12114
|
async run(ctx) {
|
|
11926
|
-
const p =
|
|
12115
|
+
const p = resolve34(ctx.syntaurRoot, "servers");
|
|
11927
12116
|
if (!await fileExists(p)) {
|
|
11928
12117
|
return {
|
|
11929
12118
|
id: this.id,
|
|
@@ -11954,7 +12143,7 @@ var knownFilesRecognized = {
|
|
|
11954
12143
|
category: CATEGORY2,
|
|
11955
12144
|
title: "No unexpected top-level entries under ~/.syntaur/",
|
|
11956
12145
|
async run(ctx) {
|
|
11957
|
-
const entries = await
|
|
12146
|
+
const entries = await readdir12(ctx.syntaurRoot, { withFileTypes: true });
|
|
11958
12147
|
const unexpected = [];
|
|
11959
12148
|
for (const e of entries) {
|
|
11960
12149
|
if (e.name.startsWith(".")) continue;
|
|
@@ -11968,7 +12157,7 @@ var knownFilesRecognized = {
|
|
|
11968
12157
|
title: this.title,
|
|
11969
12158
|
status: "warn",
|
|
11970
12159
|
detail: `unexpected top-level entries: ${unexpected.join(", ")}`,
|
|
11971
|
-
affected: unexpected.map((n) =>
|
|
12160
|
+
affected: unexpected.map((n) => resolve34(ctx.syntaurRoot, n)),
|
|
11972
12161
|
remediation: {
|
|
11973
12162
|
kind: "manual",
|
|
11974
12163
|
suggestion: "Review these entries \u2014 they may be leftover state from older versions",
|
|
@@ -11997,8 +12186,8 @@ function pass2(check) {
|
|
|
11997
12186
|
|
|
11998
12187
|
// src/utils/doctor/checks/project.ts
|
|
11999
12188
|
init_fs();
|
|
12000
|
-
import { resolve as
|
|
12001
|
-
import { readdir as
|
|
12189
|
+
import { resolve as resolve35 } from "path";
|
|
12190
|
+
import { readdir as readdir13, stat as stat4 } from "fs/promises";
|
|
12002
12191
|
var CATEGORY3 = "project";
|
|
12003
12192
|
var REQUIRED_PROJECT_FILES = [
|
|
12004
12193
|
"project.md",
|
|
@@ -12022,15 +12211,15 @@ var PROJECT_MARKERS = ["project.md", "manifest.md", "assignments"];
|
|
|
12022
12211
|
async function listProjects2(ctx) {
|
|
12023
12212
|
const dir = ctx.config.defaultProjectDir;
|
|
12024
12213
|
if (!await fileExists(dir)) return [];
|
|
12025
|
-
const entries = await
|
|
12214
|
+
const entries = await readdir13(dir, { withFileTypes: true });
|
|
12026
12215
|
const result = [];
|
|
12027
12216
|
for (const e of entries) {
|
|
12028
12217
|
if (!e.isDirectory()) continue;
|
|
12029
12218
|
if (e.name.startsWith(".") || e.name.startsWith("_")) continue;
|
|
12030
|
-
const projectDir =
|
|
12219
|
+
const projectDir = resolve35(dir, e.name);
|
|
12031
12220
|
let looksLikeProject = false;
|
|
12032
12221
|
for (const marker of PROJECT_MARKERS) {
|
|
12033
|
-
if (await fileExists(
|
|
12222
|
+
if (await fileExists(resolve35(projectDir, marker))) {
|
|
12034
12223
|
looksLikeProject = true;
|
|
12035
12224
|
break;
|
|
12036
12225
|
}
|
|
@@ -12049,7 +12238,7 @@ var requiredFiles = {
|
|
|
12049
12238
|
for (const projectDir of projects) {
|
|
12050
12239
|
const missing = [];
|
|
12051
12240
|
for (const rel of REQUIRED_PROJECT_FILES) {
|
|
12052
|
-
const p =
|
|
12241
|
+
const p = resolve35(projectDir, rel);
|
|
12053
12242
|
if (!await fileExists(p)) missing.push(rel);
|
|
12054
12243
|
}
|
|
12055
12244
|
if (missing.length === 0) continue;
|
|
@@ -12059,7 +12248,7 @@ var requiredFiles = {
|
|
|
12059
12248
|
title: this.title,
|
|
12060
12249
|
status: "error",
|
|
12061
12250
|
detail: `project at ${projectDir} is missing: ${missing.join(", ")}`,
|
|
12062
|
-
affected: missing.map((m) =>
|
|
12251
|
+
affected: missing.map((m) => resolve35(projectDir, m)),
|
|
12063
12252
|
remediation: {
|
|
12064
12253
|
kind: "manual",
|
|
12065
12254
|
suggestion: "Recreate the missing scaffold files from templates",
|
|
@@ -12082,7 +12271,7 @@ var manifestStale = {
|
|
|
12082
12271
|
const projects = await listProjects2(ctx);
|
|
12083
12272
|
const results = [];
|
|
12084
12273
|
for (const projectDir of projects) {
|
|
12085
|
-
const manifestPath =
|
|
12274
|
+
const manifestPath = resolve35(projectDir, "manifest.md");
|
|
12086
12275
|
if (!await fileExists(manifestPath)) continue;
|
|
12087
12276
|
const manifestMtime = (await stat4(manifestPath)).mtimeMs;
|
|
12088
12277
|
const newestAssignment = await newestAssignmentMtime(projectDir);
|
|
@@ -12116,7 +12305,7 @@ var orphanFiles = {
|
|
|
12116
12305
|
const projects = await listProjects2(ctx);
|
|
12117
12306
|
const results = [];
|
|
12118
12307
|
for (const projectDir of projects) {
|
|
12119
|
-
const entries = await
|
|
12308
|
+
const entries = await readdir13(projectDir, { withFileTypes: true });
|
|
12120
12309
|
const orphans = [];
|
|
12121
12310
|
for (const e of entries) {
|
|
12122
12311
|
if (e.name.startsWith(".")) continue;
|
|
@@ -12131,7 +12320,7 @@ var orphanFiles = {
|
|
|
12131
12320
|
title: this.title,
|
|
12132
12321
|
status: "warn",
|
|
12133
12322
|
detail: `project at ${projectDir} has unexpected entries: ${orphans.join(", ")}`,
|
|
12134
|
-
affected: orphans.map((o) =>
|
|
12323
|
+
affected: orphans.map((o) => resolve35(projectDir, o)),
|
|
12135
12324
|
autoFixable: false
|
|
12136
12325
|
});
|
|
12137
12326
|
}
|
|
@@ -12141,18 +12330,18 @@ var orphanFiles = {
|
|
|
12141
12330
|
};
|
|
12142
12331
|
var projectChecks = [requiredFiles, manifestStale, orphanFiles];
|
|
12143
12332
|
async function newestAssignmentMtime(projectDir) {
|
|
12144
|
-
const assignmentsRoot =
|
|
12333
|
+
const assignmentsRoot = resolve35(projectDir, "assignments");
|
|
12145
12334
|
if (!await fileExists(assignmentsRoot)) return 0;
|
|
12146
12335
|
let newest = 0;
|
|
12147
12336
|
let entries;
|
|
12148
12337
|
try {
|
|
12149
|
-
entries = await
|
|
12338
|
+
entries = await readdir13(assignmentsRoot, { withFileTypes: true });
|
|
12150
12339
|
} catch {
|
|
12151
12340
|
return 0;
|
|
12152
12341
|
}
|
|
12153
12342
|
for (const e of entries) {
|
|
12154
12343
|
if (!e.isDirectory()) continue;
|
|
12155
|
-
const assignmentMd =
|
|
12344
|
+
const assignmentMd = resolve35(assignmentsRoot, e.name, "assignment.md");
|
|
12156
12345
|
try {
|
|
12157
12346
|
const s = await stat4(assignmentMd);
|
|
12158
12347
|
if (s.mtimeMs > newest) newest = s.mtimeMs;
|
|
@@ -12176,28 +12365,28 @@ init_fs();
|
|
|
12176
12365
|
init_parser();
|
|
12177
12366
|
init_types();
|
|
12178
12367
|
init_paths();
|
|
12179
|
-
import { resolve as
|
|
12180
|
-
import { readdir as
|
|
12368
|
+
import { resolve as resolve36 } from "path";
|
|
12369
|
+
import { readdir as readdir14, readFile as readFile19 } from "fs/promises";
|
|
12181
12370
|
var CATEGORY4 = "assignment";
|
|
12182
12371
|
var STATUSES_REQUIRING_HANDOFF = /* @__PURE__ */ new Set(["review", "completed"]);
|
|
12183
12372
|
async function listAssignments(ctx) {
|
|
12184
12373
|
const result = { withAssignmentMd: [], orphanFolders: [] };
|
|
12185
12374
|
const projectsDir2 = ctx.config.defaultProjectDir;
|
|
12186
12375
|
if (await fileExists(projectsDir2)) {
|
|
12187
|
-
const projects = await
|
|
12376
|
+
const projects = await readdir14(projectsDir2, { withFileTypes: true });
|
|
12188
12377
|
for (const m of projects) {
|
|
12189
12378
|
if (!m.isDirectory()) continue;
|
|
12190
12379
|
if (m.name.startsWith(".") || m.name.startsWith("_")) continue;
|
|
12191
|
-
const assignmentsDir2 =
|
|
12380
|
+
const assignmentsDir2 = resolve36(projectsDir2, m.name, "assignments");
|
|
12192
12381
|
if (!await fileExists(assignmentsDir2)) continue;
|
|
12193
|
-
const entries = await
|
|
12382
|
+
const entries = await readdir14(assignmentsDir2, { withFileTypes: true });
|
|
12194
12383
|
for (const a of entries) {
|
|
12195
12384
|
if (!a.isDirectory()) continue;
|
|
12196
12385
|
if (a.name.startsWith(".") || a.name.startsWith("_")) continue;
|
|
12197
|
-
const assignmentDir =
|
|
12198
|
-
const assignmentMd =
|
|
12386
|
+
const assignmentDir = resolve36(assignmentsDir2, a.name);
|
|
12387
|
+
const assignmentMd = resolve36(assignmentDir, "assignment.md");
|
|
12199
12388
|
const entry = {
|
|
12200
|
-
projectDir:
|
|
12389
|
+
projectDir: resolve36(projectsDir2, m.name),
|
|
12201
12390
|
projectSlug: m.name,
|
|
12202
12391
|
assignmentDir,
|
|
12203
12392
|
assignmentSlug: a.name,
|
|
@@ -12213,12 +12402,12 @@ async function listAssignments(ctx) {
|
|
|
12213
12402
|
}
|
|
12214
12403
|
const standaloneRoot = assignmentsDir();
|
|
12215
12404
|
if (await fileExists(standaloneRoot)) {
|
|
12216
|
-
const entries = await
|
|
12405
|
+
const entries = await readdir14(standaloneRoot, { withFileTypes: true });
|
|
12217
12406
|
for (const a of entries) {
|
|
12218
12407
|
if (!a.isDirectory()) continue;
|
|
12219
12408
|
if (a.name.startsWith(".") || a.name.startsWith("_")) continue;
|
|
12220
|
-
const assignmentDir =
|
|
12221
|
-
const assignmentMd =
|
|
12409
|
+
const assignmentDir = resolve36(standaloneRoot, a.name);
|
|
12410
|
+
const assignmentMd = resolve36(assignmentDir, "assignment.md");
|
|
12222
12411
|
const entry = {
|
|
12223
12412
|
projectDir: standaloneRoot,
|
|
12224
12413
|
projectSlug: null,
|
|
@@ -12296,7 +12485,7 @@ var invalidStatus = {
|
|
|
12296
12485
|
const allowed = configuredStatuses(ctx);
|
|
12297
12486
|
const results = [];
|
|
12298
12487
|
for (const a of withAssignmentMd) {
|
|
12299
|
-
const path =
|
|
12488
|
+
const path = resolve36(a.assignmentDir, "assignment.md");
|
|
12300
12489
|
const parsed = await parseSafe(path);
|
|
12301
12490
|
if (!parsed) continue;
|
|
12302
12491
|
if (!allowed.has(parsed.status)) {
|
|
@@ -12329,7 +12518,7 @@ var workspaceMissing = {
|
|
|
12329
12518
|
const terminal = terminalStatuses(ctx);
|
|
12330
12519
|
const results = [];
|
|
12331
12520
|
for (const a of withAssignmentMd) {
|
|
12332
|
-
const path =
|
|
12521
|
+
const path = resolve36(a.assignmentDir, "assignment.md");
|
|
12333
12522
|
const parsed = await parseSafe(path);
|
|
12334
12523
|
if (!parsed) continue;
|
|
12335
12524
|
if (terminal.has(parsed.status)) continue;
|
|
@@ -12376,12 +12565,12 @@ var requiredFilesByStatus = {
|
|
|
12376
12565
|
const { withAssignmentMd } = await listAssignments(ctx);
|
|
12377
12566
|
const results = [];
|
|
12378
12567
|
for (const a of withAssignmentMd) {
|
|
12379
|
-
const assignmentPath =
|
|
12568
|
+
const assignmentPath = resolve36(a.assignmentDir, "assignment.md");
|
|
12380
12569
|
const parsed = await parseSafe(assignmentPath);
|
|
12381
12570
|
if (!parsed) continue;
|
|
12382
12571
|
const missing = [];
|
|
12383
12572
|
if (STATUSES_REQUIRING_HANDOFF.has(parsed.status)) {
|
|
12384
|
-
const handoffPath =
|
|
12573
|
+
const handoffPath = resolve36(a.assignmentDir, "handoff.md");
|
|
12385
12574
|
if (!await fileExists(handoffPath)) missing.push("handoff.md");
|
|
12386
12575
|
}
|
|
12387
12576
|
if (missing.length === 0) continue;
|
|
@@ -12391,7 +12580,7 @@ var requiredFilesByStatus = {
|
|
|
12391
12580
|
title: this.title,
|
|
12392
12581
|
status: "warn",
|
|
12393
12582
|
detail: `${a.projectSlug}/${a.assignmentSlug} (status: ${parsed.status}) is missing ${missing.join(", ")}`,
|
|
12394
|
-
affected: missing.map((m) =>
|
|
12583
|
+
affected: missing.map((m) => resolve36(a.assignmentDir, m)),
|
|
12395
12584
|
remediation: {
|
|
12396
12585
|
kind: "manual",
|
|
12397
12586
|
suggestion: `Create the missing ${missing.join(" and ")} files for this assignment`,
|
|
@@ -12414,7 +12603,7 @@ var companionFilesScaffolded = {
|
|
|
12414
12603
|
for (const a of withAssignmentMd) {
|
|
12415
12604
|
const missing = [];
|
|
12416
12605
|
for (const filename of ["progress.md", "comments.md"]) {
|
|
12417
|
-
if (!await fileExists(
|
|
12606
|
+
if (!await fileExists(resolve36(a.assignmentDir, filename))) {
|
|
12418
12607
|
missing.push(filename);
|
|
12419
12608
|
}
|
|
12420
12609
|
}
|
|
@@ -12426,7 +12615,7 @@ var companionFilesScaffolded = {
|
|
|
12426
12615
|
title: this.title,
|
|
12427
12616
|
status: "warn",
|
|
12428
12617
|
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) =>
|
|
12618
|
+
affected: missing.map((m) => resolve36(a.assignmentDir, m)),
|
|
12430
12619
|
remediation: {
|
|
12431
12620
|
kind: "manual",
|
|
12432
12621
|
suggestion: `Create ${missing.join(" and ")} with the renderProgress/renderComments templates, or re-scaffold via the CLI`,
|
|
@@ -12459,7 +12648,7 @@ var typeDefinition = {
|
|
|
12459
12648
|
const { withAssignmentMd } = await listAssignments(ctx);
|
|
12460
12649
|
const results = [];
|
|
12461
12650
|
for (const a of withAssignmentMd) {
|
|
12462
|
-
const path =
|
|
12651
|
+
const path = resolve36(a.assignmentDir, "assignment.md");
|
|
12463
12652
|
const parsed = await parseSafe(path);
|
|
12464
12653
|
if (!parsed) continue;
|
|
12465
12654
|
if (!parsed.type) continue;
|
|
@@ -12493,7 +12682,7 @@ var projectFrontmatterMatchesContainer = {
|
|
|
12493
12682
|
const { withAssignmentMd } = await listAssignments(ctx);
|
|
12494
12683
|
const results = [];
|
|
12495
12684
|
for (const a of withAssignmentMd) {
|
|
12496
|
-
const path =
|
|
12685
|
+
const path = resolve36(a.assignmentDir, "assignment.md");
|
|
12497
12686
|
const parsed = await parseSafe(path);
|
|
12498
12687
|
if (!parsed) continue;
|
|
12499
12688
|
if (a.standalone) {
|
|
@@ -12548,7 +12737,7 @@ var assignmentChecks = [
|
|
|
12548
12737
|
];
|
|
12549
12738
|
async function parseSafe(path) {
|
|
12550
12739
|
try {
|
|
12551
|
-
const content = await
|
|
12740
|
+
const content = await readFile19(path, "utf-8");
|
|
12552
12741
|
return parseAssignmentFull(content);
|
|
12553
12742
|
} catch {
|
|
12554
12743
|
return null;
|
|
@@ -12567,7 +12756,7 @@ function pass4(check, detail) {
|
|
|
12567
12756
|
|
|
12568
12757
|
// src/utils/doctor/checks/dashboard.ts
|
|
12569
12758
|
init_fs();
|
|
12570
|
-
import { resolve as
|
|
12759
|
+
import { resolve as resolve37 } from "path";
|
|
12571
12760
|
var CATEGORY5 = "dashboard";
|
|
12572
12761
|
var dbReachable = {
|
|
12573
12762
|
id: "dashboard.db-reachable",
|
|
@@ -12581,7 +12770,7 @@ var dbReachable = {
|
|
|
12581
12770
|
title: this.title,
|
|
12582
12771
|
status: "error",
|
|
12583
12772
|
detail: `could not open syntaur.db: ${ctx.dbError ?? "unknown error"}`,
|
|
12584
|
-
affected: [
|
|
12773
|
+
affected: [resolve37(ctx.syntaurRoot, "syntaur.db")],
|
|
12585
12774
|
remediation: {
|
|
12586
12775
|
kind: "manual",
|
|
12587
12776
|
suggestion: "Start the dashboard once (`syntaur dashboard`) to initialize the DB, or restore it from backup",
|
|
@@ -12599,7 +12788,7 @@ var dbReachable = {
|
|
|
12599
12788
|
title: this.title,
|
|
12600
12789
|
status: "error",
|
|
12601
12790
|
detail: 'syntaur.db is missing the expected "sessions" table',
|
|
12602
|
-
affected: [
|
|
12791
|
+
affected: [resolve37(ctx.syntaurRoot, "syntaur.db")],
|
|
12603
12792
|
autoFixable: false
|
|
12604
12793
|
};
|
|
12605
12794
|
}
|
|
@@ -12611,7 +12800,7 @@ var dbReachable = {
|
|
|
12611
12800
|
title: this.title,
|
|
12612
12801
|
status: "error",
|
|
12613
12802
|
detail: `syntaur.db query failed: ${err2 instanceof Error ? err2.message : String(err2)}`,
|
|
12614
|
-
affected: [
|
|
12803
|
+
affected: [resolve37(ctx.syntaurRoot, "syntaur.db")],
|
|
12615
12804
|
autoFixable: false
|
|
12616
12805
|
};
|
|
12617
12806
|
}
|
|
@@ -12637,7 +12826,7 @@ var ghostSessions = {
|
|
|
12637
12826
|
const results = [];
|
|
12638
12827
|
for (const row of rows) {
|
|
12639
12828
|
if (!row.project_slug) continue;
|
|
12640
|
-
const projectPath =
|
|
12829
|
+
const projectPath = resolve37(projectsDir2, row.project_slug, "project.md");
|
|
12641
12830
|
if (!await fileExists(projectPath)) {
|
|
12642
12831
|
results.push({
|
|
12643
12832
|
id: this.id,
|
|
@@ -12656,7 +12845,7 @@ var ghostSessions = {
|
|
|
12656
12845
|
continue;
|
|
12657
12846
|
}
|
|
12658
12847
|
if (row.assignment_slug) {
|
|
12659
|
-
const assignmentPath =
|
|
12848
|
+
const assignmentPath = resolve37(
|
|
12660
12849
|
projectsDir2,
|
|
12661
12850
|
row.project_slug,
|
|
12662
12851
|
"assignments",
|
|
@@ -12708,7 +12897,7 @@ function skipped(check, reason) {
|
|
|
12708
12897
|
|
|
12709
12898
|
// src/utils/doctor/checks/integrations.ts
|
|
12710
12899
|
init_fs();
|
|
12711
|
-
import { readdir as
|
|
12900
|
+
import { readdir as readdir15 } from "fs/promises";
|
|
12712
12901
|
var CATEGORY6 = "integrations";
|
|
12713
12902
|
var claudePluginLinked = {
|
|
12714
12903
|
id: "integrations.claude-plugin-linked",
|
|
@@ -12770,7 +12959,7 @@ var backupConfigured = {
|
|
|
12770
12959
|
if (ctx.config.backup?.repo) return pass6(this);
|
|
12771
12960
|
const projectsDir2 = ctx.config.defaultProjectDir;
|
|
12772
12961
|
if (!await fileExists(projectsDir2)) return skipped2(this, "no projects dir");
|
|
12773
|
-
const entries = await
|
|
12962
|
+
const entries = await readdir15(projectsDir2, { withFileTypes: true });
|
|
12774
12963
|
const hasProjects = entries.some((e) => e.isDirectory() && !e.name.startsWith(".") && !e.name.startsWith("_"));
|
|
12775
12964
|
if (!hasProjects) return skipped2(this, "no projects yet");
|
|
12776
12965
|
return {
|
|
@@ -12813,8 +13002,8 @@ function skipped2(check, reason) {
|
|
|
12813
13002
|
init_fs();
|
|
12814
13003
|
init_parser();
|
|
12815
13004
|
init_types();
|
|
12816
|
-
import { resolve as
|
|
12817
|
-
import { readFile as
|
|
13005
|
+
import { resolve as resolve38 } from "path";
|
|
13006
|
+
import { readFile as readFile20 } from "fs/promises";
|
|
12818
13007
|
var CATEGORY7 = "workspace";
|
|
12819
13008
|
var ASSIGNMENT_FIELDS = ["projectSlug", "assignmentSlug", "projectDir", "assignmentDir"];
|
|
12820
13009
|
function hasAnyAssignmentField(ctx) {
|
|
@@ -12826,12 +13015,12 @@ function isStandaloneSession(ctx) {
|
|
|
12826
13015
|
return !hasAnyAssignmentField(ctx) && typeof ctx.sessionId === "string" && ctx.sessionId.length > 0;
|
|
12827
13016
|
}
|
|
12828
13017
|
async function loadContext(ctx) {
|
|
12829
|
-
const path =
|
|
13018
|
+
const path = resolve38(ctx.cwd, ".syntaur", "context.json");
|
|
12830
13019
|
if (!await fileExists(path)) {
|
|
12831
13020
|
return { data: null, path, exists: false, parseError: null };
|
|
12832
13021
|
}
|
|
12833
13022
|
try {
|
|
12834
|
-
const raw = await
|
|
13023
|
+
const raw = await readFile20(path, "utf-8");
|
|
12835
13024
|
return { data: JSON.parse(raw), path, exists: true, parseError: null };
|
|
12836
13025
|
} catch (err2) {
|
|
12837
13026
|
return {
|
|
@@ -12906,7 +13095,7 @@ var contextAssignmentResolves = {
|
|
|
12906
13095
|
if (!exists) return skipped3(this, "no context to resolve");
|
|
12907
13096
|
if (isStandaloneSession(data)) return skipped3(this, "standalone session context \u2014 no assignment to resolve");
|
|
12908
13097
|
if (!data?.assignmentDir) return skipped3(this, "context has no assignmentDir");
|
|
12909
|
-
const assignmentMd =
|
|
13098
|
+
const assignmentMd = resolve38(data.assignmentDir, "assignment.md");
|
|
12910
13099
|
if (!await fileExists(assignmentMd)) {
|
|
12911
13100
|
return {
|
|
12912
13101
|
id: this.id,
|
|
@@ -12935,10 +13124,10 @@ var contextTerminal = {
|
|
|
12935
13124
|
if (!exists) return skipped3(this, "no context to check");
|
|
12936
13125
|
if (isStandaloneSession(data)) return skipped3(this, "standalone session context \u2014 no assignment to check");
|
|
12937
13126
|
if (!data?.assignmentDir) return skipped3(this, "context has no assignmentDir");
|
|
12938
|
-
const assignmentMd =
|
|
13127
|
+
const assignmentMd = resolve38(data.assignmentDir, "assignment.md");
|
|
12939
13128
|
if (!await fileExists(assignmentMd)) return skipped3(this, "assignment file missing");
|
|
12940
13129
|
try {
|
|
12941
|
-
const content = await
|
|
13130
|
+
const content = await readFile20(assignmentMd, "utf-8");
|
|
12942
13131
|
const parsed = parseAssignmentFull(content);
|
|
12943
13132
|
const terminal = terminalStatuses2(ctx);
|
|
12944
13133
|
if (terminal.has(parsed.status)) {
|
|
@@ -13085,7 +13274,7 @@ async function readVersion() {
|
|
|
13085
13274
|
let dir = dirname8(here);
|
|
13086
13275
|
for (let i = 0; i < 6; i++) {
|
|
13087
13276
|
try {
|
|
13088
|
-
const raw = await
|
|
13277
|
+
const raw = await readFile21(join4(dir, "package.json"), "utf-8");
|
|
13089
13278
|
const parsed = JSON.parse(raw);
|
|
13090
13279
|
return typeof parsed.version === "string" ? parsed.version : null;
|
|
13091
13280
|
} catch {
|
|
@@ -13222,8 +13411,8 @@ var doctorCommand = new Command3("doctor").description("Diagnose Syntaur state a
|
|
|
13222
13411
|
init_paths();
|
|
13223
13412
|
init_fs();
|
|
13224
13413
|
init_config2();
|
|
13225
|
-
import { resolve as
|
|
13226
|
-
import { readFile as
|
|
13414
|
+
import { resolve as resolve39 } from "path";
|
|
13415
|
+
import { readFile as readFile22 } from "fs/promises";
|
|
13227
13416
|
init_timestamp();
|
|
13228
13417
|
init_assignment_resolver();
|
|
13229
13418
|
function shortId() {
|
|
@@ -13255,7 +13444,7 @@ async function commentCommand(target, text, options = {}) {
|
|
|
13255
13444
|
if (!isValidSlug(target)) {
|
|
13256
13445
|
throw new Error(`Invalid assignment slug "${target}".`);
|
|
13257
13446
|
}
|
|
13258
|
-
assignmentDir =
|
|
13447
|
+
assignmentDir = resolve39(baseDir, options.project, "assignments", target);
|
|
13259
13448
|
assignmentRef = target;
|
|
13260
13449
|
} else {
|
|
13261
13450
|
const resolved = await resolveAssignmentById(baseDir, assignmentsDir(), target);
|
|
@@ -13265,13 +13454,13 @@ async function commentCommand(target, text, options = {}) {
|
|
|
13265
13454
|
assignmentDir = resolved.assignmentDir;
|
|
13266
13455
|
assignmentRef = resolved.standalone ? resolved.id : resolved.assignmentSlug;
|
|
13267
13456
|
}
|
|
13268
|
-
const commentsPath =
|
|
13457
|
+
const commentsPath = resolve39(assignmentDir, "comments.md");
|
|
13269
13458
|
const timestamp = nowTimestamp();
|
|
13270
13459
|
const author = options.author ?? process.env.USER ?? "unknown";
|
|
13271
13460
|
let currentContent;
|
|
13272
13461
|
let currentCount = 0;
|
|
13273
13462
|
if (await fileExists(commentsPath)) {
|
|
13274
|
-
currentContent = await
|
|
13463
|
+
currentContent = await readFile22(commentsPath, "utf-8");
|
|
13275
13464
|
const countMatch = currentContent.match(/^entryCount:\s*(\d+)/m);
|
|
13276
13465
|
if (countMatch) currentCount = parseInt(countMatch[1], 10);
|
|
13277
13466
|
} else {
|
|
@@ -13308,8 +13497,8 @@ ${entry}`;
|
|
|
13308
13497
|
init_paths();
|
|
13309
13498
|
init_fs();
|
|
13310
13499
|
init_config2();
|
|
13311
|
-
import { resolve as
|
|
13312
|
-
import { readFile as
|
|
13500
|
+
import { resolve as resolve40 } from "path";
|
|
13501
|
+
import { readFile as readFile23 } from "fs/promises";
|
|
13313
13502
|
init_timestamp();
|
|
13314
13503
|
init_assignment_resolver();
|
|
13315
13504
|
function setTopLevelField3(content, key, value) {
|
|
@@ -13334,7 +13523,7 @@ async function requestCommand(target, text, options = {}) {
|
|
|
13334
13523
|
if (!isValidSlug(target)) {
|
|
13335
13524
|
throw new Error(`Invalid assignment slug "${target}".`);
|
|
13336
13525
|
}
|
|
13337
|
-
assignmentDir =
|
|
13526
|
+
assignmentDir = resolve40(baseDir, options.project, "assignments", target);
|
|
13338
13527
|
targetRef = target;
|
|
13339
13528
|
} else {
|
|
13340
13529
|
const resolved = await resolveAssignmentById(baseDir, assignmentsDir(), target);
|
|
@@ -13344,12 +13533,12 @@ async function requestCommand(target, text, options = {}) {
|
|
|
13344
13533
|
assignmentDir = resolved.assignmentDir;
|
|
13345
13534
|
targetRef = resolved.standalone ? resolved.id : resolved.assignmentSlug;
|
|
13346
13535
|
}
|
|
13347
|
-
const assignmentMdPath =
|
|
13536
|
+
const assignmentMdPath = resolve40(assignmentDir, "assignment.md");
|
|
13348
13537
|
if (!await fileExists(assignmentMdPath)) {
|
|
13349
13538
|
throw new Error(`assignment.md not found at ${assignmentMdPath}`);
|
|
13350
13539
|
}
|
|
13351
13540
|
const source = options.from ?? process.env.SYNTAUR_ASSIGNMENT ?? "unknown";
|
|
13352
|
-
let content = await
|
|
13541
|
+
let content = await readFile23(assignmentMdPath, "utf-8");
|
|
13353
13542
|
const todoLine = `- [ ] ${text.trim()} (from: ${source})`;
|
|
13354
13543
|
const todosHeading = /^## Todos\s*$/m;
|
|
13355
13544
|
if (todosHeading.test(content)) {
|
|
@@ -13377,10 +13566,10 @@ ${todoLine}
|
|
|
13377
13566
|
|
|
13378
13567
|
// src/cli-default-command.ts
|
|
13379
13568
|
init_config2();
|
|
13380
|
-
import { readdir as
|
|
13569
|
+
import { readdir as readdir16 } from "fs/promises";
|
|
13381
13570
|
async function hasAnyProjectContent(projectsDir2) {
|
|
13382
13571
|
try {
|
|
13383
|
-
const entries = await
|
|
13572
|
+
const entries = await readdir16(projectsDir2, { withFileTypes: true });
|
|
13384
13573
|
return entries.some((entry) => entry.isDirectory());
|
|
13385
13574
|
} catch {
|
|
13386
13575
|
return false;
|
|
@@ -13417,20 +13606,20 @@ async function getDefaultCommandName() {
|
|
|
13417
13606
|
init_paths();
|
|
13418
13607
|
init_fs();
|
|
13419
13608
|
import { fileURLToPath as fileURLToPath7 } from "url";
|
|
13420
|
-
import { readFile as
|
|
13421
|
-
import { dirname as dirname10, join as join6, resolve as
|
|
13609
|
+
import { readFile as readFile25 } from "fs/promises";
|
|
13610
|
+
import { dirname as dirname10, join as join6, resolve as resolve41 } from "path";
|
|
13422
13611
|
import { spawn as spawn3 } from "child_process";
|
|
13423
13612
|
import { createInterface as createInterface2 } from "readline/promises";
|
|
13424
13613
|
|
|
13425
13614
|
// src/utils/version.ts
|
|
13426
13615
|
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
13427
|
-
import { readFile as
|
|
13616
|
+
import { readFile as readFile24 } from "fs/promises";
|
|
13428
13617
|
import { dirname as dirname9, join as join5 } from "path";
|
|
13429
13618
|
async function readPackageVersion(scriptUrl) {
|
|
13430
13619
|
try {
|
|
13431
13620
|
const scriptPath = fileURLToPath6(scriptUrl);
|
|
13432
13621
|
const pkgRoot = dirname9(dirname9(scriptPath));
|
|
13433
|
-
const raw = await
|
|
13622
|
+
const raw = await readFile24(join5(pkgRoot, "package.json"), "utf-8");
|
|
13434
13623
|
const parsed = JSON.parse(raw);
|
|
13435
13624
|
return typeof parsed.version === "string" ? parsed.version : null;
|
|
13436
13625
|
} catch {
|
|
@@ -13439,7 +13628,7 @@ async function readPackageVersion(scriptUrl) {
|
|
|
13439
13628
|
}
|
|
13440
13629
|
|
|
13441
13630
|
// src/utils/npx-prompt.ts
|
|
13442
|
-
var STATE_FILE =
|
|
13631
|
+
var STATE_FILE = resolve41(syntaurRoot(), "npx-install.json");
|
|
13443
13632
|
var META_ARGS = /* @__PURE__ */ new Set(["-h", "--help", "-V", "--version", "help"]);
|
|
13444
13633
|
var GLOBAL_VERSION_TIMEOUT_MS = 2e3;
|
|
13445
13634
|
function isRunningViaNpx(scriptUrl) {
|
|
@@ -13460,7 +13649,7 @@ function isRunningViaNpx(scriptUrl) {
|
|
|
13460
13649
|
async function readState() {
|
|
13461
13650
|
if (!await fileExists(STATE_FILE)) return null;
|
|
13462
13651
|
try {
|
|
13463
|
-
const raw = await
|
|
13652
|
+
const raw = await readFile25(STATE_FILE, "utf-8");
|
|
13464
13653
|
return JSON.parse(raw);
|
|
13465
13654
|
} catch {
|
|
13466
13655
|
return null;
|
|
@@ -13519,7 +13708,7 @@ async function readGlobalVersion() {
|
|
|
13519
13708
|
try {
|
|
13520
13709
|
const manifestPath = join6(rootPath, "syntaur", "package.json");
|
|
13521
13710
|
if (!await fileExists(manifestPath)) return null;
|
|
13522
|
-
const raw = await
|
|
13711
|
+
const raw = await readFile25(manifestPath, "utf-8");
|
|
13523
13712
|
const parsed = JSON.parse(raw);
|
|
13524
13713
|
return typeof parsed.version === "string" ? parsed.version : null;
|
|
13525
13714
|
} catch {
|
|
@@ -13858,7 +14047,13 @@ program.command("setup-adapter").description("Generate adapter instruction files
|
|
|
13858
14047
|
process.exit(1);
|
|
13859
14048
|
}
|
|
13860
14049
|
});
|
|
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)").
|
|
14050
|
+
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(
|
|
14051
|
+
"--session-id <id>",
|
|
14052
|
+
"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."
|
|
14053
|
+
).option(
|
|
14054
|
+
"--transcript-path <path>",
|
|
14055
|
+
"Absolute path to the agent rollout/transcript file (e.g. the Codex rollout jsonl or Claude transcript jsonl)."
|
|
14056
|
+
).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
14057
|
try {
|
|
13863
14058
|
await trackSessionCommand(options);
|
|
13864
14059
|
} catch (error) {
|