syntaur 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -3
- package/dist/dashboard/server.js +497 -315
- package/dist/dashboard/server.js.map +1 -1
- package/dist/index.js +1240 -550
- package/dist/index.js.map +1 -1
- package/package.json +8 -2
- package/platforms/claude-code/.claude-plugin/plugin.json +1 -1
- package/platforms/claude-code/agents/syntaur-expert.md +35 -20
- package/platforms/claude-code/commands/complete-assignment/complete-assignment.md +20 -0
- package/platforms/claude-code/commands/create-assignment/create-assignment.md +20 -0
- package/platforms/claude-code/commands/create-project/create-project.md +20 -0
- package/platforms/claude-code/commands/grab-assignment/grab-assignment.md +20 -0
- package/platforms/claude-code/commands/plan-assignment/plan-assignment.md +20 -0
- 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/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/statusline/statusline.sh +133 -0
- package/vendor/syntaur-skills/LICENSE +21 -0
- package/vendor/syntaur-skills/README.md +43 -0
- package/vendor/syntaur-skills/skills/complete-assignment/SKILL.md +146 -0
- package/vendor/syntaur-skills/skills/create-assignment/SKILL.md +72 -0
- package/vendor/syntaur-skills/skills/create-project/SKILL.md +56 -0
- package/vendor/syntaur-skills/skills/grab-assignment/SKILL.md +158 -0
- package/vendor/syntaur-skills/skills/plan-assignment/SKILL.md +137 -0
- package/vendor/syntaur-skills/skills/syntaur-protocol/SKILL.md +119 -0
- package/vendor/syntaur-skills/skills/syntaur-protocol/references/file-ownership.md +67 -0
- package/vendor/syntaur-skills/skills/syntaur-protocol/references/protocol-summary.md +82 -0
- package/platforms/claude-code/skills/complete-assignment/SKILL.md +0 -155
- package/platforms/claude-code/skills/create-assignment/SKILL.md +0 -67
- package/platforms/claude-code/skills/grab-assignment/SKILL.md +0 -187
- package/platforms/claude-code/skills/plan-assignment/SKILL.md +0 -148
- package/platforms/claude-code/skills/syntaur-protocol/SKILL.md +0 -86
- package/platforms/codex/skills/complete-assignment/SKILL.md +0 -64
- package/platforms/codex/skills/create-assignment/SKILL.md +0 -49
- package/platforms/codex/skills/grab-assignment/SKILL.md +0 -71
- package/platforms/codex/skills/plan-assignment/SKILL.md +0 -57
- package/platforms/codex/skills/syntaur-protocol/SKILL.md +0 -102
package/dist/index.js
CHANGED
|
@@ -168,9 +168,10 @@ function parseListField(frontmatter, fieldName) {
|
|
|
168
168
|
}
|
|
169
169
|
function parseProject(fileContent) {
|
|
170
170
|
const [fm, body] = extractFrontmatter(fileContent);
|
|
171
|
+
const slug = getField(fm, "slug") ?? getField(fm, "mission") ?? "";
|
|
171
172
|
return {
|
|
172
173
|
id: getField(fm, "id") ?? "",
|
|
173
|
-
slug
|
|
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
|
|
4402
|
-
import { resolve as
|
|
4549
|
+
import { mkdir as mkdir4, writeFile as writeFile8 } from "fs/promises";
|
|
4550
|
+
import { resolve as resolve30 } from "path";
|
|
4403
4551
|
async function launchAgent(options) {
|
|
4404
4552
|
const { projectsDir: projectsDir2, projectSlug, assignmentSlug, agent } = options;
|
|
4405
4553
|
const command = AGENT_COMMANDS[agent];
|
|
@@ -4409,10 +4557,10 @@ async function launchAgent(options) {
|
|
|
4409
4557
|
process.exit(1);
|
|
4410
4558
|
}
|
|
4411
4559
|
const workspaceDir = detail.workspace.worktreePath ?? (detail.workspace.repository?.startsWith("/") ? detail.workspace.repository : null) ?? process.cwd();
|
|
4412
|
-
const projectDir =
|
|
4413
|
-
const assignmentDir =
|
|
4414
|
-
const contextDir =
|
|
4415
|
-
await
|
|
4560
|
+
const projectDir = resolve30(projectsDir2, projectSlug);
|
|
4561
|
+
const assignmentDir = resolve30(projectDir, "assignments", assignmentSlug);
|
|
4562
|
+
const contextDir = resolve30(workspaceDir, ".syntaur");
|
|
4563
|
+
await mkdir4(contextDir, { recursive: true });
|
|
4416
4564
|
const context = {
|
|
4417
4565
|
projectSlug,
|
|
4418
4566
|
assignmentSlug,
|
|
@@ -4423,8 +4571,8 @@ async function launchAgent(options) {
|
|
|
4423
4571
|
branch: detail.workspace.branch ?? null,
|
|
4424
4572
|
grabbedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4425
4573
|
};
|
|
4426
|
-
await
|
|
4427
|
-
|
|
4574
|
+
await writeFile8(
|
|
4575
|
+
resolve30(contextDir, "context.json"),
|
|
4428
4576
|
JSON.stringify(context, null, 2) + "\n"
|
|
4429
4577
|
);
|
|
4430
4578
|
return new Promise((resolvePromise, reject) => {
|
|
@@ -4573,7 +4721,7 @@ async function seedDefaultPlaybooks(playbooksDir3) {
|
|
|
4573
4721
|
}
|
|
4574
4722
|
|
|
4575
4723
|
// src/commands/create-project.ts
|
|
4576
|
-
import { resolve as
|
|
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: readFile28 } = await import("fs/promises");
|
|
5913
|
+
const raw = await readFile28(filePath, "utf-8");
|
|
5747
5914
|
const sessions = [];
|
|
5748
5915
|
const lines = raw.split("\n");
|
|
5749
5916
|
let inTable = false;
|
|
@@ -5799,13 +5966,13 @@ async function appendSession(_projectDir, session) {
|
|
|
5799
5966
|
INSERT INTO sessions (session_id, project_slug, assignment_slug, agent, started, status, path, description, transcript_path)
|
|
5800
5967
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
5801
5968
|
ON CONFLICT(session_id) DO UPDATE SET
|
|
5802
|
-
project_slug = COALESCE(excluded.project_slug, project_slug),
|
|
5803
|
-
assignment_slug = COALESCE(excluded.assignment_slug, assignment_slug),
|
|
5969
|
+
project_slug = COALESCE(NULLIF(excluded.project_slug, ''), project_slug),
|
|
5970
|
+
assignment_slug = COALESCE(NULLIF(excluded.assignment_slug, ''), assignment_slug),
|
|
5804
5971
|
agent = excluded.agent,
|
|
5805
5972
|
status = CASE WHEN status IN ('completed','stopped') THEN status ELSE excluded.status END,
|
|
5806
|
-
path = COALESCE(excluded.path, path),
|
|
5807
|
-
description = COALESCE(excluded.description, description),
|
|
5808
|
-
transcript_path = COALESCE(excluded.transcript_path, transcript_path),
|
|
5973
|
+
path = COALESCE(NULLIF(excluded.path, ''), path),
|
|
5974
|
+
description = COALESCE(NULLIF(excluded.description, ''), description),
|
|
5975
|
+
transcript_path = COALESCE(NULLIF(excluded.transcript_path, ''), transcript_path),
|
|
5809
5976
|
updated_at = datetime('now')
|
|
5810
5977
|
`).run(
|
|
5811
5978
|
session.sessionId,
|
|
@@ -5855,13 +6022,13 @@ async function deleteSessions(sessionIds) {
|
|
|
5855
6022
|
var DONE_ASSIGNMENT_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "review"]);
|
|
5856
6023
|
async function readAssignmentStatusFromPath(assignmentMdPath) {
|
|
5857
6024
|
if (!await fileExists(assignmentMdPath)) return null;
|
|
5858
|
-
const raw = await
|
|
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: resolve44 } = await import("path");
|
|
8222
|
+
const { readFile: readFile28 } = await import("fs/promises");
|
|
8053
8223
|
const { writeFileForce: writeFileForce2 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
|
|
8054
8224
|
const workspace = getWorkspaceParam(req.params.workspace);
|
|
8055
8225
|
const checklist = await readChecklist(todosDir2, workspace);
|
|
@@ -8065,10 +8235,10 @@ function createTodosRouter(todosDir2, broadcast) {
|
|
|
8065
8235
|
(e) => e.itemIds.every((id) => completedIds.has(id))
|
|
8066
8236
|
);
|
|
8067
8237
|
const archFile = archivePath2(todosDir2, workspace, checklist.archiveInterval);
|
|
8068
|
-
await ensureDir(
|
|
8238
|
+
await ensureDir(resolve44(todosDir2, "archive"));
|
|
8069
8239
|
let archContent = "";
|
|
8070
8240
|
if (await fileExists(archFile)) {
|
|
8071
|
-
archContent = await
|
|
8241
|
+
archContent = await readFile28(archFile, "utf-8");
|
|
8072
8242
|
archContent = archContent.trimEnd() + "\n\n";
|
|
8073
8243
|
} else {
|
|
8074
8244
|
archContent = `---
|
|
@@ -8328,8 +8498,8 @@ init_fs();
|
|
|
8328
8498
|
init_config2();
|
|
8329
8499
|
import { execFile as execFile2 } from "child_process";
|
|
8330
8500
|
import { promisify as promisify2 } from "util";
|
|
8331
|
-
import { cp, mkdtemp, rm as rm2, readFile as
|
|
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;
|
|
@@ -10307,6 +10489,165 @@ async function textPrompt(question, defaultValue) {
|
|
|
10307
10489
|
}
|
|
10308
10490
|
}
|
|
10309
10491
|
|
|
10492
|
+
// src/utils/install-skills.ts
|
|
10493
|
+
init_fs();
|
|
10494
|
+
import { readFile as readFile16, readdir as readdir11, mkdir as mkdir2, copyFile, rm as rm4 } from "fs/promises";
|
|
10495
|
+
import { dirname as dirname7, resolve as resolve25, relative as relative3, join as join3 } from "path";
|
|
10496
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
10497
|
+
import { homedir as homedir3 } from "os";
|
|
10498
|
+
var REQUIRED_SKILLS = [
|
|
10499
|
+
"syntaur-protocol",
|
|
10500
|
+
"grab-assignment",
|
|
10501
|
+
"plan-assignment",
|
|
10502
|
+
"complete-assignment",
|
|
10503
|
+
"create-assignment",
|
|
10504
|
+
"create-project"
|
|
10505
|
+
];
|
|
10506
|
+
function getVendoredSkillsDir() {
|
|
10507
|
+
const here = dirname7(fileURLToPath4(import.meta.url));
|
|
10508
|
+
return resolve25(here, "..", "vendor", "syntaur-skills", "skills");
|
|
10509
|
+
}
|
|
10510
|
+
function defaultSkillTargetDir(target) {
|
|
10511
|
+
if (target === "claude") return resolve25(homedir3(), ".claude", "skills");
|
|
10512
|
+
return resolve25(homedir3(), ".codex", "skills");
|
|
10513
|
+
}
|
|
10514
|
+
async function walkFiles(root) {
|
|
10515
|
+
const out = [];
|
|
10516
|
+
async function walk(dir) {
|
|
10517
|
+
const entries = await readdir11(dir, { withFileTypes: true });
|
|
10518
|
+
for (const entry of entries) {
|
|
10519
|
+
const full = join3(dir, entry.name);
|
|
10520
|
+
if (entry.isDirectory()) {
|
|
10521
|
+
await walk(full);
|
|
10522
|
+
} else if (entry.isFile()) {
|
|
10523
|
+
out.push(full);
|
|
10524
|
+
}
|
|
10525
|
+
}
|
|
10526
|
+
}
|
|
10527
|
+
await walk(root);
|
|
10528
|
+
return out.sort();
|
|
10529
|
+
}
|
|
10530
|
+
async function filesEqual(a, b) {
|
|
10531
|
+
try {
|
|
10532
|
+
const [ba, bb] = await Promise.all([readFile16(a), readFile16(b)]);
|
|
10533
|
+
if (ba.length !== bb.length) return false;
|
|
10534
|
+
return ba.equals(bb);
|
|
10535
|
+
} catch {
|
|
10536
|
+
return false;
|
|
10537
|
+
}
|
|
10538
|
+
}
|
|
10539
|
+
async function copyDir(srcDir, destDir) {
|
|
10540
|
+
await mkdir2(destDir, { recursive: true });
|
|
10541
|
+
const entries = await readdir11(srcDir, { withFileTypes: true });
|
|
10542
|
+
for (const entry of entries) {
|
|
10543
|
+
const src = join3(srcDir, entry.name);
|
|
10544
|
+
const dest = join3(destDir, entry.name);
|
|
10545
|
+
if (entry.isDirectory()) {
|
|
10546
|
+
await copyDir(src, dest);
|
|
10547
|
+
} else if (entry.isFile()) {
|
|
10548
|
+
await copyFile(src, dest);
|
|
10549
|
+
}
|
|
10550
|
+
}
|
|
10551
|
+
}
|
|
10552
|
+
async function skillMatches(srcDir, destDir) {
|
|
10553
|
+
if (!await fileExists(destDir)) return false;
|
|
10554
|
+
const srcFiles = await walkFiles(srcDir);
|
|
10555
|
+
for (const srcFile of srcFiles) {
|
|
10556
|
+
const rel = relative3(srcDir, srcFile);
|
|
10557
|
+
const destFile = join3(destDir, rel);
|
|
10558
|
+
if (!await filesEqual(srcFile, destFile)) return false;
|
|
10559
|
+
}
|
|
10560
|
+
const destFiles = await walkFiles(destDir);
|
|
10561
|
+
if (destFiles.length !== srcFiles.length) return false;
|
|
10562
|
+
return true;
|
|
10563
|
+
}
|
|
10564
|
+
async function installSkills(options) {
|
|
10565
|
+
const source = options.sourceDir ?? getVendoredSkillsDir();
|
|
10566
|
+
const targetRoot = options.targetDir ?? defaultSkillTargetDir(options.target);
|
|
10567
|
+
const force = options.force ?? false;
|
|
10568
|
+
if (!await fileExists(source)) {
|
|
10569
|
+
throw new Error(
|
|
10570
|
+
`Vendored skills not found at ${source}. Reinstall syntaur: npm install -g syntaur@latest`
|
|
10571
|
+
);
|
|
10572
|
+
}
|
|
10573
|
+
const results = [];
|
|
10574
|
+
await mkdir2(targetRoot, { recursive: true });
|
|
10575
|
+
for (const skill of REQUIRED_SKILLS) {
|
|
10576
|
+
const srcDir = join3(source, skill);
|
|
10577
|
+
const destDir = join3(targetRoot, skill);
|
|
10578
|
+
if (!await fileExists(srcDir)) continue;
|
|
10579
|
+
if (!await fileExists(destDir)) {
|
|
10580
|
+
await copyDir(srcDir, destDir);
|
|
10581
|
+
results.push({
|
|
10582
|
+
skill,
|
|
10583
|
+
status: "installed",
|
|
10584
|
+
targetPath: destDir
|
|
10585
|
+
});
|
|
10586
|
+
continue;
|
|
10587
|
+
}
|
|
10588
|
+
if (await skillMatches(srcDir, destDir)) {
|
|
10589
|
+
results.push({
|
|
10590
|
+
skill,
|
|
10591
|
+
status: "already-current",
|
|
10592
|
+
targetPath: destDir
|
|
10593
|
+
});
|
|
10594
|
+
continue;
|
|
10595
|
+
}
|
|
10596
|
+
if (force) {
|
|
10597
|
+
await rm4(destDir, { recursive: true, force: true });
|
|
10598
|
+
await copyDir(srcDir, destDir);
|
|
10599
|
+
results.push({
|
|
10600
|
+
skill,
|
|
10601
|
+
status: "overwritten",
|
|
10602
|
+
targetPath: destDir
|
|
10603
|
+
});
|
|
10604
|
+
} else {
|
|
10605
|
+
results.push({
|
|
10606
|
+
skill,
|
|
10607
|
+
status: "differs-preserved",
|
|
10608
|
+
targetPath: destDir
|
|
10609
|
+
});
|
|
10610
|
+
}
|
|
10611
|
+
}
|
|
10612
|
+
return results;
|
|
10613
|
+
}
|
|
10614
|
+
async function uninstallSkills(options) {
|
|
10615
|
+
const targetRoot = options.targetDir ?? defaultSkillTargetDir(options.target);
|
|
10616
|
+
if (!await fileExists(targetRoot)) return [];
|
|
10617
|
+
const removed = [];
|
|
10618
|
+
for (const skill of REQUIRED_SKILLS) {
|
|
10619
|
+
const destDir = join3(targetRoot, skill);
|
|
10620
|
+
if (!await fileExists(destDir)) continue;
|
|
10621
|
+
const skillMd = join3(destDir, "SKILL.md");
|
|
10622
|
+
if (!await fileExists(skillMd)) continue;
|
|
10623
|
+
const content = await readFile16(skillMd, "utf-8").catch(() => "");
|
|
10624
|
+
const match = content.match(/^name:\s*(\S+)\s*$/m);
|
|
10625
|
+
if (!match || match[1] !== skill) continue;
|
|
10626
|
+
await rm4(destDir, { recursive: true, force: true });
|
|
10627
|
+
removed.push(destDir);
|
|
10628
|
+
}
|
|
10629
|
+
return removed;
|
|
10630
|
+
}
|
|
10631
|
+
function formatInstallReport(results, target) {
|
|
10632
|
+
const lines = [];
|
|
10633
|
+
lines.push(`Skill install (${target}):`);
|
|
10634
|
+
for (const r of results) {
|
|
10635
|
+
const marker = r.status === "installed" ? "+" : r.status === "overwritten" ? "!" : r.status === "differs-preserved" ? "?" : "=";
|
|
10636
|
+
lines.push(` ${marker} ${r.skill} (${r.status})`);
|
|
10637
|
+
}
|
|
10638
|
+
const diffs = results.filter((r) => r.status === "differs-preserved");
|
|
10639
|
+
if (diffs.length > 0) {
|
|
10640
|
+
lines.push("");
|
|
10641
|
+
lines.push(
|
|
10642
|
+
` Note: ${diffs.length} skill(s) already exist with different content and were preserved.`
|
|
10643
|
+
);
|
|
10644
|
+
lines.push(
|
|
10645
|
+
" Run with --force-skills to overwrite with the vendored version."
|
|
10646
|
+
);
|
|
10647
|
+
}
|
|
10648
|
+
return lines.join("\n");
|
|
10649
|
+
}
|
|
10650
|
+
|
|
10310
10651
|
// src/commands/install-plugin.ts
|
|
10311
10652
|
async function promptForInstallPath(question, recommendedPath) {
|
|
10312
10653
|
while (true) {
|
|
@@ -10389,10 +10730,261 @@ async function installPluginCommand(options) {
|
|
|
10389
10730
|
if (currentMarketplace) {
|
|
10390
10731
|
console.log(` marketplace: ${currentMarketplace.manifestPath}`);
|
|
10391
10732
|
}
|
|
10733
|
+
if (!options.skipSkills) {
|
|
10734
|
+
try {
|
|
10735
|
+
const skillResults = await installSkills({
|
|
10736
|
+
target: "claude",
|
|
10737
|
+
force: options.forceSkills
|
|
10738
|
+
});
|
|
10739
|
+
console.log("");
|
|
10740
|
+
console.log(formatInstallReport(skillResults, "claude"));
|
|
10741
|
+
} catch (error) {
|
|
10742
|
+
console.warn(
|
|
10743
|
+
`Warning: skill install failed \u2014 ${error instanceof Error ? error.message : String(error)}`
|
|
10744
|
+
);
|
|
10745
|
+
}
|
|
10746
|
+
}
|
|
10392
10747
|
console.log("\nThe plugin is now available in Claude Code.");
|
|
10393
|
-
console.log("
|
|
10394
|
-
console.log(" Background: syntaur-protocol (auto-invoked)");
|
|
10395
|
-
console.log(" Hook: write boundary enforcement (PreToolUse)");
|
|
10748
|
+
console.log(" Slash commands: /grab-assignment, /plan-assignment, /complete-assignment, /create-assignment, /create-project");
|
|
10749
|
+
console.log(" Background: syntaur-protocol skill (auto-invoked)");
|
|
10750
|
+
console.log(" Hook: write boundary enforcement (PreToolUse) + SessionStart/End");
|
|
10751
|
+
}
|
|
10752
|
+
|
|
10753
|
+
// src/commands/install-statusline.ts
|
|
10754
|
+
init_paths();
|
|
10755
|
+
init_fs();
|
|
10756
|
+
import { readFile as readFile17, writeFile as writeFile7, copyFile as copyFile2, rm as rm5, stat as stat3, symlink as symlink2, unlink as unlink6, lstat as lstat2 } from "fs/promises";
|
|
10757
|
+
import { resolve as resolve26, dirname as dirname8 } from "path";
|
|
10758
|
+
import { homedir as homedir4 } from "os";
|
|
10759
|
+
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
10760
|
+
function getPackageStatuslineSource() {
|
|
10761
|
+
const here = dirname8(fileURLToPath5(import.meta.url));
|
|
10762
|
+
return resolve26(here, "..", "statusline", "statusline.sh");
|
|
10763
|
+
}
|
|
10764
|
+
async function readSettingsJson(settingsPath) {
|
|
10765
|
+
if (!await fileExists(settingsPath)) return {};
|
|
10766
|
+
const raw = await readFile17(settingsPath, "utf-8");
|
|
10767
|
+
if (raw.trim() === "") return {};
|
|
10768
|
+
try {
|
|
10769
|
+
const parsed = JSON.parse(raw);
|
|
10770
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
10771
|
+
} catch (error) {
|
|
10772
|
+
throw new Error(
|
|
10773
|
+
`Unable to parse ${settingsPath}: ${error.message}. Fix the JSON and re-run.`
|
|
10774
|
+
);
|
|
10775
|
+
}
|
|
10776
|
+
}
|
|
10777
|
+
async function writeSettingsJson(settingsPath, data) {
|
|
10778
|
+
await ensureDir(dirname8(settingsPath));
|
|
10779
|
+
await writeFile7(settingsPath, JSON.stringify(data, null, 2) + "\n", "utf-8");
|
|
10780
|
+
}
|
|
10781
|
+
async function resolveMode(mode, existingCommand, ourCommand) {
|
|
10782
|
+
if (mode !== "ask") return mode;
|
|
10783
|
+
if (!existingCommand || existingCommand === ourCommand) return "replace";
|
|
10784
|
+
if (!isInteractiveTerminal()) {
|
|
10785
|
+
return "wrap";
|
|
10786
|
+
}
|
|
10787
|
+
console.log(
|
|
10788
|
+
`A Claude Code statusLine is already configured:
|
|
10789
|
+
${existingCommand}
|
|
10790
|
+
`
|
|
10791
|
+
);
|
|
10792
|
+
const wantWrap = await confirmPrompt(
|
|
10793
|
+
"Compose (wrap) your existing statusline with syntaur segments? [Y to wrap / n to replace]",
|
|
10794
|
+
true
|
|
10795
|
+
);
|
|
10796
|
+
if (wantWrap) return "wrap";
|
|
10797
|
+
const confirmReplace = await confirmPrompt(
|
|
10798
|
+
"Replace your existing statusline with syntaur only?",
|
|
10799
|
+
false
|
|
10800
|
+
);
|
|
10801
|
+
return confirmReplace ? "replace" : "skip";
|
|
10802
|
+
}
|
|
10803
|
+
function extractExistingCommand(settings) {
|
|
10804
|
+
const sl = settings.statusLine;
|
|
10805
|
+
if (!sl || typeof sl !== "object") return void 0;
|
|
10806
|
+
const obj = sl;
|
|
10807
|
+
return {
|
|
10808
|
+
type: typeof obj.type === "string" ? obj.type : void 0,
|
|
10809
|
+
command: typeof obj.command === "string" ? obj.command : void 0
|
|
10810
|
+
};
|
|
10811
|
+
}
|
|
10812
|
+
async function backupSettings(settingsSnapshot, backupPath) {
|
|
10813
|
+
await ensureDir(dirname8(backupPath));
|
|
10814
|
+
await writeFile7(
|
|
10815
|
+
backupPath,
|
|
10816
|
+
JSON.stringify(
|
|
10817
|
+
{
|
|
10818
|
+
version: 1,
|
|
10819
|
+
takenAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10820
|
+
settingsPath: settingsSnapshot.settingsPath,
|
|
10821
|
+
previousStatusLine: settingsSnapshot.existingStatusLine ?? null
|
|
10822
|
+
},
|
|
10823
|
+
null,
|
|
10824
|
+
2
|
|
10825
|
+
) + "\n",
|
|
10826
|
+
"utf-8"
|
|
10827
|
+
);
|
|
10828
|
+
}
|
|
10829
|
+
async function installScript(sourceScript, destScript, link) {
|
|
10830
|
+
await ensureDir(dirname8(destScript));
|
|
10831
|
+
try {
|
|
10832
|
+
const s = await lstat2(destScript);
|
|
10833
|
+
if (s.isSymbolicLink() || s.isFile()) {
|
|
10834
|
+
await unlink6(destScript);
|
|
10835
|
+
}
|
|
10836
|
+
} catch {
|
|
10837
|
+
}
|
|
10838
|
+
if (link) {
|
|
10839
|
+
await symlink2(sourceScript, destScript);
|
|
10840
|
+
} else {
|
|
10841
|
+
await copyFile2(sourceScript, destScript);
|
|
10842
|
+
}
|
|
10843
|
+
}
|
|
10844
|
+
async function installStatuslineCommand(options = {}) {
|
|
10845
|
+
const mode = options.mode ?? "ask";
|
|
10846
|
+
const settingsPath = options.settingsPath ?? resolve26(homedir4(), ".claude", "settings.json");
|
|
10847
|
+
const installRoot = options.installRoot ?? syntaurRoot();
|
|
10848
|
+
const sourceScript = options.sourceScript ?? getPackageStatuslineSource();
|
|
10849
|
+
const destScript = resolve26(installRoot, "statusline.sh");
|
|
10850
|
+
const confPath = resolve26(installRoot, "statusline.conf");
|
|
10851
|
+
const backupPath = resolve26(installRoot, "statusline.backup.json");
|
|
10852
|
+
if (!await fileExists(sourceScript)) {
|
|
10853
|
+
throw new Error(
|
|
10854
|
+
`Statusline source script not found at ${sourceScript}. Try re-installing syntaur (npm install -g syntaur) or pass --source-script explicitly.`
|
|
10855
|
+
);
|
|
10856
|
+
}
|
|
10857
|
+
await installScript(sourceScript, destScript, Boolean(options.link));
|
|
10858
|
+
const settings = await readSettingsJson(settingsPath);
|
|
10859
|
+
const existingStatusLine = extractExistingCommand(settings);
|
|
10860
|
+
const existingCommand = existingStatusLine?.command;
|
|
10861
|
+
const ourCommand = `bash ${destScript}`;
|
|
10862
|
+
const resolvedMode = await resolveMode(mode, existingCommand, ourCommand);
|
|
10863
|
+
if (resolvedMode === "skip") {
|
|
10864
|
+
console.log("Installed statusline script only:");
|
|
10865
|
+
console.log(` script: ${destScript}`);
|
|
10866
|
+
console.log(` source: ${sourceScript}`);
|
|
10867
|
+
console.log(
|
|
10868
|
+
" (settings.json left unchanged \u2014 run with --mode=replace or --mode=wrap to wire it up)"
|
|
10869
|
+
);
|
|
10870
|
+
return;
|
|
10871
|
+
}
|
|
10872
|
+
await backupSettings(
|
|
10873
|
+
{
|
|
10874
|
+
settingsPath,
|
|
10875
|
+
existingStatusLine,
|
|
10876
|
+
existingCommand
|
|
10877
|
+
},
|
|
10878
|
+
backupPath
|
|
10879
|
+
);
|
|
10880
|
+
let wrapTarget = "";
|
|
10881
|
+
if (resolvedMode === "wrap" && existingCommand && existingCommand !== ourCommand) {
|
|
10882
|
+
const parsed = parseWrapCommand(existingCommand);
|
|
10883
|
+
if (parsed) {
|
|
10884
|
+
wrapTarget = parsed;
|
|
10885
|
+
} else {
|
|
10886
|
+
const wrapperPath = resolve26(installRoot, "statusline-wrapped.sh");
|
|
10887
|
+
const wrapperBody = `#!/usr/bin/env bash
|
|
10888
|
+
# Auto-generated by syntaur install-statusline.
|
|
10889
|
+
# Executes the previously configured statusLine command.
|
|
10890
|
+
exec ${existingCommand}
|
|
10891
|
+
`;
|
|
10892
|
+
await writeFile7(wrapperPath, wrapperBody, "utf-8");
|
|
10893
|
+
await chmodExec(wrapperPath);
|
|
10894
|
+
wrapTarget = wrapperPath;
|
|
10895
|
+
}
|
|
10896
|
+
}
|
|
10897
|
+
await ensureDir(dirname8(confPath));
|
|
10898
|
+
await writeFile7(
|
|
10899
|
+
confPath,
|
|
10900
|
+
wrapTarget ? `# Wrap target \u2014 the command below is invoked with the same stdin; its
|
|
10901
|
+
# stdout becomes the leading segment of the statusline. Remove this
|
|
10902
|
+
# line or comment it out to disable wrapping.
|
|
10903
|
+
${wrapTarget}
|
|
10904
|
+
` : `# syntaur statusline config. Add a single path on a non-comment line to
|
|
10905
|
+
# wrap another statusline script with syntaur segments appended.
|
|
10906
|
+
`,
|
|
10907
|
+
"utf-8"
|
|
10908
|
+
);
|
|
10909
|
+
settings.statusLine = {
|
|
10910
|
+
type: "command",
|
|
10911
|
+
command: ourCommand
|
|
10912
|
+
};
|
|
10913
|
+
await writeSettingsJson(settingsPath, settings);
|
|
10914
|
+
console.log("Installed syntaur statusline:");
|
|
10915
|
+
console.log(` script: ${destScript}`);
|
|
10916
|
+
console.log(` source: ${sourceScript}`);
|
|
10917
|
+
console.log(` mode: ${resolvedMode}`);
|
|
10918
|
+
console.log(` settings.json:${settingsPath}`);
|
|
10919
|
+
console.log(` backup: ${backupPath}`);
|
|
10920
|
+
if (wrapTarget) {
|
|
10921
|
+
console.log(` wrap target: ${wrapTarget}`);
|
|
10922
|
+
console.log(` (edit ${confPath} to change or disable wrapping)`);
|
|
10923
|
+
}
|
|
10924
|
+
}
|
|
10925
|
+
function parseWrapCommand(command) {
|
|
10926
|
+
const trimmed = command.trim();
|
|
10927
|
+
const bashMatch = trimmed.match(/^bash\s+(\S+)$/);
|
|
10928
|
+
if (bashMatch) return bashMatch[1];
|
|
10929
|
+
if (/^\S+\.(sh|bash)$/.test(trimmed)) return trimmed;
|
|
10930
|
+
return null;
|
|
10931
|
+
}
|
|
10932
|
+
async function chmodExec(path) {
|
|
10933
|
+
const fs = await import("fs/promises");
|
|
10934
|
+
try {
|
|
10935
|
+
const s = await stat3(path);
|
|
10936
|
+
await fs.chmod(path, s.mode | 73);
|
|
10937
|
+
} catch {
|
|
10938
|
+
}
|
|
10939
|
+
}
|
|
10940
|
+
async function uninstallStatuslineCommand(options = {}) {
|
|
10941
|
+
const settingsPath = options.settingsPath ?? resolve26(homedir4(), ".claude", "settings.json");
|
|
10942
|
+
const installRoot = options.installRoot ?? syntaurRoot();
|
|
10943
|
+
const destScript = resolve26(installRoot, "statusline.sh");
|
|
10944
|
+
const confPath = resolve26(installRoot, "statusline.conf");
|
|
10945
|
+
const backupPath = resolve26(installRoot, "statusline.backup.json");
|
|
10946
|
+
const wrapperPath = resolve26(installRoot, "statusline-wrapped.sh");
|
|
10947
|
+
const settings = await readSettingsJson(settingsPath);
|
|
10948
|
+
const existing = extractExistingCommand(settings);
|
|
10949
|
+
const ourCommand = `bash ${destScript}`;
|
|
10950
|
+
let restored = null;
|
|
10951
|
+
if (await fileExists(backupPath)) {
|
|
10952
|
+
try {
|
|
10953
|
+
const raw = await readFile17(backupPath, "utf-8");
|
|
10954
|
+
const parsed = JSON.parse(raw);
|
|
10955
|
+
const prev = parsed?.previousStatusLine;
|
|
10956
|
+
if (prev && typeof prev === "object" && typeof prev.command === "string") {
|
|
10957
|
+
restored = { command: prev.command };
|
|
10958
|
+
}
|
|
10959
|
+
} catch {
|
|
10960
|
+
}
|
|
10961
|
+
}
|
|
10962
|
+
if (existing?.command === ourCommand) {
|
|
10963
|
+
if (restored) {
|
|
10964
|
+
settings.statusLine = {
|
|
10965
|
+
type: "command",
|
|
10966
|
+
command: restored.command
|
|
10967
|
+
};
|
|
10968
|
+
} else {
|
|
10969
|
+
delete settings.statusLine;
|
|
10970
|
+
}
|
|
10971
|
+
await writeSettingsJson(settingsPath, settings);
|
|
10972
|
+
}
|
|
10973
|
+
if (!options.keepScript) {
|
|
10974
|
+
for (const path of [destScript, confPath, backupPath, wrapperPath]) {
|
|
10975
|
+
try {
|
|
10976
|
+
await rm5(path, { force: true });
|
|
10977
|
+
} catch {
|
|
10978
|
+
}
|
|
10979
|
+
}
|
|
10980
|
+
}
|
|
10981
|
+
console.log("Uninstalled syntaur statusline.");
|
|
10982
|
+
if (restored) {
|
|
10983
|
+
console.log(` Restored previous command: ${restored.command}`);
|
|
10984
|
+
} else {
|
|
10985
|
+
console.log(" Removed statusLine entry from settings.json.");
|
|
10986
|
+
}
|
|
10987
|
+
console.log(` settings.json: ${settingsPath}`);
|
|
10396
10988
|
}
|
|
10397
10989
|
|
|
10398
10990
|
// src/commands/install-codex-plugin.ts
|
|
@@ -10474,14 +11066,61 @@ async function installCodexPluginCommand(options) {
|
|
|
10474
11066
|
console.log(` source: ${result.sourceDir}`);
|
|
10475
11067
|
console.log(` mode: ${result.mode}`);
|
|
10476
11068
|
console.log(` marketplace: ${marketplace.marketplacePath}`);
|
|
11069
|
+
if (!options.skipSkills) {
|
|
11070
|
+
try {
|
|
11071
|
+
const skillResults = await installSkills({
|
|
11072
|
+
target: "codex",
|
|
11073
|
+
force: options.forceSkills
|
|
11074
|
+
});
|
|
11075
|
+
console.log("");
|
|
11076
|
+
console.log(formatInstallReport(skillResults, "codex"));
|
|
11077
|
+
} catch (error) {
|
|
11078
|
+
console.warn(
|
|
11079
|
+
`Warning: skill install failed \u2014 ${error instanceof Error ? error.message : String(error)}`
|
|
11080
|
+
);
|
|
11081
|
+
}
|
|
11082
|
+
}
|
|
10477
11083
|
console.log("\nThe plugin is now available to Codex.");
|
|
10478
11084
|
console.log(
|
|
10479
|
-
"
|
|
11085
|
+
" Protocol skills: syntaur-protocol, create-project, create-assignment, grab-assignment, plan-assignment, complete-assignment"
|
|
10480
11086
|
);
|
|
10481
|
-
console.log("
|
|
11087
|
+
console.log(" Codex-specific: track-session skill (rollout path aware)");
|
|
10482
11088
|
console.log(" Hooks: write boundary enforcement, session cleanup");
|
|
10483
11089
|
}
|
|
10484
11090
|
|
|
11091
|
+
// src/commands/uninstall-skills.ts
|
|
11092
|
+
async function uninstallSkillsCommand(options) {
|
|
11093
|
+
const runClaude = Boolean(options.claude || options.all);
|
|
11094
|
+
const runCodex = Boolean(options.codex || options.all);
|
|
11095
|
+
if (!runClaude && !runCodex) {
|
|
11096
|
+
throw new Error(
|
|
11097
|
+
"Specify --claude, --codex, or --all (use one or more)."
|
|
11098
|
+
);
|
|
11099
|
+
}
|
|
11100
|
+
let totalRemoved = 0;
|
|
11101
|
+
if (runClaude) {
|
|
11102
|
+
const removed = await uninstallSkills({ target: "claude" });
|
|
11103
|
+
totalRemoved += removed.length;
|
|
11104
|
+
console.log(
|
|
11105
|
+
`Removed ${removed.length} Syntaur protocol skill(s) from ~/.claude/skills:`
|
|
11106
|
+
);
|
|
11107
|
+
for (const p of removed) console.log(` - ${p}`);
|
|
11108
|
+
}
|
|
11109
|
+
if (runCodex) {
|
|
11110
|
+
const removed = await uninstallSkills({ target: "codex" });
|
|
11111
|
+
totalRemoved += removed.length;
|
|
11112
|
+
console.log(
|
|
11113
|
+
`Removed ${removed.length} Syntaur protocol skill(s) from ~/.codex/skills:`
|
|
11114
|
+
);
|
|
11115
|
+
for (const p of removed) console.log(` - ${p}`);
|
|
11116
|
+
}
|
|
11117
|
+
if (totalRemoved === 0) {
|
|
11118
|
+
console.log(
|
|
11119
|
+
"No Syntaur protocol skills found to remove. (User-authored skills with matching directory names are preserved.)"
|
|
11120
|
+
);
|
|
11121
|
+
}
|
|
11122
|
+
}
|
|
11123
|
+
|
|
10485
11124
|
// src/commands/setup.ts
|
|
10486
11125
|
import { execSync } from "child_process";
|
|
10487
11126
|
init_config2();
|
|
@@ -10586,7 +11225,7 @@ async function setupCommand(options) {
|
|
|
10586
11225
|
}
|
|
10587
11226
|
|
|
10588
11227
|
// src/commands/uninstall.ts
|
|
10589
|
-
import { resolve as
|
|
11228
|
+
import { resolve as resolve27 } from "path";
|
|
10590
11229
|
init_paths();
|
|
10591
11230
|
function expandTargets(options) {
|
|
10592
11231
|
if (options.all) {
|
|
@@ -10666,7 +11305,7 @@ async function uninstallCommand(options) {
|
|
|
10666
11305
|
const configuredProjectDir = await getConfiguredProjectDir();
|
|
10667
11306
|
await removeSyntaurData();
|
|
10668
11307
|
console.log(`Removed ${syntaurRoot()}`);
|
|
10669
|
-
if (configuredProjectDir &&
|
|
11308
|
+
if (configuredProjectDir && resolve27(configuredProjectDir) !== resolve27(syntaurRoot(), "projects")) {
|
|
10670
11309
|
console.warn(
|
|
10671
11310
|
`Warning: config.md pointed to an external project directory (${configuredProjectDir}). That directory was not removed automatically.`
|
|
10672
11311
|
);
|
|
@@ -10681,7 +11320,7 @@ async function uninstallCommand(options) {
|
|
|
10681
11320
|
init_paths();
|
|
10682
11321
|
init_fs();
|
|
10683
11322
|
init_config2();
|
|
10684
|
-
import { resolve as
|
|
11323
|
+
import { resolve as resolve28 } from "path";
|
|
10685
11324
|
var SUPPORTED_FRAMEWORKS = ["cursor", "codex", "opencode"];
|
|
10686
11325
|
async function setupAdapterCommand(framework, options) {
|
|
10687
11326
|
if (!SUPPORTED_FRAMEWORKS.includes(framework)) {
|
|
@@ -10707,19 +11346,19 @@ async function setupAdapterCommand(framework, options) {
|
|
|
10707
11346
|
}
|
|
10708
11347
|
const config = await readConfig();
|
|
10709
11348
|
const baseDir = options.dir ? expandHome(options.dir) : config.defaultProjectDir;
|
|
10710
|
-
const projectDir =
|
|
10711
|
-
const assignmentDir =
|
|
11349
|
+
const projectDir = resolve28(baseDir, options.project);
|
|
11350
|
+
const assignmentDir = resolve28(
|
|
10712
11351
|
projectDir,
|
|
10713
11352
|
"assignments",
|
|
10714
11353
|
options.assignment
|
|
10715
11354
|
);
|
|
10716
|
-
const projectMdPath =
|
|
11355
|
+
const projectMdPath = resolve28(projectDir, "project.md");
|
|
10717
11356
|
if (!await fileExists(projectDir) || !await fileExists(projectMdPath)) {
|
|
10718
11357
|
throw new Error(
|
|
10719
11358
|
`Project "${options.project}" not found at ${projectDir}.`
|
|
10720
11359
|
);
|
|
10721
11360
|
}
|
|
10722
|
-
const assignmentMdPath =
|
|
11361
|
+
const assignmentMdPath = resolve28(assignmentDir, "assignment.md");
|
|
10723
11362
|
if (!await fileExists(assignmentDir) || !await fileExists(assignmentMdPath)) {
|
|
10724
11363
|
throw new Error(
|
|
10725
11364
|
`Assignment "${options.assignment}" not found at ${assignmentDir}.`
|
|
@@ -10747,15 +11386,15 @@ async function setupAdapterCommand(framework, options) {
|
|
|
10747
11386
|
}
|
|
10748
11387
|
}
|
|
10749
11388
|
if (framework === "cursor") {
|
|
10750
|
-
const protocolPath =
|
|
10751
|
-
const assignmentPath =
|
|
11389
|
+
const protocolPath = resolve28(cwd, ".cursor", "rules", "syntaur-protocol.mdc");
|
|
11390
|
+
const assignmentPath = resolve28(cwd, ".cursor", "rules", "syntaur-assignment.mdc");
|
|
10752
11391
|
await writeAdapterFile(protocolPath, renderCursorProtocol());
|
|
10753
11392
|
await writeAdapterFile(assignmentPath, renderCursorAssignment(rendererParams));
|
|
10754
11393
|
} else if (framework === "codex" || framework === "opencode") {
|
|
10755
|
-
const agentsPath =
|
|
11394
|
+
const agentsPath = resolve28(cwd, "AGENTS.md");
|
|
10756
11395
|
await writeAdapterFile(agentsPath, renderCodexAgents(rendererParams));
|
|
10757
11396
|
if (framework === "opencode") {
|
|
10758
|
-
const configPath =
|
|
11397
|
+
const configPath = resolve28(cwd, "opencode.json");
|
|
10759
11398
|
await writeAdapterFile(configPath, renderOpenCodeConfig({ projectDir }));
|
|
10760
11399
|
}
|
|
10761
11400
|
}
|
|
@@ -10780,16 +11419,20 @@ async function setupAdapterCommand(framework, options) {
|
|
|
10780
11419
|
init_paths();
|
|
10781
11420
|
init_fs();
|
|
10782
11421
|
init_config2();
|
|
10783
|
-
import { resolve as
|
|
10784
|
-
import { randomUUID as randomUUID2 } from "crypto";
|
|
11422
|
+
import { resolve as resolve29 } from "path";
|
|
10785
11423
|
async function trackSessionCommand(options) {
|
|
10786
11424
|
if (!options.agent) {
|
|
10787
11425
|
throw new Error("--agent <name> is required.");
|
|
10788
11426
|
}
|
|
11427
|
+
if (!options.sessionId) {
|
|
11428
|
+
throw new Error(
|
|
11429
|
+
"--session-id <id> is required. Pass the real agent-generated session id \u2014 do not synthesize one."
|
|
11430
|
+
);
|
|
11431
|
+
}
|
|
10789
11432
|
if (options.project) {
|
|
10790
11433
|
const config = await readConfig();
|
|
10791
11434
|
const baseDir = options.dir ? expandHome(options.dir) : config.defaultProjectDir;
|
|
10792
|
-
const projectDir =
|
|
11435
|
+
const projectDir = resolve29(baseDir, options.project);
|
|
10793
11436
|
if (!await fileExists(projectDir)) {
|
|
10794
11437
|
throw new Error(
|
|
10795
11438
|
`Project "${options.project}" not found at ${projectDir}.`
|
|
@@ -10797,7 +11440,7 @@ async function trackSessionCommand(options) {
|
|
|
10797
11440
|
}
|
|
10798
11441
|
}
|
|
10799
11442
|
initSessionDb();
|
|
10800
|
-
const sessionId = options
|
|
11443
|
+
const { sessionId } = options;
|
|
10801
11444
|
await appendSession("", {
|
|
10802
11445
|
projectSlug: options.project || null,
|
|
10803
11446
|
assignmentSlug: options.assignment || null,
|
|
@@ -10806,10 +11449,13 @@ async function trackSessionCommand(options) {
|
|
|
10806
11449
|
started: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10807
11450
|
status: "active",
|
|
10808
11451
|
path: options.path || process.cwd(),
|
|
10809
|
-
description: options.description || null
|
|
11452
|
+
description: options.description || null,
|
|
11453
|
+
transcriptPath: options.transcriptPath ?? null
|
|
10810
11454
|
});
|
|
10811
11455
|
if (options.project && options.assignment) {
|
|
10812
|
-
console.log(
|
|
11456
|
+
console.log(
|
|
11457
|
+
`Registered agent session ${sessionId} for ${options.assignment} in ${options.project}.`
|
|
11458
|
+
);
|
|
10813
11459
|
} else {
|
|
10814
11460
|
console.log(`Registered standalone agent session ${sessionId}.`);
|
|
10815
11461
|
}
|
|
@@ -10841,7 +11487,7 @@ async function browseCommand(options) {
|
|
|
10841
11487
|
}
|
|
10842
11488
|
|
|
10843
11489
|
// src/commands/create-playbook.ts
|
|
10844
|
-
import { resolve as
|
|
11490
|
+
import { resolve as resolve31 } from "path";
|
|
10845
11491
|
init_timestamp();
|
|
10846
11492
|
init_paths();
|
|
10847
11493
|
init_fs();
|
|
@@ -10857,7 +11503,7 @@ async function createPlaybookCommand(name, options) {
|
|
|
10857
11503
|
}
|
|
10858
11504
|
const dir = playbooksDir();
|
|
10859
11505
|
await ensureDir(dir);
|
|
10860
|
-
const filePath =
|
|
11506
|
+
const filePath = resolve31(dir, `${slug}.md`);
|
|
10861
11507
|
if (await fileExists(filePath)) {
|
|
10862
11508
|
throw new Error(
|
|
10863
11509
|
`Playbook "${slug}" already exists at ${filePath}
|
|
@@ -10878,15 +11524,15 @@ Use --slug to specify a different slug.`
|
|
|
10878
11524
|
init_paths();
|
|
10879
11525
|
init_fs();
|
|
10880
11526
|
init_parser();
|
|
10881
|
-
import { readdir as
|
|
10882
|
-
import { resolve as
|
|
11527
|
+
import { readdir as readdir12, readFile as readFile18 } from "fs/promises";
|
|
11528
|
+
import { resolve as resolve32 } from "path";
|
|
10883
11529
|
async function listPlaybooksCommand() {
|
|
10884
11530
|
const dir = playbooksDir();
|
|
10885
11531
|
if (!await fileExists(dir)) {
|
|
10886
11532
|
console.log('No playbooks directory found. Run "syntaur init" first.');
|
|
10887
11533
|
return;
|
|
10888
11534
|
}
|
|
10889
|
-
const entries = await
|
|
11535
|
+
const entries = await readdir12(dir, { withFileTypes: true });
|
|
10890
11536
|
const mdFiles = entries.filter((e) => e.isFile() && e.name.endsWith(".md") && !e.name.startsWith("_") && e.name !== "manifest.md");
|
|
10891
11537
|
if (mdFiles.length === 0) {
|
|
10892
11538
|
console.log('No playbooks found. Create one with "syntaur create-playbook <name>".');
|
|
@@ -10897,8 +11543,8 @@ async function listPlaybooksCommand() {
|
|
|
10897
11543
|
console.log(`${"Slug".padEnd(30)} ${"Name".padEnd(30)} Description`);
|
|
10898
11544
|
console.log(`${"\u2500".repeat(30)} ${"\u2500".repeat(30)} ${"\u2500".repeat(40)}`);
|
|
10899
11545
|
for (const entry of mdFiles) {
|
|
10900
|
-
const filePath =
|
|
10901
|
-
const raw = await
|
|
11546
|
+
const filePath = resolve32(dir, entry.name);
|
|
11547
|
+
const raw = await readFile18(filePath, "utf-8");
|
|
10902
11548
|
const parsed = parsePlaybook(raw);
|
|
10903
11549
|
const slug = parsed.slug || entry.name.replace(/\.md$/, "");
|
|
10904
11550
|
const name = parsed.name || slug;
|
|
@@ -10912,8 +11558,8 @@ init_paths();
|
|
|
10912
11558
|
init_parser2();
|
|
10913
11559
|
init_fs();
|
|
10914
11560
|
import { Command } from "commander";
|
|
10915
|
-
import { readFile as
|
|
10916
|
-
import { resolve as
|
|
11561
|
+
import { readFile as readFile19 } from "fs/promises";
|
|
11562
|
+
import { resolve as resolve33 } from "path";
|
|
10917
11563
|
var WORKSPACE_REGEX2 = /^[a-z0-9_][a-z0-9-]*$/;
|
|
10918
11564
|
function resolveWorkspace(options) {
|
|
10919
11565
|
if (options.global) return "_global";
|
|
@@ -11194,10 +11840,10 @@ todoCommand.command("archive").description("Archive completed todos and their lo
|
|
|
11194
11840
|
(e) => e.itemIds.every((id) => completedIds.has(id))
|
|
11195
11841
|
);
|
|
11196
11842
|
const archFile = archivePath(todosPath, workspace, checklist.archiveInterval);
|
|
11197
|
-
await ensureDir(
|
|
11843
|
+
await ensureDir(resolve33(todosPath, "archive"));
|
|
11198
11844
|
let archContent = "";
|
|
11199
11845
|
if (await fileExists(archFile)) {
|
|
11200
|
-
archContent = await
|
|
11846
|
+
archContent = await readFile19(archFile, "utf-8");
|
|
11201
11847
|
archContent = archContent.trimEnd() + "\n\n";
|
|
11202
11848
|
} else {
|
|
11203
11849
|
archContent = `---
|
|
@@ -11385,20 +12031,20 @@ backupCommand.command("config").description("Show or update backup configuration
|
|
|
11385
12031
|
import { Command as Command3 } from "commander";
|
|
11386
12032
|
|
|
11387
12033
|
// src/utils/doctor/index.ts
|
|
11388
|
-
import { fileURLToPath as
|
|
11389
|
-
import { readFile as
|
|
11390
|
-
import { dirname as
|
|
12034
|
+
import { fileURLToPath as fileURLToPath7 } from "url";
|
|
12035
|
+
import { readFile as readFile23 } from "fs/promises";
|
|
12036
|
+
import { dirname as dirname10, join as join5 } from "path";
|
|
11391
12037
|
|
|
11392
12038
|
// src/utils/doctor/context.ts
|
|
11393
12039
|
init_config2();
|
|
11394
12040
|
init_paths();
|
|
11395
12041
|
init_fs();
|
|
11396
12042
|
import Database2 from "better-sqlite3";
|
|
11397
|
-
import { resolve as
|
|
12043
|
+
import { resolve as resolve34 } from "path";
|
|
11398
12044
|
async function buildCheckContext(cwd = process.cwd()) {
|
|
11399
12045
|
const config = await readConfig();
|
|
11400
12046
|
const root = syntaurRoot();
|
|
11401
|
-
const dbPath =
|
|
12047
|
+
const dbPath = resolve34(root, "syntaur.db");
|
|
11402
12048
|
let db2 = null;
|
|
11403
12049
|
let dbError = null;
|
|
11404
12050
|
if (await fileExists(dbPath)) {
|
|
@@ -11432,10 +12078,10 @@ function closeCheckContext(ctx) {
|
|
|
11432
12078
|
// src/utils/doctor/checks/env.ts
|
|
11433
12079
|
init_fs();
|
|
11434
12080
|
init_paths();
|
|
11435
|
-
import { resolve as
|
|
11436
|
-
import { readFile as
|
|
11437
|
-
import { fileURLToPath as
|
|
11438
|
-
import { dirname as
|
|
12081
|
+
import { resolve as resolve35, isAbsolute as isAbsolute3 } from "path";
|
|
12082
|
+
import { readFile as readFile20, stat as stat4 } from "fs/promises";
|
|
12083
|
+
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
12084
|
+
import { dirname as dirname9, join as join4 } from "path";
|
|
11439
12085
|
var CATEGORY = "env";
|
|
11440
12086
|
var syntaurRootExists = {
|
|
11441
12087
|
id: "env.syntaur-root-exists",
|
|
@@ -11443,7 +12089,7 @@ var syntaurRootExists = {
|
|
|
11443
12089
|
title: "~/.syntaur/ directory exists",
|
|
11444
12090
|
async run(ctx) {
|
|
11445
12091
|
try {
|
|
11446
|
-
const s = await
|
|
12092
|
+
const s = await stat4(ctx.syntaurRoot);
|
|
11447
12093
|
if (!s.isDirectory()) {
|
|
11448
12094
|
return err(this, `${ctx.syntaurRoot} exists but is not a directory`, [
|
|
11449
12095
|
ctx.syntaurRoot
|
|
@@ -11473,7 +12119,7 @@ var configValid = {
|
|
|
11473
12119
|
category: CATEGORY,
|
|
11474
12120
|
title: "~/.syntaur/config.md is valid",
|
|
11475
12121
|
async run(ctx) {
|
|
11476
|
-
const configPath =
|
|
12122
|
+
const configPath = resolve35(ctx.syntaurRoot, "config.md");
|
|
11477
12123
|
if (!await fileExists(configPath)) {
|
|
11478
12124
|
return {
|
|
11479
12125
|
id: this.id,
|
|
@@ -11490,7 +12136,7 @@ var configValid = {
|
|
|
11490
12136
|
autoFixable: false
|
|
11491
12137
|
};
|
|
11492
12138
|
}
|
|
11493
|
-
const content = await
|
|
12139
|
+
const content = await readFile20(configPath, "utf-8");
|
|
11494
12140
|
const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
11495
12141
|
if (!fmMatch || fmMatch[1].trim() === "") {
|
|
11496
12142
|
return {
|
|
@@ -11765,15 +12411,15 @@ async function readLocalVersion() {
|
|
|
11765
12411
|
}
|
|
11766
12412
|
async function readLocalPkg() {
|
|
11767
12413
|
try {
|
|
11768
|
-
const here =
|
|
11769
|
-
let dir =
|
|
12414
|
+
const here = fileURLToPath6(import.meta.url);
|
|
12415
|
+
let dir = dirname9(here);
|
|
11770
12416
|
for (let i = 0; i < 6; i++) {
|
|
11771
|
-
const candidate =
|
|
12417
|
+
const candidate = join4(dir, "package.json");
|
|
11772
12418
|
try {
|
|
11773
|
-
const text = await
|
|
12419
|
+
const text = await readFile20(candidate, "utf-8");
|
|
11774
12420
|
return JSON.parse(text);
|
|
11775
12421
|
} catch {
|
|
11776
|
-
dir =
|
|
12422
|
+
dir = dirname9(dir);
|
|
11777
12423
|
}
|
|
11778
12424
|
}
|
|
11779
12425
|
return null;
|
|
@@ -11822,8 +12468,8 @@ function versionGte(a, b) {
|
|
|
11822
12468
|
|
|
11823
12469
|
// src/utils/doctor/checks/structure.ts
|
|
11824
12470
|
init_fs();
|
|
11825
|
-
import { resolve as
|
|
11826
|
-
import { readdir as
|
|
12471
|
+
import { resolve as resolve36 } from "path";
|
|
12472
|
+
import { readdir as readdir13, stat as stat5 } from "fs/promises";
|
|
11827
12473
|
var CATEGORY2 = "structure";
|
|
11828
12474
|
var KNOWN_TOP_LEVEL = /* @__PURE__ */ new Set([
|
|
11829
12475
|
"projects",
|
|
@@ -11842,7 +12488,7 @@ var projectsDir = {
|
|
|
11842
12488
|
category: CATEGORY2,
|
|
11843
12489
|
title: "projects/ directory exists",
|
|
11844
12490
|
async run(ctx) {
|
|
11845
|
-
const p =
|
|
12491
|
+
const p = resolve36(ctx.syntaurRoot, "projects");
|
|
11846
12492
|
if (!await fileExists(p)) {
|
|
11847
12493
|
return {
|
|
11848
12494
|
id: this.id,
|
|
@@ -11867,7 +12513,7 @@ var playbooksDir2 = {
|
|
|
11867
12513
|
category: CATEGORY2,
|
|
11868
12514
|
title: "playbooks/ directory exists",
|
|
11869
12515
|
async run(ctx) {
|
|
11870
|
-
const p =
|
|
12516
|
+
const p = resolve36(ctx.syntaurRoot, "playbooks");
|
|
11871
12517
|
if (!await fileExists(p)) {
|
|
11872
12518
|
return {
|
|
11873
12519
|
id: this.id,
|
|
@@ -11892,7 +12538,7 @@ var todosDirValid = {
|
|
|
11892
12538
|
category: CATEGORY2,
|
|
11893
12539
|
title: "todos/ directory is readable (if present)",
|
|
11894
12540
|
async run(ctx) {
|
|
11895
|
-
const p =
|
|
12541
|
+
const p = resolve36(ctx.syntaurRoot, "todos");
|
|
11896
12542
|
if (!await fileExists(p)) {
|
|
11897
12543
|
return {
|
|
11898
12544
|
id: this.id,
|
|
@@ -11903,7 +12549,7 @@ var todosDirValid = {
|
|
|
11903
12549
|
autoFixable: false
|
|
11904
12550
|
};
|
|
11905
12551
|
}
|
|
11906
|
-
const s = await
|
|
12552
|
+
const s = await stat5(p);
|
|
11907
12553
|
if (!s.isDirectory()) {
|
|
11908
12554
|
return {
|
|
11909
12555
|
id: this.id,
|
|
@@ -11923,7 +12569,7 @@ var serversDirValid = {
|
|
|
11923
12569
|
category: CATEGORY2,
|
|
11924
12570
|
title: "servers/ directory is readable (if present)",
|
|
11925
12571
|
async run(ctx) {
|
|
11926
|
-
const p =
|
|
12572
|
+
const p = resolve36(ctx.syntaurRoot, "servers");
|
|
11927
12573
|
if (!await fileExists(p)) {
|
|
11928
12574
|
return {
|
|
11929
12575
|
id: this.id,
|
|
@@ -11934,7 +12580,7 @@ var serversDirValid = {
|
|
|
11934
12580
|
autoFixable: false
|
|
11935
12581
|
};
|
|
11936
12582
|
}
|
|
11937
|
-
const s = await
|
|
12583
|
+
const s = await stat5(p);
|
|
11938
12584
|
if (!s.isDirectory()) {
|
|
11939
12585
|
return {
|
|
11940
12586
|
id: this.id,
|
|
@@ -11954,7 +12600,7 @@ var knownFilesRecognized = {
|
|
|
11954
12600
|
category: CATEGORY2,
|
|
11955
12601
|
title: "No unexpected top-level entries under ~/.syntaur/",
|
|
11956
12602
|
async run(ctx) {
|
|
11957
|
-
const entries = await
|
|
12603
|
+
const entries = await readdir13(ctx.syntaurRoot, { withFileTypes: true });
|
|
11958
12604
|
const unexpected = [];
|
|
11959
12605
|
for (const e of entries) {
|
|
11960
12606
|
if (e.name.startsWith(".")) continue;
|
|
@@ -11968,7 +12614,7 @@ var knownFilesRecognized = {
|
|
|
11968
12614
|
title: this.title,
|
|
11969
12615
|
status: "warn",
|
|
11970
12616
|
detail: `unexpected top-level entries: ${unexpected.join(", ")}`,
|
|
11971
|
-
affected: unexpected.map((n) =>
|
|
12617
|
+
affected: unexpected.map((n) => resolve36(ctx.syntaurRoot, n)),
|
|
11972
12618
|
remediation: {
|
|
11973
12619
|
kind: "manual",
|
|
11974
12620
|
suggestion: "Review these entries \u2014 they may be leftover state from older versions",
|
|
@@ -11997,8 +12643,8 @@ function pass2(check) {
|
|
|
11997
12643
|
|
|
11998
12644
|
// src/utils/doctor/checks/project.ts
|
|
11999
12645
|
init_fs();
|
|
12000
|
-
import { resolve as
|
|
12001
|
-
import { readdir as
|
|
12646
|
+
import { resolve as resolve37 } from "path";
|
|
12647
|
+
import { readdir as readdir14, stat as stat6 } from "fs/promises";
|
|
12002
12648
|
var CATEGORY3 = "project";
|
|
12003
12649
|
var REQUIRED_PROJECT_FILES = [
|
|
12004
12650
|
"project.md",
|
|
@@ -12022,15 +12668,15 @@ var PROJECT_MARKERS = ["project.md", "manifest.md", "assignments"];
|
|
|
12022
12668
|
async function listProjects2(ctx) {
|
|
12023
12669
|
const dir = ctx.config.defaultProjectDir;
|
|
12024
12670
|
if (!await fileExists(dir)) return [];
|
|
12025
|
-
const entries = await
|
|
12671
|
+
const entries = await readdir14(dir, { withFileTypes: true });
|
|
12026
12672
|
const result = [];
|
|
12027
12673
|
for (const e of entries) {
|
|
12028
12674
|
if (!e.isDirectory()) continue;
|
|
12029
12675
|
if (e.name.startsWith(".") || e.name.startsWith("_")) continue;
|
|
12030
|
-
const projectDir =
|
|
12676
|
+
const projectDir = resolve37(dir, e.name);
|
|
12031
12677
|
let looksLikeProject = false;
|
|
12032
12678
|
for (const marker of PROJECT_MARKERS) {
|
|
12033
|
-
if (await fileExists(
|
|
12679
|
+
if (await fileExists(resolve37(projectDir, marker))) {
|
|
12034
12680
|
looksLikeProject = true;
|
|
12035
12681
|
break;
|
|
12036
12682
|
}
|
|
@@ -12049,7 +12695,7 @@ var requiredFiles = {
|
|
|
12049
12695
|
for (const projectDir of projects) {
|
|
12050
12696
|
const missing = [];
|
|
12051
12697
|
for (const rel of REQUIRED_PROJECT_FILES) {
|
|
12052
|
-
const p =
|
|
12698
|
+
const p = resolve37(projectDir, rel);
|
|
12053
12699
|
if (!await fileExists(p)) missing.push(rel);
|
|
12054
12700
|
}
|
|
12055
12701
|
if (missing.length === 0) continue;
|
|
@@ -12059,7 +12705,7 @@ var requiredFiles = {
|
|
|
12059
12705
|
title: this.title,
|
|
12060
12706
|
status: "error",
|
|
12061
12707
|
detail: `project at ${projectDir} is missing: ${missing.join(", ")}`,
|
|
12062
|
-
affected: missing.map((m) =>
|
|
12708
|
+
affected: missing.map((m) => resolve37(projectDir, m)),
|
|
12063
12709
|
remediation: {
|
|
12064
12710
|
kind: "manual",
|
|
12065
12711
|
suggestion: "Recreate the missing scaffold files from templates",
|
|
@@ -12082,9 +12728,9 @@ var manifestStale = {
|
|
|
12082
12728
|
const projects = await listProjects2(ctx);
|
|
12083
12729
|
const results = [];
|
|
12084
12730
|
for (const projectDir of projects) {
|
|
12085
|
-
const manifestPath =
|
|
12731
|
+
const manifestPath = resolve37(projectDir, "manifest.md");
|
|
12086
12732
|
if (!await fileExists(manifestPath)) continue;
|
|
12087
|
-
const manifestMtime = (await
|
|
12733
|
+
const manifestMtime = (await stat6(manifestPath)).mtimeMs;
|
|
12088
12734
|
const newestAssignment = await newestAssignmentMtime(projectDir);
|
|
12089
12735
|
if (newestAssignment === 0) continue;
|
|
12090
12736
|
if (newestAssignment > manifestMtime) {
|
|
@@ -12116,7 +12762,7 @@ var orphanFiles = {
|
|
|
12116
12762
|
const projects = await listProjects2(ctx);
|
|
12117
12763
|
const results = [];
|
|
12118
12764
|
for (const projectDir of projects) {
|
|
12119
|
-
const entries = await
|
|
12765
|
+
const entries = await readdir14(projectDir, { withFileTypes: true });
|
|
12120
12766
|
const orphans = [];
|
|
12121
12767
|
for (const e of entries) {
|
|
12122
12768
|
if (e.name.startsWith(".")) continue;
|
|
@@ -12131,7 +12777,7 @@ var orphanFiles = {
|
|
|
12131
12777
|
title: this.title,
|
|
12132
12778
|
status: "warn",
|
|
12133
12779
|
detail: `project at ${projectDir} has unexpected entries: ${orphans.join(", ")}`,
|
|
12134
|
-
affected: orphans.map((o) =>
|
|
12780
|
+
affected: orphans.map((o) => resolve37(projectDir, o)),
|
|
12135
12781
|
autoFixable: false
|
|
12136
12782
|
});
|
|
12137
12783
|
}
|
|
@@ -12141,20 +12787,20 @@ var orphanFiles = {
|
|
|
12141
12787
|
};
|
|
12142
12788
|
var projectChecks = [requiredFiles, manifestStale, orphanFiles];
|
|
12143
12789
|
async function newestAssignmentMtime(projectDir) {
|
|
12144
|
-
const assignmentsRoot =
|
|
12790
|
+
const assignmentsRoot = resolve37(projectDir, "assignments");
|
|
12145
12791
|
if (!await fileExists(assignmentsRoot)) return 0;
|
|
12146
12792
|
let newest = 0;
|
|
12147
12793
|
let entries;
|
|
12148
12794
|
try {
|
|
12149
|
-
entries = await
|
|
12795
|
+
entries = await readdir14(assignmentsRoot, { withFileTypes: true });
|
|
12150
12796
|
} catch {
|
|
12151
12797
|
return 0;
|
|
12152
12798
|
}
|
|
12153
12799
|
for (const e of entries) {
|
|
12154
12800
|
if (!e.isDirectory()) continue;
|
|
12155
|
-
const assignmentMd =
|
|
12801
|
+
const assignmentMd = resolve37(assignmentsRoot, e.name, "assignment.md");
|
|
12156
12802
|
try {
|
|
12157
|
-
const s = await
|
|
12803
|
+
const s = await stat6(assignmentMd);
|
|
12158
12804
|
if (s.mtimeMs > newest) newest = s.mtimeMs;
|
|
12159
12805
|
} catch {
|
|
12160
12806
|
}
|
|
@@ -12176,28 +12822,28 @@ init_fs();
|
|
|
12176
12822
|
init_parser();
|
|
12177
12823
|
init_types();
|
|
12178
12824
|
init_paths();
|
|
12179
|
-
import { resolve as
|
|
12180
|
-
import { readdir as
|
|
12825
|
+
import { resolve as resolve38 } from "path";
|
|
12826
|
+
import { readdir as readdir15, readFile as readFile21 } from "fs/promises";
|
|
12181
12827
|
var CATEGORY4 = "assignment";
|
|
12182
12828
|
var STATUSES_REQUIRING_HANDOFF = /* @__PURE__ */ new Set(["review", "completed"]);
|
|
12183
12829
|
async function listAssignments(ctx) {
|
|
12184
12830
|
const result = { withAssignmentMd: [], orphanFolders: [] };
|
|
12185
12831
|
const projectsDir2 = ctx.config.defaultProjectDir;
|
|
12186
12832
|
if (await fileExists(projectsDir2)) {
|
|
12187
|
-
const projects = await
|
|
12833
|
+
const projects = await readdir15(projectsDir2, { withFileTypes: true });
|
|
12188
12834
|
for (const m of projects) {
|
|
12189
12835
|
if (!m.isDirectory()) continue;
|
|
12190
12836
|
if (m.name.startsWith(".") || m.name.startsWith("_")) continue;
|
|
12191
|
-
const assignmentsDir2 =
|
|
12837
|
+
const assignmentsDir2 = resolve38(projectsDir2, m.name, "assignments");
|
|
12192
12838
|
if (!await fileExists(assignmentsDir2)) continue;
|
|
12193
|
-
const entries = await
|
|
12839
|
+
const entries = await readdir15(assignmentsDir2, { withFileTypes: true });
|
|
12194
12840
|
for (const a of entries) {
|
|
12195
12841
|
if (!a.isDirectory()) continue;
|
|
12196
12842
|
if (a.name.startsWith(".") || a.name.startsWith("_")) continue;
|
|
12197
|
-
const assignmentDir =
|
|
12198
|
-
const assignmentMd =
|
|
12843
|
+
const assignmentDir = resolve38(assignmentsDir2, a.name);
|
|
12844
|
+
const assignmentMd = resolve38(assignmentDir, "assignment.md");
|
|
12199
12845
|
const entry = {
|
|
12200
|
-
projectDir:
|
|
12846
|
+
projectDir: resolve38(projectsDir2, m.name),
|
|
12201
12847
|
projectSlug: m.name,
|
|
12202
12848
|
assignmentDir,
|
|
12203
12849
|
assignmentSlug: a.name,
|
|
@@ -12213,12 +12859,12 @@ async function listAssignments(ctx) {
|
|
|
12213
12859
|
}
|
|
12214
12860
|
const standaloneRoot = assignmentsDir();
|
|
12215
12861
|
if (await fileExists(standaloneRoot)) {
|
|
12216
|
-
const entries = await
|
|
12862
|
+
const entries = await readdir15(standaloneRoot, { withFileTypes: true });
|
|
12217
12863
|
for (const a of entries) {
|
|
12218
12864
|
if (!a.isDirectory()) continue;
|
|
12219
12865
|
if (a.name.startsWith(".") || a.name.startsWith("_")) continue;
|
|
12220
|
-
const assignmentDir =
|
|
12221
|
-
const assignmentMd =
|
|
12866
|
+
const assignmentDir = resolve38(standaloneRoot, a.name);
|
|
12867
|
+
const assignmentMd = resolve38(assignmentDir, "assignment.md");
|
|
12222
12868
|
const entry = {
|
|
12223
12869
|
projectDir: standaloneRoot,
|
|
12224
12870
|
projectSlug: null,
|
|
@@ -12296,7 +12942,7 @@ var invalidStatus = {
|
|
|
12296
12942
|
const allowed = configuredStatuses(ctx);
|
|
12297
12943
|
const results = [];
|
|
12298
12944
|
for (const a of withAssignmentMd) {
|
|
12299
|
-
const path =
|
|
12945
|
+
const path = resolve38(a.assignmentDir, "assignment.md");
|
|
12300
12946
|
const parsed = await parseSafe(path);
|
|
12301
12947
|
if (!parsed) continue;
|
|
12302
12948
|
if (!allowed.has(parsed.status)) {
|
|
@@ -12329,7 +12975,7 @@ var workspaceMissing = {
|
|
|
12329
12975
|
const terminal = terminalStatuses(ctx);
|
|
12330
12976
|
const results = [];
|
|
12331
12977
|
for (const a of withAssignmentMd) {
|
|
12332
|
-
const path =
|
|
12978
|
+
const path = resolve38(a.assignmentDir, "assignment.md");
|
|
12333
12979
|
const parsed = await parseSafe(path);
|
|
12334
12980
|
if (!parsed) continue;
|
|
12335
12981
|
if (terminal.has(parsed.status)) continue;
|
|
@@ -12376,12 +13022,12 @@ var requiredFilesByStatus = {
|
|
|
12376
13022
|
const { withAssignmentMd } = await listAssignments(ctx);
|
|
12377
13023
|
const results = [];
|
|
12378
13024
|
for (const a of withAssignmentMd) {
|
|
12379
|
-
const assignmentPath =
|
|
13025
|
+
const assignmentPath = resolve38(a.assignmentDir, "assignment.md");
|
|
12380
13026
|
const parsed = await parseSafe(assignmentPath);
|
|
12381
13027
|
if (!parsed) continue;
|
|
12382
13028
|
const missing = [];
|
|
12383
13029
|
if (STATUSES_REQUIRING_HANDOFF.has(parsed.status)) {
|
|
12384
|
-
const handoffPath =
|
|
13030
|
+
const handoffPath = resolve38(a.assignmentDir, "handoff.md");
|
|
12385
13031
|
if (!await fileExists(handoffPath)) missing.push("handoff.md");
|
|
12386
13032
|
}
|
|
12387
13033
|
if (missing.length === 0) continue;
|
|
@@ -12391,7 +13037,7 @@ var requiredFilesByStatus = {
|
|
|
12391
13037
|
title: this.title,
|
|
12392
13038
|
status: "warn",
|
|
12393
13039
|
detail: `${a.projectSlug}/${a.assignmentSlug} (status: ${parsed.status}) is missing ${missing.join(", ")}`,
|
|
12394
|
-
affected: missing.map((m) =>
|
|
13040
|
+
affected: missing.map((m) => resolve38(a.assignmentDir, m)),
|
|
12395
13041
|
remediation: {
|
|
12396
13042
|
kind: "manual",
|
|
12397
13043
|
suggestion: `Create the missing ${missing.join(" and ")} files for this assignment`,
|
|
@@ -12414,7 +13060,7 @@ var companionFilesScaffolded = {
|
|
|
12414
13060
|
for (const a of withAssignmentMd) {
|
|
12415
13061
|
const missing = [];
|
|
12416
13062
|
for (const filename of ["progress.md", "comments.md"]) {
|
|
12417
|
-
if (!await fileExists(
|
|
13063
|
+
if (!await fileExists(resolve38(a.assignmentDir, filename))) {
|
|
12418
13064
|
missing.push(filename);
|
|
12419
13065
|
}
|
|
12420
13066
|
}
|
|
@@ -12426,7 +13072,7 @@ var companionFilesScaffolded = {
|
|
|
12426
13072
|
title: this.title,
|
|
12427
13073
|
status: "warn",
|
|
12428
13074
|
detail: `${label} is missing ${missing.join(" and ")} (pre-v2.0 assignment \u2014 not required, but scaffolding them keeps the dashboard and CLIs consistent)`,
|
|
12429
|
-
affected: missing.map((m) =>
|
|
13075
|
+
affected: missing.map((m) => resolve38(a.assignmentDir, m)),
|
|
12430
13076
|
remediation: {
|
|
12431
13077
|
kind: "manual",
|
|
12432
13078
|
suggestion: `Create ${missing.join(" and ")} with the renderProgress/renderComments templates, or re-scaffold via the CLI`,
|
|
@@ -12459,7 +13105,7 @@ var typeDefinition = {
|
|
|
12459
13105
|
const { withAssignmentMd } = await listAssignments(ctx);
|
|
12460
13106
|
const results = [];
|
|
12461
13107
|
for (const a of withAssignmentMd) {
|
|
12462
|
-
const path =
|
|
13108
|
+
const path = resolve38(a.assignmentDir, "assignment.md");
|
|
12463
13109
|
const parsed = await parseSafe(path);
|
|
12464
13110
|
if (!parsed) continue;
|
|
12465
13111
|
if (!parsed.type) continue;
|
|
@@ -12493,7 +13139,7 @@ var projectFrontmatterMatchesContainer = {
|
|
|
12493
13139
|
const { withAssignmentMd } = await listAssignments(ctx);
|
|
12494
13140
|
const results = [];
|
|
12495
13141
|
for (const a of withAssignmentMd) {
|
|
12496
|
-
const path =
|
|
13142
|
+
const path = resolve38(a.assignmentDir, "assignment.md");
|
|
12497
13143
|
const parsed = await parseSafe(path);
|
|
12498
13144
|
if (!parsed) continue;
|
|
12499
13145
|
if (a.standalone) {
|
|
@@ -12548,7 +13194,7 @@ var assignmentChecks = [
|
|
|
12548
13194
|
];
|
|
12549
13195
|
async function parseSafe(path) {
|
|
12550
13196
|
try {
|
|
12551
|
-
const content = await
|
|
13197
|
+
const content = await readFile21(path, "utf-8");
|
|
12552
13198
|
return parseAssignmentFull(content);
|
|
12553
13199
|
} catch {
|
|
12554
13200
|
return null;
|
|
@@ -12567,7 +13213,7 @@ function pass4(check, detail) {
|
|
|
12567
13213
|
|
|
12568
13214
|
// src/utils/doctor/checks/dashboard.ts
|
|
12569
13215
|
init_fs();
|
|
12570
|
-
import { resolve as
|
|
13216
|
+
import { resolve as resolve39 } from "path";
|
|
12571
13217
|
var CATEGORY5 = "dashboard";
|
|
12572
13218
|
var dbReachable = {
|
|
12573
13219
|
id: "dashboard.db-reachable",
|
|
@@ -12581,7 +13227,7 @@ var dbReachable = {
|
|
|
12581
13227
|
title: this.title,
|
|
12582
13228
|
status: "error",
|
|
12583
13229
|
detail: `could not open syntaur.db: ${ctx.dbError ?? "unknown error"}`,
|
|
12584
|
-
affected: [
|
|
13230
|
+
affected: [resolve39(ctx.syntaurRoot, "syntaur.db")],
|
|
12585
13231
|
remediation: {
|
|
12586
13232
|
kind: "manual",
|
|
12587
13233
|
suggestion: "Start the dashboard once (`syntaur dashboard`) to initialize the DB, or restore it from backup",
|
|
@@ -12599,7 +13245,7 @@ var dbReachable = {
|
|
|
12599
13245
|
title: this.title,
|
|
12600
13246
|
status: "error",
|
|
12601
13247
|
detail: 'syntaur.db is missing the expected "sessions" table',
|
|
12602
|
-
affected: [
|
|
13248
|
+
affected: [resolve39(ctx.syntaurRoot, "syntaur.db")],
|
|
12603
13249
|
autoFixable: false
|
|
12604
13250
|
};
|
|
12605
13251
|
}
|
|
@@ -12611,7 +13257,7 @@ var dbReachable = {
|
|
|
12611
13257
|
title: this.title,
|
|
12612
13258
|
status: "error",
|
|
12613
13259
|
detail: `syntaur.db query failed: ${err2 instanceof Error ? err2.message : String(err2)}`,
|
|
12614
|
-
affected: [
|
|
13260
|
+
affected: [resolve39(ctx.syntaurRoot, "syntaur.db")],
|
|
12615
13261
|
autoFixable: false
|
|
12616
13262
|
};
|
|
12617
13263
|
}
|
|
@@ -12637,7 +13283,7 @@ var ghostSessions = {
|
|
|
12637
13283
|
const results = [];
|
|
12638
13284
|
for (const row of rows) {
|
|
12639
13285
|
if (!row.project_slug) continue;
|
|
12640
|
-
const projectPath =
|
|
13286
|
+
const projectPath = resolve39(projectsDir2, row.project_slug, "project.md");
|
|
12641
13287
|
if (!await fileExists(projectPath)) {
|
|
12642
13288
|
results.push({
|
|
12643
13289
|
id: this.id,
|
|
@@ -12656,7 +13302,7 @@ var ghostSessions = {
|
|
|
12656
13302
|
continue;
|
|
12657
13303
|
}
|
|
12658
13304
|
if (row.assignment_slug) {
|
|
12659
|
-
const assignmentPath =
|
|
13305
|
+
const assignmentPath = resolve39(
|
|
12660
13306
|
projectsDir2,
|
|
12661
13307
|
row.project_slug,
|
|
12662
13308
|
"assignments",
|
|
@@ -12708,7 +13354,7 @@ function skipped(check, reason) {
|
|
|
12708
13354
|
|
|
12709
13355
|
// src/utils/doctor/checks/integrations.ts
|
|
12710
13356
|
init_fs();
|
|
12711
|
-
import { readdir as
|
|
13357
|
+
import { readdir as readdir16 } from "fs/promises";
|
|
12712
13358
|
var CATEGORY6 = "integrations";
|
|
12713
13359
|
var claudePluginLinked = {
|
|
12714
13360
|
id: "integrations.claude-plugin-linked",
|
|
@@ -12770,7 +13416,7 @@ var backupConfigured = {
|
|
|
12770
13416
|
if (ctx.config.backup?.repo) return pass6(this);
|
|
12771
13417
|
const projectsDir2 = ctx.config.defaultProjectDir;
|
|
12772
13418
|
if (!await fileExists(projectsDir2)) return skipped2(this, "no projects dir");
|
|
12773
|
-
const entries = await
|
|
13419
|
+
const entries = await readdir16(projectsDir2, { withFileTypes: true });
|
|
12774
13420
|
const hasProjects = entries.some((e) => e.isDirectory() && !e.name.startsWith(".") && !e.name.startsWith("_"));
|
|
12775
13421
|
if (!hasProjects) return skipped2(this, "no projects yet");
|
|
12776
13422
|
return {
|
|
@@ -12813,8 +13459,8 @@ function skipped2(check, reason) {
|
|
|
12813
13459
|
init_fs();
|
|
12814
13460
|
init_parser();
|
|
12815
13461
|
init_types();
|
|
12816
|
-
import { resolve as
|
|
12817
|
-
import { readFile as
|
|
13462
|
+
import { resolve as resolve40 } from "path";
|
|
13463
|
+
import { readFile as readFile22 } from "fs/promises";
|
|
12818
13464
|
var CATEGORY7 = "workspace";
|
|
12819
13465
|
var ASSIGNMENT_FIELDS = ["projectSlug", "assignmentSlug", "projectDir", "assignmentDir"];
|
|
12820
13466
|
function hasAnyAssignmentField(ctx) {
|
|
@@ -12826,12 +13472,12 @@ function isStandaloneSession(ctx) {
|
|
|
12826
13472
|
return !hasAnyAssignmentField(ctx) && typeof ctx.sessionId === "string" && ctx.sessionId.length > 0;
|
|
12827
13473
|
}
|
|
12828
13474
|
async function loadContext(ctx) {
|
|
12829
|
-
const path =
|
|
13475
|
+
const path = resolve40(ctx.cwd, ".syntaur", "context.json");
|
|
12830
13476
|
if (!await fileExists(path)) {
|
|
12831
13477
|
return { data: null, path, exists: false, parseError: null };
|
|
12832
13478
|
}
|
|
12833
13479
|
try {
|
|
12834
|
-
const raw = await
|
|
13480
|
+
const raw = await readFile22(path, "utf-8");
|
|
12835
13481
|
return { data: JSON.parse(raw), path, exists: true, parseError: null };
|
|
12836
13482
|
} catch (err2) {
|
|
12837
13483
|
return {
|
|
@@ -12906,7 +13552,7 @@ var contextAssignmentResolves = {
|
|
|
12906
13552
|
if (!exists) return skipped3(this, "no context to resolve");
|
|
12907
13553
|
if (isStandaloneSession(data)) return skipped3(this, "standalone session context \u2014 no assignment to resolve");
|
|
12908
13554
|
if (!data?.assignmentDir) return skipped3(this, "context has no assignmentDir");
|
|
12909
|
-
const assignmentMd =
|
|
13555
|
+
const assignmentMd = resolve40(data.assignmentDir, "assignment.md");
|
|
12910
13556
|
if (!await fileExists(assignmentMd)) {
|
|
12911
13557
|
return {
|
|
12912
13558
|
id: this.id,
|
|
@@ -12935,10 +13581,10 @@ var contextTerminal = {
|
|
|
12935
13581
|
if (!exists) return skipped3(this, "no context to check");
|
|
12936
13582
|
if (isStandaloneSession(data)) return skipped3(this, "standalone session context \u2014 no assignment to check");
|
|
12937
13583
|
if (!data?.assignmentDir) return skipped3(this, "context has no assignmentDir");
|
|
12938
|
-
const assignmentMd =
|
|
13584
|
+
const assignmentMd = resolve40(data.assignmentDir, "assignment.md");
|
|
12939
13585
|
if (!await fileExists(assignmentMd)) return skipped3(this, "assignment file missing");
|
|
12940
13586
|
try {
|
|
12941
|
-
const content = await
|
|
13587
|
+
const content = await readFile22(assignmentMd, "utf-8");
|
|
12942
13588
|
const parsed = parseAssignmentFull(content);
|
|
12943
13589
|
const terminal = terminalStatuses2(ctx);
|
|
12944
13590
|
if (terminal.has(parsed.status)) {
|
|
@@ -13081,15 +13727,15 @@ async function finalize(checks) {
|
|
|
13081
13727
|
}
|
|
13082
13728
|
async function readVersion() {
|
|
13083
13729
|
try {
|
|
13084
|
-
const here =
|
|
13085
|
-
let dir =
|
|
13730
|
+
const here = fileURLToPath7(import.meta.url);
|
|
13731
|
+
let dir = dirname10(here);
|
|
13086
13732
|
for (let i = 0; i < 6; i++) {
|
|
13087
13733
|
try {
|
|
13088
|
-
const raw = await
|
|
13734
|
+
const raw = await readFile23(join5(dir, "package.json"), "utf-8");
|
|
13089
13735
|
const parsed = JSON.parse(raw);
|
|
13090
13736
|
return typeof parsed.version === "string" ? parsed.version : null;
|
|
13091
13737
|
} catch {
|
|
13092
|
-
dir =
|
|
13738
|
+
dir = dirname10(dir);
|
|
13093
13739
|
}
|
|
13094
13740
|
}
|
|
13095
13741
|
return null;
|
|
@@ -13222,8 +13868,8 @@ var doctorCommand = new Command3("doctor").description("Diagnose Syntaur state a
|
|
|
13222
13868
|
init_paths();
|
|
13223
13869
|
init_fs();
|
|
13224
13870
|
init_config2();
|
|
13225
|
-
import { resolve as
|
|
13226
|
-
import { readFile as
|
|
13871
|
+
import { resolve as resolve41 } from "path";
|
|
13872
|
+
import { readFile as readFile24 } from "fs/promises";
|
|
13227
13873
|
init_timestamp();
|
|
13228
13874
|
init_assignment_resolver();
|
|
13229
13875
|
function shortId() {
|
|
@@ -13255,7 +13901,7 @@ async function commentCommand(target, text, options = {}) {
|
|
|
13255
13901
|
if (!isValidSlug(target)) {
|
|
13256
13902
|
throw new Error(`Invalid assignment slug "${target}".`);
|
|
13257
13903
|
}
|
|
13258
|
-
assignmentDir =
|
|
13904
|
+
assignmentDir = resolve41(baseDir, options.project, "assignments", target);
|
|
13259
13905
|
assignmentRef = target;
|
|
13260
13906
|
} else {
|
|
13261
13907
|
const resolved = await resolveAssignmentById(baseDir, assignmentsDir(), target);
|
|
@@ -13265,13 +13911,13 @@ async function commentCommand(target, text, options = {}) {
|
|
|
13265
13911
|
assignmentDir = resolved.assignmentDir;
|
|
13266
13912
|
assignmentRef = resolved.standalone ? resolved.id : resolved.assignmentSlug;
|
|
13267
13913
|
}
|
|
13268
|
-
const commentsPath =
|
|
13914
|
+
const commentsPath = resolve41(assignmentDir, "comments.md");
|
|
13269
13915
|
const timestamp = nowTimestamp();
|
|
13270
13916
|
const author = options.author ?? process.env.USER ?? "unknown";
|
|
13271
13917
|
let currentContent;
|
|
13272
13918
|
let currentCount = 0;
|
|
13273
13919
|
if (await fileExists(commentsPath)) {
|
|
13274
|
-
currentContent = await
|
|
13920
|
+
currentContent = await readFile24(commentsPath, "utf-8");
|
|
13275
13921
|
const countMatch = currentContent.match(/^entryCount:\s*(\d+)/m);
|
|
13276
13922
|
if (countMatch) currentCount = parseInt(countMatch[1], 10);
|
|
13277
13923
|
} else {
|
|
@@ -13308,8 +13954,8 @@ ${entry}`;
|
|
|
13308
13954
|
init_paths();
|
|
13309
13955
|
init_fs();
|
|
13310
13956
|
init_config2();
|
|
13311
|
-
import { resolve as
|
|
13312
|
-
import { readFile as
|
|
13957
|
+
import { resolve as resolve42 } from "path";
|
|
13958
|
+
import { readFile as readFile25 } from "fs/promises";
|
|
13313
13959
|
init_timestamp();
|
|
13314
13960
|
init_assignment_resolver();
|
|
13315
13961
|
function setTopLevelField3(content, key, value) {
|
|
@@ -13334,7 +13980,7 @@ async function requestCommand(target, text, options = {}) {
|
|
|
13334
13980
|
if (!isValidSlug(target)) {
|
|
13335
13981
|
throw new Error(`Invalid assignment slug "${target}".`);
|
|
13336
13982
|
}
|
|
13337
|
-
assignmentDir =
|
|
13983
|
+
assignmentDir = resolve42(baseDir, options.project, "assignments", target);
|
|
13338
13984
|
targetRef = target;
|
|
13339
13985
|
} else {
|
|
13340
13986
|
const resolved = await resolveAssignmentById(baseDir, assignmentsDir(), target);
|
|
@@ -13344,12 +13990,12 @@ async function requestCommand(target, text, options = {}) {
|
|
|
13344
13990
|
assignmentDir = resolved.assignmentDir;
|
|
13345
13991
|
targetRef = resolved.standalone ? resolved.id : resolved.assignmentSlug;
|
|
13346
13992
|
}
|
|
13347
|
-
const assignmentMdPath =
|
|
13993
|
+
const assignmentMdPath = resolve42(assignmentDir, "assignment.md");
|
|
13348
13994
|
if (!await fileExists(assignmentMdPath)) {
|
|
13349
13995
|
throw new Error(`assignment.md not found at ${assignmentMdPath}`);
|
|
13350
13996
|
}
|
|
13351
13997
|
const source = options.from ?? process.env.SYNTAUR_ASSIGNMENT ?? "unknown";
|
|
13352
|
-
let content = await
|
|
13998
|
+
let content = await readFile25(assignmentMdPath, "utf-8");
|
|
13353
13999
|
const todoLine = `- [ ] ${text.trim()} (from: ${source})`;
|
|
13354
14000
|
const todosHeading = /^## Todos\s*$/m;
|
|
13355
14001
|
if (todosHeading.test(content)) {
|
|
@@ -13377,10 +14023,10 @@ ${todoLine}
|
|
|
13377
14023
|
|
|
13378
14024
|
// src/cli-default-command.ts
|
|
13379
14025
|
init_config2();
|
|
13380
|
-
import { readdir as
|
|
14026
|
+
import { readdir as readdir17 } from "fs/promises";
|
|
13381
14027
|
async function hasAnyProjectContent(projectsDir2) {
|
|
13382
14028
|
try {
|
|
13383
|
-
const entries = await
|
|
14029
|
+
const entries = await readdir17(projectsDir2, { withFileTypes: true });
|
|
13384
14030
|
return entries.some((entry) => entry.isDirectory());
|
|
13385
14031
|
} catch {
|
|
13386
14032
|
return false;
|
|
@@ -13416,21 +14062,21 @@ async function getDefaultCommandName() {
|
|
|
13416
14062
|
// src/utils/npx-prompt.ts
|
|
13417
14063
|
init_paths();
|
|
13418
14064
|
init_fs();
|
|
13419
|
-
import { fileURLToPath as
|
|
13420
|
-
import { readFile as
|
|
13421
|
-
import { dirname as
|
|
14065
|
+
import { fileURLToPath as fileURLToPath9 } from "url";
|
|
14066
|
+
import { readFile as readFile27 } from "fs/promises";
|
|
14067
|
+
import { dirname as dirname12, join as join7, resolve as resolve43 } from "path";
|
|
13422
14068
|
import { spawn as spawn3 } from "child_process";
|
|
13423
14069
|
import { createInterface as createInterface2 } from "readline/promises";
|
|
13424
14070
|
|
|
13425
14071
|
// src/utils/version.ts
|
|
13426
|
-
import { fileURLToPath as
|
|
13427
|
-
import { readFile as
|
|
13428
|
-
import { dirname as
|
|
14072
|
+
import { fileURLToPath as fileURLToPath8 } from "url";
|
|
14073
|
+
import { readFile as readFile26 } from "fs/promises";
|
|
14074
|
+
import { dirname as dirname11, join as join6 } from "path";
|
|
13429
14075
|
async function readPackageVersion(scriptUrl) {
|
|
13430
14076
|
try {
|
|
13431
|
-
const scriptPath =
|
|
13432
|
-
const pkgRoot =
|
|
13433
|
-
const raw = await
|
|
14077
|
+
const scriptPath = fileURLToPath8(scriptUrl);
|
|
14078
|
+
const pkgRoot = dirname11(dirname11(scriptPath));
|
|
14079
|
+
const raw = await readFile26(join6(pkgRoot, "package.json"), "utf-8");
|
|
13434
14080
|
const parsed = JSON.parse(raw);
|
|
13435
14081
|
return typeof parsed.version === "string" ? parsed.version : null;
|
|
13436
14082
|
} catch {
|
|
@@ -13439,13 +14085,13 @@ async function readPackageVersion(scriptUrl) {
|
|
|
13439
14085
|
}
|
|
13440
14086
|
|
|
13441
14087
|
// src/utils/npx-prompt.ts
|
|
13442
|
-
var STATE_FILE =
|
|
14088
|
+
var STATE_FILE = resolve43(syntaurRoot(), "npx-install.json");
|
|
13443
14089
|
var META_ARGS = /* @__PURE__ */ new Set(["-h", "--help", "-V", "--version", "help"]);
|
|
13444
14090
|
var GLOBAL_VERSION_TIMEOUT_MS = 2e3;
|
|
13445
14091
|
function isRunningViaNpx(scriptUrl) {
|
|
13446
14092
|
let scriptPath;
|
|
13447
14093
|
try {
|
|
13448
|
-
scriptPath =
|
|
14094
|
+
scriptPath = fileURLToPath9(scriptUrl);
|
|
13449
14095
|
} catch {
|
|
13450
14096
|
return false;
|
|
13451
14097
|
}
|
|
@@ -13460,7 +14106,7 @@ function isRunningViaNpx(scriptUrl) {
|
|
|
13460
14106
|
async function readState() {
|
|
13461
14107
|
if (!await fileExists(STATE_FILE)) return null;
|
|
13462
14108
|
try {
|
|
13463
|
-
const raw = await
|
|
14109
|
+
const raw = await readFile27(STATE_FILE, "utf-8");
|
|
13464
14110
|
return JSON.parse(raw);
|
|
13465
14111
|
} catch {
|
|
13466
14112
|
return null;
|
|
@@ -13471,10 +14117,10 @@ async function writeState(state) {
|
|
|
13471
14117
|
`);
|
|
13472
14118
|
}
|
|
13473
14119
|
async function resolveNpmBin() {
|
|
13474
|
-
const nodeDir =
|
|
14120
|
+
const nodeDir = dirname12(process.execPath);
|
|
13475
14121
|
const isWin = process.platform === "win32";
|
|
13476
14122
|
const npmName = isWin ? "npm.cmd" : "npm";
|
|
13477
|
-
const nearNode =
|
|
14123
|
+
const nearNode = join7(nodeDir, npmName);
|
|
13478
14124
|
if (await fileExists(nearNode)) {
|
|
13479
14125
|
return { cmd: nearNode, shell: false };
|
|
13480
14126
|
}
|
|
@@ -13517,9 +14163,9 @@ async function readGlobalVersion() {
|
|
|
13517
14163
|
});
|
|
13518
14164
|
if (!rootPath) return null;
|
|
13519
14165
|
try {
|
|
13520
|
-
const manifestPath =
|
|
14166
|
+
const manifestPath = join7(rootPath, "syntaur", "package.json");
|
|
13521
14167
|
if (!await fileExists(manifestPath)) return null;
|
|
13522
|
-
const raw = await
|
|
14168
|
+
const raw = await readFile27(manifestPath, "utf-8");
|
|
13523
14169
|
const parsed = JSON.parse(raw);
|
|
13524
14170
|
return typeof parsed.version === "string" ? parsed.version : null;
|
|
13525
14171
|
} catch {
|
|
@@ -13814,7 +14460,7 @@ program.command("setup").description("Initialize Syntaur and optionally install
|
|
|
13814
14460
|
process.exit(1);
|
|
13815
14461
|
}
|
|
13816
14462
|
});
|
|
13817
|
-
program.command("install-plugin").description("Install the Syntaur Claude Code plugin").option("--force", "Overwrite an existing Syntaur-managed install").option("--target-dir <path>", "Install the plugin at a specific directory").option("--link", "Use a symlink instead of copying files (repo-local dev only)").action(async (options) => {
|
|
14463
|
+
program.command("install-plugin").description("Install the Syntaur Claude Code plugin").option("--force", "Overwrite an existing Syntaur-managed install").option("--target-dir <path>", "Install the plugin at a specific directory").option("--link", "Use a symlink instead of copying files (repo-local dev only)").option("--force-skills", "Overwrite user-edited skills in ~/.claude/skills").option("--skip-skills", "Do not install protocol skills into ~/.claude/skills").action(async (options) => {
|
|
13818
14464
|
try {
|
|
13819
14465
|
await installPluginCommand({ ...options, promptForTarget: true });
|
|
13820
14466
|
} catch (error) {
|
|
@@ -13825,7 +14471,45 @@ program.command("install-plugin").description("Install the Syntaur Claude Code p
|
|
|
13825
14471
|
process.exit(1);
|
|
13826
14472
|
}
|
|
13827
14473
|
});
|
|
13828
|
-
program.command("install-
|
|
14474
|
+
program.command("install-statusline").description(
|
|
14475
|
+
"Install the syntaur statusLine for Claude Code. Augments ~/.claude/settings.json; wraps any existing script by default."
|
|
14476
|
+
).option("--mode <mode>", "replace | wrap | skip | ask (default: ask, wrap in non-TTY)", "ask").option("--link", "Symlink the installed script to the package source (dev mode)").action(async (options) => {
|
|
14477
|
+
try {
|
|
14478
|
+
const rawMode = (options.mode ?? "ask").toLowerCase();
|
|
14479
|
+
const valid = ["replace", "wrap", "skip", "ask"];
|
|
14480
|
+
if (!valid.includes(rawMode)) {
|
|
14481
|
+
throw new Error(
|
|
14482
|
+
`Invalid --mode "${rawMode}". Must be one of: ${valid.join(", ")}.`
|
|
14483
|
+
);
|
|
14484
|
+
}
|
|
14485
|
+
await installStatuslineCommand({
|
|
14486
|
+
mode: rawMode,
|
|
14487
|
+
link: options.link
|
|
14488
|
+
});
|
|
14489
|
+
} catch (error) {
|
|
14490
|
+
console.error("Error:", error instanceof Error ? error.message : String(error));
|
|
14491
|
+
process.exit(1);
|
|
14492
|
+
}
|
|
14493
|
+
});
|
|
14494
|
+
program.command("uninstall-statusline").description(
|
|
14495
|
+
"Remove the syntaur statusLine. Restores the previously configured command from backup if present."
|
|
14496
|
+
).option("--keep-script", "Leave ~/.syntaur/statusline.sh on disk (only edit settings.json)").action(async (options) => {
|
|
14497
|
+
try {
|
|
14498
|
+
await uninstallStatuslineCommand({ keepScript: options.keepScript });
|
|
14499
|
+
} catch (error) {
|
|
14500
|
+
console.error("Error:", error instanceof Error ? error.message : String(error));
|
|
14501
|
+
process.exit(1);
|
|
14502
|
+
}
|
|
14503
|
+
});
|
|
14504
|
+
program.command("uninstall-skills").description("Remove Syntaur protocol skills from ~/.claude/skills and/or ~/.codex/skills").option("--claude", "Remove from ~/.claude/skills").option("--codex", "Remove from ~/.codex/skills").option("--all", "Remove from both").action(async (options) => {
|
|
14505
|
+
try {
|
|
14506
|
+
await uninstallSkillsCommand(options);
|
|
14507
|
+
} catch (error) {
|
|
14508
|
+
console.error("Error:", error instanceof Error ? error.message : String(error));
|
|
14509
|
+
process.exit(1);
|
|
14510
|
+
}
|
|
14511
|
+
});
|
|
14512
|
+
program.command("install-codex-plugin").description("Install the Syntaur Codex plugin and marketplace entry").option("--force", "Overwrite an existing Syntaur-managed install").option("--target-dir <path>", "Install the plugin at a specific directory").option("--marketplace-path <path>", "Write the marketplace entry to a specific file").option("--link", "Use a symlink instead of copying files (repo-local dev only)").option("--force-skills", "Overwrite user-edited skills in ~/.codex/skills").option("--skip-skills", "Do not install protocol skills into ~/.codex/skills").action(async (options) => {
|
|
13829
14513
|
try {
|
|
13830
14514
|
await installCodexPluginCommand({ ...options, promptForTarget: true });
|
|
13831
14515
|
} catch (error) {
|
|
@@ -13858,7 +14542,13 @@ program.command("setup-adapter").description("Generate adapter instruction files
|
|
|
13858
14542
|
process.exit(1);
|
|
13859
14543
|
}
|
|
13860
14544
|
});
|
|
13861
|
-
program.command("track-session").description("Register an agent session (optionally linked to a project/assignment)").option("--project <slug>", "Target project slug").option("--assignment <slug>", "Assignment slug").option("--agent <name>", "Agent name, e.g. claude, codex, cursor (required)").
|
|
14545
|
+
program.command("track-session").description("Register an agent session (optionally linked to a project/assignment)").option("--project <slug>", "Target project slug").option("--assignment <slug>", "Assignment slug").option("--agent <name>", "Agent name, e.g. claude, codex, cursor (required)").requiredOption(
|
|
14546
|
+
"--session-id <id>",
|
|
14547
|
+
"Session id from the agent runtime (real, not generated). Claude: read from ~/.claude/sessions/<pid>.json or the SessionStart hook payload. Codex: `payload.id` from the first line of the matching ~/.codex/sessions/YYYY/MM/DD/rollout-*.jsonl."
|
|
14548
|
+
).option(
|
|
14549
|
+
"--transcript-path <path>",
|
|
14550
|
+
"Absolute path to the agent rollout/transcript file (e.g. the Codex rollout jsonl or Claude transcript jsonl)."
|
|
14551
|
+
).option("--path <path>", "Full path to session on disk (defaults to cwd)").option("--dir <path>", "Override default project directory").option("--description <text>", "Description of what this session is for").action(async (options) => {
|
|
13862
14552
|
try {
|
|
13863
14553
|
await trackSessionCommand(options);
|
|
13864
14554
|
} catch (error) {
|