syntaur 0.16.0 → 0.16.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dashboard/dist/assets/{_basePickBy-B1S22H85.js → _basePickBy-DTuHiZCP.js} +1 -1
- package/dashboard/dist/assets/{_baseUniq-CJIRCCsh.js → _baseUniq-BjbISCNN.js} +1 -1
- package/dashboard/dist/assets/{arc-CdlNYTcd.js → arc-D9mCa8If.js} +1 -1
- package/dashboard/dist/assets/{architectureDiagram-2XIMDMQ5-KVI0HWOy.js → architectureDiagram-2XIMDMQ5-CLWheiZu.js} +1 -1
- package/dashboard/dist/assets/{blockDiagram-WCTKOSBZ-ClnG7gaq.js → blockDiagram-WCTKOSBZ-BBxrVTWB.js} +1 -1
- package/dashboard/dist/assets/{c4Diagram-IC4MRINW-BD9lkttL.js → c4Diagram-IC4MRINW-DnhDZ2W3.js} +1 -1
- package/dashboard/dist/assets/channel-CN5VmjNa.js +1 -0
- package/dashboard/dist/assets/{chunk-4BX2VUAB-BM3DXZMG.js → chunk-4BX2VUAB-DeAfVeDa.js} +1 -1
- package/dashboard/dist/assets/{chunk-55IACEB6-DUIbFJ3r.js → chunk-55IACEB6-cKEeGDNI.js} +1 -1
- package/dashboard/dist/assets/{chunk-FMBD7UC4-B_cxCRkA.js → chunk-FMBD7UC4-7RuZ6HEq.js} +1 -1
- package/dashboard/dist/assets/{chunk-JSJVCQXG-2mLf1WoM.js → chunk-JSJVCQXG-B5dwG0bv.js} +1 -1
- package/dashboard/dist/assets/{chunk-KX2RTZJC-BMz3ND2R.js → chunk-KX2RTZJC-DeyQYDVj.js} +1 -1
- package/dashboard/dist/assets/{chunk-NQ4KR5QH-DiCxpw3L.js → chunk-NQ4KR5QH-NYo4hSqA.js} +1 -1
- package/dashboard/dist/assets/{chunk-QZHKN3VN-BcaWPzd5.js → chunk-QZHKN3VN-CtJFICF8.js} +1 -1
- package/dashboard/dist/assets/{chunk-WL4C6EOR-BbsfhS6J.js → chunk-WL4C6EOR-DZ3WYdab.js} +1 -1
- package/dashboard/dist/assets/classDiagram-VBA2DB6C-CqLb9GuU.js +1 -0
- package/dashboard/dist/assets/classDiagram-v2-RAHNMMFH-CqLb9GuU.js +1 -0
- package/dashboard/dist/assets/clone-DwvrjCQs.js +1 -0
- package/dashboard/dist/assets/{cose-bilkent-S5V4N54A-iuQzyyrA.js → cose-bilkent-S5V4N54A-DaYOsf-M.js} +1 -1
- package/dashboard/dist/assets/{dagre-KLK3FWXG-C0TB1zg5.js → dagre-KLK3FWXG-DzLc7ykF.js} +1 -1
- package/dashboard/dist/assets/{diagram-E7M64L7V-Ck8tvxP2.js → diagram-E7M64L7V-kpjEdOqb.js} +1 -1
- package/dashboard/dist/assets/{diagram-IFDJBPK2-rQKIi-sc.js → diagram-IFDJBPK2-D3EHoh6j.js} +1 -1
- package/dashboard/dist/assets/{diagram-P4PSJMXO-DS1RGSgx.js → diagram-P4PSJMXO-Cp6Un2ys.js} +1 -1
- package/dashboard/dist/assets/{erDiagram-INFDFZHY-BqWCybWW.js → erDiagram-INFDFZHY-DDjOUPWk.js} +1 -1
- package/dashboard/dist/assets/{flowDiagram-PKNHOUZH-BCoQtyQ5.js → flowDiagram-PKNHOUZH-CfO51xge.js} +1 -1
- package/dashboard/dist/assets/{ganttDiagram-A5KZAMGK-CD1BmicP.js → ganttDiagram-A5KZAMGK-BDzAtkcp.js} +1 -1
- package/dashboard/dist/assets/{gitGraphDiagram-K3NZZRJ6-tGDYOZuO.js → gitGraphDiagram-K3NZZRJ6-JPMFN2zF.js} +1 -1
- package/dashboard/dist/assets/{graph-BT0s-094.js → graph-DXDryilu.js} +1 -1
- package/dashboard/dist/assets/index-D7UtkCYM.js +515 -0
- package/dashboard/dist/assets/{index-D2Yoi6DJ.css → index-DTXSWSzH.css} +1 -1
- package/dashboard/dist/assets/{infoDiagram-LFFYTUFH-E4dVb8BD.js → infoDiagram-LFFYTUFH-CYi5kkvz.js} +1 -1
- package/dashboard/dist/assets/{ishikawaDiagram-PHBUUO56-CubuM_ev.js → ishikawaDiagram-PHBUUO56-DQl_IUe8.js} +1 -1
- package/dashboard/dist/assets/{journeyDiagram-4ABVD52K-DfyJbgMF.js → journeyDiagram-4ABVD52K-BXXGPcrS.js} +1 -1
- package/dashboard/dist/assets/{kanban-definition-K7BYSVSG-BXamajyZ.js → kanban-definition-K7BYSVSG-BqJestUY.js} +1 -1
- package/dashboard/dist/assets/{layout-6b_SlWD_.js → layout-D5VdYmWn.js} +1 -1
- package/dashboard/dist/assets/{linear-BE0O-66A.js → linear-dZA_O_GN.js} +1 -1
- package/dashboard/dist/assets/{mermaid.core-DM3qWr07.js → mermaid.core-B0Ixd1yP.js} +4 -4
- package/dashboard/dist/assets/{mindmap-definition-YRQLILUH-puOtERrG.js → mindmap-definition-YRQLILUH-CSJYdSMG.js} +1 -1
- package/dashboard/dist/assets/{pieDiagram-SKSYHLDU-cE6l8UBX.js → pieDiagram-SKSYHLDU-DmYrRZHN.js} +1 -1
- package/dashboard/dist/assets/{quadrantDiagram-337W2JSQ-CgDAyZQP.js → quadrantDiagram-337W2JSQ-La3ce5kE.js} +1 -1
- package/dashboard/dist/assets/{requirementDiagram-Z7DCOOCP-CJrai_8U.js → requirementDiagram-Z7DCOOCP-DPitIZQl.js} +1 -1
- package/dashboard/dist/assets/{sankeyDiagram-WA2Y5GQK-Bf72l7UW.js → sankeyDiagram-WA2Y5GQK-CAmCqGkr.js} +1 -1
- package/dashboard/dist/assets/{sequenceDiagram-2WXFIKYE-0D8RN7ds.js → sequenceDiagram-2WXFIKYE-6lEzNTWG.js} +1 -1
- package/dashboard/dist/assets/{stateDiagram-RAJIS63D-B8LucUIt.js → stateDiagram-RAJIS63D-DyGKCS2C.js} +1 -1
- package/dashboard/dist/assets/stateDiagram-v2-FVOUBMTO-BOmRICoY.js +1 -0
- package/dashboard/dist/assets/{timeline-definition-YZTLITO2-DL3Hq8ii.js → timeline-definition-YZTLITO2-D9RxQE3j.js} +1 -1
- package/dashboard/dist/assets/{treemap-KZPCXAKY-wF1X4_uI.js → treemap-KZPCXAKY-TuXbGNN4.js} +1 -1
- package/dashboard/dist/assets/{vennDiagram-LZ73GAT5-DDAjoGTZ.js → vennDiagram-LZ73GAT5-Bly5knr-.js} +1 -1
- package/dashboard/dist/assets/{xychartDiagram-JWTSCODW-T-YmzRW8.js → xychartDiagram-JWTSCODW-CA-5z7-4.js} +1 -1
- package/dashboard/dist/index.html +2 -2
- package/dist/dashboard/server.js +820 -166
- package/dist/dashboard/server.js.map +1 -1
- package/dist/index.js +1244 -774
- package/dist/index.js.map +1 -1
- package/dist/launch/index.d.ts +20 -2
- package/dist/launch/index.js +67 -20
- package/dist/launch/index.js.map +1 -1
- package/package.json +1 -1
- package/scripts/install-macos-url-handler.mjs +40 -7
- package/dashboard/dist/assets/channel-Cm_FzKJq.js +0 -1
- package/dashboard/dist/assets/classDiagram-VBA2DB6C-Bdan2Dp5.js +0 -1
- package/dashboard/dist/assets/classDiagram-v2-RAHNMMFH-Bdan2Dp5.js +0 -1
- package/dashboard/dist/assets/clone-9PoPEM_s.js +0 -1
- package/dashboard/dist/assets/index-BdSkANCC.js +0 -510
- package/dashboard/dist/assets/stateDiagram-v2-FVOUBMTO-D0dc7wxQ.js +0 -1
package/dist/index.js
CHANGED
|
@@ -131,7 +131,6 @@ backup:
|
|
|
131
131
|
categories: projects, playbooks, todos, servers, config
|
|
132
132
|
lastBackup: null
|
|
133
133
|
lastRestore: null
|
|
134
|
-
terminal: terminal-app
|
|
135
134
|
---
|
|
136
135
|
|
|
137
136
|
# Syntaur Configuration
|
|
@@ -193,6 +192,12 @@ function parseListField(frontmatter, fieldName) {
|
|
|
193
192
|
}
|
|
194
193
|
return results;
|
|
195
194
|
}
|
|
195
|
+
function unquoteYamlString(value) {
|
|
196
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
197
|
+
return value.slice(1, -1);
|
|
198
|
+
}
|
|
199
|
+
return value;
|
|
200
|
+
}
|
|
196
201
|
function parseProject(fileContent) {
|
|
197
202
|
const [fm, body] = extractFrontmatter(fileContent);
|
|
198
203
|
const slug = getField(fm, "slug") ?? getField(fm, "mission") ?? "";
|
|
@@ -208,6 +213,7 @@ function parseProject(fileContent) {
|
|
|
208
213
|
updated: getField(fm, "updated") ?? "",
|
|
209
214
|
tags: parseListField(fm, "tags"),
|
|
210
215
|
workspace: getField(fm, "workspace"),
|
|
216
|
+
repositories: parseListField(fm, "repositories").map(unquoteYamlString),
|
|
211
217
|
externalIds: parseExternalIds(fm),
|
|
212
218
|
body
|
|
213
219
|
};
|
|
@@ -700,6 +706,22 @@ var init_agents_schema = __esm({
|
|
|
700
706
|
}
|
|
701
707
|
});
|
|
702
708
|
|
|
709
|
+
// src/utils/terminal-schema.ts
|
|
710
|
+
var TERMINAL_CHOICES;
|
|
711
|
+
var init_terminal_schema = __esm({
|
|
712
|
+
"src/utils/terminal-schema.ts"() {
|
|
713
|
+
"use strict";
|
|
714
|
+
TERMINAL_CHOICES = [
|
|
715
|
+
"terminal-app",
|
|
716
|
+
"iterm",
|
|
717
|
+
"ghostty",
|
|
718
|
+
"alacritty",
|
|
719
|
+
"warp",
|
|
720
|
+
"kitty"
|
|
721
|
+
];
|
|
722
|
+
}
|
|
723
|
+
});
|
|
724
|
+
|
|
703
725
|
// src/utils/config.ts
|
|
704
726
|
var config_exports = {};
|
|
705
727
|
__export(config_exports, {
|
|
@@ -713,6 +735,7 @@ __export(config_exports, {
|
|
|
713
735
|
deleteAgentsConfig: () => deleteAgentsConfig,
|
|
714
736
|
deleteHotkeyBindingsConfig: () => deleteHotkeyBindingsConfig,
|
|
715
737
|
deleteStatusConfig: () => deleteStatusConfig,
|
|
738
|
+
deleteTerminalConfig: () => deleteTerminalConfig,
|
|
716
739
|
deleteThemeConfig: () => deleteThemeConfig,
|
|
717
740
|
getAgents: () => getAgents,
|
|
718
741
|
getAssignmentTypes: () => getAssignmentTypes,
|
|
@@ -729,9 +752,11 @@ __export(config_exports, {
|
|
|
729
752
|
writeAgentsConfig: () => writeAgentsConfig,
|
|
730
753
|
writeHotkeyBindingsConfig: () => writeHotkeyBindingsConfig,
|
|
731
754
|
writeStatusConfig: () => writeStatusConfig,
|
|
755
|
+
writeTerminalConfig: () => writeTerminalConfig,
|
|
732
756
|
writeThemeConfig: () => writeThemeConfig
|
|
733
757
|
});
|
|
734
758
|
import { readFile as readFile2 } from "fs/promises";
|
|
759
|
+
import { spawnSync } from "child_process";
|
|
735
760
|
import { resolve as resolve3, isAbsolute } from "path";
|
|
736
761
|
function parseAgentCommand(value, agentId) {
|
|
737
762
|
if (typeof value !== "string" || value.trim() === "") {
|
|
@@ -1144,6 +1169,52 @@ ${cleanedFm}
|
|
|
1144
1169
|
---${afterFrontmatter}`;
|
|
1145
1170
|
await writeFileForce(configPath, newContent);
|
|
1146
1171
|
}
|
|
1172
|
+
function stripTopLevelScalar(fmBlock, key) {
|
|
1173
|
+
const lines = fmBlock.split("\n");
|
|
1174
|
+
const keyRegex = new RegExp(`^${key}:\\s*\\S`);
|
|
1175
|
+
const filtered = lines.filter((line) => !keyRegex.test(line));
|
|
1176
|
+
return filtered.join("\n").replace(/\n+$/, "");
|
|
1177
|
+
}
|
|
1178
|
+
async function writeTerminalConfig(terminal) {
|
|
1179
|
+
const configPath = resolve3(syntaurRoot(), "config.md");
|
|
1180
|
+
const terminalLine = `terminal: ${terminal}`;
|
|
1181
|
+
const existing = await fileExists(configPath) ? await readFile2(configPath, "utf-8") : renderConfig({ defaultProjectDir: defaultProjectDir() });
|
|
1182
|
+
const fmMatch = existing.match(/^(---\n)([\s\S]*?)\n(---)/);
|
|
1183
|
+
if (!fmMatch) {
|
|
1184
|
+
const content = `---
|
|
1185
|
+
version: "2.0"
|
|
1186
|
+
defaultProjectDir: ${defaultProjectDir()}
|
|
1187
|
+
${terminalLine}
|
|
1188
|
+
---
|
|
1189
|
+
${existing}`;
|
|
1190
|
+
await writeFileForce(configPath, content);
|
|
1191
|
+
return;
|
|
1192
|
+
}
|
|
1193
|
+
const fmBlock = fmMatch[2];
|
|
1194
|
+
const afterFrontmatter = existing.slice(fmMatch[0].length);
|
|
1195
|
+
const cleanedFm = stripTopLevelScalar(fmBlock, "terminal");
|
|
1196
|
+
const newFm = `${cleanedFm}
|
|
1197
|
+
${terminalLine}`.replace(/^\n+/, "");
|
|
1198
|
+
const normalizedFm = newFm.replace(/\n+$/, "");
|
|
1199
|
+
const newContent = `---
|
|
1200
|
+
${normalizedFm}
|
|
1201
|
+
---${afterFrontmatter}`;
|
|
1202
|
+
await writeFileForce(configPath, newContent);
|
|
1203
|
+
}
|
|
1204
|
+
async function deleteTerminalConfig() {
|
|
1205
|
+
const configPath = resolve3(syntaurRoot(), "config.md");
|
|
1206
|
+
if (!await fileExists(configPath)) return;
|
|
1207
|
+
const existing = await readFile2(configPath, "utf-8");
|
|
1208
|
+
const fmMatch = existing.match(/^(---\n)([\s\S]*?)\n(---)/);
|
|
1209
|
+
if (!fmMatch) return;
|
|
1210
|
+
const fmBlock = fmMatch[2];
|
|
1211
|
+
const afterFrontmatter = existing.slice(fmMatch[0].length);
|
|
1212
|
+
const cleanedFm = stripTopLevelScalar(fmBlock, "terminal");
|
|
1213
|
+
const newContent = `---
|
|
1214
|
+
${cleanedFm}
|
|
1215
|
+
---${afterFrontmatter}`;
|
|
1216
|
+
await writeFileForce(configPath, newContent);
|
|
1217
|
+
}
|
|
1147
1218
|
function parseHotkeyBindingsConfig(content) {
|
|
1148
1219
|
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
1149
1220
|
if (!match) return null;
|
|
@@ -1839,7 +1910,18 @@ function parseTerminalConfig(value) {
|
|
|
1839
1910
|
return trimmed;
|
|
1840
1911
|
}
|
|
1841
1912
|
function getTerminal(config) {
|
|
1842
|
-
|
|
1913
|
+
if (config.terminal) return config.terminal;
|
|
1914
|
+
if (process.platform === "darwin") return "terminal-app";
|
|
1915
|
+
if (process.platform === "linux") {
|
|
1916
|
+
const order = ["kitty", "alacritty", "warp"];
|
|
1917
|
+
for (const candidate of order) {
|
|
1918
|
+
const result = spawnSync("which", [candidate], { encoding: "utf-8" });
|
|
1919
|
+
if (result.status === 0 && result.stdout.trim().length > 0) {
|
|
1920
|
+
return candidate;
|
|
1921
|
+
}
|
|
1922
|
+
}
|
|
1923
|
+
}
|
|
1924
|
+
return "terminal-app";
|
|
1843
1925
|
}
|
|
1844
1926
|
async function updateAgentsConfig(mutation, options = {}) {
|
|
1845
1927
|
const config = await readConfig();
|
|
@@ -1852,7 +1934,7 @@ async function updateAgentsConfig(mutation, options = {}) {
|
|
|
1852
1934
|
await writeAgentsConfig(next);
|
|
1853
1935
|
return { previous, next, written: true };
|
|
1854
1936
|
}
|
|
1855
|
-
var DEFAULT_ASSIGNMENT_TYPES,
|
|
1937
|
+
var DEFAULT_ASSIGNMENT_TYPES, DEFAULT_CONFIG, AUTO_CREATE_WORKTREE_VALUES, AgentConfigError, KNOWN_AGENT_SCALAR_FIELDS, migratedConfigPaths, TerminalConfigError;
|
|
1856
1938
|
var init_config2 = __esm({
|
|
1857
1939
|
"src/utils/config.ts"() {
|
|
1858
1940
|
"use strict";
|
|
@@ -1862,6 +1944,7 @@ var init_config2 = __esm({
|
|
|
1862
1944
|
init_fs_migration();
|
|
1863
1945
|
init_hotkeysCatalog();
|
|
1864
1946
|
init_agents_schema();
|
|
1947
|
+
init_terminal_schema();
|
|
1865
1948
|
DEFAULT_ASSIGNMENT_TYPES = {
|
|
1866
1949
|
definitions: [
|
|
1867
1950
|
{ id: "feature", label: "Feature" },
|
|
@@ -1872,14 +1955,6 @@ var init_config2 = __esm({
|
|
|
1872
1955
|
],
|
|
1873
1956
|
default: "feature"
|
|
1874
1957
|
};
|
|
1875
|
-
TERMINAL_CHOICES = [
|
|
1876
|
-
"terminal-app",
|
|
1877
|
-
"iterm",
|
|
1878
|
-
"ghostty",
|
|
1879
|
-
"alacritty",
|
|
1880
|
-
"warp",
|
|
1881
|
-
"kitty"
|
|
1882
|
-
];
|
|
1883
1958
|
DEFAULT_CONFIG = {
|
|
1884
1959
|
version: "2.0",
|
|
1885
1960
|
defaultProjectDir: defaultProjectDir(),
|
|
@@ -2182,10 +2257,17 @@ var init_yaml = __esm({
|
|
|
2182
2257
|
});
|
|
2183
2258
|
|
|
2184
2259
|
// src/templates/project.ts
|
|
2260
|
+
function renderRepositoriesBlock(repos) {
|
|
2261
|
+
if (!repos || repos.length === 0) {
|
|
2262
|
+
return "repositories: []";
|
|
2263
|
+
}
|
|
2264
|
+
return ["repositories:", ...repos.map((p) => ` - ${escapeYamlString(p)}`)].join("\n");
|
|
2265
|
+
}
|
|
2185
2266
|
function renderProject(params2) {
|
|
2186
2267
|
const safeTitle = escapeYamlString(params2.title);
|
|
2187
2268
|
const workspaceLine = params2.workspace ? `
|
|
2188
2269
|
workspace: ${params2.workspace}` : "";
|
|
2270
|
+
const repositoriesBlock = renderRepositoriesBlock(params2.repositories);
|
|
2189
2271
|
return `---
|
|
2190
2272
|
id: ${params2.id}
|
|
2191
2273
|
slug: ${params2.slug}
|
|
@@ -2196,7 +2278,8 @@ archivedReason: null
|
|
|
2196
2278
|
created: "${params2.timestamp}"
|
|
2197
2279
|
updated: "${params2.timestamp}"
|
|
2198
2280
|
externalIds: []
|
|
2199
|
-
tags: []
|
|
2281
|
+
tags: []
|
|
2282
|
+
${repositoriesBlock}${workspaceLine}
|
|
2200
2283
|
---
|
|
2201
2284
|
|
|
2202
2285
|
# ${params2.title}
|
|
@@ -4924,8 +5007,8 @@ async function migrateFromMarkdown(projectsDir2) {
|
|
|
4924
5007
|
return allSessions.length;
|
|
4925
5008
|
}
|
|
4926
5009
|
async function parseMarkdownSessionsIndex(filePath, projectSlug) {
|
|
4927
|
-
const { readFile:
|
|
4928
|
-
const raw = await
|
|
5010
|
+
const { readFile: readFile50 } = await import("fs/promises");
|
|
5011
|
+
const raw = await readFile50(filePath, "utf-8");
|
|
4929
5012
|
const sessions = [];
|
|
4930
5013
|
const lines = raw.split("\n");
|
|
4931
5014
|
let inTable = false;
|
|
@@ -6114,7 +6197,8 @@ async function getProjectDetail(projectsDir2, slug) {
|
|
|
6114
6197
|
resources,
|
|
6115
6198
|
memories,
|
|
6116
6199
|
dependencyGraph,
|
|
6117
|
-
workspace: project.workspace
|
|
6200
|
+
workspace: project.workspace,
|
|
6201
|
+
repositories: project.repositories
|
|
6118
6202
|
};
|
|
6119
6203
|
}
|
|
6120
6204
|
async function getAssignmentDetail(projectsDir2, projectSlug, assignmentSlug) {
|
|
@@ -7340,6 +7424,114 @@ var init_api = __esm({
|
|
|
7340
7424
|
}
|
|
7341
7425
|
});
|
|
7342
7426
|
|
|
7427
|
+
// src/utils/git-worktree.ts
|
|
7428
|
+
var git_worktree_exports = {};
|
|
7429
|
+
__export(git_worktree_exports, {
|
|
7430
|
+
GitWorktreeError: () => GitWorktreeError,
|
|
7431
|
+
createWorktree: () => createWorktree,
|
|
7432
|
+
createWorktreeAndRecord: () => createWorktreeAndRecord,
|
|
7433
|
+
deleteBranch: () => deleteBranch,
|
|
7434
|
+
formatRollbackError: () => formatRollbackError,
|
|
7435
|
+
removeWorktree: () => removeWorktree
|
|
7436
|
+
});
|
|
7437
|
+
import { spawn } from "child_process";
|
|
7438
|
+
import { readFile as readFile13 } from "fs/promises";
|
|
7439
|
+
function run(command, args, cwd) {
|
|
7440
|
+
return new Promise((resolvePromise) => {
|
|
7441
|
+
const child = spawn(command, args, { cwd, stdio: ["ignore", "pipe", "pipe"] });
|
|
7442
|
+
let stdout = "";
|
|
7443
|
+
let stderr = "";
|
|
7444
|
+
child.stdout.on("data", (chunk) => stdout += chunk.toString());
|
|
7445
|
+
child.stderr.on("data", (chunk) => stderr += chunk.toString());
|
|
7446
|
+
child.on("error", (err2) => {
|
|
7447
|
+
resolvePromise({ code: -1, stdout, stderr: stderr + String(err2) });
|
|
7448
|
+
});
|
|
7449
|
+
child.on("close", (code) => {
|
|
7450
|
+
resolvePromise({ code: code ?? -1, stdout, stderr });
|
|
7451
|
+
});
|
|
7452
|
+
});
|
|
7453
|
+
}
|
|
7454
|
+
async function createWorktree(opts) {
|
|
7455
|
+
const { repository, branch, worktreePath, parentBranch } = opts;
|
|
7456
|
+
const result = await run(
|
|
7457
|
+
"git",
|
|
7458
|
+
["-C", repository, "worktree", "add", "-b", branch, worktreePath, parentBranch]
|
|
7459
|
+
);
|
|
7460
|
+
if (result.code !== 0) {
|
|
7461
|
+
throw new GitWorktreeError(
|
|
7462
|
+
`git worktree add failed (exit ${result.code}): ${result.stderr.trim() || "(no stderr)"}`,
|
|
7463
|
+
result.stderr
|
|
7464
|
+
);
|
|
7465
|
+
}
|
|
7466
|
+
}
|
|
7467
|
+
async function removeWorktree(repository, worktreePath) {
|
|
7468
|
+
const result = await run(
|
|
7469
|
+
"git",
|
|
7470
|
+
["-C", repository, "worktree", "remove", "--force", worktreePath]
|
|
7471
|
+
);
|
|
7472
|
+
return { ok: result.code === 0, stderr: result.stderr };
|
|
7473
|
+
}
|
|
7474
|
+
async function deleteBranch(repository, branch) {
|
|
7475
|
+
const result = await run("git", ["-C", repository, "branch", "-D", branch]);
|
|
7476
|
+
return { ok: result.code === 0, stderr: result.stderr };
|
|
7477
|
+
}
|
|
7478
|
+
async function createWorktreeAndRecord(opts) {
|
|
7479
|
+
const { assignmentPath, repository, branch, worktreePath, parentBranch } = opts;
|
|
7480
|
+
await createWorktree({ repository, branch, worktreePath, parentBranch });
|
|
7481
|
+
try {
|
|
7482
|
+
const content = await readFile13(assignmentPath, "utf-8");
|
|
7483
|
+
const updated = updateAssignmentWorkspace(content, {
|
|
7484
|
+
repository,
|
|
7485
|
+
worktreePath,
|
|
7486
|
+
branch,
|
|
7487
|
+
parentBranch
|
|
7488
|
+
});
|
|
7489
|
+
await writeFileForce(assignmentPath, updated);
|
|
7490
|
+
} catch (writeErr) {
|
|
7491
|
+
const cleanup = await removeWorktree(repository, worktreePath);
|
|
7492
|
+
const branchCleanup = await deleteBranch(repository, branch);
|
|
7493
|
+
const writeMsg = writeErr instanceof Error ? writeErr.message : String(writeErr);
|
|
7494
|
+
throw new Error(
|
|
7495
|
+
formatRollbackError({
|
|
7496
|
+
writeMsg,
|
|
7497
|
+
worktreePath,
|
|
7498
|
+
branch,
|
|
7499
|
+
worktreeCleanup: cleanup,
|
|
7500
|
+
branchCleanup
|
|
7501
|
+
})
|
|
7502
|
+
);
|
|
7503
|
+
}
|
|
7504
|
+
}
|
|
7505
|
+
function formatRollbackError(opts) {
|
|
7506
|
+
const { writeMsg, worktreePath, branch, worktreeCleanup, branchCleanup } = opts;
|
|
7507
|
+
const wtMsg = worktreeCleanup.stderr.trim() || "(no stderr)";
|
|
7508
|
+
const brMsg = branchCleanup.stderr.trim() || "(no stderr)";
|
|
7509
|
+
if (!worktreeCleanup.ok && !branchCleanup.ok) {
|
|
7510
|
+
return `Failed to update assignment frontmatter AND failed to clean up both worktree and branch. Write error: ${writeMsg}. Worktree cleanup error: ${wtMsg}. Branch cleanup error: ${brMsg}. Orphan worktree at ${worktreePath} and orphan branch "${branch}" \u2014 remove them manually.`;
|
|
7511
|
+
}
|
|
7512
|
+
if (!worktreeCleanup.ok) {
|
|
7513
|
+
return `Failed to update assignment frontmatter AND failed to clean up worktree. Write error: ${writeMsg}. Worktree cleanup error: ${wtMsg}. Orphan worktree at ${worktreePath} \u2014 remove it manually.`;
|
|
7514
|
+
}
|
|
7515
|
+
if (!branchCleanup.ok) {
|
|
7516
|
+
return `Failed to update assignment frontmatter: ${writeMsg}. Rolled back git worktree at ${worktreePath}, but could not delete branch "${branch}": ${brMsg}. Remove the branch manually.`;
|
|
7517
|
+
}
|
|
7518
|
+
return `Failed to update assignment frontmatter: ${writeMsg}. Rolled back git worktree at ${worktreePath} and branch "${branch}".`;
|
|
7519
|
+
}
|
|
7520
|
+
var GitWorktreeError;
|
|
7521
|
+
var init_git_worktree = __esm({
|
|
7522
|
+
"src/utils/git-worktree.ts"() {
|
|
7523
|
+
"use strict";
|
|
7524
|
+
init_frontmatter();
|
|
7525
|
+
init_fs();
|
|
7526
|
+
GitWorktreeError = class extends Error {
|
|
7527
|
+
constructor(message, stderr) {
|
|
7528
|
+
super(message);
|
|
7529
|
+
this.stderr = stderr;
|
|
7530
|
+
}
|
|
7531
|
+
};
|
|
7532
|
+
}
|
|
7533
|
+
});
|
|
7534
|
+
|
|
7343
7535
|
// src/utils/promote-todos.ts
|
|
7344
7536
|
var promote_todos_exports = {};
|
|
7345
7537
|
__export(promote_todos_exports, {
|
|
@@ -7512,9 +7704,9 @@ __export(launch_exports, {
|
|
|
7512
7704
|
launchAgent: () => launchAgent,
|
|
7513
7705
|
shellQuote: () => shellQuote
|
|
7514
7706
|
});
|
|
7515
|
-
import { spawn as
|
|
7707
|
+
import { spawn as spawn3 } from "child_process";
|
|
7516
7708
|
import { mkdir as mkdir6, writeFile as writeFile9 } from "fs/promises";
|
|
7517
|
-
import { isAbsolute as
|
|
7709
|
+
import { isAbsolute as isAbsolute4, resolve as resolve39 } from "path";
|
|
7518
7710
|
function formatFallbackCwdWarning(opts) {
|
|
7519
7711
|
const missing = [];
|
|
7520
7712
|
if (!opts.worktreePath) missing.push("worktreePath");
|
|
@@ -7535,7 +7727,7 @@ function buildAgentArgv(agent, prompt, env = process.env) {
|
|
|
7535
7727
|
const requested = env.SHELL;
|
|
7536
7728
|
let shell = requested;
|
|
7537
7729
|
let warning = null;
|
|
7538
|
-
if (!shell || !
|
|
7730
|
+
if (!shell || !isAbsolute4(shell)) {
|
|
7539
7731
|
warning = `syntaur: $SHELL ${requested ? `("${requested}") is not absolute` : "is unset"} \u2014 falling back to /bin/sh for shell-alias resolution`;
|
|
7540
7732
|
shell = "/bin/sh";
|
|
7541
7733
|
}
|
|
@@ -7558,8 +7750,8 @@ async function launchAgent(options) {
|
|
|
7558
7750
|
console.error(`Assignment not found: ${projectSlug}/${assignmentSlug}`);
|
|
7559
7751
|
process.exit(1);
|
|
7560
7752
|
}
|
|
7561
|
-
const projectDir =
|
|
7562
|
-
const assignmentDir =
|
|
7753
|
+
const projectDir = resolve39(projectsDir2, projectSlug);
|
|
7754
|
+
const assignmentDir = resolve39(projectDir, "assignments", assignmentSlug);
|
|
7563
7755
|
const resolvedFromWorkspace = cwdOverride ?? detail.workspace.worktreePath ?? (detail.workspace.repository?.startsWith("/") ? detail.workspace.repository : null);
|
|
7564
7756
|
const workspaceDir = resolvedFromWorkspace ?? process.cwd();
|
|
7565
7757
|
if (!cwdOverride) {
|
|
@@ -7571,7 +7763,7 @@ async function launchAgent(options) {
|
|
|
7571
7763
|
});
|
|
7572
7764
|
if (warning) console.warn(warning);
|
|
7573
7765
|
}
|
|
7574
|
-
const contextDir =
|
|
7766
|
+
const contextDir = resolve39(workspaceDir, ".syntaur");
|
|
7575
7767
|
await mkdir6(contextDir, { recursive: true });
|
|
7576
7768
|
const context = {
|
|
7577
7769
|
projectSlug,
|
|
@@ -7584,7 +7776,7 @@ async function launchAgent(options) {
|
|
|
7584
7776
|
grabbedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
7585
7777
|
};
|
|
7586
7778
|
await writeFile9(
|
|
7587
|
-
|
|
7779
|
+
resolve39(contextDir, "context.json"),
|
|
7588
7780
|
JSON.stringify(context, null, 2) + "\n"
|
|
7589
7781
|
);
|
|
7590
7782
|
const { argv, shellFallbackWarning } = buildAgentArgv(
|
|
@@ -7595,7 +7787,7 @@ async function launchAgent(options) {
|
|
|
7595
7787
|
console.warn(shellFallbackWarning);
|
|
7596
7788
|
}
|
|
7597
7789
|
return new Promise((resolvePromise) => {
|
|
7598
|
-
const child =
|
|
7790
|
+
const child = spawn3(argv.command, argv.args, {
|
|
7599
7791
|
cwd: workspaceDir,
|
|
7600
7792
|
stdio: "inherit"
|
|
7601
7793
|
});
|
|
@@ -7656,9 +7848,9 @@ import {
|
|
|
7656
7848
|
unlinkSync
|
|
7657
7849
|
} from "fs";
|
|
7658
7850
|
import { fileURLToPath as fileURLToPath6, pathToFileURL } from "url";
|
|
7659
|
-
import { dirname as dirname13, resolve as
|
|
7851
|
+
import { dirname as dirname13, resolve as resolve41, join as join5 } from "path";
|
|
7660
7852
|
import { homedir as homedir6, tmpdir as tmpdir2 } from "os";
|
|
7661
|
-
import { spawnSync as
|
|
7853
|
+
import { spawnSync as spawnSync6 } from "child_process";
|
|
7662
7854
|
function syntaurRootMjs() {
|
|
7663
7855
|
const override = process.env.SYNTAUR_HOME;
|
|
7664
7856
|
if (override && override.length > 0) {
|
|
@@ -7678,7 +7870,7 @@ async function registerMacosUrlHandler(options = { throwOnFailure: false }) {
|
|
|
7678
7870
|
}
|
|
7679
7871
|
const stateRoot = syntaurRootMjs();
|
|
7680
7872
|
mkdirSync2(stateRoot, { recursive: true });
|
|
7681
|
-
const lockPath =
|
|
7873
|
+
const lockPath = resolve41(stateRoot, "install-url-handler.lock");
|
|
7682
7874
|
let lockFd;
|
|
7683
7875
|
try {
|
|
7684
7876
|
lockFd = openSync(lockPath, "wx");
|
|
@@ -7694,8 +7886,8 @@ async function registerMacosUrlHandler(options = { throwOnFailure: false }) {
|
|
|
7694
7886
|
throw err2;
|
|
7695
7887
|
}
|
|
7696
7888
|
try {
|
|
7697
|
-
const pkgRoot =
|
|
7698
|
-
const cliBin = realpathSync2(
|
|
7889
|
+
const pkgRoot = resolve41(dirname13(fileURLToPath6(import.meta.url)), "..");
|
|
7890
|
+
const cliBin = realpathSync2(resolve41(pkgRoot, "bin/syntaur.js"));
|
|
7699
7891
|
const nodeBin = process.execPath;
|
|
7700
7892
|
const bundleParent = join5(
|
|
7701
7893
|
homedir6(),
|
|
@@ -7718,7 +7910,7 @@ async function registerMacosUrlHandler(options = { throwOnFailure: false }) {
|
|
|
7718
7910
|
renderAppleScript({ nodeBin, cliBin, installedTerminals }),
|
|
7719
7911
|
"utf-8"
|
|
7720
7912
|
);
|
|
7721
|
-
const compile =
|
|
7913
|
+
const compile = spawnSync6(
|
|
7722
7914
|
OSACOMPILE,
|
|
7723
7915
|
["-o", bundlePath, scriptPath],
|
|
7724
7916
|
{ stdio: "pipe", encoding: "utf-8" }
|
|
@@ -7762,7 +7954,7 @@ async function registerMacosUrlHandler(options = { throwOnFailure: false }) {
|
|
|
7762
7954
|
["Add :CFBundleURLTypes:0:CFBundleURLSchemes array", null],
|
|
7763
7955
|
[`Add :CFBundleURLTypes:0:CFBundleURLSchemes:0 string ${URL_SCHEME}`, null]
|
|
7764
7956
|
]);
|
|
7765
|
-
const sign =
|
|
7957
|
+
const sign = spawnSync6(
|
|
7766
7958
|
"/usr/bin/codesign",
|
|
7767
7959
|
["--force", "--deep", "--sign", "-", bundlePath],
|
|
7768
7960
|
{ stdio: "pipe", encoding: "utf-8" }
|
|
@@ -7774,7 +7966,7 @@ async function registerMacosUrlHandler(options = { throwOnFailure: false }) {
|
|
|
7774
7966
|
}
|
|
7775
7967
|
console.warn(`syntaur: ${msg} \u2014 macOS may deny Automation permission.`);
|
|
7776
7968
|
}
|
|
7777
|
-
const ls =
|
|
7969
|
+
const ls = spawnSync6(LSREGISTER, ["-f", bundlePath], {
|
|
7778
7970
|
stdio: "pipe",
|
|
7779
7971
|
encoding: "utf-8"
|
|
7780
7972
|
});
|
|
@@ -7815,10 +8007,11 @@ function detectInstalledTerminals() {
|
|
|
7815
8007
|
const installed = /* @__PURE__ */ new Set(["terminal-app"]);
|
|
7816
8008
|
const bundleIds = {
|
|
7817
8009
|
iterm: "com.googlecode.iterm2",
|
|
7818
|
-
ghostty: "com.mitchellh.ghostty"
|
|
8010
|
+
ghostty: "com.mitchellh.ghostty",
|
|
8011
|
+
warp: "dev.warp.Warp-Stable"
|
|
7819
8012
|
};
|
|
7820
8013
|
for (const [id, bundleId] of Object.entries(bundleIds)) {
|
|
7821
|
-
const r =
|
|
8014
|
+
const r = spawnSync6(
|
|
7822
8015
|
"mdfind",
|
|
7823
8016
|
[`kMDItemCFBundleIdentifier == '${bundleId}'`],
|
|
7824
8017
|
{ encoding: "utf-8" }
|
|
@@ -7858,13 +8051,28 @@ function buildTerminalDispatch(installedTerminals) {
|
|
|
7858
8051
|
branches.push({
|
|
7859
8052
|
id: "ghostty",
|
|
7860
8053
|
block: [
|
|
7861
|
-
' tell application "Ghostty"',
|
|
7862
|
-
"
|
|
7863
|
-
|
|
7864
|
-
|
|
7865
|
-
"
|
|
7866
|
-
"
|
|
7867
|
-
|
|
8054
|
+
' tell application "Ghostty" to activate',
|
|
8055
|
+
" delay 0.3",
|
|
8056
|
+
' tell application "System Events"',
|
|
8057
|
+
' keystroke "n" using command down',
|
|
8058
|
+
" delay 0.4",
|
|
8059
|
+
" keystroke shellCmd",
|
|
8060
|
+
" key code 36",
|
|
8061
|
+
" end tell"
|
|
8062
|
+
]
|
|
8063
|
+
});
|
|
8064
|
+
}
|
|
8065
|
+
if (installedTerminals.has("warp")) {
|
|
8066
|
+
branches.push({
|
|
8067
|
+
id: "warp",
|
|
8068
|
+
block: [
|
|
8069
|
+
' tell application "Warp" to activate',
|
|
8070
|
+
" delay 0.3",
|
|
8071
|
+
' tell application "System Events"',
|
|
8072
|
+
' keystroke "n" using command down',
|
|
8073
|
+
" delay 0.4",
|
|
8074
|
+
" keystroke shellCmd",
|
|
8075
|
+
" key code 36",
|
|
7868
8076
|
" end tell"
|
|
7869
8077
|
]
|
|
7870
8078
|
});
|
|
@@ -7949,13 +8157,13 @@ function runPlistBuddy(plistPath, steps) {
|
|
|
7949
8157
|
for (const step of steps) {
|
|
7950
8158
|
if (Array.isArray(step)) {
|
|
7951
8159
|
const [primary, fallback] = step;
|
|
7952
|
-
const a =
|
|
8160
|
+
const a = spawnSync6("/usr/libexec/PlistBuddy", ["-c", primary, plistPath], {
|
|
7953
8161
|
stdio: "pipe",
|
|
7954
8162
|
encoding: "utf-8"
|
|
7955
8163
|
});
|
|
7956
8164
|
if (a.status === 0) continue;
|
|
7957
8165
|
if (fallback === null) continue;
|
|
7958
|
-
const b =
|
|
8166
|
+
const b = spawnSync6("/usr/libexec/PlistBuddy", ["-c", fallback, plistPath], {
|
|
7959
8167
|
stdio: "pipe",
|
|
7960
8168
|
encoding: "utf-8"
|
|
7961
8169
|
});
|
|
@@ -7965,7 +8173,7 @@ function runPlistBuddy(plistPath, steps) {
|
|
|
7965
8173
|
);
|
|
7966
8174
|
}
|
|
7967
8175
|
} else {
|
|
7968
|
-
const r =
|
|
8176
|
+
const r = spawnSync6("/usr/libexec/PlistBuddy", ["-c", step, plistPath], {
|
|
7969
8177
|
stdio: "pipe",
|
|
7970
8178
|
encoding: "utf-8"
|
|
7971
8179
|
});
|
|
@@ -8500,114 +8708,6 @@ var init_App = __esm({
|
|
|
8500
8708
|
}
|
|
8501
8709
|
});
|
|
8502
8710
|
|
|
8503
|
-
// src/utils/git-worktree.ts
|
|
8504
|
-
var git_worktree_exports = {};
|
|
8505
|
-
__export(git_worktree_exports, {
|
|
8506
|
-
GitWorktreeError: () => GitWorktreeError,
|
|
8507
|
-
createWorktree: () => createWorktree,
|
|
8508
|
-
createWorktreeAndRecord: () => createWorktreeAndRecord,
|
|
8509
|
-
deleteBranch: () => deleteBranch,
|
|
8510
|
-
formatRollbackError: () => formatRollbackError,
|
|
8511
|
-
removeWorktree: () => removeWorktree
|
|
8512
|
-
});
|
|
8513
|
-
import { spawn as spawn4 } from "child_process";
|
|
8514
|
-
import { readFile as readFile23 } from "fs/promises";
|
|
8515
|
-
function run(command, args, cwd) {
|
|
8516
|
-
return new Promise((resolvePromise) => {
|
|
8517
|
-
const child = spawn4(command, args, { cwd, stdio: ["ignore", "pipe", "pipe"] });
|
|
8518
|
-
let stdout = "";
|
|
8519
|
-
let stderr = "";
|
|
8520
|
-
child.stdout.on("data", (chunk) => stdout += chunk.toString());
|
|
8521
|
-
child.stderr.on("data", (chunk) => stderr += chunk.toString());
|
|
8522
|
-
child.on("error", (err2) => {
|
|
8523
|
-
resolvePromise({ code: -1, stdout, stderr: stderr + String(err2) });
|
|
8524
|
-
});
|
|
8525
|
-
child.on("close", (code) => {
|
|
8526
|
-
resolvePromise({ code: code ?? -1, stdout, stderr });
|
|
8527
|
-
});
|
|
8528
|
-
});
|
|
8529
|
-
}
|
|
8530
|
-
async function createWorktree(opts) {
|
|
8531
|
-
const { repository, branch, worktreePath, parentBranch } = opts;
|
|
8532
|
-
const result = await run(
|
|
8533
|
-
"git",
|
|
8534
|
-
["-C", repository, "worktree", "add", "-b", branch, worktreePath, parentBranch]
|
|
8535
|
-
);
|
|
8536
|
-
if (result.code !== 0) {
|
|
8537
|
-
throw new GitWorktreeError(
|
|
8538
|
-
`git worktree add failed (exit ${result.code}): ${result.stderr.trim() || "(no stderr)"}`,
|
|
8539
|
-
result.stderr
|
|
8540
|
-
);
|
|
8541
|
-
}
|
|
8542
|
-
}
|
|
8543
|
-
async function removeWorktree(repository, worktreePath) {
|
|
8544
|
-
const result = await run(
|
|
8545
|
-
"git",
|
|
8546
|
-
["-C", repository, "worktree", "remove", "--force", worktreePath]
|
|
8547
|
-
);
|
|
8548
|
-
return { ok: result.code === 0, stderr: result.stderr };
|
|
8549
|
-
}
|
|
8550
|
-
async function deleteBranch(repository, branch) {
|
|
8551
|
-
const result = await run("git", ["-C", repository, "branch", "-D", branch]);
|
|
8552
|
-
return { ok: result.code === 0, stderr: result.stderr };
|
|
8553
|
-
}
|
|
8554
|
-
async function createWorktreeAndRecord(opts) {
|
|
8555
|
-
const { assignmentPath, repository, branch, worktreePath, parentBranch } = opts;
|
|
8556
|
-
await createWorktree({ repository, branch, worktreePath, parentBranch });
|
|
8557
|
-
try {
|
|
8558
|
-
const content = await readFile23(assignmentPath, "utf-8");
|
|
8559
|
-
const updated = updateAssignmentWorkspace(content, {
|
|
8560
|
-
repository,
|
|
8561
|
-
worktreePath,
|
|
8562
|
-
branch,
|
|
8563
|
-
parentBranch
|
|
8564
|
-
});
|
|
8565
|
-
await writeFileForce(assignmentPath, updated);
|
|
8566
|
-
} catch (writeErr) {
|
|
8567
|
-
const cleanup = await removeWorktree(repository, worktreePath);
|
|
8568
|
-
const branchCleanup = await deleteBranch(repository, branch);
|
|
8569
|
-
const writeMsg = writeErr instanceof Error ? writeErr.message : String(writeErr);
|
|
8570
|
-
throw new Error(
|
|
8571
|
-
formatRollbackError({
|
|
8572
|
-
writeMsg,
|
|
8573
|
-
worktreePath,
|
|
8574
|
-
branch,
|
|
8575
|
-
worktreeCleanup: cleanup,
|
|
8576
|
-
branchCleanup
|
|
8577
|
-
})
|
|
8578
|
-
);
|
|
8579
|
-
}
|
|
8580
|
-
}
|
|
8581
|
-
function formatRollbackError(opts) {
|
|
8582
|
-
const { writeMsg, worktreePath, branch, worktreeCleanup, branchCleanup } = opts;
|
|
8583
|
-
const wtMsg = worktreeCleanup.stderr.trim() || "(no stderr)";
|
|
8584
|
-
const brMsg = branchCleanup.stderr.trim() || "(no stderr)";
|
|
8585
|
-
if (!worktreeCleanup.ok && !branchCleanup.ok) {
|
|
8586
|
-
return `Failed to update assignment frontmatter AND failed to clean up both worktree and branch. Write error: ${writeMsg}. Worktree cleanup error: ${wtMsg}. Branch cleanup error: ${brMsg}. Orphan worktree at ${worktreePath} and orphan branch "${branch}" \u2014 remove them manually.`;
|
|
8587
|
-
}
|
|
8588
|
-
if (!worktreeCleanup.ok) {
|
|
8589
|
-
return `Failed to update assignment frontmatter AND failed to clean up worktree. Write error: ${writeMsg}. Worktree cleanup error: ${wtMsg}. Orphan worktree at ${worktreePath} \u2014 remove it manually.`;
|
|
8590
|
-
}
|
|
8591
|
-
if (!branchCleanup.ok) {
|
|
8592
|
-
return `Failed to update assignment frontmatter: ${writeMsg}. Rolled back git worktree at ${worktreePath}, but could not delete branch "${branch}": ${brMsg}. Remove the branch manually.`;
|
|
8593
|
-
}
|
|
8594
|
-
return `Failed to update assignment frontmatter: ${writeMsg}. Rolled back git worktree at ${worktreePath} and branch "${branch}".`;
|
|
8595
|
-
}
|
|
8596
|
-
var GitWorktreeError;
|
|
8597
|
-
var init_git_worktree = __esm({
|
|
8598
|
-
"src/utils/git-worktree.ts"() {
|
|
8599
|
-
"use strict";
|
|
8600
|
-
init_frontmatter();
|
|
8601
|
-
init_fs();
|
|
8602
|
-
GitWorktreeError = class extends Error {
|
|
8603
|
-
constructor(message, stderr) {
|
|
8604
|
-
super(message);
|
|
8605
|
-
this.stderr = stderr;
|
|
8606
|
-
}
|
|
8607
|
-
};
|
|
8608
|
-
}
|
|
8609
|
-
});
|
|
8610
|
-
|
|
8611
8711
|
// src/index.ts
|
|
8612
8712
|
import { Command as Command13, InvalidArgumentError } from "commander";
|
|
8613
8713
|
|
|
@@ -8765,9 +8865,9 @@ init_create_assignment();
|
|
|
8765
8865
|
|
|
8766
8866
|
// src/commands/dashboard.ts
|
|
8767
8867
|
init_config2();
|
|
8768
|
-
import { spawn } from "child_process";
|
|
8868
|
+
import { spawn as spawn2 } from "child_process";
|
|
8769
8869
|
import { createServer as createNetServer } from "net";
|
|
8770
|
-
import { resolve as
|
|
8870
|
+
import { resolve as resolve27, dirname as dirname7 } from "path";
|
|
8771
8871
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
8772
8872
|
|
|
8773
8873
|
// src/dashboard/server.ts
|
|
@@ -8777,7 +8877,7 @@ init_assignment_resolver();
|
|
|
8777
8877
|
init_agent_sessions();
|
|
8778
8878
|
import express from "express";
|
|
8779
8879
|
import { createServer } from "http";
|
|
8780
|
-
import { resolve as
|
|
8880
|
+
import { resolve as resolve26 } from "path";
|
|
8781
8881
|
import { writeFile as writeFile5, unlink as unlink5 } from "fs/promises";
|
|
8782
8882
|
import { WebSocketServer, WebSocket } from "ws";
|
|
8783
8883
|
|
|
@@ -9312,10 +9412,116 @@ init_slug();
|
|
|
9312
9412
|
init_uuid();
|
|
9313
9413
|
init_timestamp();
|
|
9314
9414
|
init_fs();
|
|
9315
|
-
|
|
9415
|
+
init_git_worktree();
|
|
9316
9416
|
import { Router } from "express";
|
|
9317
|
-
import { resolve as
|
|
9318
|
-
import { rm, readFile as
|
|
9417
|
+
import { resolve as resolve20, basename as basename3, isAbsolute as isAbsolute2 } from "path";
|
|
9418
|
+
import { rm, readFile as readFile15, open as fsOpen, stat as fsStat, realpath as fsRealpath } from "fs/promises";
|
|
9419
|
+
import { spawnSync as spawnSync3 } from "child_process";
|
|
9420
|
+
|
|
9421
|
+
// src/utils/worktree-defaults.ts
|
|
9422
|
+
init_paths();
|
|
9423
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
9424
|
+
import { resolve as resolve18 } from "path";
|
|
9425
|
+
function computeWorktreeDefaults(opts) {
|
|
9426
|
+
const repository = opts.existing.repository ?? detectCurrentGitRoot(opts.cwd);
|
|
9427
|
+
const branch = opts.projectSlug ? `syntaur/${opts.projectSlug}/${opts.assignmentSlug}` : `syntaur/${opts.assignmentSlug}`;
|
|
9428
|
+
const parentBranch = opts.existing.parentBranch ?? detectCurrentBranch(opts.cwd) ?? "main";
|
|
9429
|
+
const worktreeBase = repository ? resolve18(repository, ".worktrees", branch) : resolve18(
|
|
9430
|
+
syntaurRoot(),
|
|
9431
|
+
"worktrees",
|
|
9432
|
+
opts.projectSlug || "standalone",
|
|
9433
|
+
opts.assignmentSlug
|
|
9434
|
+
);
|
|
9435
|
+
return {
|
|
9436
|
+
...repository ? { repository } : {},
|
|
9437
|
+
branch,
|
|
9438
|
+
parentBranch,
|
|
9439
|
+
worktreePath: worktreeBase
|
|
9440
|
+
};
|
|
9441
|
+
}
|
|
9442
|
+
function detectCurrentGitRoot(cwd) {
|
|
9443
|
+
const result = spawnSync2("git", ["rev-parse", "--show-toplevel"], {
|
|
9444
|
+
cwd,
|
|
9445
|
+
encoding: "utf-8"
|
|
9446
|
+
});
|
|
9447
|
+
if (result.status !== 0) return void 0;
|
|
9448
|
+
const out = result.stdout.trim();
|
|
9449
|
+
return out.length > 0 ? out : void 0;
|
|
9450
|
+
}
|
|
9451
|
+
function detectCurrentBranch(cwd) {
|
|
9452
|
+
const result = spawnSync2("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
|
|
9453
|
+
cwd,
|
|
9454
|
+
encoding: "utf-8"
|
|
9455
|
+
});
|
|
9456
|
+
if (result.status !== 0) return void 0;
|
|
9457
|
+
const out = result.stdout.trim();
|
|
9458
|
+
if (!out || out === "HEAD") return void 0;
|
|
9459
|
+
return out;
|
|
9460
|
+
}
|
|
9461
|
+
|
|
9462
|
+
// src/dashboard/repository-candidates.ts
|
|
9463
|
+
init_fs();
|
|
9464
|
+
init_parser();
|
|
9465
|
+
import { readdir as readdir10, readFile as readFile14 } from "fs/promises";
|
|
9466
|
+
import { resolve as resolve19 } from "path";
|
|
9467
|
+
async function getProjectRepositoryCandidates(projectsDir2, projectSlug) {
|
|
9468
|
+
const seen = /* @__PURE__ */ new Set();
|
|
9469
|
+
const out = [];
|
|
9470
|
+
const projectPath = resolve19(projectsDir2, projectSlug, "project.md");
|
|
9471
|
+
if (await fileExists(projectPath)) {
|
|
9472
|
+
const project = parseProject(await readFile14(projectPath, "utf-8"));
|
|
9473
|
+
for (const raw of project.repositories) {
|
|
9474
|
+
const path = raw.trim();
|
|
9475
|
+
if (!path) continue;
|
|
9476
|
+
const abs = resolve19(path);
|
|
9477
|
+
if (seen.has(abs)) continue;
|
|
9478
|
+
seen.add(abs);
|
|
9479
|
+
out.push({ path: abs, source: "project", sourceAssignmentSlug: null });
|
|
9480
|
+
}
|
|
9481
|
+
}
|
|
9482
|
+
const assignmentsDir2 = resolve19(projectsDir2, projectSlug, "assignments");
|
|
9483
|
+
if (await fileExists(assignmentsDir2)) {
|
|
9484
|
+
const entries = await readdir10(assignmentsDir2, { withFileTypes: true });
|
|
9485
|
+
for (const entry of entries) {
|
|
9486
|
+
if (!entry.isDirectory()) continue;
|
|
9487
|
+
const assignmentMd = resolve19(assignmentsDir2, entry.name, "assignment.md");
|
|
9488
|
+
if (!await fileExists(assignmentMd)) continue;
|
|
9489
|
+
const parsed = parseAssignmentFull(await readFile14(assignmentMd, "utf-8"));
|
|
9490
|
+
const repo = parsed.workspace.repository?.trim();
|
|
9491
|
+
if (!repo) continue;
|
|
9492
|
+
const abs = resolve19(repo);
|
|
9493
|
+
if (seen.has(abs)) continue;
|
|
9494
|
+
seen.add(abs);
|
|
9495
|
+
out.push({ path: abs, source: "sibling", sourceAssignmentSlug: parsed.slug });
|
|
9496
|
+
}
|
|
9497
|
+
}
|
|
9498
|
+
return out;
|
|
9499
|
+
}
|
|
9500
|
+
async function getStandaloneRepositoryCandidates(assignmentsDir2, excludeAssignmentId) {
|
|
9501
|
+
if (!await fileExists(assignmentsDir2)) {
|
|
9502
|
+
return [];
|
|
9503
|
+
}
|
|
9504
|
+
const seen = /* @__PURE__ */ new Set();
|
|
9505
|
+
const out = [];
|
|
9506
|
+
const entries = await readdir10(assignmentsDir2, { withFileTypes: true });
|
|
9507
|
+
for (const entry of entries) {
|
|
9508
|
+
if (!entry.isDirectory()) continue;
|
|
9509
|
+
if (entry.name === excludeAssignmentId) continue;
|
|
9510
|
+
const assignmentMd = resolve19(assignmentsDir2, entry.name, "assignment.md");
|
|
9511
|
+
if (!await fileExists(assignmentMd)) continue;
|
|
9512
|
+
const parsed = parseAssignmentFull(await readFile14(assignmentMd, "utf-8"));
|
|
9513
|
+
const repo = parsed.workspace.repository?.trim();
|
|
9514
|
+
if (!repo) continue;
|
|
9515
|
+
const abs = resolve19(repo);
|
|
9516
|
+
if (seen.has(abs)) continue;
|
|
9517
|
+
seen.add(abs);
|
|
9518
|
+
out.push({ path: abs, source: "sibling", sourceAssignmentSlug: parsed.slug });
|
|
9519
|
+
}
|
|
9520
|
+
return out;
|
|
9521
|
+
}
|
|
9522
|
+
|
|
9523
|
+
// src/dashboard/api-write.ts
|
|
9524
|
+
init_parser();
|
|
9319
9525
|
|
|
9320
9526
|
// src/dashboard/acceptance-criteria.ts
|
|
9321
9527
|
function splitFrontmatter(content) {
|
|
@@ -9468,7 +9674,101 @@ async function readCurrentDocument(filePath) {
|
|
|
9468
9674
|
if (!await fileExists(filePath)) {
|
|
9469
9675
|
return null;
|
|
9470
9676
|
}
|
|
9471
|
-
return
|
|
9677
|
+
return readFile15(filePath, "utf-8");
|
|
9678
|
+
}
|
|
9679
|
+
async function handleWorktreeCreate(req, res, ctx) {
|
|
9680
|
+
if (!await fileExists(ctx.assignmentPath)) {
|
|
9681
|
+
res.status(404).json({ error: "Assignment not found" });
|
|
9682
|
+
return;
|
|
9683
|
+
}
|
|
9684
|
+
const parsed = parseAssignmentFull(await readFile15(ctx.assignmentPath, "utf-8"));
|
|
9685
|
+
if (parsed.workspace.worktreePath) {
|
|
9686
|
+
res.status(409).json({ error: "Worktree already configured for this assignment" });
|
|
9687
|
+
return;
|
|
9688
|
+
}
|
|
9689
|
+
const { repository, branch: bodyBranch, parentBranch: bodyParent } = req.body ?? {};
|
|
9690
|
+
if (typeof repository !== "string" || !repository.trim()) {
|
|
9691
|
+
res.status(400).json({ error: "`repository` is required." });
|
|
9692
|
+
return;
|
|
9693
|
+
}
|
|
9694
|
+
if (!isAbsolute2(repository)) {
|
|
9695
|
+
res.status(400).json({ error: "`repository` must be an absolute path." });
|
|
9696
|
+
return;
|
|
9697
|
+
}
|
|
9698
|
+
try {
|
|
9699
|
+
const st = await fsStat(repository);
|
|
9700
|
+
if (!st.isDirectory()) {
|
|
9701
|
+
res.status(400).json({ error: `Repository path is not a directory: ${repository}` });
|
|
9702
|
+
return;
|
|
9703
|
+
}
|
|
9704
|
+
} catch {
|
|
9705
|
+
res.status(400).json({ error: `Repository path does not exist: ${repository}` });
|
|
9706
|
+
return;
|
|
9707
|
+
}
|
|
9708
|
+
const topLevel = spawnSync3("git", ["-C", repository, "rev-parse", "--show-toplevel"], {
|
|
9709
|
+
encoding: "utf-8"
|
|
9710
|
+
});
|
|
9711
|
+
const topLevelOut = topLevel.stdout.trim();
|
|
9712
|
+
if (topLevel.status !== 0 || !topLevelOut) {
|
|
9713
|
+
res.status(400).json({ error: `Repository path is not a git working tree: ${repository}` });
|
|
9714
|
+
return;
|
|
9715
|
+
}
|
|
9716
|
+
const [requestReal, topLevelReal] = await Promise.all([
|
|
9717
|
+
fsRealpath(repository),
|
|
9718
|
+
fsRealpath(topLevelOut)
|
|
9719
|
+
]);
|
|
9720
|
+
if (requestReal !== topLevelReal) {
|
|
9721
|
+
res.status(400).json({
|
|
9722
|
+
error: `Repository path must be the git working-tree root. Got ${repository}; the enclosing repo root is ${topLevelOut}.`
|
|
9723
|
+
});
|
|
9724
|
+
return;
|
|
9725
|
+
}
|
|
9726
|
+
const defaults = computeWorktreeDefaults({
|
|
9727
|
+
projectSlug: ctx.projectSlug,
|
|
9728
|
+
assignmentSlug: ctx.assignmentSlug,
|
|
9729
|
+
existing: parsed.workspace,
|
|
9730
|
+
cwd: repository
|
|
9731
|
+
});
|
|
9732
|
+
const branch = typeof bodyBranch === "string" && bodyBranch.trim() ? bodyBranch.trim() : defaults.branch;
|
|
9733
|
+
const parentBranch = typeof bodyParent === "string" && bodyParent.trim() ? bodyParent.trim() : defaults.parentBranch;
|
|
9734
|
+
const worktreePath = resolve20(repository, ".worktrees", branch);
|
|
9735
|
+
try {
|
|
9736
|
+
await fsStat(worktreePath);
|
|
9737
|
+
res.status(409).json({
|
|
9738
|
+
error: `A file or directory already exists at ${worktreePath}. Remove it or choose a different branch.`
|
|
9739
|
+
});
|
|
9740
|
+
return;
|
|
9741
|
+
} catch {
|
|
9742
|
+
}
|
|
9743
|
+
const parentCheck = spawnSync3(
|
|
9744
|
+
"git",
|
|
9745
|
+
["-C", repository, "rev-parse", "--verify", "--quiet", parentBranch],
|
|
9746
|
+
{ encoding: "utf-8" }
|
|
9747
|
+
);
|
|
9748
|
+
if (parentCheck.status !== 0) {
|
|
9749
|
+
res.status(400).json({
|
|
9750
|
+
error: `Parent branch "${parentBranch}" does not exist in ${repository}.`
|
|
9751
|
+
});
|
|
9752
|
+
return;
|
|
9753
|
+
}
|
|
9754
|
+
try {
|
|
9755
|
+
await createWorktreeAndRecord({
|
|
9756
|
+
assignmentPath: ctx.assignmentPath,
|
|
9757
|
+
repository,
|
|
9758
|
+
branch,
|
|
9759
|
+
worktreePath,
|
|
9760
|
+
parentBranch
|
|
9761
|
+
});
|
|
9762
|
+
} catch (error) {
|
|
9763
|
+
if (error instanceof GitWorktreeError) {
|
|
9764
|
+
res.status(400).json({ error: error.message, stderr: error.stderr });
|
|
9765
|
+
return;
|
|
9766
|
+
}
|
|
9767
|
+
res.status(500).json({ error: error.message });
|
|
9768
|
+
return;
|
|
9769
|
+
}
|
|
9770
|
+
const assignment = await ctx.reload();
|
|
9771
|
+
res.json({ assignment });
|
|
9472
9772
|
}
|
|
9473
9773
|
function createWriteRouter(projectsDir2, assignmentsDir2, todosDir2) {
|
|
9474
9774
|
const linkedTodosLookup = todosDir2 ? { todosDir: todosDir2, projectsDir: projectsDir2 } : void 0;
|
|
@@ -9697,9 +9997,9 @@ ${body.startsWith("\n") ? body.slice(1) : body}${body.endsWith("\n") ? "" : "\n"
|
|
|
9697
9997
|
});
|
|
9698
9998
|
return;
|
|
9699
9999
|
}
|
|
9700
|
-
const folderPath =
|
|
10000
|
+
const folderPath = resolve20(projectDir, folder);
|
|
9701
10001
|
await ensureDir(folderPath);
|
|
9702
|
-
const filePath =
|
|
10002
|
+
const filePath = resolve20(folderPath, `${requestedSlug}.md`);
|
|
9703
10003
|
const timestamp = nowTimestamp();
|
|
9704
10004
|
let content = renderItemStub(kind, {
|
|
9705
10005
|
slug: requestedSlug,
|
|
@@ -9743,14 +10043,14 @@ ${body.startsWith("\n") ? body.slice(1) : body}${body.endsWith("\n") ? "" : "\n"
|
|
|
9743
10043
|
res.status(404).json({ error: `Project "${projectSlug}" not found` });
|
|
9744
10044
|
return;
|
|
9745
10045
|
}
|
|
9746
|
-
const filePath =
|
|
10046
|
+
const filePath = resolve20(projectDir, folder, `${itemSlug}.md`);
|
|
9747
10047
|
if (!await fileExists(filePath)) {
|
|
9748
10048
|
res.status(404).json({ error: `${kind === "memory" ? "Memory" : "Resource"} not found` });
|
|
9749
10049
|
return;
|
|
9750
10050
|
}
|
|
9751
10051
|
const nextContentRaw = requireContent(req, res);
|
|
9752
10052
|
if (!nextContentRaw) return;
|
|
9753
|
-
const currentContent = await
|
|
10053
|
+
const currentContent = await readFile15(filePath, "utf-8");
|
|
9754
10054
|
const frontmatterBlock = extractFrontmatterBlock(currentContent);
|
|
9755
10055
|
if (!frontmatterBlock) {
|
|
9756
10056
|
res.status(500).json({ error: `${kind} file is malformed (no frontmatter)` });
|
|
@@ -9779,7 +10079,7 @@ ${nextBody}${nextBody.endsWith("\n") ? "" : "\n"}`;
|
|
|
9779
10079
|
res.status(404).json({ error: `Project "${projectSlug}" not found` });
|
|
9780
10080
|
return;
|
|
9781
10081
|
}
|
|
9782
|
-
const filePath =
|
|
10082
|
+
const filePath = resolve20(projectDir, folder, `${itemSlug}.md`);
|
|
9783
10083
|
if (!await fileExists(filePath)) {
|
|
9784
10084
|
res.status(404).json({ error: `${kind === "memory" ? "Memory" : "Resource"} not found` });
|
|
9785
10085
|
return;
|
|
@@ -9813,26 +10113,26 @@ ${nextBody}${nextBody.endsWith("\n") ? "" : "\n"}`;
|
|
|
9813
10113
|
res.status(400).json({ error: `Invalid slug "${slug}". Must be lowercase and hyphen-separated.` });
|
|
9814
10114
|
return;
|
|
9815
10115
|
}
|
|
9816
|
-
const projectDir =
|
|
10116
|
+
const projectDir = resolve20(projectsDir2, slug);
|
|
9817
10117
|
if (await fileExists(projectDir)) {
|
|
9818
10118
|
res.status(409).json({ error: `Project "${slug}" already exists` });
|
|
9819
10119
|
return;
|
|
9820
10120
|
}
|
|
9821
10121
|
const title = fields.title;
|
|
9822
10122
|
const timestamp = fields.created || nowTimestamp();
|
|
9823
|
-
await ensureDir(
|
|
9824
|
-
await ensureDir(
|
|
9825
|
-
await ensureDir(
|
|
9826
|
-
await writeFileForce(
|
|
10123
|
+
await ensureDir(resolve20(projectDir, "assignments"));
|
|
10124
|
+
await ensureDir(resolve20(projectDir, "resources"));
|
|
10125
|
+
await ensureDir(resolve20(projectDir, "memories"));
|
|
10126
|
+
await writeFileForce(resolve20(projectDir, "project.md"), content);
|
|
9827
10127
|
try {
|
|
9828
10128
|
const companions = [
|
|
9829
|
-
[
|
|
9830
|
-
[
|
|
9831
|
-
[
|
|
9832
|
-
[
|
|
9833
|
-
[
|
|
9834
|
-
[
|
|
9835
|
-
[
|
|
10129
|
+
[resolve20(projectDir, "manifest.md"), renderManifest({ slug, timestamp })],
|
|
10130
|
+
[resolve20(projectDir, "_index-assignments.md"), renderIndexAssignments({ slug, title, timestamp })],
|
|
10131
|
+
[resolve20(projectDir, "_index-plans.md"), renderIndexPlans({ slug, title, timestamp })],
|
|
10132
|
+
[resolve20(projectDir, "_index-decisions.md"), renderIndexDecisions({ slug, title, timestamp })],
|
|
10133
|
+
[resolve20(projectDir, "_status.md"), renderStatus({ slug, title, timestamp })],
|
|
10134
|
+
[resolve20(projectDir, "resources", "_index.md"), renderResourcesIndex({ slug, title, timestamp })],
|
|
10135
|
+
[resolve20(projectDir, "memories", "_index.md"), renderMemoriesIndex({ slug, title, timestamp })]
|
|
9836
10136
|
];
|
|
9837
10137
|
for (const [filePath, fileContent] of companions) {
|
|
9838
10138
|
await writeFileForce(filePath, fileContent);
|
|
@@ -9853,8 +10153,8 @@ ${nextBody}${nextBody.endsWith("\n") ? "" : "\n"}`;
|
|
|
9853
10153
|
router.post("/api/projects/:slug/assignments", async (req, res) => {
|
|
9854
10154
|
try {
|
|
9855
10155
|
const projectSlug = getParam(req.params.slug);
|
|
9856
|
-
const projectDir =
|
|
9857
|
-
const projectMdPath =
|
|
10156
|
+
const projectDir = resolve20(projectsDir2, projectSlug);
|
|
10157
|
+
const projectMdPath = resolve20(projectDir, "project.md");
|
|
9858
10158
|
if (!await fileExists(projectMdPath)) {
|
|
9859
10159
|
res.status(404).json({ error: `Project "${projectSlug}" not found` });
|
|
9860
10160
|
return;
|
|
@@ -9884,7 +10184,7 @@ ${nextBody}${nextBody.endsWith("\n") ? "" : "\n"}`;
|
|
|
9884
10184
|
res.status(400).json({ error: `Invalid priority "${priority}". Must be low, medium, high, or critical.` });
|
|
9885
10185
|
return;
|
|
9886
10186
|
}
|
|
9887
|
-
const assignmentDir =
|
|
10187
|
+
const assignmentDir = resolve20(projectDir, "assignments", assignmentSlug);
|
|
9888
10188
|
if (await fileExists(assignmentDir)) {
|
|
9889
10189
|
res.status(409).json({
|
|
9890
10190
|
error: `Assignment "${assignmentSlug}" already exists in project "${projectSlug}"`
|
|
@@ -9893,12 +10193,12 @@ ${nextBody}${nextBody.endsWith("\n") ? "" : "\n"}`;
|
|
|
9893
10193
|
}
|
|
9894
10194
|
const timestamp = fields.created || nowTimestamp();
|
|
9895
10195
|
await ensureDir(assignmentDir);
|
|
9896
|
-
await writeFileForce(
|
|
10196
|
+
await writeFileForce(resolve20(assignmentDir, "assignment.md"), content);
|
|
9897
10197
|
try {
|
|
9898
10198
|
const companions = [
|
|
9899
|
-
[
|
|
9900
|
-
[
|
|
9901
|
-
[
|
|
10199
|
+
[resolve20(assignmentDir, "scratchpad.md"), renderScratchpad({ assignmentSlug, timestamp })],
|
|
10200
|
+
[resolve20(assignmentDir, "handoff.md"), renderHandoff({ assignmentSlug, timestamp })],
|
|
10201
|
+
[resolve20(assignmentDir, "decision-record.md"), renderDecisionRecord({ assignmentSlug, timestamp })]
|
|
9902
10202
|
];
|
|
9903
10203
|
for (const [filePath, fileContent] of companions) {
|
|
9904
10204
|
await writeFileForce(filePath, fileContent);
|
|
@@ -9919,7 +10219,7 @@ ${nextBody}${nextBody.endsWith("\n") ? "" : "\n"}`;
|
|
|
9919
10219
|
router.patch("/api/projects/:slug", async (req, res) => {
|
|
9920
10220
|
try {
|
|
9921
10221
|
const projectSlug = getParam(req.params.slug);
|
|
9922
|
-
const projectPath =
|
|
10222
|
+
const projectPath = resolve20(projectsDir2, projectSlug, "project.md");
|
|
9923
10223
|
const currentContent = await readCurrentDocument(projectPath);
|
|
9924
10224
|
if (!currentContent) {
|
|
9925
10225
|
res.status(404).json({ error: `Project "${projectSlug}" not found` });
|
|
@@ -9952,7 +10252,7 @@ ${nextBody}${nextBody.endsWith("\n") ? "" : "\n"}`;
|
|
|
9952
10252
|
try {
|
|
9953
10253
|
const projectSlug = getParam(req.params.slug);
|
|
9954
10254
|
const assignmentSlug = getParam(req.params.aslug);
|
|
9955
|
-
const assignmentPath =
|
|
10255
|
+
const assignmentPath = resolve20(
|
|
9956
10256
|
projectsDir2,
|
|
9957
10257
|
projectSlug,
|
|
9958
10258
|
"assignments",
|
|
@@ -9995,7 +10295,7 @@ ${nextBody}${nextBody.endsWith("\n") ? "" : "\n"}`;
|
|
|
9995
10295
|
try {
|
|
9996
10296
|
const projectSlug = getParam(req.params.slug);
|
|
9997
10297
|
const assignmentSlug = getParam(req.params.aslug);
|
|
9998
|
-
const assignmentPath =
|
|
10298
|
+
const assignmentPath = resolve20(
|
|
9999
10299
|
projectsDir2,
|
|
10000
10300
|
projectSlug,
|
|
10001
10301
|
"assignments",
|
|
@@ -10031,7 +10331,7 @@ ${nextBody}${nextBody.endsWith("\n") ? "" : "\n"}`;
|
|
|
10031
10331
|
try {
|
|
10032
10332
|
const projectSlug = getParam(req.params.slug);
|
|
10033
10333
|
const assignmentSlug = getParam(req.params.aslug);
|
|
10034
|
-
const planPath =
|
|
10334
|
+
const planPath = resolve20(
|
|
10035
10335
|
projectsDir2,
|
|
10036
10336
|
projectSlug,
|
|
10037
10337
|
"assignments",
|
|
@@ -10069,7 +10369,7 @@ ${nextBody}${nextBody.endsWith("\n") ? "" : "\n"}`;
|
|
|
10069
10369
|
try {
|
|
10070
10370
|
const projectSlug = getParam(req.params.slug);
|
|
10071
10371
|
const assignmentSlug = getParam(req.params.aslug);
|
|
10072
|
-
const scratchpadPath =
|
|
10372
|
+
const scratchpadPath = resolve20(
|
|
10073
10373
|
projectsDir2,
|
|
10074
10374
|
projectSlug,
|
|
10075
10375
|
"assignments",
|
|
@@ -10107,7 +10407,7 @@ ${nextBody}${nextBody.endsWith("\n") ? "" : "\n"}`;
|
|
|
10107
10407
|
try {
|
|
10108
10408
|
const projectSlug = getParam(req.params.slug);
|
|
10109
10409
|
const assignmentSlug = getParam(req.params.aslug);
|
|
10110
|
-
const handoffPath =
|
|
10410
|
+
const handoffPath = resolve20(
|
|
10111
10411
|
projectsDir2,
|
|
10112
10412
|
projectSlug,
|
|
10113
10413
|
"assignments",
|
|
@@ -10145,7 +10445,7 @@ ${nextBody}${nextBody.endsWith("\n") ? "" : "\n"}`;
|
|
|
10145
10445
|
try {
|
|
10146
10446
|
const projectSlug = getParam(req.params.slug);
|
|
10147
10447
|
const assignmentSlug = getParam(req.params.aslug);
|
|
10148
|
-
const decisionPath =
|
|
10448
|
+
const decisionPath = resolve20(
|
|
10149
10449
|
projectsDir2,
|
|
10150
10450
|
projectSlug,
|
|
10151
10451
|
"assignments",
|
|
@@ -10183,7 +10483,7 @@ ${nextBody}${nextBody.endsWith("\n") ? "" : "\n"}`;
|
|
|
10183
10483
|
try {
|
|
10184
10484
|
const projectSlug = getParam(req.params.slug);
|
|
10185
10485
|
const assignmentSlug = getParam(req.params.aslug);
|
|
10186
|
-
const commentsPath =
|
|
10486
|
+
const commentsPath = resolve20(
|
|
10187
10487
|
projectsDir2,
|
|
10188
10488
|
projectSlug,
|
|
10189
10489
|
"assignments",
|
|
@@ -10201,7 +10501,7 @@ ${nextBody}${nextBody.endsWith("\n") ? "" : "\n"}`;
|
|
|
10201
10501
|
let currentContent;
|
|
10202
10502
|
let currentCount = 0;
|
|
10203
10503
|
if (await fileExists(commentsPath)) {
|
|
10204
|
-
currentContent = await
|
|
10504
|
+
currentContent = await readFile15(commentsPath, "utf-8");
|
|
10205
10505
|
const countMatch = currentContent.match(/^entryCount:\s*(\d+)/m);
|
|
10206
10506
|
if (countMatch) currentCount = parseInt(countMatch[1], 10);
|
|
10207
10507
|
} else {
|
|
@@ -10242,7 +10542,7 @@ ${entry}`;
|
|
|
10242
10542
|
const projectSlug = getParam(req.params.slug);
|
|
10243
10543
|
const assignmentSlug = getParam(req.params.aslug);
|
|
10244
10544
|
const commentId = getParam(req.params.commentId);
|
|
10245
|
-
const commentsPath =
|
|
10545
|
+
const commentsPath = resolve20(
|
|
10246
10546
|
projectsDir2,
|
|
10247
10547
|
projectSlug,
|
|
10248
10548
|
"assignments",
|
|
@@ -10258,7 +10558,7 @@ ${entry}`;
|
|
|
10258
10558
|
res.status(400).json({ error: "resolved (boolean) is required" });
|
|
10259
10559
|
return;
|
|
10260
10560
|
}
|
|
10261
|
-
const content = await
|
|
10561
|
+
const content = await readFile15(commentsPath, "utf-8");
|
|
10262
10562
|
const parsed = parseComments(content);
|
|
10263
10563
|
const target = parsed.entries.find((e) => e.id === commentId);
|
|
10264
10564
|
if (!target) {
|
|
@@ -10293,7 +10593,7 @@ ${entry}`;
|
|
|
10293
10593
|
router.post("/api/projects/:slug/move-workspace", async (req, res) => {
|
|
10294
10594
|
try {
|
|
10295
10595
|
const projectSlug = getParam(req.params.slug);
|
|
10296
|
-
const projectPath =
|
|
10596
|
+
const projectPath = resolve20(projectsDir2, projectSlug, "project.md");
|
|
10297
10597
|
if (!await fileExists(projectPath)) {
|
|
10298
10598
|
res.status(404).json({ error: `Project "${projectSlug}" not found` });
|
|
10299
10599
|
return;
|
|
@@ -10305,7 +10605,7 @@ ${entry}`;
|
|
|
10305
10605
|
});
|
|
10306
10606
|
return;
|
|
10307
10607
|
}
|
|
10308
|
-
let content = await
|
|
10608
|
+
let content = await readFile15(projectPath, "utf-8");
|
|
10309
10609
|
content = setTopLevelField(content, "workspace", workspace ?? null);
|
|
10310
10610
|
content = setTopLevelField(content, "updated", nowTimestamp());
|
|
10311
10611
|
await writeFileForce(projectPath, content);
|
|
@@ -10332,31 +10632,129 @@ ${entry}`;
|
|
|
10332
10632
|
res.status(400).json({
|
|
10333
10633
|
error: "Project-nested assignments inherit workspace from their parent project. Move the project instead."
|
|
10334
10634
|
});
|
|
10335
|
-
return;
|
|
10635
|
+
return;
|
|
10636
|
+
}
|
|
10637
|
+
const { workspaceGroup } = req.body || {};
|
|
10638
|
+
if (workspaceGroup !== null && (typeof workspaceGroup !== "string" || !workspaceGroup.trim() || !isValidSlug(workspaceGroup))) {
|
|
10639
|
+
res.status(400).json({
|
|
10640
|
+
error: "workspaceGroup must be a valid slug (lowercase letters, numbers, hyphens) or null (for ungrouped)."
|
|
10641
|
+
});
|
|
10642
|
+
return;
|
|
10643
|
+
}
|
|
10644
|
+
const assignmentPath = resolve20(resolved.assignmentDir, "assignment.md");
|
|
10645
|
+
let content = await readFile15(assignmentPath, "utf-8");
|
|
10646
|
+
content = setTopLevelField(content, "workspaceGroup", workspaceGroup ?? null);
|
|
10647
|
+
content = setTopLevelField(content, "updated", nowTimestamp());
|
|
10648
|
+
await writeFileForce(assignmentPath, content);
|
|
10649
|
+
const assignment = await getAssignmentDetailById(projectsDir2, assignmentsDir2, id);
|
|
10650
|
+
res.json({ assignment });
|
|
10651
|
+
} catch (error) {
|
|
10652
|
+
console.error("Error moving assignment workspace:", error);
|
|
10653
|
+
res.status(500).json({ error: `Failed to move workspace: ${error.message}` });
|
|
10654
|
+
}
|
|
10655
|
+
});
|
|
10656
|
+
router.get(
|
|
10657
|
+
"/api/projects/:slug/repository-candidates",
|
|
10658
|
+
async (req, res) => {
|
|
10659
|
+
try {
|
|
10660
|
+
const projectSlug = getParam(req.params.slug);
|
|
10661
|
+
const projectPath = resolve20(projectsDir2, projectSlug, "project.md");
|
|
10662
|
+
if (!await fileExists(projectPath)) {
|
|
10663
|
+
res.status(404).json({ error: `Project "${projectSlug}" not found` });
|
|
10664
|
+
return;
|
|
10665
|
+
}
|
|
10666
|
+
const candidates = await getProjectRepositoryCandidates(projectsDir2, projectSlug);
|
|
10667
|
+
res.json({ candidates });
|
|
10668
|
+
} catch (error) {
|
|
10669
|
+
console.error("Error listing repository candidates:", error);
|
|
10670
|
+
res.status(500).json({
|
|
10671
|
+
error: `Failed to list repository candidates: ${error.message}`
|
|
10672
|
+
});
|
|
10673
|
+
}
|
|
10674
|
+
}
|
|
10675
|
+
);
|
|
10676
|
+
router.get(
|
|
10677
|
+
"/api/assignments/:id/repository-candidates",
|
|
10678
|
+
async (req, res) => {
|
|
10679
|
+
try {
|
|
10680
|
+
if (!assignmentsDir2) {
|
|
10681
|
+
res.status(501).json({ error: "Standalone assignments not configured on this server" });
|
|
10682
|
+
return;
|
|
10683
|
+
}
|
|
10684
|
+
const id = getParam(req.params.id);
|
|
10685
|
+
const resolved = await resolveAssignmentById(projectsDir2, assignmentsDir2, id);
|
|
10686
|
+
if (!resolved) {
|
|
10687
|
+
res.status(404).json({ error: `Assignment "${id}" not found` });
|
|
10688
|
+
return;
|
|
10689
|
+
}
|
|
10690
|
+
const candidates = resolved.standalone ? await getStandaloneRepositoryCandidates(assignmentsDir2, id) : await getProjectRepositoryCandidates(projectsDir2, resolved.projectSlug);
|
|
10691
|
+
res.json({ candidates });
|
|
10692
|
+
} catch (error) {
|
|
10693
|
+
console.error("Error listing repository candidates:", error);
|
|
10694
|
+
res.status(500).json({
|
|
10695
|
+
error: `Failed to list repository candidates: ${error.message}`
|
|
10696
|
+
});
|
|
10697
|
+
}
|
|
10698
|
+
}
|
|
10699
|
+
);
|
|
10700
|
+
router.post(
|
|
10701
|
+
"/api/projects/:slug/assignments/:aslug/worktree",
|
|
10702
|
+
async (req, res) => {
|
|
10703
|
+
try {
|
|
10704
|
+
const projectSlug = getParam(req.params.slug);
|
|
10705
|
+
const assignmentSlug = getParam(req.params.aslug);
|
|
10706
|
+
const assignmentPath = resolve20(
|
|
10707
|
+
projectsDir2,
|
|
10708
|
+
projectSlug,
|
|
10709
|
+
"assignments",
|
|
10710
|
+
assignmentSlug,
|
|
10711
|
+
"assignment.md"
|
|
10712
|
+
);
|
|
10713
|
+
await handleWorktreeCreate(req, res, {
|
|
10714
|
+
assignmentPath,
|
|
10715
|
+
projectSlug,
|
|
10716
|
+
assignmentSlug,
|
|
10717
|
+
reload: () => getAssignmentDetail(projectsDir2, projectSlug, assignmentSlug)
|
|
10718
|
+
});
|
|
10719
|
+
} catch (error) {
|
|
10720
|
+
console.error("Error creating worktree:", error);
|
|
10721
|
+
res.status(500).json({ error: `Failed to create worktree: ${error.message}` });
|
|
10336
10722
|
}
|
|
10337
|
-
|
|
10338
|
-
|
|
10339
|
-
|
|
10340
|
-
|
|
10723
|
+
}
|
|
10724
|
+
);
|
|
10725
|
+
router.post(
|
|
10726
|
+
"/api/assignments/:id/worktree",
|
|
10727
|
+
async (req, res) => {
|
|
10728
|
+
try {
|
|
10729
|
+
if (!assignmentsDir2) {
|
|
10730
|
+
res.status(501).json({ error: "Standalone assignments not configured on this server" });
|
|
10731
|
+
return;
|
|
10732
|
+
}
|
|
10733
|
+
const id = getParam(req.params.id);
|
|
10734
|
+
const resolved = await resolveAssignmentById(projectsDir2, assignmentsDir2, id);
|
|
10735
|
+
if (!resolved) {
|
|
10736
|
+
res.status(404).json({ error: `Assignment "${id}" not found` });
|
|
10737
|
+
return;
|
|
10738
|
+
}
|
|
10739
|
+
const assignmentPath = resolve20(resolved.assignmentDir, "assignment.md");
|
|
10740
|
+
const parsedForSlug = parseAssignmentFull(await readFile15(assignmentPath, "utf-8"));
|
|
10741
|
+
const assignmentSlugForBranch = parsedForSlug.slug || resolved.id;
|
|
10742
|
+
await handleWorktreeCreate(req, res, {
|
|
10743
|
+
assignmentPath,
|
|
10744
|
+
projectSlug: resolved.projectSlug ?? "",
|
|
10745
|
+
assignmentSlug: assignmentSlugForBranch,
|
|
10746
|
+
reload: () => getAssignmentDetailById(projectsDir2, assignmentsDir2, id)
|
|
10341
10747
|
});
|
|
10342
|
-
|
|
10748
|
+
} catch (error) {
|
|
10749
|
+
console.error("Error creating worktree:", error);
|
|
10750
|
+
res.status(500).json({ error: `Failed to create worktree: ${error.message}` });
|
|
10343
10751
|
}
|
|
10344
|
-
const assignmentPath = resolve18(resolved.assignmentDir, "assignment.md");
|
|
10345
|
-
let content = await readFile13(assignmentPath, "utf-8");
|
|
10346
|
-
content = setTopLevelField(content, "workspaceGroup", workspaceGroup ?? null);
|
|
10347
|
-
content = setTopLevelField(content, "updated", nowTimestamp());
|
|
10348
|
-
await writeFileForce(assignmentPath, content);
|
|
10349
|
-
const assignment = await getAssignmentDetailById(projectsDir2, assignmentsDir2, id);
|
|
10350
|
-
res.json({ assignment });
|
|
10351
|
-
} catch (error) {
|
|
10352
|
-
console.error("Error moving assignment workspace:", error);
|
|
10353
|
-
res.status(500).json({ error: `Failed to move workspace: ${error.message}` });
|
|
10354
10752
|
}
|
|
10355
|
-
|
|
10753
|
+
);
|
|
10356
10754
|
router.post("/api/projects/:slug/status-override", async (req, res) => {
|
|
10357
10755
|
try {
|
|
10358
10756
|
const projectSlug = getParam(req.params.slug);
|
|
10359
|
-
const projectPath =
|
|
10757
|
+
const projectPath = resolve20(projectsDir2, projectSlug, "project.md");
|
|
10360
10758
|
if (!await fileExists(projectPath)) {
|
|
10361
10759
|
res.status(404).json({ error: `Project "${projectSlug}" not found` });
|
|
10362
10760
|
return;
|
|
@@ -10368,7 +10766,7 @@ ${entry}`;
|
|
|
10368
10766
|
res.status(400).json({ error: `Invalid status. Must be one of: ${validStatuses.join(", ")}, or null to clear.` });
|
|
10369
10767
|
return;
|
|
10370
10768
|
}
|
|
10371
|
-
let content = await
|
|
10769
|
+
let content = await readFile15(projectPath, "utf-8");
|
|
10372
10770
|
content = setTopLevelField(content, "statusOverride", status ?? null);
|
|
10373
10771
|
content = setTopLevelField(content, "updated", nowTimestamp());
|
|
10374
10772
|
await writeFileForce(projectPath, content);
|
|
@@ -10383,7 +10781,7 @@ ${entry}`;
|
|
|
10383
10781
|
try {
|
|
10384
10782
|
const projectSlug = getParam(req.params.slug);
|
|
10385
10783
|
const assignmentSlug = getParam(req.params.aslug);
|
|
10386
|
-
const assignmentPath =
|
|
10784
|
+
const assignmentPath = resolve20(
|
|
10387
10785
|
projectsDir2,
|
|
10388
10786
|
projectSlug,
|
|
10389
10787
|
"assignments",
|
|
@@ -10401,7 +10799,7 @@ ${entry}`;
|
|
|
10401
10799
|
res.status(400).json({ error: `Invalid status. Must be one of: ${validStatuses.join(", ")}.` });
|
|
10402
10800
|
return;
|
|
10403
10801
|
}
|
|
10404
|
-
let content = await
|
|
10802
|
+
let content = await readFile15(assignmentPath, "utf-8");
|
|
10405
10803
|
content = setTopLevelField(content, "status", status);
|
|
10406
10804
|
content = setTopLevelField(content, "updated", nowTimestamp());
|
|
10407
10805
|
if (status !== "blocked") {
|
|
@@ -10419,7 +10817,7 @@ ${entry}`;
|
|
|
10419
10817
|
try {
|
|
10420
10818
|
const projectSlug = getParam(req.params.slug);
|
|
10421
10819
|
const assignmentSlug = getParam(req.params.aslug);
|
|
10422
|
-
const assignmentPath =
|
|
10820
|
+
const assignmentPath = resolve20(
|
|
10423
10821
|
projectsDir2,
|
|
10424
10822
|
projectSlug,
|
|
10425
10823
|
"assignments",
|
|
@@ -10435,7 +10833,7 @@ ${entry}`;
|
|
|
10435
10833
|
res.status(400).json({ error: validation.error });
|
|
10436
10834
|
return;
|
|
10437
10835
|
}
|
|
10438
|
-
let content = await
|
|
10836
|
+
let content = await readFile15(assignmentPath, "utf-8");
|
|
10439
10837
|
content = setTopLevelField(content, "assignee", validation.value);
|
|
10440
10838
|
content = setTopLevelField(content, "updated", nowTimestamp());
|
|
10441
10839
|
await writeFileForce(assignmentPath, content);
|
|
@@ -10457,8 +10855,8 @@ ${entry}`;
|
|
|
10457
10855
|
res.status(400).json({ error: `Unsupported transition command "${req.params.command}"` });
|
|
10458
10856
|
return;
|
|
10459
10857
|
}
|
|
10460
|
-
const projectDir =
|
|
10461
|
-
const assignmentPath =
|
|
10858
|
+
const projectDir = resolve20(projectsDir2, projectSlug);
|
|
10859
|
+
const assignmentPath = resolve20(projectDir, "assignments", assignmentSlug, "assignment.md");
|
|
10462
10860
|
if (!await fileExists(assignmentPath)) {
|
|
10463
10861
|
res.status(404).json({ error: "Assignment not found" });
|
|
10464
10862
|
return;
|
|
@@ -10485,8 +10883,8 @@ ${entry}`;
|
|
|
10485
10883
|
try {
|
|
10486
10884
|
const projectSlug = getParam(req.params.slug);
|
|
10487
10885
|
const assignmentSlug = getParam(req.params.aslug);
|
|
10488
|
-
const assignmentDir =
|
|
10489
|
-
const assignmentPath =
|
|
10886
|
+
const assignmentDir = resolve20(projectsDir2, projectSlug, "assignments", assignmentSlug);
|
|
10887
|
+
const assignmentPath = resolve20(assignmentDir, "assignment.md");
|
|
10490
10888
|
if (!await fileExists(assignmentPath)) {
|
|
10491
10889
|
res.status(404).json({ error: `Assignment "${assignmentSlug}" not found in project "${projectSlug}"` });
|
|
10492
10890
|
return;
|
|
@@ -10541,7 +10939,7 @@ ${entry}`;
|
|
|
10541
10939
|
return;
|
|
10542
10940
|
}
|
|
10543
10941
|
const id2 = generateId();
|
|
10544
|
-
const assignmentDir2 =
|
|
10942
|
+
const assignmentDir2 = resolve20(assignmentsDir2, id2);
|
|
10545
10943
|
if (await fileExists(assignmentDir2)) {
|
|
10546
10944
|
res.status(500).json({ error: "UUID collision \u2014 try again" });
|
|
10547
10945
|
return;
|
|
@@ -10549,25 +10947,25 @@ ${entry}`;
|
|
|
10549
10947
|
const timestamp2 = fields.created || nowTimestamp();
|
|
10550
10948
|
await ensureDir(assignmentDir2);
|
|
10551
10949
|
const normalizedContent = setTopLevelField(rawContent, "id", id2);
|
|
10552
|
-
await writeFileForce(
|
|
10950
|
+
await writeFileForce(resolve20(assignmentDir2, "assignment.md"), normalizedContent);
|
|
10553
10951
|
await writeFileForce(
|
|
10554
|
-
|
|
10952
|
+
resolve20(assignmentDir2, "scratchpad.md"),
|
|
10555
10953
|
renderScratchpad({ assignmentSlug: id2, timestamp: timestamp2 })
|
|
10556
10954
|
);
|
|
10557
10955
|
await writeFileForce(
|
|
10558
|
-
|
|
10956
|
+
resolve20(assignmentDir2, "handoff.md"),
|
|
10559
10957
|
renderHandoff({ assignmentSlug: id2, timestamp: timestamp2 })
|
|
10560
10958
|
);
|
|
10561
10959
|
await writeFileForce(
|
|
10562
|
-
|
|
10960
|
+
resolve20(assignmentDir2, "decision-record.md"),
|
|
10563
10961
|
renderDecisionRecord({ assignmentSlug: id2, timestamp: timestamp2 })
|
|
10564
10962
|
);
|
|
10565
10963
|
await writeFileForce(
|
|
10566
|
-
|
|
10964
|
+
resolve20(assignmentDir2, "progress.md"),
|
|
10567
10965
|
renderProgress({ assignment: id2, timestamp: timestamp2 })
|
|
10568
10966
|
);
|
|
10569
10967
|
await writeFileForce(
|
|
10570
|
-
|
|
10968
|
+
resolve20(assignmentDir2, "comments.md"),
|
|
10571
10969
|
renderComments({ assignment: id2, timestamp: timestamp2 })
|
|
10572
10970
|
);
|
|
10573
10971
|
const detail2 = await getAssignmentDetailById(projectsDir2, assignmentsDir2, id2);
|
|
@@ -10585,7 +10983,7 @@ ${entry}`;
|
|
|
10585
10983
|
return;
|
|
10586
10984
|
}
|
|
10587
10985
|
const id = generateId();
|
|
10588
|
-
const assignmentDir =
|
|
10986
|
+
const assignmentDir = resolve20(assignmentsDir2, id);
|
|
10589
10987
|
if (await fileExists(assignmentDir)) {
|
|
10590
10988
|
res.status(500).json({ error: "UUID collision \u2014 try again" });
|
|
10591
10989
|
return;
|
|
@@ -10605,25 +11003,25 @@ ${entry}`;
|
|
|
10605
11003
|
project: null,
|
|
10606
11004
|
type: typeof type === "string" ? type : void 0
|
|
10607
11005
|
});
|
|
10608
|
-
await writeFileForce(
|
|
11006
|
+
await writeFileForce(resolve20(assignmentDir, "assignment.md"), assignmentContent);
|
|
10609
11007
|
await writeFileForce(
|
|
10610
|
-
|
|
11008
|
+
resolve20(assignmentDir, "scratchpad.md"),
|
|
10611
11009
|
renderScratchpad({ assignmentSlug: id, timestamp })
|
|
10612
11010
|
);
|
|
10613
11011
|
await writeFileForce(
|
|
10614
|
-
|
|
11012
|
+
resolve20(assignmentDir, "handoff.md"),
|
|
10615
11013
|
renderHandoff({ assignmentSlug: id, timestamp })
|
|
10616
11014
|
);
|
|
10617
11015
|
await writeFileForce(
|
|
10618
|
-
|
|
11016
|
+
resolve20(assignmentDir, "decision-record.md"),
|
|
10619
11017
|
renderDecisionRecord({ assignmentSlug: id, timestamp })
|
|
10620
11018
|
);
|
|
10621
11019
|
await writeFileForce(
|
|
10622
|
-
|
|
11020
|
+
resolve20(assignmentDir, "progress.md"),
|
|
10623
11021
|
renderProgress({ assignment: id, timestamp })
|
|
10624
11022
|
);
|
|
10625
11023
|
await writeFileForce(
|
|
10626
|
-
|
|
11024
|
+
resolve20(assignmentDir, "comments.md"),
|
|
10627
11025
|
renderComments({ assignment: id, timestamp })
|
|
10628
11026
|
);
|
|
10629
11027
|
const detail = await getAssignmentDetailById(projectsDir2, assignmentsDir2, id);
|
|
@@ -10751,7 +11149,7 @@ ${entry}`;
|
|
|
10751
11149
|
res.status(404).json({ error: `Assignment "${id}" not found` });
|
|
10752
11150
|
return;
|
|
10753
11151
|
}
|
|
10754
|
-
const assignmentPath =
|
|
11152
|
+
const assignmentPath = resolve20(resolved.assignmentDir, "assignment.md");
|
|
10755
11153
|
const currentContent = await readCurrentDocument(assignmentPath);
|
|
10756
11154
|
if (!currentContent) {
|
|
10757
11155
|
res.status(404).json({ error: "Assignment not found" });
|
|
@@ -10793,7 +11191,7 @@ ${entry}`;
|
|
|
10793
11191
|
res.status(404).json({ error: `Assignment "${id}" not found` });
|
|
10794
11192
|
return;
|
|
10795
11193
|
}
|
|
10796
|
-
const planPath =
|
|
11194
|
+
const planPath = resolve20(resolved.assignmentDir, "plan.md");
|
|
10797
11195
|
const currentContent = await readCurrentDocument(planPath);
|
|
10798
11196
|
if (!currentContent) {
|
|
10799
11197
|
res.status(404).json({ error: "Plan not found" });
|
|
@@ -10827,7 +11225,7 @@ ${entry}`;
|
|
|
10827
11225
|
res.status(404).json({ error: `Assignment "${id}" not found` });
|
|
10828
11226
|
return;
|
|
10829
11227
|
}
|
|
10830
|
-
const scratchpadPath =
|
|
11228
|
+
const scratchpadPath = resolve20(resolved.assignmentDir, "scratchpad.md");
|
|
10831
11229
|
const currentContent = await readCurrentDocument(scratchpadPath);
|
|
10832
11230
|
if (!currentContent) {
|
|
10833
11231
|
res.status(404).json({ error: "Scratchpad not found" });
|
|
@@ -10861,7 +11259,7 @@ ${entry}`;
|
|
|
10861
11259
|
res.status(404).json({ error: `Assignment "${id}" not found` });
|
|
10862
11260
|
return;
|
|
10863
11261
|
}
|
|
10864
|
-
const handoffPath =
|
|
11262
|
+
const handoffPath = resolve20(resolved.assignmentDir, "handoff.md");
|
|
10865
11263
|
const currentContent = await readCurrentDocument(handoffPath);
|
|
10866
11264
|
if (!currentContent) {
|
|
10867
11265
|
res.status(404).json({ error: "Handoff log not found" });
|
|
@@ -10901,7 +11299,7 @@ ${entry}`;
|
|
|
10901
11299
|
res.status(404).json({ error: `Assignment "${id}" not found` });
|
|
10902
11300
|
return;
|
|
10903
11301
|
}
|
|
10904
|
-
const decisionPath =
|
|
11302
|
+
const decisionPath = resolve20(resolved.assignmentDir, "decision-record.md");
|
|
10905
11303
|
const currentContent = await readCurrentDocument(decisionPath);
|
|
10906
11304
|
if (!currentContent) {
|
|
10907
11305
|
res.status(404).json({ error: "Decision record not found" });
|
|
@@ -10941,7 +11339,7 @@ ${entry}`;
|
|
|
10941
11339
|
res.status(404).json({ error: `Assignment "${id}" not found` });
|
|
10942
11340
|
return;
|
|
10943
11341
|
}
|
|
10944
|
-
const assignmentPath =
|
|
11342
|
+
const assignmentPath = resolve20(resolved.assignmentDir, "assignment.md");
|
|
10945
11343
|
if (!await fileExists(assignmentPath)) {
|
|
10946
11344
|
res.status(404).json({ error: "Assignment not found" });
|
|
10947
11345
|
return;
|
|
@@ -10953,7 +11351,7 @@ ${entry}`;
|
|
|
10953
11351
|
res.status(400).json({ error: `Invalid status. Must be one of: ${validStatuses.join(", ")}.` });
|
|
10954
11352
|
return;
|
|
10955
11353
|
}
|
|
10956
|
-
let content = await
|
|
11354
|
+
let content = await readFile15(assignmentPath, "utf-8");
|
|
10957
11355
|
content = setTopLevelField(content, "status", status);
|
|
10958
11356
|
content = setTopLevelField(content, "updated", nowTimestamp());
|
|
10959
11357
|
if (status !== "blocked") {
|
|
@@ -10979,7 +11377,7 @@ ${entry}`;
|
|
|
10979
11377
|
res.status(404).json({ error: `Assignment "${id}" not found` });
|
|
10980
11378
|
return;
|
|
10981
11379
|
}
|
|
10982
|
-
const assignmentPath =
|
|
11380
|
+
const assignmentPath = resolve20(resolved.assignmentDir, "assignment.md");
|
|
10983
11381
|
if (!await fileExists(assignmentPath)) {
|
|
10984
11382
|
res.status(404).json({ error: "Assignment not found" });
|
|
10985
11383
|
return;
|
|
@@ -10989,7 +11387,7 @@ ${entry}`;
|
|
|
10989
11387
|
res.status(400).json({ error: validation.error });
|
|
10990
11388
|
return;
|
|
10991
11389
|
}
|
|
10992
|
-
let content = await
|
|
11390
|
+
let content = await readFile15(assignmentPath, "utf-8");
|
|
10993
11391
|
content = setTopLevelField(content, "assignee", validation.value);
|
|
10994
11392
|
content = setTopLevelField(content, "updated", nowTimestamp());
|
|
10995
11393
|
await writeFileForce(assignmentPath, content);
|
|
@@ -11043,9 +11441,9 @@ ${entry}`;
|
|
|
11043
11441
|
if (!resolved) {
|
|
11044
11442
|
throw new Error(`assignment "${item.id}" not found`);
|
|
11045
11443
|
}
|
|
11046
|
-
assignmentPath =
|
|
11444
|
+
assignmentPath = resolve20(resolved.assignmentDir, "assignment.md");
|
|
11047
11445
|
} else if (typeof item.projectSlug === "string" && typeof item.assignmentSlug === "string" && item.projectSlug && item.assignmentSlug) {
|
|
11048
|
-
assignmentPath =
|
|
11446
|
+
assignmentPath = resolve20(
|
|
11049
11447
|
projectsDir2,
|
|
11050
11448
|
item.projectSlug,
|
|
11051
11449
|
"assignments",
|
|
@@ -11058,7 +11456,7 @@ ${entry}`;
|
|
|
11058
11456
|
if (!await fileExists(assignmentPath)) {
|
|
11059
11457
|
throw new Error("assignment file not found");
|
|
11060
11458
|
}
|
|
11061
|
-
let content = await
|
|
11459
|
+
let content = await readFile15(assignmentPath, "utf-8");
|
|
11062
11460
|
content = setTopLevelField(content, "status", status);
|
|
11063
11461
|
content = setTopLevelField(content, "updated", timestamp);
|
|
11064
11462
|
if (status !== "blocked") {
|
|
@@ -11090,7 +11488,7 @@ ${entry}`;
|
|
|
11090
11488
|
res.status(404).json({ error: `Assignment "${id}" not found` });
|
|
11091
11489
|
return;
|
|
11092
11490
|
}
|
|
11093
|
-
const assignmentPath =
|
|
11491
|
+
const assignmentPath = resolve20(resolved.assignmentDir, "assignment.md");
|
|
11094
11492
|
const currentContent = await readCurrentDocument(assignmentPath);
|
|
11095
11493
|
if (!currentContent) {
|
|
11096
11494
|
res.status(404).json({ error: "Assignment not found" });
|
|
@@ -11182,7 +11580,7 @@ function buildBulkItemKey(raw, index) {
|
|
|
11182
11580
|
return `#${index}`;
|
|
11183
11581
|
}
|
|
11184
11582
|
async function appendCommentTo(assignmentDir, assignmentRef, req, res, reloadDetail) {
|
|
11185
|
-
const commentsPath =
|
|
11583
|
+
const commentsPath = resolve20(assignmentDir, "comments.md");
|
|
11186
11584
|
const { body, author, type, replyTo } = req.body || {};
|
|
11187
11585
|
if (!body || typeof body !== "string" || !body.trim()) {
|
|
11188
11586
|
res.status(400).json({ error: "body is required" });
|
|
@@ -11194,7 +11592,7 @@ async function appendCommentTo(assignmentDir, assignmentRef, req, res, reloadDet
|
|
|
11194
11592
|
let currentContent;
|
|
11195
11593
|
let currentCount = 0;
|
|
11196
11594
|
if (await fileExists(commentsPath)) {
|
|
11197
|
-
currentContent = await
|
|
11595
|
+
currentContent = await readFile15(commentsPath, "utf-8");
|
|
11198
11596
|
const countMatch = currentContent.match(/^entryCount:\s*(\d+)/m);
|
|
11199
11597
|
if (countMatch) currentCount = parseInt(countMatch[1], 10);
|
|
11200
11598
|
} else {
|
|
@@ -11224,7 +11622,7 @@ ${entry}`;
|
|
|
11224
11622
|
res.status(201).json({ assignment, comment: { id: comment.id } });
|
|
11225
11623
|
}
|
|
11226
11624
|
async function toggleCommentResolvedAt(assignmentDir, commentId, req, res, reloadDetail) {
|
|
11227
|
-
const commentsPath =
|
|
11625
|
+
const commentsPath = resolve20(assignmentDir, "comments.md");
|
|
11228
11626
|
if (!await fileExists(commentsPath)) {
|
|
11229
11627
|
res.status(404).json({ error: "Comments file not found" });
|
|
11230
11628
|
return;
|
|
@@ -11234,7 +11632,7 @@ async function toggleCommentResolvedAt(assignmentDir, commentId, req, res, reloa
|
|
|
11234
11632
|
res.status(400).json({ error: "resolved (boolean) is required" });
|
|
11235
11633
|
return;
|
|
11236
11634
|
}
|
|
11237
|
-
const content = await
|
|
11635
|
+
const content = await readFile15(commentsPath, "utf-8");
|
|
11238
11636
|
const parsed = parseComments(content);
|
|
11239
11637
|
const target = parsed.entries.find((e) => e.id === commentId);
|
|
11240
11638
|
if (!target) {
|
|
@@ -11381,7 +11779,7 @@ function createServersRouter(serversDir2, projectsDir2, assignmentsDir2) {
|
|
|
11381
11779
|
init_agent_sessions();
|
|
11382
11780
|
init_fs();
|
|
11383
11781
|
import { Router as Router3 } from "express";
|
|
11384
|
-
import { resolve as
|
|
11782
|
+
import { resolve as resolve21 } from "path";
|
|
11385
11783
|
|
|
11386
11784
|
// src/utils/transcript.ts
|
|
11387
11785
|
import { open } from "fs/promises";
|
|
@@ -11479,7 +11877,7 @@ function createAgentSessionsRouter(projectsDir2, broadcast, assignmentsDir2) {
|
|
|
11479
11877
|
try {
|
|
11480
11878
|
const { projectSlug } = req.params;
|
|
11481
11879
|
const assignment = req.query.assignment;
|
|
11482
|
-
const projectDir =
|
|
11880
|
+
const projectDir = resolve21(projectsDir2, projectSlug);
|
|
11483
11881
|
if (!await fileExists(projectDir)) {
|
|
11484
11882
|
res.status(404).json({ error: `Project "${projectSlug}" not found` });
|
|
11485
11883
|
return;
|
|
@@ -11509,7 +11907,7 @@ function createAgentSessionsRouter(projectsDir2, broadcast, assignmentsDir2) {
|
|
|
11509
11907
|
return;
|
|
11510
11908
|
}
|
|
11511
11909
|
if (projectSlug) {
|
|
11512
|
-
const projectDir =
|
|
11910
|
+
const projectDir = resolve21(projectsDir2, projectSlug);
|
|
11513
11911
|
if (!await fileExists(projectDir)) {
|
|
11514
11912
|
res.status(404).json({ error: `Project "${projectSlug}" not found` });
|
|
11515
11913
|
return;
|
|
@@ -11833,14 +12231,136 @@ function createAgentsRouter() {
|
|
|
11833
12231
|
return router;
|
|
11834
12232
|
}
|
|
11835
12233
|
|
|
11836
|
-
// src/dashboard/api-
|
|
12234
|
+
// src/dashboard/api-launch-preflight.ts
|
|
12235
|
+
init_config2();
|
|
11837
12236
|
import { Router as Router5 } from "express";
|
|
11838
12237
|
|
|
12238
|
+
// src/utils/terminal-probe.ts
|
|
12239
|
+
import { spawnSync as spawnSync4 } from "child_process";
|
|
12240
|
+
var APP_BUNDLE_IDS = {
|
|
12241
|
+
"terminal-app": "com.apple.Terminal",
|
|
12242
|
+
iterm: "com.googlecode.iterm2",
|
|
12243
|
+
ghostty: "com.mitchellh.ghostty",
|
|
12244
|
+
warp: "dev.warp.Warp-Stable"
|
|
12245
|
+
};
|
|
12246
|
+
var CLI_NAMES = {
|
|
12247
|
+
alacritty: "alacritty",
|
|
12248
|
+
kitty: "kitty"
|
|
12249
|
+
};
|
|
12250
|
+
function probeTerminalInstalled(terminal) {
|
|
12251
|
+
const bundleId = APP_BUNDLE_IDS[terminal];
|
|
12252
|
+
if (bundleId) {
|
|
12253
|
+
const result = spawnSync4(
|
|
12254
|
+
"mdfind",
|
|
12255
|
+
[`kMDItemCFBundleIdentifier == '${bundleId}'`],
|
|
12256
|
+
{ encoding: "utf-8" }
|
|
12257
|
+
);
|
|
12258
|
+
if (result.status === 0 && result.stdout.trim().length > 0) {
|
|
12259
|
+
return { ok: true, foundPath: result.stdout.trim().split("\n")[0] };
|
|
12260
|
+
}
|
|
12261
|
+
return { ok: false, reason: "not-installed" };
|
|
12262
|
+
}
|
|
12263
|
+
const cliName = CLI_NAMES[terminal];
|
|
12264
|
+
if (cliName) {
|
|
12265
|
+
const result = spawnSync4("which", [cliName], { encoding: "utf-8" });
|
|
12266
|
+
if (result.status === 0 && result.stdout.trim().length > 0) {
|
|
12267
|
+
return { ok: true, foundPath: result.stdout.trim() };
|
|
12268
|
+
}
|
|
12269
|
+
return { ok: false, reason: "not-installed" };
|
|
12270
|
+
}
|
|
12271
|
+
return { ok: false, reason: "no-probe-available" };
|
|
12272
|
+
}
|
|
12273
|
+
|
|
12274
|
+
// src/dashboard/api-launch-preflight.ts
|
|
12275
|
+
function createLaunchPreflightRouter() {
|
|
12276
|
+
const router = Router5();
|
|
12277
|
+
router.post("/preflight", async (req, res) => {
|
|
12278
|
+
try {
|
|
12279
|
+
const body = req.body ?? {};
|
|
12280
|
+
if (body.terminal !== void 0 && (typeof body.terminal !== "string" || !TERMINAL_CHOICES.includes(body.terminal))) {
|
|
12281
|
+
res.status(400).json({
|
|
12282
|
+
error: `terminal must be one of: ${TERMINAL_CHOICES.join(", ")}`
|
|
12283
|
+
});
|
|
12284
|
+
return;
|
|
12285
|
+
}
|
|
12286
|
+
const config = await readConfig();
|
|
12287
|
+
const terminal = body.terminal ?? getTerminal(config);
|
|
12288
|
+
const probe = probeTerminalInstalled(terminal);
|
|
12289
|
+
if (probe.ok) {
|
|
12290
|
+
const response2 = { ok: true, terminal };
|
|
12291
|
+
res.json(response2);
|
|
12292
|
+
return;
|
|
12293
|
+
}
|
|
12294
|
+
const suggestedFallback = getTerminal({ ...config, terminal: null });
|
|
12295
|
+
const response = {
|
|
12296
|
+
ok: false,
|
|
12297
|
+
terminal,
|
|
12298
|
+
reason: "not-installed",
|
|
12299
|
+
suggestedFallback
|
|
12300
|
+
};
|
|
12301
|
+
res.json(response);
|
|
12302
|
+
} catch (error) {
|
|
12303
|
+
console.error("Error in launch preflight:", error);
|
|
12304
|
+
res.status(500).json({ error: "preflight failed" });
|
|
12305
|
+
}
|
|
12306
|
+
});
|
|
12307
|
+
return router;
|
|
12308
|
+
}
|
|
12309
|
+
|
|
12310
|
+
// src/dashboard/api-terminal-config.ts
|
|
12311
|
+
init_config2();
|
|
12312
|
+
import { Router as Router6 } from "express";
|
|
12313
|
+
function createTerminalConfigRouter() {
|
|
12314
|
+
const router = Router6();
|
|
12315
|
+
router.get("/", async (_req, res) => {
|
|
12316
|
+
try {
|
|
12317
|
+
const config = await readConfig();
|
|
12318
|
+
res.json({
|
|
12319
|
+
terminal: getTerminal(config),
|
|
12320
|
+
custom: config.terminal !== null
|
|
12321
|
+
});
|
|
12322
|
+
} catch (error) {
|
|
12323
|
+
console.error("Error getting terminal config:", error);
|
|
12324
|
+
res.status(500).json({ error: "Failed to get terminal config" });
|
|
12325
|
+
}
|
|
12326
|
+
});
|
|
12327
|
+
router.post("/", async (req, res) => {
|
|
12328
|
+
try {
|
|
12329
|
+
const { terminal } = req.body ?? {};
|
|
12330
|
+
if (typeof terminal !== "string" || !TERMINAL_CHOICES.includes(terminal)) {
|
|
12331
|
+
res.status(400).json({
|
|
12332
|
+
error: `terminal must be one of: ${TERMINAL_CHOICES.join(", ")}`
|
|
12333
|
+
});
|
|
12334
|
+
return;
|
|
12335
|
+
}
|
|
12336
|
+
await writeTerminalConfig(terminal);
|
|
12337
|
+
res.json({ terminal, custom: true });
|
|
12338
|
+
} catch (error) {
|
|
12339
|
+
console.error("Error saving terminal config:", error);
|
|
12340
|
+
res.status(500).json({ error: "Failed to save terminal config" });
|
|
12341
|
+
}
|
|
12342
|
+
});
|
|
12343
|
+
router.delete("/", async (_req, res) => {
|
|
12344
|
+
try {
|
|
12345
|
+
await deleteTerminalConfig();
|
|
12346
|
+
const config = await readConfig();
|
|
12347
|
+
res.json({ terminal: getTerminal(config), custom: false });
|
|
12348
|
+
} catch (error) {
|
|
12349
|
+
console.error("Error resetting terminal config:", error);
|
|
12350
|
+
res.status(500).json({ error: "Failed to reset terminal config" });
|
|
12351
|
+
}
|
|
12352
|
+
});
|
|
12353
|
+
return router;
|
|
12354
|
+
}
|
|
12355
|
+
|
|
12356
|
+
// src/dashboard/api-leases.ts
|
|
12357
|
+
import { Router as Router7 } from "express";
|
|
12358
|
+
|
|
11839
12359
|
// src/db/leases-db.ts
|
|
11840
12360
|
init_paths();
|
|
11841
12361
|
import Database2 from "better-sqlite3";
|
|
11842
12362
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
11843
|
-
import { resolve as
|
|
12363
|
+
import { resolve as resolve22 } from "path";
|
|
11844
12364
|
var db2 = null;
|
|
11845
12365
|
var LEASE_SCHEMA_VERSION = "1";
|
|
11846
12366
|
var SCHEMA_SQL2 = `
|
|
@@ -11964,7 +12484,7 @@ function isBusyError(err2) {
|
|
|
11964
12484
|
}
|
|
11965
12485
|
function initLeasesDb(dbPath) {
|
|
11966
12486
|
if (db2) return db2;
|
|
11967
|
-
const finalPath = dbPath ??
|
|
12487
|
+
const finalPath = dbPath ?? resolve22(syntaurRoot(), "syntaur.db");
|
|
11968
12488
|
db2 = new Database2(finalPath);
|
|
11969
12489
|
db2.pragma("journal_mode = WAL");
|
|
11970
12490
|
db2.pragma("busy_timeout = 5000");
|
|
@@ -12465,7 +12985,7 @@ function releaseLeasesByRequestedFor(tag) {
|
|
|
12465
12985
|
|
|
12466
12986
|
// src/dashboard/api-leases.ts
|
|
12467
12987
|
function createLeasesRouter(broadcast) {
|
|
12468
|
-
const router =
|
|
12988
|
+
const router = Router7();
|
|
12469
12989
|
router.get("/", (_req, res) => {
|
|
12470
12990
|
try {
|
|
12471
12991
|
initLeasesDb();
|
|
@@ -12527,9 +13047,9 @@ init_timestamp();
|
|
|
12527
13047
|
init_fs();
|
|
12528
13048
|
init_playbook();
|
|
12529
13049
|
init_playbooks();
|
|
12530
|
-
import { Router as
|
|
12531
|
-
import { resolve as
|
|
12532
|
-
import { readFile as
|
|
13050
|
+
import { Router as Router8 } from "express";
|
|
13051
|
+
import { resolve as resolve23 } from "path";
|
|
13052
|
+
import { readFile as readFile16 } from "fs/promises";
|
|
12533
13053
|
function statusForPlaybookError(code) {
|
|
12534
13054
|
switch (code) {
|
|
12535
13055
|
case "manifest":
|
|
@@ -12543,7 +13063,7 @@ function statusForPlaybookError(code) {
|
|
|
12543
13063
|
}
|
|
12544
13064
|
}
|
|
12545
13065
|
function createPlaybooksRouter(playbooksDir3) {
|
|
12546
|
-
const router =
|
|
13066
|
+
const router = Router8();
|
|
12547
13067
|
router.get("/", async (_req, res) => {
|
|
12548
13068
|
try {
|
|
12549
13069
|
const playbooks = await listPlaybooks(playbooksDir3);
|
|
@@ -12610,8 +13130,8 @@ function createPlaybooksRouter(playbooksDir3) {
|
|
|
12610
13130
|
res.status(404).json({ error: `Playbook "${req.params.slug}" not found` });
|
|
12611
13131
|
return;
|
|
12612
13132
|
}
|
|
12613
|
-
const filePath =
|
|
12614
|
-
const content = await
|
|
13133
|
+
const filePath = resolve23(playbooksDir3, resolved.filename);
|
|
13134
|
+
const content = await readFile16(filePath, "utf-8");
|
|
12615
13135
|
res.json({
|
|
12616
13136
|
documentType: "playbook",
|
|
12617
13137
|
title: `Edit Playbook: ${resolved.slug}`,
|
|
@@ -12636,7 +13156,7 @@ function createPlaybooksRouter(playbooksDir3) {
|
|
|
12636
13156
|
return;
|
|
12637
13157
|
}
|
|
12638
13158
|
await ensureDir(playbooksDir3);
|
|
12639
|
-
const filePath =
|
|
13159
|
+
const filePath = resolve23(playbooksDir3, `${slug}.md`);
|
|
12640
13160
|
if (await fileExists(filePath)) {
|
|
12641
13161
|
res.status(409).json({ error: `Playbook "${slug}" already exists` });
|
|
12642
13162
|
return;
|
|
@@ -12660,7 +13180,7 @@ function createPlaybooksRouter(playbooksDir3) {
|
|
|
12660
13180
|
res.status(404).json({ error: `Playbook "${req.params.slug}" not found` });
|
|
12661
13181
|
return;
|
|
12662
13182
|
}
|
|
12663
|
-
const filePath =
|
|
13183
|
+
const filePath = resolve23(playbooksDir3, resolved.filename);
|
|
12664
13184
|
await writeFileForce(filePath, content);
|
|
12665
13185
|
await rebuildPlaybookManifest(playbooksDir3);
|
|
12666
13186
|
res.json({ slug: resolved.slug, path: filePath });
|
|
@@ -12707,8 +13227,8 @@ init_fs_migration();
|
|
|
12707
13227
|
init_parser2();
|
|
12708
13228
|
init_fs();
|
|
12709
13229
|
init_paths();
|
|
12710
|
-
import { Router as
|
|
12711
|
-
import { readdir as
|
|
13230
|
+
import { Router as Router9 } from "express";
|
|
13231
|
+
import { readdir as readdir11 } from "fs/promises";
|
|
12712
13232
|
import { resolve as resolvePath, dirname as dirname5 } from "path";
|
|
12713
13233
|
import { rename as rename4, mkdir as mkdir2 } from "fs/promises";
|
|
12714
13234
|
init_slug();
|
|
@@ -12726,7 +13246,7 @@ function touchItem3(item) {
|
|
|
12726
13246
|
item.updatedAt = now;
|
|
12727
13247
|
}
|
|
12728
13248
|
function createTodosRouter(todosDir2, broadcast, projectsDir2) {
|
|
12729
|
-
const router =
|
|
13249
|
+
const router = Router9();
|
|
12730
13250
|
function broadcastUpdate() {
|
|
12731
13251
|
broadcast({ type: "todos-updated", timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
12732
13252
|
}
|
|
@@ -12842,7 +13362,7 @@ function createTodosRouter(todosDir2, broadcast, projectsDir2) {
|
|
|
12842
13362
|
router.get("/", async (_req, res) => {
|
|
12843
13363
|
try {
|
|
12844
13364
|
await ensureDir(todosDir2);
|
|
12845
|
-
const files = await
|
|
13365
|
+
const files = await readdir11(todosDir2).catch(() => []);
|
|
12846
13366
|
const workspaces = [];
|
|
12847
13367
|
for (const file of files) {
|
|
12848
13368
|
if (typeof file !== "string") continue;
|
|
@@ -12955,8 +13475,8 @@ function createTodosRouter(todosDir2, broadcast, projectsDir2) {
|
|
|
12955
13475
|
router.post("/:workspace/archive", async (req, res) => {
|
|
12956
13476
|
try {
|
|
12957
13477
|
const { archivePath: archivePath2 } = await Promise.resolve().then(() => (init_parser2(), parser_exports));
|
|
12958
|
-
const { resolve:
|
|
12959
|
-
const { readFile:
|
|
13478
|
+
const { resolve: resolve72 } = await import("path");
|
|
13479
|
+
const { readFile: readFile50 } = await import("fs/promises");
|
|
12960
13480
|
const { writeFileForce: writeFileForce2 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
|
|
12961
13481
|
const workspace = getWorkspaceParam(req.params.workspace);
|
|
12962
13482
|
const checklist = await readChecklist(todosDir2, workspace);
|
|
@@ -12972,10 +13492,10 @@ function createTodosRouter(todosDir2, broadcast, projectsDir2) {
|
|
|
12972
13492
|
(e) => e.itemIds.every((id) => completedIds.has(id))
|
|
12973
13493
|
);
|
|
12974
13494
|
const archFile = archivePath2(todosDir2, workspace, checklist.archiveInterval);
|
|
12975
|
-
await ensureDir(
|
|
13495
|
+
await ensureDir(resolve72(todosDir2, "archive"));
|
|
12976
13496
|
let archContent = "";
|
|
12977
13497
|
if (await fileExists(archFile)) {
|
|
12978
|
-
archContent = await
|
|
13498
|
+
archContent = await readFile50(archFile, "utf-8");
|
|
12979
13499
|
archContent = archContent.trimEnd() + "\n\n";
|
|
12980
13500
|
} else {
|
|
12981
13501
|
archContent = `---
|
|
@@ -13254,7 +13774,7 @@ workspace: ${workspace}
|
|
|
13254
13774
|
const { readConfig: readConfig3 } = await Promise.resolve().then(() => (init_config2(), config_exports));
|
|
13255
13775
|
const { assignmentsDir: assignmentsDirFn } = await Promise.resolve().then(() => (init_paths(), paths_exports));
|
|
13256
13776
|
const { fileExists: fileExists2, writeFileForce: writeFileForce2 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
|
|
13257
|
-
const { readFile:
|
|
13777
|
+
const { readFile: readFile50 } = await import("fs/promises");
|
|
13258
13778
|
const { appendTodosToAssignmentBody: appendTodosToAssignmentBody2, touchAssignmentUpdated: touchAssignmentUpdated2 } = await Promise.resolve().then(() => (init_assignment_todos(), assignment_todos_exports));
|
|
13259
13779
|
const { nowTimestamp: nowTimestamp3 } = await Promise.resolve().then(() => (init_timestamp(), timestamp_exports));
|
|
13260
13780
|
let assignmentRef;
|
|
@@ -13275,7 +13795,7 @@ workspace: ${workspace}
|
|
|
13275
13795
|
}
|
|
13276
13796
|
const assignmentMdPath2 = resolvePath2(assignmentDir, "assignment.md");
|
|
13277
13797
|
if (!await fileExists2(assignmentMdPath2)) return { error: `Target assignment not found: ${assignmentMdPath2}` };
|
|
13278
|
-
let content = await
|
|
13798
|
+
let content = await readFile50(assignmentMdPath2, "utf-8");
|
|
13279
13799
|
content = appendTodosToAssignmentBody2(
|
|
13280
13800
|
content,
|
|
13281
13801
|
items.map((it) => ({
|
|
@@ -13458,9 +13978,9 @@ init_parser2();
|
|
|
13458
13978
|
init_fs();
|
|
13459
13979
|
init_paths();
|
|
13460
13980
|
init_slug();
|
|
13461
|
-
import { Router as
|
|
13462
|
-
import { mkdir as mkdir3, readFile as
|
|
13463
|
-
import { resolve as
|
|
13981
|
+
import { Router as Router10 } from "express";
|
|
13982
|
+
import { mkdir as mkdir3, readFile as readFile17, rename as rename5 } from "fs/promises";
|
|
13983
|
+
import { resolve as resolve24, dirname as dirname6 } from "path";
|
|
13464
13984
|
init_promote_todos();
|
|
13465
13985
|
var WORKSPACE_REGEX2 = /^[a-z0-9_][a-z0-9-]*$/;
|
|
13466
13986
|
function touchItem4(item) {
|
|
@@ -13476,7 +13996,7 @@ function params(req) {
|
|
|
13476
13996
|
return req.params;
|
|
13477
13997
|
}
|
|
13478
13998
|
async function projectExists(projectsDir2, slug) {
|
|
13479
|
-
return fileExists(
|
|
13999
|
+
return fileExists(resolve24(projectsDir2, slug, "project.md"));
|
|
13480
14000
|
}
|
|
13481
14001
|
async function ensureProjectTodosDir(projectsDir2, slug) {
|
|
13482
14002
|
const todosDir2 = projectTodosDir(projectsDir2, slug);
|
|
@@ -13493,7 +14013,7 @@ async function ensureProjectTodosDir(projectsDir2, slug) {
|
|
|
13493
14013
|
throw err2;
|
|
13494
14014
|
}
|
|
13495
14015
|
try {
|
|
13496
|
-
await mkdir3(
|
|
14016
|
+
await mkdir3(resolve24(todosDir2, "archive"), { recursive: false });
|
|
13497
14017
|
} catch (err2) {
|
|
13498
14018
|
const code = err2.code;
|
|
13499
14019
|
if (code === "EEXIST") return;
|
|
@@ -13509,7 +14029,7 @@ function notFound(res, slug) {
|
|
|
13509
14029
|
res.status(404).json({ error: `Project "${slug}" not found` });
|
|
13510
14030
|
}
|
|
13511
14031
|
function createProjectTodosRouter(projectsDir2, broadcast, workspaceTodosDir) {
|
|
13512
|
-
const router =
|
|
14032
|
+
const router = Router10({ mergeParams: true });
|
|
13513
14033
|
function broadcastUpdate(projectSlug) {
|
|
13514
14034
|
broadcast({ type: "todos-updated", projectSlug, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
13515
14035
|
}
|
|
@@ -13681,7 +14201,7 @@ function createProjectTodosRouter(projectsDir2, broadcast, workspaceTodosDir) {
|
|
|
13681
14201
|
const archFile = archivePath(todosDir2, slug, checklist.archiveInterval);
|
|
13682
14202
|
let archContent = "";
|
|
13683
14203
|
if (await fileExists(archFile)) {
|
|
13684
|
-
archContent = await
|
|
14204
|
+
archContent = await readFile17(archFile, "utf-8");
|
|
13685
14205
|
archContent = archContent.trimEnd() + "\n\n";
|
|
13686
14206
|
} else {
|
|
13687
14207
|
archContent = `---
|
|
@@ -14125,17 +14645,17 @@ workspace: ${slug}
|
|
|
14125
14645
|
if (tg.includes("/")) {
|
|
14126
14646
|
const parts = tg.split("/");
|
|
14127
14647
|
if (parts.length !== 2) return { error: `Invalid target.assignment "${tg}"` };
|
|
14128
|
-
assignmentDir =
|
|
14648
|
+
assignmentDir = resolve24(projectsDir2, parts[0], "assignments", parts[1]);
|
|
14129
14649
|
assignmentRef = `${parts[0]}/${parts[1]}`;
|
|
14130
14650
|
} else if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(tg)) {
|
|
14131
|
-
assignmentDir =
|
|
14651
|
+
assignmentDir = resolve24(assignmentsDirFn(), tg);
|
|
14132
14652
|
assignmentRef = tg;
|
|
14133
14653
|
} else {
|
|
14134
14654
|
return { error: `Invalid target.assignment "${tg}"` };
|
|
14135
14655
|
}
|
|
14136
|
-
const assignmentMdPath2 =
|
|
14656
|
+
const assignmentMdPath2 = resolve24(assignmentDir, "assignment.md");
|
|
14137
14657
|
if (!await fileExists(assignmentMdPath2)) return { error: `Target assignment not found: ${assignmentMdPath2}` };
|
|
14138
|
-
let content = await
|
|
14658
|
+
let content = await readFile17(assignmentMdPath2, "utf-8");
|
|
14139
14659
|
content = appendTodosToAssignmentBody2(
|
|
14140
14660
|
content,
|
|
14141
14661
|
items.map((it) => ({
|
|
@@ -14327,7 +14847,7 @@ workspace: ${slug}
|
|
|
14327
14847
|
|
|
14328
14848
|
// src/dashboard/api-backup.ts
|
|
14329
14849
|
init_config2();
|
|
14330
|
-
import { Router as
|
|
14850
|
+
import { Router as Router11 } from "express";
|
|
14331
14851
|
|
|
14332
14852
|
// src/utils/github-backup.ts
|
|
14333
14853
|
init_paths();
|
|
@@ -14335,8 +14855,8 @@ init_fs();
|
|
|
14335
14855
|
init_config2();
|
|
14336
14856
|
import { execFile as execFile2 } from "child_process";
|
|
14337
14857
|
import { promisify as promisify2 } from "util";
|
|
14338
|
-
import { cp, mkdtemp, rm as rm2, readFile as
|
|
14339
|
-
import { resolve as
|
|
14858
|
+
import { cp, mkdtemp, rm as rm2, readFile as readFile18, writeFile as writeFile4, unlink as unlink4, stat, open as open2, rename as rename6 } from "fs/promises";
|
|
14859
|
+
import { resolve as resolve25, join as join2 } from "path";
|
|
14340
14860
|
import { tmpdir } from "os";
|
|
14341
14861
|
var exec2 = promisify2(execFile2);
|
|
14342
14862
|
var VALID_CATEGORIES = ["projects", "playbooks", "todos", "servers", "config"];
|
|
@@ -14376,7 +14896,7 @@ async function resolveCategoryPath(category) {
|
|
|
14376
14896
|
case "servers":
|
|
14377
14897
|
return { sourcePath: serversDir(), repoPath: "servers", isFile: false };
|
|
14378
14898
|
case "config":
|
|
14379
|
-
return { sourcePath:
|
|
14899
|
+
return { sourcePath: resolve25(syntaurRoot(), "config.md"), repoPath: "config.md", isFile: true };
|
|
14380
14900
|
}
|
|
14381
14901
|
}
|
|
14382
14902
|
async function checkGitInstalled() {
|
|
@@ -14387,7 +14907,7 @@ async function checkGitInstalled() {
|
|
|
14387
14907
|
}
|
|
14388
14908
|
}
|
|
14389
14909
|
async function acquireLock() {
|
|
14390
|
-
const lockPath =
|
|
14910
|
+
const lockPath = resolve25(syntaurRoot(), LOCK_FILE_NAME);
|
|
14391
14911
|
await ensureDir(syntaurRoot());
|
|
14392
14912
|
try {
|
|
14393
14913
|
const handle = await open2(lockPath, "wx");
|
|
@@ -14396,7 +14916,7 @@ async function acquireLock() {
|
|
|
14396
14916
|
return lockPath;
|
|
14397
14917
|
} catch (err2) {
|
|
14398
14918
|
if (err2.code === "EEXIST") {
|
|
14399
|
-
const pid = await
|
|
14919
|
+
const pid = await readFile18(lockPath, "utf-8").catch(() => "");
|
|
14400
14920
|
throw new Error(
|
|
14401
14921
|
`Backup operation already in progress (lock file at ${lockPath}, pid ${pid.trim() || "unknown"}). If stale, delete the file and retry.`
|
|
14402
14922
|
);
|
|
@@ -14434,7 +14954,7 @@ async function copyRecursive(src, dest) {
|
|
|
14434
14954
|
await ensureDir(dest);
|
|
14435
14955
|
await cp(src, dest, { recursive: true, force: true });
|
|
14436
14956
|
} else {
|
|
14437
|
-
await ensureDir(
|
|
14957
|
+
await ensureDir(resolve25(dest, ".."));
|
|
14438
14958
|
await cp(src, dest, { force: true });
|
|
14439
14959
|
}
|
|
14440
14960
|
}
|
|
@@ -14443,7 +14963,7 @@ function resolveCategoriesStrict(csv) {
|
|
|
14443
14963
|
return parseCategoriesStrict(parts);
|
|
14444
14964
|
}
|
|
14445
14965
|
async function readSanitizedConfig(configPath) {
|
|
14446
|
-
const content = await
|
|
14966
|
+
const content = await readFile18(configPath, "utf-8");
|
|
14447
14967
|
return content.replace(/^(\s*lastBackup:\s*).*$/m, "$1null").replace(/^(\s*lastRestore:\s*).*$/m, "$1null");
|
|
14448
14968
|
}
|
|
14449
14969
|
async function backupToGithub(overrides) {
|
|
@@ -14482,7 +15002,7 @@ async function backupToGithub(overrides) {
|
|
|
14482
15002
|
}
|
|
14483
15003
|
if (category === "config") {
|
|
14484
15004
|
const sanitized = await readSanitizedConfig(sourcePath);
|
|
14485
|
-
await ensureDir(
|
|
15005
|
+
await ensureDir(resolve25(destPath, ".."));
|
|
14486
15006
|
await writeFile4(destPath, sanitized, "utf-8");
|
|
14487
15007
|
} else {
|
|
14488
15008
|
await copyRecursive(sourcePath, destPath);
|
|
@@ -14536,7 +15056,7 @@ async function backupToGithub(overrides) {
|
|
|
14536
15056
|
}
|
|
14537
15057
|
async function safeRestoreCategory(localPath, repoSrcPath, isFile) {
|
|
14538
15058
|
if (isFile) {
|
|
14539
|
-
await ensureDir(
|
|
15059
|
+
await ensureDir(resolve25(localPath, ".."));
|
|
14540
15060
|
await cp(repoSrcPath, localPath, { force: true });
|
|
14541
15061
|
return;
|
|
14542
15062
|
}
|
|
@@ -14637,7 +15157,7 @@ async function restoreFromGithub(overrides) {
|
|
|
14637
15157
|
}
|
|
14638
15158
|
async function getBackupStatus() {
|
|
14639
15159
|
const config = await readConfig();
|
|
14640
|
-
const lockPath =
|
|
15160
|
+
const lockPath = resolve25(syntaurRoot(), LOCK_FILE_NAME);
|
|
14641
15161
|
const locked = await fileExists(lockPath);
|
|
14642
15162
|
return {
|
|
14643
15163
|
repo: config.backup?.repo ?? null,
|
|
@@ -14650,7 +15170,7 @@ async function getBackupStatus() {
|
|
|
14650
15170
|
|
|
14651
15171
|
// src/dashboard/api-backup.ts
|
|
14652
15172
|
function createBackupRouter() {
|
|
14653
|
-
const router =
|
|
15173
|
+
const router = Router11();
|
|
14654
15174
|
router.get("/", async (_req, res) => {
|
|
14655
15175
|
try {
|
|
14656
15176
|
const status = await getBackupStatus();
|
|
@@ -14978,7 +15498,7 @@ function createDashboardServer(options) {
|
|
|
14978
15498
|
(async () => {
|
|
14979
15499
|
try {
|
|
14980
15500
|
const configResult = await migrateLegacyConfig(
|
|
14981
|
-
|
|
15501
|
+
resolve26(syntaurRoot(), "config.md")
|
|
14982
15502
|
);
|
|
14983
15503
|
const projectResult = await migrateLegacyProjectFiles(projectsDir2);
|
|
14984
15504
|
const summary = summarizeMigration(projectResult, configResult);
|
|
@@ -15101,6 +15621,7 @@ function createDashboardServer(options) {
|
|
|
15101
15621
|
res.status(500).json({ error: "Failed to reset theme config" });
|
|
15102
15622
|
}
|
|
15103
15623
|
});
|
|
15624
|
+
app.use("/api/config/terminal", createTerminalConfigRouter());
|
|
15104
15625
|
app.get("/api/config/hotkeys", async (_req, res) => {
|
|
15105
15626
|
try {
|
|
15106
15627
|
const config = await readConfig();
|
|
@@ -15486,6 +16007,7 @@ function createDashboardServer(options) {
|
|
|
15486
16007
|
app.use("/api/leases", createLeasesRouter(broadcast));
|
|
15487
16008
|
app.use("/api/agent-sessions", createAgentSessionsRouter(projectsDir2, broadcast, assignmentsDir2));
|
|
15488
16009
|
app.use("/api/config/agents", createAgentsRouter());
|
|
16010
|
+
app.use("/api/launch", createLaunchPreflightRouter());
|
|
15489
16011
|
app.use("/api/playbooks", createPlaybooksRouter(playbooksDir3));
|
|
15490
16012
|
app.get("/api/memories", async (_req, res) => {
|
|
15491
16013
|
try {
|
|
@@ -15510,14 +16032,14 @@ function createDashboardServer(options) {
|
|
|
15510
16032
|
app.use("/api/backup", createBackupRouter());
|
|
15511
16033
|
if (serveStaticUi && dashboardDistPath) {
|
|
15512
16034
|
const sendOpts = { dotfiles: "allow" };
|
|
15513
|
-
app.use("/assets", express.static(
|
|
16035
|
+
app.use("/assets", express.static(resolve26(dashboardDistPath, "assets"), sendOpts));
|
|
15514
16036
|
app.use(express.static(dashboardDistPath, { ...sendOpts, index: false, fallthrough: true }));
|
|
15515
16037
|
app.get("{*path}", async (req, res) => {
|
|
15516
16038
|
if (req.path.startsWith("/api") || req.path === "/ws" || req.path.startsWith("/assets")) {
|
|
15517
16039
|
res.status(404).json({ error: "Not Found" });
|
|
15518
16040
|
return;
|
|
15519
16041
|
}
|
|
15520
|
-
const indexPath =
|
|
16042
|
+
const indexPath = resolve26(dashboardDistPath, "index.html");
|
|
15521
16043
|
if (!await fileExists(indexPath)) {
|
|
15522
16044
|
res.status(503).send(
|
|
15523
16045
|
'Dashboard not built. Run "npm run build:dashboard" first.'
|
|
@@ -15541,7 +16063,7 @@ function createDashboardServer(options) {
|
|
|
15541
16063
|
serversDir: serversDir2,
|
|
15542
16064
|
playbooksDir: playbooksDir3,
|
|
15543
16065
|
todosDir: todosDir2,
|
|
15544
|
-
dbPath:
|
|
16066
|
+
dbPath: resolve26(syntaurRoot(), "syntaur.db"),
|
|
15545
16067
|
onMessage: broadcast
|
|
15546
16068
|
});
|
|
15547
16069
|
startAutodiscovery({ serversDir: serversDir2, projectsDir: projectsDir2, assignmentsDir: assignmentsDir2, excludePids: /* @__PURE__ */ new Set([process.pid]) });
|
|
@@ -15556,7 +16078,7 @@ function createDashboardServer(options) {
|
|
|
15556
16078
|
}
|
|
15557
16079
|
});
|
|
15558
16080
|
server.listen(port, () => {
|
|
15559
|
-
const portFile =
|
|
16081
|
+
const portFile = resolve26(syntaurRoot(), "dashboard-port");
|
|
15560
16082
|
writeFile5(portFile, String(port), "utf-8").catch(() => {
|
|
15561
16083
|
});
|
|
15562
16084
|
resolvePromise();
|
|
@@ -15574,7 +16096,7 @@ function createDashboardServer(options) {
|
|
|
15574
16096
|
client.terminate();
|
|
15575
16097
|
}
|
|
15576
16098
|
clients.clear();
|
|
15577
|
-
const portFile =
|
|
16099
|
+
const portFile = resolve26(syntaurRoot(), "dashboard-port");
|
|
15578
16100
|
await unlink5(portFile).catch(() => {
|
|
15579
16101
|
});
|
|
15580
16102
|
server.closeAllConnections?.();
|
|
@@ -15654,8 +16176,8 @@ async function dashboardCommand(options) {
|
|
|
15654
16176
|
port = availablePort;
|
|
15655
16177
|
}
|
|
15656
16178
|
const thisFile = fileURLToPath2(import.meta.url);
|
|
15657
|
-
const packageRoot =
|
|
15658
|
-
const dashboardDist =
|
|
16179
|
+
const packageRoot = resolve27(dirname7(thisFile), "..");
|
|
16180
|
+
const dashboardDist = resolve27(packageRoot, "dashboard", "dist");
|
|
15659
16181
|
const server = createDashboardServer({
|
|
15660
16182
|
port,
|
|
15661
16183
|
projectsDir: projectsDir2,
|
|
@@ -15669,8 +16191,8 @@ async function dashboardCommand(options) {
|
|
|
15669
16191
|
await server.start();
|
|
15670
16192
|
let viteProcess = null;
|
|
15671
16193
|
if (mode === "dev") {
|
|
15672
|
-
const dashboardDir =
|
|
15673
|
-
const viteBin =
|
|
16194
|
+
const dashboardDir = resolve27(packageRoot, "dashboard");
|
|
16195
|
+
const viteBin = resolve27(dashboardDir, "node_modules", ".bin", "vite");
|
|
15674
16196
|
if (!await fileExists(viteBin)) {
|
|
15675
16197
|
console.error(
|
|
15676
16198
|
'Vite not found. Run "npm ci --prefix dashboard" first, or use the default bundled dashboard mode.'
|
|
@@ -15680,7 +16202,7 @@ async function dashboardCommand(options) {
|
|
|
15680
16202
|
}
|
|
15681
16203
|
console.log(`API server running on http://localhost:${port}`);
|
|
15682
16204
|
console.log("Starting Vite dev server...");
|
|
15683
|
-
viteProcess =
|
|
16205
|
+
viteProcess = spawn2(viteBin, [], {
|
|
15684
16206
|
cwd: dashboardDir,
|
|
15685
16207
|
env: {
|
|
15686
16208
|
...process.env,
|
|
@@ -15745,7 +16267,7 @@ init_config2();
|
|
|
15745
16267
|
init_slug();
|
|
15746
16268
|
init_lifecycle();
|
|
15747
16269
|
init_assignment_resolver();
|
|
15748
|
-
import { resolve as
|
|
16270
|
+
import { resolve as resolve28 } from "path";
|
|
15749
16271
|
function resolveLinkedTodosLookup(projectsDir2) {
|
|
15750
16272
|
return { todosDir: todosDir(), projectsDir: projectsDir2 };
|
|
15751
16273
|
}
|
|
@@ -15759,8 +16281,8 @@ async function runTransition(assignment, command, options = {}) {
|
|
|
15759
16281
|
if (!isValidSlug(assignment)) {
|
|
15760
16282
|
throw new Error(`Invalid assignment slug "${assignment}".`);
|
|
15761
16283
|
}
|
|
15762
|
-
const projectDir =
|
|
15763
|
-
const projectMdPath =
|
|
16284
|
+
const projectDir = resolve28(baseDir, options.project);
|
|
16285
|
+
const projectMdPath = resolve28(projectDir, "project.md");
|
|
15764
16286
|
if (!await fileExists(projectDir) || !await fileExists(projectMdPath)) {
|
|
15765
16287
|
throw new Error(`Project "${options.project}" not found at ${projectDir}.`);
|
|
15766
16288
|
}
|
|
@@ -15793,8 +16315,8 @@ async function runAssign(assignment, agent, options = {}) {
|
|
|
15793
16315
|
if (!isValidSlug(assignment)) {
|
|
15794
16316
|
throw new Error(`Invalid assignment slug "${assignment}".`);
|
|
15795
16317
|
}
|
|
15796
|
-
const projectDir =
|
|
15797
|
-
const projectMdPath =
|
|
16318
|
+
const projectDir = resolve28(baseDir, options.project);
|
|
16319
|
+
const projectMdPath = resolve28(projectDir, "project.md");
|
|
15798
16320
|
if (!await fileExists(projectDir) || !await fileExists(projectMdPath)) {
|
|
15799
16321
|
throw new Error(`Project "${options.project}" not found at ${projectDir}.`);
|
|
15800
16322
|
}
|
|
@@ -15857,8 +16379,8 @@ init_fs();
|
|
|
15857
16379
|
init_config2();
|
|
15858
16380
|
init_frontmatter();
|
|
15859
16381
|
init_timestamp();
|
|
15860
|
-
import { resolve as
|
|
15861
|
-
import { readdir as
|
|
16382
|
+
import { resolve as resolve29 } from "path";
|
|
16383
|
+
import { readdir as readdir12, readFile as readFile19 } from "fs/promises";
|
|
15862
16384
|
var PROMOTABLE_STATUSES = /* @__PURE__ */ new Set(["pending"]);
|
|
15863
16385
|
function objectiveIsFleshedOut(content) {
|
|
15864
16386
|
const match = content.match(/##\s+Objective\s*\n([\s\S]*?)(?=\n##\s+|$)/);
|
|
@@ -15876,15 +16398,15 @@ async function collectCandidates(baseDirs) {
|
|
|
15876
16398
|
const candidates = [];
|
|
15877
16399
|
for (const baseDir of baseDirs) {
|
|
15878
16400
|
if (!await fileExists(baseDir)) continue;
|
|
15879
|
-
const projects = await
|
|
16401
|
+
const projects = await readdir12(baseDir, { withFileTypes: true });
|
|
15880
16402
|
for (const m of projects) {
|
|
15881
16403
|
if (!m.isDirectory()) continue;
|
|
15882
16404
|
if (m.name.startsWith(".") || m.name.startsWith("_")) continue;
|
|
15883
|
-
const directAssignmentMd =
|
|
16405
|
+
const directAssignmentMd = resolve29(baseDir, m.name, "assignment.md");
|
|
15884
16406
|
if (await fileExists(directAssignmentMd)) {
|
|
15885
16407
|
const fm = await parseSafe(directAssignmentMd);
|
|
15886
16408
|
if (fm && PROMOTABLE_STATUSES.has(fm.status)) {
|
|
15887
|
-
const content = await
|
|
16409
|
+
const content = await readFile19(directAssignmentMd, "utf-8");
|
|
15888
16410
|
if (objectiveIsFleshedOut(content) && hasAcceptanceCriteria(content)) {
|
|
15889
16411
|
candidates.push({
|
|
15890
16412
|
projectSlug: null,
|
|
@@ -15897,17 +16419,17 @@ async function collectCandidates(baseDirs) {
|
|
|
15897
16419
|
}
|
|
15898
16420
|
continue;
|
|
15899
16421
|
}
|
|
15900
|
-
const assignmentsDir2 =
|
|
16422
|
+
const assignmentsDir2 = resolve29(baseDir, m.name, "assignments");
|
|
15901
16423
|
if (!await fileExists(assignmentsDir2)) continue;
|
|
15902
|
-
const entries = await
|
|
16424
|
+
const entries = await readdir12(assignmentsDir2, { withFileTypes: true });
|
|
15903
16425
|
for (const a of entries) {
|
|
15904
16426
|
if (!a.isDirectory()) continue;
|
|
15905
16427
|
if (a.name.startsWith(".") || a.name.startsWith("_")) continue;
|
|
15906
|
-
const assignmentMd =
|
|
16428
|
+
const assignmentMd = resolve29(assignmentsDir2, a.name, "assignment.md");
|
|
15907
16429
|
if (!await fileExists(assignmentMd)) continue;
|
|
15908
16430
|
const fm = await parseSafe(assignmentMd);
|
|
15909
16431
|
if (!fm || !PROMOTABLE_STATUSES.has(fm.status)) continue;
|
|
15910
|
-
const content = await
|
|
16432
|
+
const content = await readFile19(assignmentMd, "utf-8");
|
|
15911
16433
|
if (!objectiveIsFleshedOut(content)) continue;
|
|
15912
16434
|
if (!hasAcceptanceCriteria(content)) continue;
|
|
15913
16435
|
candidates.push({
|
|
@@ -15924,7 +16446,7 @@ async function collectCandidates(baseDirs) {
|
|
|
15924
16446
|
}
|
|
15925
16447
|
async function parseSafe(path) {
|
|
15926
16448
|
try {
|
|
15927
|
-
const content = await
|
|
16449
|
+
const content = await readFile19(path, "utf-8");
|
|
15928
16450
|
return parseAssignmentFrontmatter(content);
|
|
15929
16451
|
} catch {
|
|
15930
16452
|
return null;
|
|
@@ -15953,7 +16475,7 @@ async function migrateStatusesCommand(options) {
|
|
|
15953
16475
|
const now = nowTimestamp();
|
|
15954
16476
|
let migrated = 0;
|
|
15955
16477
|
for (const c2 of candidates) {
|
|
15956
|
-
const content = await
|
|
16478
|
+
const content = await readFile19(c2.assignmentMd, "utf-8");
|
|
15957
16479
|
const updated = updateAssignmentFile(content, {
|
|
15958
16480
|
status: c2.toStatus,
|
|
15959
16481
|
updated: now
|
|
@@ -16008,10 +16530,10 @@ init_config2();
|
|
|
16008
16530
|
init_fs();
|
|
16009
16531
|
import {
|
|
16010
16532
|
cp as cp2,
|
|
16011
|
-
readdir as
|
|
16533
|
+
readdir as readdir13,
|
|
16012
16534
|
symlink,
|
|
16013
16535
|
lstat,
|
|
16014
|
-
readFile as
|
|
16536
|
+
readFile as readFile20,
|
|
16015
16537
|
readlink,
|
|
16016
16538
|
rename as rename7,
|
|
16017
16539
|
rm as rm3,
|
|
@@ -16020,20 +16542,20 @@ import {
|
|
|
16020
16542
|
} from "fs/promises";
|
|
16021
16543
|
import { existsSync } from "fs";
|
|
16022
16544
|
import { homedir as homedir2 } from "os";
|
|
16023
|
-
import { basename as basename4, dirname as dirname9, isAbsolute as
|
|
16545
|
+
import { basename as basename4, dirname as dirname9, isAbsolute as isAbsolute3, relative as relative2, resolve as resolve31 } from "path";
|
|
16024
16546
|
|
|
16025
16547
|
// src/utils/package-root.ts
|
|
16026
16548
|
init_fs();
|
|
16027
|
-
import { dirname as dirname8, resolve as
|
|
16549
|
+
import { dirname as dirname8, resolve as resolve30 } from "path";
|
|
16028
16550
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
16029
16551
|
async function findPackageRoot(expectedRelativePath) {
|
|
16030
16552
|
let currentDir = dirname8(fileURLToPath3(import.meta.url));
|
|
16031
16553
|
while (true) {
|
|
16032
|
-
const candidate =
|
|
16554
|
+
const candidate = resolve30(currentDir, expectedRelativePath);
|
|
16033
16555
|
if (await fileExists(candidate)) {
|
|
16034
16556
|
return currentDir;
|
|
16035
16557
|
}
|
|
16036
|
-
const parentDir =
|
|
16558
|
+
const parentDir = resolve30(currentDir, "..");
|
|
16037
16559
|
if (parentDir === currentDir) {
|
|
16038
16560
|
throw new Error(
|
|
16039
16561
|
`Could not locate package root containing ${expectedRelativePath}.`
|
|
@@ -16054,25 +16576,25 @@ function getPluginManifestRelativePath(pluginKind) {
|
|
|
16054
16576
|
}
|
|
16055
16577
|
function getDefaultPluginTargetDir(pluginKind) {
|
|
16056
16578
|
const home = homedir2();
|
|
16057
|
-
return pluginKind === "claude" ?
|
|
16579
|
+
return pluginKind === "claude" ? resolve31(home, ".claude", "plugins", "syntaur") : resolve31(home, "plugins", "syntaur");
|
|
16058
16580
|
}
|
|
16059
16581
|
function getDefaultMarketplacePath() {
|
|
16060
|
-
return
|
|
16582
|
+
return resolve31(homedir2(), ".agents", "plugins", "marketplace.json");
|
|
16061
16583
|
}
|
|
16062
16584
|
function getClaudeMarketplacesRoot() {
|
|
16063
|
-
return
|
|
16585
|
+
return resolve31(homedir2(), ".claude", "plugins", "marketplaces");
|
|
16064
16586
|
}
|
|
16065
16587
|
function getClaudeKnownMarketplacesPath() {
|
|
16066
|
-
return
|
|
16588
|
+
return resolve31(homedir2(), ".claude", "plugins", "known_marketplaces.json");
|
|
16067
16589
|
}
|
|
16068
16590
|
function getClaudeInstalledPluginsPath() {
|
|
16069
|
-
return
|
|
16591
|
+
return resolve31(homedir2(), ".claude", "plugins", "installed_plugins.json");
|
|
16070
16592
|
}
|
|
16071
16593
|
function getInstallMarkerPath(targetDir) {
|
|
16072
|
-
return
|
|
16594
|
+
return resolve31(targetDir, INSTALL_MARKER_FILENAME);
|
|
16073
16595
|
}
|
|
16074
16596
|
async function readPackageManifest(packageRoot) {
|
|
16075
|
-
const raw = await
|
|
16597
|
+
const raw = await readFile20(resolve31(packageRoot, "package.json"), "utf-8");
|
|
16076
16598
|
return JSON.parse(raw);
|
|
16077
16599
|
}
|
|
16078
16600
|
async function readJsonFileIfExists(pathValue) {
|
|
@@ -16080,7 +16602,7 @@ async function readJsonFileIfExists(pathValue) {
|
|
|
16080
16602
|
return null;
|
|
16081
16603
|
}
|
|
16082
16604
|
try {
|
|
16083
|
-
const raw = await
|
|
16605
|
+
const raw = await readFile20(pathValue, "utf-8");
|
|
16084
16606
|
return JSON.parse(raw);
|
|
16085
16607
|
} catch {
|
|
16086
16608
|
return null;
|
|
@@ -16088,15 +16610,15 @@ async function readJsonFileIfExists(pathValue) {
|
|
|
16088
16610
|
}
|
|
16089
16611
|
async function readClaudePluginManifest(pluginDir) {
|
|
16090
16612
|
return await readJsonFileIfExists(
|
|
16091
|
-
|
|
16613
|
+
resolve31(pluginDir, ".claude-plugin", "plugin.json")
|
|
16092
16614
|
) ?? {};
|
|
16093
16615
|
}
|
|
16094
16616
|
async function readPluginManifestName(targetDir, pluginKind) {
|
|
16095
|
-
const manifestPath =
|
|
16617
|
+
const manifestPath = resolve31(targetDir, getPluginManifestRelativePath(pluginKind));
|
|
16096
16618
|
if (!await fileExists(manifestPath)) {
|
|
16097
16619
|
return void 0;
|
|
16098
16620
|
}
|
|
16099
|
-
const raw = await
|
|
16621
|
+
const raw = await readFile20(manifestPath, "utf-8");
|
|
16100
16622
|
const parsed = JSON.parse(raw);
|
|
16101
16623
|
return parsed.name;
|
|
16102
16624
|
}
|
|
@@ -16106,7 +16628,7 @@ async function readInstallMetadata(targetDir) {
|
|
|
16106
16628
|
return null;
|
|
16107
16629
|
}
|
|
16108
16630
|
try {
|
|
16109
|
-
const raw = await
|
|
16631
|
+
const raw = await readFile20(markerPath, "utf-8");
|
|
16110
16632
|
return JSON.parse(raw);
|
|
16111
16633
|
} catch {
|
|
16112
16634
|
return null;
|
|
@@ -16119,7 +16641,7 @@ async function getInstallStatus(targetDir, pluginKind) {
|
|
|
16119
16641
|
const info = await lstat(targetDir);
|
|
16120
16642
|
if (info.isSymbolicLink()) {
|
|
16121
16643
|
const symlinkTarget = await readlink(targetDir);
|
|
16122
|
-
const resolvedTarget =
|
|
16644
|
+
const resolvedTarget = resolve31(dirname9(targetDir), symlinkTarget);
|
|
16123
16645
|
const manifestName2 = await readPluginManifestName(resolvedTarget, pluginKind);
|
|
16124
16646
|
return {
|
|
16125
16647
|
exists: true,
|
|
@@ -16165,7 +16687,7 @@ async function installLink(paths) {
|
|
|
16165
16687
|
await ensureDir(dirname9(paths.targetDir));
|
|
16166
16688
|
await rm3(paths.targetDir, { recursive: true, force: true });
|
|
16167
16689
|
await ensureDir(dirname9(paths.targetDir));
|
|
16168
|
-
await symlink(
|
|
16690
|
+
await symlink(resolve31(paths.sourceDir), paths.targetDir, "dir");
|
|
16169
16691
|
}
|
|
16170
16692
|
async function removeInstallMarker(targetDir) {
|
|
16171
16693
|
const markerPath = getInstallMarkerPath(targetDir);
|
|
@@ -16176,16 +16698,16 @@ async function removeInstallMarker(targetDir) {
|
|
|
16176
16698
|
}
|
|
16177
16699
|
function normalizeAbsoluteInstallPath(pathValue, label) {
|
|
16178
16700
|
const expanded = expandHome(pathValue.trim());
|
|
16179
|
-
if (!
|
|
16701
|
+
if (!isAbsolute3(expanded)) {
|
|
16180
16702
|
throw new Error(`${label} must be an absolute path.`);
|
|
16181
16703
|
}
|
|
16182
|
-
return
|
|
16704
|
+
return resolve31(expanded);
|
|
16183
16705
|
}
|
|
16184
16706
|
async function resolvePluginPaths(pluginKind, targetDir) {
|
|
16185
16707
|
const packageRoot = await findPackageRoot(getPluginRelativePath(pluginKind));
|
|
16186
16708
|
return {
|
|
16187
16709
|
packageRoot,
|
|
16188
|
-
sourceDir:
|
|
16710
|
+
sourceDir: resolve31(packageRoot, getPluginRelativePath(pluginKind)),
|
|
16189
16711
|
targetDir: targetDir ?? getDefaultPluginTargetDir(pluginKind)
|
|
16190
16712
|
};
|
|
16191
16713
|
}
|
|
@@ -16229,7 +16751,7 @@ async function writeClaudeMarketplaceFile(manifestPath, marketplace) {
|
|
|
16229
16751
|
}
|
|
16230
16752
|
if (await fileExists(manifestPath)) {
|
|
16231
16753
|
try {
|
|
16232
|
-
const prev = await
|
|
16754
|
+
const prev = await readFile20(manifestPath, "utf-8");
|
|
16233
16755
|
JSON.parse(prev);
|
|
16234
16756
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
16235
16757
|
await writeFile6(`${manifestPath}.bak-${stamp}`, prev, "utf-8");
|
|
@@ -16302,7 +16824,7 @@ async function listClaudeMarketplaceCandidates() {
|
|
|
16302
16824
|
const [knownMarketplaces, activeMarketplaceNames, entries] = await Promise.all([
|
|
16303
16825
|
readKnownClaudeMarketplaceRecords(),
|
|
16304
16826
|
readInstalledClaudeMarketplaceNames(),
|
|
16305
|
-
|
|
16827
|
+
readdir13(rootDir, { withFileTypes: true })
|
|
16306
16828
|
]);
|
|
16307
16829
|
const candidates = [];
|
|
16308
16830
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -16310,8 +16832,8 @@ async function listClaudeMarketplaceCandidates() {
|
|
|
16310
16832
|
if (!entry.isDirectory()) {
|
|
16311
16833
|
continue;
|
|
16312
16834
|
}
|
|
16313
|
-
const candidateRoot =
|
|
16314
|
-
const manifestPath =
|
|
16835
|
+
const candidateRoot = resolve31(rootDir, entry.name);
|
|
16836
|
+
const manifestPath = resolve31(candidateRoot, ".claude-plugin", "marketplace.json");
|
|
16315
16837
|
if (!await fileExists(manifestPath)) {
|
|
16316
16838
|
continue;
|
|
16317
16839
|
}
|
|
@@ -16338,11 +16860,11 @@ async function listClaudeMarketplaceCandidates() {
|
|
|
16338
16860
|
if (!installLocation) {
|
|
16339
16861
|
continue;
|
|
16340
16862
|
}
|
|
16341
|
-
const candidateRoot =
|
|
16863
|
+
const candidateRoot = resolve31(expandHome(installLocation));
|
|
16342
16864
|
if (seen.has(candidateRoot)) {
|
|
16343
16865
|
continue;
|
|
16344
16866
|
}
|
|
16345
|
-
const manifestPath =
|
|
16867
|
+
const manifestPath = resolve31(candidateRoot, ".claude-plugin", "marketplace.json");
|
|
16346
16868
|
if (!await fileExists(manifestPath)) {
|
|
16347
16869
|
continue;
|
|
16348
16870
|
}
|
|
@@ -16370,14 +16892,14 @@ async function getPreferredClaudeMarketplace() {
|
|
|
16370
16892
|
name: candidate.name,
|
|
16371
16893
|
rootDir: candidate.rootDir,
|
|
16372
16894
|
manifestPath: candidate.manifestPath,
|
|
16373
|
-
targetDir:
|
|
16895
|
+
targetDir: resolve31(candidate.rootDir, "plugins", "syntaur")
|
|
16374
16896
|
};
|
|
16375
16897
|
}
|
|
16376
16898
|
async function registerKnownClaudeMarketplace(name, rootDir) {
|
|
16377
16899
|
const manifestPath = getClaudeKnownMarketplacesPath();
|
|
16378
16900
|
let existing = {};
|
|
16379
16901
|
if (await fileExists(manifestPath)) {
|
|
16380
|
-
const raw = await
|
|
16902
|
+
const raw = await readFile20(manifestPath, "utf-8");
|
|
16381
16903
|
try {
|
|
16382
16904
|
const parsed = JSON.parse(raw);
|
|
16383
16905
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
@@ -16404,7 +16926,7 @@ async function registerKnownClaudeMarketplace(name, rootDir) {
|
|
|
16404
16926
|
existing[name].autoUpdate = true;
|
|
16405
16927
|
await ensureDir(dirname9(manifestPath));
|
|
16406
16928
|
if (await fileExists(manifestPath)) {
|
|
16407
|
-
const prev = await
|
|
16929
|
+
const prev = await readFile20(manifestPath, "utf-8");
|
|
16408
16930
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
16409
16931
|
await writeFile6(`${manifestPath}.bak-${stamp}`, prev, "utf-8");
|
|
16410
16932
|
}
|
|
@@ -16418,11 +16940,11 @@ async function ensureKnownClaudeMarketplaceForRoot(options) {
|
|
|
16418
16940
|
return registerKnownClaudeMarketplace(options.name, options.rootDir);
|
|
16419
16941
|
}
|
|
16420
16942
|
async function setSyntaurPluginEnabled(options) {
|
|
16421
|
-
const settingsPath =
|
|
16943
|
+
const settingsPath = resolve31(homedir2(), ".claude", "settings.json");
|
|
16422
16944
|
const key = `syntaur@${options.marketplaceName}`;
|
|
16423
16945
|
let parsed = {};
|
|
16424
16946
|
if (await fileExists(settingsPath)) {
|
|
16425
|
-
const raw = await
|
|
16947
|
+
const raw = await readFile20(settingsPath, "utf-8");
|
|
16426
16948
|
try {
|
|
16427
16949
|
parsed = JSON.parse(raw);
|
|
16428
16950
|
} catch {
|
|
@@ -16441,7 +16963,7 @@ async function setSyntaurPluginEnabled(options) {
|
|
|
16441
16963
|
await ensureDir(dirname9(settingsPath));
|
|
16442
16964
|
if (await fileExists(settingsPath)) {
|
|
16443
16965
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
16444
|
-
const prev = await
|
|
16966
|
+
const prev = await readFile20(settingsPath, "utf-8");
|
|
16445
16967
|
await writeFile6(`${settingsPath}.bak-${stamp}`, prev, "utf-8");
|
|
16446
16968
|
}
|
|
16447
16969
|
const tmpPath = `${settingsPath}.tmp`;
|
|
@@ -16455,9 +16977,9 @@ async function ensureClaudeUserMarketplace() {
|
|
|
16455
16977
|
if (existing) {
|
|
16456
16978
|
return existing;
|
|
16457
16979
|
}
|
|
16458
|
-
const rootDir =
|
|
16459
|
-
const manifestPath =
|
|
16460
|
-
await ensureDir(
|
|
16980
|
+
const rootDir = resolve31(getClaudeMarketplacesRoot(), "user-plugins");
|
|
16981
|
+
const manifestPath = resolve31(rootDir, ".claude-plugin", "marketplace.json");
|
|
16982
|
+
await ensureDir(resolve31(rootDir, "plugins"));
|
|
16461
16983
|
if (!await fileExists(manifestPath)) {
|
|
16462
16984
|
const scaffold = {
|
|
16463
16985
|
plugins: []
|
|
@@ -16476,7 +16998,7 @@ async function ensureClaudeUserMarketplace() {
|
|
|
16476
16998
|
name: "user-plugins",
|
|
16477
16999
|
rootDir,
|
|
16478
17000
|
manifestPath,
|
|
16479
|
-
targetDir:
|
|
17001
|
+
targetDir: resolve31(rootDir, "plugins", "syntaur")
|
|
16480
17002
|
};
|
|
16481
17003
|
}
|
|
16482
17004
|
async function detectClaudeMarketplaceForTarget(targetDir) {
|
|
@@ -16486,7 +17008,7 @@ async function detectClaudeMarketplaceForTarget(targetDir) {
|
|
|
16486
17008
|
return null;
|
|
16487
17009
|
}
|
|
16488
17010
|
const rootDir = dirname9(pluginsDir);
|
|
16489
|
-
const manifestPath =
|
|
17011
|
+
const manifestPath = resolve31(rootDir, ".claude-plugin", "marketplace.json");
|
|
16490
17012
|
if (!await fileExists(manifestPath)) {
|
|
16491
17013
|
return null;
|
|
16492
17014
|
}
|
|
@@ -16502,7 +17024,7 @@ async function detectClaudeMarketplaceForTarget(targetDir) {
|
|
|
16502
17024
|
async function findManagedClaudeMarketplacePluginDir() {
|
|
16503
17025
|
const marketplaces = await listClaudeMarketplaceCandidates();
|
|
16504
17026
|
for (const marketplace of marketplaces) {
|
|
16505
|
-
const targetDir =
|
|
17027
|
+
const targetDir = resolve31(marketplace.rootDir, "plugins", "syntaur");
|
|
16506
17028
|
const status = await getInstallStatus(targetDir, "claude");
|
|
16507
17029
|
if (status.exists && status.managed) {
|
|
16508
17030
|
return targetDir;
|
|
@@ -16627,7 +17149,7 @@ async function installManagedPlugin(options) {
|
|
|
16627
17149
|
`${paths.targetDir} already exists and is not a Syntaur-managed install. Remove it manually before installing Syntaur there.`
|
|
16628
17150
|
);
|
|
16629
17151
|
}
|
|
16630
|
-
if (desiredMode === "link" && existing.exists && existing.installMode === "link" && existing.symlinkTarget ===
|
|
17152
|
+
if (desiredMode === "link" && existing.exists && existing.installMode === "link" && existing.symlinkTarget === resolve31(paths.sourceDir) && !force) {
|
|
16631
17153
|
return {
|
|
16632
17154
|
targetDir: paths.targetDir,
|
|
16633
17155
|
sourceDir: paths.sourceDir,
|
|
@@ -16679,7 +17201,7 @@ async function readMarketplaceFile(marketplacePath) {
|
|
|
16679
17201
|
plugins: []
|
|
16680
17202
|
};
|
|
16681
17203
|
}
|
|
16682
|
-
const raw = await
|
|
17204
|
+
const raw = await readFile20(marketplacePath, "utf-8");
|
|
16683
17205
|
const parsed = JSON.parse(raw);
|
|
16684
17206
|
return {
|
|
16685
17207
|
name: parsed.name ?? "local",
|
|
@@ -16840,13 +17362,13 @@ async function recommendMarketplacePath() {
|
|
|
16840
17362
|
return configuredOrManaged ?? getDefaultMarketplacePath();
|
|
16841
17363
|
}
|
|
16842
17364
|
async function isSyntaurDataInstalled() {
|
|
16843
|
-
return fileExists(
|
|
17365
|
+
return fileExists(resolve31(syntaurRoot(), "config.md"));
|
|
16844
17366
|
}
|
|
16845
17367
|
async function removeSyntaurData() {
|
|
16846
17368
|
await rm3(syntaurRoot(), { recursive: true, force: true });
|
|
16847
17369
|
}
|
|
16848
17370
|
async function getConfiguredProjectDir() {
|
|
16849
|
-
if (!await fileExists(
|
|
17371
|
+
if (!await fileExists(resolve31(syntaurRoot(), "config.md"))) {
|
|
16850
17372
|
return null;
|
|
16851
17373
|
}
|
|
16852
17374
|
return (await readConfig()).defaultProjectDir;
|
|
@@ -16915,24 +17437,24 @@ async function textPrompt(question, defaultValue) {
|
|
|
16915
17437
|
|
|
16916
17438
|
// src/utils/install-skills.ts
|
|
16917
17439
|
init_fs();
|
|
16918
|
-
import { readFile as
|
|
16919
|
-
import { resolve as
|
|
17440
|
+
import { readFile as readFile22, readdir as readdir14, mkdir as mkdir4, copyFile, rm as rm4, lstat as lstat2 } from "fs/promises";
|
|
17441
|
+
import { resolve as resolve33, relative as relative3, join as join3 } from "path";
|
|
16920
17442
|
import { homedir as homedir4 } from "os";
|
|
16921
17443
|
|
|
16922
17444
|
// src/utils/plugin-state.ts
|
|
16923
17445
|
init_fs();
|
|
16924
|
-
import { readFile as
|
|
16925
|
-
import { resolve as
|
|
17446
|
+
import { readFile as readFile21 } from "fs/promises";
|
|
17447
|
+
import { resolve as resolve32 } from "path";
|
|
16926
17448
|
import { homedir as homedir3 } from "os";
|
|
16927
17449
|
function settingsPathFor(agent) {
|
|
16928
|
-
if (agent === "claude") return
|
|
17450
|
+
if (agent === "claude") return resolve32(homedir3(), ".claude", "settings.json");
|
|
16929
17451
|
return null;
|
|
16930
17452
|
}
|
|
16931
17453
|
async function readJsonOrNull(path) {
|
|
16932
17454
|
if (!path) return null;
|
|
16933
17455
|
if (!await fileExists(path)) return null;
|
|
16934
17456
|
try {
|
|
16935
|
-
const raw = await
|
|
17457
|
+
const raw = await readFile21(path, "utf-8");
|
|
16936
17458
|
return JSON.parse(raw);
|
|
16937
17459
|
} catch {
|
|
16938
17460
|
return null;
|
|
@@ -16980,16 +17502,16 @@ var KNOWN_SKILL_NAMES = [
|
|
|
16980
17502
|
var KNOWN_SKILLS = KNOWN_SKILL_NAMES;
|
|
16981
17503
|
async function getSkillsDir() {
|
|
16982
17504
|
const packageRoot = await findPackageRoot("skills");
|
|
16983
|
-
return
|
|
17505
|
+
return resolve33(packageRoot, "skills");
|
|
16984
17506
|
}
|
|
16985
17507
|
function defaultSkillTargetDir(target) {
|
|
16986
|
-
if (target === "claude") return
|
|
16987
|
-
return
|
|
17508
|
+
if (target === "claude") return resolve33(homedir4(), ".claude", "skills");
|
|
17509
|
+
return resolve33(homedir4(), ".codex", "skills");
|
|
16988
17510
|
}
|
|
16989
17511
|
async function walkFiles(root) {
|
|
16990
17512
|
const out = [];
|
|
16991
17513
|
async function walk(dir) {
|
|
16992
|
-
const entries = await
|
|
17514
|
+
const entries = await readdir14(dir, { withFileTypes: true });
|
|
16993
17515
|
for (const entry of entries) {
|
|
16994
17516
|
const full = join3(dir, entry.name);
|
|
16995
17517
|
if (entry.isDirectory()) {
|
|
@@ -17004,7 +17526,7 @@ async function walkFiles(root) {
|
|
|
17004
17526
|
}
|
|
17005
17527
|
async function filesEqual(a, b) {
|
|
17006
17528
|
try {
|
|
17007
|
-
const [ba, bb] = await Promise.all([
|
|
17529
|
+
const [ba, bb] = await Promise.all([readFile22(a), readFile22(b)]);
|
|
17008
17530
|
if (ba.length !== bb.length) return false;
|
|
17009
17531
|
return ba.equals(bb);
|
|
17010
17532
|
} catch {
|
|
@@ -17013,7 +17535,7 @@ async function filesEqual(a, b) {
|
|
|
17013
17535
|
}
|
|
17014
17536
|
async function copyDir(srcDir, destDir) {
|
|
17015
17537
|
await mkdir4(destDir, { recursive: true });
|
|
17016
|
-
const entries = await
|
|
17538
|
+
const entries = await readdir14(srcDir, { withFileTypes: true });
|
|
17017
17539
|
for (const entry of entries) {
|
|
17018
17540
|
const src = join3(srcDir, entry.name);
|
|
17019
17541
|
const dest = join3(destDir, entry.name);
|
|
@@ -17067,7 +17589,7 @@ async function installSkillDir(srcDir, destDir, skillName, force) {
|
|
|
17067
17589
|
return { skill: skillName, status: "differs-preserved", targetPath: destDir };
|
|
17068
17590
|
}
|
|
17069
17591
|
async function discoverSkillNames(sourceDir) {
|
|
17070
|
-
const entries = await
|
|
17592
|
+
const entries = await readdir14(sourceDir, { withFileTypes: true });
|
|
17071
17593
|
const names = [];
|
|
17072
17594
|
for (const entry of entries) {
|
|
17073
17595
|
if (!entry.isDirectory()) continue;
|
|
@@ -17126,7 +17648,7 @@ async function uninstallSkills(options) {
|
|
|
17126
17648
|
if (await isSymlink(destDir)) continue;
|
|
17127
17649
|
const skillMd = join3(destDir, "SKILL.md");
|
|
17128
17650
|
if (!await fileExists(skillMd)) continue;
|
|
17129
|
-
const content = await
|
|
17651
|
+
const content = await readFile22(skillMd, "utf-8").catch(() => "");
|
|
17130
17652
|
const match = content.match(/^name:\s*(\S+)\s*$/m);
|
|
17131
17653
|
if (!match || match[1] !== skill) continue;
|
|
17132
17654
|
await rm4(destDir, { recursive: true, force: true });
|
|
@@ -17315,17 +17837,17 @@ async function installPluginCommand(options) {
|
|
|
17315
17837
|
// src/commands/install-statusline.ts
|
|
17316
17838
|
init_paths();
|
|
17317
17839
|
init_fs();
|
|
17318
|
-
import { readFile as
|
|
17319
|
-
import { resolve as
|
|
17840
|
+
import { readFile as readFile24, writeFile as writeFile8, copyFile as copyFile2, rm as rm5, stat as stat2, symlink as symlink2, unlink as unlink7, lstat as lstat3 } from "fs/promises";
|
|
17841
|
+
import { resolve as resolve35, dirname as dirname11 } from "path";
|
|
17320
17842
|
import { homedir as homedir5 } from "os";
|
|
17321
17843
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
17322
17844
|
|
|
17323
17845
|
// src/commands/configure-statusline.ts
|
|
17324
17846
|
init_paths();
|
|
17325
17847
|
init_fs();
|
|
17326
|
-
import { readFile as
|
|
17327
|
-
import { resolve as
|
|
17328
|
-
import { spawnSync } from "child_process";
|
|
17848
|
+
import { readFile as readFile23, writeFile as writeFile7 } from "fs/promises";
|
|
17849
|
+
import { resolve as resolve34, dirname as dirname10 } from "path";
|
|
17850
|
+
import { spawnSync as spawnSync5 } from "child_process";
|
|
17329
17851
|
import { checkbox, input as input2, confirm } from "@inquirer/prompts";
|
|
17330
17852
|
var AVAILABLE_SEGMENTS = [
|
|
17331
17853
|
{ name: "git", preview: "syntaur:main* +2", description: "repo:branch (with dirty marker and ahead/behind)" },
|
|
@@ -17345,12 +17867,12 @@ var PRESETS = {
|
|
|
17345
17867
|
tracker: { segments: ["git", "assignment", "external", "session"], separator: " \xB7 " }
|
|
17346
17868
|
};
|
|
17347
17869
|
function getConfigPath(installRoot) {
|
|
17348
|
-
return
|
|
17870
|
+
return resolve34(installRoot, "statusline.config.json");
|
|
17349
17871
|
}
|
|
17350
17872
|
async function readConfig2(path) {
|
|
17351
17873
|
if (!await fileExists(path)) return null;
|
|
17352
17874
|
try {
|
|
17353
|
-
const raw = await
|
|
17875
|
+
const raw = await readFile23(path, "utf-8");
|
|
17354
17876
|
const parsed = JSON.parse(raw);
|
|
17355
17877
|
if (!parsed || typeof parsed !== "object") return null;
|
|
17356
17878
|
const segments = Array.isArray(parsed.segments) ? parsed.segments.filter(isSegmentName) : [];
|
|
@@ -17439,7 +17961,7 @@ function renderPreview(config, statuslineScript, cwd) {
|
|
|
17439
17961
|
model: { display_name: "Opus 4.7" },
|
|
17440
17962
|
context_window: { used_percentage: 42 }
|
|
17441
17963
|
};
|
|
17442
|
-
const res =
|
|
17964
|
+
const res = spawnSync5("bash", [statuslineScript], {
|
|
17443
17965
|
input: JSON.stringify(payload),
|
|
17444
17966
|
encoding: "utf-8",
|
|
17445
17967
|
env: {
|
|
@@ -17503,7 +18025,7 @@ async function configureStatuslineCommand(options = {}) {
|
|
|
17503
18025
|
console.log(` segments: ${config.segments.join(", ")}`);
|
|
17504
18026
|
console.log(` separator: ${JSON.stringify(config.separator)}`);
|
|
17505
18027
|
if (config.wrap) console.log(` wrap: ${config.wrap}`);
|
|
17506
|
-
const script = options.statuslineScript ??
|
|
18028
|
+
const script = options.statuslineScript ?? resolve34(installRoot, "statusline.sh");
|
|
17507
18029
|
if (await fileExists(script)) {
|
|
17508
18030
|
console.log("");
|
|
17509
18031
|
console.log("Live preview:");
|
|
@@ -17534,11 +18056,11 @@ async function writeDefaultConfigIfMissing(installRoot) {
|
|
|
17534
18056
|
// src/commands/install-statusline.ts
|
|
17535
18057
|
function getPackageStatuslineSource() {
|
|
17536
18058
|
const here = dirname11(fileURLToPath4(import.meta.url));
|
|
17537
|
-
return
|
|
18059
|
+
return resolve35(here, "..", "statusline", "statusline.sh");
|
|
17538
18060
|
}
|
|
17539
18061
|
async function readSettingsJson(settingsPath) {
|
|
17540
18062
|
if (!await fileExists(settingsPath)) return {};
|
|
17541
|
-
const raw = await
|
|
18063
|
+
const raw = await readFile24(settingsPath, "utf-8");
|
|
17542
18064
|
if (raw.trim() === "") return {};
|
|
17543
18065
|
try {
|
|
17544
18066
|
const parsed = JSON.parse(raw);
|
|
@@ -17618,12 +18140,12 @@ async function installScript(sourceScript, destScript, link) {
|
|
|
17618
18140
|
}
|
|
17619
18141
|
async function installStatuslineCommand(options = {}) {
|
|
17620
18142
|
const mode = options.mode ?? "ask";
|
|
17621
|
-
const settingsPath = options.settingsPath ??
|
|
18143
|
+
const settingsPath = options.settingsPath ?? resolve35(homedir5(), ".claude", "settings.json");
|
|
17622
18144
|
const installRoot = options.installRoot ?? syntaurRoot();
|
|
17623
18145
|
const sourceScript = options.sourceScript ?? getPackageStatuslineSource();
|
|
17624
|
-
const destScript =
|
|
17625
|
-
const confPath =
|
|
17626
|
-
const backupPath =
|
|
18146
|
+
const destScript = resolve35(installRoot, "statusline.sh");
|
|
18147
|
+
const confPath = resolve35(installRoot, "statusline.conf");
|
|
18148
|
+
const backupPath = resolve35(installRoot, "statusline.backup.json");
|
|
17627
18149
|
if (!await fileExists(sourceScript)) {
|
|
17628
18150
|
throw new Error(
|
|
17629
18151
|
`Statusline source script not found at ${sourceScript}. Try re-installing syntaur (npm install -g syntaur) or pass --source-script explicitly.`
|
|
@@ -17659,7 +18181,7 @@ async function installStatuslineCommand(options = {}) {
|
|
|
17659
18181
|
if (parsed) {
|
|
17660
18182
|
wrapTarget = parsed;
|
|
17661
18183
|
} else {
|
|
17662
|
-
const wrapperPath =
|
|
18184
|
+
const wrapperPath = resolve35(installRoot, "statusline-wrapped.sh");
|
|
17663
18185
|
const wrapperBody = `#!/usr/bin/env bash
|
|
17664
18186
|
# Auto-generated by syntaur install-statusline.
|
|
17665
18187
|
# Executes the previously configured statusLine command.
|
|
@@ -17714,19 +18236,19 @@ async function chmodExec(path) {
|
|
|
17714
18236
|
}
|
|
17715
18237
|
}
|
|
17716
18238
|
async function uninstallStatuslineCommand(options = {}) {
|
|
17717
|
-
const settingsPath = options.settingsPath ??
|
|
18239
|
+
const settingsPath = options.settingsPath ?? resolve35(homedir5(), ".claude", "settings.json");
|
|
17718
18240
|
const installRoot = options.installRoot ?? syntaurRoot();
|
|
17719
|
-
const destScript =
|
|
17720
|
-
const confPath =
|
|
17721
|
-
const backupPath =
|
|
17722
|
-
const wrapperPath =
|
|
18241
|
+
const destScript = resolve35(installRoot, "statusline.sh");
|
|
18242
|
+
const confPath = resolve35(installRoot, "statusline.conf");
|
|
18243
|
+
const backupPath = resolve35(installRoot, "statusline.backup.json");
|
|
18244
|
+
const wrapperPath = resolve35(installRoot, "statusline-wrapped.sh");
|
|
17723
18245
|
const settings = await readSettingsJson(settingsPath);
|
|
17724
18246
|
const existing = extractExistingCommand(settings);
|
|
17725
18247
|
const ourCommand = `bash ${destScript}`;
|
|
17726
18248
|
let restored = null;
|
|
17727
18249
|
if (await fileExists(backupPath)) {
|
|
17728
18250
|
try {
|
|
17729
|
-
const raw = await
|
|
18251
|
+
const raw = await readFile24(backupPath, "utf-8");
|
|
17730
18252
|
const parsed = JSON.parse(raw);
|
|
17731
18253
|
const prev = parsed?.previousStatusLine;
|
|
17732
18254
|
if (prev && typeof prev === "object" && typeof prev.command === "string") {
|
|
@@ -17747,7 +18269,7 @@ async function uninstallStatuslineCommand(options = {}) {
|
|
|
17747
18269
|
await writeSettingsJson(settingsPath, settings);
|
|
17748
18270
|
}
|
|
17749
18271
|
if (!options.keepScript) {
|
|
17750
|
-
const configPath =
|
|
18272
|
+
const configPath = resolve35(installRoot, "statusline.config.json");
|
|
17751
18273
|
for (const path of [destScript, confPath, backupPath, wrapperPath, configPath]) {
|
|
17752
18274
|
try {
|
|
17753
18275
|
await rm5(path, { force: true });
|
|
@@ -18004,7 +18526,7 @@ async function setupCommand(options) {
|
|
|
18004
18526
|
}
|
|
18005
18527
|
|
|
18006
18528
|
// src/commands/uninstall.ts
|
|
18007
|
-
import { resolve as
|
|
18529
|
+
import { resolve as resolve36 } from "path";
|
|
18008
18530
|
init_paths();
|
|
18009
18531
|
function expandTargets(options) {
|
|
18010
18532
|
if (options.all) {
|
|
@@ -18084,7 +18606,7 @@ async function uninstallCommand(options) {
|
|
|
18084
18606
|
const configuredProjectDir = await getConfiguredProjectDir();
|
|
18085
18607
|
await removeSyntaurData();
|
|
18086
18608
|
console.log(`Removed ${syntaurRoot()}`);
|
|
18087
|
-
if (configuredProjectDir &&
|
|
18609
|
+
if (configuredProjectDir && resolve36(configuredProjectDir) !== resolve36(syntaurRoot(), "projects")) {
|
|
18088
18610
|
console.warn(
|
|
18089
18611
|
`Warning: config.md pointed to an external project directory (${configuredProjectDir}). That directory was not removed automatically.`
|
|
18090
18612
|
);
|
|
@@ -18103,7 +18625,7 @@ init_slug();
|
|
|
18103
18625
|
init_cursor_rules();
|
|
18104
18626
|
init_codex_agents();
|
|
18105
18627
|
init_opencode_config();
|
|
18106
|
-
import { resolve as
|
|
18628
|
+
import { resolve as resolve37 } from "path";
|
|
18107
18629
|
var SUPPORTED_FRAMEWORKS = ["cursor", "codex", "opencode"];
|
|
18108
18630
|
async function setupAdapterCommand(framework, options) {
|
|
18109
18631
|
if (!SUPPORTED_FRAMEWORKS.includes(framework)) {
|
|
@@ -18129,19 +18651,19 @@ async function setupAdapterCommand(framework, options) {
|
|
|
18129
18651
|
}
|
|
18130
18652
|
const config = await readConfig();
|
|
18131
18653
|
const baseDir = options.dir ? expandHome(options.dir) : config.defaultProjectDir;
|
|
18132
|
-
const projectDir =
|
|
18133
|
-
const assignmentDir =
|
|
18654
|
+
const projectDir = resolve37(baseDir, options.project);
|
|
18655
|
+
const assignmentDir = resolve37(
|
|
18134
18656
|
projectDir,
|
|
18135
18657
|
"assignments",
|
|
18136
18658
|
options.assignment
|
|
18137
18659
|
);
|
|
18138
|
-
const projectMdPath =
|
|
18660
|
+
const projectMdPath = resolve37(projectDir, "project.md");
|
|
18139
18661
|
if (!await fileExists(projectDir) || !await fileExists(projectMdPath)) {
|
|
18140
18662
|
throw new Error(
|
|
18141
18663
|
`Project "${options.project}" not found at ${projectDir}.`
|
|
18142
18664
|
);
|
|
18143
18665
|
}
|
|
18144
|
-
const assignmentMdPath2 =
|
|
18666
|
+
const assignmentMdPath2 = resolve37(assignmentDir, "assignment.md");
|
|
18145
18667
|
if (!await fileExists(assignmentDir) || !await fileExists(assignmentMdPath2)) {
|
|
18146
18668
|
throw new Error(
|
|
18147
18669
|
`Assignment "${options.assignment}" not found at ${assignmentDir}.`
|
|
@@ -18169,15 +18691,15 @@ async function setupAdapterCommand(framework, options) {
|
|
|
18169
18691
|
}
|
|
18170
18692
|
}
|
|
18171
18693
|
if (framework === "cursor") {
|
|
18172
|
-
const protocolPath =
|
|
18173
|
-
const assignmentPath =
|
|
18694
|
+
const protocolPath = resolve37(cwd, ".cursor", "rules", "syntaur-protocol.mdc");
|
|
18695
|
+
const assignmentPath = resolve37(cwd, ".cursor", "rules", "syntaur-assignment.mdc");
|
|
18174
18696
|
await writeAdapterFile(protocolPath, renderCursorProtocol());
|
|
18175
18697
|
await writeAdapterFile(assignmentPath, renderCursorAssignment(rendererParams));
|
|
18176
18698
|
} else if (framework === "codex" || framework === "opencode") {
|
|
18177
|
-
const agentsPath =
|
|
18699
|
+
const agentsPath = resolve37(cwd, "AGENTS.md");
|
|
18178
18700
|
await writeAdapterFile(agentsPath, renderCodexAgents(rendererParams));
|
|
18179
18701
|
if (framework === "opencode") {
|
|
18180
|
-
const configPath =
|
|
18702
|
+
const configPath = resolve37(cwd, "opencode.json");
|
|
18181
18703
|
await writeAdapterFile(configPath, renderOpenCodeConfig({ projectDir }));
|
|
18182
18704
|
}
|
|
18183
18705
|
}
|
|
@@ -18202,7 +18724,7 @@ async function setupAdapterCommand(framework, options) {
|
|
|
18202
18724
|
init_paths();
|
|
18203
18725
|
init_fs();
|
|
18204
18726
|
init_config2();
|
|
18205
|
-
import { resolve as
|
|
18727
|
+
import { resolve as resolve38 } from "path";
|
|
18206
18728
|
init_session_db();
|
|
18207
18729
|
init_agent_sessions();
|
|
18208
18730
|
async function trackSessionCommand(options) {
|
|
@@ -18217,7 +18739,7 @@ async function trackSessionCommand(options) {
|
|
|
18217
18739
|
if (options.project) {
|
|
18218
18740
|
const config = await readConfig();
|
|
18219
18741
|
const baseDir = options.dir ? expandHome(options.dir) : config.defaultProjectDir;
|
|
18220
|
-
const projectDir =
|
|
18742
|
+
const projectDir = resolve38(baseDir, options.project);
|
|
18221
18743
|
if (!await fileExists(projectDir)) {
|
|
18222
18744
|
throw new Error(
|
|
18223
18745
|
`Project "${options.project}" not found at ${projectDir}.`
|
|
@@ -18257,6 +18779,7 @@ init_config2();
|
|
|
18257
18779
|
init_paths();
|
|
18258
18780
|
|
|
18259
18781
|
// src/launch/url.ts
|
|
18782
|
+
init_terminal_schema();
|
|
18260
18783
|
var OpenUrlError = class extends Error {
|
|
18261
18784
|
code;
|
|
18262
18785
|
constructor(code, message) {
|
|
@@ -18308,6 +18831,24 @@ function parseOpenUrl(input4) {
|
|
|
18308
18831
|
"URL has both `assignment` and `session` query params \u2014 only one is allowed"
|
|
18309
18832
|
);
|
|
18310
18833
|
}
|
|
18834
|
+
const terminalVals = url.searchParams.getAll("terminal");
|
|
18835
|
+
if (terminalVals.length > 1) {
|
|
18836
|
+
throw new OpenUrlError(
|
|
18837
|
+
"duplicate-param",
|
|
18838
|
+
"URL has more than one `terminal` query param"
|
|
18839
|
+
);
|
|
18840
|
+
}
|
|
18841
|
+
let terminal;
|
|
18842
|
+
if (terminalVals.length === 1 && terminalVals[0].trim() !== "") {
|
|
18843
|
+
const candidate = terminalVals[0];
|
|
18844
|
+
if (!TERMINAL_CHOICES.includes(candidate)) {
|
|
18845
|
+
throw new OpenUrlError(
|
|
18846
|
+
"bad-terminal",
|
|
18847
|
+
`\`terminal\` query param must be one of: ${TERMINAL_CHOICES.join(", ")}`
|
|
18848
|
+
);
|
|
18849
|
+
}
|
|
18850
|
+
terminal = candidate;
|
|
18851
|
+
}
|
|
18311
18852
|
if (assignmentVals.length === 1) {
|
|
18312
18853
|
const id = assignmentVals[0];
|
|
18313
18854
|
if (id.trim() === "") {
|
|
@@ -18316,7 +18857,7 @@ function parseOpenUrl(input4) {
|
|
|
18316
18857
|
"`assignment` query param is empty"
|
|
18317
18858
|
);
|
|
18318
18859
|
}
|
|
18319
|
-
return { kind: "assignment", id };
|
|
18860
|
+
return { kind: "assignment", id, ...terminal ? { terminal } : {} };
|
|
18320
18861
|
}
|
|
18321
18862
|
if (sessionVals.length === 1) {
|
|
18322
18863
|
const id = sessionVals[0];
|
|
@@ -18341,7 +18882,7 @@ function parseOpenUrl(input4) {
|
|
|
18341
18882
|
}
|
|
18342
18883
|
mode = raw;
|
|
18343
18884
|
}
|
|
18344
|
-
return { kind: "session", id, mode };
|
|
18885
|
+
return { kind: "session", id, mode, ...terminal ? { terminal } : {} };
|
|
18345
18886
|
}
|
|
18346
18887
|
throw new OpenUrlError(
|
|
18347
18888
|
"missing-id",
|
|
@@ -18355,11 +18896,11 @@ init_assignment_resolver();
|
|
|
18355
18896
|
init_api();
|
|
18356
18897
|
init_launch();
|
|
18357
18898
|
init_agent_sessions();
|
|
18358
|
-
import { isAbsolute as
|
|
18899
|
+
import { isAbsolute as isAbsolute6 } from "path";
|
|
18359
18900
|
|
|
18360
18901
|
// src/launch/argv.ts
|
|
18361
18902
|
init_launch();
|
|
18362
|
-
import { isAbsolute as
|
|
18903
|
+
import { isAbsolute as isAbsolute5 } from "path";
|
|
18363
18904
|
var buildFreshArgv = buildAgentArgv;
|
|
18364
18905
|
function buildSessionArgv(agent, sessionId, mode, env = process.env) {
|
|
18365
18906
|
const invocation = agent[mode];
|
|
@@ -18378,7 +18919,7 @@ function buildSessionArgv(agent, sessionId, mode, env = process.env) {
|
|
|
18378
18919
|
const requested = env.SHELL;
|
|
18379
18920
|
let shell = requested;
|
|
18380
18921
|
let warning = null;
|
|
18381
|
-
if (!shell || !
|
|
18922
|
+
if (!shell || !isAbsolute5(shell)) {
|
|
18382
18923
|
warning = `syntaur: $SHELL ${requested ? `("${requested}") is not absolute` : "is unset"} \u2014 falling back to /bin/sh for shell-alias resolution`;
|
|
18383
18924
|
shell = "/bin/sh";
|
|
18384
18925
|
}
|
|
@@ -18414,7 +18955,7 @@ function pickAgent(config) {
|
|
|
18414
18955
|
return agents.find((a) => a.default) ?? agents[0];
|
|
18415
18956
|
}
|
|
18416
18957
|
async function resolveLaunchPlan(input4) {
|
|
18417
|
-
const terminal = getTerminal(input4.config);
|
|
18958
|
+
const terminal = input4.terminalOverride ?? getTerminal(input4.config);
|
|
18418
18959
|
if (input4.kind === "assignment") {
|
|
18419
18960
|
return resolveAssignmentPlan(input4, terminal);
|
|
18420
18961
|
}
|
|
@@ -18520,10 +19061,10 @@ async function resolveSessionPlan(input4, terminal) {
|
|
|
18520
19061
|
};
|
|
18521
19062
|
}
|
|
18522
19063
|
function pickCwd(input4) {
|
|
18523
|
-
if (input4.worktreePath &&
|
|
19064
|
+
if (input4.worktreePath && isAbsolute6(input4.worktreePath)) {
|
|
18524
19065
|
return { cwd: input4.worktreePath, fallbackWarning: null };
|
|
18525
19066
|
}
|
|
18526
|
-
const workspaceDir = input4.repository &&
|
|
19067
|
+
const workspaceDir = input4.repository && isAbsolute6(input4.repository) ? input4.repository : input4.fallbackPath;
|
|
18527
19068
|
const fallbackWarning = formatFallbackCwdWarning({
|
|
18528
19069
|
assignmentSlug: input4.assignmentSlug,
|
|
18529
19070
|
workspaceDir,
|
|
@@ -18535,7 +19076,7 @@ function pickCwd(input4) {
|
|
|
18535
19076
|
|
|
18536
19077
|
// src/launch/execute.ts
|
|
18537
19078
|
init_launch();
|
|
18538
|
-
import { spawn as
|
|
19079
|
+
import { spawn as spawn4 } from "child_process";
|
|
18539
19080
|
var TerminalNotFoundError = class extends Error {
|
|
18540
19081
|
terminal;
|
|
18541
19082
|
remediation;
|
|
@@ -18548,7 +19089,7 @@ var TerminalNotFoundError = class extends Error {
|
|
|
18548
19089
|
this.name = "TerminalNotFoundError";
|
|
18549
19090
|
}
|
|
18550
19091
|
};
|
|
18551
|
-
var realSpawn = (command, args, options) =>
|
|
19092
|
+
var realSpawn = (command, args, options) => spawn4(command, args, options);
|
|
18552
19093
|
var WRAPPER_COMMANDS = /* @__PURE__ */ new Set(["osascript", "open"]);
|
|
18553
19094
|
var WRAPPER_EXIT_TIMEOUT_MS = 1500;
|
|
18554
19095
|
async function executeLaunchPlan(plan, spawnFn = realSpawn) {
|
|
@@ -18575,7 +19116,7 @@ async function executeLaunchPlan(plan, spawnFn = realSpawn) {
|
|
|
18575
19116
|
`Spawn failed: ${msg}. Verify the terminal is installed and on PATH.`
|
|
18576
19117
|
);
|
|
18577
19118
|
}
|
|
18578
|
-
await new Promise((
|
|
19119
|
+
await new Promise((resolve72, reject) => {
|
|
18579
19120
|
let settled = false;
|
|
18580
19121
|
let stderr = "";
|
|
18581
19122
|
const finishOk = () => {
|
|
@@ -18585,7 +19126,7 @@ async function executeLaunchPlan(plan, spawnFn = realSpawn) {
|
|
|
18585
19126
|
child.unref();
|
|
18586
19127
|
} catch {
|
|
18587
19128
|
}
|
|
18588
|
-
|
|
19129
|
+
resolve72();
|
|
18589
19130
|
};
|
|
18590
19131
|
const finishErr = (remediation) => {
|
|
18591
19132
|
if (settled) return;
|
|
@@ -18658,19 +19199,19 @@ function buildTerminalInvocation(plan) {
|
|
|
18658
19199
|
command: "osascript",
|
|
18659
19200
|
args: [
|
|
18660
19201
|
"-e",
|
|
18661
|
-
'tell application "Ghostty"',
|
|
19202
|
+
'tell application "Ghostty" to activate',
|
|
18662
19203
|
"-e",
|
|
18663
|
-
"
|
|
19204
|
+
"delay 0.3",
|
|
18664
19205
|
"-e",
|
|
18665
|
-
|
|
19206
|
+
'tell application "System Events"',
|
|
18666
19207
|
"-e",
|
|
18667
|
-
"
|
|
19208
|
+
'keystroke "n" using command down',
|
|
18668
19209
|
"-e",
|
|
18669
|
-
"
|
|
19210
|
+
"delay 0.4",
|
|
18670
19211
|
"-e",
|
|
18671
|
-
`
|
|
19212
|
+
`keystroke ${appleScriptString(cdAndRun)}`,
|
|
18672
19213
|
"-e",
|
|
18673
|
-
|
|
19214
|
+
"key code 36",
|
|
18674
19215
|
"-e",
|
|
18675
19216
|
"end tell"
|
|
18676
19217
|
]
|
|
@@ -18714,7 +19255,7 @@ function appleScriptString(value) {
|
|
|
18714
19255
|
init_paths();
|
|
18715
19256
|
init_fs();
|
|
18716
19257
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
18717
|
-
import { dirname as dirname12, resolve as
|
|
19258
|
+
import { dirname as dirname12, resolve as resolve40, join as join4 } from "path";
|
|
18718
19259
|
import { realpathSync, readFileSync, mkdirSync } from "fs";
|
|
18719
19260
|
var NPX_PATTERNS = [
|
|
18720
19261
|
{ kind: "npm", re: /\/_npx\/([^/]+)\/node_modules(?:\/|$)/ },
|
|
@@ -18740,7 +19281,7 @@ function normalizeSlashes(p) {
|
|
|
18740
19281
|
}
|
|
18741
19282
|
function detectInstallKind(scriptUrl, opts = {}) {
|
|
18742
19283
|
const realpath3 = opts.realpath ?? realpathSync.native;
|
|
18743
|
-
const
|
|
19284
|
+
const readFile50 = opts.readFile ?? ((p) => readFileSync(p, "utf-8"));
|
|
18744
19285
|
const ua = opts.envUserAgent !== void 0 ? opts.envUserAgent : process.env.npm_config_user_agent ?? "";
|
|
18745
19286
|
const resolved = resolveScriptPath(scriptUrl, realpath3);
|
|
18746
19287
|
if (resolved === null) {
|
|
@@ -18761,7 +19302,7 @@ function detectInstallKind(scriptUrl, opts = {}) {
|
|
|
18761
19302
|
const pkgJsonPath = join4(dir, "package.json");
|
|
18762
19303
|
let raw;
|
|
18763
19304
|
try {
|
|
18764
|
-
raw =
|
|
19305
|
+
raw = readFile50(pkgJsonPath);
|
|
18765
19306
|
} catch {
|
|
18766
19307
|
const parent2 = dirname12(dir);
|
|
18767
19308
|
if (parent2 === dir) break;
|
|
@@ -18793,7 +19334,7 @@ function extractNpxHash(scriptUrl, opts = {}) {
|
|
|
18793
19334
|
return null;
|
|
18794
19335
|
}
|
|
18795
19336
|
function nudgeStampDir() {
|
|
18796
|
-
return
|
|
19337
|
+
return resolve40(syntaurRoot(), "npx-handler-nudge");
|
|
18797
19338
|
}
|
|
18798
19339
|
function sanitizeHash(hash) {
|
|
18799
19340
|
return hash.replace(/[^A-Za-z0-9_-]/g, "_") || "_";
|
|
@@ -18858,7 +19399,8 @@ async function urlCommand(input4, options = {}) {
|
|
|
18858
19399
|
mode: parsed.kind === "session" ? parsed.mode : void 0,
|
|
18859
19400
|
config,
|
|
18860
19401
|
projectsDir: projectsDir2,
|
|
18861
|
-
assignmentsDir: assignmentsDir()
|
|
19402
|
+
assignmentsDir: assignmentsDir(),
|
|
19403
|
+
terminalOverride: parsed.terminal
|
|
18862
19404
|
});
|
|
18863
19405
|
if (plan.fallbackWarning) {
|
|
18864
19406
|
console.error(plan.fallbackWarning);
|
|
@@ -18933,9 +19475,8 @@ function formatInstallUrlHandlerError(err2) {
|
|
|
18933
19475
|
init_config2();
|
|
18934
19476
|
init_paths();
|
|
18935
19477
|
init_fs();
|
|
18936
|
-
import {
|
|
18937
|
-
import {
|
|
18938
|
-
import { readFile as readFile24 } from "fs/promises";
|
|
19478
|
+
import { resolve as resolve42, isAbsolute as isAbsolute7 } from "path";
|
|
19479
|
+
import { readFile as readFile25 } from "fs/promises";
|
|
18939
19480
|
import { select, confirm as confirm2, input as input3 } from "@inquirer/prompts";
|
|
18940
19481
|
async function browseCommand(options) {
|
|
18941
19482
|
const config = await readConfig();
|
|
@@ -19004,7 +19545,7 @@ async function pickAgent2(agents) {
|
|
|
19004
19545
|
return picked;
|
|
19005
19546
|
}
|
|
19006
19547
|
async function ensureWorktree(opts) {
|
|
19007
|
-
const assignmentPath =
|
|
19548
|
+
const assignmentPath = resolve42(
|
|
19008
19549
|
opts.projectsDir,
|
|
19009
19550
|
opts.projectSlug,
|
|
19010
19551
|
"assignments",
|
|
@@ -19014,7 +19555,7 @@ async function ensureWorktree(opts) {
|
|
|
19014
19555
|
if (!await fileExists(assignmentPath)) {
|
|
19015
19556
|
return void 0;
|
|
19016
19557
|
}
|
|
19017
|
-
const content = await
|
|
19558
|
+
const content = await readFile25(assignmentPath, "utf-8");
|
|
19018
19559
|
const { parseAssignmentFrontmatter: parseAssignmentFrontmatter2 } = await Promise.resolve().then(() => (init_frontmatter(), frontmatter_exports));
|
|
19019
19560
|
const fm = parseAssignmentFrontmatter2(content);
|
|
19020
19561
|
const { workspace } = fm;
|
|
@@ -19081,44 +19622,10 @@ async function ensureWorktree(opts) {
|
|
|
19081
19622
|
worktreePath
|
|
19082
19623
|
});
|
|
19083
19624
|
}
|
|
19084
|
-
function computeWorktreeDefaults(opts) {
|
|
19085
|
-
const repository = opts.existing.repository ?? detectCurrentGitRoot();
|
|
19086
|
-
const branch = opts.projectSlug ? `syntaur/${opts.projectSlug}/${opts.assignmentSlug}` : `syntaur/${opts.assignmentSlug}`;
|
|
19087
|
-
const parentBranch = opts.existing.parentBranch ?? detectCurrentBranch() ?? "main";
|
|
19088
|
-
const worktreeBase = repository ? resolve40(repository, ".worktrees", branch) : resolve40(
|
|
19089
|
-
syntaurRoot(),
|
|
19090
|
-
"worktrees",
|
|
19091
|
-
opts.projectSlug || "standalone",
|
|
19092
|
-
opts.assignmentSlug
|
|
19093
|
-
);
|
|
19094
|
-
return {
|
|
19095
|
-
...repository ? { repository } : {},
|
|
19096
|
-
branch,
|
|
19097
|
-
parentBranch,
|
|
19098
|
-
worktreePath: worktreeBase
|
|
19099
|
-
};
|
|
19100
|
-
}
|
|
19101
|
-
function detectCurrentGitRoot() {
|
|
19102
|
-
const result = spawnSync3("git", ["rev-parse", "--show-toplevel"], {
|
|
19103
|
-
encoding: "utf-8"
|
|
19104
|
-
});
|
|
19105
|
-
if (result.status !== 0) return void 0;
|
|
19106
|
-
const out = result.stdout.trim();
|
|
19107
|
-
return out.length > 0 ? out : void 0;
|
|
19108
|
-
}
|
|
19109
|
-
function detectCurrentBranch() {
|
|
19110
|
-
const result = spawnSync3("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
|
|
19111
|
-
encoding: "utf-8"
|
|
19112
|
-
});
|
|
19113
|
-
if (result.status !== 0) return void 0;
|
|
19114
|
-
const out = result.stdout.trim();
|
|
19115
|
-
if (!out || out === "HEAD") return void 0;
|
|
19116
|
-
return out;
|
|
19117
|
-
}
|
|
19118
19625
|
async function runCreate(opts) {
|
|
19119
19626
|
const { createWorktreeAndRecord: createWorktreeAndRecord2, GitWorktreeError: GitWorktreeError2 } = await Promise.resolve().then(() => (init_git_worktree(), git_worktree_exports));
|
|
19120
19627
|
const expandedWorktree = expandHome(opts.worktreePath);
|
|
19121
|
-
const absWorktree =
|
|
19628
|
+
const absWorktree = isAbsolute7(expandedWorktree) ? expandedWorktree : resolve42(expandedWorktree);
|
|
19122
19629
|
try {
|
|
19123
19630
|
await createWorktreeAndRecord2({
|
|
19124
19631
|
assignmentPath: opts.assignmentPath,
|
|
@@ -19147,7 +19654,7 @@ init_paths();
|
|
|
19147
19654
|
init_fs();
|
|
19148
19655
|
init_playbook();
|
|
19149
19656
|
init_playbooks();
|
|
19150
|
-
import { resolve as
|
|
19657
|
+
import { resolve as resolve43 } from "path";
|
|
19151
19658
|
async function createPlaybookCommand(name, options) {
|
|
19152
19659
|
if (!name.trim()) {
|
|
19153
19660
|
throw new Error("Playbook name cannot be empty.");
|
|
@@ -19160,7 +19667,7 @@ async function createPlaybookCommand(name, options) {
|
|
|
19160
19667
|
}
|
|
19161
19668
|
const dir = playbooksDir();
|
|
19162
19669
|
await ensureDir(dir);
|
|
19163
|
-
const filePath =
|
|
19670
|
+
const filePath = resolve43(dir, `${slug}.md`);
|
|
19164
19671
|
if (await fileExists(filePath)) {
|
|
19165
19672
|
throw new Error(
|
|
19166
19673
|
`Playbook "${slug}" already exists at ${filePath}
|
|
@@ -19182,8 +19689,8 @@ init_paths();
|
|
|
19182
19689
|
init_fs();
|
|
19183
19690
|
init_parser();
|
|
19184
19691
|
init_config2();
|
|
19185
|
-
import { readdir as
|
|
19186
|
-
import { resolve as
|
|
19692
|
+
import { readdir as readdir15, readFile as readFile26 } from "fs/promises";
|
|
19693
|
+
import { resolve as resolve44 } from "path";
|
|
19187
19694
|
async function listPlaybooksCommand(options = {}) {
|
|
19188
19695
|
const dir = playbooksDir();
|
|
19189
19696
|
if (!await fileExists(dir)) {
|
|
@@ -19192,14 +19699,14 @@ async function listPlaybooksCommand(options = {}) {
|
|
|
19192
19699
|
}
|
|
19193
19700
|
const config = await readConfig();
|
|
19194
19701
|
const disabledSet = new Set(config.playbooks.disabled);
|
|
19195
|
-
const entries = await
|
|
19702
|
+
const entries = await readdir15(dir, { withFileTypes: true });
|
|
19196
19703
|
const mdFiles = entries.filter(
|
|
19197
19704
|
(e) => e.isFile() && e.name.endsWith(".md") && !e.name.startsWith("_") && e.name !== "manifest.md"
|
|
19198
19705
|
);
|
|
19199
19706
|
const rows = [];
|
|
19200
19707
|
for (const entry of mdFiles) {
|
|
19201
|
-
const filePath =
|
|
19202
|
-
const raw = await
|
|
19708
|
+
const filePath = resolve44(dir, entry.name);
|
|
19709
|
+
const raw = await readFile26(filePath, "utf-8");
|
|
19203
19710
|
const parsed = parsePlaybook(raw);
|
|
19204
19711
|
const slug = parsed.slug || entry.name.replace(/\.md$/, "");
|
|
19205
19712
|
const disabled = disabledSet.has(slug);
|
|
@@ -19322,8 +19829,8 @@ init_fs();
|
|
|
19322
19829
|
init_config2();
|
|
19323
19830
|
init_slug();
|
|
19324
19831
|
import { Command } from "commander";
|
|
19325
|
-
import { readFile as
|
|
19326
|
-
import { resolve as
|
|
19832
|
+
import { readFile as readFile27 } from "fs/promises";
|
|
19833
|
+
import { resolve as resolve45 } from "path";
|
|
19327
19834
|
var WORKSPACE_REGEX3 = /^[a-z0-9_][a-z0-9-]*$/;
|
|
19328
19835
|
async function resolveScope(options) {
|
|
19329
19836
|
const flagCount = [Boolean(options.project), Boolean(options.workspace), Boolean(options.global)].filter(Boolean).length;
|
|
@@ -19335,7 +19842,7 @@ async function resolveScope(options) {
|
|
|
19335
19842
|
throw new Error(`Invalid project slug: "${options.project}".`);
|
|
19336
19843
|
}
|
|
19337
19844
|
const config = await readConfig();
|
|
19338
|
-
const projectMd =
|
|
19845
|
+
const projectMd = resolve45(config.defaultProjectDir, options.project, "project.md");
|
|
19339
19846
|
if (!await fileExists(projectMd)) {
|
|
19340
19847
|
throw new Error(`Project "${options.project}" not found.`);
|
|
19341
19848
|
}
|
|
@@ -19656,10 +20163,10 @@ todoCommand.command("archive").description("Archive completed todos and their lo
|
|
|
19656
20163
|
(e) => e.itemIds.every((id) => completedIds.has(id))
|
|
19657
20164
|
);
|
|
19658
20165
|
const archFile = archivePath(todosPath, workspace, checklist.archiveInterval);
|
|
19659
|
-
await ensureDir(
|
|
20166
|
+
await ensureDir(resolve45(todosPath, "archive"));
|
|
19660
20167
|
let archContent = "";
|
|
19661
20168
|
if (await fileExists(archFile)) {
|
|
19662
|
-
archContent = await
|
|
20169
|
+
archContent = await readFile27(archFile, "utf-8");
|
|
19663
20170
|
archContent = archContent.trimEnd() + "\n\n";
|
|
19664
20171
|
} else {
|
|
19665
20172
|
archContent = `---
|
|
@@ -19831,12 +20338,12 @@ function describeScope(scope) {
|
|
|
19831
20338
|
}
|
|
19832
20339
|
async function injectPromotedTodos(assignmentDir, todos, scopeLabel) {
|
|
19833
20340
|
const { resolve: resolvePath2 } = await import("path");
|
|
19834
|
-
const { readFile:
|
|
20341
|
+
const { readFile: readFile50 } = await import("fs/promises");
|
|
19835
20342
|
const { writeFileForce: writeFileForce2 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
|
|
19836
20343
|
const { appendTodosToAssignmentBody: appendTodosToAssignmentBody2, touchAssignmentUpdated: touchAssignmentUpdated2 } = await Promise.resolve().then(() => (init_assignment_todos(), assignment_todos_exports));
|
|
19837
20344
|
const { nowTimestamp: nowTimestamp3 } = await Promise.resolve().then(() => (init_timestamp(), timestamp_exports));
|
|
19838
20345
|
const assignmentMdPath2 = resolvePath2(assignmentDir, "assignment.md");
|
|
19839
|
-
let content = await
|
|
20346
|
+
let content = await readFile50(assignmentMdPath2, "utf-8");
|
|
19840
20347
|
content = appendTodosToAssignmentBody2(
|
|
19841
20348
|
content,
|
|
19842
20349
|
todos.map((t) => ({
|
|
@@ -19881,13 +20388,13 @@ todoCommand.command("plan").description("Create or open a plan directory for a t
|
|
|
19881
20388
|
}
|
|
19882
20389
|
const planDir = todoPlanDir(todosPath, workspace, id);
|
|
19883
20390
|
await ensureDir(planDir);
|
|
19884
|
-
const { readdir:
|
|
19885
|
-
const existingFiles = (await
|
|
20391
|
+
const { readdir: readdir25 } = await import("fs/promises");
|
|
20392
|
+
const existingFiles = (await readdir25(planDir).catch(() => [])).filter(
|
|
19886
20393
|
(f) => /^plan(?:-v\d+)?\.md$/.test(f)
|
|
19887
20394
|
);
|
|
19888
20395
|
let target;
|
|
19889
20396
|
if (existingFiles.length === 0) {
|
|
19890
|
-
target =
|
|
20397
|
+
target = resolve45(planDir, "plan.md");
|
|
19891
20398
|
} else {
|
|
19892
20399
|
const versions = /* @__PURE__ */ new Set();
|
|
19893
20400
|
for (const f of existingFiles) {
|
|
@@ -19897,7 +20404,7 @@ todoCommand.command("plan").description("Create or open a plan directory for a t
|
|
|
19897
20404
|
}
|
|
19898
20405
|
let n = 2;
|
|
19899
20406
|
while (versions.has(n)) n++;
|
|
19900
|
-
target =
|
|
20407
|
+
target = resolve45(planDir, `plan-v${n}.md`);
|
|
19901
20408
|
}
|
|
19902
20409
|
if (!await fileExists(target)) {
|
|
19903
20410
|
const stub = `---
|
|
@@ -20086,12 +20593,12 @@ backupCommand.command("config").description("Show or update backup configuration
|
|
|
20086
20593
|
|
|
20087
20594
|
// src/commands/doctor.ts
|
|
20088
20595
|
import { Command as Command3 } from "commander";
|
|
20089
|
-
import { readFile as
|
|
20090
|
-
import { isAbsolute as
|
|
20596
|
+
import { readFile as readFile35 } from "fs/promises";
|
|
20597
|
+
import { isAbsolute as isAbsolute10, resolve as resolve56 } from "path";
|
|
20091
20598
|
|
|
20092
20599
|
// src/utils/doctor/index.ts
|
|
20093
20600
|
import { fileURLToPath as fileURLToPath8 } from "url";
|
|
20094
|
-
import { readFile as
|
|
20601
|
+
import { readFile as readFile34 } from "fs/promises";
|
|
20095
20602
|
import { dirname as dirname16, join as join8 } from "path";
|
|
20096
20603
|
|
|
20097
20604
|
// src/utils/doctor/context.ts
|
|
@@ -20099,11 +20606,11 @@ init_config2();
|
|
|
20099
20606
|
init_paths();
|
|
20100
20607
|
init_fs();
|
|
20101
20608
|
import Database3 from "better-sqlite3";
|
|
20102
|
-
import { resolve as
|
|
20609
|
+
import { resolve as resolve46 } from "path";
|
|
20103
20610
|
async function buildCheckContext(cwd = process.cwd()) {
|
|
20104
20611
|
const config = await readConfig();
|
|
20105
20612
|
const root = syntaurRoot();
|
|
20106
|
-
const dbPath =
|
|
20613
|
+
const dbPath = resolve46(root, "syntaur.db");
|
|
20107
20614
|
let db4 = null;
|
|
20108
20615
|
let dbError = null;
|
|
20109
20616
|
if (await fileExists(dbPath)) {
|
|
@@ -20137,8 +20644,8 @@ function closeCheckContext(ctx) {
|
|
|
20137
20644
|
// src/utils/doctor/checks/env.ts
|
|
20138
20645
|
init_fs();
|
|
20139
20646
|
init_paths();
|
|
20140
|
-
import { resolve as
|
|
20141
|
-
import { readFile as
|
|
20647
|
+
import { resolve as resolve47, isAbsolute as isAbsolute8 } from "path";
|
|
20648
|
+
import { readFile as readFile28, stat as stat3 } from "fs/promises";
|
|
20142
20649
|
import { fileURLToPath as fileURLToPath7 } from "url";
|
|
20143
20650
|
import { dirname as dirname14, join as join6 } from "path";
|
|
20144
20651
|
var CATEGORY = "env";
|
|
@@ -20178,7 +20685,7 @@ var configValid = {
|
|
|
20178
20685
|
category: CATEGORY,
|
|
20179
20686
|
title: "~/.syntaur/config.md is valid",
|
|
20180
20687
|
async run(ctx) {
|
|
20181
|
-
const configPath =
|
|
20688
|
+
const configPath = resolve47(ctx.syntaurRoot, "config.md");
|
|
20182
20689
|
if (!await fileExists(configPath)) {
|
|
20183
20690
|
return {
|
|
20184
20691
|
id: this.id,
|
|
@@ -20195,7 +20702,7 @@ var configValid = {
|
|
|
20195
20702
|
autoFixable: false
|
|
20196
20703
|
};
|
|
20197
20704
|
}
|
|
20198
|
-
const content = await
|
|
20705
|
+
const content = await readFile28(configPath, "utf-8");
|
|
20199
20706
|
const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
20200
20707
|
if (!fmMatch || fmMatch[1].trim() === "") {
|
|
20201
20708
|
return {
|
|
@@ -20232,7 +20739,7 @@ var configValid = {
|
|
|
20232
20739
|
};
|
|
20233
20740
|
}
|
|
20234
20741
|
const expanded = expandHome(rawProjectDir);
|
|
20235
|
-
if (!
|
|
20742
|
+
if (!isAbsolute8(expanded)) {
|
|
20236
20743
|
return {
|
|
20237
20744
|
id: this.id,
|
|
20238
20745
|
category: this.category,
|
|
@@ -20475,7 +20982,7 @@ async function readLocalPkg() {
|
|
|
20475
20982
|
for (let i = 0; i < 6; i++) {
|
|
20476
20983
|
const candidate = join6(dir, "package.json");
|
|
20477
20984
|
try {
|
|
20478
|
-
const text = await
|
|
20985
|
+
const text = await readFile28(candidate, "utf-8");
|
|
20479
20986
|
return JSON.parse(text);
|
|
20480
20987
|
} catch {
|
|
20481
20988
|
dir = dirname14(dir);
|
|
@@ -20527,8 +21034,8 @@ function versionGte(a, b) {
|
|
|
20527
21034
|
|
|
20528
21035
|
// src/utils/doctor/checks/structure.ts
|
|
20529
21036
|
init_fs();
|
|
20530
|
-
import { resolve as
|
|
20531
|
-
import { readdir as
|
|
21037
|
+
import { resolve as resolve48 } from "path";
|
|
21038
|
+
import { readdir as readdir16, stat as stat4 } from "fs/promises";
|
|
20532
21039
|
var CATEGORY2 = "structure";
|
|
20533
21040
|
var KNOWN_TOP_LEVEL = /* @__PURE__ */ new Set([
|
|
20534
21041
|
"projects",
|
|
@@ -20547,7 +21054,7 @@ var projectsDir = {
|
|
|
20547
21054
|
category: CATEGORY2,
|
|
20548
21055
|
title: "projects/ directory exists",
|
|
20549
21056
|
async run(ctx) {
|
|
20550
|
-
const p =
|
|
21057
|
+
const p = resolve48(ctx.syntaurRoot, "projects");
|
|
20551
21058
|
if (!await fileExists(p)) {
|
|
20552
21059
|
return {
|
|
20553
21060
|
id: this.id,
|
|
@@ -20572,7 +21079,7 @@ var playbooksDir2 = {
|
|
|
20572
21079
|
category: CATEGORY2,
|
|
20573
21080
|
title: "playbooks/ directory exists",
|
|
20574
21081
|
async run(ctx) {
|
|
20575
|
-
const p =
|
|
21082
|
+
const p = resolve48(ctx.syntaurRoot, "playbooks");
|
|
20576
21083
|
if (!await fileExists(p)) {
|
|
20577
21084
|
return {
|
|
20578
21085
|
id: this.id,
|
|
@@ -20597,7 +21104,7 @@ var todosDirValid = {
|
|
|
20597
21104
|
category: CATEGORY2,
|
|
20598
21105
|
title: "todos/ directory is readable (if present)",
|
|
20599
21106
|
async run(ctx) {
|
|
20600
|
-
const p =
|
|
21107
|
+
const p = resolve48(ctx.syntaurRoot, "todos");
|
|
20601
21108
|
if (!await fileExists(p)) {
|
|
20602
21109
|
return {
|
|
20603
21110
|
id: this.id,
|
|
@@ -20628,7 +21135,7 @@ var serversDirValid = {
|
|
|
20628
21135
|
category: CATEGORY2,
|
|
20629
21136
|
title: "servers/ directory is readable (if present)",
|
|
20630
21137
|
async run(ctx) {
|
|
20631
|
-
const p =
|
|
21138
|
+
const p = resolve48(ctx.syntaurRoot, "servers");
|
|
20632
21139
|
if (!await fileExists(p)) {
|
|
20633
21140
|
return {
|
|
20634
21141
|
id: this.id,
|
|
@@ -20659,7 +21166,7 @@ var knownFilesRecognized = {
|
|
|
20659
21166
|
category: CATEGORY2,
|
|
20660
21167
|
title: "No unexpected top-level entries under ~/.syntaur/",
|
|
20661
21168
|
async run(ctx) {
|
|
20662
|
-
const entries = await
|
|
21169
|
+
const entries = await readdir16(ctx.syntaurRoot, { withFileTypes: true });
|
|
20663
21170
|
const unexpected = [];
|
|
20664
21171
|
for (const e of entries) {
|
|
20665
21172
|
if (e.name.startsWith(".")) continue;
|
|
@@ -20673,7 +21180,7 @@ var knownFilesRecognized = {
|
|
|
20673
21180
|
title: this.title,
|
|
20674
21181
|
status: "warn",
|
|
20675
21182
|
detail: `unexpected top-level entries: ${unexpected.join(", ")}`,
|
|
20676
|
-
affected: unexpected.map((n) =>
|
|
21183
|
+
affected: unexpected.map((n) => resolve48(ctx.syntaurRoot, n)),
|
|
20677
21184
|
remediation: {
|
|
20678
21185
|
kind: "manual",
|
|
20679
21186
|
suggestion: "Review these entries \u2014 they may be leftover state from older versions",
|
|
@@ -20702,8 +21209,8 @@ function pass2(check) {
|
|
|
20702
21209
|
|
|
20703
21210
|
// src/utils/doctor/checks/project.ts
|
|
20704
21211
|
init_fs();
|
|
20705
|
-
import { resolve as
|
|
20706
|
-
import { readdir as
|
|
21212
|
+
import { resolve as resolve49 } from "path";
|
|
21213
|
+
import { readdir as readdir17, stat as stat5 } from "fs/promises";
|
|
20707
21214
|
var CATEGORY3 = "project";
|
|
20708
21215
|
var REQUIRED_PROJECT_FILES = [
|
|
20709
21216
|
"project.md",
|
|
@@ -20727,15 +21234,15 @@ var PROJECT_MARKERS = ["project.md", "manifest.md", "assignments"];
|
|
|
20727
21234
|
async function listProjects2(ctx) {
|
|
20728
21235
|
const dir = ctx.config.defaultProjectDir;
|
|
20729
21236
|
if (!await fileExists(dir)) return [];
|
|
20730
|
-
const entries = await
|
|
21237
|
+
const entries = await readdir17(dir, { withFileTypes: true });
|
|
20731
21238
|
const result = [];
|
|
20732
21239
|
for (const e of entries) {
|
|
20733
21240
|
if (!e.isDirectory()) continue;
|
|
20734
21241
|
if (e.name.startsWith(".") || e.name.startsWith("_")) continue;
|
|
20735
|
-
const projectDir =
|
|
21242
|
+
const projectDir = resolve49(dir, e.name);
|
|
20736
21243
|
let looksLikeProject = false;
|
|
20737
21244
|
for (const marker of PROJECT_MARKERS) {
|
|
20738
|
-
if (await fileExists(
|
|
21245
|
+
if (await fileExists(resolve49(projectDir, marker))) {
|
|
20739
21246
|
looksLikeProject = true;
|
|
20740
21247
|
break;
|
|
20741
21248
|
}
|
|
@@ -20754,7 +21261,7 @@ var requiredFiles = {
|
|
|
20754
21261
|
for (const projectDir of projects) {
|
|
20755
21262
|
const missing = [];
|
|
20756
21263
|
for (const rel of REQUIRED_PROJECT_FILES) {
|
|
20757
|
-
const p =
|
|
21264
|
+
const p = resolve49(projectDir, rel);
|
|
20758
21265
|
if (!await fileExists(p)) missing.push(rel);
|
|
20759
21266
|
}
|
|
20760
21267
|
if (missing.length === 0) continue;
|
|
@@ -20764,7 +21271,7 @@ var requiredFiles = {
|
|
|
20764
21271
|
title: this.title,
|
|
20765
21272
|
status: "error",
|
|
20766
21273
|
detail: `project at ${projectDir} is missing: ${missing.join(", ")}`,
|
|
20767
|
-
affected: missing.map((m) =>
|
|
21274
|
+
affected: missing.map((m) => resolve49(projectDir, m)),
|
|
20768
21275
|
remediation: {
|
|
20769
21276
|
kind: "manual",
|
|
20770
21277
|
suggestion: "Recreate the missing scaffold files from templates",
|
|
@@ -20787,7 +21294,7 @@ var manifestStale = {
|
|
|
20787
21294
|
const projects = await listProjects2(ctx);
|
|
20788
21295
|
const results = [];
|
|
20789
21296
|
for (const projectDir of projects) {
|
|
20790
|
-
const manifestPath =
|
|
21297
|
+
const manifestPath = resolve49(projectDir, "manifest.md");
|
|
20791
21298
|
if (!await fileExists(manifestPath)) continue;
|
|
20792
21299
|
const manifestMtime = (await stat5(manifestPath)).mtimeMs;
|
|
20793
21300
|
const newestAssignment = await newestAssignmentMtime(projectDir);
|
|
@@ -20821,7 +21328,7 @@ var orphanFiles = {
|
|
|
20821
21328
|
const projects = await listProjects2(ctx);
|
|
20822
21329
|
const results = [];
|
|
20823
21330
|
for (const projectDir of projects) {
|
|
20824
|
-
const entries = await
|
|
21331
|
+
const entries = await readdir17(projectDir, { withFileTypes: true });
|
|
20825
21332
|
const orphans = [];
|
|
20826
21333
|
for (const e of entries) {
|
|
20827
21334
|
if (e.name.startsWith(".")) continue;
|
|
@@ -20836,7 +21343,7 @@ var orphanFiles = {
|
|
|
20836
21343
|
title: this.title,
|
|
20837
21344
|
status: "warn",
|
|
20838
21345
|
detail: `project at ${projectDir} has unexpected entries: ${orphans.join(", ")}`,
|
|
20839
|
-
affected: orphans.map((o) =>
|
|
21346
|
+
affected: orphans.map((o) => resolve49(projectDir, o)),
|
|
20840
21347
|
autoFixable: false
|
|
20841
21348
|
});
|
|
20842
21349
|
}
|
|
@@ -20846,18 +21353,18 @@ var orphanFiles = {
|
|
|
20846
21353
|
};
|
|
20847
21354
|
var projectChecks = [requiredFiles, manifestStale, orphanFiles];
|
|
20848
21355
|
async function newestAssignmentMtime(projectDir) {
|
|
20849
|
-
const assignmentsRoot =
|
|
21356
|
+
const assignmentsRoot = resolve49(projectDir, "assignments");
|
|
20850
21357
|
if (!await fileExists(assignmentsRoot)) return 0;
|
|
20851
21358
|
let newest = 0;
|
|
20852
21359
|
let entries;
|
|
20853
21360
|
try {
|
|
20854
|
-
entries = await
|
|
21361
|
+
entries = await readdir17(assignmentsRoot, { withFileTypes: true });
|
|
20855
21362
|
} catch {
|
|
20856
21363
|
return 0;
|
|
20857
21364
|
}
|
|
20858
21365
|
for (const e of entries) {
|
|
20859
21366
|
if (!e.isDirectory()) continue;
|
|
20860
|
-
const assignmentMd =
|
|
21367
|
+
const assignmentMd = resolve49(assignmentsRoot, e.name, "assignment.md");
|
|
20861
21368
|
try {
|
|
20862
21369
|
const s = await stat5(assignmentMd);
|
|
20863
21370
|
if (s.mtimeMs > newest) newest = s.mtimeMs;
|
|
@@ -20881,8 +21388,8 @@ init_fs();
|
|
|
20881
21388
|
init_parser();
|
|
20882
21389
|
init_types();
|
|
20883
21390
|
init_paths();
|
|
20884
|
-
import { resolve as
|
|
20885
|
-
import { readdir as
|
|
21391
|
+
import { resolve as resolve50 } from "path";
|
|
21392
|
+
import { readdir as readdir18, readFile as readFile29 } from "fs/promises";
|
|
20886
21393
|
var CATEGORY4 = "assignment";
|
|
20887
21394
|
var STATUSES_REQUIRING_HANDOFF = /* @__PURE__ */ new Set(["review", "completed"]);
|
|
20888
21395
|
var PRE_WORKSPACE_STATUSES = /* @__PURE__ */ new Set([
|
|
@@ -20916,20 +21423,20 @@ async function listAssignments(ctx) {
|
|
|
20916
21423
|
const result = { withAssignmentMd: [], orphanFolders: [] };
|
|
20917
21424
|
const projectsDir2 = ctx.config.defaultProjectDir;
|
|
20918
21425
|
if (await fileExists(projectsDir2)) {
|
|
20919
|
-
const projects = await
|
|
21426
|
+
const projects = await readdir18(projectsDir2, { withFileTypes: true });
|
|
20920
21427
|
for (const m of projects) {
|
|
20921
21428
|
if (!m.isDirectory()) continue;
|
|
20922
21429
|
if (m.name.startsWith(".") || m.name.startsWith("_")) continue;
|
|
20923
|
-
const assignmentsDir2 =
|
|
21430
|
+
const assignmentsDir2 = resolve50(projectsDir2, m.name, "assignments");
|
|
20924
21431
|
if (!await fileExists(assignmentsDir2)) continue;
|
|
20925
|
-
const entries = await
|
|
21432
|
+
const entries = await readdir18(assignmentsDir2, { withFileTypes: true });
|
|
20926
21433
|
for (const a of entries) {
|
|
20927
21434
|
if (!a.isDirectory()) continue;
|
|
20928
21435
|
if (a.name.startsWith(".") || a.name.startsWith("_")) continue;
|
|
20929
|
-
const assignmentDir =
|
|
20930
|
-
const assignmentMd =
|
|
21436
|
+
const assignmentDir = resolve50(assignmentsDir2, a.name);
|
|
21437
|
+
const assignmentMd = resolve50(assignmentDir, "assignment.md");
|
|
20931
21438
|
const entry = {
|
|
20932
|
-
projectDir:
|
|
21439
|
+
projectDir: resolve50(projectsDir2, m.name),
|
|
20933
21440
|
projectSlug: m.name,
|
|
20934
21441
|
assignmentDir,
|
|
20935
21442
|
assignmentSlug: a.name,
|
|
@@ -20945,12 +21452,12 @@ async function listAssignments(ctx) {
|
|
|
20945
21452
|
}
|
|
20946
21453
|
const standaloneRoot = assignmentsDir();
|
|
20947
21454
|
if (await fileExists(standaloneRoot)) {
|
|
20948
|
-
const entries = await
|
|
21455
|
+
const entries = await readdir18(standaloneRoot, { withFileTypes: true });
|
|
20949
21456
|
for (const a of entries) {
|
|
20950
21457
|
if (!a.isDirectory()) continue;
|
|
20951
21458
|
if (a.name.startsWith(".") || a.name.startsWith("_")) continue;
|
|
20952
|
-
const assignmentDir =
|
|
20953
|
-
const assignmentMd =
|
|
21459
|
+
const assignmentDir = resolve50(standaloneRoot, a.name);
|
|
21460
|
+
const assignmentMd = resolve50(assignmentDir, "assignment.md");
|
|
20954
21461
|
const entry = {
|
|
20955
21462
|
projectDir: standaloneRoot,
|
|
20956
21463
|
projectSlug: null,
|
|
@@ -21028,7 +21535,7 @@ var invalidStatus = {
|
|
|
21028
21535
|
const allowed = configuredStatuses(ctx);
|
|
21029
21536
|
const results = [];
|
|
21030
21537
|
for (const a of withAssignmentMd) {
|
|
21031
|
-
const path =
|
|
21538
|
+
const path = resolve50(a.assignmentDir, "assignment.md");
|
|
21032
21539
|
const parsed = await parseSafe2(path);
|
|
21033
21540
|
if (!parsed) continue;
|
|
21034
21541
|
if (!allowed.has(parsed.status)) {
|
|
@@ -21061,7 +21568,7 @@ var workspaceMissing = {
|
|
|
21061
21568
|
const terminal = terminalStatuses(ctx);
|
|
21062
21569
|
const results = [];
|
|
21063
21570
|
for (const a of withAssignmentMd) {
|
|
21064
|
-
const path =
|
|
21571
|
+
const path = resolve50(a.assignmentDir, "assignment.md");
|
|
21065
21572
|
const parsed = await parseSafe2(path);
|
|
21066
21573
|
if (!parsed) continue;
|
|
21067
21574
|
if (terminal.has(parsed.status)) continue;
|
|
@@ -21108,12 +21615,12 @@ var requiredFilesByStatus = {
|
|
|
21108
21615
|
const { withAssignmentMd } = await listAssignments(ctx);
|
|
21109
21616
|
const results = [];
|
|
21110
21617
|
for (const a of withAssignmentMd) {
|
|
21111
|
-
const assignmentPath =
|
|
21618
|
+
const assignmentPath = resolve50(a.assignmentDir, "assignment.md");
|
|
21112
21619
|
const parsed = await parseSafe2(assignmentPath);
|
|
21113
21620
|
if (!parsed) continue;
|
|
21114
21621
|
const missing = [];
|
|
21115
21622
|
if (STATUSES_REQUIRING_HANDOFF.has(parsed.status)) {
|
|
21116
|
-
const handoffPath =
|
|
21623
|
+
const handoffPath = resolve50(a.assignmentDir, "handoff.md");
|
|
21117
21624
|
if (!await fileExists(handoffPath)) missing.push("handoff.md");
|
|
21118
21625
|
}
|
|
21119
21626
|
if (missing.length === 0) continue;
|
|
@@ -21123,7 +21630,7 @@ var requiredFilesByStatus = {
|
|
|
21123
21630
|
title: this.title,
|
|
21124
21631
|
status: "warn",
|
|
21125
21632
|
detail: `${a.projectSlug}/${a.assignmentSlug} (status: ${parsed.status}) is missing ${missing.join(", ")}`,
|
|
21126
|
-
affected: missing.map((m) =>
|
|
21633
|
+
affected: missing.map((m) => resolve50(a.assignmentDir, m)),
|
|
21127
21634
|
remediation: {
|
|
21128
21635
|
kind: "manual",
|
|
21129
21636
|
suggestion: `Create the missing ${missing.join(" and ")} files for this assignment`,
|
|
@@ -21146,7 +21653,7 @@ var companionFilesScaffolded = {
|
|
|
21146
21653
|
for (const a of withAssignmentMd) {
|
|
21147
21654
|
const missing = [];
|
|
21148
21655
|
for (const filename of ["progress.md", "comments.md"]) {
|
|
21149
|
-
if (!await fileExists(
|
|
21656
|
+
if (!await fileExists(resolve50(a.assignmentDir, filename))) {
|
|
21150
21657
|
missing.push(filename);
|
|
21151
21658
|
}
|
|
21152
21659
|
}
|
|
@@ -21158,7 +21665,7 @@ var companionFilesScaffolded = {
|
|
|
21158
21665
|
title: this.title,
|
|
21159
21666
|
status: "warn",
|
|
21160
21667
|
detail: `${label} is missing ${missing.join(" and ")} (pre-v2.0 assignment \u2014 not required, but scaffolding them keeps the dashboard and CLIs consistent)`,
|
|
21161
|
-
affected: missing.map((m) =>
|
|
21668
|
+
affected: missing.map((m) => resolve50(a.assignmentDir, m)),
|
|
21162
21669
|
remediation: {
|
|
21163
21670
|
kind: "manual",
|
|
21164
21671
|
suggestion: `Create ${missing.join(" and ")} with the renderProgress/renderComments templates, or re-scaffold via the CLI`,
|
|
@@ -21191,7 +21698,7 @@ var typeDefinition = {
|
|
|
21191
21698
|
const { withAssignmentMd } = await listAssignments(ctx);
|
|
21192
21699
|
const results = [];
|
|
21193
21700
|
for (const a of withAssignmentMd) {
|
|
21194
|
-
const path =
|
|
21701
|
+
const path = resolve50(a.assignmentDir, "assignment.md");
|
|
21195
21702
|
const parsed = await parseSafe2(path);
|
|
21196
21703
|
if (!parsed) continue;
|
|
21197
21704
|
if (!parsed.type) continue;
|
|
@@ -21225,7 +21732,7 @@ var projectFrontmatterMatchesContainer = {
|
|
|
21225
21732
|
const { withAssignmentMd } = await listAssignments(ctx);
|
|
21226
21733
|
const results = [];
|
|
21227
21734
|
for (const a of withAssignmentMd) {
|
|
21228
|
-
const path =
|
|
21735
|
+
const path = resolve50(a.assignmentDir, "assignment.md");
|
|
21229
21736
|
const parsed = await parseSafe2(path);
|
|
21230
21737
|
if (!parsed) continue;
|
|
21231
21738
|
if (a.standalone) {
|
|
@@ -21276,13 +21783,13 @@ var draftMissingObjective = {
|
|
|
21276
21783
|
const { withAssignmentMd } = await listAssignments(ctx);
|
|
21277
21784
|
const results = [];
|
|
21278
21785
|
for (const a of withAssignmentMd) {
|
|
21279
|
-
const path =
|
|
21786
|
+
const path = resolve50(a.assignmentDir, "assignment.md");
|
|
21280
21787
|
const parsed = await parseSafe2(path);
|
|
21281
21788
|
if (!parsed) continue;
|
|
21282
21789
|
if (parsed.status !== "draft") continue;
|
|
21283
21790
|
let raw;
|
|
21284
21791
|
try {
|
|
21285
|
-
raw = await
|
|
21792
|
+
raw = await readFile29(path, "utf-8");
|
|
21286
21793
|
} catch {
|
|
21287
21794
|
continue;
|
|
21288
21795
|
}
|
|
@@ -21315,16 +21822,16 @@ var readyToImplementMissingPlan = {
|
|
|
21315
21822
|
const { withAssignmentMd } = await listAssignments(ctx);
|
|
21316
21823
|
const results = [];
|
|
21317
21824
|
for (const a of withAssignmentMd) {
|
|
21318
|
-
const path =
|
|
21825
|
+
const path = resolve50(a.assignmentDir, "assignment.md");
|
|
21319
21826
|
const parsed = await parseSafe2(path);
|
|
21320
21827
|
if (!parsed) continue;
|
|
21321
21828
|
if (parsed.status !== "ready_to_implement") continue;
|
|
21322
|
-
const entries = await
|
|
21829
|
+
const entries = await readdir18(a.assignmentDir).catch(() => []);
|
|
21323
21830
|
const planFiles = entries.filter((f) => /^plan(?:-v\d+)?\.md$/i.test(f));
|
|
21324
21831
|
let hasPlanContent = false;
|
|
21325
21832
|
for (const f of planFiles) {
|
|
21326
21833
|
try {
|
|
21327
|
-
const c2 = await
|
|
21834
|
+
const c2 = await readFile29(resolve50(a.assignmentDir, f), "utf-8");
|
|
21328
21835
|
if (c2.trim().length > 0) {
|
|
21329
21836
|
hasPlanContent = true;
|
|
21330
21837
|
break;
|
|
@@ -21340,7 +21847,7 @@ var readyToImplementMissingPlan = {
|
|
|
21340
21847
|
title: this.title,
|
|
21341
21848
|
status: "warn",
|
|
21342
21849
|
detail: `${label} (status: ready_to_implement) has no plan.md or plan-v<N>.md`,
|
|
21343
|
-
affected: [
|
|
21850
|
+
affected: [resolve50(a.assignmentDir, "plan.md")],
|
|
21344
21851
|
remediation: {
|
|
21345
21852
|
kind: "manual",
|
|
21346
21853
|
suggestion: `Write a plan with '/plan-assignment' (or 'syntaur plan'), then re-mark ready_to_implement`,
|
|
@@ -21367,7 +21874,7 @@ var assignmentChecks = [
|
|
|
21367
21874
|
];
|
|
21368
21875
|
async function parseSafe2(path) {
|
|
21369
21876
|
try {
|
|
21370
|
-
const content = await
|
|
21877
|
+
const content = await readFile29(path, "utf-8");
|
|
21371
21878
|
return parseAssignmentFull(content);
|
|
21372
21879
|
} catch {
|
|
21373
21880
|
return null;
|
|
@@ -21386,7 +21893,7 @@ function pass4(check, detail) {
|
|
|
21386
21893
|
|
|
21387
21894
|
// src/utils/doctor/checks/dashboard.ts
|
|
21388
21895
|
init_fs();
|
|
21389
|
-
import { resolve as
|
|
21896
|
+
import { resolve as resolve51 } from "path";
|
|
21390
21897
|
var CATEGORY5 = "dashboard";
|
|
21391
21898
|
var dbReachable = {
|
|
21392
21899
|
id: "dashboard.db-reachable",
|
|
@@ -21400,7 +21907,7 @@ var dbReachable = {
|
|
|
21400
21907
|
title: this.title,
|
|
21401
21908
|
status: "error",
|
|
21402
21909
|
detail: `could not open syntaur.db: ${ctx.dbError ?? "unknown error"}`,
|
|
21403
|
-
affected: [
|
|
21910
|
+
affected: [resolve51(ctx.syntaurRoot, "syntaur.db")],
|
|
21404
21911
|
remediation: {
|
|
21405
21912
|
kind: "manual",
|
|
21406
21913
|
suggestion: "Start the dashboard once (`syntaur dashboard`) to initialize the DB, or restore it from backup",
|
|
@@ -21418,7 +21925,7 @@ var dbReachable = {
|
|
|
21418
21925
|
title: this.title,
|
|
21419
21926
|
status: "error",
|
|
21420
21927
|
detail: 'syntaur.db is missing the expected "sessions" table',
|
|
21421
|
-
affected: [
|
|
21928
|
+
affected: [resolve51(ctx.syntaurRoot, "syntaur.db")],
|
|
21422
21929
|
autoFixable: false
|
|
21423
21930
|
};
|
|
21424
21931
|
}
|
|
@@ -21430,7 +21937,7 @@ var dbReachable = {
|
|
|
21430
21937
|
title: this.title,
|
|
21431
21938
|
status: "error",
|
|
21432
21939
|
detail: `syntaur.db query failed: ${err2 instanceof Error ? err2.message : String(err2)}`,
|
|
21433
|
-
affected: [
|
|
21940
|
+
affected: [resolve51(ctx.syntaurRoot, "syntaur.db")],
|
|
21434
21941
|
autoFixable: false
|
|
21435
21942
|
};
|
|
21436
21943
|
}
|
|
@@ -21456,7 +21963,7 @@ var ghostSessions = {
|
|
|
21456
21963
|
const results = [];
|
|
21457
21964
|
for (const row of rows) {
|
|
21458
21965
|
if (!row.project_slug) continue;
|
|
21459
|
-
const projectPath =
|
|
21966
|
+
const projectPath = resolve51(projectsDir2, row.project_slug, "project.md");
|
|
21460
21967
|
if (!await fileExists(projectPath)) {
|
|
21461
21968
|
results.push({
|
|
21462
21969
|
id: this.id,
|
|
@@ -21475,7 +21982,7 @@ var ghostSessions = {
|
|
|
21475
21982
|
continue;
|
|
21476
21983
|
}
|
|
21477
21984
|
if (row.assignment_slug) {
|
|
21478
|
-
const assignmentPath =
|
|
21985
|
+
const assignmentPath = resolve51(
|
|
21479
21986
|
projectsDir2,
|
|
21480
21987
|
row.project_slug,
|
|
21481
21988
|
"assignments",
|
|
@@ -21527,8 +22034,8 @@ function skipped(check, reason) {
|
|
|
21527
22034
|
|
|
21528
22035
|
// src/utils/doctor/checks/integrations.ts
|
|
21529
22036
|
init_fs();
|
|
21530
|
-
import { resolve as
|
|
21531
|
-
import { readdir as
|
|
22037
|
+
import { resolve as resolve52, dirname as dirname15, basename as basename5 } from "path";
|
|
22038
|
+
import { readdir as readdir19, readFile as readFile30 } from "fs/promises";
|
|
21532
22039
|
import { homedir as homedir7 } from "os";
|
|
21533
22040
|
var CATEGORY6 = "integrations";
|
|
21534
22041
|
var claudePluginLinked = {
|
|
@@ -21591,7 +22098,7 @@ var backupConfigured = {
|
|
|
21591
22098
|
if (ctx.config.backup?.repo) return pass6(this);
|
|
21592
22099
|
const projectsDir2 = ctx.config.defaultProjectDir;
|
|
21593
22100
|
if (!await fileExists(projectsDir2)) return skipped2(this, "no projects dir");
|
|
21594
|
-
const entries = await
|
|
22101
|
+
const entries = await readdir19(projectsDir2, { withFileTypes: true });
|
|
21595
22102
|
const hasProjects = entries.some((e) => e.isDirectory() && !e.name.startsWith(".") && !e.name.startsWith("_"));
|
|
21596
22103
|
if (!hasProjects) return skipped2(this, "no projects yet");
|
|
21597
22104
|
return {
|
|
@@ -21610,10 +22117,10 @@ var backupConfigured = {
|
|
|
21610
22117
|
}
|
|
21611
22118
|
};
|
|
21612
22119
|
async function readKnownMarketplaces() {
|
|
21613
|
-
const path =
|
|
22120
|
+
const path = resolve52(homedir7(), ".claude", "plugins", "known_marketplaces.json");
|
|
21614
22121
|
if (!await fileExists(path)) return {};
|
|
21615
22122
|
try {
|
|
21616
|
-
const raw = await
|
|
22123
|
+
const raw = await readFile30(path, "utf-8");
|
|
21617
22124
|
return JSON.parse(raw);
|
|
21618
22125
|
} catch {
|
|
21619
22126
|
return {};
|
|
@@ -21647,7 +22154,7 @@ var claudeMarketplaceRegistered = {
|
|
|
21647
22154
|
};
|
|
21648
22155
|
}
|
|
21649
22156
|
const marketplaceRoot = dirname15(pluginsParent);
|
|
21650
|
-
const marketplaceManifest =
|
|
22157
|
+
const marketplaceManifest = resolve52(marketplaceRoot, ".claude-plugin", "marketplace.json");
|
|
21651
22158
|
if (!await fileExists(marketplaceManifest)) {
|
|
21652
22159
|
return {
|
|
21653
22160
|
id: this.id,
|
|
@@ -21666,7 +22173,7 @@ var claudeMarketplaceRegistered = {
|
|
|
21666
22173
|
}
|
|
21667
22174
|
let parsed = {};
|
|
21668
22175
|
try {
|
|
21669
|
-
parsed = JSON.parse(await
|
|
22176
|
+
parsed = JSON.parse(await readFile30(marketplaceManifest, "utf-8"));
|
|
21670
22177
|
} catch {
|
|
21671
22178
|
return {
|
|
21672
22179
|
id: this.id,
|
|
@@ -21698,7 +22205,7 @@ var claudeMarketplaceRegistered = {
|
|
|
21698
22205
|
title: this.title,
|
|
21699
22206
|
status: "error",
|
|
21700
22207
|
detail: issues.join("; "),
|
|
21701
|
-
affected: [marketplaceManifest,
|
|
22208
|
+
affected: [marketplaceManifest, resolve52(homedir7(), ".claude", "plugins", "known_marketplaces.json")],
|
|
21702
22209
|
remediation: {
|
|
21703
22210
|
kind: "manual",
|
|
21704
22211
|
suggestion: "Re-run install-plugin to ensure both files are in sync.",
|
|
@@ -21738,8 +22245,8 @@ function skipped2(check, reason) {
|
|
|
21738
22245
|
init_fs();
|
|
21739
22246
|
init_parser();
|
|
21740
22247
|
init_types();
|
|
21741
|
-
import { resolve as
|
|
21742
|
-
import { readFile as
|
|
22248
|
+
import { resolve as resolve53 } from "path";
|
|
22249
|
+
import { readFile as readFile31 } from "fs/promises";
|
|
21743
22250
|
var CATEGORY7 = "workspace";
|
|
21744
22251
|
var ASSIGNMENT_FIELDS = ["projectSlug", "assignmentSlug", "projectDir", "assignmentDir"];
|
|
21745
22252
|
function hasAnyAssignmentField(ctx) {
|
|
@@ -21751,12 +22258,12 @@ function isStandaloneSession(ctx) {
|
|
|
21751
22258
|
return !hasAnyAssignmentField(ctx) && typeof ctx.sessionId === "string" && ctx.sessionId.length > 0;
|
|
21752
22259
|
}
|
|
21753
22260
|
async function loadContext(ctx) {
|
|
21754
|
-
const path =
|
|
22261
|
+
const path = resolve53(ctx.cwd, ".syntaur", "context.json");
|
|
21755
22262
|
if (!await fileExists(path)) {
|
|
21756
22263
|
return { data: null, path, exists: false, parseError: null };
|
|
21757
22264
|
}
|
|
21758
22265
|
try {
|
|
21759
|
-
const raw = await
|
|
22266
|
+
const raw = await readFile31(path, "utf-8");
|
|
21760
22267
|
return { data: JSON.parse(raw), path, exists: true, parseError: null };
|
|
21761
22268
|
} catch (err2) {
|
|
21762
22269
|
return {
|
|
@@ -21831,7 +22338,7 @@ var contextAssignmentResolves = {
|
|
|
21831
22338
|
if (!exists) return skipped3(this, "no context to resolve");
|
|
21832
22339
|
if (isStandaloneSession(data)) return skipped3(this, "standalone session context \u2014 no assignment to resolve");
|
|
21833
22340
|
if (!data?.assignmentDir) return skipped3(this, "context has no assignmentDir");
|
|
21834
|
-
const assignmentMd =
|
|
22341
|
+
const assignmentMd = resolve53(data.assignmentDir, "assignment.md");
|
|
21835
22342
|
if (!await fileExists(assignmentMd)) {
|
|
21836
22343
|
return {
|
|
21837
22344
|
id: this.id,
|
|
@@ -21860,10 +22367,10 @@ var contextTerminal = {
|
|
|
21860
22367
|
if (!exists) return skipped3(this, "no context to check");
|
|
21861
22368
|
if (isStandaloneSession(data)) return skipped3(this, "standalone session context \u2014 no assignment to check");
|
|
21862
22369
|
if (!data?.assignmentDir) return skipped3(this, "context has no assignmentDir");
|
|
21863
|
-
const assignmentMd =
|
|
22370
|
+
const assignmentMd = resolve53(data.assignmentDir, "assignment.md");
|
|
21864
22371
|
if (!await fileExists(assignmentMd)) return skipped3(this, "assignment file missing");
|
|
21865
22372
|
try {
|
|
21866
|
-
const content = await
|
|
22373
|
+
const content = await readFile31(assignmentMd, "utf-8");
|
|
21867
22374
|
const parsed = parseAssignmentFull(content);
|
|
21868
22375
|
const terminal = terminalStatuses2(ctx);
|
|
21869
22376
|
if (terminal.has(parsed.status)) {
|
|
@@ -21917,9 +22424,9 @@ function skipped3(check, reason) {
|
|
|
21917
22424
|
|
|
21918
22425
|
// src/utils/doctor/checks/agents.ts
|
|
21919
22426
|
init_config2();
|
|
21920
|
-
import { isAbsolute as
|
|
22427
|
+
import { isAbsolute as isAbsolute9 } from "path";
|
|
21921
22428
|
import { access as access2, constants as fsConstants } from "fs/promises";
|
|
21922
|
-
import { spawnSync as
|
|
22429
|
+
import { spawnSync as spawnSync7 } from "child_process";
|
|
21923
22430
|
var CATEGORY8 = "agents";
|
|
21924
22431
|
var agentsResolvable = {
|
|
21925
22432
|
id: "agents.commands-resolvable",
|
|
@@ -21963,7 +22470,7 @@ async function checkAgent(agent) {
|
|
|
21963
22470
|
autoFixable: false
|
|
21964
22471
|
};
|
|
21965
22472
|
}
|
|
21966
|
-
if (
|
|
22473
|
+
if (isAbsolute9(agent.command)) {
|
|
21967
22474
|
try {
|
|
21968
22475
|
await access2(agent.command, fsConstants.X_OK);
|
|
21969
22476
|
return { ...base, status: "pass", autoFixable: false };
|
|
@@ -21983,7 +22490,7 @@ async function checkAgent(agent) {
|
|
|
21983
22490
|
};
|
|
21984
22491
|
}
|
|
21985
22492
|
}
|
|
21986
|
-
const result =
|
|
22493
|
+
const result = spawnSync7("which", [agent.command], { encoding: "utf-8" });
|
|
21987
22494
|
if (result.status === 0 && result.stdout.trim().length > 0) {
|
|
21988
22495
|
return {
|
|
21989
22496
|
...base,
|
|
@@ -22008,26 +22515,16 @@ var agentChecks = [agentsResolvable];
|
|
|
22008
22515
|
|
|
22009
22516
|
// src/utils/doctor/checks/terminal.ts
|
|
22010
22517
|
init_config2();
|
|
22518
|
+
import { spawnSync as spawnSync8 } from "child_process";
|
|
22519
|
+
import { readFile as readFile32 } from "fs/promises";
|
|
22520
|
+
import { resolve as resolve54 } from "path";
|
|
22011
22521
|
init_paths();
|
|
22012
22522
|
init_fs();
|
|
22013
|
-
import { spawnSync as spawnSync5 } from "child_process";
|
|
22014
|
-
import { readFile as readFile31 } from "fs/promises";
|
|
22015
|
-
import { resolve as resolve52 } from "path";
|
|
22016
22523
|
var CATEGORY9 = "terminal";
|
|
22017
|
-
var APP_BUNDLE_IDS = {
|
|
22018
|
-
"terminal-app": "com.apple.Terminal",
|
|
22019
|
-
iterm: "com.googlecode.iterm2",
|
|
22020
|
-
ghostty: "com.mitchellh.ghostty",
|
|
22021
|
-
warp: "dev.warp.Warp-Stable"
|
|
22022
|
-
};
|
|
22023
|
-
var CLI_NAMES = {
|
|
22024
|
-
alacritty: "alacritty",
|
|
22025
|
-
kitty: "kitty"
|
|
22026
|
-
};
|
|
22027
22524
|
async function readRawTerminalKey() {
|
|
22028
|
-
const configPath =
|
|
22525
|
+
const configPath = resolve54(syntaurRoot(), "config.md");
|
|
22029
22526
|
if (!await fileExists(configPath)) return null;
|
|
22030
|
-
const content = await
|
|
22527
|
+
const content = await readFile32(configPath, "utf-8");
|
|
22031
22528
|
const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
22032
22529
|
if (!fmMatch) return null;
|
|
22033
22530
|
const line = fmMatch[1].split("\n").find((l) => /^terminal:\s*/.test(l));
|
|
@@ -22086,68 +22583,41 @@ var terminalInstalled = {
|
|
|
22086
22583
|
const terminal = getTerminal(ctx.config);
|
|
22087
22584
|
const bundleId = APP_BUNDLE_IDS[terminal];
|
|
22088
22585
|
const cliName = CLI_NAMES[terminal];
|
|
22089
|
-
|
|
22090
|
-
|
|
22091
|
-
"mdfind",
|
|
22092
|
-
[`kMDItemCFBundleIdentifier == '${bundleId}'`],
|
|
22093
|
-
{ encoding: "utf-8" }
|
|
22094
|
-
);
|
|
22095
|
-
if (result.status === 0 && result.stdout.trim().length > 0) {
|
|
22096
|
-
return {
|
|
22097
|
-
id: this.id,
|
|
22098
|
-
category: this.category,
|
|
22099
|
-
title: this.title,
|
|
22100
|
-
status: "pass",
|
|
22101
|
-
detail: `found ${terminal} at ${result.stdout.trim().split("\n")[0]}`,
|
|
22102
|
-
autoFixable: false
|
|
22103
|
-
};
|
|
22104
|
-
}
|
|
22586
|
+
const probe = probeTerminalInstalled(terminal);
|
|
22587
|
+
if (probe.reason === "no-probe-available") {
|
|
22105
22588
|
return {
|
|
22106
22589
|
id: this.id,
|
|
22107
22590
|
category: this.category,
|
|
22108
22591
|
title: this.title,
|
|
22109
|
-
status: "
|
|
22110
|
-
detail:
|
|
22111
|
-
remediation: {
|
|
22112
|
-
kind: "manual",
|
|
22113
|
-
suggestion: `Install ${terminal} or change \`terminal:\` in ~/.syntaur/config.md to a different choice`,
|
|
22114
|
-
command: null
|
|
22115
|
-
},
|
|
22592
|
+
status: "skipped",
|
|
22593
|
+
detail: `no install check defined for ${terminal}`,
|
|
22116
22594
|
autoFixable: false
|
|
22117
22595
|
};
|
|
22118
22596
|
}
|
|
22119
|
-
if (
|
|
22120
|
-
const
|
|
22121
|
-
if (result.status === 0 && result.stdout.trim().length > 0) {
|
|
22122
|
-
return {
|
|
22123
|
-
id: this.id,
|
|
22124
|
-
category: this.category,
|
|
22125
|
-
title: this.title,
|
|
22126
|
-
status: "pass",
|
|
22127
|
-
detail: `resolved ${cliName} \u2192 ${result.stdout.trim()}`,
|
|
22128
|
-
autoFixable: false
|
|
22129
|
-
};
|
|
22130
|
-
}
|
|
22597
|
+
if (probe.ok) {
|
|
22598
|
+
const detail2 = bundleId ? `found ${terminal} at ${probe.foundPath}` : `resolved ${cliName} \u2192 ${probe.foundPath}`;
|
|
22131
22599
|
return {
|
|
22132
22600
|
id: this.id,
|
|
22133
22601
|
category: this.category,
|
|
22134
22602
|
title: this.title,
|
|
22135
|
-
status: "
|
|
22136
|
-
detail:
|
|
22137
|
-
remediation: {
|
|
22138
|
-
kind: "manual",
|
|
22139
|
-
suggestion: `Install ${cliName} or change \`terminal:\` in ~/.syntaur/config.md to a different choice`,
|
|
22140
|
-
command: null
|
|
22141
|
-
},
|
|
22603
|
+
status: "pass",
|
|
22604
|
+
detail: detail2,
|
|
22142
22605
|
autoFixable: false
|
|
22143
22606
|
};
|
|
22144
22607
|
}
|
|
22608
|
+
const detail = bundleId ? `${terminal} (bundle id ${bundleId}) not found via Spotlight` : `${cliName} not found on PATH`;
|
|
22609
|
+
const suggestion = bundleId ? `Install ${terminal} or change \`terminal:\` in ~/.syntaur/config.md to a different choice` : `Install ${cliName} or change \`terminal:\` in ~/.syntaur/config.md to a different choice`;
|
|
22145
22610
|
return {
|
|
22146
22611
|
id: this.id,
|
|
22147
22612
|
category: this.category,
|
|
22148
22613
|
title: this.title,
|
|
22149
|
-
status: "
|
|
22150
|
-
detail
|
|
22614
|
+
status: "warn",
|
|
22615
|
+
detail,
|
|
22616
|
+
remediation: {
|
|
22617
|
+
kind: "manual",
|
|
22618
|
+
suggestion,
|
|
22619
|
+
command: null
|
|
22620
|
+
},
|
|
22151
22621
|
autoFixable: false
|
|
22152
22622
|
};
|
|
22153
22623
|
}
|
|
@@ -22167,7 +22637,7 @@ var kittyRemoteControl = {
|
|
|
22167
22637
|
autoFixable: false
|
|
22168
22638
|
};
|
|
22169
22639
|
}
|
|
22170
|
-
const result =
|
|
22640
|
+
const result = spawnSync8("kitty", ["@", "ls"], {
|
|
22171
22641
|
encoding: "utf-8",
|
|
22172
22642
|
timeout: 2e3
|
|
22173
22643
|
});
|
|
@@ -22204,13 +22674,13 @@ var terminalChecks = [
|
|
|
22204
22674
|
|
|
22205
22675
|
// src/utils/doctor/checks/skills.ts
|
|
22206
22676
|
init_fs();
|
|
22207
|
-
import { resolve as
|
|
22208
|
-
import { readdir as
|
|
22677
|
+
import { resolve as resolve55, join as join7 } from "path";
|
|
22678
|
+
import { readdir as readdir20, readFile as readFile33, lstat as lstat4 } from "fs/promises";
|
|
22209
22679
|
import { homedir as homedir8 } from "os";
|
|
22210
22680
|
var CATEGORY10 = "skills";
|
|
22211
22681
|
var skillTargets = [
|
|
22212
|
-
{ agent: "claude", dir:
|
|
22213
|
-
{ agent: "codex", dir:
|
|
22682
|
+
{ agent: "claude", dir: resolve55(homedir8(), ".claude", "skills"), label: "~/.claude/skills" },
|
|
22683
|
+
{ agent: "codex", dir: resolve55(homedir8(), ".codex", "skills"), label: "~/.codex/skills" }
|
|
22214
22684
|
];
|
|
22215
22685
|
var skillsDedupCheck = {
|
|
22216
22686
|
id: "skills.dedup",
|
|
@@ -22225,7 +22695,7 @@ var skillsDedupCheck = {
|
|
|
22225
22695
|
const present = [];
|
|
22226
22696
|
let entries;
|
|
22227
22697
|
try {
|
|
22228
|
-
entries = await
|
|
22698
|
+
entries = await readdir20(dir, { withFileTypes: true });
|
|
22229
22699
|
} catch {
|
|
22230
22700
|
continue;
|
|
22231
22701
|
}
|
|
@@ -22234,7 +22704,7 @@ var skillsDedupCheck = {
|
|
|
22234
22704
|
if (!KNOWN_SKILLS.includes(entry.name)) continue;
|
|
22235
22705
|
const skillMd = join7(dir, entry.name, "SKILL.md");
|
|
22236
22706
|
if (!await fileExists(skillMd)) continue;
|
|
22237
|
-
const content = await
|
|
22707
|
+
const content = await readFile33(skillMd, "utf-8").catch(() => "");
|
|
22238
22708
|
const match = content.match(/^name:\s*(\S+)\s*$/m);
|
|
22239
22709
|
if (!match || match[1] !== entry.name) continue;
|
|
22240
22710
|
let isSymlink2 = false;
|
|
@@ -22380,7 +22850,7 @@ async function readVersion() {
|
|
|
22380
22850
|
let dir = dirname16(here);
|
|
22381
22851
|
for (let i = 0; i < 6; i++) {
|
|
22382
22852
|
try {
|
|
22383
|
-
const raw = await
|
|
22853
|
+
const raw = await readFile34(join8(dir, "package.json"), "utf-8");
|
|
22384
22854
|
const parsed = JSON.parse(raw);
|
|
22385
22855
|
return typeof parsed.version === "string" ? parsed.version : null;
|
|
22386
22856
|
} catch {
|
|
@@ -22477,7 +22947,7 @@ var REQUIRED_WORKSPACE_FIELDS = [
|
|
|
22477
22947
|
];
|
|
22478
22948
|
var ISO_DATE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z$/;
|
|
22479
22949
|
async function validateAssignmentFile(inputPath, cwd = process.cwd()) {
|
|
22480
|
-
const absolute =
|
|
22950
|
+
const absolute = isAbsolute10(inputPath) ? inputPath : resolve56(cwd, inputPath);
|
|
22481
22951
|
const errors = [];
|
|
22482
22952
|
const warnings = [];
|
|
22483
22953
|
if (!await fileExists(absolute)) {
|
|
@@ -22490,7 +22960,7 @@ async function validateAssignmentFile(inputPath, cwd = process.cwd()) {
|
|
|
22490
22960
|
}
|
|
22491
22961
|
let content;
|
|
22492
22962
|
try {
|
|
22493
|
-
content = await
|
|
22963
|
+
content = await readFile35(absolute, "utf-8");
|
|
22494
22964
|
} catch (err2) {
|
|
22495
22965
|
return {
|
|
22496
22966
|
ok: false,
|
|
@@ -22814,8 +23284,8 @@ init_uuid();
|
|
|
22814
23284
|
init_timestamp();
|
|
22815
23285
|
init_assignment_resolver();
|
|
22816
23286
|
init_templates();
|
|
22817
|
-
import { resolve as
|
|
22818
|
-
import { readFile as
|
|
23287
|
+
import { resolve as resolve57 } from "path";
|
|
23288
|
+
import { readFile as readFile36 } from "fs/promises";
|
|
22819
23289
|
function shortId() {
|
|
22820
23290
|
return generateId().split("-")[0];
|
|
22821
23291
|
}
|
|
@@ -22845,7 +23315,7 @@ async function commentCommand(target, text, options = {}) {
|
|
|
22845
23315
|
if (!isValidSlug(target)) {
|
|
22846
23316
|
throw new Error(`Invalid assignment slug "${target}".`);
|
|
22847
23317
|
}
|
|
22848
|
-
assignmentDir =
|
|
23318
|
+
assignmentDir = resolve57(baseDir, options.project, "assignments", target);
|
|
22849
23319
|
assignmentRef = target;
|
|
22850
23320
|
} else {
|
|
22851
23321
|
const resolved = await resolveAssignmentById(baseDir, assignmentsDir(), target);
|
|
@@ -22855,13 +23325,13 @@ async function commentCommand(target, text, options = {}) {
|
|
|
22855
23325
|
assignmentDir = resolved.assignmentDir;
|
|
22856
23326
|
assignmentRef = resolved.standalone ? resolved.id : resolved.assignmentSlug;
|
|
22857
23327
|
}
|
|
22858
|
-
const commentsPath =
|
|
23328
|
+
const commentsPath = resolve57(assignmentDir, "comments.md");
|
|
22859
23329
|
const timestamp = nowTimestamp();
|
|
22860
23330
|
const author = options.author ?? process.env.USER ?? "unknown";
|
|
22861
23331
|
let currentContent;
|
|
22862
23332
|
let currentCount = 0;
|
|
22863
23333
|
if (await fileExists(commentsPath)) {
|
|
22864
|
-
currentContent = await
|
|
23334
|
+
currentContent = await readFile36(commentsPath, "utf-8");
|
|
22865
23335
|
const countMatch = currentContent.match(/^entryCount:\s*(\d+)/m);
|
|
22866
23336
|
if (countMatch) currentCount = parseInt(countMatch[1], 10);
|
|
22867
23337
|
} else {
|
|
@@ -22895,7 +23365,7 @@ ${entry}`;
|
|
|
22895
23365
|
}
|
|
22896
23366
|
|
|
22897
23367
|
// src/commands/capture.ts
|
|
22898
|
-
import { resolve as
|
|
23368
|
+
import { resolve as resolve61, relative as relative4, dirname as dirname17 } from "path";
|
|
22899
23369
|
import { copyFile as copyFile3, mkdir as mkdir8, realpath as realpath2, rm as rm10, stat as stat8, writeFile as writeFile11 } from "fs/promises";
|
|
22900
23370
|
import { existsSync as existsSync3 } from "fs";
|
|
22901
23371
|
|
|
@@ -22906,15 +23376,15 @@ init_config2();
|
|
|
22906
23376
|
init_slug();
|
|
22907
23377
|
init_assignment_resolver();
|
|
22908
23378
|
init_parser();
|
|
22909
|
-
import { resolve as
|
|
22910
|
-
import { readFile as
|
|
23379
|
+
import { resolve as resolve58 } from "path";
|
|
23380
|
+
import { readFile as readFile37 } from "fs/promises";
|
|
22911
23381
|
var AssignmentTargetError = class extends Error {
|
|
22912
23382
|
};
|
|
22913
23383
|
async function readAssignmentFrontmatterId(assignmentDir) {
|
|
22914
|
-
const path =
|
|
23384
|
+
const path = resolve58(assignmentDir, "assignment.md");
|
|
22915
23385
|
if (!await fileExists(path)) return null;
|
|
22916
23386
|
try {
|
|
22917
|
-
const content = await
|
|
23387
|
+
const content = await readFile37(path, "utf-8");
|
|
22918
23388
|
const [fm] = extractFrontmatter(content);
|
|
22919
23389
|
return getField(fm, "id");
|
|
22920
23390
|
} catch {
|
|
@@ -22922,10 +23392,10 @@ async function readAssignmentFrontmatterId(assignmentDir) {
|
|
|
22922
23392
|
}
|
|
22923
23393
|
}
|
|
22924
23394
|
async function readContextJson(cwd) {
|
|
22925
|
-
const path =
|
|
23395
|
+
const path = resolve58(cwd, ".syntaur", "context.json");
|
|
22926
23396
|
if (!await fileExists(path)) return null;
|
|
22927
23397
|
try {
|
|
22928
|
-
const raw = await
|
|
23398
|
+
const raw = await readFile37(path, "utf-8");
|
|
22929
23399
|
return JSON.parse(raw);
|
|
22930
23400
|
} catch {
|
|
22931
23401
|
return null;
|
|
@@ -22946,15 +23416,15 @@ async function resolveAssignmentTarget(input4, opts = {}) {
|
|
|
22946
23416
|
if (!isValidSlug(input4)) {
|
|
22947
23417
|
throw new AssignmentTargetError(`Invalid assignment slug "${input4}".`);
|
|
22948
23418
|
}
|
|
22949
|
-
const projectDir =
|
|
22950
|
-
const projectMdPath =
|
|
23419
|
+
const projectDir = resolve58(baseDir, opts.project);
|
|
23420
|
+
const projectMdPath = resolve58(projectDir, "project.md");
|
|
22951
23421
|
if (!await fileExists(projectDir) || !await fileExists(projectMdPath)) {
|
|
22952
23422
|
throw new AssignmentTargetError(
|
|
22953
23423
|
`Project "${opts.project}" not found at ${projectDir}.`
|
|
22954
23424
|
);
|
|
22955
23425
|
}
|
|
22956
|
-
const assignmentDir =
|
|
22957
|
-
const assignmentMdPath2 =
|
|
23426
|
+
const assignmentDir = resolve58(projectDir, "assignments", input4);
|
|
23427
|
+
const assignmentMdPath2 = resolve58(assignmentDir, "assignment.md");
|
|
22958
23428
|
if (!await fileExists(assignmentMdPath2)) {
|
|
22959
23429
|
throw new AssignmentTargetError(
|
|
22960
23430
|
`Assignment "${input4}" not found in project "${opts.project}".`
|
|
@@ -22988,7 +23458,7 @@ async function resolveAssignmentTarget(input4, opts = {}) {
|
|
|
22988
23458
|
}
|
|
22989
23459
|
if (ctx.assignmentDir) {
|
|
22990
23460
|
const dir = expandHome(ctx.assignmentDir);
|
|
22991
|
-
const assignmentMdPath2 =
|
|
23461
|
+
const assignmentMdPath2 = resolve58(dir, "assignment.md");
|
|
22992
23462
|
if (!await fileExists(assignmentMdPath2)) {
|
|
22993
23463
|
throw new AssignmentTargetError(
|
|
22994
23464
|
`.syntaur/context.json points to a missing assignment dir: ${dir}.`
|
|
@@ -23017,8 +23487,8 @@ async function resolveAssignmentTarget(input4, opts = {}) {
|
|
|
23017
23487
|
`.syntaur/context.json contains invalid slugs: project="${ctx.projectSlug}" assignment="${ctx.assignmentSlug}".`
|
|
23018
23488
|
);
|
|
23019
23489
|
}
|
|
23020
|
-
const assignmentDir =
|
|
23021
|
-
const assignmentMdPath2 =
|
|
23490
|
+
const assignmentDir = resolve58(baseDir, ctx.projectSlug, "assignments", ctx.assignmentSlug);
|
|
23491
|
+
const assignmentMdPath2 = resolve58(assignmentDir, "assignment.md");
|
|
23022
23492
|
if (!await fileExists(assignmentMdPath2)) {
|
|
23023
23493
|
throw new AssignmentTargetError(
|
|
23024
23494
|
`.syntaur/context.json points to a missing assignment: ${assignmentDir}.`
|
|
@@ -23153,7 +23623,7 @@ async function captureScreenshot(mode) {
|
|
|
23153
23623
|
|
|
23154
23624
|
// src/utils/asciinema.ts
|
|
23155
23625
|
import { spawn as spawn6 } from "child_process";
|
|
23156
|
-
import { mkdtemp as mkdtemp3, readFile as
|
|
23626
|
+
import { mkdtemp as mkdtemp3, readFile as readFile38, rm as rm7 } from "fs/promises";
|
|
23157
23627
|
import { tmpdir as tmpdir4 } from "os";
|
|
23158
23628
|
import { join as join10 } from "path";
|
|
23159
23629
|
var SAFE_RE = /^[A-Za-z0-9_@%+=:,./-]+$/;
|
|
@@ -23216,7 +23686,7 @@ async function captureAsciinema(opts) {
|
|
|
23216
23686
|
}
|
|
23217
23687
|
throw err2;
|
|
23218
23688
|
}
|
|
23219
|
-
const text = await
|
|
23689
|
+
const text = await readFile38(castPath, "utf8").catch(() => null);
|
|
23220
23690
|
if (text === null) {
|
|
23221
23691
|
throw new Error(
|
|
23222
23692
|
`asciinema produced no cast file at ${castPath} (exit ${exitCode}). Try running 'asciinema rec ${castPath}' directly to diagnose.`
|
|
@@ -23244,9 +23714,9 @@ async function captureAsciinema(opts) {
|
|
|
23244
23714
|
// src/utils/recording.ts
|
|
23245
23715
|
init_paths();
|
|
23246
23716
|
import { spawn as spawn7 } from "child_process";
|
|
23247
|
-
import { mkdir as mkdir7, mkdtemp as mkdtemp4, open as open3, readFile as
|
|
23717
|
+
import { mkdir as mkdir7, mkdtemp as mkdtemp4, open as open3, readFile as readFile39, rm as rm8, stat as stat7, unlink as unlink8, writeFile as writeFile10 } from "fs/promises";
|
|
23248
23718
|
import { tmpdir as tmpdir5 } from "os";
|
|
23249
|
-
import { join as join11, resolve as
|
|
23719
|
+
import { join as join11, resolve as resolve59 } from "path";
|
|
23250
23720
|
import { setTimeout as sleep } from "timers/promises";
|
|
23251
23721
|
function sigintPollIntervalMs() {
|
|
23252
23722
|
const raw = process.env.SYNTAUR_RECORDING_POLL_INTERVAL_MS;
|
|
@@ -23267,13 +23737,13 @@ function sigtermWaitMs() {
|
|
|
23267
23737
|
return Number.isFinite(parsed) && parsed >= 0 ? parsed : 1e3;
|
|
23268
23738
|
}
|
|
23269
23739
|
function pidfilePath() {
|
|
23270
|
-
return
|
|
23740
|
+
return resolve59(syntaurRoot(), "recording.pid");
|
|
23271
23741
|
}
|
|
23272
23742
|
function logPath2() {
|
|
23273
|
-
return
|
|
23743
|
+
return resolve59(syntaurRoot(), "recording.log");
|
|
23274
23744
|
}
|
|
23275
23745
|
function sidecarPath() {
|
|
23276
|
-
return
|
|
23746
|
+
return resolve59(syntaurRoot(), "recording.json");
|
|
23277
23747
|
}
|
|
23278
23748
|
function ffmpegArgs(device, fps, mp4Path) {
|
|
23279
23749
|
return [
|
|
@@ -23318,7 +23788,7 @@ async function acquirePidfile(pidfile) {
|
|
|
23318
23788
|
} catch (err2) {
|
|
23319
23789
|
if (err2.code !== "EEXIST") throw err2;
|
|
23320
23790
|
if (attempt === 1) throw err2;
|
|
23321
|
-
const existing = (await
|
|
23791
|
+
const existing = (await readFile39(pidfile, "utf-8").catch(() => "")).trim();
|
|
23322
23792
|
if (existing.startsWith(STARTING_SENTINEL_PREFIX)) {
|
|
23323
23793
|
const parentPidRaw = existing.slice(STARTING_SENTINEL_PREFIX.length);
|
|
23324
23794
|
const parentPid = Number.parseInt(parentPidRaw, 10);
|
|
@@ -23420,7 +23890,7 @@ async function startRecording(input4) {
|
|
|
23420
23890
|
logHandle = null;
|
|
23421
23891
|
if (warmupMs > 0) await sleep(warmupMs);
|
|
23422
23892
|
if (!await isProcessAlive(pid)) {
|
|
23423
|
-
const tail = await
|
|
23893
|
+
const tail = await readFile39(log, "utf-8").then((s) => s.split("\n").slice(-20).join("\n")).catch(() => "");
|
|
23424
23894
|
acquiredPid = null;
|
|
23425
23895
|
throw new Error(
|
|
23426
23896
|
`ffmpeg exited during startup \u2014 likely macOS Screen Recording permission missing. Grant access to your terminal in System Settings \u2192 Privacy & Security \u2192 Screen Recording, then retry. Log: ${log}
|
|
@@ -23477,7 +23947,7 @@ ${tail}`
|
|
|
23477
23947
|
async function stopRecording() {
|
|
23478
23948
|
const pidfile = pidfilePath();
|
|
23479
23949
|
const sidecar = sidecarPath();
|
|
23480
|
-
const pidRaw = await
|
|
23950
|
+
const pidRaw = await readFile39(pidfile, "utf-8").catch(() => null);
|
|
23481
23951
|
if (pidRaw === null) {
|
|
23482
23952
|
throw new Error(
|
|
23483
23953
|
`No active recording found (no pidfile at ${pidfile}). Did you run --start?`
|
|
@@ -23487,7 +23957,7 @@ async function stopRecording() {
|
|
|
23487
23957
|
if (!Number.isInteger(pid) || pid <= 0) {
|
|
23488
23958
|
throw new Error(`Pidfile at ${pidfile} is corrupt (got "${pidRaw}").`);
|
|
23489
23959
|
}
|
|
23490
|
-
const sidecarRaw = await
|
|
23960
|
+
const sidecarRaw = await readFile39(sidecar, "utf-8").catch(() => null);
|
|
23491
23961
|
if (sidecarRaw === null) {
|
|
23492
23962
|
throw new Error(
|
|
23493
23963
|
`No recording sidecar at ${sidecar}. The recording state is inconsistent \u2014 delete ${pidfile} and re-run --start.`
|
|
@@ -23552,7 +24022,7 @@ async function stopRecording() {
|
|
|
23552
24022
|
// src/db/proof-db.ts
|
|
23553
24023
|
init_paths();
|
|
23554
24024
|
import Database4 from "better-sqlite3";
|
|
23555
|
-
import { resolve as
|
|
24025
|
+
import { resolve as resolve60 } from "path";
|
|
23556
24026
|
var db3 = null;
|
|
23557
24027
|
var PROOF_SCHEMA_VERSION = "1";
|
|
23558
24028
|
var SCHEMA_SQL3 = `
|
|
@@ -23572,7 +24042,7 @@ CREATE TABLE IF NOT EXISTS meta (key TEXT PRIMARY KEY, value TEXT);
|
|
|
23572
24042
|
`;
|
|
23573
24043
|
function initProofDb(dbPath) {
|
|
23574
24044
|
if (db3) return db3;
|
|
23575
|
-
const finalPath = dbPath ??
|
|
24045
|
+
const finalPath = dbPath ?? resolve60(syntaurRoot(), "syntaur.db");
|
|
23576
24046
|
db3 = new Database4(finalPath);
|
|
23577
24047
|
db3.pragma("journal_mode = WAL");
|
|
23578
24048
|
db3.exec(SCHEMA_SQL3);
|
|
@@ -23620,7 +24090,7 @@ function listArtifactsByAssignment(assignmentId) {
|
|
|
23620
24090
|
|
|
23621
24091
|
// src/utils/transcribers/elevenlabs.ts
|
|
23622
24092
|
import { spawn as spawn8 } from "child_process";
|
|
23623
|
-
import { mkdtemp as mkdtemp5, readFile as
|
|
24093
|
+
import { mkdtemp as mkdtemp5, readFile as readFile40, rm as rm9 } from "fs/promises";
|
|
23624
24094
|
import { tmpdir as tmpdir6 } from "os";
|
|
23625
24095
|
import { join as join12 } from "path";
|
|
23626
24096
|
var SCRIBE_URL = "https://api.elevenlabs.io/v1/speech-to-text";
|
|
@@ -23684,7 +24154,7 @@ async function extractAudio(videoAbsPath, wavOut) {
|
|
|
23684
24154
|
throw new TranscribeFfmpegError(`ffmpeg failed (exit ${result.code}): ${tail}`);
|
|
23685
24155
|
}
|
|
23686
24156
|
async function callScribe(wavPath, apiKey, opts) {
|
|
23687
|
-
const audio = await
|
|
24157
|
+
const audio = await readFile40(wavPath);
|
|
23688
24158
|
const form = new FormData();
|
|
23689
24159
|
form.set("file", new Blob([new Uint8Array(audio)], { type: "audio/wav" }), "audio.wav");
|
|
23690
24160
|
form.set("model_id", "scribe_v1");
|
|
@@ -24000,7 +24470,7 @@ async function captureCommand(target, options = {}) {
|
|
|
24000
24470
|
});
|
|
24001
24471
|
}
|
|
24002
24472
|
if (options.file) {
|
|
24003
|
-
const expanded = options.file.startsWith("~/") ?
|
|
24473
|
+
const expanded = options.file.startsWith("~/") ? resolve61(process.env.HOME ?? "", options.file.slice(2)) : resolve61(options.file);
|
|
24004
24474
|
if (!await fileExists(expanded)) {
|
|
24005
24475
|
throw new Error(`--file does not exist: ${options.file}`);
|
|
24006
24476
|
}
|
|
@@ -24041,7 +24511,7 @@ async function captureCommand(target, options = {}) {
|
|
|
24041
24511
|
}
|
|
24042
24512
|
initProofDb();
|
|
24043
24513
|
const subdir = criterionIndex === null ? "untagged" : String(criterionIndex);
|
|
24044
|
-
const destDir =
|
|
24514
|
+
const destDir = resolve61(proofDir(resolved.assignmentDir), subdir);
|
|
24045
24515
|
if (resolvedSource) await mkdir8(destDir, { recursive: true });
|
|
24046
24516
|
const ext = resolvedSource ? extensionForKind(kind) : null;
|
|
24047
24517
|
let id = null;
|
|
@@ -24050,7 +24520,7 @@ async function captureCommand(target, options = {}) {
|
|
|
24050
24520
|
let lastErr = null;
|
|
24051
24521
|
for (let attempt = 0; attempt < MAX_ID_RETRIES; attempt += 1) {
|
|
24052
24522
|
const candidate = generateArtifactId();
|
|
24053
|
-
const candidateAbsPath = resolvedSource && ext ?
|
|
24523
|
+
const candidateAbsPath = resolvedSource && ext ? resolve61(destDir, `${candidate}.${ext}`) : null;
|
|
24054
24524
|
const candidateRel = candidateAbsPath ? relative4(resolved.assignmentDir, candidateAbsPath) : null;
|
|
24055
24525
|
try {
|
|
24056
24526
|
insertArtifact({
|
|
@@ -24094,7 +24564,7 @@ async function captureCommand(target, options = {}) {
|
|
|
24094
24564
|
}
|
|
24095
24565
|
}
|
|
24096
24566
|
if (options.transcribe && kind === "video" && absPath && id) {
|
|
24097
|
-
const sidecarPath2 =
|
|
24567
|
+
const sidecarPath2 = resolve61(destDir, `${id}.transcript.md`);
|
|
24098
24568
|
if (existsSync3(sidecarPath2)) {
|
|
24099
24569
|
console.warn(
|
|
24100
24570
|
`transcript: ${sidecarPath2} already exists, skipping (delete to re-transcribe)`
|
|
@@ -24126,7 +24596,7 @@ async function captureCommand(target, options = {}) {
|
|
|
24126
24596
|
const tagSuffix = criterionIndex === null ? "untagged" : `criterion ${criterionIndex}`;
|
|
24127
24597
|
console.log(`Captured artifact ${id} (${kind}) for ${ref} \u2014 ${tagSuffix}.`);
|
|
24128
24598
|
if (relativeFilePath) {
|
|
24129
|
-
console.log(` file: ${
|
|
24599
|
+
console.log(` file: ${resolve61(resolved.assignmentDir, relativeFilePath)}`);
|
|
24130
24600
|
}
|
|
24131
24601
|
} catch (err2) {
|
|
24132
24602
|
if (options.stop && kind === "video" && shelloutCleanup && resolvedSource) {
|
|
@@ -24149,8 +24619,8 @@ async function captureCommand(target, options = {}) {
|
|
|
24149
24619
|
|
|
24150
24620
|
// src/commands/proof.ts
|
|
24151
24621
|
import { Command as Command5 } from "commander";
|
|
24152
|
-
import { readFile as
|
|
24153
|
-
import { resolve as
|
|
24622
|
+
import { readFile as readFile41, writeFile as writeFile12, rename as rename8, stat as stat9 } from "fs/promises";
|
|
24623
|
+
import { resolve as resolve62, relative as relative5, isAbsolute as isAbsolute11, dirname as dirname18 } from "path";
|
|
24154
24624
|
import { randomBytes as randomBytes3 } from "crypto";
|
|
24155
24625
|
|
|
24156
24626
|
// src/utils/acceptance-criteria-parse.ts
|
|
@@ -24390,11 +24860,11 @@ function renderProofHtml(params2, inlineFiles = /* @__PURE__ */ new Map(), trans
|
|
|
24390
24860
|
|
|
24391
24861
|
// src/commands/proof.ts
|
|
24392
24862
|
async function readAssignmentMeta(assignmentDir) {
|
|
24393
|
-
const path =
|
|
24863
|
+
const path = resolve62(assignmentDir, "assignment.md");
|
|
24394
24864
|
if (!await fileExists(path)) {
|
|
24395
24865
|
return { title: "", body: "" };
|
|
24396
24866
|
}
|
|
24397
|
-
const content = await
|
|
24867
|
+
const content = await readFile41(path, "utf-8");
|
|
24398
24868
|
const fmMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?/);
|
|
24399
24869
|
let title = "";
|
|
24400
24870
|
if (fmMatch) {
|
|
@@ -24413,7 +24883,7 @@ async function atomicWrite(destPath, content) {
|
|
|
24413
24883
|
}
|
|
24414
24884
|
function isWithin(root, target) {
|
|
24415
24885
|
const rel = relative5(root, target);
|
|
24416
|
-
return rel === "" || !rel.startsWith("..") && !
|
|
24886
|
+
return rel === "" || !rel.startsWith("..") && !isAbsolute11(rel);
|
|
24417
24887
|
}
|
|
24418
24888
|
function groupArtifacts(rows, criterionCount) {
|
|
24419
24889
|
const artifactsByCriterion = /* @__PURE__ */ new Map();
|
|
@@ -24441,7 +24911,7 @@ async function loadInlineFiles(rows, assignmentDir) {
|
|
|
24441
24911
|
for (const r of rows) {
|
|
24442
24912
|
if (!r.file_path) continue;
|
|
24443
24913
|
if (r.kind !== "http" && r.kind !== "text") continue;
|
|
24444
|
-
const abs =
|
|
24914
|
+
const abs = resolve62(assignmentDir, r.file_path);
|
|
24445
24915
|
if (!isWithin(proofRoot, abs)) {
|
|
24446
24916
|
out.set(r.file_path, null);
|
|
24447
24917
|
continue;
|
|
@@ -24456,7 +24926,7 @@ async function loadInlineFiles(rows, assignmentDir) {
|
|
|
24456
24926
|
continue;
|
|
24457
24927
|
}
|
|
24458
24928
|
try {
|
|
24459
|
-
out.set(r.file_path, await
|
|
24929
|
+
out.set(r.file_path, await readFile41(abs, "utf-8"));
|
|
24460
24930
|
} catch {
|
|
24461
24931
|
out.set(r.file_path, null);
|
|
24462
24932
|
}
|
|
@@ -24468,14 +24938,14 @@ async function loadTranscriptSidecars(rows, assignmentDir) {
|
|
|
24468
24938
|
const proofRoot = proofDir(assignmentDir);
|
|
24469
24939
|
for (const r of rows) {
|
|
24470
24940
|
if (r.kind !== "video" || !r.file_path) continue;
|
|
24471
|
-
const videoAbs =
|
|
24472
|
-
const sidecar =
|
|
24941
|
+
const videoAbs = resolve62(assignmentDir, r.file_path);
|
|
24942
|
+
const sidecar = resolve62(dirname18(videoAbs), `${r.id}.transcript.md`);
|
|
24473
24943
|
if (!isWithin(proofRoot, sidecar)) continue;
|
|
24474
24944
|
if (!await fileExists(sidecar)) continue;
|
|
24475
24945
|
const st = await stat9(sidecar);
|
|
24476
24946
|
if (st.size > INLINE_TEXT_LIMIT_BYTES) continue;
|
|
24477
24947
|
try {
|
|
24478
|
-
out.set(r.id, await
|
|
24948
|
+
out.set(r.id, await readFile41(sidecar, "utf-8"));
|
|
24479
24949
|
} catch {
|
|
24480
24950
|
}
|
|
24481
24951
|
}
|
|
@@ -24509,8 +24979,8 @@ async function proofBuildCommand(target, options = {}) {
|
|
|
24509
24979
|
};
|
|
24510
24980
|
const md = renderProofMarkdown(renderParams);
|
|
24511
24981
|
const html = renderProofHtml(renderParams, inlineFiles, transcriptSidecars);
|
|
24512
|
-
const mdPath =
|
|
24513
|
-
const htmlPath =
|
|
24982
|
+
const mdPath = resolve62(resolved.assignmentDir, "proof.md");
|
|
24983
|
+
const htmlPath = resolve62(resolved.assignmentDir, "proof.html");
|
|
24514
24984
|
await atomicWrite(mdPath, md);
|
|
24515
24985
|
await atomicWrite(htmlPath, html);
|
|
24516
24986
|
console.log(`Wrote ${htmlPath}`);
|
|
@@ -25007,8 +25477,8 @@ init_slug();
|
|
|
25007
25477
|
init_timestamp();
|
|
25008
25478
|
init_assignment_resolver();
|
|
25009
25479
|
init_assignment_todos();
|
|
25010
|
-
import { resolve as
|
|
25011
|
-
import { readFile as
|
|
25480
|
+
import { resolve as resolve63 } from "path";
|
|
25481
|
+
import { readFile as readFile42 } from "fs/promises";
|
|
25012
25482
|
async function requestCommand(target, text, options = {}) {
|
|
25013
25483
|
if (!text || !text.trim()) {
|
|
25014
25484
|
throw new Error("Request text cannot be empty.");
|
|
@@ -25024,7 +25494,7 @@ async function requestCommand(target, text, options = {}) {
|
|
|
25024
25494
|
if (!isValidSlug(target)) {
|
|
25025
25495
|
throw new Error(`Invalid assignment slug "${target}".`);
|
|
25026
25496
|
}
|
|
25027
|
-
assignmentDir =
|
|
25497
|
+
assignmentDir = resolve63(baseDir, options.project, "assignments", target);
|
|
25028
25498
|
targetRef = target;
|
|
25029
25499
|
} else {
|
|
25030
25500
|
const resolved = await resolveAssignmentById(baseDir, assignmentsDir(), target);
|
|
@@ -25034,12 +25504,12 @@ async function requestCommand(target, text, options = {}) {
|
|
|
25034
25504
|
assignmentDir = resolved.assignmentDir;
|
|
25035
25505
|
targetRef = resolved.standalone ? resolved.id : resolved.assignmentSlug;
|
|
25036
25506
|
}
|
|
25037
|
-
const assignmentMdPath2 =
|
|
25507
|
+
const assignmentMdPath2 = resolve63(assignmentDir, "assignment.md");
|
|
25038
25508
|
if (!await fileExists(assignmentMdPath2)) {
|
|
25039
25509
|
throw new Error(`assignment.md not found at ${assignmentMdPath2}`);
|
|
25040
25510
|
}
|
|
25041
25511
|
const source = options.from ?? process.env.SYNTAUR_ASSIGNMENT ?? "unknown";
|
|
25042
|
-
let content = await
|
|
25512
|
+
let content = await readFile42(assignmentMdPath2, "utf-8");
|
|
25043
25513
|
content = appendTodosToAssignmentBody(content, [
|
|
25044
25514
|
{ description: `${text.trim()} (from: ${source})` }
|
|
25045
25515
|
]);
|
|
@@ -25052,13 +25522,13 @@ async function requestCommand(target, text, options = {}) {
|
|
|
25052
25522
|
init_fs();
|
|
25053
25523
|
init_paths();
|
|
25054
25524
|
import { Command as Command7 } from "commander";
|
|
25055
|
-
import { readFile as
|
|
25056
|
-
import { resolve as
|
|
25525
|
+
import { readFile as readFile43, readdir as readdir21 } from "fs/promises";
|
|
25526
|
+
import { resolve as resolve64 } from "path";
|
|
25057
25527
|
async function readContextAssignmentDir(cwd) {
|
|
25058
|
-
const path =
|
|
25528
|
+
const path = resolve64(cwd, ".syntaur", "context.json");
|
|
25059
25529
|
if (!await fileExists(path)) return null;
|
|
25060
25530
|
try {
|
|
25061
|
-
const raw = await
|
|
25531
|
+
const raw = await readFile43(path, "utf-8");
|
|
25062
25532
|
const ctx = JSON.parse(raw);
|
|
25063
25533
|
if (typeof ctx.assignmentDir === "string" && ctx.assignmentDir.length > 0) {
|
|
25064
25534
|
return ctx.assignmentDir;
|
|
@@ -25072,9 +25542,9 @@ async function resolveAssignmentDir(opts) {
|
|
|
25072
25542
|
const cwd = opts.cwd ?? process.cwd();
|
|
25073
25543
|
if (opts.assignment) {
|
|
25074
25544
|
if (opts.project) {
|
|
25075
|
-
return
|
|
25545
|
+
return resolve64(defaultProjectDir(), opts.project, "assignments", opts.assignment);
|
|
25076
25546
|
}
|
|
25077
|
-
return
|
|
25547
|
+
return resolve64(assignmentsDir(), opts.assignment);
|
|
25078
25548
|
}
|
|
25079
25549
|
const fromCtx = await readContextAssignmentDir(cwd);
|
|
25080
25550
|
if (fromCtx) return fromCtx;
|
|
@@ -25085,7 +25555,7 @@ async function resolveAssignmentDir(opts) {
|
|
|
25085
25555
|
var PLAN_PATTERN = /^plan(?:-v(\d+))?\.md$/;
|
|
25086
25556
|
async function listPlanFiles(assignmentDir) {
|
|
25087
25557
|
if (!await fileExists(assignmentDir)) return [];
|
|
25088
|
-
const entries = await
|
|
25558
|
+
const entries = await readdir21(assignmentDir, { withFileTypes: true });
|
|
25089
25559
|
const out = [];
|
|
25090
25560
|
for (const e of entries) {
|
|
25091
25561
|
if (!e.isFile()) continue;
|
|
@@ -25217,7 +25687,7 @@ async function runPlanVersion(options) {
|
|
|
25217
25687
|
if (!await fileExists(assignmentDir)) {
|
|
25218
25688
|
throw new Error(`Assignment directory does not exist: ${assignmentDir}`);
|
|
25219
25689
|
}
|
|
25220
|
-
const assignmentMdPath2 =
|
|
25690
|
+
const assignmentMdPath2 = resolve64(assignmentDir, "assignment.md");
|
|
25221
25691
|
if (!await fileExists(assignmentMdPath2)) {
|
|
25222
25692
|
throw new Error(`Missing assignment.md at: ${assignmentMdPath2}`);
|
|
25223
25693
|
}
|
|
@@ -25229,15 +25699,15 @@ async function runPlanVersion(options) {
|
|
|
25229
25699
|
}
|
|
25230
25700
|
const current = planFiles[planFiles.length - 1];
|
|
25231
25701
|
const next = nextPlanFileName(current.version);
|
|
25232
|
-
const newPath =
|
|
25702
|
+
const newPath = resolve64(assignmentDir, next.fileName);
|
|
25233
25703
|
if (await fileExists(newPath) && !options.force) {
|
|
25234
25704
|
throw new Error(`${next.fileName} already exists. Use --force to overwrite.`);
|
|
25235
25705
|
}
|
|
25236
|
-
const assignmentMd = await
|
|
25706
|
+
const assignmentMd = await readFile43(assignmentMdPath2, "utf-8");
|
|
25237
25707
|
const slugMatch = assignmentMd.match(/^slug:\s*(.+?)\s*$/m);
|
|
25238
25708
|
const slug = slugMatch ? slugMatch[1].trim() : assignmentDir.split("/").pop() ?? "";
|
|
25239
|
-
const oldPlanPath =
|
|
25240
|
-
const oldPlanContent = await
|
|
25709
|
+
const oldPlanPath = resolve64(assignmentDir, current.fileName);
|
|
25710
|
+
const oldPlanContent = await readFile43(oldPlanPath, "utf-8");
|
|
25241
25711
|
const oldBody = oldPlanContent.replace(/^---[\s\S]*?\n---\n?/, "");
|
|
25242
25712
|
const carriedTodos = extractUncheckedTodos(oldBody);
|
|
25243
25713
|
const stub = buildNewPlanStub({
|
|
@@ -25274,26 +25744,26 @@ planCommand.command("version").description(
|
|
|
25274
25744
|
// src/commands/session.ts
|
|
25275
25745
|
init_fs();
|
|
25276
25746
|
import { Command as Command8 } from "commander";
|
|
25277
|
-
import { readFile as
|
|
25278
|
-
import { resolve as
|
|
25747
|
+
import { readFile as readFile44, readdir as readdir22, stat as stat10 } from "fs/promises";
|
|
25748
|
+
import { resolve as resolve65 } from "path";
|
|
25279
25749
|
async function readContext(cwd) {
|
|
25280
|
-
const path =
|
|
25750
|
+
const path = resolve65(cwd, ".syntaur", "context.json");
|
|
25281
25751
|
if (!await fileExists(path)) return null;
|
|
25282
25752
|
try {
|
|
25283
|
-
const raw = await
|
|
25753
|
+
const raw = await readFile44(path, "utf-8");
|
|
25284
25754
|
return JSON.parse(raw);
|
|
25285
25755
|
} catch {
|
|
25286
25756
|
return null;
|
|
25287
25757
|
}
|
|
25288
25758
|
}
|
|
25289
25759
|
async function findLatestSessionSummary(assignmentDir) {
|
|
25290
|
-
const sessionsRoot =
|
|
25760
|
+
const sessionsRoot = resolve65(assignmentDir, "sessions");
|
|
25291
25761
|
if (!await fileExists(sessionsRoot)) return null;
|
|
25292
|
-
const entries = await
|
|
25762
|
+
const entries = await readdir22(sessionsRoot, { withFileTypes: true });
|
|
25293
25763
|
let best = null;
|
|
25294
25764
|
for (const entry of entries) {
|
|
25295
25765
|
if (!entry.isDirectory()) continue;
|
|
25296
|
-
const summaryPath =
|
|
25766
|
+
const summaryPath = resolve65(sessionsRoot, entry.name, "summary.md");
|
|
25297
25767
|
if (!await fileExists(summaryPath)) continue;
|
|
25298
25768
|
const st = await stat10(summaryPath);
|
|
25299
25769
|
if (best === null || st.mtime.getTime() > best.mtime.getTime()) {
|
|
@@ -25303,9 +25773,9 @@ async function findLatestSessionSummary(assignmentDir) {
|
|
|
25303
25773
|
return best;
|
|
25304
25774
|
}
|
|
25305
25775
|
async function findOpenHandoff(assignmentDir) {
|
|
25306
|
-
const handoffPath =
|
|
25776
|
+
const handoffPath = resolve65(assignmentDir, "handoff.md");
|
|
25307
25777
|
if (!await fileExists(handoffPath)) return null;
|
|
25308
|
-
const content = await
|
|
25778
|
+
const content = await readFile44(handoffPath, "utf-8");
|
|
25309
25779
|
const body = content.replace(/^---[\s\S]*?\n---\n?/, "").trim();
|
|
25310
25780
|
if (body.length === 0) return null;
|
|
25311
25781
|
if (/^<!--[\s\S]*-->$/.test(body)) return null;
|
|
@@ -25413,13 +25883,13 @@ init_git_worktree();
|
|
|
25413
25883
|
init_fs();
|
|
25414
25884
|
init_paths();
|
|
25415
25885
|
import { Command as Command9 } from "commander";
|
|
25416
|
-
import { readFile as
|
|
25417
|
-
import { resolve as
|
|
25886
|
+
import { readFile as readFile45 } from "fs/promises";
|
|
25887
|
+
import { resolve as resolve66 } from "path";
|
|
25418
25888
|
async function readContext2(cwd) {
|
|
25419
|
-
const path =
|
|
25889
|
+
const path = resolve66(cwd, ".syntaur", "context.json");
|
|
25420
25890
|
if (!await fileExists(path)) return null;
|
|
25421
25891
|
try {
|
|
25422
|
-
return JSON.parse(await
|
|
25892
|
+
return JSON.parse(await readFile45(path, "utf-8"));
|
|
25423
25893
|
} catch {
|
|
25424
25894
|
return null;
|
|
25425
25895
|
}
|
|
@@ -25427,7 +25897,7 @@ async function readContext2(cwd) {
|
|
|
25427
25897
|
async function resolveAssignmentPath2(opts) {
|
|
25428
25898
|
if (opts.assignment) {
|
|
25429
25899
|
if (opts.project) {
|
|
25430
|
-
return
|
|
25900
|
+
return resolve66(
|
|
25431
25901
|
defaultProjectDir(),
|
|
25432
25902
|
opts.project,
|
|
25433
25903
|
"assignments",
|
|
@@ -25435,10 +25905,10 @@ async function resolveAssignmentPath2(opts) {
|
|
|
25435
25905
|
"assignment.md"
|
|
25436
25906
|
);
|
|
25437
25907
|
}
|
|
25438
|
-
return
|
|
25908
|
+
return resolve66(assignmentsDir(), opts.assignment, "assignment.md");
|
|
25439
25909
|
}
|
|
25440
25910
|
const ctx = await readContext2(opts.cwd);
|
|
25441
|
-
if (ctx?.assignmentDir) return
|
|
25911
|
+
if (ctx?.assignmentDir) return resolve66(ctx.assignmentDir, "assignment.md");
|
|
25442
25912
|
throw new Error(
|
|
25443
25913
|
"No active assignment. Pass --assignment <slug> [--project <slug>] or run from a workspace with .syntaur/context.json."
|
|
25444
25914
|
);
|
|
@@ -25449,7 +25919,7 @@ async function runWorktreeCreate(options, cwd = process.cwd()) {
|
|
|
25449
25919
|
}
|
|
25450
25920
|
const repository = options.repository ?? cwd;
|
|
25451
25921
|
const parentBranch = options.parentBranch ?? "main";
|
|
25452
|
-
const worktreePath = options.worktreePath ??
|
|
25922
|
+
const worktreePath = options.worktreePath ?? resolve66(repository, ".worktrees", options.branch);
|
|
25453
25923
|
const assignmentPath = await resolveAssignmentPath2({
|
|
25454
25924
|
assignment: options.assignment,
|
|
25455
25925
|
project: options.project,
|
|
@@ -25486,13 +25956,13 @@ init_paths();
|
|
|
25486
25956
|
init_fs();
|
|
25487
25957
|
init_slug();
|
|
25488
25958
|
import { Command as Command10 } from "commander";
|
|
25489
|
-
import { resolve as
|
|
25959
|
+
import { resolve as resolve68 } from "path";
|
|
25490
25960
|
|
|
25491
25961
|
// src/utils/project-indexes.ts
|
|
25492
25962
|
init_parser();
|
|
25493
25963
|
init_fs();
|
|
25494
|
-
import { readdir as
|
|
25495
|
-
import { resolve as
|
|
25964
|
+
import { readdir as readdir23, readFile as readFile46 } from "fs/promises";
|
|
25965
|
+
import { resolve as resolve67 } from "path";
|
|
25496
25966
|
function nowIso2() {
|
|
25497
25967
|
return (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
25498
25968
|
}
|
|
@@ -25501,7 +25971,7 @@ function readProjectSlug(projectDir) {
|
|
|
25501
25971
|
}
|
|
25502
25972
|
async function listSlugFiles(dir) {
|
|
25503
25973
|
if (!await fileExists(dir)) return [];
|
|
25504
|
-
const entries = await
|
|
25974
|
+
const entries = await readdir23(dir, { withFileTypes: true });
|
|
25505
25975
|
return entries.filter((e) => e.isFile() && e.name.endsWith(".md") && !e.name.startsWith("_")).map((e) => e.name).sort();
|
|
25506
25976
|
}
|
|
25507
25977
|
function escapeCell(value) {
|
|
@@ -25511,7 +25981,7 @@ function joinList(items) {
|
|
|
25511
25981
|
return items.length === 0 ? "\u2014" : items.map(escapeCell).join(", ");
|
|
25512
25982
|
}
|
|
25513
25983
|
async function rebuildResourcesIndex(projectDir) {
|
|
25514
|
-
const dir =
|
|
25984
|
+
const dir = resolve67(projectDir, "resources");
|
|
25515
25985
|
await ensureDir(dir);
|
|
25516
25986
|
const files = await listSlugFiles(dir);
|
|
25517
25987
|
const slug = readProjectSlug(projectDir);
|
|
@@ -25527,7 +25997,7 @@ async function rebuildResourcesIndex(projectDir) {
|
|
|
25527
25997
|
lines.push("| Name | Category | Source | Related Assignments | Updated |");
|
|
25528
25998
|
lines.push("|------|----------|--------|---------------------|---------|");
|
|
25529
25999
|
for (const fileName of files) {
|
|
25530
|
-
const content = await
|
|
26000
|
+
const content = await readFile46(resolve67(dir, fileName), "utf-8");
|
|
25531
26001
|
const parsed = parseResource(content);
|
|
25532
26002
|
const slugBase = fileName.replace(/\.md$/, "");
|
|
25533
26003
|
const name = parsed.name || slugBase;
|
|
@@ -25537,12 +26007,12 @@ async function rebuildResourcesIndex(projectDir) {
|
|
|
25537
26007
|
);
|
|
25538
26008
|
}
|
|
25539
26009
|
lines.push("");
|
|
25540
|
-
const indexPath =
|
|
26010
|
+
const indexPath = resolve67(dir, "_index.md");
|
|
25541
26011
|
await writeFileForce(indexPath, lines.join("\n"));
|
|
25542
26012
|
return { total: files.length, path: indexPath };
|
|
25543
26013
|
}
|
|
25544
26014
|
async function rebuildMemoriesIndex(projectDir) {
|
|
25545
|
-
const dir =
|
|
26015
|
+
const dir = resolve67(projectDir, "memories");
|
|
25546
26016
|
await ensureDir(dir);
|
|
25547
26017
|
const files = await listSlugFiles(dir);
|
|
25548
26018
|
const slug = readProjectSlug(projectDir);
|
|
@@ -25558,7 +26028,7 @@ async function rebuildMemoriesIndex(projectDir) {
|
|
|
25558
26028
|
lines.push("| Name | Source | Scope | Source Assignment | Updated |");
|
|
25559
26029
|
lines.push("|------|--------|-------|-------------------|---------|");
|
|
25560
26030
|
for (const fileName of files) {
|
|
25561
|
-
const content = await
|
|
26031
|
+
const content = await readFile46(resolve67(dir, fileName), "utf-8");
|
|
25562
26032
|
const parsed = parseMemory(content);
|
|
25563
26033
|
const slugBase = fileName.replace(/\.md$/, "");
|
|
25564
26034
|
const name = parsed.name || slugBase;
|
|
@@ -25568,7 +26038,7 @@ async function rebuildMemoriesIndex(projectDir) {
|
|
|
25568
26038
|
);
|
|
25569
26039
|
}
|
|
25570
26040
|
lines.push("");
|
|
25571
|
-
const indexPath =
|
|
26041
|
+
const indexPath = resolve67(dir, "_index.md");
|
|
25572
26042
|
await writeFileForce(indexPath, lines.join("\n"));
|
|
25573
26043
|
return { total: files.length, path: indexPath };
|
|
25574
26044
|
}
|
|
@@ -25606,8 +26076,8 @@ async function runResourceAdd(options) {
|
|
|
25606
26076
|
if (!isValidSlug(options.project)) {
|
|
25607
26077
|
throw new Error(`Invalid project slug: "${options.project}".`);
|
|
25608
26078
|
}
|
|
25609
|
-
const projectDir =
|
|
25610
|
-
if (!await fileExists(
|
|
26079
|
+
const projectDir = resolve68(defaultProjectDir(), options.project);
|
|
26080
|
+
if (!await fileExists(resolve68(projectDir, "project.md"))) {
|
|
25611
26081
|
throw new Error(`Project "${options.project}" not found at ${projectDir}.`);
|
|
25612
26082
|
}
|
|
25613
26083
|
if (!options.name) throw new Error("--name is required.");
|
|
@@ -25616,7 +26086,7 @@ async function runResourceAdd(options) {
|
|
|
25616
26086
|
if (!isValidSlug(slug)) {
|
|
25617
26087
|
throw new Error(`Invalid resource slug: "${slug}".`);
|
|
25618
26088
|
}
|
|
25619
|
-
const filePath =
|
|
26089
|
+
const filePath = resolve68(projectDir, "resources", `${slug}.md`);
|
|
25620
26090
|
if (await fileExists(filePath) && !options.force) {
|
|
25621
26091
|
throw new Error(
|
|
25622
26092
|
`Resource "${slug}" already exists at ${filePath}. Use --force to overwrite.`
|
|
@@ -25651,7 +26121,7 @@ init_paths();
|
|
|
25651
26121
|
init_fs();
|
|
25652
26122
|
init_slug();
|
|
25653
26123
|
import { Command as Command11 } from "commander";
|
|
25654
|
-
import { resolve as
|
|
26124
|
+
import { resolve as resolve69 } from "path";
|
|
25655
26125
|
function nowIso4() {
|
|
25656
26126
|
return (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
25657
26127
|
}
|
|
@@ -25686,8 +26156,8 @@ async function runMemoryAdd(options) {
|
|
|
25686
26156
|
if (!isValidSlug(options.project)) {
|
|
25687
26157
|
throw new Error(`Invalid project slug: "${options.project}".`);
|
|
25688
26158
|
}
|
|
25689
|
-
const projectDir =
|
|
25690
|
-
if (!await fileExists(
|
|
26159
|
+
const projectDir = resolve69(defaultProjectDir(), options.project);
|
|
26160
|
+
if (!await fileExists(resolve69(projectDir, "project.md"))) {
|
|
25691
26161
|
throw new Error(`Project "${options.project}" not found at ${projectDir}.`);
|
|
25692
26162
|
}
|
|
25693
26163
|
if (!options.name) throw new Error("--name is required.");
|
|
@@ -25696,7 +26166,7 @@ async function runMemoryAdd(options) {
|
|
|
25696
26166
|
if (!isValidSlug(slug)) {
|
|
25697
26167
|
throw new Error(`Invalid memory slug: "${slug}".`);
|
|
25698
26168
|
}
|
|
25699
|
-
const filePath =
|
|
26169
|
+
const filePath = resolve69(projectDir, "memories", `${slug}.md`);
|
|
25700
26170
|
if (await fileExists(filePath) && !options.force) {
|
|
25701
26171
|
throw new Error(
|
|
25702
26172
|
`Memory "${slug}" already exists at ${filePath}. Use --force to overwrite.`
|
|
@@ -25733,8 +26203,8 @@ init_paths();
|
|
|
25733
26203
|
init_fs();
|
|
25734
26204
|
init_frontmatter();
|
|
25735
26205
|
import { Command as Command12 } from "commander";
|
|
25736
|
-
import { readFile as
|
|
25737
|
-
import { resolve as
|
|
26206
|
+
import { readFile as readFile47 } from "fs/promises";
|
|
26207
|
+
import { resolve as resolve70 } from "path";
|
|
25738
26208
|
var AGE_PATTERN = /^(\d+)([dhwm])$/i;
|
|
25739
26209
|
function parseAgeToCutoff(age) {
|
|
25740
26210
|
const match = age.match(AGE_PATTERN);
|
|
@@ -25753,7 +26223,7 @@ function parseAgeToCutoff(age) {
|
|
|
25753
26223
|
}
|
|
25754
26224
|
function assignmentMdPath(item) {
|
|
25755
26225
|
if (item.projectSlug) {
|
|
25756
|
-
return
|
|
26226
|
+
return resolve70(
|
|
25757
26227
|
defaultProjectDir(),
|
|
25758
26228
|
item.projectSlug,
|
|
25759
26229
|
"assignments",
|
|
@@ -25761,13 +26231,13 @@ function assignmentMdPath(item) {
|
|
|
25761
26231
|
"assignment.md"
|
|
25762
26232
|
);
|
|
25763
26233
|
}
|
|
25764
|
-
return
|
|
26234
|
+
return resolve70(assignmentsDir(), item.id, "assignment.md");
|
|
25765
26235
|
}
|
|
25766
26236
|
async function loadTags(item) {
|
|
25767
26237
|
const path = assignmentMdPath(item);
|
|
25768
26238
|
if (!await fileExists(path)) return [];
|
|
25769
26239
|
try {
|
|
25770
|
-
const content = await
|
|
26240
|
+
const content = await readFile47(path, "utf-8");
|
|
25771
26241
|
return parseAssignmentFrontmatter(content).tags;
|
|
25772
26242
|
} catch {
|
|
25773
26243
|
return [];
|
|
@@ -25845,10 +26315,10 @@ var lsCommand = new Command12("ls").description(
|
|
|
25845
26315
|
|
|
25846
26316
|
// src/cli-default-command.ts
|
|
25847
26317
|
init_config2();
|
|
25848
|
-
import { readdir as
|
|
26318
|
+
import { readdir as readdir24 } from "fs/promises";
|
|
25849
26319
|
async function hasAnyProjectContent(projectsDir2) {
|
|
25850
26320
|
try {
|
|
25851
|
-
const entries = await
|
|
26321
|
+
const entries = await readdir24(projectsDir2, { withFileTypes: true });
|
|
25852
26322
|
return entries.some((entry) => entry.isDirectory());
|
|
25853
26323
|
} catch {
|
|
25854
26324
|
return false;
|
|
@@ -25885,20 +26355,20 @@ async function getDefaultCommandName() {
|
|
|
25885
26355
|
init_paths();
|
|
25886
26356
|
init_fs();
|
|
25887
26357
|
import { fileURLToPath as fileURLToPath10 } from "url";
|
|
25888
|
-
import { readFile as
|
|
25889
|
-
import { dirname as dirname20, join as join14, resolve as
|
|
26358
|
+
import { readFile as readFile49 } from "fs/promises";
|
|
26359
|
+
import { dirname as dirname20, join as join14, resolve as resolve71 } from "path";
|
|
25890
26360
|
import { spawn as spawn9 } from "child_process";
|
|
25891
26361
|
import { createInterface as createInterface2 } from "readline/promises";
|
|
25892
26362
|
|
|
25893
26363
|
// src/utils/version.ts
|
|
25894
26364
|
import { fileURLToPath as fileURLToPath9 } from "url";
|
|
25895
|
-
import { readFile as
|
|
26365
|
+
import { readFile as readFile48 } from "fs/promises";
|
|
25896
26366
|
import { dirname as dirname19, join as join13 } from "path";
|
|
25897
26367
|
async function readPackageVersion(scriptUrl) {
|
|
25898
26368
|
try {
|
|
25899
26369
|
const scriptPath = fileURLToPath9(scriptUrl);
|
|
25900
26370
|
const pkgRoot = dirname19(dirname19(scriptPath));
|
|
25901
|
-
const raw = await
|
|
26371
|
+
const raw = await readFile48(join13(pkgRoot, "package.json"), "utf-8");
|
|
25902
26372
|
const parsed = JSON.parse(raw);
|
|
25903
26373
|
return typeof parsed.version === "string" ? parsed.version : null;
|
|
25904
26374
|
} catch {
|
|
@@ -25907,7 +26377,7 @@ async function readPackageVersion(scriptUrl) {
|
|
|
25907
26377
|
}
|
|
25908
26378
|
|
|
25909
26379
|
// src/utils/npx-prompt.ts
|
|
25910
|
-
var STATE_FILE =
|
|
26380
|
+
var STATE_FILE = resolve71(syntaurRoot(), "npx-install.json");
|
|
25911
26381
|
var META_ARGS2 = /* @__PURE__ */ new Set(["-h", "--help", "-V", "--version", "help"]);
|
|
25912
26382
|
var GLOBAL_VERSION_TIMEOUT_MS = 2e3;
|
|
25913
26383
|
function isRunningViaNpx(scriptUrl) {
|
|
@@ -25928,7 +26398,7 @@ function isRunningViaNpx(scriptUrl) {
|
|
|
25928
26398
|
async function readState() {
|
|
25929
26399
|
if (!await fileExists(STATE_FILE)) return null;
|
|
25930
26400
|
try {
|
|
25931
|
-
const raw = await
|
|
26401
|
+
const raw = await readFile49(STATE_FILE, "utf-8");
|
|
25932
26402
|
return JSON.parse(raw);
|
|
25933
26403
|
} catch {
|
|
25934
26404
|
return null;
|
|
@@ -25987,7 +26457,7 @@ async function readGlobalVersion() {
|
|
|
25987
26457
|
try {
|
|
25988
26458
|
const manifestPath = join14(rootPath, "syntaur", "package.json");
|
|
25989
26459
|
if (!await fileExists(manifestPath)) return null;
|
|
25990
|
-
const raw = await
|
|
26460
|
+
const raw = await readFile49(manifestPath, "utf-8");
|
|
25991
26461
|
const parsed = JSON.parse(raw);
|
|
25992
26462
|
return typeof parsed.version === "string" ? parsed.version : null;
|
|
25993
26463
|
} catch {
|