syntaur 0.4.4 → 0.4.5
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/dashboard/dist/assets/{_basePickBy-CWivToyi.js → _basePickBy-ZY1FrcKw.js} +1 -1
- package/dashboard/dist/assets/{_baseUniq-C-qj6E4l.js → _baseUniq-C7xZB6ea.js} +1 -1
- package/dashboard/dist/assets/{arc-Dn5BIqMa.js → arc-CPtDVk1A.js} +1 -1
- package/dashboard/dist/assets/{architectureDiagram-2XIMDMQ5-D5D0K7rY.js → architectureDiagram-2XIMDMQ5-Dr5rnxwf.js} +1 -1
- package/dashboard/dist/assets/{blockDiagram-WCTKOSBZ-DVmYPMbu.js → blockDiagram-WCTKOSBZ-SsDboTb2.js} +1 -1
- package/dashboard/dist/assets/{c4Diagram-IC4MRINW-BEasxbnl.js → c4Diagram-IC4MRINW-CZqjXmV0.js} +1 -1
- package/dashboard/dist/assets/channel-ejDeCb7i.js +1 -0
- package/dashboard/dist/assets/{chunk-4BX2VUAB-LDIrtI5E.js → chunk-4BX2VUAB-BYskd63Z.js} +1 -1
- package/dashboard/dist/assets/{chunk-55IACEB6-CaEBUJYu.js → chunk-55IACEB6-CWLImr1E.js} +1 -1
- package/dashboard/dist/assets/{chunk-FMBD7UC4-B-GjCpdr.js → chunk-FMBD7UC4-fQXSIXhy.js} +1 -1
- package/dashboard/dist/assets/{chunk-JSJVCQXG-BLVVcezm.js → chunk-JSJVCQXG-DJXtEexG.js} +1 -1
- package/dashboard/dist/assets/{chunk-KX2RTZJC-DqCNEw4h.js → chunk-KX2RTZJC-C0ivaZ1M.js} +1 -1
- package/dashboard/dist/assets/{chunk-NQ4KR5QH-BCPbFf5I.js → chunk-NQ4KR5QH-DlwLjqC5.js} +1 -1
- package/dashboard/dist/assets/{chunk-QZHKN3VN-Ci0C85q_.js → chunk-QZHKN3VN-DBEYrRqx.js} +1 -1
- package/dashboard/dist/assets/{chunk-WL4C6EOR-VVhAMMYU.js → chunk-WL4C6EOR-BQyYCp1z.js} +1 -1
- package/dashboard/dist/assets/classDiagram-VBA2DB6C-BW_esmsF.js +1 -0
- package/dashboard/dist/assets/classDiagram-v2-RAHNMMFH-BW_esmsF.js +1 -0
- package/dashboard/dist/assets/clone-y13L00zF.js +1 -0
- package/dashboard/dist/assets/{cose-bilkent-S5V4N54A-CO9uwgYO.js → cose-bilkent-S5V4N54A-CAYzelqd.js} +1 -1
- package/dashboard/dist/assets/{dagre-KLK3FWXG-bwLLXcL4.js → dagre-KLK3FWXG-xpTJb8E7.js} +1 -1
- package/dashboard/dist/assets/{diagram-E7M64L7V-RuS5R6V1.js → diagram-E7M64L7V-DRDjskHd.js} +1 -1
- package/dashboard/dist/assets/{diagram-IFDJBPK2-BQDJAHQd.js → diagram-IFDJBPK2-B1r1ZXm3.js} +1 -1
- package/dashboard/dist/assets/{diagram-P4PSJMXO-yLEsgzE5.js → diagram-P4PSJMXO-BeE6-ZUH.js} +1 -1
- package/dashboard/dist/assets/{erDiagram-INFDFZHY-na6dUhY0.js → erDiagram-INFDFZHY-BG6KKBm1.js} +1 -1
- package/dashboard/dist/assets/{flowDiagram-PKNHOUZH-BIcrzwJR.js → flowDiagram-PKNHOUZH-CSFv3RpZ.js} +1 -1
- package/dashboard/dist/assets/{ganttDiagram-A5KZAMGK-DHWRJn-D.js → ganttDiagram-A5KZAMGK-Off4zK-k.js} +1 -1
- package/dashboard/dist/assets/{gitGraphDiagram-K3NZZRJ6-LGxDjL71.js → gitGraphDiagram-K3NZZRJ6-Msv_4mYB.js} +1 -1
- package/dashboard/dist/assets/{graph-BUqNu277.js → graph-rPPnNEMq.js} +1 -1
- package/dashboard/dist/assets/index-Bu6ma6my.css +1 -0
- package/dashboard/dist/assets/{index-D-fepllQ.js → index-D_uE8gHg.js} +87 -87
- package/dashboard/dist/assets/{infoDiagram-LFFYTUFH-DidoA2hb.js → infoDiagram-LFFYTUFH-CeBirxFd.js} +1 -1
- package/dashboard/dist/assets/{ishikawaDiagram-PHBUUO56-CdlZkbhV.js → ishikawaDiagram-PHBUUO56-BZ_w8PcD.js} +1 -1
- package/dashboard/dist/assets/{journeyDiagram-4ABVD52K-luhcz_gn.js → journeyDiagram-4ABVD52K-3FqMPEfs.js} +1 -1
- package/dashboard/dist/assets/{kanban-definition-K7BYSVSG-Coidw9XE.js → kanban-definition-K7BYSVSG-BlCmFkEx.js} +1 -1
- package/dashboard/dist/assets/{layout-_aBAAleE.js → layout-C50nYMhx.js} +1 -1
- package/dashboard/dist/assets/{linear-D8mFnDSx.js → linear-D2Cjmh1X.js} +1 -1
- package/dashboard/dist/assets/{mermaid.core-BpP2keU-.js → mermaid.core-BXFuNYa4.js} +4 -4
- package/dashboard/dist/assets/{mindmap-definition-YRQLILUH-LjvrTe2z.js → mindmap-definition-YRQLILUH-CowIfM07.js} +1 -1
- package/dashboard/dist/assets/{pieDiagram-SKSYHLDU-CkA2iU6e.js → pieDiagram-SKSYHLDU-DG5IQcKl.js} +1 -1
- package/dashboard/dist/assets/{quadrantDiagram-337W2JSQ-BRmhKHQG.js → quadrantDiagram-337W2JSQ-DKD46CSX.js} +1 -1
- package/dashboard/dist/assets/{requirementDiagram-Z7DCOOCP-BYCQ4uFX.js → requirementDiagram-Z7DCOOCP-Dd-qX6Ul.js} +1 -1
- package/dashboard/dist/assets/{sankeyDiagram-WA2Y5GQK-C8SVk50M.js → sankeyDiagram-WA2Y5GQK-CDAylULt.js} +1 -1
- package/dashboard/dist/assets/{sequenceDiagram-2WXFIKYE-CbA_2lnP.js → sequenceDiagram-2WXFIKYE-D-5Jq5jy.js} +1 -1
- package/dashboard/dist/assets/{stateDiagram-RAJIS63D-D6ZtjAHE.js → stateDiagram-RAJIS63D-BmBOa8i0.js} +1 -1
- package/dashboard/dist/assets/stateDiagram-v2-FVOUBMTO-Dsk6o9iT.js +1 -0
- package/dashboard/dist/assets/{timeline-definition-YZTLITO2-B2Uf-emK.js → timeline-definition-YZTLITO2-C9Sdg7du.js} +1 -1
- package/dashboard/dist/assets/{treemap-KZPCXAKY-CYnFKsuJ.js → treemap-KZPCXAKY-D-PDxUdK.js} +1 -1
- package/dashboard/dist/assets/{vennDiagram-LZ73GAT5-Cnj0qiDO.js → vennDiagram-LZ73GAT5-CBO5f6MT.js} +1 -1
- package/dashboard/dist/assets/{xychartDiagram-JWTSCODW-BJy2mbL9.js → xychartDiagram-JWTSCODW-4xZrquqP.js} +1 -1
- package/dashboard/dist/index.html +2 -2
- package/dist/dashboard/server.js +601 -285
- package/dist/dashboard/server.js.map +1 -1
- package/dist/index.js +567 -147
- package/dist/index.js.map +1 -1
- package/examples/playbooks/assignment-creation.md +19 -0
- package/examples/playbooks/assignment-planning.md +28 -0
- package/package.json +1 -1
- package/platforms/claude-code/.orphaned_at +1 -0
- package/platforms/claude-code/agents/syntaur-expert.md +2 -2
- package/platforms/claude-code/commands/create-assignment/create-assignment.md +1 -1
- package/platforms/codex/agents/syntaur-operator.md +2 -2
- package/vendor/syntaur-skills/README.md +12 -0
- package/vendor/syntaur-skills/skills/create-assignment/SKILL.md +5 -4
- package/dashboard/dist/assets/channel-DqU_8tiy.js +0 -1
- package/dashboard/dist/assets/classDiagram-VBA2DB6C-D29Eeoe8.js +0 -1
- package/dashboard/dist/assets/classDiagram-v2-RAHNMMFH-D29Eeoe8.js +0 -1
- package/dashboard/dist/assets/clone-Bok8Q3Jj.js +0 -1
- package/dashboard/dist/assets/index-DnHyQJJH.css +0 -1
- package/dashboard/dist/assets/stateDiagram-v2-FVOUBMTO-DeYN4wV6.js +0 -1
- package/examples/playbooks/plan-versioning.md +0 -36
- package/examples/playbooks/read-before-plan.md +0 -30
package/dist/index.js
CHANGED
|
@@ -247,6 +247,7 @@ function parseAssignmentFull(fileContent) {
|
|
|
247
247
|
slug: getField(fm, "slug") ?? "",
|
|
248
248
|
title: getField(fm, "title") ?? "",
|
|
249
249
|
project: getField(fm, "project"),
|
|
250
|
+
workspaceGroup: getField(fm, "workspaceGroup"),
|
|
250
251
|
type: getField(fm, "type"),
|
|
251
252
|
status: getField(fm, "status") ?? "pending",
|
|
252
253
|
priority: getField(fm, "priority") ?? "medium",
|
|
@@ -414,8 +415,8 @@ var init_timestamp = __esm({
|
|
|
414
415
|
});
|
|
415
416
|
|
|
416
417
|
// src/utils/fs-migration.ts
|
|
417
|
-
import { readdir
|
|
418
|
-
import { resolve as
|
|
418
|
+
import { readdir, readFile, rename as rename2, writeFile as writeFile2 } from "fs/promises";
|
|
419
|
+
import { resolve as resolve2 } from "path";
|
|
419
420
|
async function migrateLegacyProjectFiles(projectsDir2) {
|
|
420
421
|
const result = {
|
|
421
422
|
renamedProjectFiles: [],
|
|
@@ -424,15 +425,15 @@ async function migrateLegacyProjectFiles(projectsDir2) {
|
|
|
424
425
|
if (!await fileExists(projectsDir2)) return result;
|
|
425
426
|
let entries;
|
|
426
427
|
try {
|
|
427
|
-
entries = await
|
|
428
|
+
entries = await readdir(projectsDir2, { withFileTypes: true });
|
|
428
429
|
} catch {
|
|
429
430
|
return result;
|
|
430
431
|
}
|
|
431
432
|
for (const entry of entries) {
|
|
432
433
|
if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
|
|
433
|
-
const projectDir =
|
|
434
|
-
const legacy =
|
|
435
|
-
const target =
|
|
434
|
+
const projectDir = resolve2(projectsDir2, entry.name);
|
|
435
|
+
const legacy = resolve2(projectDir, "mission.md");
|
|
436
|
+
const target = resolve2(projectDir, "project.md");
|
|
436
437
|
try {
|
|
437
438
|
if (await fileExists(legacy) && !await fileExists(target)) {
|
|
438
439
|
await rename2(legacy, target);
|
|
@@ -443,7 +444,7 @@ async function migrateLegacyProjectFiles(projectsDir2) {
|
|
|
443
444
|
}
|
|
444
445
|
for (const stale of ["agent.md", "claude.md"]) {
|
|
445
446
|
try {
|
|
446
|
-
if (await fileExists(
|
|
447
|
+
if (await fileExists(resolve2(projectDir, stale))) {
|
|
447
448
|
result.legacyExtras.push(`${entry.name}/${stale}`);
|
|
448
449
|
}
|
|
449
450
|
} catch {
|
|
@@ -461,7 +462,7 @@ async function migrateLegacyConfig(configPath) {
|
|
|
461
462
|
if (!await fileExists(configPath)) return result;
|
|
462
463
|
let content;
|
|
463
464
|
try {
|
|
464
|
-
content = await
|
|
465
|
+
content = await readFile(configPath, "utf-8");
|
|
465
466
|
} catch {
|
|
466
467
|
return result;
|
|
467
468
|
}
|
|
@@ -490,7 +491,7 @@ async function migrateLegacyConfig(configPath) {
|
|
|
490
491
|
const projectLineRe = /^\s*defaultProjectDir\s*:\s*(.*)$/m;
|
|
491
492
|
const projectLineMatch = newFmBlock.match(projectLineRe);
|
|
492
493
|
const projectsDirRaw = projectLineMatch ? projectLineMatch[1].trim().replace(/^['"]|['"]$/g, "") : missionValue;
|
|
493
|
-
const expand = (p) => p.startsWith("~") ?
|
|
494
|
+
const expand = (p) => p.startsWith("~") ? resolve2(process.env.HOME ?? "/", p.slice(p.startsWith("~/") ? 2 : 1)) : p;
|
|
494
495
|
let resolvedProjectsDir = projectsDirRaw ? expand(projectsDirRaw) : null;
|
|
495
496
|
if (resolvedProjectsDir && resolvedProjectsDir.endsWith("/missions")) {
|
|
496
497
|
const siblingProjectsDir = resolvedProjectsDir.replace(/\/missions$/, "/projects");
|
|
@@ -549,8 +550,29 @@ var init_fs_migration = __esm({
|
|
|
549
550
|
});
|
|
550
551
|
|
|
551
552
|
// src/utils/config.ts
|
|
552
|
-
import { readFile as
|
|
553
|
-
import { resolve as
|
|
553
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
554
|
+
import { resolve as resolve3, isAbsolute } from "path";
|
|
555
|
+
function cloneDefaultConfig() {
|
|
556
|
+
return {
|
|
557
|
+
...DEFAULT_CONFIG,
|
|
558
|
+
onboarding: { ...DEFAULT_CONFIG.onboarding },
|
|
559
|
+
agentDefaults: { ...DEFAULT_CONFIG.agentDefaults },
|
|
560
|
+
integrations: { ...DEFAULT_CONFIG.integrations },
|
|
561
|
+
backup: DEFAULT_CONFIG.backup ? { ...DEFAULT_CONFIG.backup } : null,
|
|
562
|
+
statuses: DEFAULT_CONFIG.statuses ? {
|
|
563
|
+
statuses: DEFAULT_CONFIG.statuses.statuses.map((s) => ({ ...s })),
|
|
564
|
+
order: [...DEFAULT_CONFIG.statuses.order],
|
|
565
|
+
transitions: DEFAULT_CONFIG.statuses.transitions.map((t) => ({ ...t }))
|
|
566
|
+
} : null,
|
|
567
|
+
types: DEFAULT_CONFIG.types ? {
|
|
568
|
+
definitions: DEFAULT_CONFIG.types.definitions.map((d) => ({ ...d })),
|
|
569
|
+
default: DEFAULT_CONFIG.types.default
|
|
570
|
+
} : null,
|
|
571
|
+
playbooks: {
|
|
572
|
+
disabled: [...DEFAULT_CONFIG.playbooks.disabled]
|
|
573
|
+
}
|
|
574
|
+
};
|
|
575
|
+
}
|
|
554
576
|
function parseFrontmatter(content) {
|
|
555
577
|
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
556
578
|
if (!match) return {};
|
|
@@ -722,6 +744,82 @@ function serializeBackupConfig(backup) {
|
|
|
722
744
|
lines.push(` lastRestore: ${backup.lastRestore ?? "null"}`);
|
|
723
745
|
return lines.join("\n");
|
|
724
746
|
}
|
|
747
|
+
function serializePlaybooksConfig(playbooks) {
|
|
748
|
+
if (!playbooks.disabled || playbooks.disabled.length === 0) {
|
|
749
|
+
return null;
|
|
750
|
+
}
|
|
751
|
+
const lines = ["playbooks:", " disabled:"];
|
|
752
|
+
for (const slug of playbooks.disabled) {
|
|
753
|
+
lines.push(` - ${slug}`);
|
|
754
|
+
}
|
|
755
|
+
return lines.join("\n");
|
|
756
|
+
}
|
|
757
|
+
function parsePlaybooksConfig(fmBlock) {
|
|
758
|
+
const blockStart = fmBlock.match(/^playbooks:\s*$/m);
|
|
759
|
+
if (!blockStart) {
|
|
760
|
+
return { disabled: [] };
|
|
761
|
+
}
|
|
762
|
+
const startIdx = fmBlock.indexOf(blockStart[0]) + blockStart[0].length;
|
|
763
|
+
const remaining = fmBlock.slice(startIdx).split("\n");
|
|
764
|
+
const disabled = [];
|
|
765
|
+
let currentSection = null;
|
|
766
|
+
for (const line of remaining) {
|
|
767
|
+
const trimmed = line.trimStart();
|
|
768
|
+
const indent = line.length - trimmed.length;
|
|
769
|
+
if (indent === 0 && trimmed.length > 0) break;
|
|
770
|
+
if (trimmed === "") continue;
|
|
771
|
+
if (indent === 2 && trimmed.startsWith("disabled:")) {
|
|
772
|
+
currentSection = "disabled";
|
|
773
|
+
const afterColon = trimmed.slice("disabled:".length).trim();
|
|
774
|
+
if (afterColon === "[]" || afterColon === "") {
|
|
775
|
+
continue;
|
|
776
|
+
}
|
|
777
|
+
continue;
|
|
778
|
+
}
|
|
779
|
+
if (currentSection === "disabled" && indent >= 4 && trimmed.startsWith("- ")) {
|
|
780
|
+
const raw = trimmed.slice(2).trim().replace(/^["']|["']$/g, "");
|
|
781
|
+
if (raw.length === 0) continue;
|
|
782
|
+
if (/\s/.test(raw)) {
|
|
783
|
+
console.warn(`Warning: config.md playbooks.disabled entry "${raw}" contains whitespace, ignoring`);
|
|
784
|
+
continue;
|
|
785
|
+
}
|
|
786
|
+
disabled.push(raw);
|
|
787
|
+
continue;
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
return { disabled };
|
|
791
|
+
}
|
|
792
|
+
async function updatePlaybooksConfig(playbooks) {
|
|
793
|
+
const configPath = resolve3(syntaurRoot(), "config.md");
|
|
794
|
+
const current = (await readConfig()).playbooks;
|
|
795
|
+
const nextPlaybooks = {
|
|
796
|
+
disabled: Array.from(new Set(playbooks.disabled ?? current.disabled))
|
|
797
|
+
};
|
|
798
|
+
const playbooksBlock = serializePlaybooksConfig(nextPlaybooks);
|
|
799
|
+
const existing = await fileExists(configPath) ? await readFile2(configPath, "utf-8") : renderConfig({ defaultProjectDir: defaultProjectDir() });
|
|
800
|
+
const fmMatch = existing.match(/^(---\n)([\s\S]*?)\n(---)/);
|
|
801
|
+
if (!fmMatch) {
|
|
802
|
+
const bodyBlock = playbooksBlock ? `${playbooksBlock}
|
|
803
|
+
` : "";
|
|
804
|
+
const content = `---
|
|
805
|
+
version: "2.0"
|
|
806
|
+
defaultProjectDir: ${defaultProjectDir()}
|
|
807
|
+
${bodyBlock}---
|
|
808
|
+
${existing}`;
|
|
809
|
+
await writeFileForce(configPath, content);
|
|
810
|
+
return;
|
|
811
|
+
}
|
|
812
|
+
const fmBlock = fmMatch[2];
|
|
813
|
+
const afterFrontmatter = existing.slice(fmMatch[0].length);
|
|
814
|
+
const cleanedFm = stripTopLevelBlock(fmBlock, "playbooks");
|
|
815
|
+
const newFm = playbooksBlock ? `${cleanedFm}
|
|
816
|
+
${playbooksBlock}`.replace(/^\n+/, "") : cleanedFm;
|
|
817
|
+
const normalizedFm = newFm.replace(/\n+$/, "");
|
|
818
|
+
const newContent = `---
|
|
819
|
+
${normalizedFm}
|
|
820
|
+
---${afterFrontmatter}`;
|
|
821
|
+
await writeFileForce(configPath, newContent);
|
|
822
|
+
}
|
|
725
823
|
function stripTopLevelBlock(fmBlock, key) {
|
|
726
824
|
const blockStart = fmBlock.match(new RegExp(`^${key}:\\s*$`, "m"));
|
|
727
825
|
if (!blockStart) {
|
|
@@ -756,10 +854,10 @@ function parseOptionalAbsolutePath(value, fieldName) {
|
|
|
756
854
|
);
|
|
757
855
|
return null;
|
|
758
856
|
}
|
|
759
|
-
return
|
|
857
|
+
return resolve3(expanded);
|
|
760
858
|
}
|
|
761
859
|
async function writeStatusConfig(statuses) {
|
|
762
|
-
const configPath =
|
|
860
|
+
const configPath = resolve3(syntaurRoot(), "config.md");
|
|
763
861
|
const statusBlock = serializeStatusConfig(statuses);
|
|
764
862
|
if (!await fileExists(configPath)) {
|
|
765
863
|
const content = `---
|
|
@@ -771,7 +869,7 @@ ${statusBlock}
|
|
|
771
869
|
await writeFileForce(configPath, content);
|
|
772
870
|
return;
|
|
773
871
|
}
|
|
774
|
-
const existing = await
|
|
872
|
+
const existing = await readFile2(configPath, "utf-8");
|
|
775
873
|
const fmMatch = existing.match(/^(---\n)([\s\S]*?)\n(---)/);
|
|
776
874
|
if (!fmMatch) {
|
|
777
875
|
const content = `---
|
|
@@ -813,9 +911,9 @@ ${statusBlock}
|
|
|
813
911
|
await writeFileForce(configPath, newContent);
|
|
814
912
|
}
|
|
815
913
|
async function deleteStatusConfig() {
|
|
816
|
-
const configPath =
|
|
914
|
+
const configPath = resolve3(syntaurRoot(), "config.md");
|
|
817
915
|
if (!await fileExists(configPath)) return;
|
|
818
|
-
const existing = await
|
|
916
|
+
const existing = await readFile2(configPath, "utf-8");
|
|
819
917
|
const fmMatch = existing.match(/^(---\n)([\s\S]*?)\n(---)/);
|
|
820
918
|
if (!fmMatch) return;
|
|
821
919
|
const fmBlock = fmMatch[2];
|
|
@@ -827,13 +925,13 @@ ${cleanedFm}
|
|
|
827
925
|
await writeFileForce(configPath, newContent);
|
|
828
926
|
}
|
|
829
927
|
async function updateIntegrationConfig(integrations) {
|
|
830
|
-
const configPath =
|
|
928
|
+
const configPath = resolve3(syntaurRoot(), "config.md");
|
|
831
929
|
const nextIntegrations = {
|
|
832
930
|
...(await readConfig()).integrations,
|
|
833
931
|
...integrations
|
|
834
932
|
};
|
|
835
933
|
const integrationBlock = serializeIntegrationConfig(nextIntegrations);
|
|
836
|
-
const existing = await fileExists(configPath) ? await
|
|
934
|
+
const existing = await fileExists(configPath) ? await readFile2(configPath, "utf-8") : renderConfig({ defaultProjectDir: defaultProjectDir() });
|
|
837
935
|
const fmMatch = existing.match(/^(---\n)([\s\S]*?)\n(---)/);
|
|
838
936
|
if (!fmMatch) {
|
|
839
937
|
const content = `---
|
|
@@ -857,13 +955,13 @@ ${normalizedFm}
|
|
|
857
955
|
await writeFileForce(configPath, newContent);
|
|
858
956
|
}
|
|
859
957
|
async function updateOnboardingConfig(onboarding) {
|
|
860
|
-
const configPath =
|
|
958
|
+
const configPath = resolve3(syntaurRoot(), "config.md");
|
|
861
959
|
const nextOnboarding = {
|
|
862
960
|
...(await readConfig()).onboarding,
|
|
863
961
|
...onboarding
|
|
864
962
|
};
|
|
865
963
|
const onboardingBlock = serializeOnboardingConfig(nextOnboarding);
|
|
866
|
-
const existing = await fileExists(configPath) ? await
|
|
964
|
+
const existing = await fileExists(configPath) ? await readFile2(configPath, "utf-8") : renderConfig({ defaultProjectDir: defaultProjectDir() });
|
|
867
965
|
const fmMatch = existing.match(/^(---\n)([\s\S]*?)\n(---)/);
|
|
868
966
|
if (!fmMatch) {
|
|
869
967
|
const content = `---
|
|
@@ -887,7 +985,7 @@ ${normalizedFm}
|
|
|
887
985
|
await writeFileForce(configPath, newContent);
|
|
888
986
|
}
|
|
889
987
|
async function updateBackupConfig(backup) {
|
|
890
|
-
const configPath =
|
|
988
|
+
const configPath = resolve3(syntaurRoot(), "config.md");
|
|
891
989
|
const current = (await readConfig()).backup;
|
|
892
990
|
const nextBackup = {
|
|
893
991
|
repo: current?.repo ?? null,
|
|
@@ -897,7 +995,7 @@ async function updateBackupConfig(backup) {
|
|
|
897
995
|
...backup
|
|
898
996
|
};
|
|
899
997
|
const backupBlock = serializeBackupConfig(nextBackup);
|
|
900
|
-
const existing = await fileExists(configPath) ? await
|
|
998
|
+
const existing = await fileExists(configPath) ? await readFile2(configPath, "utf-8") : renderConfig({ defaultProjectDir: defaultProjectDir() });
|
|
901
999
|
const fmMatch = existing.match(/^(---\n)([\s\S]*?)\n(---)/);
|
|
902
1000
|
if (!fmMatch) {
|
|
903
1001
|
const content = `---
|
|
@@ -921,19 +1019,19 @@ ${normalizedFm}
|
|
|
921
1019
|
await writeFileForce(configPath, newContent);
|
|
922
1020
|
}
|
|
923
1021
|
async function readConfig() {
|
|
924
|
-
const configPath =
|
|
1022
|
+
const configPath = resolve3(syntaurRoot(), "config.md");
|
|
925
1023
|
if (!await fileExists(configPath)) {
|
|
926
|
-
return
|
|
1024
|
+
return cloneDefaultConfig();
|
|
927
1025
|
}
|
|
928
1026
|
if (!migratedConfigPaths.has(configPath)) {
|
|
929
1027
|
migratedConfigPaths.add(configPath);
|
|
930
1028
|
await migrateLegacyConfig(configPath);
|
|
931
1029
|
}
|
|
932
|
-
const content = await
|
|
1030
|
+
const content = await readFile2(configPath, "utf-8");
|
|
933
1031
|
const fm = parseFrontmatter(content);
|
|
934
1032
|
if (Object.keys(fm).length === 0) {
|
|
935
1033
|
console.warn("Warning: ~/.syntaur/config.md has malformed frontmatter, using defaults");
|
|
936
|
-
return
|
|
1034
|
+
return cloneDefaultConfig();
|
|
937
1035
|
}
|
|
938
1036
|
let projectDir = fm["defaultProjectDir"] ? expandHome(String(fm["defaultProjectDir"])) : DEFAULT_CONFIG.defaultProjectDir;
|
|
939
1037
|
if (!isAbsolute(projectDir)) {
|
|
@@ -942,6 +1040,7 @@ async function readConfig() {
|
|
|
942
1040
|
);
|
|
943
1041
|
projectDir = DEFAULT_CONFIG.defaultProjectDir;
|
|
944
1042
|
}
|
|
1043
|
+
const fmBlock = content.match(/^---\n([\s\S]*?)\n---/)?.[1] ?? "";
|
|
945
1044
|
return {
|
|
946
1045
|
version: fm["version"] || DEFAULT_CONFIG.version,
|
|
947
1046
|
defaultProjectDir: projectDir,
|
|
@@ -973,7 +1072,8 @@ async function readConfig() {
|
|
|
973
1072
|
lastRestore: fm["backup.lastRestore"] && fm["backup.lastRestore"] !== "null" ? fm["backup.lastRestore"] : null
|
|
974
1073
|
} : null,
|
|
975
1074
|
statuses: parseStatusConfig(content),
|
|
976
|
-
types: null
|
|
1075
|
+
types: null,
|
|
1076
|
+
playbooks: parsePlaybooksConfig(fmBlock)
|
|
977
1077
|
};
|
|
978
1078
|
}
|
|
979
1079
|
var DEFAULT_CONFIG, migratedConfigPaths;
|
|
@@ -1001,12 +1101,119 @@ var init_config2 = __esm({
|
|
|
1001
1101
|
},
|
|
1002
1102
|
backup: null,
|
|
1003
1103
|
statuses: null,
|
|
1004
|
-
types: null
|
|
1104
|
+
types: null,
|
|
1105
|
+
playbooks: {
|
|
1106
|
+
disabled: []
|
|
1107
|
+
}
|
|
1005
1108
|
};
|
|
1006
1109
|
migratedConfigPaths = /* @__PURE__ */ new Set();
|
|
1007
1110
|
}
|
|
1008
1111
|
});
|
|
1009
1112
|
|
|
1113
|
+
// src/utils/playbooks.ts
|
|
1114
|
+
import { resolve as resolve4 } from "path";
|
|
1115
|
+
import { readdir as readdir2, readFile as readFile3 } from "fs/promises";
|
|
1116
|
+
function isVisiblePlaybookFile(name, isFile) {
|
|
1117
|
+
return isFile && name.endsWith(".md") && !name.startsWith("_") && name !== "manifest.md";
|
|
1118
|
+
}
|
|
1119
|
+
async function resolvePlaybookSlug(playbooksDir3, slug) {
|
|
1120
|
+
if (!await fileExists(playbooksDir3)) return null;
|
|
1121
|
+
const entries = await readdir2(playbooksDir3, { withFileTypes: true });
|
|
1122
|
+
let filenameStemFallback = null;
|
|
1123
|
+
for (const entry of entries) {
|
|
1124
|
+
if (!isVisiblePlaybookFile(entry.name, entry.isFile())) continue;
|
|
1125
|
+
const filePath = resolve4(playbooksDir3, entry.name);
|
|
1126
|
+
const raw = await readFile3(filePath, "utf-8");
|
|
1127
|
+
const parsed = parsePlaybook(raw);
|
|
1128
|
+
const canonical = parsed.slug || entry.name.replace(/\.md$/, "");
|
|
1129
|
+
if (canonical === slug) {
|
|
1130
|
+
return { filename: entry.name, slug: canonical, parsed };
|
|
1131
|
+
}
|
|
1132
|
+
if (!parsed.slug && entry.name.replace(/\.md$/, "") === slug) {
|
|
1133
|
+
filenameStemFallback = { filename: entry.name, slug: canonical, parsed };
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
return filenameStemFallback;
|
|
1137
|
+
}
|
|
1138
|
+
async function setPlaybookEnabled(playbooksDir3, slug, enabled) {
|
|
1139
|
+
const resolved = await resolvePlaybookSlug(playbooksDir3, slug);
|
|
1140
|
+
if (!resolved) {
|
|
1141
|
+
throw new Error(`Playbook "${slug}" not found in ${playbooksDir3}`);
|
|
1142
|
+
}
|
|
1143
|
+
const config = await readConfig();
|
|
1144
|
+
const disabledSet = new Set(config.playbooks.disabled);
|
|
1145
|
+
const wasDisabled = disabledSet.has(resolved.slug);
|
|
1146
|
+
const shouldBeDisabled = !enabled;
|
|
1147
|
+
if (wasDisabled === shouldBeDisabled) {
|
|
1148
|
+
return { slug: resolved.slug, enabled, changed: false };
|
|
1149
|
+
}
|
|
1150
|
+
if (shouldBeDisabled) {
|
|
1151
|
+
disabledSet.add(resolved.slug);
|
|
1152
|
+
} else {
|
|
1153
|
+
disabledSet.delete(resolved.slug);
|
|
1154
|
+
}
|
|
1155
|
+
await updatePlaybooksConfig({ disabled: Array.from(disabledSet).sort() });
|
|
1156
|
+
await rebuildPlaybookManifest(playbooksDir3);
|
|
1157
|
+
return { slug: resolved.slug, enabled, changed: true };
|
|
1158
|
+
}
|
|
1159
|
+
async function removeFromDisabledList(slug) {
|
|
1160
|
+
const config = await readConfig();
|
|
1161
|
+
if (!config.playbooks.disabled.includes(slug)) return;
|
|
1162
|
+
await updatePlaybooksConfig({
|
|
1163
|
+
disabled: config.playbooks.disabled.filter((s) => s !== slug)
|
|
1164
|
+
});
|
|
1165
|
+
}
|
|
1166
|
+
async function rebuildPlaybookManifest(playbooksDir3) {
|
|
1167
|
+
if (!await fileExists(playbooksDir3)) return;
|
|
1168
|
+
const config = await readConfig();
|
|
1169
|
+
const disabledSet = new Set(config.playbooks.disabled);
|
|
1170
|
+
const entries = await readdir2(playbooksDir3, { withFileTypes: true });
|
|
1171
|
+
const rows = [];
|
|
1172
|
+
for (const entry of entries) {
|
|
1173
|
+
if (!isVisiblePlaybookFile(entry.name, entry.isFile())) continue;
|
|
1174
|
+
const raw = await readFile3(resolve4(playbooksDir3, entry.name), "utf-8");
|
|
1175
|
+
const parsed = parsePlaybook(raw);
|
|
1176
|
+
const slug = parsed.slug || entry.name.replace(/\.md$/, "");
|
|
1177
|
+
if (disabledSet.has(slug)) continue;
|
|
1178
|
+
rows.push({
|
|
1179
|
+
name: parsed.name || slug,
|
|
1180
|
+
slug,
|
|
1181
|
+
description: parsed.description,
|
|
1182
|
+
whenToUse: parsed.whenToUse
|
|
1183
|
+
});
|
|
1184
|
+
}
|
|
1185
|
+
rows.sort((a, b) => a.name.localeCompare(b.name));
|
|
1186
|
+
const timestamp = nowTimestamp();
|
|
1187
|
+
const lines = [
|
|
1188
|
+
"---",
|
|
1189
|
+
`generated: "${timestamp}"`,
|
|
1190
|
+
`total: ${rows.length}`,
|
|
1191
|
+
"---",
|
|
1192
|
+
"",
|
|
1193
|
+
"# Playbooks",
|
|
1194
|
+
"",
|
|
1195
|
+
"Behavioral rules for AI agents. Read and follow all playbooks before starting work.",
|
|
1196
|
+
""
|
|
1197
|
+
];
|
|
1198
|
+
for (const row of rows) {
|
|
1199
|
+
lines.push(`- **[${row.name}](${row.slug}.md)** \u2014 ${row.description}`);
|
|
1200
|
+
if (row.whenToUse) {
|
|
1201
|
+
lines.push(` _When to use: ${row.whenToUse}_`);
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
lines.push("");
|
|
1205
|
+
await writeFileForce(resolve4(playbooksDir3, "manifest.md"), lines.join("\n"));
|
|
1206
|
+
}
|
|
1207
|
+
var init_playbooks = __esm({
|
|
1208
|
+
"src/utils/playbooks.ts"() {
|
|
1209
|
+
"use strict";
|
|
1210
|
+
init_fs();
|
|
1211
|
+
init_parser();
|
|
1212
|
+
init_timestamp();
|
|
1213
|
+
init_config2();
|
|
1214
|
+
}
|
|
1215
|
+
});
|
|
1216
|
+
|
|
1010
1217
|
// src/lifecycle/types.ts
|
|
1011
1218
|
var DEFAULT_STATUSES, DEFAULT_TERMINAL_STATUSES;
|
|
1012
1219
|
var init_types = __esm({
|
|
@@ -1409,12 +1616,20 @@ async function resolveAssignmentById(projectsDir2, assignmentsDir2, id) {
|
|
|
1409
1616
|
const standaloneDir = resolve9(assignmentsDir2, id);
|
|
1410
1617
|
const standalonePath = resolve9(standaloneDir, "assignment.md");
|
|
1411
1618
|
if (await fileExists(standalonePath)) {
|
|
1619
|
+
let workspaceGroup = null;
|
|
1620
|
+
try {
|
|
1621
|
+
const content = await readFile6(standalonePath, "utf-8");
|
|
1622
|
+
const [fm] = extractFrontmatter(content);
|
|
1623
|
+
workspaceGroup = getField(fm, "workspaceGroup");
|
|
1624
|
+
} catch {
|
|
1625
|
+
}
|
|
1412
1626
|
standaloneMatch = {
|
|
1413
1627
|
assignmentDir: standaloneDir,
|
|
1414
1628
|
projectSlug: null,
|
|
1415
1629
|
assignmentSlug: id,
|
|
1416
1630
|
id,
|
|
1417
|
-
standalone: true
|
|
1631
|
+
standalone: true,
|
|
1632
|
+
workspaceGroup
|
|
1418
1633
|
};
|
|
1419
1634
|
}
|
|
1420
1635
|
if (await fileExists(projectsDir2)) {
|
|
@@ -1440,7 +1655,8 @@ async function resolveAssignmentById(projectsDir2, assignmentsDir2, id) {
|
|
|
1440
1655
|
projectSlug: p.name,
|
|
1441
1656
|
assignmentSlug: a.name,
|
|
1442
1657
|
id,
|
|
1443
|
-
standalone: false
|
|
1658
|
+
standalone: false,
|
|
1659
|
+
workspaceGroup: null
|
|
1444
1660
|
};
|
|
1445
1661
|
break;
|
|
1446
1662
|
}
|
|
@@ -1816,8 +2032,18 @@ var init_help = __esm({
|
|
|
1816
2032
|
},
|
|
1817
2033
|
{
|
|
1818
2034
|
command: "syntaur list-playbooks",
|
|
1819
|
-
description: "List
|
|
1820
|
-
example: "syntaur list-playbooks"
|
|
2035
|
+
description: "List playbooks in the Syntaur home directory. Disabled playbooks are excluded by default; pass --all to include them with a (disabled) tag.",
|
|
2036
|
+
example: "syntaur list-playbooks --all"
|
|
2037
|
+
},
|
|
2038
|
+
{
|
|
2039
|
+
command: "syntaur enable-playbook",
|
|
2040
|
+
description: "Re-enable a previously-disabled playbook so agents load it again. Updates config.md and rebuilds manifest.md.",
|
|
2041
|
+
example: "syntaur enable-playbook commit-discipline"
|
|
2042
|
+
},
|
|
2043
|
+
{
|
|
2044
|
+
command: "syntaur disable-playbook",
|
|
2045
|
+
description: "Disable a playbook so agents no longer list or load it. Playbook file is untouched; state is tracked in config.md.",
|
|
2046
|
+
example: "syntaur disable-playbook commit-discipline"
|
|
1821
2047
|
}
|
|
1822
2048
|
];
|
|
1823
2049
|
WORKFLOW = [
|
|
@@ -2541,10 +2767,11 @@ async function writeWorkspaceRegistry(projectsDir2, workspaces) {
|
|
|
2541
2767
|
const registryPath = resolve12(dirname3(projectsDir2), "workspaces.json");
|
|
2542
2768
|
await writeFile3(registryPath, JSON.stringify(workspaces, null, 2) + "\n", "utf-8");
|
|
2543
2769
|
}
|
|
2544
|
-
async function listWorkspaces(projectsDir2) {
|
|
2545
|
-
const [projectRecords, registered] = await Promise.all([
|
|
2770
|
+
async function listWorkspaces(projectsDir2, assignmentsDir2) {
|
|
2771
|
+
const [projectRecords, registered, standaloneRecords] = await Promise.all([
|
|
2546
2772
|
listProjectRecords(projectsDir2),
|
|
2547
|
-
readWorkspaceRegistry(projectsDir2)
|
|
2773
|
+
readWorkspaceRegistry(projectsDir2),
|
|
2774
|
+
listStandaloneRecords(assignmentsDir2)
|
|
2548
2775
|
]);
|
|
2549
2776
|
const workspaceSet = new Set(registered);
|
|
2550
2777
|
let hasUngrouped = false;
|
|
@@ -2555,6 +2782,13 @@ async function listWorkspaces(projectsDir2) {
|
|
|
2555
2782
|
hasUngrouped = true;
|
|
2556
2783
|
}
|
|
2557
2784
|
}
|
|
2785
|
+
for (const sr of standaloneRecords) {
|
|
2786
|
+
if (sr.record.workspaceGroup) {
|
|
2787
|
+
workspaceSet.add(sr.record.workspaceGroup);
|
|
2788
|
+
} else {
|
|
2789
|
+
hasUngrouped = true;
|
|
2790
|
+
}
|
|
2791
|
+
}
|
|
2558
2792
|
const workspaces = Array.from(workspaceSet).sort();
|
|
2559
2793
|
return { workspaces, hasUngrouped };
|
|
2560
2794
|
}
|
|
@@ -2703,7 +2937,7 @@ async function toStandaloneBoardItem(sr) {
|
|
|
2703
2937
|
projectSlug: null,
|
|
2704
2938
|
projectTitle: null,
|
|
2705
2939
|
blockedReason: sr.record.blockedReason,
|
|
2706
|
-
projectWorkspace: null,
|
|
2940
|
+
projectWorkspace: sr.record.workspaceGroup ?? null,
|
|
2707
2941
|
availableTransitions: await getStandaloneAvailableTransitions(sr.record)
|
|
2708
2942
|
};
|
|
2709
2943
|
}
|
|
@@ -3644,6 +3878,8 @@ function getEditableDocumentTitle(documentType, projectSlug, assignmentSlug) {
|
|
|
3644
3878
|
}
|
|
3645
3879
|
async function listPlaybooks(playbooksDir3) {
|
|
3646
3880
|
if (!await fileExists(playbooksDir3)) return [];
|
|
3881
|
+
const config = await readConfig();
|
|
3882
|
+
const disabledSet = new Set(config.playbooks.disabled);
|
|
3647
3883
|
const entries = await readdir7(playbooksDir3, { withFileTypes: true });
|
|
3648
3884
|
const playbooks = [];
|
|
3649
3885
|
for (const entry of entries) {
|
|
@@ -3659,25 +3895,28 @@ async function listPlaybooks(playbooksDir3) {
|
|
|
3659
3895
|
whenToUse: parsed.whenToUse,
|
|
3660
3896
|
tags: parsed.tags,
|
|
3661
3897
|
created: parsed.created,
|
|
3662
|
-
updated: parsed.updated
|
|
3898
|
+
updated: parsed.updated,
|
|
3899
|
+
enabled: !disabledSet.has(slug)
|
|
3663
3900
|
});
|
|
3664
3901
|
}
|
|
3665
3902
|
return playbooks.sort((a, b) => (b.updated || b.created).localeCompare(a.updated || a.created));
|
|
3666
3903
|
}
|
|
3667
3904
|
async function getPlaybookDetail(playbooksDir3, slug) {
|
|
3668
|
-
const
|
|
3669
|
-
if (!
|
|
3670
|
-
const
|
|
3671
|
-
const
|
|
3905
|
+
const resolved = await resolvePlaybookSlug(playbooksDir3, slug);
|
|
3906
|
+
if (!resolved) return null;
|
|
3907
|
+
const config = await readConfig();
|
|
3908
|
+
const enabled = !config.playbooks.disabled.includes(resolved.slug);
|
|
3909
|
+
const parsed = resolved.parsed;
|
|
3672
3910
|
return {
|
|
3673
|
-
slug:
|
|
3674
|
-
name: parsed.name || slug,
|
|
3911
|
+
slug: resolved.slug,
|
|
3912
|
+
name: parsed.name || resolved.slug,
|
|
3675
3913
|
description: parsed.description,
|
|
3676
3914
|
whenToUse: parsed.whenToUse,
|
|
3677
3915
|
tags: parsed.tags,
|
|
3678
3916
|
created: parsed.created,
|
|
3679
3917
|
updated: parsed.updated,
|
|
3680
|
-
body: parsed.body
|
|
3918
|
+
body: parsed.body,
|
|
3919
|
+
enabled
|
|
3681
3920
|
};
|
|
3682
3921
|
}
|
|
3683
3922
|
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;
|
|
@@ -3687,6 +3926,7 @@ var init_api = __esm({
|
|
|
3687
3926
|
init_lifecycle();
|
|
3688
3927
|
init_fs();
|
|
3689
3928
|
init_config2();
|
|
3929
|
+
init_playbooks();
|
|
3690
3930
|
init_fs_migration();
|
|
3691
3931
|
init_assignment_resolver();
|
|
3692
3932
|
init_parser();
|
|
@@ -4613,62 +4853,16 @@ import { Command as Command4 } from "commander";
|
|
|
4613
4853
|
init_paths();
|
|
4614
4854
|
init_fs();
|
|
4615
4855
|
init_config();
|
|
4616
|
-
|
|
4617
|
-
import {
|
|
4856
|
+
init_playbooks();
|
|
4857
|
+
import { resolve as resolve5, dirname as dirname2 } from "path";
|
|
4858
|
+
import { readdir as readdir3, readFile as readFile4 } from "fs/promises";
|
|
4618
4859
|
import { fileURLToPath } from "url";
|
|
4619
|
-
|
|
4620
|
-
// src/utils/playbooks.ts
|
|
4621
|
-
init_fs();
|
|
4622
|
-
init_parser();
|
|
4623
|
-
init_timestamp();
|
|
4624
|
-
import { resolve as resolve2 } from "path";
|
|
4625
|
-
import { readdir, readFile } from "fs/promises";
|
|
4626
|
-
async function rebuildPlaybookManifest(playbooksDir3) {
|
|
4627
|
-
if (!await fileExists(playbooksDir3)) return;
|
|
4628
|
-
const entries = await readdir(playbooksDir3, { withFileTypes: true });
|
|
4629
|
-
const rows = [];
|
|
4630
|
-
for (const entry of entries) {
|
|
4631
|
-
if (!entry.isFile() || !entry.name.endsWith(".md") || entry.name.startsWith("_") || entry.name === "manifest.md") continue;
|
|
4632
|
-
const raw = await readFile(resolve2(playbooksDir3, entry.name), "utf-8");
|
|
4633
|
-
const parsed = parsePlaybook(raw);
|
|
4634
|
-
const slug = parsed.slug || entry.name.replace(/\.md$/, "");
|
|
4635
|
-
rows.push({
|
|
4636
|
-
name: parsed.name || slug,
|
|
4637
|
-
slug,
|
|
4638
|
-
description: parsed.description,
|
|
4639
|
-
whenToUse: parsed.whenToUse
|
|
4640
|
-
});
|
|
4641
|
-
}
|
|
4642
|
-
rows.sort((a, b) => a.name.localeCompare(b.name));
|
|
4643
|
-
const timestamp = nowTimestamp();
|
|
4644
|
-
const lines = [
|
|
4645
|
-
"---",
|
|
4646
|
-
`generated: "${timestamp}"`,
|
|
4647
|
-
`total: ${rows.length}`,
|
|
4648
|
-
"---",
|
|
4649
|
-
"",
|
|
4650
|
-
"# Playbooks",
|
|
4651
|
-
"",
|
|
4652
|
-
"Behavioral rules for AI agents. Read and follow all playbooks before starting work.",
|
|
4653
|
-
""
|
|
4654
|
-
];
|
|
4655
|
-
for (const row of rows) {
|
|
4656
|
-
lines.push(`- **[${row.name}](${row.slug}.md)** \u2014 ${row.description}`);
|
|
4657
|
-
if (row.whenToUse) {
|
|
4658
|
-
lines.push(` _When to use: ${row.whenToUse}_`);
|
|
4659
|
-
}
|
|
4660
|
-
}
|
|
4661
|
-
lines.push("");
|
|
4662
|
-
await writeFileForce(resolve2(playbooksDir3, "manifest.md"), lines.join("\n"));
|
|
4663
|
-
}
|
|
4664
|
-
|
|
4665
|
-
// src/commands/init.ts
|
|
4666
4860
|
async function initCommand(options) {
|
|
4667
4861
|
const root = syntaurRoot();
|
|
4668
4862
|
const projectsDir2 = defaultProjectDir();
|
|
4669
4863
|
const standaloneAssignmentsDir = assignmentsDir();
|
|
4670
|
-
const configPath =
|
|
4671
|
-
const playbooksDir3 =
|
|
4864
|
+
const configPath = resolve5(root, "config.md");
|
|
4865
|
+
const playbooksDir3 = resolve5(root, "playbooks");
|
|
4672
4866
|
await ensureDir(root);
|
|
4673
4867
|
await ensureDir(projectsDir2);
|
|
4674
4868
|
await ensureDir(standaloneAssignmentsDir);
|
|
@@ -4704,16 +4898,16 @@ async function initCommand(options) {
|
|
|
4704
4898
|
}
|
|
4705
4899
|
async function seedDefaultPlaybooks(playbooksDir3) {
|
|
4706
4900
|
const __filename = fileURLToPath(import.meta.url);
|
|
4707
|
-
const packageRoot =
|
|
4708
|
-
const examplesDir =
|
|
4901
|
+
const packageRoot = resolve5(dirname2(__filename), "..");
|
|
4902
|
+
const examplesDir = resolve5(packageRoot, "examples", "playbooks");
|
|
4709
4903
|
if (!await fileExists(examplesDir)) return 0;
|
|
4710
|
-
const entries = await
|
|
4904
|
+
const entries = await readdir3(examplesDir, { withFileTypes: true });
|
|
4711
4905
|
let count = 0;
|
|
4712
4906
|
for (const entry of entries) {
|
|
4713
4907
|
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
4714
|
-
const targetPath =
|
|
4908
|
+
const targetPath = resolve5(playbooksDir3, entry.name);
|
|
4715
4909
|
if (await fileExists(targetPath)) continue;
|
|
4716
|
-
const content = await
|
|
4910
|
+
const content = await readFile4(resolve5(examplesDir, entry.name), "utf-8");
|
|
4717
4911
|
await writeFileSafe(targetPath, content);
|
|
4718
4912
|
count++;
|
|
4719
4913
|
}
|
|
@@ -4820,12 +5014,25 @@ function renderAssignment(params) {
|
|
|
4820
5014
|
const linksYaml = params.links.length === 0 ? "links: []" : `links:
|
|
4821
5015
|
- ${params.links.join("\n - ")}`;
|
|
4822
5016
|
const projectYaml = `project: ${params.project == null ? "null" : params.project}`;
|
|
5017
|
+
const workspaceGroupLine = params.workspaceGroup ? `
|
|
5018
|
+
workspaceGroup: ${params.workspaceGroup}` : "";
|
|
4823
5019
|
const typeYaml = `type: ${params.type ?? "feature"}`;
|
|
5020
|
+
const todosSection = params.includeTodos ? `## Todos
|
|
5021
|
+
|
|
5022
|
+
<!--
|
|
5023
|
+
Checklist of work items for this assignment. Items may be simple tasks
|
|
5024
|
+
or a markdown link to a plan file (e.g., "- [ ] Execute [plan](./plan.md)").
|
|
5025
|
+
When a plan is superseded by a new one, mark the old todo as:
|
|
5026
|
+
- [x] ~~Execute [old plan](./plan.md)~~ (superseded by plan-v2)
|
|
5027
|
+
Never delete superseded todos \u2014 preserve the history.
|
|
5028
|
+
-->
|
|
5029
|
+
|
|
5030
|
+
` : "";
|
|
4824
5031
|
return `---
|
|
4825
5032
|
id: ${params.id}
|
|
4826
5033
|
slug: ${params.slug}
|
|
4827
5034
|
title: ${safeTitle}
|
|
4828
|
-
${projectYaml}
|
|
5035
|
+
${projectYaml}${workspaceGroupLine}
|
|
4829
5036
|
${typeYaml}
|
|
4830
5037
|
status: pending
|
|
4831
5038
|
priority: ${params.priority}
|
|
@@ -4856,17 +5063,7 @@ tags: []
|
|
|
4856
5063
|
- [ ] <!-- criterion 2 -->
|
|
4857
5064
|
- [ ] <!-- criterion 3 -->
|
|
4858
5065
|
|
|
4859
|
-
##
|
|
4860
|
-
|
|
4861
|
-
<!--
|
|
4862
|
-
Checklist of work items for this assignment. Items may be simple tasks
|
|
4863
|
-
or a markdown link to a plan file (e.g., "- [ ] Execute [plan](./plan.md)").
|
|
4864
|
-
When a plan is superseded by a new one, mark the old todo as:
|
|
4865
|
-
- [x] ~~Execute [old plan](./plan.md)~~ (superseded by plan-v2)
|
|
4866
|
-
Never delete superseded todos \u2014 preserve the history.
|
|
4867
|
-
-->
|
|
4868
|
-
|
|
4869
|
-
## Context
|
|
5066
|
+
${todosSection}## Context
|
|
4870
5067
|
|
|
4871
5068
|
<!-- Links to relevant docs, code, or other assignments. -->
|
|
4872
5069
|
|
|
@@ -5552,6 +5749,14 @@ async function createAssignmentCommand(title, options) {
|
|
|
5552
5749
|
if (!title.trim()) {
|
|
5553
5750
|
throw new Error("Assignment title cannot be empty.");
|
|
5554
5751
|
}
|
|
5752
|
+
if (options.workspace && options.project) {
|
|
5753
|
+
throw new Error(
|
|
5754
|
+
"Cannot use --workspace with --project (projects already carry a workspace via project.workspace)."
|
|
5755
|
+
);
|
|
5756
|
+
}
|
|
5757
|
+
if (options.workspace && !options.oneOff) {
|
|
5758
|
+
throw new Error("--workspace requires --one-off.");
|
|
5759
|
+
}
|
|
5555
5760
|
if (!options.project && !options.oneOff) {
|
|
5556
5761
|
throw new Error(
|
|
5557
5762
|
"Either --project <slug> or --one-off is required."
|
|
@@ -5567,6 +5772,11 @@ async function createAssignmentCommand(title, options) {
|
|
|
5567
5772
|
`Invalid project slug "${options.project}". Slugs must be lowercase, hyphen-separated, with no special characters.`
|
|
5568
5773
|
);
|
|
5569
5774
|
}
|
|
5775
|
+
if (options.workspace && !isValidSlug(options.workspace)) {
|
|
5776
|
+
throw new Error(
|
|
5777
|
+
`Invalid workspace slug "${options.workspace}". Slugs must be lowercase, hyphen-separated, with no special characters.`
|
|
5778
|
+
);
|
|
5779
|
+
}
|
|
5570
5780
|
if (options.oneOff && options.dependsOn) {
|
|
5571
5781
|
throw new Error("Standalone assignments cannot have dependencies (--depends-on is not allowed with --one-off).");
|
|
5572
5782
|
}
|
|
@@ -5657,7 +5867,9 @@ Use --slug to specify a different slug.`
|
|
|
5657
5867
|
dependsOn,
|
|
5658
5868
|
links,
|
|
5659
5869
|
project: projectSlug,
|
|
5660
|
-
|
|
5870
|
+
workspaceGroup: options.workspace ?? null,
|
|
5871
|
+
type: options.type,
|
|
5872
|
+
includeTodos: options.withTodos === true
|
|
5661
5873
|
})
|
|
5662
5874
|
],
|
|
5663
5875
|
[
|
|
@@ -6434,7 +6646,15 @@ function createWriteRouter(projectsDir2, assignmentsDir2) {
|
|
|
6434
6646
|
});
|
|
6435
6647
|
res.json({ content });
|
|
6436
6648
|
});
|
|
6437
|
-
router.get("/api/templates/assignment", (
|
|
6649
|
+
router.get("/api/templates/assignment", (req, res) => {
|
|
6650
|
+
const standalone = req.query.standalone === "1";
|
|
6651
|
+
const workspaceParam = typeof req.query.workspace === "string" ? req.query.workspace : "";
|
|
6652
|
+
if (workspaceParam && !isValidSlug(workspaceParam)) {
|
|
6653
|
+
res.status(400).json({
|
|
6654
|
+
error: `Invalid workspace slug "${workspaceParam}". Slugs must be lowercase, hyphen-separated, with no special characters.`
|
|
6655
|
+
});
|
|
6656
|
+
return;
|
|
6657
|
+
}
|
|
6438
6658
|
const content = renderAssignment({
|
|
6439
6659
|
id: generateId(),
|
|
6440
6660
|
slug: "my-new-assignment",
|
|
@@ -6442,7 +6662,9 @@ function createWriteRouter(projectsDir2, assignmentsDir2) {
|
|
|
6442
6662
|
timestamp: nowTimestamp(),
|
|
6443
6663
|
priority: "medium",
|
|
6444
6664
|
dependsOn: [],
|
|
6445
|
-
links: []
|
|
6665
|
+
links: [],
|
|
6666
|
+
project: standalone ? null : void 0,
|
|
6667
|
+
workspaceGroup: standalone && workspaceParam ? workspaceParam : null
|
|
6446
6668
|
});
|
|
6447
6669
|
res.json({ content });
|
|
6448
6670
|
});
|
|
@@ -7171,6 +7393,76 @@ ${entry}`;
|
|
|
7171
7393
|
res.status(501).json({ error: "Standalone assignments not configured on this server" });
|
|
7172
7394
|
return;
|
|
7173
7395
|
}
|
|
7396
|
+
const rawContent = typeof req.body?.content === "string" ? req.body.content : "";
|
|
7397
|
+
if (rawContent.trim()) {
|
|
7398
|
+
const fields = extractFrontmatter3(rawContent);
|
|
7399
|
+
if (!fields) {
|
|
7400
|
+
res.status(400).json({ error: "Invalid frontmatter: missing --- delimiters" });
|
|
7401
|
+
return;
|
|
7402
|
+
}
|
|
7403
|
+
const validation = validateRequired(fields, ["slug", "title"]);
|
|
7404
|
+
if (!validation.valid) {
|
|
7405
|
+
res.status(400).json({ error: `Missing required fields: ${validation.missing.join(", ")}` });
|
|
7406
|
+
return;
|
|
7407
|
+
}
|
|
7408
|
+
const submittedSlug = fields.slug;
|
|
7409
|
+
if (!isValidSlug(submittedSlug)) {
|
|
7410
|
+
res.status(400).json({ error: `Invalid slug "${submittedSlug}". Must be lowercase and hyphen-separated.` });
|
|
7411
|
+
return;
|
|
7412
|
+
}
|
|
7413
|
+
const validPriorities = ["low", "medium", "high", "critical"];
|
|
7414
|
+
const submittedPriority = fields.priority || "medium";
|
|
7415
|
+
if (!validPriorities.includes(submittedPriority)) {
|
|
7416
|
+
res.status(400).json({ error: `Invalid priority "${submittedPriority}". Must be low, medium, high, or critical.` });
|
|
7417
|
+
return;
|
|
7418
|
+
}
|
|
7419
|
+
if (fields.project && fields.project !== "null") {
|
|
7420
|
+
res.status(400).json({
|
|
7421
|
+
error: 'Standalone assignments cannot have a project; remove "project" or set it to null.'
|
|
7422
|
+
});
|
|
7423
|
+
return;
|
|
7424
|
+
}
|
|
7425
|
+
const submittedWorkspaceGroup = fields.workspaceGroup && fields.workspaceGroup !== "null" ? fields.workspaceGroup : "";
|
|
7426
|
+
if (submittedWorkspaceGroup && !isValidSlug(submittedWorkspaceGroup)) {
|
|
7427
|
+
res.status(400).json({
|
|
7428
|
+
error: `Invalid workspace slug "${submittedWorkspaceGroup}". Slugs must be lowercase, hyphen-separated, with no special characters.`
|
|
7429
|
+
});
|
|
7430
|
+
return;
|
|
7431
|
+
}
|
|
7432
|
+
const id2 = generateId();
|
|
7433
|
+
const assignmentDir2 = resolve15(assignmentsDir2, id2);
|
|
7434
|
+
if (await fileExists(assignmentDir2)) {
|
|
7435
|
+
res.status(500).json({ error: "UUID collision \u2014 try again" });
|
|
7436
|
+
return;
|
|
7437
|
+
}
|
|
7438
|
+
const timestamp2 = fields.created || nowTimestamp();
|
|
7439
|
+
await ensureDir(assignmentDir2);
|
|
7440
|
+
const normalizedContent = setTopLevelField(rawContent, "id", id2);
|
|
7441
|
+
await writeFileForce(resolve15(assignmentDir2, "assignment.md"), normalizedContent);
|
|
7442
|
+
await writeFileForce(
|
|
7443
|
+
resolve15(assignmentDir2, "scratchpad.md"),
|
|
7444
|
+
renderScratchpad({ assignmentSlug: id2, timestamp: timestamp2 })
|
|
7445
|
+
);
|
|
7446
|
+
await writeFileForce(
|
|
7447
|
+
resolve15(assignmentDir2, "handoff.md"),
|
|
7448
|
+
renderHandoff({ assignmentSlug: id2, timestamp: timestamp2 })
|
|
7449
|
+
);
|
|
7450
|
+
await writeFileForce(
|
|
7451
|
+
resolve15(assignmentDir2, "decision-record.md"),
|
|
7452
|
+
renderDecisionRecord({ assignmentSlug: id2, timestamp: timestamp2 })
|
|
7453
|
+
);
|
|
7454
|
+
await writeFileForce(
|
|
7455
|
+
resolve15(assignmentDir2, "progress.md"),
|
|
7456
|
+
renderProgress({ assignment: id2, timestamp: timestamp2 })
|
|
7457
|
+
);
|
|
7458
|
+
await writeFileForce(
|
|
7459
|
+
resolve15(assignmentDir2, "comments.md"),
|
|
7460
|
+
renderComments({ assignment: id2, timestamp: timestamp2 })
|
|
7461
|
+
);
|
|
7462
|
+
const detail2 = await getAssignmentDetailById(projectsDir2, assignmentsDir2, id2);
|
|
7463
|
+
res.status(201).json({ assignment: detail2 });
|
|
7464
|
+
return;
|
|
7465
|
+
}
|
|
7174
7466
|
const { title, slug, priority, type } = req.body || {};
|
|
7175
7467
|
if (!title || typeof title !== "string" || !title.trim()) {
|
|
7176
7468
|
res.status(400).json({ error: "title is required" });
|
|
@@ -7953,6 +8245,7 @@ import { resolve as resolve17 } from "path";
|
|
|
7953
8245
|
import { readFile as readFile12, unlink as unlink2 } from "fs/promises";
|
|
7954
8246
|
init_timestamp();
|
|
7955
8247
|
init_fs();
|
|
8248
|
+
init_playbooks();
|
|
7956
8249
|
function createPlaybooksRouter(playbooksDir3) {
|
|
7957
8250
|
const router = Router4();
|
|
7958
8251
|
router.get("/", async (_req, res) => {
|
|
@@ -7976,6 +8269,32 @@ function createPlaybooksRouter(playbooksDir3) {
|
|
|
7976
8269
|
res.status(500).json({ error: error instanceof Error ? error.message : "Failed to get template" });
|
|
7977
8270
|
}
|
|
7978
8271
|
});
|
|
8272
|
+
router.post("/:slug/enable", async (req, res) => {
|
|
8273
|
+
try {
|
|
8274
|
+
const result = await setPlaybookEnabled(playbooksDir3, req.params.slug, true);
|
|
8275
|
+
res.json({ slug: result.slug, enabled: result.enabled, changed: result.changed });
|
|
8276
|
+
} catch (error) {
|
|
8277
|
+
const msg = error instanceof Error ? error.message : "Failed to enable playbook";
|
|
8278
|
+
if (msg.startsWith("Playbook ")) {
|
|
8279
|
+
res.status(404).json({ error: msg });
|
|
8280
|
+
return;
|
|
8281
|
+
}
|
|
8282
|
+
res.status(500).json({ error: msg });
|
|
8283
|
+
}
|
|
8284
|
+
});
|
|
8285
|
+
router.post("/:slug/disable", async (req, res) => {
|
|
8286
|
+
try {
|
|
8287
|
+
const result = await setPlaybookEnabled(playbooksDir3, req.params.slug, false);
|
|
8288
|
+
res.json({ slug: result.slug, enabled: result.enabled, changed: result.changed });
|
|
8289
|
+
} catch (error) {
|
|
8290
|
+
const msg = error instanceof Error ? error.message : "Failed to disable playbook";
|
|
8291
|
+
if (msg.startsWith("Playbook ")) {
|
|
8292
|
+
res.status(404).json({ error: msg });
|
|
8293
|
+
return;
|
|
8294
|
+
}
|
|
8295
|
+
res.status(500).json({ error: msg });
|
|
8296
|
+
}
|
|
8297
|
+
});
|
|
7979
8298
|
router.get("/:slug", async (req, res) => {
|
|
7980
8299
|
try {
|
|
7981
8300
|
const detail = await getPlaybookDetail(playbooksDir3, req.params.slug);
|
|
@@ -7990,17 +8309,18 @@ function createPlaybooksRouter(playbooksDir3) {
|
|
|
7990
8309
|
});
|
|
7991
8310
|
router.get("/:slug/edit", async (req, res) => {
|
|
7992
8311
|
try {
|
|
7993
|
-
const
|
|
7994
|
-
if (!
|
|
8312
|
+
const resolved = await resolvePlaybookSlug(playbooksDir3, req.params.slug);
|
|
8313
|
+
if (!resolved) {
|
|
7995
8314
|
res.status(404).json({ error: `Playbook "${req.params.slug}" not found` });
|
|
7996
8315
|
return;
|
|
7997
8316
|
}
|
|
8317
|
+
const filePath = resolve17(playbooksDir3, resolved.filename);
|
|
7998
8318
|
const content = await readFile12(filePath, "utf-8");
|
|
7999
8319
|
res.json({
|
|
8000
8320
|
documentType: "playbook",
|
|
8001
|
-
title: `Edit Playbook: ${
|
|
8321
|
+
title: `Edit Playbook: ${resolved.slug}`,
|
|
8002
8322
|
content,
|
|
8003
|
-
slug:
|
|
8323
|
+
slug: resolved.slug
|
|
8004
8324
|
});
|
|
8005
8325
|
} catch (error) {
|
|
8006
8326
|
res.status(500).json({ error: error instanceof Error ? error.message : "Failed to get playbook for editing" });
|
|
@@ -8039,14 +8359,15 @@ function createPlaybooksRouter(playbooksDir3) {
|
|
|
8039
8359
|
res.status(400).json({ error: "content is required" });
|
|
8040
8360
|
return;
|
|
8041
8361
|
}
|
|
8042
|
-
const
|
|
8043
|
-
if (!
|
|
8362
|
+
const resolved = await resolvePlaybookSlug(playbooksDir3, req.params.slug);
|
|
8363
|
+
if (!resolved) {
|
|
8044
8364
|
res.status(404).json({ error: `Playbook "${req.params.slug}" not found` });
|
|
8045
8365
|
return;
|
|
8046
8366
|
}
|
|
8367
|
+
const filePath = resolve17(playbooksDir3, resolved.filename);
|
|
8047
8368
|
await writeFileForce(filePath, content);
|
|
8048
8369
|
await rebuildPlaybookManifest(playbooksDir3);
|
|
8049
|
-
res.json({ slug:
|
|
8370
|
+
res.json({ slug: resolved.slug, path: filePath });
|
|
8050
8371
|
} catch (error) {
|
|
8051
8372
|
res.status(500).json({ error: error instanceof Error ? error.message : "Failed to update playbook" });
|
|
8052
8373
|
}
|
|
@@ -8057,14 +8378,16 @@ function createPlaybooksRouter(playbooksDir3) {
|
|
|
8057
8378
|
res.status(403).json({ error: "The playbook manifest cannot be deleted" });
|
|
8058
8379
|
return;
|
|
8059
8380
|
}
|
|
8060
|
-
const
|
|
8061
|
-
if (!
|
|
8381
|
+
const resolved = await resolvePlaybookSlug(playbooksDir3, req.params.slug);
|
|
8382
|
+
if (!resolved) {
|
|
8062
8383
|
res.status(404).json({ error: `Playbook "${req.params.slug}" not found` });
|
|
8063
8384
|
return;
|
|
8064
8385
|
}
|
|
8386
|
+
const filePath = resolve17(playbooksDir3, resolved.filename);
|
|
8065
8387
|
await unlink2(filePath);
|
|
8388
|
+
await removeFromDisabledList(resolved.slug);
|
|
8066
8389
|
await rebuildPlaybookManifest(playbooksDir3);
|
|
8067
|
-
res.json({ deleted:
|
|
8390
|
+
res.json({ deleted: resolved.slug });
|
|
8068
8391
|
} catch (error) {
|
|
8069
8392
|
res.status(500).json({ error: error instanceof Error ? error.message : "Failed to delete playbook" });
|
|
8070
8393
|
}
|
|
@@ -9244,7 +9567,7 @@ function createDashboardServer(options) {
|
|
|
9244
9567
|
});
|
|
9245
9568
|
app.get("/api/workspaces", async (_req, res) => {
|
|
9246
9569
|
try {
|
|
9247
|
-
const result = await listWorkspaces(projectsDir2);
|
|
9570
|
+
const result = await listWorkspaces(projectsDir2, assignmentsDir2);
|
|
9248
9571
|
res.json(result);
|
|
9249
9572
|
} catch (error) {
|
|
9250
9573
|
console.error("Error listing workspaces:", error);
|
|
@@ -9363,16 +9686,25 @@ function createDashboardServer(options) {
|
|
|
9363
9686
|
app.use("/api/todos", createTodosRouter(todosDir2, broadcast));
|
|
9364
9687
|
app.use("/api/backup", createBackupRouter());
|
|
9365
9688
|
if (serveStaticUi && dashboardDistPath) {
|
|
9366
|
-
app.use(express.static(dashboardDistPath));
|
|
9367
|
-
app.get("{*path}", async (
|
|
9689
|
+
app.use("/assets", express.static(resolve20(dashboardDistPath, "assets")));
|
|
9690
|
+
app.get("{*path}", async (req, res) => {
|
|
9691
|
+
if (req.path.startsWith("/api") || req.path === "/ws" || req.path.startsWith("/assets")) {
|
|
9692
|
+
res.status(404).json({ error: "Not Found" });
|
|
9693
|
+
return;
|
|
9694
|
+
}
|
|
9368
9695
|
const indexPath = resolve20(dashboardDistPath, "index.html");
|
|
9369
|
-
if (await fileExists(indexPath)) {
|
|
9370
|
-
res.sendFile(indexPath);
|
|
9371
|
-
} else {
|
|
9696
|
+
if (!await fileExists(indexPath)) {
|
|
9372
9697
|
res.status(503).send(
|
|
9373
9698
|
'Dashboard not built. Run "npm run build:dashboard" first.'
|
|
9374
9699
|
);
|
|
9700
|
+
return;
|
|
9375
9701
|
}
|
|
9702
|
+
res.sendFile(indexPath, (err2) => {
|
|
9703
|
+
if (err2) {
|
|
9704
|
+
console.error("Error sending dashboard index.html:", err2);
|
|
9705
|
+
if (!res.headersSent) res.status(500).send("Dashboard load error");
|
|
9706
|
+
}
|
|
9707
|
+
});
|
|
9376
9708
|
});
|
|
9377
9709
|
}
|
|
9378
9710
|
let watcherHandle = null;
|
|
@@ -11758,6 +12090,7 @@ import { resolve as resolve32 } from "path";
|
|
|
11758
12090
|
init_timestamp();
|
|
11759
12091
|
init_paths();
|
|
11760
12092
|
init_fs();
|
|
12093
|
+
init_playbooks();
|
|
11761
12094
|
async function createPlaybookCommand(name, options) {
|
|
11762
12095
|
if (!name.trim()) {
|
|
11763
12096
|
throw new Error("Playbook name cannot be empty.");
|
|
@@ -11791,32 +12124,97 @@ Use --slug to specify a different slug.`
|
|
|
11791
12124
|
init_paths();
|
|
11792
12125
|
init_fs();
|
|
11793
12126
|
init_parser();
|
|
12127
|
+
init_config2();
|
|
11794
12128
|
import { readdir as readdir12, readFile as readFile19 } from "fs/promises";
|
|
11795
12129
|
import { resolve as resolve33 } from "path";
|
|
11796
|
-
async function listPlaybooksCommand() {
|
|
12130
|
+
async function listPlaybooksCommand(options = {}) {
|
|
11797
12131
|
const dir = playbooksDir();
|
|
11798
12132
|
if (!await fileExists(dir)) {
|
|
11799
12133
|
console.log('No playbooks directory found. Run "syntaur init" first.');
|
|
11800
12134
|
return;
|
|
11801
12135
|
}
|
|
12136
|
+
const config = await readConfig();
|
|
12137
|
+
const disabledSet = new Set(config.playbooks.disabled);
|
|
11802
12138
|
const entries = await readdir12(dir, { withFileTypes: true });
|
|
11803
|
-
const mdFiles = entries.filter(
|
|
11804
|
-
|
|
11805
|
-
|
|
11806
|
-
|
|
11807
|
-
}
|
|
11808
|
-
console.log(`Found ${mdFiles.length} playbook(s):
|
|
11809
|
-
`);
|
|
11810
|
-
console.log(`${"Slug".padEnd(30)} ${"Name".padEnd(30)} Description`);
|
|
11811
|
-
console.log(`${"\u2500".repeat(30)} ${"\u2500".repeat(30)} ${"\u2500".repeat(40)}`);
|
|
12139
|
+
const mdFiles = entries.filter(
|
|
12140
|
+
(e) => e.isFile() && e.name.endsWith(".md") && !e.name.startsWith("_") && e.name !== "manifest.md"
|
|
12141
|
+
);
|
|
12142
|
+
const rows = [];
|
|
11812
12143
|
for (const entry of mdFiles) {
|
|
11813
12144
|
const filePath = resolve33(dir, entry.name);
|
|
11814
12145
|
const raw = await readFile19(filePath, "utf-8");
|
|
11815
12146
|
const parsed = parsePlaybook(raw);
|
|
11816
12147
|
const slug = parsed.slug || entry.name.replace(/\.md$/, "");
|
|
11817
|
-
const
|
|
11818
|
-
|
|
11819
|
-
|
|
12148
|
+
const disabled = disabledSet.has(slug);
|
|
12149
|
+
if (disabled && !options.all) continue;
|
|
12150
|
+
rows.push({
|
|
12151
|
+
slug,
|
|
12152
|
+
name: parsed.name || slug,
|
|
12153
|
+
desc: parsed.description || "",
|
|
12154
|
+
disabled
|
|
12155
|
+
});
|
|
12156
|
+
}
|
|
12157
|
+
if (rows.length === 0) {
|
|
12158
|
+
if (!options.all && disabledSet.size > 0) {
|
|
12159
|
+
console.log(
|
|
12160
|
+
`No enabled playbooks found (${disabledSet.size} disabled). Use --all to include disabled playbooks.`
|
|
12161
|
+
);
|
|
12162
|
+
} else {
|
|
12163
|
+
console.log('No playbooks found. Create one with "syntaur create-playbook <name>".');
|
|
12164
|
+
}
|
|
12165
|
+
return;
|
|
12166
|
+
}
|
|
12167
|
+
const totalLabel = options.all ? `Found ${rows.length} playbook(s) (${[...disabledSet].length} disabled):` : `Found ${rows.length} enabled playbook(s):`;
|
|
12168
|
+
console.log(`${totalLabel}
|
|
12169
|
+
`);
|
|
12170
|
+
console.log(`${"Slug".padEnd(30)} ${"Name".padEnd(30)} Description`);
|
|
12171
|
+
console.log(`${"\u2500".repeat(30)} ${"\u2500".repeat(30)} ${"\u2500".repeat(40)}`);
|
|
12172
|
+
for (const row of rows) {
|
|
12173
|
+
const suffix = row.disabled ? " (disabled)" : "";
|
|
12174
|
+
const desc = `${row.desc}${suffix}`;
|
|
12175
|
+
console.log(`${row.slug.padEnd(30)} ${row.name.padEnd(30)} ${desc}`);
|
|
12176
|
+
}
|
|
12177
|
+
}
|
|
12178
|
+
|
|
12179
|
+
// src/commands/enable-playbook.ts
|
|
12180
|
+
init_paths();
|
|
12181
|
+
init_playbooks();
|
|
12182
|
+
async function enablePlaybookCommand(slug) {
|
|
12183
|
+
if (!slug.trim()) {
|
|
12184
|
+
throw new Error("Playbook slug cannot be empty.");
|
|
12185
|
+
}
|
|
12186
|
+
if (!isValidSlug(slug)) {
|
|
12187
|
+
throw new Error(
|
|
12188
|
+
`Invalid slug "${slug}". Slugs must be lowercase, hyphen-separated, with no special characters.`
|
|
12189
|
+
);
|
|
12190
|
+
}
|
|
12191
|
+
const dir = playbooksDir();
|
|
12192
|
+
const { slug: canonical, changed } = await setPlaybookEnabled(dir, slug, true);
|
|
12193
|
+
if (changed) {
|
|
12194
|
+
console.log(`Playbook "${canonical}" enabled.`);
|
|
12195
|
+
} else {
|
|
12196
|
+
console.log(`Playbook "${canonical}" is already enabled.`);
|
|
12197
|
+
}
|
|
12198
|
+
}
|
|
12199
|
+
|
|
12200
|
+
// src/commands/disable-playbook.ts
|
|
12201
|
+
init_paths();
|
|
12202
|
+
init_playbooks();
|
|
12203
|
+
async function disablePlaybookCommand(slug) {
|
|
12204
|
+
if (!slug.trim()) {
|
|
12205
|
+
throw new Error("Playbook slug cannot be empty.");
|
|
12206
|
+
}
|
|
12207
|
+
if (!isValidSlug(slug)) {
|
|
12208
|
+
throw new Error(
|
|
12209
|
+
`Invalid slug "${slug}". Slugs must be lowercase, hyphen-separated, with no special characters.`
|
|
12210
|
+
);
|
|
12211
|
+
}
|
|
12212
|
+
const dir = playbooksDir();
|
|
12213
|
+
const { slug: canonical, changed } = await setPlaybookEnabled(dir, slug, false);
|
|
12214
|
+
if (changed) {
|
|
12215
|
+
console.log(`Playbook "${canonical}" disabled.`);
|
|
12216
|
+
} else {
|
|
12217
|
+
console.log(`Playbook "${canonical}" is already disabled.`);
|
|
11820
12218
|
}
|
|
11821
12219
|
}
|
|
11822
12220
|
|
|
@@ -14580,7 +14978,7 @@ program.command("create-assignment").description("Create a new assignment within
|
|
|
14580
14978
|
"--priority <level>",
|
|
14581
14979
|
"Priority level (low|medium|high|critical)",
|
|
14582
14980
|
"medium"
|
|
14583
|
-
).option("--type <type>", "Assignment type (e.g. feature, bug, refactor)").option("--depends-on <slugs>", "Comma-separated dependency slugs (not allowed with --one-off)").option("--links <slugs>", "Comma-separated linked assignment slugs (projectSlug/assignmentSlug format)").option("--dir <path>", "Override default project directory (ignored for --one-off)").action(async (title, options) => {
|
|
14981
|
+
).option("--type <type>", "Assignment type (e.g. feature, bug, refactor)").option("--depends-on <slugs>", "Comma-separated dependency slugs (not allowed with --one-off)").option("--links <slugs>", "Comma-separated linked assignment slugs (projectSlug/assignmentSlug format)").option("--dir <path>", "Override default project directory (ignored for --one-off)").option("--with-todos", "Scaffold a ## Todos section in assignment.md (omitted by default; typically populated by /plan-assignment)").option("--workspace <slug>", "Workspace group slug (only valid with --one-off; mutually exclusive with --project)").action(async (title, options) => {
|
|
14584
14982
|
try {
|
|
14585
14983
|
await createAssignmentCommand(title, options);
|
|
14586
14984
|
} catch (error) {
|
|
@@ -14864,9 +15262,31 @@ program.command("create-playbook").description("Create a new playbook").argument
|
|
|
14864
15262
|
process.exit(1);
|
|
14865
15263
|
}
|
|
14866
15264
|
});
|
|
14867
|
-
program.command("list-playbooks").description("List all playbooks").action(async () => {
|
|
15265
|
+
program.command("list-playbooks").description("List playbooks (disabled playbooks are excluded unless --all is passed)").option("--all", "Include disabled playbooks").action(async (options) => {
|
|
15266
|
+
try {
|
|
15267
|
+
await listPlaybooksCommand({ all: Boolean(options?.all) });
|
|
15268
|
+
} catch (error) {
|
|
15269
|
+
console.error(
|
|
15270
|
+
"Error:",
|
|
15271
|
+
error instanceof Error ? error.message : String(error)
|
|
15272
|
+
);
|
|
15273
|
+
process.exit(1);
|
|
15274
|
+
}
|
|
15275
|
+
});
|
|
15276
|
+
program.command("enable-playbook").description("Enable a previously-disabled playbook").argument("<slug>", "Playbook slug").action(async (slug) => {
|
|
15277
|
+
try {
|
|
15278
|
+
await enablePlaybookCommand(slug);
|
|
15279
|
+
} catch (error) {
|
|
15280
|
+
console.error(
|
|
15281
|
+
"Error:",
|
|
15282
|
+
error instanceof Error ? error.message : String(error)
|
|
15283
|
+
);
|
|
15284
|
+
process.exit(1);
|
|
15285
|
+
}
|
|
15286
|
+
});
|
|
15287
|
+
program.command("disable-playbook").description("Disable a playbook so agents no longer load it").argument("<slug>", "Playbook slug").action(async (slug) => {
|
|
14868
15288
|
try {
|
|
14869
|
-
await
|
|
15289
|
+
await disablePlaybookCommand(slug);
|
|
14870
15290
|
} catch (error) {
|
|
14871
15291
|
console.error(
|
|
14872
15292
|
"Error:",
|