syntaur 0.41.3 → 0.43.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/.claude-plugin/plugin.json +1 -1
- package/dashboard/dist/assets/{_basePickBy-ZMecMDb-.js → _basePickBy-DzTDKHYU.js} +1 -1
- package/dashboard/dist/assets/{_baseUniq-tVAi81J9.js → _baseUniq-DW7YJayj.js} +1 -1
- package/dashboard/dist/assets/{arc-BMB5nyvD.js → arc-BkeAbQzV.js} +1 -1
- package/dashboard/dist/assets/{architectureDiagram-2XIMDMQ5-1_ttZgNb.js → architectureDiagram-2XIMDMQ5-M_COTMq5.js} +1 -1
- package/dashboard/dist/assets/{blockDiagram-WCTKOSBZ-BAuowdCy.js → blockDiagram-WCTKOSBZ-BMSQm_8K.js} +1 -1
- package/dashboard/dist/assets/{c4Diagram-IC4MRINW-DgqpYWNQ.js → c4Diagram-IC4MRINW-Bte3s8bA.js} +1 -1
- package/dashboard/dist/assets/channel-BrnqMVFz.js +1 -0
- package/dashboard/dist/assets/{chunk-4BX2VUAB-Dul2PJq1.js → chunk-4BX2VUAB-BpEBr9cy.js} +1 -1
- package/dashboard/dist/assets/{chunk-55IACEB6-BNsItfH6.js → chunk-55IACEB6-C5vYlqEx.js} +1 -1
- package/dashboard/dist/assets/{chunk-FMBD7UC4-C3btqaF-.js → chunk-FMBD7UC4-C8wRljiW.js} +1 -1
- package/dashboard/dist/assets/{chunk-JSJVCQXG-BjrX5MC8.js → chunk-JSJVCQXG-svArzivt.js} +1 -1
- package/dashboard/dist/assets/{chunk-KX2RTZJC-xMrVR3MQ.js → chunk-KX2RTZJC-DPZbN7jl.js} +1 -1
- package/dashboard/dist/assets/{chunk-NQ4KR5QH-Dd67ojLx.js → chunk-NQ4KR5QH-ZqOPA0Z2.js} +1 -1
- package/dashboard/dist/assets/{chunk-QZHKN3VN-Dqombp1k.js → chunk-QZHKN3VN-BoxtsyU8.js} +1 -1
- package/dashboard/dist/assets/{chunk-WL4C6EOR-CCYZi5vV.js → chunk-WL4C6EOR-BoTByw-8.js} +1 -1
- package/dashboard/dist/assets/classDiagram-VBA2DB6C-BfXVQbAH.js +1 -0
- package/dashboard/dist/assets/classDiagram-v2-RAHNMMFH-BfXVQbAH.js +1 -0
- package/dashboard/dist/assets/clone-Bm7jyblm.js +1 -0
- package/dashboard/dist/assets/{cose-bilkent-S5V4N54A-CYmvV1RQ.js → cose-bilkent-S5V4N54A-CV74D8s_.js} +1 -1
- package/dashboard/dist/assets/{dagre-KLK3FWXG-DXFT_Iz5.js → dagre-KLK3FWXG-NXFGSDTv.js} +1 -1
- package/dashboard/dist/assets/{diagram-E7M64L7V-CkCF8e9h.js → diagram-E7M64L7V-CO89mocJ.js} +1 -1
- package/dashboard/dist/assets/{diagram-IFDJBPK2-CqIk_zgE.js → diagram-IFDJBPK2-CGOOte10.js} +1 -1
- package/dashboard/dist/assets/{diagram-P4PSJMXO-BV-579JX.js → diagram-P4PSJMXO-tRYoQWNN.js} +1 -1
- package/dashboard/dist/assets/{erDiagram-INFDFZHY-CeFxcSCp.js → erDiagram-INFDFZHY-D88FnNdx.js} +1 -1
- package/dashboard/dist/assets/{flowDiagram-PKNHOUZH-D2IGFjb7.js → flowDiagram-PKNHOUZH-C2dCJPYX.js} +1 -1
- package/dashboard/dist/assets/{ganttDiagram-A5KZAMGK-CctYGwR7.js → ganttDiagram-A5KZAMGK-BVsm_TXU.js} +1 -1
- package/dashboard/dist/assets/{gitGraphDiagram-K3NZZRJ6-zt67Doyo.js → gitGraphDiagram-K3NZZRJ6-DeYEcfwg.js} +1 -1
- package/dashboard/dist/assets/{graph-DZiyaEGT.js → graph-iy97f49o.js} +1 -1
- package/dashboard/dist/assets/index-BqxbS9Wf.css +1 -0
- package/dashboard/dist/assets/index-esLcRMJY.js +566 -0
- package/dashboard/dist/assets/{infoDiagram-LFFYTUFH-jvCdU_x3.js → infoDiagram-LFFYTUFH-B_KPLd9N.js} +1 -1
- package/dashboard/dist/assets/{ishikawaDiagram-PHBUUO56-B3LJ4cf3.js → ishikawaDiagram-PHBUUO56-D0i4hG75.js} +1 -1
- package/dashboard/dist/assets/{journeyDiagram-4ABVD52K-CtEednnS.js → journeyDiagram-4ABVD52K-DxAv-6Dt.js} +1 -1
- package/dashboard/dist/assets/{kanban-definition-K7BYSVSG-BhPQqjK5.js → kanban-definition-K7BYSVSG-CFV3hwVv.js} +1 -1
- package/dashboard/dist/assets/{layout-DBtJEEbD.js → layout-DkzGMO0p.js} +1 -1
- package/dashboard/dist/assets/{linear-CSinjjkH.js → linear-Di0t2dWQ.js} +1 -1
- package/dashboard/dist/assets/{mermaid.core-Cu6allRd.js → mermaid.core-BWdLQDB3.js} +4 -4
- package/dashboard/dist/assets/{mindmap-definition-YRQLILUH-SFkGLxic.js → mindmap-definition-YRQLILUH-DHFtApR_.js} +1 -1
- package/dashboard/dist/assets/{pieDiagram-SKSYHLDU-CM63Vi0v.js → pieDiagram-SKSYHLDU-CKv_gkpa.js} +1 -1
- package/dashboard/dist/assets/{quadrantDiagram-337W2JSQ-KNLfFOZV.js → quadrantDiagram-337W2JSQ-D8d0F939.js} +1 -1
- package/dashboard/dist/assets/{requirementDiagram-Z7DCOOCP-CUTnDKwW.js → requirementDiagram-Z7DCOOCP-BpIRQ6xS.js} +1 -1
- package/dashboard/dist/assets/{sankeyDiagram-WA2Y5GQK-BJh1laae.js → sankeyDiagram-WA2Y5GQK-B6pEFLq9.js} +1 -1
- package/dashboard/dist/assets/{sequenceDiagram-2WXFIKYE-DLrWjp9t.js → sequenceDiagram-2WXFIKYE-C_UzJJ9L.js} +1 -1
- package/dashboard/dist/assets/{stateDiagram-RAJIS63D-AYRZyAEg.js → stateDiagram-RAJIS63D-DffNJyzP.js} +1 -1
- package/dashboard/dist/assets/stateDiagram-v2-FVOUBMTO-Bb4Jseta.js +1 -0
- package/dashboard/dist/assets/{timeline-definition-YZTLITO2-BG2DbYEn.js → timeline-definition-YZTLITO2-BiMYMTvs.js} +1 -1
- package/dashboard/dist/assets/{treemap-KZPCXAKY-D8ZVNAo3.js → treemap-KZPCXAKY-CUEZz4mB.js} +1 -1
- package/dashboard/dist/assets/{vennDiagram-LZ73GAT5-BqBHdSLd.js → vennDiagram-LZ73GAT5-Cpol_IHC.js} +1 -1
- package/dashboard/dist/assets/{xychartDiagram-JWTSCODW-DBgm8PRH.js → xychartDiagram-JWTSCODW-CbnCJEuN.js} +1 -1
- package/dashboard/dist/index.html +2 -2
- package/dist/dashboard/server.js +2584 -431
- package/dist/dashboard/server.js.map +1 -1
- package/dist/index.js +4043 -999
- package/dist/index.js.map +1 -1
- package/dist/launch/index.d.ts +159 -6
- package/dist/launch/index.js +2162 -147
- package/dist/launch/index.js.map +1 -1
- package/package.json +1 -1
- package/platforms/claude-code/.claude-plugin/plugin.json +1 -1
- package/platforms/codex/.codex-plugin/plugin.json +1 -1
- package/platforms/hermes/plugins/syntaur/__pycache__/__init__.cpython-312.pyc +0 -0
- package/platforms/hermes/plugins/syntaur/__pycache__/boundary.cpython-312.pyc +0 -0
- package/dashboard/dist/assets/channel-CIhg4t-B.js +0 -1
- package/dashboard/dist/assets/classDiagram-VBA2DB6C-Bmt2MjbS.js +0 -1
- package/dashboard/dist/assets/classDiagram-v2-RAHNMMFH-Bmt2MjbS.js +0 -1
- package/dashboard/dist/assets/clone-BfVm3try.js +0 -1
- package/dashboard/dist/assets/index-6uihSopA.css +0 -1
- package/dashboard/dist/assets/index-GK0h-4Nt.js +0 -566
- package/dashboard/dist/assets/stateDiagram-v2-FVOUBMTO-BRhe6_kr.js +0 -1
package/dist/launch/index.js
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
1
2
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
2
3
|
var __esm = (fn, res) => function __init() {
|
|
3
4
|
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
4
5
|
};
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
5
10
|
|
|
6
11
|
// src/utils/terminal-schema.ts
|
|
7
12
|
var TERMINAL_CHOICES;
|
|
@@ -39,6 +44,9 @@ function syntaurRoot() {
|
|
|
39
44
|
function defaultProjectDir() {
|
|
40
45
|
return resolve(syntaurRoot(), "projects");
|
|
41
46
|
}
|
|
47
|
+
function playbooksDir() {
|
|
48
|
+
return resolve(syntaurRoot(), "playbooks");
|
|
49
|
+
}
|
|
42
50
|
var init_paths = __esm({
|
|
43
51
|
"src/utils/paths.ts"() {
|
|
44
52
|
"use strict";
|
|
@@ -76,6 +84,27 @@ var init_fs = __esm({
|
|
|
76
84
|
});
|
|
77
85
|
|
|
78
86
|
// src/templates/config.ts
|
|
87
|
+
function renderConfig(params) {
|
|
88
|
+
return `---
|
|
89
|
+
version: "1.0"
|
|
90
|
+
defaultProjectDir: ${params.defaultProjectDir}
|
|
91
|
+
onboarding:
|
|
92
|
+
completed: false
|
|
93
|
+
agentDefaults:
|
|
94
|
+
trustLevel: medium
|
|
95
|
+
autoApprove: false
|
|
96
|
+
backup:
|
|
97
|
+
repo: null
|
|
98
|
+
categories: projects, playbooks, todos, servers, config
|
|
99
|
+
lastBackup: null
|
|
100
|
+
lastRestore: null
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
# Syntaur Configuration
|
|
104
|
+
|
|
105
|
+
Global configuration for the Syntaur CLI.
|
|
106
|
+
`;
|
|
107
|
+
}
|
|
79
108
|
var init_config = __esm({
|
|
80
109
|
"src/templates/config.ts"() {
|
|
81
110
|
"use strict";
|
|
@@ -464,6 +493,51 @@ function parseExternalIds(frontmatter) {
|
|
|
464
493
|
}
|
|
465
494
|
return results;
|
|
466
495
|
}
|
|
496
|
+
function parseStatusHistory(frontmatter) {
|
|
497
|
+
if (/^statusHistory:\s*\[\s*\]/m.test(frontmatter)) return [];
|
|
498
|
+
const headerMatch = frontmatter.match(/^statusHistory:\s*$/m);
|
|
499
|
+
if (!headerMatch) return [];
|
|
500
|
+
const headerStart = headerMatch.index ?? frontmatter.indexOf(headerMatch[0]);
|
|
501
|
+
const bodyStart = headerStart + headerMatch[0].length + 1;
|
|
502
|
+
const after = frontmatter.slice(bodyStart);
|
|
503
|
+
const bodyLines = [];
|
|
504
|
+
for (const line of after.split("\n")) {
|
|
505
|
+
if (line.length === 0) {
|
|
506
|
+
bodyLines.push(line);
|
|
507
|
+
continue;
|
|
508
|
+
}
|
|
509
|
+
if (line[0] !== " " && line[0] !== " ") break;
|
|
510
|
+
bodyLines.push(line);
|
|
511
|
+
}
|
|
512
|
+
const body = bodyLines.join("\n");
|
|
513
|
+
const results = [];
|
|
514
|
+
const itemBlocks = body.split(/\n\s+-\s+/).filter((b) => b.trim().length > 0);
|
|
515
|
+
for (const block of itemBlocks) {
|
|
516
|
+
const entry = {};
|
|
517
|
+
for (const line of block.split("\n")) {
|
|
518
|
+
const colonIdx = line.indexOf(":");
|
|
519
|
+
if (colonIdx < 0) continue;
|
|
520
|
+
const key = line.slice(0, colonIdx).trim().replace(/^-\s+/, "");
|
|
521
|
+
if (!key) continue;
|
|
522
|
+
entry[key] = parseSimpleValue(line.slice(colonIdx + 1));
|
|
523
|
+
}
|
|
524
|
+
if (!entry["to"]) continue;
|
|
525
|
+
const result = {
|
|
526
|
+
at: entry["at"] ?? "",
|
|
527
|
+
from: entry["from"] ?? null,
|
|
528
|
+
to: entry["to"],
|
|
529
|
+
command: entry["command"] ?? "",
|
|
530
|
+
by: entry["by"] ?? null
|
|
531
|
+
};
|
|
532
|
+
if (entry["reason"] != null) result.reason = entry["reason"];
|
|
533
|
+
if ("phaseFrom" in entry) result.phaseFrom = entry["phaseFrom"];
|
|
534
|
+
if ("phaseTo" in entry) result.phaseTo = entry["phaseTo"];
|
|
535
|
+
if ("dispositionFrom" in entry) result.dispositionFrom = entry["dispositionFrom"];
|
|
536
|
+
if ("dispositionTo" in entry) result.dispositionTo = entry["dispositionTo"];
|
|
537
|
+
results.push(result);
|
|
538
|
+
}
|
|
539
|
+
return results;
|
|
540
|
+
}
|
|
467
541
|
function parseAssignmentFull(fileContent) {
|
|
468
542
|
const [fm, body] = extractFrontmatter(fileContent);
|
|
469
543
|
return {
|
|
@@ -486,13 +560,40 @@ function parseAssignmentFull(fileContent) {
|
|
|
486
560
|
parentBranch: getNestedField(fm, "workspace", "parentBranch")
|
|
487
561
|
},
|
|
488
562
|
externalIds: parseExternalIds(fm),
|
|
563
|
+
statusHistory: parseStatusHistory(fm),
|
|
489
564
|
tags: parseListField(fm, "tags"),
|
|
490
565
|
archived: getField(fm, "archived") === "true",
|
|
491
566
|
archivedAt: getField(fm, "archivedAt"),
|
|
492
567
|
archivedReason: getField(fm, "archivedReason"),
|
|
493
568
|
created: getField(fm, "created") ?? "",
|
|
494
569
|
updated: getField(fm, "updated") ?? "",
|
|
495
|
-
body
|
|
570
|
+
body,
|
|
571
|
+
phase: getField(fm, "phase"),
|
|
572
|
+
disposition: getField(fm, "disposition"),
|
|
573
|
+
parked: getField(fm, "parked") === "true",
|
|
574
|
+
reviewRequested: getField(fm, "reviewRequested") === "true",
|
|
575
|
+
implementationStarted: getField(fm, "implementationStarted") === "true",
|
|
576
|
+
planApproval: (() => {
|
|
577
|
+
const file = getNestedField(fm, "planApproval", "file");
|
|
578
|
+
const digest = getNestedField(fm, "planApproval", "digest");
|
|
579
|
+
if (!file || !digest) return null;
|
|
580
|
+
return {
|
|
581
|
+
file,
|
|
582
|
+
digest,
|
|
583
|
+
by: getNestedField(fm, "planApproval", "by"),
|
|
584
|
+
at: getNestedField(fm, "planApproval", "at") ?? ""
|
|
585
|
+
};
|
|
586
|
+
})(),
|
|
587
|
+
override: (() => {
|
|
588
|
+
const status = getNestedField(fm, "override", "status");
|
|
589
|
+
if (!status) return null;
|
|
590
|
+
return {
|
|
591
|
+
status,
|
|
592
|
+
source: getNestedField(fm, "override", "source") ?? "human",
|
|
593
|
+
reason: getNestedField(fm, "override", "reason"),
|
|
594
|
+
at: getNestedField(fm, "override", "at") ?? ""
|
|
595
|
+
};
|
|
596
|
+
})()
|
|
496
597
|
};
|
|
497
598
|
}
|
|
498
599
|
function parsePlan(fileContent) {
|
|
@@ -583,6 +684,19 @@ function parseProgress(fileContent) {
|
|
|
583
684
|
body
|
|
584
685
|
};
|
|
585
686
|
}
|
|
687
|
+
function parsePlaybook(fileContent) {
|
|
688
|
+
const [fm, body] = extractFrontmatter(fileContent);
|
|
689
|
+
return {
|
|
690
|
+
slug: getField(fm, "slug") ?? "",
|
|
691
|
+
name: getField(fm, "name") ?? "",
|
|
692
|
+
description: getField(fm, "description") ?? "",
|
|
693
|
+
whenToUse: getField(fm, "when_to_use") ?? "",
|
|
694
|
+
created: getField(fm, "created") ?? "",
|
|
695
|
+
updated: getField(fm, "updated") ?? "",
|
|
696
|
+
tags: parseListField(fm, "tags"),
|
|
697
|
+
body
|
|
698
|
+
};
|
|
699
|
+
}
|
|
586
700
|
function extractMermaidGraph(body) {
|
|
587
701
|
const match = body.match(/```mermaid\n([\s\S]*?)```/);
|
|
588
702
|
return match ? match[1].trim() : null;
|
|
@@ -675,7 +789,12 @@ function canonicalizeCombo(input) {
|
|
|
675
789
|
}
|
|
676
790
|
return [...ordered, key].join("+");
|
|
677
791
|
}
|
|
678
|
-
|
|
792
|
+
function isReservedCombo(combo) {
|
|
793
|
+
const c = canonicalizeCombo(combo);
|
|
794
|
+
if (!c) return false;
|
|
795
|
+
return BUILTIN_RESERVED_COMBOS.includes(c);
|
|
796
|
+
}
|
|
797
|
+
var BINDABLE_ACTION_KINDS, BUILTIN_RESERVED_COMBOS, MODIFIER_ORDER, DEFAULT_BINDABLE_HOTKEYS;
|
|
679
798
|
var init_hotkeysCatalog = __esm({
|
|
680
799
|
"src/utils/hotkeysCatalog.ts"() {
|
|
681
800
|
"use strict";
|
|
@@ -685,6 +804,40 @@ var init_hotkeysCatalog = __esm({
|
|
|
685
804
|
"new-todo",
|
|
686
805
|
"new-assignment"
|
|
687
806
|
];
|
|
807
|
+
BUILTIN_RESERVED_COMBOS = [
|
|
808
|
+
"mod+k",
|
|
809
|
+
"mod+shift+k",
|
|
810
|
+
"?",
|
|
811
|
+
"escape",
|
|
812
|
+
"enter",
|
|
813
|
+
"shift+t",
|
|
814
|
+
// g-chord starter + suffixes
|
|
815
|
+
"g",
|
|
816
|
+
"g o",
|
|
817
|
+
"g m",
|
|
818
|
+
"g a",
|
|
819
|
+
"g t",
|
|
820
|
+
"g s",
|
|
821
|
+
"g !",
|
|
822
|
+
"g ,",
|
|
823
|
+
// list-scope navigation
|
|
824
|
+
"j",
|
|
825
|
+
"k",
|
|
826
|
+
"o",
|
|
827
|
+
// ProjectDetail page
|
|
828
|
+
"a",
|
|
829
|
+
"e",
|
|
830
|
+
// AssignmentsPage board
|
|
831
|
+
"/",
|
|
832
|
+
"r",
|
|
833
|
+
// AssignmentDetail page
|
|
834
|
+
"p",
|
|
835
|
+
"h",
|
|
836
|
+
"d",
|
|
837
|
+
"s",
|
|
838
|
+
"[",
|
|
839
|
+
"]"
|
|
840
|
+
];
|
|
688
841
|
MODIFIER_ORDER = ["mod", "ctrl", "alt", "shift"];
|
|
689
842
|
DEFAULT_BINDABLE_HOTKEYS = {
|
|
690
843
|
"new-workspace": canonicalizeCombo("Mod+Shift+Alt+w"),
|
|
@@ -808,6 +961,47 @@ var init_workspace_visibility_schema = __esm({
|
|
|
808
961
|
});
|
|
809
962
|
|
|
810
963
|
// src/utils/config.ts
|
|
964
|
+
var config_exports = {};
|
|
965
|
+
__export(config_exports, {
|
|
966
|
+
AGENT_ID_PATTERN: () => AGENT_ID_PATTERN,
|
|
967
|
+
AgentConfigError: () => AgentConfigError,
|
|
968
|
+
BUILTIN_AGENTS: () => BUILTIN_AGENTS,
|
|
969
|
+
DEFAULT_ASSIGNMENT_TYPES: () => DEFAULT_ASSIGNMENT_TYPES,
|
|
970
|
+
DEFAULT_DERIVE_CONFIG: () => DEFAULT_DERIVE_CONFIG,
|
|
971
|
+
DEFAULT_STATUS_COLORS: () => DEFAULT_STATUS_COLORS,
|
|
972
|
+
PROMPT_ARG_POSITIONS: () => PROMPT_ARG_POSITIONS,
|
|
973
|
+
TERMINAL_CHOICES: () => TERMINAL_CHOICES,
|
|
974
|
+
TerminalConfigError: () => TerminalConfigError,
|
|
975
|
+
buildDefaultStatusConfig: () => buildDefaultStatusConfig,
|
|
976
|
+
deleteAgentsConfig: () => deleteAgentsConfig,
|
|
977
|
+
deleteHotkeyBindingsConfig: () => deleteHotkeyBindingsConfig,
|
|
978
|
+
deleteStatusConfig: () => deleteStatusConfig,
|
|
979
|
+
deleteTerminalConfig: () => deleteTerminalConfig,
|
|
980
|
+
deleteThemeConfig: () => deleteThemeConfig,
|
|
981
|
+
deleteWorkspaceVisibilityConfig: () => deleteWorkspaceVisibilityConfig,
|
|
982
|
+
getAgents: () => getAgents,
|
|
983
|
+
getAssignmentTypes: () => getAssignmentTypes,
|
|
984
|
+
getTerminal: () => getTerminal,
|
|
985
|
+
parseAgentCommand: () => parseAgentCommand,
|
|
986
|
+
parseStatusConfig: () => parseStatusConfig,
|
|
987
|
+
parseTerminalConfig: () => parseTerminalConfig,
|
|
988
|
+
readConfig: () => readConfig,
|
|
989
|
+
serializeStatusConfig: () => serializeStatusConfig,
|
|
990
|
+
toTitleCase: () => toTitleCase,
|
|
991
|
+
updateAgentsConfig: () => updateAgentsConfig,
|
|
992
|
+
updateBackupConfig: () => updateBackupConfig,
|
|
993
|
+
updateIntegrationConfig: () => updateIntegrationConfig,
|
|
994
|
+
updateOnboardingConfig: () => updateOnboardingConfig,
|
|
995
|
+
updatePlaybooksConfig: () => updatePlaybooksConfig,
|
|
996
|
+
validateAgentList: () => validateAgentList,
|
|
997
|
+
validateDeriveConfig: () => validateDeriveConfig,
|
|
998
|
+
writeAgentsConfig: () => writeAgentsConfig,
|
|
999
|
+
writeHotkeyBindingsConfig: () => writeHotkeyBindingsConfig,
|
|
1000
|
+
writeStatusConfig: () => writeStatusConfig,
|
|
1001
|
+
writeTerminalConfig: () => writeTerminalConfig,
|
|
1002
|
+
writeThemeConfig: () => writeThemeConfig,
|
|
1003
|
+
writeWorkspaceVisibilityConfig: () => writeWorkspaceVisibilityConfig
|
|
1004
|
+
});
|
|
811
1005
|
import { readFile as readFile5 } from "fs/promises";
|
|
812
1006
|
import { spawnSync } from "child_process";
|
|
813
1007
|
import { resolve as resolve6, isAbsolute } from "path";
|
|
@@ -860,6 +1054,11 @@ function validateAgentList(agents) {
|
|
|
860
1054
|
`agent "${agent.id}" has invalid playbook "${agent.playbook}" \u2014 must be a valid playbook slug`
|
|
861
1055
|
);
|
|
862
1056
|
}
|
|
1057
|
+
if (agent.launchPrompt !== void 0 && /[\r\n]/.test(agent.launchPrompt)) {
|
|
1058
|
+
throw new AgentConfigError(
|
|
1059
|
+
`agent "${agent.id}" has invalid launchPrompt \u2014 must be a single line (no newlines)`
|
|
1060
|
+
);
|
|
1061
|
+
}
|
|
863
1062
|
validateSessionInvocation(agent, "resume", agent.resume);
|
|
864
1063
|
validateSessionInvocation(agent, "fork", agent.fork);
|
|
865
1064
|
if (agent.default) defaults++;
|
|
@@ -972,6 +1171,16 @@ function parseStatusConfig(content) {
|
|
|
972
1171
|
const statuses = [];
|
|
973
1172
|
const order = [];
|
|
974
1173
|
const transitions = [];
|
|
1174
|
+
const phaseLadder = [];
|
|
1175
|
+
const disposition = [];
|
|
1176
|
+
const headline = {};
|
|
1177
|
+
const unquote = (v) => {
|
|
1178
|
+
const t = v.trim();
|
|
1179
|
+
if (t.startsWith('"') && t.endsWith('"') || t.startsWith("'") && t.endsWith("'")) {
|
|
1180
|
+
return t.slice(1, -1);
|
|
1181
|
+
}
|
|
1182
|
+
return t;
|
|
1183
|
+
};
|
|
975
1184
|
let currentSection = null;
|
|
976
1185
|
const lines = remaining.split("\n");
|
|
977
1186
|
function parseListEntry(lineIdx, baseIndent) {
|
|
@@ -1004,6 +1213,9 @@ function parseStatusConfig(content) {
|
|
|
1004
1213
|
if (key === "definitions") currentSection = "definitions";
|
|
1005
1214
|
else if (key === "order") currentSection = "order";
|
|
1006
1215
|
else if (key === "transitions") currentSection = "transitions";
|
|
1216
|
+
else if (key === "phaseLadder") currentSection = "phaseLadder";
|
|
1217
|
+
else if (key === "disposition") currentSection = "disposition";
|
|
1218
|
+
else if (key === "headline") currentSection = "headline";
|
|
1007
1219
|
else currentSection = null;
|
|
1008
1220
|
continue;
|
|
1009
1221
|
}
|
|
@@ -1042,12 +1254,52 @@ function parseStatusConfig(content) {
|
|
|
1042
1254
|
lineIdx += consumed - 1;
|
|
1043
1255
|
continue;
|
|
1044
1256
|
}
|
|
1257
|
+
if (currentSection === "phaseLadder" && indent >= 4 && trimmed.startsWith("- ")) {
|
|
1258
|
+
const { entry, consumed } = parseListEntry(lineIdx, indent);
|
|
1259
|
+
if (entry["phase"] && entry["when"] !== void 0) {
|
|
1260
|
+
phaseLadder.push({
|
|
1261
|
+
phase: unquote(entry["phase"]),
|
|
1262
|
+
when: unquote(entry["when"]),
|
|
1263
|
+
next: entry["next"] !== void 0 ? unquote(entry["next"]) : void 0
|
|
1264
|
+
});
|
|
1265
|
+
}
|
|
1266
|
+
lineIdx += consumed - 1;
|
|
1267
|
+
continue;
|
|
1268
|
+
}
|
|
1269
|
+
if (currentSection === "disposition" && indent >= 4 && trimmed.startsWith("- ")) {
|
|
1270
|
+
const { entry, consumed } = parseListEntry(lineIdx, indent);
|
|
1271
|
+
if (entry["else"] !== void 0) {
|
|
1272
|
+
disposition.push({ when: null, is: unquote(entry["else"]) });
|
|
1273
|
+
} else if (entry["when"] !== void 0 && entry["is"]) {
|
|
1274
|
+
disposition.push({ when: unquote(entry["when"]), is: unquote(entry["is"]) });
|
|
1275
|
+
}
|
|
1276
|
+
lineIdx += consumed - 1;
|
|
1277
|
+
continue;
|
|
1278
|
+
}
|
|
1279
|
+
if (currentSection === "headline" && indent >= 4 && !trimmed.startsWith("- ")) {
|
|
1280
|
+
const ci = trimmed.indexOf(":");
|
|
1281
|
+
if (ci > 0) {
|
|
1282
|
+
headline[trimmed.slice(0, ci).trim()] = unquote(trimmed.slice(ci + 1));
|
|
1283
|
+
}
|
|
1284
|
+
continue;
|
|
1285
|
+
}
|
|
1045
1286
|
}
|
|
1046
1287
|
if (statuses.length === 0) return null;
|
|
1288
|
+
const derive = phaseLadder.length > 0 || disposition.length > 0 || Object.keys(headline).length > 0 ? {
|
|
1289
|
+
phaseLadder: phaseLadder.length > 0 ? phaseLadder : DEFAULT_DERIVE_CONFIG.phaseLadder,
|
|
1290
|
+
disposition: disposition.length > 0 ? disposition : DEFAULT_DERIVE_CONFIG.disposition,
|
|
1291
|
+
headline: {
|
|
1292
|
+
terminal: "passthrough",
|
|
1293
|
+
parked: headline["parked"] ?? DEFAULT_DERIVE_CONFIG.headline.parked,
|
|
1294
|
+
blocked: headline["blocked"] ?? DEFAULT_DERIVE_CONFIG.headline.blocked,
|
|
1295
|
+
active: "phase"
|
|
1296
|
+
}
|
|
1297
|
+
} : null;
|
|
1047
1298
|
return {
|
|
1048
1299
|
statuses,
|
|
1049
1300
|
order: order.length > 0 ? order : statuses.map((s) => s.id),
|
|
1050
|
-
transitions
|
|
1301
|
+
transitions,
|
|
1302
|
+
derive
|
|
1051
1303
|
};
|
|
1052
1304
|
}
|
|
1053
1305
|
function toTitleCase(s) {
|
|
@@ -1068,6 +1320,137 @@ function buildDefaultStatusConfig() {
|
|
|
1068
1320
|
})
|
|
1069
1321
|
};
|
|
1070
1322
|
}
|
|
1323
|
+
function serializeStatusConfig(statuses) {
|
|
1324
|
+
const lines = [];
|
|
1325
|
+
lines.push("statuses:");
|
|
1326
|
+
lines.push(" definitions:");
|
|
1327
|
+
for (const s of statuses.statuses) {
|
|
1328
|
+
lines.push(` - id: ${s.id}`);
|
|
1329
|
+
lines.push(` label: ${s.label}`);
|
|
1330
|
+
if (s.description) lines.push(` description: ${s.description}`);
|
|
1331
|
+
if (s.color) lines.push(` color: ${s.color}`);
|
|
1332
|
+
if (s.icon) lines.push(` icon: ${s.icon}`);
|
|
1333
|
+
if (s.terminal) lines.push(` terminal: true`);
|
|
1334
|
+
}
|
|
1335
|
+
lines.push(" order:");
|
|
1336
|
+
for (const id of statuses.order) {
|
|
1337
|
+
lines.push(` - ${id}`);
|
|
1338
|
+
}
|
|
1339
|
+
if (statuses.transitions.length > 0) {
|
|
1340
|
+
lines.push(" transitions:");
|
|
1341
|
+
for (const t of statuses.transitions) {
|
|
1342
|
+
lines.push(` - from: ${t.from}`);
|
|
1343
|
+
lines.push(` command: ${t.command}`);
|
|
1344
|
+
lines.push(` to: ${t.to}`);
|
|
1345
|
+
if (t.label) lines.push(` label: ${t.label}`);
|
|
1346
|
+
if (t.description) lines.push(` description: ${t.description}`);
|
|
1347
|
+
if (t.requiresReason) lines.push(` requiresReason: true`);
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
if (statuses.derive) {
|
|
1351
|
+
const d = statuses.derive;
|
|
1352
|
+
lines.push(" phaseLadder:");
|
|
1353
|
+
for (const rung of d.phaseLadder) {
|
|
1354
|
+
lines.push(` - phase: ${rung.phase}`);
|
|
1355
|
+
lines.push(` when: "${rung.when.replace(/"/g, '\\"')}"`);
|
|
1356
|
+
if (rung.next) lines.push(` next: "${rung.next.replace(/"/g, '\\"')}"`);
|
|
1357
|
+
}
|
|
1358
|
+
lines.push(" disposition:");
|
|
1359
|
+
for (const rule of d.disposition) {
|
|
1360
|
+
if (rule.when === null) {
|
|
1361
|
+
lines.push(` - else: ${rule.is}`);
|
|
1362
|
+
} else {
|
|
1363
|
+
lines.push(` - when: "${rule.when.replace(/"/g, '\\"')}"`);
|
|
1364
|
+
lines.push(` is: ${rule.is}`);
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
lines.push(" headline:");
|
|
1368
|
+
lines.push(` terminal: passthrough`);
|
|
1369
|
+
lines.push(` parked: ${d.headline.parked}`);
|
|
1370
|
+
lines.push(` blocked: ${d.headline.blocked}`);
|
|
1371
|
+
lines.push(` active: phase`);
|
|
1372
|
+
}
|
|
1373
|
+
return lines.join("\n");
|
|
1374
|
+
}
|
|
1375
|
+
function validateDeriveConfig(derive, statusConfig, validateWhen = () => null) {
|
|
1376
|
+
const problems = [];
|
|
1377
|
+
const ids = new Set(statusConfig.statuses.map((s) => s.id));
|
|
1378
|
+
if (derive.phaseLadder.length === 0) {
|
|
1379
|
+
problems.push("phaseLadder must have at least one rung");
|
|
1380
|
+
}
|
|
1381
|
+
for (const rung of derive.phaseLadder) {
|
|
1382
|
+
if (!ids.has(rung.phase)) {
|
|
1383
|
+
problems.push(`phaseLadder rung "${rung.phase}" is not a defined status id`);
|
|
1384
|
+
}
|
|
1385
|
+
const err = rung.when === "*" ? null : validateWhen(rung.when);
|
|
1386
|
+
if (err) problems.push(`phaseLadder rung "${rung.phase}": invalid condition \u2014 ${err}`);
|
|
1387
|
+
}
|
|
1388
|
+
const VALID_DISPOSITIONS = /* @__PURE__ */ new Set(["active", "blocked", "parked"]);
|
|
1389
|
+
let sawElse = false;
|
|
1390
|
+
for (const rule of derive.disposition) {
|
|
1391
|
+
if (!VALID_DISPOSITIONS.has(rule.is)) {
|
|
1392
|
+
problems.push(
|
|
1393
|
+
`disposition "${rule.is}" is not valid (expected active, blocked, or parked \u2014 terminal is never a rule)`
|
|
1394
|
+
);
|
|
1395
|
+
}
|
|
1396
|
+
if (rule.when === null) sawElse = true;
|
|
1397
|
+
else {
|
|
1398
|
+
const err = validateWhen(rule.when);
|
|
1399
|
+
if (err) problems.push(`disposition rule "${rule.is}": invalid condition \u2014 ${err}`);
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
if (!sawElse) problems.push("disposition rules must end with an `else:` arm");
|
|
1403
|
+
for (const key of ["parked", "blocked"]) {
|
|
1404
|
+
if (!ids.has(derive.headline[key])) {
|
|
1405
|
+
problems.push(
|
|
1406
|
+
`headline.${key} \u2192 "${derive.headline[key]}" is not a defined status id (add the definition or run migrate-derive)`
|
|
1407
|
+
);
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
return problems;
|
|
1411
|
+
}
|
|
1412
|
+
function serializeIntegrationConfig(integrations) {
|
|
1413
|
+
const lines = [];
|
|
1414
|
+
if (integrations.claudePluginDir) {
|
|
1415
|
+
lines.push(` claudePluginDir: ${integrations.claudePluginDir}`);
|
|
1416
|
+
}
|
|
1417
|
+
if (integrations.codexPluginDir) {
|
|
1418
|
+
lines.push(` codexPluginDir: ${integrations.codexPluginDir}`);
|
|
1419
|
+
}
|
|
1420
|
+
if (integrations.codexMarketplacePath) {
|
|
1421
|
+
lines.push(` codexMarketplacePath: ${integrations.codexMarketplacePath}`);
|
|
1422
|
+
}
|
|
1423
|
+
if (integrations.installedAgents) {
|
|
1424
|
+
for (const [id, rec] of Object.entries(integrations.installedAgents)) {
|
|
1425
|
+
lines.push(` installedAgents.${id}: ${rec.scope}`);
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1428
|
+
if (lines.length === 0) {
|
|
1429
|
+
return null;
|
|
1430
|
+
}
|
|
1431
|
+
return ["integrations:", ...lines].join("\n");
|
|
1432
|
+
}
|
|
1433
|
+
function serializeOnboardingConfig(onboarding) {
|
|
1434
|
+
return ["onboarding:", ` completed: ${onboarding.completed ? "true" : "false"}`].join("\n");
|
|
1435
|
+
}
|
|
1436
|
+
function serializeBackupConfig(backup) {
|
|
1437
|
+
const lines = ["backup:"];
|
|
1438
|
+
lines.push(` repo: ${backup.repo ?? "null"}`);
|
|
1439
|
+
lines.push(` categories: ${backup.categories}`);
|
|
1440
|
+
lines.push(` lastBackup: ${backup.lastBackup ?? "null"}`);
|
|
1441
|
+
lines.push(` lastRestore: ${backup.lastRestore ?? "null"}`);
|
|
1442
|
+
return lines.join("\n");
|
|
1443
|
+
}
|
|
1444
|
+
function serializePlaybooksConfig(playbooks) {
|
|
1445
|
+
if (!playbooks.disabled || playbooks.disabled.length === 0) {
|
|
1446
|
+
return null;
|
|
1447
|
+
}
|
|
1448
|
+
const lines = ["playbooks:", " disabled:"];
|
|
1449
|
+
for (const slug of playbooks.disabled) {
|
|
1450
|
+
lines.push(` - ${slug}`);
|
|
1451
|
+
}
|
|
1452
|
+
return lines.join("\n");
|
|
1453
|
+
}
|
|
1071
1454
|
function parsePlaybooksConfig(fmBlock) {
|
|
1072
1455
|
const blockStart = fmBlock.match(/^playbooks:\s*$/m);
|
|
1073
1456
|
if (!blockStart) {
|
|
@@ -1103,6 +1486,37 @@ function parsePlaybooksConfig(fmBlock) {
|
|
|
1103
1486
|
}
|
|
1104
1487
|
return { disabled };
|
|
1105
1488
|
}
|
|
1489
|
+
async function updatePlaybooksConfig(playbooks) {
|
|
1490
|
+
const configPath = resolve6(syntaurRoot(), "config.md");
|
|
1491
|
+
const current = (await readConfig()).playbooks;
|
|
1492
|
+
const nextPlaybooks = {
|
|
1493
|
+
disabled: Array.from(new Set(playbooks.disabled ?? current.disabled))
|
|
1494
|
+
};
|
|
1495
|
+
const playbooksBlock = serializePlaybooksConfig(nextPlaybooks);
|
|
1496
|
+
const existing = await fileExists(configPath) ? await readFile5(configPath, "utf-8") : renderConfig({ defaultProjectDir: defaultProjectDir() });
|
|
1497
|
+
const fmMatch = existing.match(/^(---\n)([\s\S]*?)\n(---)/);
|
|
1498
|
+
if (!fmMatch) {
|
|
1499
|
+
const bodyBlock = playbooksBlock ? `${playbooksBlock}
|
|
1500
|
+
` : "";
|
|
1501
|
+
const content = `---
|
|
1502
|
+
version: "2.0"
|
|
1503
|
+
defaultProjectDir: ${defaultProjectDir()}
|
|
1504
|
+
${bodyBlock}---
|
|
1505
|
+
${existing}`;
|
|
1506
|
+
await writeFileForce(configPath, content);
|
|
1507
|
+
return;
|
|
1508
|
+
}
|
|
1509
|
+
const fmBlock = fmMatch[2];
|
|
1510
|
+
const afterFrontmatter = existing.slice(fmMatch[0].length);
|
|
1511
|
+
const cleanedFm = stripTopLevelBlock(fmBlock, "playbooks");
|
|
1512
|
+
const newFm = playbooksBlock ? `${cleanedFm}
|
|
1513
|
+
${playbooksBlock}`.replace(/^\n+/, "") : cleanedFm;
|
|
1514
|
+
const normalizedFm = newFm.replace(/\n+$/, "");
|
|
1515
|
+
const newContent = `---
|
|
1516
|
+
${normalizedFm}
|
|
1517
|
+
---${afterFrontmatter}`;
|
|
1518
|
+
await writeFileForce(configPath, newContent);
|
|
1519
|
+
}
|
|
1106
1520
|
function parseThemeConfig(content) {
|
|
1107
1521
|
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
1108
1522
|
if (!match) return null;
|
|
@@ -1125,6 +1539,58 @@ function parseThemeConfig(content) {
|
|
|
1125
1539
|
if (!preset) return null;
|
|
1126
1540
|
return { preset };
|
|
1127
1541
|
}
|
|
1542
|
+
function serializeThemeConfig(theme) {
|
|
1543
|
+
return ["theme:", ` preset: ${theme.preset}`].join("\n");
|
|
1544
|
+
}
|
|
1545
|
+
async function writeThemeConfig(theme) {
|
|
1546
|
+
const configPath = resolve6(syntaurRoot(), "config.md");
|
|
1547
|
+
const themeBlock = serializeThemeConfig(theme);
|
|
1548
|
+
const existing = await fileExists(configPath) ? await readFile5(configPath, "utf-8") : renderConfig({ defaultProjectDir: defaultProjectDir() });
|
|
1549
|
+
const fmMatch = existing.match(/^(---\n)([\s\S]*?)\n(---)/);
|
|
1550
|
+
if (!fmMatch) {
|
|
1551
|
+
const content = `---
|
|
1552
|
+
version: "2.0"
|
|
1553
|
+
defaultProjectDir: ${defaultProjectDir()}
|
|
1554
|
+
${themeBlock}
|
|
1555
|
+
---
|
|
1556
|
+
${existing}`;
|
|
1557
|
+
await writeFileForce(configPath, content);
|
|
1558
|
+
return;
|
|
1559
|
+
}
|
|
1560
|
+
const fmBlock = fmMatch[2];
|
|
1561
|
+
const afterFrontmatter = existing.slice(fmMatch[0].length);
|
|
1562
|
+
const cleanedFm = stripTopLevelBlock(fmBlock, "theme");
|
|
1563
|
+
const newFm = `${cleanedFm}
|
|
1564
|
+
${themeBlock}`.replace(/^\n+/, "");
|
|
1565
|
+
const normalizedFm = newFm.replace(/\n+$/, "");
|
|
1566
|
+
const newContent = `---
|
|
1567
|
+
${normalizedFm}
|
|
1568
|
+
---${afterFrontmatter}`;
|
|
1569
|
+
await writeFileForce(configPath, newContent);
|
|
1570
|
+
}
|
|
1571
|
+
async function deleteThemeConfig() {
|
|
1572
|
+
const configPath = resolve6(syntaurRoot(), "config.md");
|
|
1573
|
+
if (!await fileExists(configPath)) return;
|
|
1574
|
+
const existing = await readFile5(configPath, "utf-8");
|
|
1575
|
+
const fmMatch = existing.match(/^(---\n)([\s\S]*?)\n(---)/);
|
|
1576
|
+
if (!fmMatch) return;
|
|
1577
|
+
const fmBlock = fmMatch[2];
|
|
1578
|
+
const afterFrontmatter = existing.slice(fmMatch[0].length);
|
|
1579
|
+
const cleanedFm = stripTopLevelBlock(fmBlock, "theme");
|
|
1580
|
+
const newContent = `---
|
|
1581
|
+
${cleanedFm}
|
|
1582
|
+
---${afterFrontmatter}`;
|
|
1583
|
+
await writeFileForce(configPath, newContent);
|
|
1584
|
+
}
|
|
1585
|
+
function serializeWorkspaceVisibilityConfig(cfg) {
|
|
1586
|
+
const hidden = normalizeHiddenList(cfg.hidden);
|
|
1587
|
+
if (hidden.length === 0) return null;
|
|
1588
|
+
const lines = ["workspaceVisibility:", " hidden:"];
|
|
1589
|
+
for (const name of hidden) {
|
|
1590
|
+
lines.push(` - ${JSON.stringify(name)}`);
|
|
1591
|
+
}
|
|
1592
|
+
return lines.join("\n");
|
|
1593
|
+
}
|
|
1128
1594
|
function parseWorkspaceVisibilityConfig(fmBlock) {
|
|
1129
1595
|
const blockStart = fmBlock.match(/^workspaceVisibility:\s*$/m);
|
|
1130
1596
|
if (!blockStart) {
|
|
@@ -1162,6 +1628,93 @@ function parseWorkspaceVisibilityConfig(fmBlock) {
|
|
|
1162
1628
|
}
|
|
1163
1629
|
return { hidden: normalizeHiddenList(hidden) };
|
|
1164
1630
|
}
|
|
1631
|
+
async function writeWorkspaceVisibilityConfig(cfg) {
|
|
1632
|
+
const configPath = resolve6(syntaurRoot(), "config.md");
|
|
1633
|
+
const block = serializeWorkspaceVisibilityConfig(cfg);
|
|
1634
|
+
const existing = await fileExists(configPath) ? await readFile5(configPath, "utf-8") : renderConfig({ defaultProjectDir: defaultProjectDir() });
|
|
1635
|
+
const fmMatch = existing.match(/^(---\n)([\s\S]*?)\n(---)/);
|
|
1636
|
+
if (!fmMatch) {
|
|
1637
|
+
const bodyBlock = block ? `${block}
|
|
1638
|
+
` : "";
|
|
1639
|
+
const content = `---
|
|
1640
|
+
version: "2.0"
|
|
1641
|
+
defaultProjectDir: ${defaultProjectDir()}
|
|
1642
|
+
${bodyBlock}---
|
|
1643
|
+
${existing}`;
|
|
1644
|
+
await writeFileForce(configPath, content);
|
|
1645
|
+
return;
|
|
1646
|
+
}
|
|
1647
|
+
const fmBlock = fmMatch[2];
|
|
1648
|
+
const afterFrontmatter = existing.slice(fmMatch[0].length);
|
|
1649
|
+
const cleanedFm = stripTopLevelBlock(fmBlock, "workspaceVisibility");
|
|
1650
|
+
const newFm = block ? `${cleanedFm}
|
|
1651
|
+
${block}`.replace(/^\n+/, "") : cleanedFm;
|
|
1652
|
+
const normalizedFm = newFm.replace(/\n+$/, "");
|
|
1653
|
+
const newContent = `---
|
|
1654
|
+
${normalizedFm}
|
|
1655
|
+
---${afterFrontmatter}`;
|
|
1656
|
+
await writeFileForce(configPath, newContent);
|
|
1657
|
+
}
|
|
1658
|
+
async function deleteWorkspaceVisibilityConfig() {
|
|
1659
|
+
const configPath = resolve6(syntaurRoot(), "config.md");
|
|
1660
|
+
if (!await fileExists(configPath)) return;
|
|
1661
|
+
const existing = await readFile5(configPath, "utf-8");
|
|
1662
|
+
const fmMatch = existing.match(/^(---\n)([\s\S]*?)\n(---)/);
|
|
1663
|
+
if (!fmMatch) return;
|
|
1664
|
+
const fmBlock = fmMatch[2];
|
|
1665
|
+
const afterFrontmatter = existing.slice(fmMatch[0].length);
|
|
1666
|
+
const cleanedFm = stripTopLevelBlock(fmBlock, "workspaceVisibility");
|
|
1667
|
+
const newContent = `---
|
|
1668
|
+
${cleanedFm}
|
|
1669
|
+
---${afterFrontmatter}`;
|
|
1670
|
+
await writeFileForce(configPath, newContent);
|
|
1671
|
+
}
|
|
1672
|
+
function stripTopLevelScalar(fmBlock, key) {
|
|
1673
|
+
const lines = fmBlock.split("\n");
|
|
1674
|
+
const keyRegex = new RegExp(`^${key}:\\s*\\S`);
|
|
1675
|
+
const filtered = lines.filter((line) => !keyRegex.test(line));
|
|
1676
|
+
return filtered.join("\n").replace(/\n+$/, "");
|
|
1677
|
+
}
|
|
1678
|
+
async function writeTerminalConfig(terminal) {
|
|
1679
|
+
const configPath = resolve6(syntaurRoot(), "config.md");
|
|
1680
|
+
const terminalLine = `terminal: ${terminal}`;
|
|
1681
|
+
const existing = await fileExists(configPath) ? await readFile5(configPath, "utf-8") : renderConfig({ defaultProjectDir: defaultProjectDir() });
|
|
1682
|
+
const fmMatch = existing.match(/^(---\n)([\s\S]*?)\n(---)/);
|
|
1683
|
+
if (!fmMatch) {
|
|
1684
|
+
const content = `---
|
|
1685
|
+
version: "2.0"
|
|
1686
|
+
defaultProjectDir: ${defaultProjectDir()}
|
|
1687
|
+
${terminalLine}
|
|
1688
|
+
---
|
|
1689
|
+
${existing}`;
|
|
1690
|
+
await writeFileForce(configPath, content);
|
|
1691
|
+
return;
|
|
1692
|
+
}
|
|
1693
|
+
const fmBlock = fmMatch[2];
|
|
1694
|
+
const afterFrontmatter = existing.slice(fmMatch[0].length);
|
|
1695
|
+
const cleanedFm = stripTopLevelScalar(fmBlock, "terminal");
|
|
1696
|
+
const newFm = `${cleanedFm}
|
|
1697
|
+
${terminalLine}`.replace(/^\n+/, "");
|
|
1698
|
+
const normalizedFm = newFm.replace(/\n+$/, "");
|
|
1699
|
+
const newContent = `---
|
|
1700
|
+
${normalizedFm}
|
|
1701
|
+
---${afterFrontmatter}`;
|
|
1702
|
+
await writeFileForce(configPath, newContent);
|
|
1703
|
+
}
|
|
1704
|
+
async function deleteTerminalConfig() {
|
|
1705
|
+
const configPath = resolve6(syntaurRoot(), "config.md");
|
|
1706
|
+
if (!await fileExists(configPath)) return;
|
|
1707
|
+
const existing = await readFile5(configPath, "utf-8");
|
|
1708
|
+
const fmMatch = existing.match(/^(---\n)([\s\S]*?)\n(---)/);
|
|
1709
|
+
if (!fmMatch) return;
|
|
1710
|
+
const fmBlock = fmMatch[2];
|
|
1711
|
+
const afterFrontmatter = existing.slice(fmMatch[0].length);
|
|
1712
|
+
const cleanedFm = stripTopLevelScalar(fmBlock, "terminal");
|
|
1713
|
+
const newContent = `---
|
|
1714
|
+
${cleanedFm}
|
|
1715
|
+
---${afterFrontmatter}`;
|
|
1716
|
+
await writeFileForce(configPath, newContent);
|
|
1717
|
+
}
|
|
1165
1718
|
function parseHotkeyBindingsConfig(content) {
|
|
1166
1719
|
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
1167
1720
|
if (!match) return null;
|
|
@@ -1194,6 +1747,92 @@ function parseHotkeyBindingsConfig(content) {
|
|
|
1194
1747
|
if (Object.keys(bindings).length === 0) return null;
|
|
1195
1748
|
return { bindings };
|
|
1196
1749
|
}
|
|
1750
|
+
function serializeHotkeyBindingsConfig(cfg) {
|
|
1751
|
+
const lines = ["hotkeys:", " bindings:"];
|
|
1752
|
+
for (const kind of BINDABLE_ACTION_KINDS) {
|
|
1753
|
+
const value = cfg.bindings[kind];
|
|
1754
|
+
if (!value) continue;
|
|
1755
|
+
lines.push(` ${kind}: "${canonicalizeCombo(value)}"`);
|
|
1756
|
+
}
|
|
1757
|
+
if (lines.length === 2) return "";
|
|
1758
|
+
return lines.join("\n");
|
|
1759
|
+
}
|
|
1760
|
+
async function writeHotkeyBindingsConfig(cfg) {
|
|
1761
|
+
const cleaned = {};
|
|
1762
|
+
for (const kind of BINDABLE_ACTION_KINDS) {
|
|
1763
|
+
const raw = cfg.bindings[kind];
|
|
1764
|
+
if (typeof raw !== "string" || raw.trim() === "") continue;
|
|
1765
|
+
const canonical = canonicalizeCombo(raw);
|
|
1766
|
+
if (!canonical) continue;
|
|
1767
|
+
if (isReservedCombo(canonical)) continue;
|
|
1768
|
+
cleaned[kind] = canonical;
|
|
1769
|
+
}
|
|
1770
|
+
if (Object.keys(cleaned).length === 0) {
|
|
1771
|
+
await deleteHotkeyBindingsConfig();
|
|
1772
|
+
return;
|
|
1773
|
+
}
|
|
1774
|
+
const configPath = resolve6(syntaurRoot(), "config.md");
|
|
1775
|
+
const block = serializeHotkeyBindingsConfig({ bindings: cleaned });
|
|
1776
|
+
const existing = await fileExists(configPath) ? await readFile5(configPath, "utf-8") : renderConfig({ defaultProjectDir: defaultProjectDir() });
|
|
1777
|
+
const fmMatch = existing.match(/^(---\n)([\s\S]*?)\n(---)/);
|
|
1778
|
+
if (!fmMatch) {
|
|
1779
|
+
const content = `---
|
|
1780
|
+
version: "2.0"
|
|
1781
|
+
defaultProjectDir: ${defaultProjectDir()}
|
|
1782
|
+
${block}
|
|
1783
|
+
---
|
|
1784
|
+
${existing}`;
|
|
1785
|
+
await writeFileForce(configPath, content);
|
|
1786
|
+
return;
|
|
1787
|
+
}
|
|
1788
|
+
const fmBlock = fmMatch[2];
|
|
1789
|
+
const afterFrontmatter = existing.slice(fmMatch[0].length);
|
|
1790
|
+
const cleanedFm = stripTopLevelBlock(fmBlock, "hotkeys");
|
|
1791
|
+
const newFm = `${cleanedFm}
|
|
1792
|
+
${block}`.replace(/^\n+/, "");
|
|
1793
|
+
const normalizedFm = newFm.replace(/\n+$/, "");
|
|
1794
|
+
const newContent = `---
|
|
1795
|
+
${normalizedFm}
|
|
1796
|
+
---${afterFrontmatter}`;
|
|
1797
|
+
await writeFileForce(configPath, newContent);
|
|
1798
|
+
}
|
|
1799
|
+
async function deleteHotkeyBindingsConfig() {
|
|
1800
|
+
const configPath = resolve6(syntaurRoot(), "config.md");
|
|
1801
|
+
if (!await fileExists(configPath)) return;
|
|
1802
|
+
const existing = await readFile5(configPath, "utf-8");
|
|
1803
|
+
const fmMatch = existing.match(/^(---\n)([\s\S]*?)\n(---)/);
|
|
1804
|
+
if (!fmMatch) return;
|
|
1805
|
+
const fmBlock = fmMatch[2];
|
|
1806
|
+
const afterFrontmatter = existing.slice(fmMatch[0].length);
|
|
1807
|
+
const cleanedFm = stripTopLevelBlock(fmBlock, "hotkeys");
|
|
1808
|
+
const newContent = `---
|
|
1809
|
+
${cleanedFm}
|
|
1810
|
+
---${afterFrontmatter}`;
|
|
1811
|
+
await writeFileForce(configPath, newContent);
|
|
1812
|
+
}
|
|
1813
|
+
function stripTopLevelBlock(fmBlock, key) {
|
|
1814
|
+
const blockStart = fmBlock.match(new RegExp(`^${key}:\\s*$`, "m"));
|
|
1815
|
+
if (!blockStart) {
|
|
1816
|
+
return fmBlock.replace(/\n+$/, "");
|
|
1817
|
+
}
|
|
1818
|
+
const startIdx = fmBlock.indexOf(blockStart[0]);
|
|
1819
|
+
const before = fmBlock.slice(0, startIdx);
|
|
1820
|
+
const after = fmBlock.slice(startIdx + blockStart[0].length);
|
|
1821
|
+
const remaining = after.split("\n");
|
|
1822
|
+
let endIdx = 0;
|
|
1823
|
+
for (let i = 0; i < remaining.length; i++) {
|
|
1824
|
+
const line = remaining[i];
|
|
1825
|
+
if (line.trim() === "") {
|
|
1826
|
+
endIdx = i + 1;
|
|
1827
|
+
continue;
|
|
1828
|
+
}
|
|
1829
|
+
if (line.length > 0 && line[0] !== " ") {
|
|
1830
|
+
break;
|
|
1831
|
+
}
|
|
1832
|
+
endIdx = i + 1;
|
|
1833
|
+
}
|
|
1834
|
+
return (before + remaining.slice(endIdx).join("\n")).replace(/\n+$/, "");
|
|
1835
|
+
}
|
|
1197
1836
|
function parseOptionalAbsolutePath(value, fieldName) {
|
|
1198
1837
|
if (!value) {
|
|
1199
1838
|
return null;
|
|
@@ -1239,6 +1878,7 @@ function parseAgentsConfig(content) {
|
|
|
1239
1878
|
...current.resolveFromShellAliases ? { resolveFromShellAliases: true } : {},
|
|
1240
1879
|
...current.model ? { model: current.model } : {},
|
|
1241
1880
|
...current.playbook ? { playbook: current.playbook } : {},
|
|
1881
|
+
...current.launchPrompt ? { launchPrompt: current.launchPrompt } : {},
|
|
1242
1882
|
...current.resume ? { resume: current.resume } : {},
|
|
1243
1883
|
...current.fork ? { fork: current.fork } : {}
|
|
1244
1884
|
});
|
|
@@ -1427,7 +2067,273 @@ function assignAgentField(target, key, rawValue) {
|
|
|
1427
2067
|
case "playbook":
|
|
1428
2068
|
target.playbook = value;
|
|
1429
2069
|
break;
|
|
2070
|
+
case "launchPrompt":
|
|
2071
|
+
target.launchPrompt = value;
|
|
2072
|
+
break;
|
|
2073
|
+
}
|
|
2074
|
+
}
|
|
2075
|
+
function yamlQuoteScalar(value) {
|
|
2076
|
+
if (/[\r\n]/.test(value)) {
|
|
2077
|
+
throw new AgentConfigError(
|
|
2078
|
+
`value contains newlines, which the agents config serializer does not support: ${JSON.stringify(value)}`
|
|
2079
|
+
);
|
|
2080
|
+
}
|
|
2081
|
+
if (value === "" || /[:#{}[\],&*?|>!%@`"'\\\t]/.test(value) || /^\s|\s$/.test(value)) {
|
|
2082
|
+
const escaped = value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\t/g, "\\t");
|
|
2083
|
+
return `"${escaped}"`;
|
|
2084
|
+
}
|
|
2085
|
+
return value;
|
|
2086
|
+
}
|
|
2087
|
+
function serializeAgentsConfig(agents) {
|
|
2088
|
+
const lines = ["agents:"];
|
|
2089
|
+
for (const a of agents) {
|
|
2090
|
+
lines.push(` - id: ${yamlQuoteScalar(a.id)}`);
|
|
2091
|
+
lines.push(` label: ${yamlQuoteScalar(a.label)}`);
|
|
2092
|
+
lines.push(` command: ${yamlQuoteScalar(a.command)}`);
|
|
2093
|
+
if (a.model) {
|
|
2094
|
+
lines.push(` model: ${yamlQuoteScalar(a.model)}`);
|
|
2095
|
+
}
|
|
2096
|
+
if (a.playbook) {
|
|
2097
|
+
lines.push(` playbook: ${yamlQuoteScalar(a.playbook)}`);
|
|
2098
|
+
}
|
|
2099
|
+
if (a.launchPrompt) {
|
|
2100
|
+
lines.push(` launchPrompt: ${yamlQuoteScalar(a.launchPrompt)}`);
|
|
2101
|
+
}
|
|
2102
|
+
if (a.args && a.args.length > 0) {
|
|
2103
|
+
lines.push(` args:`);
|
|
2104
|
+
for (const arg of a.args) {
|
|
2105
|
+
lines.push(` - ${yamlQuoteScalar(arg)}`);
|
|
2106
|
+
}
|
|
2107
|
+
}
|
|
2108
|
+
if (a.promptArgPosition && a.promptArgPosition !== "first") {
|
|
2109
|
+
lines.push(` promptArgPosition: ${a.promptArgPosition}`);
|
|
2110
|
+
}
|
|
2111
|
+
if (a.default) {
|
|
2112
|
+
lines.push(` default: true`);
|
|
2113
|
+
}
|
|
2114
|
+
if (a.resolveFromShellAliases) {
|
|
2115
|
+
lines.push(` resolveFromShellAliases: true`);
|
|
2116
|
+
}
|
|
2117
|
+
if (a.resume) {
|
|
2118
|
+
appendSessionInvocation(lines, "resume", a.resume);
|
|
2119
|
+
}
|
|
2120
|
+
if (a.fork) {
|
|
2121
|
+
appendSessionInvocation(lines, "fork", a.fork);
|
|
2122
|
+
}
|
|
2123
|
+
}
|
|
2124
|
+
return lines.join("\n");
|
|
2125
|
+
}
|
|
2126
|
+
function appendSessionInvocation(lines, key, invocation) {
|
|
2127
|
+
lines.push(` ${key}:`);
|
|
2128
|
+
if (invocation.command !== void 0) {
|
|
2129
|
+
lines.push(` command: ${yamlQuoteScalar(invocation.command)}`);
|
|
2130
|
+
}
|
|
2131
|
+
lines.push(` args:`);
|
|
2132
|
+
for (const arg of invocation.args) {
|
|
2133
|
+
lines.push(` - ${yamlQuoteScalar(arg)}`);
|
|
2134
|
+
}
|
|
2135
|
+
}
|
|
2136
|
+
async function writeAgentsConfig(agents) {
|
|
2137
|
+
validateAgentList(agents);
|
|
2138
|
+
const configPath = resolve6(syntaurRoot(), "config.md");
|
|
2139
|
+
const agentsBlock = serializeAgentsConfig(agents);
|
|
2140
|
+
const existing = await fileExists(configPath) ? await readFile5(configPath, "utf-8") : renderConfig({ defaultProjectDir: defaultProjectDir() });
|
|
2141
|
+
const fmMatch = existing.match(/^(---\n)([\s\S]*?)\n(---)/);
|
|
2142
|
+
if (!fmMatch) {
|
|
2143
|
+
const content = `---
|
|
2144
|
+
version: "2.0"
|
|
2145
|
+
defaultProjectDir: ${defaultProjectDir()}
|
|
2146
|
+
${agentsBlock}
|
|
2147
|
+
---
|
|
2148
|
+
${existing}`;
|
|
2149
|
+
await writeFileForce(configPath, content.replace(/\n\n---/, "\n---"));
|
|
2150
|
+
return;
|
|
2151
|
+
}
|
|
2152
|
+
const fmBlock = fmMatch[2];
|
|
2153
|
+
const afterFrontmatter = existing.slice(fmMatch[0].length);
|
|
2154
|
+
const cleanedFm = stripTopLevelBlock(fmBlock, "agents");
|
|
2155
|
+
const newFm = `${cleanedFm}
|
|
2156
|
+
${agentsBlock}`.replace(/^\n+/, "").replace(/\n+$/, "");
|
|
2157
|
+
const newContent = `---
|
|
2158
|
+
${newFm}
|
|
2159
|
+
---${afterFrontmatter}`;
|
|
2160
|
+
await writeFileForce(configPath, newContent);
|
|
2161
|
+
}
|
|
2162
|
+
async function deleteAgentsConfig() {
|
|
2163
|
+
const configPath = resolve6(syntaurRoot(), "config.md");
|
|
2164
|
+
if (!await fileExists(configPath)) return;
|
|
2165
|
+
const existing = await readFile5(configPath, "utf-8");
|
|
2166
|
+
const fmMatch = existing.match(/^(---\n)([\s\S]*?)\n(---)/);
|
|
2167
|
+
if (!fmMatch) return;
|
|
2168
|
+
const fmBlock = fmMatch[2];
|
|
2169
|
+
const afterFrontmatter = existing.slice(fmMatch[0].length);
|
|
2170
|
+
const cleanedFm = stripTopLevelBlock(fmBlock, "agents");
|
|
2171
|
+
const newContent = `---
|
|
2172
|
+
${cleanedFm}
|
|
2173
|
+
---${afterFrontmatter}`;
|
|
2174
|
+
await writeFileForce(configPath, newContent);
|
|
2175
|
+
}
|
|
2176
|
+
async function writeStatusConfig(statuses) {
|
|
2177
|
+
const configPath = resolve6(syntaurRoot(), "config.md");
|
|
2178
|
+
const statusBlock = serializeStatusConfig(statuses);
|
|
2179
|
+
if (!await fileExists(configPath)) {
|
|
2180
|
+
const content = `---
|
|
2181
|
+
version: "2.0"
|
|
2182
|
+
defaultProjectDir: ~/projects
|
|
2183
|
+
${statusBlock}
|
|
2184
|
+
---
|
|
2185
|
+
`;
|
|
2186
|
+
await writeFileForce(configPath, content);
|
|
2187
|
+
return;
|
|
2188
|
+
}
|
|
2189
|
+
const existing = await readFile5(configPath, "utf-8");
|
|
2190
|
+
const fmMatch = existing.match(/^(---\n)([\s\S]*?)\n(---)/);
|
|
2191
|
+
if (!fmMatch) {
|
|
2192
|
+
const content = `---
|
|
2193
|
+
version: "2.0"
|
|
2194
|
+
${statusBlock}
|
|
2195
|
+
---
|
|
2196
|
+
${existing}`;
|
|
2197
|
+
await writeFileForce(configPath, content);
|
|
2198
|
+
return;
|
|
1430
2199
|
}
|
|
2200
|
+
const fmBlock = fmMatch[2];
|
|
2201
|
+
const afterFrontmatter = existing.slice(fmMatch[0].length);
|
|
2202
|
+
const statusesStart = fmBlock.match(/^statuses:\s*$/m);
|
|
2203
|
+
let cleanedFm;
|
|
2204
|
+
if (statusesStart) {
|
|
2205
|
+
const startIdx = fmBlock.indexOf(statusesStart[0]);
|
|
2206
|
+
const before = fmBlock.slice(0, startIdx);
|
|
2207
|
+
const after = fmBlock.slice(startIdx + statusesStart[0].length);
|
|
2208
|
+
const remaining = after.split("\n");
|
|
2209
|
+
let endIdx = 0;
|
|
2210
|
+
for (let i = 0; i < remaining.length; i++) {
|
|
2211
|
+
const line = remaining[i];
|
|
2212
|
+
if (line.trim() === "") {
|
|
2213
|
+
endIdx = i + 1;
|
|
2214
|
+
continue;
|
|
2215
|
+
}
|
|
2216
|
+
if (line.length > 0 && line[0] !== " ") break;
|
|
2217
|
+
endIdx = i + 1;
|
|
2218
|
+
}
|
|
2219
|
+
cleanedFm = before + remaining.slice(endIdx).join("\n");
|
|
2220
|
+
} else {
|
|
2221
|
+
cleanedFm = fmBlock;
|
|
2222
|
+
}
|
|
2223
|
+
cleanedFm = cleanedFm.replace(/\n+$/, "");
|
|
2224
|
+
const newContent = `---
|
|
2225
|
+
${cleanedFm}
|
|
2226
|
+
${statusBlock}
|
|
2227
|
+
---${afterFrontmatter}`;
|
|
2228
|
+
await writeFileForce(configPath, newContent);
|
|
2229
|
+
}
|
|
2230
|
+
async function deleteStatusConfig() {
|
|
2231
|
+
const configPath = resolve6(syntaurRoot(), "config.md");
|
|
2232
|
+
if (!await fileExists(configPath)) return;
|
|
2233
|
+
const existing = await readFile5(configPath, "utf-8");
|
|
2234
|
+
const fmMatch = existing.match(/^(---\n)([\s\S]*?)\n(---)/);
|
|
2235
|
+
if (!fmMatch) return;
|
|
2236
|
+
const fmBlock = fmMatch[2];
|
|
2237
|
+
const afterFrontmatter = existing.slice(fmMatch[0].length);
|
|
2238
|
+
const cleanedFm = stripTopLevelBlock(fmBlock, "statuses");
|
|
2239
|
+
const newContent = `---
|
|
2240
|
+
${cleanedFm}
|
|
2241
|
+
---${afterFrontmatter}`;
|
|
2242
|
+
await writeFileForce(configPath, newContent);
|
|
2243
|
+
}
|
|
2244
|
+
async function updateIntegrationConfig(integrations) {
|
|
2245
|
+
const configPath = resolve6(syntaurRoot(), "config.md");
|
|
2246
|
+
const nextIntegrations = {
|
|
2247
|
+
...(await readConfig()).integrations,
|
|
2248
|
+
...integrations
|
|
2249
|
+
};
|
|
2250
|
+
const integrationBlock = serializeIntegrationConfig(nextIntegrations);
|
|
2251
|
+
const existing = await fileExists(configPath) ? await readFile5(configPath, "utf-8") : renderConfig({ defaultProjectDir: defaultProjectDir() });
|
|
2252
|
+
const fmMatch = existing.match(/^(---\n)([\s\S]*?)\n(---)/);
|
|
2253
|
+
if (!fmMatch) {
|
|
2254
|
+
const content = `---
|
|
2255
|
+
version: "2.0"
|
|
2256
|
+
defaultProjectDir: ${defaultProjectDir()}
|
|
2257
|
+
${integrationBlock ?? ""}
|
|
2258
|
+
---
|
|
2259
|
+
${existing}`;
|
|
2260
|
+
await writeFileForce(configPath, content.replace(/\n\n---/, "\n---"));
|
|
2261
|
+
return;
|
|
2262
|
+
}
|
|
2263
|
+
const fmBlock = fmMatch[2];
|
|
2264
|
+
const afterFrontmatter = existing.slice(fmMatch[0].length);
|
|
2265
|
+
const cleanedFm = stripTopLevelBlock(fmBlock, "integrations");
|
|
2266
|
+
const newFm = integrationBlock ? `${cleanedFm}
|
|
2267
|
+
${integrationBlock}`.replace(/^\n+/, "") : cleanedFm;
|
|
2268
|
+
const normalizedFm = newFm.replace(/\n+$/, "");
|
|
2269
|
+
const newContent = `---
|
|
2270
|
+
${normalizedFm}
|
|
2271
|
+
---${afterFrontmatter}`;
|
|
2272
|
+
await writeFileForce(configPath, newContent);
|
|
2273
|
+
}
|
|
2274
|
+
async function updateOnboardingConfig(onboarding) {
|
|
2275
|
+
const configPath = resolve6(syntaurRoot(), "config.md");
|
|
2276
|
+
const nextOnboarding = {
|
|
2277
|
+
...(await readConfig()).onboarding,
|
|
2278
|
+
...onboarding
|
|
2279
|
+
};
|
|
2280
|
+
const onboardingBlock = serializeOnboardingConfig(nextOnboarding);
|
|
2281
|
+
const existing = await fileExists(configPath) ? await readFile5(configPath, "utf-8") : renderConfig({ defaultProjectDir: defaultProjectDir() });
|
|
2282
|
+
const fmMatch = existing.match(/^(---\n)([\s\S]*?)\n(---)/);
|
|
2283
|
+
if (!fmMatch) {
|
|
2284
|
+
const content = `---
|
|
2285
|
+
version: "2.0"
|
|
2286
|
+
defaultProjectDir: ${defaultProjectDir()}
|
|
2287
|
+
${onboardingBlock}
|
|
2288
|
+
---
|
|
2289
|
+
${existing}`;
|
|
2290
|
+
await writeFileForce(configPath, content.replace(/\n\n---/, "\n---"));
|
|
2291
|
+
return;
|
|
2292
|
+
}
|
|
2293
|
+
const fmBlock = fmMatch[2];
|
|
2294
|
+
const afterFrontmatter = existing.slice(fmMatch[0].length);
|
|
2295
|
+
const cleanedFm = stripTopLevelBlock(fmBlock, "onboarding");
|
|
2296
|
+
const newFm = `${cleanedFm}
|
|
2297
|
+
${onboardingBlock}`.replace(/^\n+/, "");
|
|
2298
|
+
const normalizedFm = newFm.replace(/\n+$/, "");
|
|
2299
|
+
const newContent = `---
|
|
2300
|
+
${normalizedFm}
|
|
2301
|
+
---${afterFrontmatter}`;
|
|
2302
|
+
await writeFileForce(configPath, newContent);
|
|
2303
|
+
}
|
|
2304
|
+
async function updateBackupConfig(backup) {
|
|
2305
|
+
const configPath = resolve6(syntaurRoot(), "config.md");
|
|
2306
|
+
const current = (await readConfig()).backup;
|
|
2307
|
+
const nextBackup = {
|
|
2308
|
+
repo: current?.repo ?? null,
|
|
2309
|
+
categories: current?.categories ?? "projects, playbooks, todos, servers, config",
|
|
2310
|
+
lastBackup: current?.lastBackup ?? null,
|
|
2311
|
+
lastRestore: current?.lastRestore ?? null,
|
|
2312
|
+
...backup
|
|
2313
|
+
};
|
|
2314
|
+
const backupBlock = serializeBackupConfig(nextBackup);
|
|
2315
|
+
const existing = await fileExists(configPath) ? await readFile5(configPath, "utf-8") : renderConfig({ defaultProjectDir: defaultProjectDir() });
|
|
2316
|
+
const fmMatch = existing.match(/^(---\n)([\s\S]*?)\n(---)/);
|
|
2317
|
+
if (!fmMatch) {
|
|
2318
|
+
const content = `---
|
|
2319
|
+
version: "2.0"
|
|
2320
|
+
defaultProjectDir: ${defaultProjectDir()}
|
|
2321
|
+
${backupBlock}
|
|
2322
|
+
---
|
|
2323
|
+
${existing}`;
|
|
2324
|
+
await writeFileForce(configPath, content.replace(/\n\n---/, "\n---"));
|
|
2325
|
+
return;
|
|
2326
|
+
}
|
|
2327
|
+
const fmBlock = fmMatch[2];
|
|
2328
|
+
const afterFrontmatter = existing.slice(fmMatch[0].length);
|
|
2329
|
+
const cleanedFm = stripTopLevelBlock(fmBlock, "backup");
|
|
2330
|
+
const newFm = `${cleanedFm}
|
|
2331
|
+
${backupBlock}`.replace(/^\n+/, "");
|
|
2332
|
+
const normalizedFm = newFm.replace(/\n+$/, "");
|
|
2333
|
+
const newContent = `---
|
|
2334
|
+
${normalizedFm}
|
|
2335
|
+
---${afterFrontmatter}`;
|
|
2336
|
+
await writeFileForce(configPath, newContent);
|
|
1431
2337
|
}
|
|
1432
2338
|
async function readConfig() {
|
|
1433
2339
|
const configPath = resolve6(syntaurRoot(), "config.md");
|
|
@@ -1504,6 +2410,9 @@ async function readConfig() {
|
|
|
1504
2410
|
workspaceVisibility: parseWorkspaceVisibilityConfig(fmBlock)
|
|
1505
2411
|
};
|
|
1506
2412
|
}
|
|
2413
|
+
function getAssignmentTypes(config) {
|
|
2414
|
+
return config.types ?? DEFAULT_ASSIGNMENT_TYPES;
|
|
2415
|
+
}
|
|
1507
2416
|
function getAgents(config) {
|
|
1508
2417
|
if (config.agents === null) return BUILTIN_AGENTS;
|
|
1509
2418
|
const builtinById = new Map(BUILTIN_AGENTS.map((a) => [a.id, a]));
|
|
@@ -1550,7 +2459,18 @@ function getTerminal(config) {
|
|
|
1550
2459
|
}
|
|
1551
2460
|
return "terminal-app";
|
|
1552
2461
|
}
|
|
1553
|
-
|
|
2462
|
+
async function updateAgentsConfig(mutation, options = {}) {
|
|
2463
|
+
const config = await readConfig();
|
|
2464
|
+
const previous = config.agents ?? [...BUILTIN_AGENTS];
|
|
2465
|
+
const next = mutation.apply(previous);
|
|
2466
|
+
validateAgentList(next);
|
|
2467
|
+
if (options.dryRun) {
|
|
2468
|
+
return { previous, next, written: false };
|
|
2469
|
+
}
|
|
2470
|
+
await writeAgentsConfig(next);
|
|
2471
|
+
return { previous, next, written: true };
|
|
2472
|
+
}
|
|
2473
|
+
var DEFAULT_DERIVE_CONFIG, DEFAULT_ASSIGNMENT_TYPES, DEFAULT_CONFIG, AUTO_CREATE_WORKTREE_VALUES, AgentConfigError, DEFAULT_STATUS_COLORS, KNOWN_AGENT_SCALAR_FIELDS, migratedConfigPaths, TerminalConfigError;
|
|
1554
2474
|
var init_config2 = __esm({
|
|
1555
2475
|
"src/utils/config.ts"() {
|
|
1556
2476
|
"use strict";
|
|
@@ -1564,6 +2484,45 @@ var init_config2 = __esm({
|
|
|
1564
2484
|
init_slug();
|
|
1565
2485
|
init_terminal_schema();
|
|
1566
2486
|
init_workspace_visibility_schema();
|
|
2487
|
+
DEFAULT_DERIVE_CONFIG = {
|
|
2488
|
+
phaseLadder: [
|
|
2489
|
+
{ phase: "draft", when: "*", next: "Fill in the objective and acceptance criteria" },
|
|
2490
|
+
{
|
|
2491
|
+
// planExists-but-not-approved also sits here: the default status set has
|
|
2492
|
+
// no `planning` id. Users who define one add a `planExists:true` rung.
|
|
2493
|
+
phase: "ready_for_planning",
|
|
2494
|
+
when: "hasRealObjective:true AND acRealTotal > 0",
|
|
2495
|
+
next: "Write a plan and get it approved"
|
|
2496
|
+
},
|
|
2497
|
+
{ phase: "ready_to_implement", when: "planApproved:true", next: "Start implementing" },
|
|
2498
|
+
{
|
|
2499
|
+
phase: "in_progress",
|
|
2500
|
+
when: "planApproved:true AND implementationStarted:true",
|
|
2501
|
+
next: "Finish acceptance criteria, then request review"
|
|
2502
|
+
},
|
|
2503
|
+
{
|
|
2504
|
+
phase: "review",
|
|
2505
|
+
when: "acAllChecked:true OR reviewRequested:true",
|
|
2506
|
+
next: "Complete, or address review feedback"
|
|
2507
|
+
}
|
|
2508
|
+
],
|
|
2509
|
+
disposition: [
|
|
2510
|
+
{ when: "parked:true", is: "parked" },
|
|
2511
|
+
{ when: "blocked:true", is: "blocked" },
|
|
2512
|
+
{ when: null, is: "active" }
|
|
2513
|
+
],
|
|
2514
|
+
headline: { terminal: "passthrough", parked: "parked", blocked: "blocked", active: "phase" }
|
|
2515
|
+
};
|
|
2516
|
+
DEFAULT_ASSIGNMENT_TYPES = {
|
|
2517
|
+
definitions: [
|
|
2518
|
+
{ id: "feature", label: "Feature" },
|
|
2519
|
+
{ id: "bug", label: "Bug" },
|
|
2520
|
+
{ id: "refactor", label: "Refactor" },
|
|
2521
|
+
{ id: "research", label: "Research" },
|
|
2522
|
+
{ id: "chore", label: "Chore" }
|
|
2523
|
+
],
|
|
2524
|
+
default: "feature"
|
|
2525
|
+
};
|
|
1567
2526
|
DEFAULT_CONFIG = {
|
|
1568
2527
|
version: "2.0",
|
|
1569
2528
|
defaultProjectDir: defaultProjectDir(),
|
|
@@ -1613,7 +2572,8 @@ var init_config2 = __esm({
|
|
|
1613
2572
|
"default",
|
|
1614
2573
|
"resolveFromShellAliases",
|
|
1615
2574
|
"model",
|
|
1616
|
-
"playbook"
|
|
2575
|
+
"playbook",
|
|
2576
|
+
"launchPrompt"
|
|
1617
2577
|
]);
|
|
1618
2578
|
migratedConfigPaths = /* @__PURE__ */ new Set();
|
|
1619
2579
|
TerminalConfigError = class extends Error {
|
|
@@ -1701,6 +2661,22 @@ var init_assignment_resolver = __esm({
|
|
|
1701
2661
|
// src/utils/playbooks.ts
|
|
1702
2662
|
import { resolve as resolve8 } from "path";
|
|
1703
2663
|
import { readdir as readdir4, readFile as readFile7, unlink } from "fs/promises";
|
|
2664
|
+
function isVisiblePlaybookFile(name, isFile) {
|
|
2665
|
+
return isFile && name.endsWith(".md") && !name.startsWith("_") && name !== "manifest.md";
|
|
2666
|
+
}
|
|
2667
|
+
async function listPlaybookSlugs(playbooksDir2) {
|
|
2668
|
+
const slugs = /* @__PURE__ */ new Set();
|
|
2669
|
+
if (!await fileExists(playbooksDir2)) return slugs;
|
|
2670
|
+
const entries = await readdir4(playbooksDir2, { withFileTypes: true });
|
|
2671
|
+
for (const entry of entries) {
|
|
2672
|
+
if (!isVisiblePlaybookFile(entry.name, entry.isFile())) continue;
|
|
2673
|
+
const filePath = resolve8(playbooksDir2, entry.name);
|
|
2674
|
+
const raw = await readFile7(filePath, "utf-8");
|
|
2675
|
+
const parsed = parsePlaybook(raw);
|
|
2676
|
+
slugs.add(parsed.slug || entry.name.replace(/\.md$/, ""));
|
|
2677
|
+
}
|
|
2678
|
+
return slugs;
|
|
2679
|
+
}
|
|
1704
2680
|
var init_playbooks = __esm({
|
|
1705
2681
|
"src/utils/playbooks.ts"() {
|
|
1706
2682
|
"use strict";
|
|
@@ -1965,62 +2941,928 @@ var init_overviewCopy = __esm({
|
|
|
1965
2941
|
}
|
|
1966
2942
|
});
|
|
1967
2943
|
|
|
1968
|
-
// src/
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
2944
|
+
// src/lifecycle/facts.ts
|
|
2945
|
+
var facts_exports = {};
|
|
2946
|
+
__export(facts_exports, {
|
|
2947
|
+
areDependenciesSatisfied: () => areDependenciesSatisfied,
|
|
2948
|
+
computeFacts: () => computeFacts,
|
|
2949
|
+
countRealAcceptanceCriteria: () => countRealAcceptanceCriteria,
|
|
2950
|
+
countUnresolvedQuestions: () => countUnresolvedQuestions,
|
|
2951
|
+
hasRealObjective: () => hasRealObjective,
|
|
2952
|
+
isPlanApproved: () => isPlanApproved,
|
|
2953
|
+
latestPlanFile: () => latestPlanFile,
|
|
2954
|
+
planDigest: () => planDigest
|
|
2955
|
+
});
|
|
2956
|
+
import { createHash } from "crypto";
|
|
2957
|
+
import { readdir as readdir6, readFile as readFile9 } from "fs/promises";
|
|
2958
|
+
import { resolve as resolve11 } from "path";
|
|
2959
|
+
function sectionBody(body, heading) {
|
|
2960
|
+
const re = new RegExp(`^##\\s+${heading}\\s*$`, "m");
|
|
2961
|
+
const m = body.match(re);
|
|
2962
|
+
if (!m || m.index === void 0) return null;
|
|
2963
|
+
const start = m.index + m[0].length;
|
|
2964
|
+
const rest = body.slice(start);
|
|
2965
|
+
const next = rest.search(/^##\s+/m);
|
|
2966
|
+
return next >= 0 ? rest.slice(0, next) : rest;
|
|
1973
2967
|
}
|
|
1974
|
-
function
|
|
1975
|
-
|
|
1976
|
-
|
|
2968
|
+
function hasRealObjective(body) {
|
|
2969
|
+
const section = sectionBody(body, "Objective");
|
|
2970
|
+
if (section === null) return false;
|
|
2971
|
+
return section.replace(HTML_COMMENT_RE, "").trim().length > 0;
|
|
1977
2972
|
}
|
|
1978
|
-
|
|
1979
|
-
const
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
2973
|
+
function countRealAcceptanceCriteria(body) {
|
|
2974
|
+
const section = sectionBody(body, "Acceptance Criteria");
|
|
2975
|
+
if (section === null) return { total: 0, checked: 0 };
|
|
2976
|
+
let total = 0;
|
|
2977
|
+
let checked = 0;
|
|
2978
|
+
for (const line of section.split("\n")) {
|
|
2979
|
+
const m = line.match(/^\s*-\s*\[([ xX])\]\s*(.*)$/);
|
|
2980
|
+
if (!m) continue;
|
|
2981
|
+
const content = m[2].replace(HTML_COMMENT_RE, "").trim();
|
|
2982
|
+
if (content.length === 0) continue;
|
|
2983
|
+
total++;
|
|
2984
|
+
if (m[1].toLowerCase() === "x") checked++;
|
|
2985
|
+
}
|
|
2986
|
+
return { total, checked };
|
|
1986
2987
|
}
|
|
1987
|
-
async function
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
2988
|
+
async function latestPlanFile(assignmentDir) {
|
|
2989
|
+
let entries;
|
|
2990
|
+
try {
|
|
2991
|
+
entries = await readdir6(assignmentDir);
|
|
2992
|
+
} catch {
|
|
2993
|
+
return null;
|
|
2994
|
+
}
|
|
2995
|
+
let best = null;
|
|
2996
|
+
for (const name of entries) {
|
|
2997
|
+
const m = name.match(PLAN_FILE_RE);
|
|
2998
|
+
if (!m) continue;
|
|
2999
|
+
const version = m[1] ? parseInt(m[1], 10) : 1;
|
|
3000
|
+
if (!best || version > best.version) best = { name, version };
|
|
3001
|
+
}
|
|
3002
|
+
return best?.name ?? null;
|
|
3003
|
+
}
|
|
3004
|
+
function planDigest(content) {
|
|
3005
|
+
return createHash("sha256").update(content, "utf-8").digest("hex");
|
|
3006
|
+
}
|
|
3007
|
+
async function isPlanApproved(assignmentDir, frontmatter) {
|
|
3008
|
+
const approval = frontmatter.planApproval;
|
|
3009
|
+
if (!approval) return false;
|
|
3010
|
+
const latest = await latestPlanFile(assignmentDir);
|
|
3011
|
+
if (!latest || latest !== approval.file) return false;
|
|
3012
|
+
try {
|
|
3013
|
+
const content = await readFile9(resolve11(assignmentDir, latest), "utf-8");
|
|
3014
|
+
return planDigest(content) === approval.digest;
|
|
3015
|
+
} catch {
|
|
3016
|
+
return false;
|
|
3017
|
+
}
|
|
3018
|
+
}
|
|
3019
|
+
async function countUnresolvedQuestions(assignmentDir) {
|
|
3020
|
+
const commentsPath = resolve11(assignmentDir, "comments.md");
|
|
3021
|
+
if (!await fileExists(commentsPath)) return 0;
|
|
3022
|
+
try {
|
|
3023
|
+
const content = await readFile9(commentsPath, "utf-8");
|
|
3024
|
+
let count = 0;
|
|
3025
|
+
for (const block of content.split(/^##\s+/m).slice(1)) {
|
|
3026
|
+
if (/^\*\*Type:\*\*\s*question\s*$/m.test(block) && /^\*\*Resolved:\*\*\s*false\s*$/m.test(block)) {
|
|
3027
|
+
count++;
|
|
3028
|
+
}
|
|
3029
|
+
}
|
|
3030
|
+
return count;
|
|
3031
|
+
} catch {
|
|
3032
|
+
return 0;
|
|
3033
|
+
}
|
|
3034
|
+
}
|
|
3035
|
+
async function areDependenciesSatisfied(projectDir, dependsOn, terminalStatuses) {
|
|
3036
|
+
if (dependsOn.length === 0 || projectDir === null) return true;
|
|
3037
|
+
for (const depSlug of dependsOn) {
|
|
3038
|
+
const depPath = resolve11(projectDir, "assignments", depSlug, "assignment.md");
|
|
3039
|
+
if (!await fileExists(depPath)) return false;
|
|
1997
3040
|
try {
|
|
1998
|
-
const content = await readFile9(
|
|
1999
|
-
const
|
|
2000
|
-
|
|
3041
|
+
const content = await readFile9(depPath, "utf-8");
|
|
3042
|
+
const m = content.match(/^status:\s*(.+)$/m);
|
|
3043
|
+
const status = m ? m[1].trim() : "";
|
|
3044
|
+
if (!terminalStatuses.has(status)) return false;
|
|
2001
3045
|
} catch {
|
|
3046
|
+
return false;
|
|
2002
3047
|
}
|
|
2003
3048
|
}
|
|
2004
|
-
|
|
2005
|
-
return records;
|
|
3049
|
+
return true;
|
|
2006
3050
|
}
|
|
2007
|
-
function
|
|
2008
|
-
|
|
2009
|
-
const
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
3051
|
+
async function computeFacts(input) {
|
|
3052
|
+
const { assignmentDir, frontmatter, body, projectDir, terminalStatuses } = input;
|
|
3053
|
+
const ac = countRealAcceptanceCriteria(body);
|
|
3054
|
+
const [planFile, planApproved, unresolvedQuestions, depsSatisfied] = await Promise.all([
|
|
3055
|
+
latestPlanFile(assignmentDir),
|
|
3056
|
+
isPlanApproved(assignmentDir, frontmatter),
|
|
3057
|
+
countUnresolvedQuestions(assignmentDir),
|
|
3058
|
+
areDependenciesSatisfied(projectDir, frontmatter.dependsOn, terminalStatuses)
|
|
3059
|
+
]);
|
|
3060
|
+
return {
|
|
3061
|
+
hasRealObjective: hasRealObjective(body),
|
|
3062
|
+
acRealTotal: ac.total,
|
|
3063
|
+
acRealChecked: ac.checked,
|
|
3064
|
+
acAllChecked: ac.total > 0 && ac.checked === ac.total,
|
|
3065
|
+
planExists: planFile !== null,
|
|
3066
|
+
planApproved,
|
|
3067
|
+
workspaceSet: frontmatter.workspace.repository !== null && frontmatter.workspace.branch !== null,
|
|
3068
|
+
implementationStarted: frontmatter.implementationStarted,
|
|
3069
|
+
depsSatisfied,
|
|
3070
|
+
unresolvedQuestions,
|
|
3071
|
+
blocked: frontmatter.blockedReason !== null,
|
|
3072
|
+
parked: frontmatter.parked,
|
|
3073
|
+
reviewRequested: frontmatter.reviewRequested,
|
|
3074
|
+
pinned: frontmatter.override !== null
|
|
3075
|
+
};
|
|
2020
3076
|
}
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
3077
|
+
var HTML_COMMENT_RE, PLAN_FILE_RE;
|
|
3078
|
+
var init_facts = __esm({
|
|
3079
|
+
"src/lifecycle/facts.ts"() {
|
|
3080
|
+
"use strict";
|
|
3081
|
+
init_fs();
|
|
3082
|
+
HTML_COMMENT_RE = /<!--[\s\S]*?-->/g;
|
|
3083
|
+
PLAN_FILE_RE = /^plan(?:-v(\d+))?\.md$/;
|
|
3084
|
+
}
|
|
3085
|
+
});
|
|
3086
|
+
|
|
3087
|
+
// src/utils/query/fields.ts
|
|
3088
|
+
function resolveField(registry, name) {
|
|
3089
|
+
return registry[name.toLowerCase()] ?? null;
|
|
3090
|
+
}
|
|
3091
|
+
function readField(def, fieldName, item) {
|
|
3092
|
+
if (def.get) return def.get(item);
|
|
3093
|
+
return item[fieldName] ?? item[fieldName.toLowerCase()];
|
|
3094
|
+
}
|
|
3095
|
+
var init_fields = __esm({
|
|
3096
|
+
"src/utils/query/fields.ts"() {
|
|
3097
|
+
"use strict";
|
|
3098
|
+
}
|
|
3099
|
+
});
|
|
3100
|
+
|
|
3101
|
+
// src/utils/query/evaluate.ts
|
|
3102
|
+
function localDayBounds(date) {
|
|
3103
|
+
const [y, m, d] = date.split("-").map((n) => parseInt(n, 10));
|
|
3104
|
+
const start = new Date(y, m - 1, d).getTime();
|
|
3105
|
+
const end = new Date(y, m - 1, d + 1).getTime();
|
|
3106
|
+
return [start, end];
|
|
3107
|
+
}
|
|
3108
|
+
function toEpoch(value) {
|
|
3109
|
+
if (typeof value === "number") return Number.isFinite(value) ? value : null;
|
|
3110
|
+
if (typeof value === "string" && value.length > 0) {
|
|
3111
|
+
const t = Date.parse(value);
|
|
3112
|
+
return Number.isNaN(t) ? null : t;
|
|
3113
|
+
}
|
|
3114
|
+
return null;
|
|
3115
|
+
}
|
|
3116
|
+
function toNumber(value) {
|
|
3117
|
+
if (typeof value === "number") return Number.isFinite(value) ? value : null;
|
|
3118
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
3119
|
+
const n = Number(value);
|
|
3120
|
+
return Number.isFinite(n) ? n : null;
|
|
3121
|
+
}
|
|
3122
|
+
return null;
|
|
3123
|
+
}
|
|
3124
|
+
function ciEquals(a, b) {
|
|
3125
|
+
return typeof a === "string" && a.toLowerCase() === b.toLowerCase();
|
|
3126
|
+
}
|
|
3127
|
+
function isNone(value) {
|
|
3128
|
+
return value === null || value === void 0 || value === "";
|
|
3129
|
+
}
|
|
3130
|
+
function compileEquality(def, field, value, atomPos) {
|
|
3131
|
+
switch (def.kind) {
|
|
3132
|
+
case "enum":
|
|
3133
|
+
case "string":
|
|
3134
|
+
if (def.noneSentinel && value.raw.toLowerCase() === "none") {
|
|
3135
|
+
return (item) => isNone(readField(def, field, item));
|
|
3136
|
+
}
|
|
3137
|
+
return (item) => ciEquals(readField(def, field, item), value.raw);
|
|
3138
|
+
case "substring":
|
|
3139
|
+
return (item) => {
|
|
3140
|
+
const v = readField(def, field, item);
|
|
3141
|
+
return typeof v === "string" && v.toLowerCase().includes(value.raw.toLowerCase());
|
|
3142
|
+
};
|
|
3143
|
+
case "bool": {
|
|
3144
|
+
const want = value.raw.toLowerCase();
|
|
3145
|
+
if (want !== "true" && want !== "false") {
|
|
3146
|
+
throw new CompileError([
|
|
3147
|
+
{ pos: value.pos, message: `Field "${field}" is boolean \u2014 use ${field}:true or ${field}:false` }
|
|
3148
|
+
]);
|
|
3149
|
+
}
|
|
3150
|
+
const expected = want === "true";
|
|
3151
|
+
return (item) => {
|
|
3152
|
+
const v = readField(def, field, item);
|
|
3153
|
+
const b = typeof v === "boolean" ? v : v === "true" ? true : v === "false" || v === null || v === void 0 || v === "" ? false : null;
|
|
3154
|
+
return b !== null && b === expected;
|
|
3155
|
+
};
|
|
3156
|
+
}
|
|
3157
|
+
case "number": {
|
|
3158
|
+
const n = value.num ?? toNumber(value.raw);
|
|
3159
|
+
if (n === null) {
|
|
3160
|
+
throw new CompileError([{ pos: value.pos, message: `Field "${field}" is numeric \u2014 "${value.raw}" is not a number` }]);
|
|
3161
|
+
}
|
|
3162
|
+
return (item) => toNumber(readField(def, field, item)) === n;
|
|
3163
|
+
}
|
|
3164
|
+
case "ordinal":
|
|
3165
|
+
return (item) => ciEquals(readField(def, field, item), value.raw);
|
|
3166
|
+
case "list":
|
|
3167
|
+
return (item) => {
|
|
3168
|
+
const v = readField(def, field, item);
|
|
3169
|
+
return Array.isArray(v) && v.some((el) => ciEquals(el, value.raw));
|
|
3170
|
+
};
|
|
3171
|
+
case "timestamp": {
|
|
3172
|
+
if (value.type === "date") {
|
|
3173
|
+
const [start, end] = localDayBounds(value.raw);
|
|
3174
|
+
return (item) => {
|
|
3175
|
+
const t = toEpoch(readField(def, field, item));
|
|
3176
|
+
return t !== null && t >= start && t < end;
|
|
3177
|
+
};
|
|
3178
|
+
}
|
|
3179
|
+
throw new CompileError([
|
|
3180
|
+
{ pos: value.pos, message: `Field "${field}" is a timestamp \u2014 use a comparison (e.g. ${field} > -36h) or an absolute date (${field}:2026-06-01)` }
|
|
3181
|
+
]);
|
|
3182
|
+
}
|
|
3183
|
+
case "duration":
|
|
3184
|
+
throw new CompileError([
|
|
3185
|
+
{ pos: atomPos, message: `Field "${field}" is a duration \u2014 use a comparison (e.g. ${field} > 3d)` }
|
|
3186
|
+
]);
|
|
3187
|
+
}
|
|
3188
|
+
}
|
|
3189
|
+
function compileComparison(def, field, op, value) {
|
|
3190
|
+
const cmp = (a, b) => {
|
|
3191
|
+
switch (op) {
|
|
3192
|
+
case "<":
|
|
3193
|
+
return a < b;
|
|
3194
|
+
case ">":
|
|
3195
|
+
return a > b;
|
|
3196
|
+
case "<=":
|
|
3197
|
+
return a <= b;
|
|
3198
|
+
case ">=":
|
|
3199
|
+
return a >= b;
|
|
3200
|
+
case "=":
|
|
3201
|
+
return a === b;
|
|
3202
|
+
case "!=":
|
|
3203
|
+
return a !== b;
|
|
3204
|
+
default:
|
|
3205
|
+
return false;
|
|
3206
|
+
}
|
|
3207
|
+
};
|
|
3208
|
+
switch (def.kind) {
|
|
3209
|
+
case "number": {
|
|
3210
|
+
const n = value.num ?? toNumber(value.raw);
|
|
3211
|
+
if (n === null) {
|
|
3212
|
+
throw new CompileError([{ pos: value.pos, message: `"${value.raw}" is not a number (field "${field}")` }]);
|
|
3213
|
+
}
|
|
3214
|
+
return (item) => {
|
|
3215
|
+
const v = toNumber(readField(def, field, item));
|
|
3216
|
+
return v !== null && cmp(v, n);
|
|
3217
|
+
};
|
|
3218
|
+
}
|
|
3219
|
+
case "ordinal": {
|
|
3220
|
+
const order = def.order ?? [];
|
|
3221
|
+
const idx = order.findIndex((o) => o.toLowerCase() === value.raw.toLowerCase());
|
|
3222
|
+
if (idx < 0) {
|
|
3223
|
+
throw new CompileError([
|
|
3224
|
+
{ pos: value.pos, message: `"${value.raw}" is not a valid ${field} (expected one of: ${order.join(", ")})` }
|
|
3225
|
+
]);
|
|
3226
|
+
}
|
|
3227
|
+
return (item) => {
|
|
3228
|
+
const raw = readField(def, field, item);
|
|
3229
|
+
const vIdx = typeof raw === "string" ? order.findIndex((o) => o.toLowerCase() === raw.toLowerCase()) : -1;
|
|
3230
|
+
return vIdx >= 0 && cmp(vIdx, idx);
|
|
3231
|
+
};
|
|
3232
|
+
}
|
|
3233
|
+
case "timestamp": {
|
|
3234
|
+
if (value.type === "duration") {
|
|
3235
|
+
const sign = value.sign === 0 ? -1 : value.sign ?? -1;
|
|
3236
|
+
const offset = sign * (value.num ?? 0);
|
|
3237
|
+
return (item, ctx) => {
|
|
3238
|
+
const t = toEpoch(readField(def, field, item));
|
|
3239
|
+
return t !== null && cmp(t, ctx.now + offset);
|
|
3240
|
+
};
|
|
3241
|
+
}
|
|
3242
|
+
if (value.type === "date") {
|
|
3243
|
+
const [start, end] = localDayBounds(value.raw);
|
|
3244
|
+
return (item) => {
|
|
3245
|
+
const t = toEpoch(readField(def, field, item));
|
|
3246
|
+
if (t === null) return false;
|
|
3247
|
+
switch (op) {
|
|
3248
|
+
case "<":
|
|
3249
|
+
return t < start;
|
|
3250
|
+
case "<=":
|
|
3251
|
+
return t < end;
|
|
3252
|
+
case ">":
|
|
3253
|
+
return t >= end;
|
|
3254
|
+
case ">=":
|
|
3255
|
+
return t >= start;
|
|
3256
|
+
case "=":
|
|
3257
|
+
return t >= start && t < end;
|
|
3258
|
+
case "!=":
|
|
3259
|
+
return t < start || t >= end;
|
|
3260
|
+
default:
|
|
3261
|
+
return false;
|
|
3262
|
+
}
|
|
3263
|
+
};
|
|
3264
|
+
}
|
|
3265
|
+
throw new CompileError([
|
|
3266
|
+
{ pos: value.pos, message: `Compare timestamp field "${field}" to a duration (e.g. -36h) or a date (YYYY-MM-DD)` }
|
|
3267
|
+
]);
|
|
3268
|
+
}
|
|
3269
|
+
case "duration": {
|
|
3270
|
+
if (value.type !== "duration") {
|
|
3271
|
+
throw new CompileError([
|
|
3272
|
+
{ pos: value.pos, message: `Compare duration field "${field}" to a duration literal (e.g. 3d)` }
|
|
3273
|
+
]);
|
|
3274
|
+
}
|
|
3275
|
+
const magnitude = value.num ?? 0;
|
|
3276
|
+
return (item) => {
|
|
3277
|
+
const v = toNumber(readField(def, field, item));
|
|
3278
|
+
return v !== null && cmp(v, magnitude);
|
|
3279
|
+
};
|
|
3280
|
+
}
|
|
3281
|
+
case "enum":
|
|
3282
|
+
case "string":
|
|
3283
|
+
case "substring":
|
|
3284
|
+
case "list": {
|
|
3285
|
+
if (op === "=") {
|
|
3286
|
+
return compileEquality(def, field, value, value.pos);
|
|
3287
|
+
}
|
|
3288
|
+
if (op === "!=") {
|
|
3289
|
+
const eq = compileEquality(def, field, value, value.pos);
|
|
3290
|
+
return (item, ctx) => !eq(item, ctx);
|
|
3291
|
+
}
|
|
3292
|
+
throw new CompileError([
|
|
3293
|
+
{ pos: value.pos, message: `Field "${field}" does not support ordering comparisons (use ":" or "=").` }
|
|
3294
|
+
]);
|
|
3295
|
+
}
|
|
3296
|
+
case "bool": {
|
|
3297
|
+
if (op === "=" || op === "!=") {
|
|
3298
|
+
const eq = compileEquality(def, field, value, value.pos);
|
|
3299
|
+
return op === "=" ? eq : (item, ctx) => !eq(item, ctx);
|
|
3300
|
+
}
|
|
3301
|
+
throw new CompileError([{ pos: value.pos, message: `Field "${field}" is boolean \u2014 use ${field}:true / ${field}:false` }]);
|
|
3302
|
+
}
|
|
3303
|
+
}
|
|
3304
|
+
}
|
|
3305
|
+
function compileAtom(atom, registry) {
|
|
3306
|
+
const def = resolveField(registry, atom.field);
|
|
3307
|
+
if (!def) {
|
|
3308
|
+
throw new CompileError([{ pos: atom.pos, message: `Unknown field "${atom.field}"` }]);
|
|
3309
|
+
}
|
|
3310
|
+
if (atom.op === ":") {
|
|
3311
|
+
const preds = atom.values.map((v) => compileEquality(def, atom.field, v, atom.pos));
|
|
3312
|
+
if (preds.length === 1) return preds[0];
|
|
3313
|
+
return (item, ctx) => preds.some((p) => p(item, ctx));
|
|
3314
|
+
}
|
|
3315
|
+
return compileComparison(def, atom.field, atom.op, atom.values[0]);
|
|
3316
|
+
}
|
|
3317
|
+
function compileNode(node, registry) {
|
|
3318
|
+
switch (node.kind) {
|
|
3319
|
+
case "all":
|
|
3320
|
+
return () => true;
|
|
3321
|
+
case "atom":
|
|
3322
|
+
return compileAtom(node, registry);
|
|
3323
|
+
case "not": {
|
|
3324
|
+
const inner = compileNode(node.child, registry);
|
|
3325
|
+
return (item, ctx) => !inner(item, ctx);
|
|
3326
|
+
}
|
|
3327
|
+
case "and": {
|
|
3328
|
+
const preds = node.children.map((c) => compileNode(c, registry));
|
|
3329
|
+
return (item, ctx) => preds.every((p) => p(item, ctx));
|
|
3330
|
+
}
|
|
3331
|
+
case "or": {
|
|
3332
|
+
const preds = node.children.map((c) => compileNode(c, registry));
|
|
3333
|
+
return (item, ctx) => preds.some((p) => p(item, ctx));
|
|
3334
|
+
}
|
|
3335
|
+
}
|
|
3336
|
+
}
|
|
3337
|
+
var CompileError;
|
|
3338
|
+
var init_evaluate = __esm({
|
|
3339
|
+
"src/utils/query/evaluate.ts"() {
|
|
3340
|
+
"use strict";
|
|
3341
|
+
init_fields();
|
|
3342
|
+
CompileError = class extends Error {
|
|
3343
|
+
constructor(errors) {
|
|
3344
|
+
super(errors.map((e) => `${e.message} (at ${e.pos})`).join("; "));
|
|
3345
|
+
this.errors = errors;
|
|
3346
|
+
this.name = "CompileError";
|
|
3347
|
+
}
|
|
3348
|
+
};
|
|
3349
|
+
}
|
|
3350
|
+
});
|
|
3351
|
+
|
|
3352
|
+
// src/utils/query/lexer.ts
|
|
3353
|
+
function lex(input) {
|
|
3354
|
+
const tokens = [];
|
|
3355
|
+
let i = 0;
|
|
3356
|
+
const numberOrDuration = (start, sign) => {
|
|
3357
|
+
let j = i;
|
|
3358
|
+
while (j < input.length && /\d/.test(input[j])) j++;
|
|
3359
|
+
const digits = input.slice(i, j);
|
|
3360
|
+
let unit = "";
|
|
3361
|
+
while (j < input.length && /[a-z]/i.test(input[j])) {
|
|
3362
|
+
unit += input[j];
|
|
3363
|
+
j++;
|
|
3364
|
+
}
|
|
3365
|
+
i = j;
|
|
3366
|
+
if (unit.length > 0) {
|
|
3367
|
+
const ms = DURATION_MS[unit.toLowerCase()];
|
|
3368
|
+
if (ms === void 0) {
|
|
3369
|
+
throw new LexError(start, `Unknown duration unit "${unit}" (expected h, d, w, m, mo, or y)`);
|
|
3370
|
+
}
|
|
3371
|
+
return {
|
|
3372
|
+
type: "DURATION",
|
|
3373
|
+
text: input.slice(start, j),
|
|
3374
|
+
pos: start,
|
|
3375
|
+
num: parseInt(digits, 10) * ms,
|
|
3376
|
+
sign
|
|
3377
|
+
};
|
|
3378
|
+
}
|
|
3379
|
+
if (sign !== 0) {
|
|
3380
|
+
return { type: "NUMBER", text: input.slice(start, j), pos: start, num: sign * parseInt(digits, 10) };
|
|
3381
|
+
}
|
|
3382
|
+
return { type: "NUMBER", text: digits, pos: start, num: parseInt(digits, 10) };
|
|
3383
|
+
};
|
|
3384
|
+
while (i < input.length) {
|
|
3385
|
+
const c = input[i];
|
|
3386
|
+
const start = i;
|
|
3387
|
+
if (c === " " || c === " " || c === "\n" || c === "\r") {
|
|
3388
|
+
i++;
|
|
3389
|
+
continue;
|
|
3390
|
+
}
|
|
3391
|
+
if (c === "(") {
|
|
3392
|
+
tokens.push({ type: "LPAREN", text: c, pos: start });
|
|
3393
|
+
i++;
|
|
3394
|
+
continue;
|
|
3395
|
+
}
|
|
3396
|
+
if (c === ")") {
|
|
3397
|
+
tokens.push({ type: "RPAREN", text: c, pos: start });
|
|
3398
|
+
i++;
|
|
3399
|
+
continue;
|
|
3400
|
+
}
|
|
3401
|
+
if (c === ",") {
|
|
3402
|
+
tokens.push({ type: "COMMA", text: c, pos: start });
|
|
3403
|
+
i++;
|
|
3404
|
+
continue;
|
|
3405
|
+
}
|
|
3406
|
+
if (c === ":") {
|
|
3407
|
+
tokens.push({ type: "COLON", text: c, pos: start });
|
|
3408
|
+
i++;
|
|
3409
|
+
continue;
|
|
3410
|
+
}
|
|
3411
|
+
if (c === "*") {
|
|
3412
|
+
tokens.push({ type: "STAR", text: c, pos: start });
|
|
3413
|
+
i++;
|
|
3414
|
+
continue;
|
|
3415
|
+
}
|
|
3416
|
+
if (c === "<" || c === ">") {
|
|
3417
|
+
if (input[i + 1] === "=") {
|
|
3418
|
+
tokens.push({ type: "OP", text: c + "=", pos: start });
|
|
3419
|
+
i += 2;
|
|
3420
|
+
} else {
|
|
3421
|
+
tokens.push({ type: "OP", text: c, pos: start });
|
|
3422
|
+
i++;
|
|
3423
|
+
}
|
|
3424
|
+
continue;
|
|
3425
|
+
}
|
|
3426
|
+
if (c === "!") {
|
|
3427
|
+
if (input[i + 1] === "=") {
|
|
3428
|
+
tokens.push({ type: "OP", text: "!=", pos: start });
|
|
3429
|
+
i += 2;
|
|
3430
|
+
continue;
|
|
3431
|
+
}
|
|
3432
|
+
throw new LexError(start, `Unexpected "!" (did you mean "!="?)`);
|
|
3433
|
+
}
|
|
3434
|
+
if (c === "=") {
|
|
3435
|
+
i += input[i + 1] === "=" ? 2 : 1;
|
|
3436
|
+
tokens.push({ type: "OP", text: "=", pos: start });
|
|
3437
|
+
continue;
|
|
3438
|
+
}
|
|
3439
|
+
if (c === '"' || c === "'") {
|
|
3440
|
+
const quote = c;
|
|
3441
|
+
let j = i + 1;
|
|
3442
|
+
let out = "";
|
|
3443
|
+
while (j < input.length && input[j] !== quote) {
|
|
3444
|
+
if (input[j] === "\\" && j + 1 < input.length) {
|
|
3445
|
+
out += input[j + 1];
|
|
3446
|
+
j += 2;
|
|
3447
|
+
} else {
|
|
3448
|
+
out += input[j];
|
|
3449
|
+
j++;
|
|
3450
|
+
}
|
|
3451
|
+
}
|
|
3452
|
+
if (j >= input.length) throw new LexError(start, "Unterminated string literal");
|
|
3453
|
+
tokens.push({ type: "STRING", text: out, pos: start });
|
|
3454
|
+
i = j + 1;
|
|
3455
|
+
continue;
|
|
3456
|
+
}
|
|
3457
|
+
if (c === "-" || c === "+") {
|
|
3458
|
+
if (/\d/.test(input[i + 1] ?? "")) {
|
|
3459
|
+
const sign = c === "-" ? -1 : 1;
|
|
3460
|
+
i++;
|
|
3461
|
+
tokens.push(numberOrDuration(start, sign));
|
|
3462
|
+
continue;
|
|
3463
|
+
}
|
|
3464
|
+
if (c === "-") {
|
|
3465
|
+
tokens.push({ type: "MINUS", text: "-", pos: start });
|
|
3466
|
+
i++;
|
|
3467
|
+
continue;
|
|
3468
|
+
}
|
|
3469
|
+
throw new LexError(start, 'Unexpected "+"');
|
|
3470
|
+
}
|
|
3471
|
+
if (/\d/.test(c)) {
|
|
3472
|
+
const dateMatch = input.slice(i).match(DATE_RE);
|
|
3473
|
+
if (dateMatch) {
|
|
3474
|
+
tokens.push({ type: "DATE", text: dateMatch[0], pos: start });
|
|
3475
|
+
i += dateMatch[0].length;
|
|
3476
|
+
continue;
|
|
3477
|
+
}
|
|
3478
|
+
tokens.push(numberOrDuration(start, 0));
|
|
3479
|
+
continue;
|
|
3480
|
+
}
|
|
3481
|
+
if (IDENT_START.test(c)) {
|
|
3482
|
+
let j = i + 1;
|
|
3483
|
+
while (j < input.length && IDENT_CHAR.test(input[j])) j++;
|
|
3484
|
+
const word = input.slice(i, j);
|
|
3485
|
+
const kw = word.toLowerCase();
|
|
3486
|
+
if (kw === "and") tokens.push({ type: "AND", text: word, pos: start });
|
|
3487
|
+
else if (kw === "or") tokens.push({ type: "OR", text: word, pos: start });
|
|
3488
|
+
else if (kw === "not") tokens.push({ type: "NOT", text: word, pos: start });
|
|
3489
|
+
else tokens.push({ type: "IDENT", text: word, pos: start });
|
|
3490
|
+
i = j;
|
|
3491
|
+
continue;
|
|
3492
|
+
}
|
|
3493
|
+
throw new LexError(start, `Unexpected character "${c}"`);
|
|
3494
|
+
}
|
|
3495
|
+
tokens.push({ type: "EOF", text: "", pos: input.length });
|
|
3496
|
+
return tokens;
|
|
3497
|
+
}
|
|
3498
|
+
var LexError, DURATION_MS, IDENT_START, IDENT_CHAR, DATE_RE;
|
|
3499
|
+
var init_lexer = __esm({
|
|
3500
|
+
"src/utils/query/lexer.ts"() {
|
|
3501
|
+
"use strict";
|
|
3502
|
+
LexError = class extends Error {
|
|
3503
|
+
constructor(pos, message) {
|
|
3504
|
+
super(message);
|
|
3505
|
+
this.pos = pos;
|
|
3506
|
+
this.name = "LexError";
|
|
3507
|
+
}
|
|
3508
|
+
};
|
|
3509
|
+
DURATION_MS = {
|
|
3510
|
+
h: 36e5,
|
|
3511
|
+
d: 864e5,
|
|
3512
|
+
w: 7 * 864e5,
|
|
3513
|
+
m: 30 * 864e5,
|
|
3514
|
+
mo: 30 * 864e5,
|
|
3515
|
+
y: 365 * 864e5
|
|
3516
|
+
};
|
|
3517
|
+
IDENT_START = /[A-Za-z_]/;
|
|
3518
|
+
IDENT_CHAR = /[A-Za-z0-9_-]/;
|
|
3519
|
+
DATE_RE = /^\d{4}-\d{2}-\d{2}/;
|
|
3520
|
+
}
|
|
3521
|
+
});
|
|
3522
|
+
|
|
3523
|
+
// src/utils/query/parser.ts
|
|
3524
|
+
function parseQuery(input) {
|
|
3525
|
+
try {
|
|
3526
|
+
const tokens = lex(input);
|
|
3527
|
+
const ast = new Parser(tokens).parseQuery();
|
|
3528
|
+
return { ast, errors: [] };
|
|
3529
|
+
} catch (err) {
|
|
3530
|
+
if (err instanceof LexError || err instanceof ParseError) {
|
|
3531
|
+
return { ast: null, errors: [{ pos: err.pos, message: err.message }] };
|
|
3532
|
+
}
|
|
3533
|
+
throw err;
|
|
3534
|
+
}
|
|
3535
|
+
}
|
|
3536
|
+
var ParseError, VALUE_TOKENS, TERM_START, Parser;
|
|
3537
|
+
var init_parser3 = __esm({
|
|
3538
|
+
"src/utils/query/parser.ts"() {
|
|
3539
|
+
"use strict";
|
|
3540
|
+
init_lexer();
|
|
3541
|
+
ParseError = class extends Error {
|
|
3542
|
+
constructor(pos, message) {
|
|
3543
|
+
super(message);
|
|
3544
|
+
this.pos = pos;
|
|
3545
|
+
this.name = "ParseError";
|
|
3546
|
+
}
|
|
3547
|
+
};
|
|
3548
|
+
VALUE_TOKENS = /* @__PURE__ */ new Set(["IDENT", "STRING", "NUMBER", "DATE", "DURATION"]);
|
|
3549
|
+
TERM_START = /* @__PURE__ */ new Set(["IDENT", "NOT", "MINUS", "LPAREN", "STAR"]);
|
|
3550
|
+
Parser = class {
|
|
3551
|
+
constructor(tokens) {
|
|
3552
|
+
this.tokens = tokens;
|
|
3553
|
+
}
|
|
3554
|
+
pos = 0;
|
|
3555
|
+
peek() {
|
|
3556
|
+
return this.tokens[this.pos];
|
|
3557
|
+
}
|
|
3558
|
+
next() {
|
|
3559
|
+
return this.tokens[this.pos++];
|
|
3560
|
+
}
|
|
3561
|
+
expect(type, what) {
|
|
3562
|
+
const tok = this.peek();
|
|
3563
|
+
if (tok.type !== type) {
|
|
3564
|
+
throw new ParseError(tok.pos, `Expected ${what}, got "${tok.text || tok.type}"`);
|
|
3565
|
+
}
|
|
3566
|
+
return this.next();
|
|
3567
|
+
}
|
|
3568
|
+
parseQuery() {
|
|
3569
|
+
if (this.peek().type === "EOF") return { kind: "all" };
|
|
3570
|
+
const node = this.orExpr();
|
|
3571
|
+
const tok = this.peek();
|
|
3572
|
+
if (tok.type !== "EOF") {
|
|
3573
|
+
throw new ParseError(tok.pos, `Unexpected "${tok.text}" \u2014 unbalanced parentheses or stray token`);
|
|
3574
|
+
}
|
|
3575
|
+
return node;
|
|
3576
|
+
}
|
|
3577
|
+
orExpr() {
|
|
3578
|
+
const children = [this.andExpr()];
|
|
3579
|
+
while (this.peek().type === "OR") {
|
|
3580
|
+
this.next();
|
|
3581
|
+
children.push(this.andExpr());
|
|
3582
|
+
}
|
|
3583
|
+
return children.length === 1 ? children[0] : { kind: "or", children };
|
|
3584
|
+
}
|
|
3585
|
+
andExpr() {
|
|
3586
|
+
const children = [this.unary()];
|
|
3587
|
+
for (; ; ) {
|
|
3588
|
+
const tok = this.peek();
|
|
3589
|
+
if (tok.type === "AND") {
|
|
3590
|
+
this.next();
|
|
3591
|
+
children.push(this.unary());
|
|
3592
|
+
} else if (TERM_START.has(tok.type)) {
|
|
3593
|
+
children.push(this.unary());
|
|
3594
|
+
} else {
|
|
3595
|
+
break;
|
|
3596
|
+
}
|
|
3597
|
+
}
|
|
3598
|
+
return children.length === 1 ? children[0] : { kind: "and", children };
|
|
3599
|
+
}
|
|
3600
|
+
unary() {
|
|
3601
|
+
const tok = this.peek();
|
|
3602
|
+
if (tok.type === "NOT") {
|
|
3603
|
+
this.next();
|
|
3604
|
+
return { kind: "not", child: this.unary() };
|
|
3605
|
+
}
|
|
3606
|
+
if (tok.type === "MINUS") {
|
|
3607
|
+
this.next();
|
|
3608
|
+
const inner = this.peek();
|
|
3609
|
+
if (inner.type !== "IDENT") {
|
|
3610
|
+
throw new ParseError(inner.pos, 'Expected a field atom after "-" negation');
|
|
3611
|
+
}
|
|
3612
|
+
return { kind: "not", child: this.atom() };
|
|
3613
|
+
}
|
|
3614
|
+
return this.primary();
|
|
3615
|
+
}
|
|
3616
|
+
primary() {
|
|
3617
|
+
const tok = this.peek();
|
|
3618
|
+
if (tok.type === "LPAREN") {
|
|
3619
|
+
this.next();
|
|
3620
|
+
const node = this.orExpr();
|
|
3621
|
+
this.expect("RPAREN", '")"');
|
|
3622
|
+
return node;
|
|
3623
|
+
}
|
|
3624
|
+
if (tok.type === "STAR") {
|
|
3625
|
+
this.next();
|
|
3626
|
+
return { kind: "all" };
|
|
3627
|
+
}
|
|
3628
|
+
if (tok.type === "IDENT") {
|
|
3629
|
+
return this.atom();
|
|
3630
|
+
}
|
|
3631
|
+
throw new ParseError(tok.pos, `Expected a field, "(", "*", or NOT \u2014 got "${tok.text || "end of query"}"`);
|
|
3632
|
+
}
|
|
3633
|
+
atom() {
|
|
3634
|
+
const fieldTok = this.expect("IDENT", "a field name");
|
|
3635
|
+
const opTok = this.peek();
|
|
3636
|
+
if (opTok.type === "COLON") {
|
|
3637
|
+
this.next();
|
|
3638
|
+
const values = this.valueOrList();
|
|
3639
|
+
return { kind: "atom", field: fieldTok.text, op: ":", values, pos: fieldTok.pos };
|
|
3640
|
+
}
|
|
3641
|
+
if (opTok.type === "OP") {
|
|
3642
|
+
this.next();
|
|
3643
|
+
const value = this.value();
|
|
3644
|
+
return {
|
|
3645
|
+
kind: "atom",
|
|
3646
|
+
field: fieldTok.text,
|
|
3647
|
+
op: opTok.text,
|
|
3648
|
+
values: [value],
|
|
3649
|
+
pos: fieldTok.pos
|
|
3650
|
+
};
|
|
3651
|
+
}
|
|
3652
|
+
throw new ParseError(
|
|
3653
|
+
opTok.pos,
|
|
3654
|
+
`Expected ":" or a comparison operator after field "${fieldTok.text}"`
|
|
3655
|
+
);
|
|
3656
|
+
}
|
|
3657
|
+
valueOrList() {
|
|
3658
|
+
if (this.peek().type === "LPAREN") {
|
|
3659
|
+
this.next();
|
|
3660
|
+
const values = [this.value()];
|
|
3661
|
+
while (this.peek().type === "COMMA") {
|
|
3662
|
+
this.next();
|
|
3663
|
+
values.push(this.value());
|
|
3664
|
+
}
|
|
3665
|
+
this.expect("RPAREN", '")" to close the value list');
|
|
3666
|
+
return values;
|
|
3667
|
+
}
|
|
3668
|
+
return [this.value()];
|
|
3669
|
+
}
|
|
3670
|
+
value() {
|
|
3671
|
+
const tok = this.peek();
|
|
3672
|
+
if (!VALUE_TOKENS.has(tok.type)) {
|
|
3673
|
+
throw new ParseError(tok.pos, `Expected a value, got "${tok.text || tok.type}"`);
|
|
3674
|
+
}
|
|
3675
|
+
this.next();
|
|
3676
|
+
switch (tok.type) {
|
|
3677
|
+
case "STRING":
|
|
3678
|
+
return { type: "string", raw: tok.text, pos: tok.pos };
|
|
3679
|
+
case "NUMBER":
|
|
3680
|
+
return { type: "number", raw: tok.text, num: tok.num, pos: tok.pos };
|
|
3681
|
+
case "DATE":
|
|
3682
|
+
return { type: "date", raw: tok.text, pos: tok.pos };
|
|
3683
|
+
case "DURATION":
|
|
3684
|
+
return { type: "duration", raw: tok.text, num: tok.num, sign: tok.sign ?? 0, pos: tok.pos };
|
|
3685
|
+
default:
|
|
3686
|
+
return { type: "word", raw: tok.text, pos: tok.pos };
|
|
3687
|
+
}
|
|
3688
|
+
}
|
|
3689
|
+
};
|
|
3690
|
+
}
|
|
3691
|
+
});
|
|
3692
|
+
|
|
3693
|
+
// src/utils/query/index.ts
|
|
3694
|
+
var init_query = __esm({
|
|
3695
|
+
"src/utils/query/index.ts"() {
|
|
3696
|
+
"use strict";
|
|
3697
|
+
init_evaluate();
|
|
3698
|
+
init_fields();
|
|
3699
|
+
init_parser3();
|
|
3700
|
+
init_parser3();
|
|
3701
|
+
init_evaluate();
|
|
3702
|
+
init_fields();
|
|
3703
|
+
}
|
|
3704
|
+
});
|
|
3705
|
+
|
|
3706
|
+
// src/lifecycle/derive.ts
|
|
3707
|
+
var derive_exports = {};
|
|
3708
|
+
__export(derive_exports, {
|
|
3709
|
+
DERIVE_FIELDS: () => DERIVE_FIELDS,
|
|
3710
|
+
deriveDimensions: () => deriveDimensions,
|
|
3711
|
+
validateDeriveCondition: () => validateDeriveCondition
|
|
3712
|
+
});
|
|
3713
|
+
function validateDeriveCondition(when) {
|
|
3714
|
+
if (when === "*") return null;
|
|
3715
|
+
const parsed = parseQuery(when);
|
|
3716
|
+
if (!parsed.ast) return parsed.errors[0]?.message ?? "unparseable condition";
|
|
3717
|
+
try {
|
|
3718
|
+
compileNode(parsed.ast, DERIVE_FIELDS);
|
|
3719
|
+
return null;
|
|
3720
|
+
} catch (err) {
|
|
3721
|
+
if (err instanceof CompileError) return err.errors[0]?.message ?? "invalid condition";
|
|
3722
|
+
throw err;
|
|
3723
|
+
}
|
|
3724
|
+
}
|
|
3725
|
+
function compiledWhen(derive, when) {
|
|
3726
|
+
let cache = conditionCache.get(derive);
|
|
3727
|
+
if (!cache) {
|
|
3728
|
+
cache = /* @__PURE__ */ new Map();
|
|
3729
|
+
conditionCache.set(derive, cache);
|
|
3730
|
+
}
|
|
3731
|
+
let pred = cache.get(when);
|
|
3732
|
+
if (!pred) {
|
|
3733
|
+
if (when === "*") {
|
|
3734
|
+
pred = () => true;
|
|
3735
|
+
} else {
|
|
3736
|
+
const parsed = parseQuery(when);
|
|
3737
|
+
if (!parsed.ast) {
|
|
3738
|
+
throw new CompileError(parsed.errors);
|
|
3739
|
+
}
|
|
3740
|
+
pred = compileNode(parsed.ast, DERIVE_FIELDS);
|
|
3741
|
+
}
|
|
3742
|
+
cache.set(when, pred);
|
|
3743
|
+
}
|
|
3744
|
+
return pred;
|
|
3745
|
+
}
|
|
3746
|
+
function deriveDimensions(input) {
|
|
3747
|
+
const { facts, derive, currentStatus, terminalStatuses, knownStatusIds, override } = input;
|
|
3748
|
+
if (terminalStatuses.has(currentStatus)) return null;
|
|
3749
|
+
const ctx = { now: 0 };
|
|
3750
|
+
const item = facts;
|
|
3751
|
+
let phase = derive.phaseLadder[0]?.phase ?? currentStatus;
|
|
3752
|
+
let nextAction = derive.phaseLadder[0]?.next ?? null;
|
|
3753
|
+
for (let i = derive.phaseLadder.length - 1; i >= 0; i--) {
|
|
3754
|
+
const rung = derive.phaseLadder[i];
|
|
3755
|
+
if (compiledWhen(derive, rung.when)(item, ctx)) {
|
|
3756
|
+
phase = rung.phase;
|
|
3757
|
+
nextAction = rung.next ?? null;
|
|
3758
|
+
break;
|
|
3759
|
+
}
|
|
3760
|
+
}
|
|
3761
|
+
let disposition = "active";
|
|
3762
|
+
for (const rule of derive.disposition) {
|
|
3763
|
+
if (rule.when === null || compiledWhen(derive, rule.when)(item, ctx)) {
|
|
3764
|
+
disposition = rule.is;
|
|
3765
|
+
break;
|
|
3766
|
+
}
|
|
3767
|
+
}
|
|
3768
|
+
let derivedStatus;
|
|
3769
|
+
switch (disposition) {
|
|
3770
|
+
case "parked":
|
|
3771
|
+
derivedStatus = knownStatusIds.has(derive.headline.parked) ? derive.headline.parked : phase;
|
|
3772
|
+
break;
|
|
3773
|
+
case "blocked":
|
|
3774
|
+
derivedStatus = knownStatusIds.has(derive.headline.blocked) ? derive.headline.blocked : phase;
|
|
3775
|
+
break;
|
|
3776
|
+
default:
|
|
3777
|
+
derivedStatus = phase;
|
|
3778
|
+
}
|
|
3779
|
+
let status = derivedStatus;
|
|
3780
|
+
if (override && override.status && !terminalStatuses.has(override.status) && knownStatusIds.has(override.status)) {
|
|
3781
|
+
status = override.status;
|
|
3782
|
+
}
|
|
3783
|
+
return { phase, disposition, derivedStatus, status, nextAction };
|
|
3784
|
+
}
|
|
3785
|
+
var DERIVE_FIELDS, conditionCache;
|
|
3786
|
+
var init_derive = __esm({
|
|
3787
|
+
"src/lifecycle/derive.ts"() {
|
|
3788
|
+
"use strict";
|
|
3789
|
+
init_query();
|
|
3790
|
+
DERIVE_FIELDS = {
|
|
3791
|
+
hasrealobjective: { kind: "bool", get: (i) => i["hasRealObjective"] },
|
|
3792
|
+
acrealtotal: { kind: "number", get: (i) => i["acRealTotal"] },
|
|
3793
|
+
acrealchecked: { kind: "number", get: (i) => i["acRealChecked"] },
|
|
3794
|
+
acallchecked: { kind: "bool", get: (i) => i["acAllChecked"] },
|
|
3795
|
+
planexists: { kind: "bool", get: (i) => i["planExists"] },
|
|
3796
|
+
planapproved: { kind: "bool", get: (i) => i["planApproved"] },
|
|
3797
|
+
workspaceset: { kind: "bool", get: (i) => i["workspaceSet"] },
|
|
3798
|
+
implementationstarted: { kind: "bool", get: (i) => i["implementationStarted"] },
|
|
3799
|
+
depssatisfied: { kind: "bool", get: (i) => i["depsSatisfied"] },
|
|
3800
|
+
unresolvedquestions: { kind: "number", get: (i) => i["unresolvedQuestions"] },
|
|
3801
|
+
blocked: { kind: "bool" },
|
|
3802
|
+
parked: { kind: "bool" },
|
|
3803
|
+
reviewrequested: { kind: "bool", get: (i) => i["reviewRequested"] },
|
|
3804
|
+
pinned: { kind: "bool" }
|
|
3805
|
+
};
|
|
3806
|
+
conditionCache = /* @__PURE__ */ new WeakMap();
|
|
3807
|
+
}
|
|
3808
|
+
});
|
|
3809
|
+
|
|
3810
|
+
// src/dashboard/api.ts
|
|
3811
|
+
import { readdir as readdir7, readFile as readFile10, writeFile as writeFile3 } from "fs/promises";
|
|
3812
|
+
import { resolve as resolve12, dirname as dirname2, basename } from "path";
|
|
3813
|
+
function activeAssignments(items) {
|
|
3814
|
+
return items.filter((item) => item.archived !== true);
|
|
3815
|
+
}
|
|
3816
|
+
function accumulatePhase(traces, label, ms) {
|
|
3817
|
+
if (!traces) return;
|
|
3818
|
+
traces.subPhases.set(label, (traces.subPhases.get(label) ?? 0) + ms);
|
|
3819
|
+
}
|
|
3820
|
+
async function listStandaloneRecords(assignmentsDir) {
|
|
3821
|
+
const key = assignmentsDir ?? "";
|
|
3822
|
+
const cached = standaloneRecordsCache.get(key);
|
|
3823
|
+
if (cached) return cached;
|
|
3824
|
+
const promise = computeStandaloneRecords(assignmentsDir);
|
|
3825
|
+
standaloneRecordsCache.set(key, promise);
|
|
3826
|
+
promise.catch(() => standaloneRecordsCache.delete(key));
|
|
3827
|
+
return promise;
|
|
3828
|
+
}
|
|
3829
|
+
async function computeStandaloneRecords(assignmentsDir) {
|
|
3830
|
+
if (!assignmentsDir) return [];
|
|
3831
|
+
if (!await fileExists(assignmentsDir)) return [];
|
|
3832
|
+
const entries = await readdir7(assignmentsDir, { withFileTypes: true });
|
|
3833
|
+
const records = [];
|
|
3834
|
+
for (const entry of entries) {
|
|
3835
|
+
if (!entry.isDirectory() || entry.name.startsWith(".") || entry.name.startsWith("_")) continue;
|
|
3836
|
+
const assignmentDir = resolve12(assignmentsDir, entry.name);
|
|
3837
|
+
const assignmentMdPath = resolve12(assignmentDir, "assignment.md");
|
|
3838
|
+
if (!await fileExists(assignmentMdPath)) continue;
|
|
3839
|
+
try {
|
|
3840
|
+
const content = await readFile10(assignmentMdPath, "utf-8");
|
|
3841
|
+
const record = parseAssignmentFull(content);
|
|
3842
|
+
records.push({ assignmentDir, id: entry.name, record });
|
|
3843
|
+
} catch {
|
|
3844
|
+
}
|
|
3845
|
+
}
|
|
3846
|
+
records.sort((left, right) => compareTimestamps(right.record.updated, left.record.updated));
|
|
3847
|
+
return records;
|
|
3848
|
+
}
|
|
3849
|
+
function getTransitionDefinitions(config) {
|
|
3850
|
+
if (!config.custom) return DEFAULT_TRANSITION_DEFINITIONS;
|
|
3851
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3852
|
+
return config.transitions.filter((t) => {
|
|
3853
|
+
if (seen.has(t.command)) return false;
|
|
3854
|
+
seen.add(t.command);
|
|
3855
|
+
return true;
|
|
3856
|
+
}).map((t) => ({
|
|
3857
|
+
command: t.command,
|
|
3858
|
+
label: t.label ?? toTitleCase(t.command),
|
|
3859
|
+
description: t.description ?? `Transition via ${t.command}.`,
|
|
3860
|
+
requiresReason: t.requiresReason ?? false
|
|
3861
|
+
}));
|
|
3862
|
+
}
|
|
3863
|
+
async function getStatusConfig() {
|
|
3864
|
+
if (_cachedConfig) return _cachedConfig;
|
|
3865
|
+
const config = await readConfig();
|
|
2024
3866
|
if (config.statuses) {
|
|
2025
3867
|
const terminalSet = new Set(
|
|
2026
3868
|
config.statuses.statuses.filter((s) => s.terminal).map((s) => s.id)
|
|
@@ -2036,7 +3878,8 @@ async function getStatusConfig() {
|
|
|
2036
3878
|
order: config.statuses.order,
|
|
2037
3879
|
transitions: effectiveTransitions,
|
|
2038
3880
|
transitionTable: buildTransitionTable(effectiveTransitions),
|
|
2039
|
-
terminalStatuses: terminalSet.size > 0 ? terminalSet : /* @__PURE__ */ new Set(["completed", "failed"])
|
|
3881
|
+
terminalStatuses: terminalSet.size > 0 ? terminalSet : /* @__PURE__ */ new Set(["completed", "failed"]),
|
|
3882
|
+
derive: config.statuses.derive ?? null
|
|
2040
3883
|
};
|
|
2041
3884
|
} else {
|
|
2042
3885
|
const def = buildDefaultStatusConfig();
|
|
@@ -2046,7 +3889,8 @@ async function getStatusConfig() {
|
|
|
2046
3889
|
order: def.order,
|
|
2047
3890
|
transitions: def.transitions,
|
|
2048
3891
|
transitionTable: DEFAULT_TRANSITION_TABLE,
|
|
2049
|
-
terminalStatuses: /* @__PURE__ */ new Set(["completed", "failed"])
|
|
3892
|
+
terminalStatuses: /* @__PURE__ */ new Set(["completed", "failed"]),
|
|
3893
|
+
derive: null
|
|
2050
3894
|
};
|
|
2051
3895
|
}
|
|
2052
3896
|
return _cachedConfig;
|
|
@@ -2076,23 +3920,23 @@ async function getStandaloneAvailableTransitions(assignment) {
|
|
|
2076
3920
|
return actions;
|
|
2077
3921
|
}
|
|
2078
3922
|
async function getAssignmentDetail(projectsDir, projectSlug, assignmentSlug) {
|
|
2079
|
-
const assignmentDir =
|
|
2080
|
-
const assignmentMdPath =
|
|
3923
|
+
const assignmentDir = resolve12(projectsDir, projectSlug, "assignments", assignmentSlug);
|
|
3924
|
+
const assignmentMdPath = resolve12(assignmentDir, "assignment.md");
|
|
2081
3925
|
if (!await fileExists(assignmentMdPath)) {
|
|
2082
3926
|
return null;
|
|
2083
3927
|
}
|
|
2084
|
-
const assignmentContent = await
|
|
3928
|
+
const assignmentContent = await readFile10(assignmentMdPath, "utf-8");
|
|
2085
3929
|
const assignment = parseAssignmentFull(assignmentContent);
|
|
2086
3930
|
let projectWorkspace = null;
|
|
2087
|
-
const projectMdPath =
|
|
3931
|
+
const projectMdPath = resolve12(projectsDir, projectSlug, "project.md");
|
|
2088
3932
|
if (await fileExists(projectMdPath)) {
|
|
2089
|
-
const projectContent = await
|
|
3933
|
+
const projectContent = await readFile10(projectMdPath, "utf-8");
|
|
2090
3934
|
projectWorkspace = parseProject(projectContent).workspace;
|
|
2091
3935
|
}
|
|
2092
3936
|
let plan = null;
|
|
2093
|
-
const planPath =
|
|
3937
|
+
const planPath = resolve12(assignmentDir, "plan.md");
|
|
2094
3938
|
if (await fileExists(planPath)) {
|
|
2095
|
-
const planContent = await
|
|
3939
|
+
const planContent = await readFile10(planPath, "utf-8");
|
|
2096
3940
|
const parsed = parsePlan(planContent);
|
|
2097
3941
|
plan = {
|
|
2098
3942
|
status: parsed.status,
|
|
@@ -2101,9 +3945,9 @@ async function getAssignmentDetail(projectsDir, projectSlug, assignmentSlug) {
|
|
|
2101
3945
|
};
|
|
2102
3946
|
}
|
|
2103
3947
|
let scratchpad = null;
|
|
2104
|
-
const scratchpadPath =
|
|
3948
|
+
const scratchpadPath = resolve12(assignmentDir, "scratchpad.md");
|
|
2105
3949
|
if (await fileExists(scratchpadPath)) {
|
|
2106
|
-
const scratchpadContent = await
|
|
3950
|
+
const scratchpadContent = await readFile10(scratchpadPath, "utf-8");
|
|
2107
3951
|
const parsed = parseScratchpad(scratchpadContent);
|
|
2108
3952
|
scratchpad = {
|
|
2109
3953
|
updated: parsed.updated,
|
|
@@ -2111,9 +3955,9 @@ async function getAssignmentDetail(projectsDir, projectSlug, assignmentSlug) {
|
|
|
2111
3955
|
};
|
|
2112
3956
|
}
|
|
2113
3957
|
let handoff = null;
|
|
2114
|
-
const handoffPath =
|
|
3958
|
+
const handoffPath = resolve12(assignmentDir, "handoff.md");
|
|
2115
3959
|
if (await fileExists(handoffPath)) {
|
|
2116
|
-
const handoffContent = await
|
|
3960
|
+
const handoffContent = await readFile10(handoffPath, "utf-8");
|
|
2117
3961
|
const parsed = parseHandoff(handoffContent);
|
|
2118
3962
|
handoff = {
|
|
2119
3963
|
updated: parsed.updated,
|
|
@@ -2122,9 +3966,9 @@ async function getAssignmentDetail(projectsDir, projectSlug, assignmentSlug) {
|
|
|
2122
3966
|
};
|
|
2123
3967
|
}
|
|
2124
3968
|
let decisionRecord = null;
|
|
2125
|
-
const decisionRecordPath =
|
|
3969
|
+
const decisionRecordPath = resolve12(assignmentDir, "decision-record.md");
|
|
2126
3970
|
if (await fileExists(decisionRecordPath)) {
|
|
2127
|
-
const decisionRecordContent = await
|
|
3971
|
+
const decisionRecordContent = await readFile10(decisionRecordPath, "utf-8");
|
|
2128
3972
|
const parsed = parseDecisionRecord(decisionRecordContent);
|
|
2129
3973
|
decisionRecord = {
|
|
2130
3974
|
updated: parsed.updated,
|
|
@@ -2133,9 +3977,9 @@ async function getAssignmentDetail(projectsDir, projectSlug, assignmentSlug) {
|
|
|
2133
3977
|
};
|
|
2134
3978
|
}
|
|
2135
3979
|
let progress = null;
|
|
2136
|
-
const progressPath =
|
|
3980
|
+
const progressPath = resolve12(assignmentDir, "progress.md");
|
|
2137
3981
|
if (await fileExists(progressPath)) {
|
|
2138
|
-
const progressContent = await
|
|
3982
|
+
const progressContent = await readFile10(progressPath, "utf-8");
|
|
2139
3983
|
const parsed = parseProgress(progressContent);
|
|
2140
3984
|
progress = {
|
|
2141
3985
|
updated: parsed.updated,
|
|
@@ -2144,9 +3988,9 @@ async function getAssignmentDetail(projectsDir, projectSlug, assignmentSlug) {
|
|
|
2144
3988
|
};
|
|
2145
3989
|
}
|
|
2146
3990
|
let comments = null;
|
|
2147
|
-
const commentsPath =
|
|
3991
|
+
const commentsPath = resolve12(assignmentDir, "comments.md");
|
|
2148
3992
|
if (await fileExists(commentsPath)) {
|
|
2149
|
-
const commentsContent = await
|
|
3993
|
+
const commentsContent = await readFile10(commentsPath, "utf-8");
|
|
2150
3994
|
const parsed = parseComments(commentsContent);
|
|
2151
3995
|
comments = {
|
|
2152
3996
|
updated: parsed.updated,
|
|
@@ -2154,6 +3998,7 @@ async function getAssignmentDetail(projectsDir, projectSlug, assignmentSlug) {
|
|
|
2154
3998
|
entries: parsed.entries
|
|
2155
3999
|
};
|
|
2156
4000
|
}
|
|
4001
|
+
const { terminalStatuses } = await getStatusConfig();
|
|
2157
4002
|
const detail = {
|
|
2158
4003
|
id: assignment.id,
|
|
2159
4004
|
projectSlug,
|
|
@@ -2175,6 +4020,9 @@ async function getAssignmentDetail(projectsDir, projectSlug, assignmentSlug) {
|
|
|
2175
4020
|
archived: assignment.archived,
|
|
2176
4021
|
archivedAt: assignment.archivedAt,
|
|
2177
4022
|
archivedReason: assignment.archivedReason,
|
|
4023
|
+
...deriveStatusVirtuals(assignment, terminalStatuses),
|
|
4024
|
+
override: assignment.override,
|
|
4025
|
+
derived: await buildDerivedDetail(assignment, assignmentDir, resolve12(projectsDir, projectSlug)),
|
|
2178
4026
|
created: assignment.created,
|
|
2179
4027
|
updated: assignment.updated,
|
|
2180
4028
|
body: assignment.body,
|
|
@@ -2265,7 +4113,7 @@ async function computeReferencedBy(target, projectsDir, assignmentsDir) {
|
|
|
2265
4113
|
slug: a.slug,
|
|
2266
4114
|
title: a.title,
|
|
2267
4115
|
projectSlug: rec.summary.slug,
|
|
2268
|
-
assignmentDir:
|
|
4116
|
+
assignmentDir: resolve12(rec.projectPath, "assignments", a.slug)
|
|
2269
4117
|
});
|
|
2270
4118
|
}
|
|
2271
4119
|
}
|
|
@@ -2298,17 +4146,17 @@ async function computeReferencedBy(target, projectsDir, assignmentsDir) {
|
|
|
2298
4146
|
}
|
|
2299
4147
|
async function countMentionsInAssignment(sourceDir, target) {
|
|
2300
4148
|
const bodies = [];
|
|
2301
|
-
const assignmentMd =
|
|
4149
|
+
const assignmentMd = resolve12(sourceDir, "assignment.md");
|
|
2302
4150
|
if (await fileExists(assignmentMd)) {
|
|
2303
|
-
const content = await
|
|
4151
|
+
const content = await readFile10(assignmentMd, "utf-8");
|
|
2304
4152
|
const todosMatch = content.match(/^## Todos\s*$([\s\S]*?)(?=^## |$(?![\r\n]))/m);
|
|
2305
4153
|
if (todosMatch) bodies.push(todosMatch[1]);
|
|
2306
4154
|
}
|
|
2307
4155
|
for (const filename of ["progress.md", "comments.md", "handoff.md"]) {
|
|
2308
|
-
const path =
|
|
4156
|
+
const path = resolve12(sourceDir, filename);
|
|
2309
4157
|
if (await fileExists(path)) {
|
|
2310
4158
|
try {
|
|
2311
|
-
bodies.push(await
|
|
4159
|
+
bodies.push(await readFile10(path, "utf-8"));
|
|
2312
4160
|
} catch {
|
|
2313
4161
|
}
|
|
2314
4162
|
}
|
|
@@ -2366,46 +4214,47 @@ async function getAssignmentDetailById(projectsDir, assignmentsDir, id) {
|
|
|
2366
4214
|
}
|
|
2367
4215
|
async function buildStandaloneAssignmentDetail(resolved) {
|
|
2368
4216
|
const assignmentDir = resolved.assignmentDir;
|
|
2369
|
-
const assignmentMdPath =
|
|
4217
|
+
const assignmentMdPath = resolve12(assignmentDir, "assignment.md");
|
|
2370
4218
|
if (!await fileExists(assignmentMdPath)) return null;
|
|
2371
|
-
const assignmentContent = await
|
|
4219
|
+
const assignmentContent = await readFile10(assignmentMdPath, "utf-8");
|
|
2372
4220
|
const assignment = parseAssignmentFull(assignmentContent);
|
|
2373
4221
|
let plan = null;
|
|
2374
|
-
const planPath =
|
|
4222
|
+
const planPath = resolve12(assignmentDir, "plan.md");
|
|
2375
4223
|
if (await fileExists(planPath)) {
|
|
2376
|
-
const parsed = parsePlan(await
|
|
4224
|
+
const parsed = parsePlan(await readFile10(planPath, "utf-8"));
|
|
2377
4225
|
plan = { status: parsed.status, updated: parsed.updated, body: parsed.body };
|
|
2378
4226
|
}
|
|
2379
4227
|
let scratchpad = null;
|
|
2380
|
-
const scratchpadPath =
|
|
4228
|
+
const scratchpadPath = resolve12(assignmentDir, "scratchpad.md");
|
|
2381
4229
|
if (await fileExists(scratchpadPath)) {
|
|
2382
|
-
const parsed = parseScratchpad(await
|
|
4230
|
+
const parsed = parseScratchpad(await readFile10(scratchpadPath, "utf-8"));
|
|
2383
4231
|
scratchpad = { updated: parsed.updated, body: parsed.body };
|
|
2384
4232
|
}
|
|
2385
4233
|
let handoff = null;
|
|
2386
|
-
const handoffPath =
|
|
4234
|
+
const handoffPath = resolve12(assignmentDir, "handoff.md");
|
|
2387
4235
|
if (await fileExists(handoffPath)) {
|
|
2388
|
-
const parsed = parseHandoff(await
|
|
4236
|
+
const parsed = parseHandoff(await readFile10(handoffPath, "utf-8"));
|
|
2389
4237
|
handoff = { updated: parsed.updated, handoffCount: parsed.handoffCount, body: parsed.body };
|
|
2390
4238
|
}
|
|
2391
4239
|
let decisionRecord = null;
|
|
2392
|
-
const decisionRecordPath =
|
|
4240
|
+
const decisionRecordPath = resolve12(assignmentDir, "decision-record.md");
|
|
2393
4241
|
if (await fileExists(decisionRecordPath)) {
|
|
2394
|
-
const parsed = parseDecisionRecord(await
|
|
4242
|
+
const parsed = parseDecisionRecord(await readFile10(decisionRecordPath, "utf-8"));
|
|
2395
4243
|
decisionRecord = { updated: parsed.updated, decisionCount: parsed.decisionCount, body: parsed.body };
|
|
2396
4244
|
}
|
|
2397
4245
|
let progress = null;
|
|
2398
|
-
const progressPath =
|
|
4246
|
+
const progressPath = resolve12(assignmentDir, "progress.md");
|
|
2399
4247
|
if (await fileExists(progressPath)) {
|
|
2400
|
-
const parsed = parseProgress(await
|
|
4248
|
+
const parsed = parseProgress(await readFile10(progressPath, "utf-8"));
|
|
2401
4249
|
progress = { updated: parsed.updated, entryCount: parsed.entryCount, entries: parsed.entries };
|
|
2402
4250
|
}
|
|
2403
4251
|
let comments = null;
|
|
2404
|
-
const commentsPath =
|
|
4252
|
+
const commentsPath = resolve12(assignmentDir, "comments.md");
|
|
2405
4253
|
if (await fileExists(commentsPath)) {
|
|
2406
|
-
const parsed = parseComments(await
|
|
4254
|
+
const parsed = parseComments(await readFile10(commentsPath, "utf-8"));
|
|
2407
4255
|
comments = { updated: parsed.updated, entryCount: parsed.entryCount, entries: parsed.entries };
|
|
2408
4256
|
}
|
|
4257
|
+
const { terminalStatuses } = await getStatusConfig();
|
|
2409
4258
|
const detail = {
|
|
2410
4259
|
id: assignment.id,
|
|
2411
4260
|
projectSlug: null,
|
|
@@ -2428,6 +4277,9 @@ async function buildStandaloneAssignmentDetail(resolved) {
|
|
|
2428
4277
|
archived: assignment.archived,
|
|
2429
4278
|
archivedAt: assignment.archivedAt,
|
|
2430
4279
|
archivedReason: assignment.archivedReason,
|
|
4280
|
+
...deriveStatusVirtuals(assignment, terminalStatuses),
|
|
4281
|
+
override: assignment.override,
|
|
4282
|
+
derived: await buildDerivedDetail(assignment, assignmentDir, null),
|
|
2431
4283
|
created: assignment.created,
|
|
2432
4284
|
updated: assignment.updated,
|
|
2433
4285
|
body: assignment.body,
|
|
@@ -2459,17 +4311,17 @@ async function computeProjectRecords(projectsDir, traces) {
|
|
|
2459
4311
|
await migrateLegacyProjectFiles(projectsDir);
|
|
2460
4312
|
await migrateLegacyArchivedProjects(projectsDir);
|
|
2461
4313
|
}
|
|
2462
|
-
const entries = await
|
|
4314
|
+
const entries = await readdir7(projectsDir, { withFileTypes: true });
|
|
2463
4315
|
const projectDirs = entries.filter((entry) => entry.isDirectory() && !entry.name.startsWith("."));
|
|
2464
4316
|
const maybeRecords = await Promise.all(
|
|
2465
4317
|
projectDirs.map(async (entry) => {
|
|
2466
|
-
const projectPath =
|
|
2467
|
-
const projectMdPath =
|
|
4318
|
+
const projectPath = resolve12(projectsDir, entry.name);
|
|
4319
|
+
const projectMdPath = resolve12(projectPath, "project.md");
|
|
2468
4320
|
if (!await fileExists(projectMdPath)) {
|
|
2469
4321
|
return null;
|
|
2470
4322
|
}
|
|
2471
4323
|
const t0 = traces ? performance.now() : 0;
|
|
2472
|
-
const projectContent = await
|
|
4324
|
+
const projectContent = await readFile10(projectMdPath, "utf-8");
|
|
2473
4325
|
const project = parseProject(projectContent);
|
|
2474
4326
|
if (traces) accumulatePhase(traces, "parse-project-md", performance.now() - t0);
|
|
2475
4327
|
const t1 = traces ? performance.now() : 0;
|
|
@@ -2510,20 +4362,20 @@ async function computeProjectRecords(projectsDir, traces) {
|
|
|
2510
4362
|
return records;
|
|
2511
4363
|
}
|
|
2512
4364
|
async function listAssignmentRecords(projectPath, traces) {
|
|
2513
|
-
const assignmentsDir =
|
|
4365
|
+
const assignmentsDir = resolve12(projectPath, "assignments");
|
|
2514
4366
|
if (!await fileExists(assignmentsDir)) {
|
|
2515
4367
|
return [];
|
|
2516
4368
|
}
|
|
2517
|
-
const entries = await
|
|
4369
|
+
const entries = await readdir7(assignmentsDir, { withFileTypes: true });
|
|
2518
4370
|
const dirEntries = entries.filter((entry) => entry.isDirectory());
|
|
2519
4371
|
const maybeRecords = await Promise.all(
|
|
2520
4372
|
dirEntries.map(async (entry) => {
|
|
2521
|
-
const assignmentMd =
|
|
4373
|
+
const assignmentMd = resolve12(assignmentsDir, entry.name, "assignment.md");
|
|
2522
4374
|
if (!await fileExists(assignmentMd)) {
|
|
2523
4375
|
return null;
|
|
2524
4376
|
}
|
|
2525
4377
|
const t0 = traces ? performance.now() : 0;
|
|
2526
|
-
const content = await
|
|
4378
|
+
const content = await readFile10(assignmentMd, "utf-8");
|
|
2527
4379
|
const parsed = parseAssignmentFull(content);
|
|
2528
4380
|
if (traces) accumulatePhase(traces, "read-assignment-md", performance.now() - t0);
|
|
2529
4381
|
return parsed;
|
|
@@ -2534,9 +4386,9 @@ async function listAssignmentRecords(projectPath, traces) {
|
|
|
2534
4386
|
return records;
|
|
2535
4387
|
}
|
|
2536
4388
|
async function loadDependencyGraph(projectPath, assignments) {
|
|
2537
|
-
const statusPath =
|
|
4389
|
+
const statusPath = resolve12(projectPath, "_status.md");
|
|
2538
4390
|
if (await fileExists(statusPath)) {
|
|
2539
|
-
const statusContent = await
|
|
4391
|
+
const statusContent = await readFile10(statusPath, "utf-8");
|
|
2540
4392
|
const parsed = parseStatus(statusContent);
|
|
2541
4393
|
const derivedGraph = extractMermaidGraph(parsed.body);
|
|
2542
4394
|
if (derivedGraph) {
|
|
@@ -2586,6 +4438,78 @@ async function buildProjectRollup(projectPath, project, assignments, traces) {
|
|
|
2586
4438
|
}
|
|
2587
4439
|
return { progress, needsAttention, status };
|
|
2588
4440
|
}
|
|
4441
|
+
function deriveStatusVirtuals(assignment, terminalStatuses) {
|
|
4442
|
+
const hist = assignment.statusHistory ?? [];
|
|
4443
|
+
let completedAt = null;
|
|
4444
|
+
if (terminalStatuses.has(assignment.status)) {
|
|
4445
|
+
for (const entry of hist) {
|
|
4446
|
+
if (entry.to === assignment.status) completedAt = entry.at;
|
|
4447
|
+
}
|
|
4448
|
+
}
|
|
4449
|
+
let statusAge = null;
|
|
4450
|
+
for (let i = hist.length - 1; i >= 0; i--) {
|
|
4451
|
+
const entry = hist[i];
|
|
4452
|
+
if (entry.from !== entry.to || entry.from === null) {
|
|
4453
|
+
const t = Date.parse(entry.at);
|
|
4454
|
+
statusAge = Number.isNaN(t) ? null : Date.now() - t;
|
|
4455
|
+
break;
|
|
4456
|
+
}
|
|
4457
|
+
}
|
|
4458
|
+
let phaseAge = null;
|
|
4459
|
+
for (let i = hist.length - 1; i >= 0; i--) {
|
|
4460
|
+
const entry = hist[i];
|
|
4461
|
+
if (entry.phaseTo !== void 0 && entry.phaseFrom !== entry.phaseTo) {
|
|
4462
|
+
const t = Date.parse(entry.at);
|
|
4463
|
+
phaseAge = Number.isNaN(t) ? null : Date.now() - t;
|
|
4464
|
+
break;
|
|
4465
|
+
}
|
|
4466
|
+
}
|
|
4467
|
+
return {
|
|
4468
|
+
completedAt,
|
|
4469
|
+
statusAge,
|
|
4470
|
+
phaseAge,
|
|
4471
|
+
phase: assignment.phase,
|
|
4472
|
+
disposition: assignment.disposition,
|
|
4473
|
+
pinned: assignment.override !== null
|
|
4474
|
+
};
|
|
4475
|
+
}
|
|
4476
|
+
async function buildDerivedDetail(assignment, assignmentDir, projectDir) {
|
|
4477
|
+
const config = await getStatusConfig();
|
|
4478
|
+
if (config.terminalStatuses.has(assignment.status)) return null;
|
|
4479
|
+
try {
|
|
4480
|
+
const { computeFacts: computeFacts2 } = await Promise.resolve().then(() => (init_facts(), facts_exports));
|
|
4481
|
+
const { deriveDimensions: deriveDimensions2 } = await Promise.resolve().then(() => (init_derive(), derive_exports));
|
|
4482
|
+
const { DEFAULT_DERIVE_CONFIG: DEFAULT_DERIVE_CONFIG2 } = await Promise.resolve().then(() => (init_config2(), config_exports));
|
|
4483
|
+
const facts = await computeFacts2({
|
|
4484
|
+
assignmentDir,
|
|
4485
|
+
frontmatter: {
|
|
4486
|
+
...assignment
|
|
4487
|
+
// AssignmentRecord ⊃ the fields computeFacts reads; statusHistory and
|
|
4488
|
+
// derived caches ride along untouched.
|
|
4489
|
+
},
|
|
4490
|
+
body: assignment.body,
|
|
4491
|
+
projectDir,
|
|
4492
|
+
terminalStatuses: config.terminalStatuses
|
|
4493
|
+
});
|
|
4494
|
+
const dims = deriveDimensions2({
|
|
4495
|
+
facts,
|
|
4496
|
+
derive: config.derive ?? DEFAULT_DERIVE_CONFIG2,
|
|
4497
|
+
currentStatus: assignment.status,
|
|
4498
|
+
terminalStatuses: config.terminalStatuses,
|
|
4499
|
+
knownStatusIds: new Set(config.statuses.map((s) => s.id)),
|
|
4500
|
+
override: assignment.override
|
|
4501
|
+
});
|
|
4502
|
+
if (!dims) return null;
|
|
4503
|
+
return {
|
|
4504
|
+
derivedStatus: dims.derivedStatus,
|
|
4505
|
+
nextAction: dims.nextAction,
|
|
4506
|
+
facts
|
|
4507
|
+
};
|
|
4508
|
+
} catch (err) {
|
|
4509
|
+
console.warn(`buildDerivedDetail failed for ${assignmentDir}:`, err);
|
|
4510
|
+
return null;
|
|
4511
|
+
}
|
|
4512
|
+
}
|
|
2589
4513
|
function buildDependencyGraph(assignments) {
|
|
2590
4514
|
const edges = [];
|
|
2591
4515
|
const usedStatuses = /* @__PURE__ */ new Set();
|
|
@@ -2616,7 +4540,7 @@ async function getAvailableTransitions(projectsDir, projectSlug, assignmentSlug,
|
|
|
2616
4540
|
const config = await getStatusConfig();
|
|
2617
4541
|
const transitionDefs = getTransitionDefinitions(config);
|
|
2618
4542
|
const actions = [];
|
|
2619
|
-
const projectPath =
|
|
4543
|
+
const projectPath = resolve12(projectsDir, projectSlug);
|
|
2620
4544
|
const traces = options?.traces;
|
|
2621
4545
|
for (const definition of transitionDefs) {
|
|
2622
4546
|
const target = getTargetStatus(assignment.status, definition.command, config.transitionTable);
|
|
@@ -2664,12 +4588,12 @@ async function getUnmetDependencies(projectPath, dependsOn, terminalStatuses, de
|
|
|
2664
4588
|
continue;
|
|
2665
4589
|
}
|
|
2666
4590
|
}
|
|
2667
|
-
const dependencyPath =
|
|
4591
|
+
const dependencyPath = resolve12(projectPath, "assignments", dependency, "assignment.md");
|
|
2668
4592
|
if (!await fileExists(dependencyPath)) {
|
|
2669
4593
|
unmet.push(`${dependency} (missing)`);
|
|
2670
4594
|
continue;
|
|
2671
4595
|
}
|
|
2672
|
-
const content = await
|
|
4596
|
+
const content = await readFile10(dependencyPath, "utf-8");
|
|
2673
4597
|
const parsed = parseAssignmentFull(content);
|
|
2674
4598
|
if (!terminals.has(parsed.status)) {
|
|
2675
4599
|
unmet.push(`${dependency} (${parsed.status})`);
|
|
@@ -2685,7 +4609,7 @@ function parseTimestamp(timestamp) {
|
|
|
2685
4609
|
return Number.isFinite(parsed) ? parsed : 0;
|
|
2686
4610
|
}
|
|
2687
4611
|
async function countOpenQuestions(projectPath, assignmentSlug) {
|
|
2688
|
-
const commentsPath =
|
|
4612
|
+
const commentsPath = resolve12(
|
|
2689
4613
|
projectPath,
|
|
2690
4614
|
"assignments",
|
|
2691
4615
|
assignmentSlug,
|
|
@@ -2695,7 +4619,7 @@ async function countOpenQuestions(projectPath, assignmentSlug) {
|
|
|
2695
4619
|
return 0;
|
|
2696
4620
|
}
|
|
2697
4621
|
try {
|
|
2698
|
-
const content = await
|
|
4622
|
+
const content = await readFile10(commentsPath, "utf-8");
|
|
2699
4623
|
const parsed = parseComments(content);
|
|
2700
4624
|
return parsed.entries.filter(
|
|
2701
4625
|
(e) => e.type === "question" && e.resolved !== true
|
|
@@ -2809,6 +4733,7 @@ var init_api = __esm({
|
|
|
2809
4733
|
|
|
2810
4734
|
// src/launch/url.ts
|
|
2811
4735
|
init_terminal_schema();
|
|
4736
|
+
var MAX_OPEN_PROMPT_LENGTH = 2e3;
|
|
2812
4737
|
var OpenUrlError = class extends Error {
|
|
2813
4738
|
code;
|
|
2814
4739
|
constructor(code, message) {
|
|
@@ -2889,6 +4814,30 @@ function parseOpenUrl(input) {
|
|
|
2889
4814
|
if (agentVals.length === 1 && agentVals[0].trim() !== "") {
|
|
2890
4815
|
agent = agentVals[0];
|
|
2891
4816
|
}
|
|
4817
|
+
const promptVals = url.searchParams.getAll("prompt");
|
|
4818
|
+
if (promptVals.length > 1) {
|
|
4819
|
+
throw new OpenUrlError(
|
|
4820
|
+
"duplicate-param",
|
|
4821
|
+
"URL has more than one `prompt` query param"
|
|
4822
|
+
);
|
|
4823
|
+
}
|
|
4824
|
+
let prompt;
|
|
4825
|
+
if (promptVals.length === 1) {
|
|
4826
|
+
const value = promptVals[0];
|
|
4827
|
+
if (/[\r\n]/.test(value)) {
|
|
4828
|
+
throw new OpenUrlError(
|
|
4829
|
+
"invalid-prompt",
|
|
4830
|
+
"`prompt` query param must be a single line (no newlines)"
|
|
4831
|
+
);
|
|
4832
|
+
}
|
|
4833
|
+
if (value.length > MAX_OPEN_PROMPT_LENGTH) {
|
|
4834
|
+
throw new OpenUrlError(
|
|
4835
|
+
"invalid-prompt",
|
|
4836
|
+
`\`prompt\` query param exceeds ${MAX_OPEN_PROMPT_LENGTH} characters`
|
|
4837
|
+
);
|
|
4838
|
+
}
|
|
4839
|
+
prompt = value;
|
|
4840
|
+
}
|
|
2892
4841
|
if (assignmentVals.length === 1) {
|
|
2893
4842
|
const id = assignmentVals[0];
|
|
2894
4843
|
if (id.trim() === "") {
|
|
@@ -2901,7 +4850,9 @@ function parseOpenUrl(input) {
|
|
|
2901
4850
|
kind: "assignment",
|
|
2902
4851
|
id,
|
|
2903
4852
|
...terminal ? { terminal } : {},
|
|
2904
|
-
...agent ? { agent } : {}
|
|
4853
|
+
...agent ? { agent } : {},
|
|
4854
|
+
// assignment-only; keep '' (presence-significant) — hence !== undefined.
|
|
4855
|
+
...prompt !== void 0 ? { prompt } : {}
|
|
2905
4856
|
};
|
|
2906
4857
|
}
|
|
2907
4858
|
if (sessionVals.length === 1) {
|
|
@@ -2946,12 +4897,73 @@ init_config2();
|
|
|
2946
4897
|
init_assignment_resolver();
|
|
2947
4898
|
init_api();
|
|
2948
4899
|
|
|
2949
|
-
// src/
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
4900
|
+
// src/launch/launch-prompt.ts
|
|
4901
|
+
init_slug();
|
|
4902
|
+
function bareGrabSeed(params) {
|
|
4903
|
+
if (params.projectSlug) {
|
|
4904
|
+
return `/grab-assignment ${params.projectSlug} ${params.assignmentSlug}`;
|
|
4905
|
+
}
|
|
4906
|
+
if (params.id) {
|
|
4907
|
+
return `/grab-assignment --id ${params.id}`;
|
|
4908
|
+
}
|
|
4909
|
+
return `/grab-assignment ${params.assignmentSlug}`;
|
|
4910
|
+
}
|
|
4911
|
+
function runPlaybookClause(slug) {
|
|
4912
|
+
return `the \`${slug}\` playbook via the /run-playbook skill`;
|
|
4913
|
+
}
|
|
4914
|
+
function assignmentPointer(id, assignmentDir) {
|
|
4915
|
+
const subject = id ? `This session is Syntaur assignment ${id}, with records at ${assignmentDir}.` : `This session's Syntaur assignment records are at ${assignmentDir}.`;
|
|
4916
|
+
return `${subject} Claim and bind it with the /grab-assignment skill if available; otherwise read assignment.md, plan*.md, and progress.md in that directory for full context.`;
|
|
4917
|
+
}
|
|
4918
|
+
var TOKEN_RE = /(^|\s)@([A-Za-z0-9_-]+)/g;
|
|
4919
|
+
function resolveTemplate(template, ctx) {
|
|
4920
|
+
const warnings = [];
|
|
4921
|
+
const prompt = template.replace(TOKEN_RE, (_match, boundary, token) => {
|
|
4922
|
+
if (token === "assignment") {
|
|
4923
|
+
return boundary + assignmentPointer(ctx.id, ctx.assignmentDir);
|
|
4924
|
+
}
|
|
4925
|
+
if (!isValidSlug(token)) {
|
|
4926
|
+
warnings.push(`launchPrompt: "@${token}" is not a valid playbook token \u2014 left as literal text.`);
|
|
4927
|
+
return boundary + "@" + token;
|
|
4928
|
+
}
|
|
4929
|
+
if (ctx.knownPlaybookSlugs !== void 0 && !ctx.knownPlaybookSlugs.has(token)) {
|
|
4930
|
+
warnings.push(`launchPrompt: playbook "${token}" (from "@${token}") is not installed \u2014 left as literal text.`);
|
|
4931
|
+
return boundary + "@" + token;
|
|
4932
|
+
}
|
|
4933
|
+
return boundary + runPlaybookClause(token);
|
|
4934
|
+
});
|
|
4935
|
+
return { prompt, warnings };
|
|
4936
|
+
}
|
|
4937
|
+
function resolveLaunchPrompt(input) {
|
|
4938
|
+
const { template, playbook, id, assignmentDir, projectSlug, assignmentSlug, knownPlaybookSlugs } = input;
|
|
4939
|
+
if (template && template.trim()) {
|
|
4940
|
+
return resolveTemplate(template, { id, assignmentDir, knownPlaybookSlugs });
|
|
4941
|
+
}
|
|
4942
|
+
const pb = playbook?.trim();
|
|
4943
|
+
if (pb) {
|
|
4944
|
+
const pointer = assignmentPointer(id, assignmentDir);
|
|
4945
|
+
return { prompt: `${pointer} Run ${runPlaybookClause(pb)} end-to-end.`, warnings: [] };
|
|
4946
|
+
}
|
|
4947
|
+
return { prompt: bareGrabSeed({ projectSlug, assignmentSlug, id }), warnings: [] };
|
|
4948
|
+
}
|
|
4949
|
+
function effectiveLaunchTemplate(input) {
|
|
4950
|
+
if (input.launchPrompt && input.launchPrompt.trim()) {
|
|
4951
|
+
return input.launchPrompt;
|
|
4952
|
+
}
|
|
4953
|
+
const pb = input.playbook?.trim();
|
|
4954
|
+
if (pb) {
|
|
4955
|
+
return `@assignment Run ${runPlaybookClause(pb)} end-to-end.`;
|
|
4956
|
+
}
|
|
4957
|
+
return bareGrabSeed({
|
|
4958
|
+
projectSlug: input.projectSlug,
|
|
4959
|
+
assignmentSlug: input.assignmentSlug,
|
|
4960
|
+
id: input.id
|
|
4961
|
+
});
|
|
4962
|
+
}
|
|
4963
|
+
|
|
4964
|
+
// src/launch/plan.ts
|
|
4965
|
+
init_paths();
|
|
4966
|
+
init_playbooks();
|
|
2955
4967
|
|
|
2956
4968
|
// src/launch/cwd.ts
|
|
2957
4969
|
import { existsSync, statSync } from "fs";
|
|
@@ -2994,21 +5006,21 @@ function formatFallbackCwdWarning(opts) {
|
|
|
2994
5006
|
return `syntaur: ${fields} not set for ${opts.assignmentSlug} \u2014 launching in ${opts.workspaceDir}`;
|
|
2995
5007
|
}
|
|
2996
5008
|
|
|
5009
|
+
// src/launch/plan.ts
|
|
5010
|
+
init_agent_sessions();
|
|
5011
|
+
|
|
5012
|
+
// src/launch/argv.ts
|
|
5013
|
+
init_agents_schema();
|
|
5014
|
+
import { isAbsolute as isAbsolute4 } from "path";
|
|
5015
|
+
|
|
2997
5016
|
// src/tui/launch.ts
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
return `/grab-assignment --id ${params.id}`;
|
|
3006
|
-
}
|
|
3007
|
-
return `/grab-assignment ${params.assignmentSlug}`;
|
|
3008
|
-
}
|
|
3009
|
-
const grabClause = params.projectSlug ? `the assignment \`${params.projectSlug}/${params.assignmentSlug}\` using the /grab-assignment skill` : params.id ? `the assignment id \`${params.id}\` using /grab-assignment --id ${params.id}` : `the assignment \`${params.assignmentSlug}\` using the /grab-assignment skill`;
|
|
3010
|
-
return `Grab ${grabClause}, then load and run the \`${playbook}\` playbook using the /run-playbook skill and carry it out end-to-end.`;
|
|
3011
|
-
};
|
|
5017
|
+
init_api();
|
|
5018
|
+
init_agents_schema();
|
|
5019
|
+
import { spawn } from "child_process";
|
|
5020
|
+
import { mkdir as mkdir2, writeFile as writeFile4 } from "fs/promises";
|
|
5021
|
+
import { isAbsolute as isAbsolute3, resolve as resolve13 } from "path";
|
|
5022
|
+
init_paths();
|
|
5023
|
+
init_playbooks();
|
|
3012
5024
|
function shellQuote(arg) {
|
|
3013
5025
|
if (arg === "") return "''";
|
|
3014
5026
|
return `'${arg.replace(/'/g, `'\\''`)}'`;
|
|
@@ -3037,12 +5049,7 @@ function buildAgentArgv(agent, prompt, env = process.env) {
|
|
|
3037
5049
|
};
|
|
3038
5050
|
}
|
|
3039
5051
|
|
|
3040
|
-
// src/launch/plan.ts
|
|
3041
|
-
init_agent_sessions();
|
|
3042
|
-
|
|
3043
5052
|
// src/launch/argv.ts
|
|
3044
|
-
init_agents_schema();
|
|
3045
|
-
import { isAbsolute as isAbsolute4 } from "path";
|
|
3046
5053
|
var buildFreshArgv = buildAgentArgv;
|
|
3047
5054
|
function buildSessionArgv(agent, sessionId, mode, env = process.env) {
|
|
3048
5055
|
const invocation = agent[mode];
|
|
@@ -3150,15 +5157,18 @@ async function resolveAssignmentPlan(input, terminal) {
|
|
|
3150
5157
|
} else {
|
|
3151
5158
|
agent = pickAgent(input.config);
|
|
3152
5159
|
}
|
|
3153
|
-
const
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
5160
|
+
const knownPlaybookSlugs = await listPlaybookSlugs(playbooksDir());
|
|
5161
|
+
const template = input.promptOverride !== void 0 ? input.promptOverride : agent.launchPrompt;
|
|
5162
|
+
const { prompt, warnings: promptWarnings } = resolveLaunchPrompt({
|
|
5163
|
+
template,
|
|
5164
|
+
playbook: agent.playbook,
|
|
5165
|
+
id: resolved.id,
|
|
5166
|
+
assignmentDir: resolved.assignmentDir,
|
|
5167
|
+
projectSlug: resolved.projectSlug,
|
|
5168
|
+
assignmentSlug: resolved.assignmentSlug,
|
|
5169
|
+
knownPlaybookSlugs
|
|
5170
|
+
});
|
|
5171
|
+
const { argv, shellFallbackWarning } = buildFreshArgv(agent, prompt);
|
|
3162
5172
|
return {
|
|
3163
5173
|
terminal,
|
|
3164
5174
|
cwd,
|
|
@@ -3166,7 +5176,8 @@ async function resolveAssignmentPlan(input, terminal) {
|
|
|
3166
5176
|
env: process.env,
|
|
3167
5177
|
agentId: agent.id,
|
|
3168
5178
|
fallbackWarning,
|
|
3169
|
-
shellFallbackWarning
|
|
5179
|
+
shellFallbackWarning,
|
|
5180
|
+
promptWarnings
|
|
3170
5181
|
};
|
|
3171
5182
|
}
|
|
3172
5183
|
async function resolveSessionPlan(input, terminal) {
|
|
@@ -3327,7 +5338,7 @@ async function executeLaunchPlan(plan, spawnFn = realSpawn) {
|
|
|
3327
5338
|
`Spawn failed: ${msg}. Verify the terminal is installed and on PATH.`
|
|
3328
5339
|
);
|
|
3329
5340
|
}
|
|
3330
|
-
await new Promise((
|
|
5341
|
+
await new Promise((resolve15, reject) => {
|
|
3331
5342
|
let settled = false;
|
|
3332
5343
|
let stderr = "";
|
|
3333
5344
|
const finishOk = () => {
|
|
@@ -3337,7 +5348,7 @@ async function executeLaunchPlan(plan, spawnFn = realSpawn) {
|
|
|
3337
5348
|
child.unref();
|
|
3338
5349
|
} catch {
|
|
3339
5350
|
}
|
|
3340
|
-
|
|
5351
|
+
resolve15();
|
|
3341
5352
|
};
|
|
3342
5353
|
const finishErr = (remediation) => {
|
|
3343
5354
|
if (settled) return;
|
|
@@ -3522,7 +5533,7 @@ function appleScriptString(value) {
|
|
|
3522
5533
|
init_paths();
|
|
3523
5534
|
init_fs();
|
|
3524
5535
|
import { fileURLToPath } from "url";
|
|
3525
|
-
import { dirname as dirname3, resolve as
|
|
5536
|
+
import { dirname as dirname3, resolve as resolve14, join as join3 } from "path";
|
|
3526
5537
|
import { realpathSync, readFileSync, mkdirSync } from "fs";
|
|
3527
5538
|
var NPX_PATTERNS = [
|
|
3528
5539
|
{ kind: "npm", re: /\/_npx\/([^/]+)\/node_modules(?:\/|$)/ },
|
|
@@ -3548,7 +5559,7 @@ function normalizeSlashes(p) {
|
|
|
3548
5559
|
}
|
|
3549
5560
|
function detectInstallKind(scriptUrl, opts = {}) {
|
|
3550
5561
|
const realpath = opts.realpath ?? realpathSync.native;
|
|
3551
|
-
const
|
|
5562
|
+
const readFile11 = opts.readFile ?? ((p) => readFileSync(p, "utf-8"));
|
|
3552
5563
|
const ua = opts.envUserAgent !== void 0 ? opts.envUserAgent : process.env.npm_config_user_agent ?? "";
|
|
3553
5564
|
const resolved = resolveScriptPath(scriptUrl, realpath);
|
|
3554
5565
|
if (resolved === null) {
|
|
@@ -3569,7 +5580,7 @@ function detectInstallKind(scriptUrl, opts = {}) {
|
|
|
3569
5580
|
const pkgJsonPath = join3(dir, "package.json");
|
|
3570
5581
|
let raw;
|
|
3571
5582
|
try {
|
|
3572
|
-
raw =
|
|
5583
|
+
raw = readFile11(pkgJsonPath);
|
|
3573
5584
|
} catch {
|
|
3574
5585
|
const parent2 = dirname3(dir);
|
|
3575
5586
|
if (parent2 === dir) break;
|
|
@@ -3601,7 +5612,7 @@ function extractNpxHash(scriptUrl, opts = {}) {
|
|
|
3601
5612
|
return null;
|
|
3602
5613
|
}
|
|
3603
5614
|
function nudgeStampDir() {
|
|
3604
|
-
return
|
|
5615
|
+
return resolve14(syntaurRoot(), "npx-handler-nudge");
|
|
3605
5616
|
}
|
|
3606
5617
|
function sanitizeHash(hash) {
|
|
3607
5618
|
return hash.replace(/[^A-Za-z0-9_-]/g, "_") || "_";
|
|
@@ -3653,11 +5664,13 @@ export {
|
|
|
3653
5664
|
LaunchError,
|
|
3654
5665
|
OpenUrlError,
|
|
3655
5666
|
TerminalNotFoundError,
|
|
5667
|
+
bareGrabSeed,
|
|
3656
5668
|
buildFreshArgv,
|
|
3657
5669
|
buildSessionArgv,
|
|
3658
5670
|
buildShellCommandLine,
|
|
3659
5671
|
buildTerminalInvocation,
|
|
3660
5672
|
detectInstallKind,
|
|
5673
|
+
effectiveLaunchTemplate,
|
|
3661
5674
|
executeLaunchPlan,
|
|
3662
5675
|
extractNpxHash,
|
|
3663
5676
|
hasNudgedHash,
|
|
@@ -3670,6 +5683,8 @@ export {
|
|
|
3670
5683
|
pickAgent,
|
|
3671
5684
|
recordNudge,
|
|
3672
5685
|
resolveLaunchPlan,
|
|
5686
|
+
resolveLaunchPrompt,
|
|
5687
|
+
runPlaybookClause,
|
|
3673
5688
|
shouldNudgeForNpx
|
|
3674
5689
|
};
|
|
3675
5690
|
//# sourceMappingURL=index.js.map
|