syntaur 0.4.5 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dashboard/dist/assets/{_basePickBy-ZY1FrcKw.js → _basePickBy-Bcut0btZ.js} +1 -1
- package/dashboard/dist/assets/{_baseUniq-C7xZB6ea.js → _baseUniq-AQSP2JEk.js} +1 -1
- package/dashboard/dist/assets/{arc-CPtDVk1A.js → arc-BLTpY9lc.js} +1 -1
- package/dashboard/dist/assets/{architectureDiagram-2XIMDMQ5-Dr5rnxwf.js → architectureDiagram-2XIMDMQ5-CJtwMY_X.js} +1 -1
- package/dashboard/dist/assets/{blockDiagram-WCTKOSBZ-SsDboTb2.js → blockDiagram-WCTKOSBZ-Don-O7X7.js} +1 -1
- package/dashboard/dist/assets/{c4Diagram-IC4MRINW-CZqjXmV0.js → c4Diagram-IC4MRINW-C_M3yTTB.js} +1 -1
- package/dashboard/dist/assets/channel-BfXmPwE5.js +1 -0
- package/dashboard/dist/assets/{chunk-4BX2VUAB-BYskd63Z.js → chunk-4BX2VUAB-CGss0jXe.js} +1 -1
- package/dashboard/dist/assets/{chunk-55IACEB6-CWLImr1E.js → chunk-55IACEB6-BatoPJga.js} +1 -1
- package/dashboard/dist/assets/{chunk-FMBD7UC4-fQXSIXhy.js → chunk-FMBD7UC4-DxH4wO82.js} +1 -1
- package/dashboard/dist/assets/{chunk-JSJVCQXG-DJXtEexG.js → chunk-JSJVCQXG-BL3izAFQ.js} +1 -1
- package/dashboard/dist/assets/{chunk-KX2RTZJC-C0ivaZ1M.js → chunk-KX2RTZJC-GnqXwnge.js} +1 -1
- package/dashboard/dist/assets/{chunk-NQ4KR5QH-DlwLjqC5.js → chunk-NQ4KR5QH-gvCn4QMb.js} +1 -1
- package/dashboard/dist/assets/{chunk-QZHKN3VN-DBEYrRqx.js → chunk-QZHKN3VN-CYGWogyi.js} +1 -1
- package/dashboard/dist/assets/{chunk-WL4C6EOR-BQyYCp1z.js → chunk-WL4C6EOR-D9mVTQ1F.js} +1 -1
- package/dashboard/dist/assets/classDiagram-VBA2DB6C-D7_G1qy0.js +1 -0
- package/dashboard/dist/assets/classDiagram-v2-RAHNMMFH-D7_G1qy0.js +1 -0
- package/dashboard/dist/assets/clone-BKG-N796.js +1 -0
- package/dashboard/dist/assets/{cose-bilkent-S5V4N54A-CAYzelqd.js → cose-bilkent-S5V4N54A-CUWQCKt4.js} +1 -1
- package/dashboard/dist/assets/{dagre-KLK3FWXG-xpTJb8E7.js → dagre-KLK3FWXG-CH3ijEvV.js} +1 -1
- package/dashboard/dist/assets/{diagram-E7M64L7V-DRDjskHd.js → diagram-E7M64L7V-sq83lpV1.js} +1 -1
- package/dashboard/dist/assets/{diagram-IFDJBPK2-B1r1ZXm3.js → diagram-IFDJBPK2-BzQG_rtq.js} +1 -1
- package/dashboard/dist/assets/{diagram-P4PSJMXO-BeE6-ZUH.js → diagram-P4PSJMXO-Dg0eZn0q.js} +1 -1
- package/dashboard/dist/assets/{erDiagram-INFDFZHY-BG6KKBm1.js → erDiagram-INFDFZHY-4b9eQ0uj.js} +1 -1
- package/dashboard/dist/assets/{flowDiagram-PKNHOUZH-CSFv3RpZ.js → flowDiagram-PKNHOUZH-C9fzKcsZ.js} +1 -1
- package/dashboard/dist/assets/{ganttDiagram-A5KZAMGK-Off4zK-k.js → ganttDiagram-A5KZAMGK-Bzt6i9SH.js} +1 -1
- package/dashboard/dist/assets/{gitGraphDiagram-K3NZZRJ6-Msv_4mYB.js → gitGraphDiagram-K3NZZRJ6-D0wFOagh.js} +1 -1
- package/dashboard/dist/assets/{graph-rPPnNEMq.js → graph-EEIGvqDh.js} +1 -1
- package/dashboard/dist/assets/index-C7f0ySJE.js +481 -0
- package/dashboard/dist/assets/{infoDiagram-LFFYTUFH-CeBirxFd.js → infoDiagram-LFFYTUFH-DLYMsj1D.js} +1 -1
- package/dashboard/dist/assets/{ishikawaDiagram-PHBUUO56-BZ_w8PcD.js → ishikawaDiagram-PHBUUO56-DVebKkzl.js} +1 -1
- package/dashboard/dist/assets/{journeyDiagram-4ABVD52K-3FqMPEfs.js → journeyDiagram-4ABVD52K-BsmgOWVw.js} +1 -1
- package/dashboard/dist/assets/{kanban-definition-K7BYSVSG-BlCmFkEx.js → kanban-definition-K7BYSVSG-BTnHf0ey.js} +1 -1
- package/dashboard/dist/assets/{layout-C50nYMhx.js → layout-BbM7HRvv.js} +1 -1
- package/dashboard/dist/assets/{linear-D2Cjmh1X.js → linear-C37bJKPO.js} +1 -1
- package/dashboard/dist/assets/{mermaid.core-BXFuNYa4.js → mermaid.core-MZ_JgnRL.js} +4 -4
- package/dashboard/dist/assets/{mindmap-definition-YRQLILUH-CowIfM07.js → mindmap-definition-YRQLILUH-CgHS4hFo.js} +1 -1
- package/dashboard/dist/assets/{pieDiagram-SKSYHLDU-DG5IQcKl.js → pieDiagram-SKSYHLDU-CmAgopJe.js} +1 -1
- package/dashboard/dist/assets/{quadrantDiagram-337W2JSQ-DKD46CSX.js → quadrantDiagram-337W2JSQ-BvzYUPR6.js} +1 -1
- package/dashboard/dist/assets/{requirementDiagram-Z7DCOOCP-Dd-qX6Ul.js → requirementDiagram-Z7DCOOCP-Bs52VP7k.js} +1 -1
- package/dashboard/dist/assets/{sankeyDiagram-WA2Y5GQK-CDAylULt.js → sankeyDiagram-WA2Y5GQK-aXvGPR1o.js} +1 -1
- package/dashboard/dist/assets/{sequenceDiagram-2WXFIKYE-D-5Jq5jy.js → sequenceDiagram-2WXFIKYE-CzgcfU6K.js} +1 -1
- package/dashboard/dist/assets/{stateDiagram-RAJIS63D-BmBOa8i0.js → stateDiagram-RAJIS63D-BXBJf9Hq.js} +1 -1
- package/dashboard/dist/assets/stateDiagram-v2-FVOUBMTO-QqOtsuOs.js +1 -0
- package/dashboard/dist/assets/{timeline-definition-YZTLITO2-C9Sdg7du.js → timeline-definition-YZTLITO2-BsXp26Ai.js} +1 -1
- package/dashboard/dist/assets/{treemap-KZPCXAKY-D-PDxUdK.js → treemap-KZPCXAKY-C3WbDii1.js} +1 -1
- package/dashboard/dist/assets/{vennDiagram-LZ73GAT5-CBO5f6MT.js → vennDiagram-LZ73GAT5-B28LMHWd.js} +1 -1
- package/dashboard/dist/assets/{xychartDiagram-JWTSCODW-4xZrquqP.js → xychartDiagram-JWTSCODW-C3Xwz8mS.js} +1 -1
- package/dashboard/dist/index.html +1 -1
- package/dist/dashboard/server.js +726 -115
- package/dist/dashboard/server.js.map +1 -1
- package/dist/index.js +1019 -381
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dashboard/dist/assets/channel-ejDeCb7i.js +0 -1
- package/dashboard/dist/assets/classDiagram-VBA2DB6C-BW_esmsF.js +0 -1
- package/dashboard/dist/assets/classDiagram-v2-RAHNMMFH-BW_esmsF.js +0 -1
- package/dashboard/dist/assets/clone-y13L00zF.js +0 -1
- package/dashboard/dist/assets/index-D_uE8gHg.js +0 -481
- package/dashboard/dist/assets/stateDiagram-v2-FVOUBMTO-Dsk6o9iT.js +0 -1
package/dist/index.js
CHANGED
|
@@ -39,6 +39,9 @@ function playbooksDir() {
|
|
|
39
39
|
function todosDir() {
|
|
40
40
|
return resolve(syntaurRoot(), "todos");
|
|
41
41
|
}
|
|
42
|
+
function projectTodosDir(projectsDir2, projectSlug) {
|
|
43
|
+
return resolve(projectsDir2, projectSlug, "todos");
|
|
44
|
+
}
|
|
42
45
|
var init_paths = __esm({
|
|
43
46
|
"src/utils/paths.ts"() {
|
|
44
47
|
"use strict";
|
|
@@ -91,10 +94,10 @@ var init_fs = __esm({
|
|
|
91
94
|
});
|
|
92
95
|
|
|
93
96
|
// src/templates/config.ts
|
|
94
|
-
function renderConfig(
|
|
97
|
+
function renderConfig(params2) {
|
|
95
98
|
return `---
|
|
96
99
|
version: "1.0"
|
|
97
|
-
defaultProjectDir: ${
|
|
100
|
+
defaultProjectDir: ${params2.defaultProjectDir}
|
|
98
101
|
onboarding:
|
|
99
102
|
completed: false
|
|
100
103
|
agentDefaults:
|
|
@@ -4786,8 +4789,8 @@ __export(launch_exports, {
|
|
|
4786
4789
|
launchAgent: () => launchAgent
|
|
4787
4790
|
});
|
|
4788
4791
|
import { spawn as spawn2 } from "child_process";
|
|
4789
|
-
import { mkdir as
|
|
4790
|
-
import { resolve as
|
|
4792
|
+
import { mkdir as mkdir5, writeFile as writeFile9 } from "fs/promises";
|
|
4793
|
+
import { resolve as resolve32 } from "path";
|
|
4791
4794
|
async function launchAgent(options) {
|
|
4792
4795
|
const { projectsDir: projectsDir2, projectSlug, assignmentSlug, agent } = options;
|
|
4793
4796
|
const command = AGENT_COMMANDS[agent];
|
|
@@ -4797,10 +4800,10 @@ async function launchAgent(options) {
|
|
|
4797
4800
|
process.exit(1);
|
|
4798
4801
|
}
|
|
4799
4802
|
const workspaceDir = detail.workspace.worktreePath ?? (detail.workspace.repository?.startsWith("/") ? detail.workspace.repository : null) ?? process.cwd();
|
|
4800
|
-
const projectDir =
|
|
4801
|
-
const assignmentDir =
|
|
4802
|
-
const contextDir =
|
|
4803
|
-
await
|
|
4803
|
+
const projectDir = resolve32(projectsDir2, projectSlug);
|
|
4804
|
+
const assignmentDir = resolve32(projectDir, "assignments", assignmentSlug);
|
|
4805
|
+
const contextDir = resolve32(workspaceDir, ".syntaur");
|
|
4806
|
+
await mkdir5(contextDir, { recursive: true });
|
|
4804
4807
|
const context = {
|
|
4805
4808
|
projectSlug,
|
|
4806
4809
|
assignmentSlug,
|
|
@@ -4812,7 +4815,7 @@ async function launchAgent(options) {
|
|
|
4812
4815
|
grabbedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4813
4816
|
};
|
|
4814
4817
|
await writeFile9(
|
|
4815
|
-
|
|
4818
|
+
resolve32(contextDir, "context.json"),
|
|
4816
4819
|
JSON.stringify(context, null, 2) + "\n"
|
|
4817
4820
|
);
|
|
4818
4821
|
return new Promise((resolvePromise, reject) => {
|
|
@@ -4943,14 +4946,14 @@ init_config2();
|
|
|
4943
4946
|
init_config();
|
|
4944
4947
|
|
|
4945
4948
|
// src/templates/manifest.ts
|
|
4946
|
-
function renderManifest(
|
|
4949
|
+
function renderManifest(params2) {
|
|
4947
4950
|
return `---
|
|
4948
4951
|
version: "2.0"
|
|
4949
|
-
project: ${
|
|
4950
|
-
generated: "${
|
|
4952
|
+
project: ${params2.slug}
|
|
4953
|
+
generated: "${params2.timestamp}"
|
|
4951
4954
|
---
|
|
4952
4955
|
|
|
4953
|
-
# Project: ${
|
|
4956
|
+
# Project: ${params2.slug}
|
|
4954
4957
|
|
|
4955
4958
|
## Overview
|
|
4956
4959
|
- [Project Overview](./project.md)
|
|
@@ -4977,24 +4980,24 @@ function escapeYamlString(value) {
|
|
|
4977
4980
|
}
|
|
4978
4981
|
|
|
4979
4982
|
// src/templates/project.ts
|
|
4980
|
-
function renderProject(
|
|
4981
|
-
const safeTitle = escapeYamlString(
|
|
4982
|
-
const workspaceLine =
|
|
4983
|
-
workspace: ${
|
|
4983
|
+
function renderProject(params2) {
|
|
4984
|
+
const safeTitle = escapeYamlString(params2.title);
|
|
4985
|
+
const workspaceLine = params2.workspace ? `
|
|
4986
|
+
workspace: ${params2.workspace}` : "";
|
|
4984
4987
|
return `---
|
|
4985
|
-
id: ${
|
|
4986
|
-
slug: ${
|
|
4988
|
+
id: ${params2.id}
|
|
4989
|
+
slug: ${params2.slug}
|
|
4987
4990
|
title: ${safeTitle}
|
|
4988
4991
|
archived: false
|
|
4989
4992
|
archivedAt: null
|
|
4990
4993
|
archivedReason: null
|
|
4991
|
-
created: "${
|
|
4992
|
-
updated: "${
|
|
4994
|
+
created: "${params2.timestamp}"
|
|
4995
|
+
updated: "${params2.timestamp}"
|
|
4993
4996
|
externalIds: []
|
|
4994
4997
|
tags: []${workspaceLine}
|
|
4995
4998
|
---
|
|
4996
4999
|
|
|
4997
|
-
# ${
|
|
5000
|
+
# ${params2.title}
|
|
4998
5001
|
|
|
4999
5002
|
## Overview
|
|
5000
5003
|
|
|
@@ -5007,17 +5010,17 @@ tags: []${workspaceLine}
|
|
|
5007
5010
|
}
|
|
5008
5011
|
|
|
5009
5012
|
// src/templates/assignment.ts
|
|
5010
|
-
function renderAssignment(
|
|
5011
|
-
const safeTitle = escapeYamlString(
|
|
5012
|
-
const dependsOnYaml =
|
|
5013
|
-
- ${
|
|
5014
|
-
const linksYaml =
|
|
5015
|
-
- ${
|
|
5016
|
-
const projectYaml = `project: ${
|
|
5017
|
-
const workspaceGroupLine =
|
|
5018
|
-
workspaceGroup: ${
|
|
5019
|
-
const typeYaml = `type: ${
|
|
5020
|
-
const todosSection =
|
|
5013
|
+
function renderAssignment(params2) {
|
|
5014
|
+
const safeTitle = escapeYamlString(params2.title);
|
|
5015
|
+
const dependsOnYaml = params2.dependsOn.length === 0 ? "dependsOn: []" : `dependsOn:
|
|
5016
|
+
- ${params2.dependsOn.join("\n - ")}`;
|
|
5017
|
+
const linksYaml = params2.links.length === 0 ? "links: []" : `links:
|
|
5018
|
+
- ${params2.links.join("\n - ")}`;
|
|
5019
|
+
const projectYaml = `project: ${params2.project == null ? "null" : params2.project}`;
|
|
5020
|
+
const workspaceGroupLine = params2.workspaceGroup ? `
|
|
5021
|
+
workspaceGroup: ${params2.workspaceGroup}` : "";
|
|
5022
|
+
const typeYaml = `type: ${params2.type ?? "feature"}`;
|
|
5023
|
+
const todosSection = params2.includeTodos ? `## Todos
|
|
5021
5024
|
|
|
5022
5025
|
<!--
|
|
5023
5026
|
Checklist of work items for this assignment. Items may be simple tasks
|
|
@@ -5029,15 +5032,15 @@ Never delete superseded todos \u2014 preserve the history.
|
|
|
5029
5032
|
|
|
5030
5033
|
` : "";
|
|
5031
5034
|
return `---
|
|
5032
|
-
id: ${
|
|
5033
|
-
slug: ${
|
|
5035
|
+
id: ${params2.id}
|
|
5036
|
+
slug: ${params2.slug}
|
|
5034
5037
|
title: ${safeTitle}
|
|
5035
5038
|
${projectYaml}${workspaceGroupLine}
|
|
5036
5039
|
${typeYaml}
|
|
5037
5040
|
status: pending
|
|
5038
|
-
priority: ${
|
|
5039
|
-
created: "${
|
|
5040
|
-
updated: "${
|
|
5041
|
+
priority: ${params2.priority}
|
|
5042
|
+
created: "${params2.timestamp}"
|
|
5043
|
+
updated: "${params2.timestamp}"
|
|
5041
5044
|
assignee: null
|
|
5042
5045
|
externalIds: []
|
|
5043
5046
|
${dependsOnYaml}
|
|
@@ -5051,7 +5054,7 @@ workspace:
|
|
|
5051
5054
|
tags: []
|
|
5052
5055
|
---
|
|
5053
5056
|
|
|
5054
|
-
# ${
|
|
5057
|
+
# ${params2.title}
|
|
5055
5058
|
|
|
5056
5059
|
## Objective
|
|
5057
5060
|
|
|
@@ -5078,10 +5081,10 @@ ${todosSection}## Context
|
|
|
5078
5081
|
}
|
|
5079
5082
|
|
|
5080
5083
|
// src/templates/scratchpad.ts
|
|
5081
|
-
function renderScratchpad(
|
|
5084
|
+
function renderScratchpad(params2) {
|
|
5082
5085
|
return `---
|
|
5083
|
-
assignment: ${
|
|
5084
|
-
updated: "${
|
|
5086
|
+
assignment: ${params2.assignmentSlug}
|
|
5087
|
+
updated: "${params2.timestamp}"
|
|
5085
5088
|
---
|
|
5086
5089
|
|
|
5087
5090
|
# Scratchpad
|
|
@@ -5091,10 +5094,10 @@ No working notes yet.
|
|
|
5091
5094
|
}
|
|
5092
5095
|
|
|
5093
5096
|
// src/templates/handoff.ts
|
|
5094
|
-
function renderHandoff(
|
|
5097
|
+
function renderHandoff(params2) {
|
|
5095
5098
|
return `---
|
|
5096
|
-
assignment: ${
|
|
5097
|
-
updated: "${
|
|
5099
|
+
assignment: ${params2.assignmentSlug}
|
|
5100
|
+
updated: "${params2.timestamp}"
|
|
5098
5101
|
handoffCount: 0
|
|
5099
5102
|
---
|
|
5100
5103
|
|
|
@@ -5105,12 +5108,12 @@ No handoffs recorded yet.
|
|
|
5105
5108
|
}
|
|
5106
5109
|
|
|
5107
5110
|
// src/templates/progress.ts
|
|
5108
|
-
function renderProgress(
|
|
5111
|
+
function renderProgress(params2) {
|
|
5109
5112
|
return `---
|
|
5110
|
-
assignment: ${
|
|
5113
|
+
assignment: ${params2.assignment}
|
|
5111
5114
|
entryCount: 0
|
|
5112
|
-
generated: "${
|
|
5113
|
-
updated: "${
|
|
5115
|
+
generated: "${params2.timestamp}"
|
|
5116
|
+
updated: "${params2.timestamp}"
|
|
5114
5117
|
---
|
|
5115
5118
|
|
|
5116
5119
|
# Progress
|
|
@@ -5120,12 +5123,12 @@ No progress yet.
|
|
|
5120
5123
|
}
|
|
5121
5124
|
|
|
5122
5125
|
// src/templates/comments.ts
|
|
5123
|
-
function renderComments(
|
|
5126
|
+
function renderComments(params2) {
|
|
5124
5127
|
return `---
|
|
5125
|
-
assignment: ${
|
|
5128
|
+
assignment: ${params2.assignment}
|
|
5126
5129
|
entryCount: 0
|
|
5127
|
-
generated: "${
|
|
5128
|
-
updated: "${
|
|
5130
|
+
generated: "${params2.timestamp}"
|
|
5131
|
+
updated: "${params2.timestamp}"
|
|
5129
5132
|
---
|
|
5130
5133
|
|
|
5131
5134
|
# Comments
|
|
@@ -5153,10 +5156,10 @@ function formatCommentEntry(comment) {
|
|
|
5153
5156
|
}
|
|
5154
5157
|
|
|
5155
5158
|
// src/templates/decision-record.ts
|
|
5156
|
-
function renderDecisionRecord(
|
|
5159
|
+
function renderDecisionRecord(params2) {
|
|
5157
5160
|
return `---
|
|
5158
|
-
assignment: ${
|
|
5159
|
-
updated: "${
|
|
5161
|
+
assignment: ${params2.assignmentSlug}
|
|
5162
|
+
updated: "${params2.timestamp}"
|
|
5160
5163
|
decisionCount: 0
|
|
5161
5164
|
---
|
|
5162
5165
|
|
|
@@ -5167,10 +5170,10 @@ No decisions recorded yet.
|
|
|
5167
5170
|
}
|
|
5168
5171
|
|
|
5169
5172
|
// src/templates/index-stubs.ts
|
|
5170
|
-
function renderIndexAssignments(
|
|
5173
|
+
function renderIndexAssignments(params2) {
|
|
5171
5174
|
return `---
|
|
5172
|
-
project: ${
|
|
5173
|
-
generated: "${
|
|
5175
|
+
project: ${params2.slug}
|
|
5176
|
+
generated: "${params2.timestamp}"
|
|
5174
5177
|
total: 0
|
|
5175
5178
|
by_status:
|
|
5176
5179
|
pending: 0
|
|
@@ -5187,10 +5190,10 @@ by_status:
|
|
|
5187
5190
|
|------|-------|--------|----------|----------|--------------|---------|
|
|
5188
5191
|
`;
|
|
5189
5192
|
}
|
|
5190
|
-
function renderIndexPlans(
|
|
5193
|
+
function renderIndexPlans(params2) {
|
|
5191
5194
|
return `---
|
|
5192
|
-
project: ${
|
|
5193
|
-
generated: "${
|
|
5195
|
+
project: ${params2.slug}
|
|
5196
|
+
generated: "${params2.timestamp}"
|
|
5194
5197
|
---
|
|
5195
5198
|
|
|
5196
5199
|
# Plans
|
|
@@ -5199,10 +5202,10 @@ generated: "${params.timestamp}"
|
|
|
5199
5202
|
|------------|-------------|---------|
|
|
5200
5203
|
`;
|
|
5201
5204
|
}
|
|
5202
|
-
function renderIndexDecisions(
|
|
5205
|
+
function renderIndexDecisions(params2) {
|
|
5203
5206
|
return `---
|
|
5204
|
-
project: ${
|
|
5205
|
-
generated: "${
|
|
5207
|
+
project: ${params2.slug}
|
|
5208
|
+
generated: "${params2.timestamp}"
|
|
5206
5209
|
---
|
|
5207
5210
|
|
|
5208
5211
|
# Decision Records
|
|
@@ -5211,10 +5214,10 @@ generated: "${params.timestamp}"
|
|
|
5211
5214
|
|------------|-------|-----------------|---------------|---------|
|
|
5212
5215
|
`;
|
|
5213
5216
|
}
|
|
5214
|
-
function renderStatus(
|
|
5217
|
+
function renderStatus(params2) {
|
|
5215
5218
|
return `---
|
|
5216
|
-
project: ${
|
|
5217
|
-
generated: "${
|
|
5219
|
+
project: ${params2.slug}
|
|
5220
|
+
generated: "${params2.timestamp}"
|
|
5218
5221
|
status: pending
|
|
5219
5222
|
progress:
|
|
5220
5223
|
total: 0
|
|
@@ -5230,7 +5233,7 @@ needsAttention:
|
|
|
5230
5233
|
openQuestions: 0
|
|
5231
5234
|
---
|
|
5232
5235
|
|
|
5233
|
-
# Project Status: ${
|
|
5236
|
+
# Project Status: ${params2.title}
|
|
5234
5237
|
|
|
5235
5238
|
**Status:** pending
|
|
5236
5239
|
**Progress:** 0/0 assignments complete
|
|
@@ -5250,10 +5253,10 @@ No dependencies yet.
|
|
|
5250
5253
|
- **0 unanswered** questions
|
|
5251
5254
|
`;
|
|
5252
5255
|
}
|
|
5253
|
-
function renderResourcesIndex(
|
|
5256
|
+
function renderResourcesIndex(params2) {
|
|
5254
5257
|
return `---
|
|
5255
|
-
project: ${
|
|
5256
|
-
generated: "${
|
|
5258
|
+
project: ${params2.slug}
|
|
5259
|
+
generated: "${params2.timestamp}"
|
|
5257
5260
|
total: 0
|
|
5258
5261
|
---
|
|
5259
5262
|
|
|
@@ -5263,10 +5266,10 @@ total: 0
|
|
|
5263
5266
|
|------|----------|--------|---------------------|---------|
|
|
5264
5267
|
`;
|
|
5265
5268
|
}
|
|
5266
|
-
function renderMemoriesIndex(
|
|
5269
|
+
function renderMemoriesIndex(params2) {
|
|
5267
5270
|
return `---
|
|
5268
|
-
project: ${
|
|
5269
|
-
generated: "${
|
|
5271
|
+
project: ${params2.slug}
|
|
5272
|
+
generated: "${params2.timestamp}"
|
|
5270
5273
|
total: 0
|
|
5271
5274
|
---
|
|
5272
5275
|
|
|
@@ -5278,19 +5281,19 @@ total: 0
|
|
|
5278
5281
|
}
|
|
5279
5282
|
|
|
5280
5283
|
// src/templates/playbook.ts
|
|
5281
|
-
function renderPlaybook(
|
|
5282
|
-
const whenToUse =
|
|
5284
|
+
function renderPlaybook(params2) {
|
|
5285
|
+
const whenToUse = params2.whenToUse ? escapeYamlString(params2.whenToUse) : "null";
|
|
5283
5286
|
return `---
|
|
5284
|
-
name: ${escapeYamlString(
|
|
5285
|
-
slug: ${
|
|
5286
|
-
description: ${escapeYamlString(
|
|
5287
|
+
name: ${escapeYamlString(params2.name)}
|
|
5288
|
+
slug: ${params2.slug}
|
|
5289
|
+
description: ${escapeYamlString(params2.description)}
|
|
5287
5290
|
when_to_use: ${whenToUse}
|
|
5288
|
-
created: "${
|
|
5289
|
-
updated: "${
|
|
5291
|
+
created: "${params2.timestamp}"
|
|
5292
|
+
updated: "${params2.timestamp}"
|
|
5290
5293
|
tags: []
|
|
5291
5294
|
---
|
|
5292
5295
|
|
|
5293
|
-
# ${
|
|
5296
|
+
# ${params2.name}
|
|
5294
5297
|
|
|
5295
5298
|
<!-- Write imperative rules and workflows here. Keep it under 50 lines. -->
|
|
5296
5299
|
`;
|
|
@@ -5433,58 +5436,58 @@ Follow the rules in each playbook. They take precedence over default conventions
|
|
|
5433
5436
|
- Commit frequently with messages referencing the assignment slug.
|
|
5434
5437
|
`;
|
|
5435
5438
|
}
|
|
5436
|
-
function renderCursorAssignment(
|
|
5439
|
+
function renderCursorAssignment(params2) {
|
|
5437
5440
|
return `---
|
|
5438
|
-
description: Syntaur assignment context for ${
|
|
5441
|
+
description: Syntaur assignment context for ${params2.projectSlug}/${params2.assignmentSlug}
|
|
5439
5442
|
globs:
|
|
5440
5443
|
alwaysApply: true
|
|
5441
5444
|
---
|
|
5442
5445
|
|
|
5443
5446
|
# Current Assignment Context
|
|
5444
5447
|
|
|
5445
|
-
- **Project:** ${
|
|
5446
|
-
- **Assignment:** ${
|
|
5447
|
-
- **Project directory:** ${
|
|
5448
|
-
- **Assignment directory:** ${
|
|
5448
|
+
- **Project:** ${params2.projectSlug}
|
|
5449
|
+
- **Assignment:** ${params2.assignmentSlug}
|
|
5450
|
+
- **Project directory:** ${params2.projectDir}
|
|
5451
|
+
- **Assignment directory:** ${params2.assignmentDir}
|
|
5449
5452
|
|
|
5450
5453
|
## Reading Order
|
|
5451
5454
|
|
|
5452
5455
|
Before starting work, read these files in order:
|
|
5453
|
-
1. \`${
|
|
5454
|
-
2. \`${
|
|
5455
|
-
3. any \`${
|
|
5456
|
-
4. \`${
|
|
5457
|
-
5. \`${
|
|
5458
|
-
6. \`${
|
|
5456
|
+
1. \`${params2.projectDir}/project.md\` -- project overview and goals (project-nested assignments only)
|
|
5457
|
+
2. \`${params2.assignmentDir}/assignment.md\` -- your assignment details, acceptance criteria, todos, current status. Frontmatter includes \`project: <slug> | null\` (null for standalone) and \`type: <classification> | null\`.
|
|
5458
|
+
3. any \`${params2.assignmentDir}/plan*.md\` files linked from active todos in the \`## Todos\` section (may be 0, 1, or many)
|
|
5459
|
+
4. \`${params2.assignmentDir}/progress.md\` -- reverse-chron progress log (if present)
|
|
5460
|
+
5. \`${params2.assignmentDir}/comments.md\` -- threaded questions/notes/feedback (if present)
|
|
5461
|
+
6. \`${params2.assignmentDir}/handoff.md\` -- previous session handoff notes
|
|
5459
5462
|
|
|
5460
5463
|
## Your Writable Files
|
|
5461
5464
|
|
|
5462
5465
|
You may write directly to these files inside your assignment folder:
|
|
5463
|
-
- \`${
|
|
5464
|
-
- \`${
|
|
5465
|
-
- \`${
|
|
5466
|
-
- \`${
|
|
5467
|
-
- \`${
|
|
5468
|
-
- \`${
|
|
5466
|
+
- \`${params2.assignmentDir}/assignment.md\`
|
|
5467
|
+
- \`${params2.assignmentDir}/plan*.md\` (0 or more versioned plan files, e.g., \`plan.md\`, \`plan-v2.md\`)
|
|
5468
|
+
- \`${params2.assignmentDir}/progress.md\` (append timestamped entries, newest first)
|
|
5469
|
+
- \`${params2.assignmentDir}/scratchpad.md\`
|
|
5470
|
+
- \`${params2.assignmentDir}/handoff.md\`
|
|
5471
|
+
- \`${params2.assignmentDir}/decision-record.md\`
|
|
5469
5472
|
|
|
5470
|
-
Do NOT edit \`${
|
|
5473
|
+
Do NOT edit \`${params2.assignmentDir}/comments.md\` directly \u2014 use \`syntaur comment\`. Do NOT edit other assignments' files \u2014 use \`syntaur request\` for cross-assignment todos.
|
|
5471
5474
|
|
|
5472
5475
|
And source code files in your workspace. Read the \`workspace\` field from your assignment's frontmatter to determine the exact boundary. If not set, the current working directory is your workspace.
|
|
5473
5476
|
`;
|
|
5474
5477
|
}
|
|
5475
5478
|
|
|
5476
5479
|
// src/templates/codex-agents.ts
|
|
5477
|
-
function renderCodexAgents(
|
|
5480
|
+
function renderCodexAgents(params2) {
|
|
5478
5481
|
return `# Syntaur Protocol -- Agent Instructions
|
|
5479
5482
|
|
|
5480
5483
|
This project uses the Syntaur protocol for multi-agent project coordination.
|
|
5481
5484
|
|
|
5482
5485
|
## Current Assignment
|
|
5483
5486
|
|
|
5484
|
-
- **Project:** ${
|
|
5485
|
-
- **Assignment:** ${
|
|
5486
|
-
- **Project directory:** ${
|
|
5487
|
-
- **Assignment directory:** ${
|
|
5487
|
+
- **Project:** ${params2.projectSlug}
|
|
5488
|
+
- **Assignment:** ${params2.assignmentSlug}
|
|
5489
|
+
- **Project directory:** ${params2.projectDir}
|
|
5490
|
+
- **Assignment directory:** ${params2.assignmentDir}
|
|
5488
5491
|
|
|
5489
5492
|
## Preferred Workflow
|
|
5490
5493
|
|
|
@@ -5504,13 +5507,13 @@ If the plugin is unavailable, follow the same workflow manually with the \`synta
|
|
|
5504
5507
|
## Reading Order
|
|
5505
5508
|
|
|
5506
5509
|
Before starting work, read these files in order:
|
|
5507
|
-
1. \`${
|
|
5508
|
-
2. \`${
|
|
5509
|
-
3. \`${
|
|
5510
|
-
4. any \`${
|
|
5511
|
-
5. \`${
|
|
5512
|
-
6. \`${
|
|
5513
|
-
7. \`${
|
|
5510
|
+
1. \`${params2.projectDir}/manifest.md\` -- root navigation entry point (project-nested assignments only)
|
|
5511
|
+
2. \`${params2.projectDir}/project.md\` -- project overview and goals (project-nested assignments only)
|
|
5512
|
+
3. \`${params2.assignmentDir}/assignment.md\` -- your assignment details, acceptance criteria, todos, current status. Frontmatter now includes \`project: <slug> | null\` (null for standalone) and \`type: <classification> | null\`.
|
|
5513
|
+
4. any \`${params2.assignmentDir}/plan*.md\` files linked from active todos in the \`## Todos\` section (may be 0, 1, or many)
|
|
5514
|
+
5. \`${params2.assignmentDir}/progress.md\` -- reverse-chron progress log (if present)
|
|
5515
|
+
6. \`${params2.assignmentDir}/comments.md\` -- threaded questions/notes/feedback (if present)
|
|
5516
|
+
7. \`${params2.assignmentDir}/handoff.md\` -- previous session handoff notes
|
|
5514
5517
|
|
|
5515
5518
|
## Context File
|
|
5516
5519
|
|
|
@@ -5562,10 +5565,10 @@ Before starting work, read these files in order:
|
|
|
5562
5565
|
### Files you may WRITE:
|
|
5563
5566
|
1. **Your assignment folder** -- only the assignment you are currently working on:
|
|
5564
5567
|
- \`assignment.md\`, \`plan*.md\` (0 or more versioned plan files), \`progress.md\`, \`scratchpad.md\`, \`handoff.md\`, \`decision-record.md\`
|
|
5565
|
-
- Path: \`${
|
|
5568
|
+
- Path: \`${params2.assignmentDir}/\`
|
|
5566
5569
|
2. **Shared resources and memories** at the project level:
|
|
5567
|
-
- \`${
|
|
5568
|
-
- \`${
|
|
5570
|
+
- \`${params2.projectDir}/resources/<slug>.md\`
|
|
5571
|
+
- \`${params2.projectDir}/memories/<slug>.md\`
|
|
5569
5572
|
3. **Your workspace** -- source code files in the current working directory (the directory where this AGENTS.md lives). If your assignment's frontmatter specifies a \`workspace\` field, read it at runtime to determine the exact boundary.
|
|
5570
5573
|
|
|
5571
5574
|
> **Note:** Workspace boundaries are resolved by the agent at runtime by reading \`assignment.md\` frontmatter. If no \`workspace\` field is set, treat the current working directory as your workspace.
|
|
@@ -5610,15 +5613,15 @@ Before starting work, read these files in order:
|
|
|
5610
5613
|
## Lifecycle Commands
|
|
5611
5614
|
|
|
5612
5615
|
Use the \`syntaur\` CLI for state transitions and coordination:
|
|
5613
|
-
- \`syntaur assign ${
|
|
5614
|
-
- \`syntaur start ${
|
|
5615
|
-
- \`syntaur review ${
|
|
5616
|
-
- \`syntaur complete ${
|
|
5617
|
-
- \`syntaur block ${
|
|
5618
|
-
- \`syntaur unblock ${
|
|
5619
|
-
- \`syntaur fail ${
|
|
5620
|
-
- \`syntaur comment ${
|
|
5621
|
-
- \`syntaur request ${
|
|
5616
|
+
- \`syntaur assign ${params2.assignmentSlug} --agent <name> --project ${params2.projectSlug}\` -- set assignee
|
|
5617
|
+
- \`syntaur start ${params2.assignmentSlug} --project ${params2.projectSlug}\` -- pending -> in_progress
|
|
5618
|
+
- \`syntaur review ${params2.assignmentSlug} --project ${params2.projectSlug}\` -- in_progress -> review
|
|
5619
|
+
- \`syntaur complete ${params2.assignmentSlug} --project ${params2.projectSlug}\` -- in_progress/review -> completed
|
|
5620
|
+
- \`syntaur block ${params2.assignmentSlug} --project ${params2.projectSlug} --reason <text>\` -- block
|
|
5621
|
+
- \`syntaur unblock ${params2.assignmentSlug} --project ${params2.projectSlug}\` -- unblock
|
|
5622
|
+
- \`syntaur fail ${params2.assignmentSlug} --project ${params2.projectSlug}\` -- mark as failed
|
|
5623
|
+
- \`syntaur comment ${params2.assignmentSlug} "body" --type question|note|feedback [--reply-to <id>]\` -- append to \`comments.md\` (use for all Q&A; questions support resolve toggle)
|
|
5624
|
+
- \`syntaur request ${params2.assignmentSlug} <target-slug-or-uuid> "text"\` -- append a todo to another assignment's \`## Todos\` annotated \`(from: ${params2.assignmentSlug})\`
|
|
5622
5625
|
|
|
5623
5626
|
## Troubleshooting
|
|
5624
5627
|
|
|
@@ -5649,11 +5652,11 @@ Read each linked playbook and follow the rules in its body section. The \`when_t
|
|
|
5649
5652
|
}
|
|
5650
5653
|
|
|
5651
5654
|
// src/templates/opencode-config.ts
|
|
5652
|
-
function renderOpenCodeConfig(
|
|
5655
|
+
function renderOpenCodeConfig(params2) {
|
|
5653
5656
|
const config = {
|
|
5654
5657
|
instructions: [
|
|
5655
5658
|
`Read AGENTS.md in this directory for Syntaur protocol (v2.0) instructions.`,
|
|
5656
|
-
`Read ${
|
|
5659
|
+
`Read ${params2.projectDir}/project.md for project overview (project-nested assignments only).`,
|
|
5657
5660
|
`Append timestamped progress entries to the assignment's progress.md (not to assignment.md).`,
|
|
5658
5661
|
`Use 'syntaur comment <slug-or-uuid> "body" --type question|note|feedback' to append to comments.md \u2014 never edit it directly.`,
|
|
5659
5662
|
`Use 'syntaur request <source> <target> "text"' to append a todo to another assignment's ## Todos.`,
|
|
@@ -5949,7 +5952,7 @@ Use --slug to specify a different slug.`
|
|
|
5949
5952
|
init_config2();
|
|
5950
5953
|
import { spawn } from "child_process";
|
|
5951
5954
|
import { createServer as createNetServer } from "net";
|
|
5952
|
-
import { resolve as
|
|
5955
|
+
import { resolve as resolve22, dirname as dirname4 } from "path";
|
|
5953
5956
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
5954
5957
|
|
|
5955
5958
|
// src/dashboard/server.ts
|
|
@@ -5958,7 +5961,7 @@ init_api();
|
|
|
5958
5961
|
init_assignment_resolver();
|
|
5959
5962
|
import express from "express";
|
|
5960
5963
|
import { createServer } from "http";
|
|
5961
|
-
import { resolve as
|
|
5964
|
+
import { resolve as resolve21 } from "path";
|
|
5962
5965
|
import { writeFile as writeFile5, unlink as unlink4 } from "fs/promises";
|
|
5963
5966
|
import { WebSocketServer, WebSocket } from "ws";
|
|
5964
5967
|
|
|
@@ -6121,8 +6124,8 @@ async function migrateFromMarkdown(projectsDir2) {
|
|
|
6121
6124
|
return allSessions.length;
|
|
6122
6125
|
}
|
|
6123
6126
|
async function parseMarkdownSessionsIndex(filePath, projectSlug) {
|
|
6124
|
-
const { readFile:
|
|
6125
|
-
const raw = await
|
|
6127
|
+
const { readFile: readFile30 } = await import("fs/promises");
|
|
6128
|
+
const raw = await readFile30(filePath, "utf-8");
|
|
6126
6129
|
const sessions = [];
|
|
6127
6130
|
const lines = raw.split("\n");
|
|
6128
6131
|
let inTable = false;
|
|
@@ -6309,18 +6312,25 @@ function createWatcher(options) {
|
|
|
6309
6312
|
if (parts.length === 0) return;
|
|
6310
6313
|
const projectSlug = parts[0];
|
|
6311
6314
|
let assignmentSlug;
|
|
6315
|
+
let isProjectTodos = false;
|
|
6312
6316
|
if (parts.length >= 3 && parts[1] === "assignments") {
|
|
6313
6317
|
assignmentSlug = parts[2];
|
|
6318
|
+
} else if (parts.length >= 2 && parts[1] === "todos") {
|
|
6319
|
+
isProjectTodos = true;
|
|
6314
6320
|
}
|
|
6315
|
-
const debounceKey = assignmentSlug ? `${projectSlug}/${assignmentSlug}` : projectSlug;
|
|
6321
|
+
const debounceKey = isProjectTodos ? `todos:${projectSlug}` : assignmentSlug ? `${projectSlug}/${assignmentSlug}` : projectSlug;
|
|
6316
6322
|
const existing = pendingEvents.get(debounceKey);
|
|
6317
6323
|
if (existing) clearTimeout(existing);
|
|
6318
|
-
const messageType = assignmentSlug ? "assignment-updated" : "project-updated";
|
|
6324
|
+
const messageType = isProjectTodos ? "todos-updated" : assignmentSlug ? "assignment-updated" : "project-updated";
|
|
6319
6325
|
pendingEvents.set(
|
|
6320
6326
|
debounceKey,
|
|
6321
6327
|
setTimeout(() => {
|
|
6322
6328
|
pendingEvents.delete(debounceKey);
|
|
6323
|
-
const message = {
|
|
6329
|
+
const message = isProjectTodos ? {
|
|
6330
|
+
type: "todos-updated",
|
|
6331
|
+
projectSlug,
|
|
6332
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
6333
|
+
} : {
|
|
6324
6334
|
type: messageType,
|
|
6325
6335
|
projectSlug,
|
|
6326
6336
|
assignmentSlug,
|
|
@@ -8411,14 +8421,17 @@ function getWorkspaceParam(value) {
|
|
|
8411
8421
|
return value ?? "";
|
|
8412
8422
|
}
|
|
8413
8423
|
var writeLocks = /* @__PURE__ */ new Map();
|
|
8414
|
-
function withLock(
|
|
8415
|
-
const prev = writeLocks.get(
|
|
8424
|
+
function withLock(lockKey, fn) {
|
|
8425
|
+
const prev = writeLocks.get(lockKey) ?? Promise.resolve();
|
|
8416
8426
|
const next = prev.then(fn);
|
|
8417
|
-
writeLocks.set(
|
|
8427
|
+
writeLocks.set(lockKey, next.then(() => {
|
|
8418
8428
|
}, () => {
|
|
8419
8429
|
}));
|
|
8420
8430
|
return next;
|
|
8421
8431
|
}
|
|
8432
|
+
function wsLock(workspace, fn) {
|
|
8433
|
+
return withLock(`ws:${workspace}`, fn);
|
|
8434
|
+
}
|
|
8422
8435
|
function createTodosRouter(todosDir2, broadcast) {
|
|
8423
8436
|
const router = Router5();
|
|
8424
8437
|
function broadcastUpdate() {
|
|
@@ -8477,7 +8490,7 @@ function createTodosRouter(todosDir2, broadcast) {
|
|
|
8477
8490
|
res.status(400).json({ error: "description is required" });
|
|
8478
8491
|
return;
|
|
8479
8492
|
}
|
|
8480
|
-
const item = await
|
|
8493
|
+
const item = await wsLock(workspace, async () => {
|
|
8481
8494
|
const checklist = await readChecklist(todosDir2, workspace);
|
|
8482
8495
|
const existingIds = new Set(checklist.items.map((i) => i.id));
|
|
8483
8496
|
const id = generateUniqueId(existingIds);
|
|
@@ -8506,7 +8519,7 @@ function createTodosRouter(todosDir2, broadcast) {
|
|
|
8506
8519
|
res.status(400).json({ error: "ids must be an array of strings" });
|
|
8507
8520
|
return;
|
|
8508
8521
|
}
|
|
8509
|
-
const items = await
|
|
8522
|
+
const items = await wsLock(workspace, async () => {
|
|
8510
8523
|
const checklist = await readChecklist(todosDir2, workspace);
|
|
8511
8524
|
const itemMap = new Map(checklist.items.map((i) => [i.id, i]));
|
|
8512
8525
|
const reordered = [];
|
|
@@ -8541,8 +8554,8 @@ function createTodosRouter(todosDir2, broadcast) {
|
|
|
8541
8554
|
router.post("/:workspace/archive", async (req, res) => {
|
|
8542
8555
|
try {
|
|
8543
8556
|
const { archivePath: archivePath2 } = await Promise.resolve().then(() => (init_parser2(), parser_exports));
|
|
8544
|
-
const { resolve:
|
|
8545
|
-
const { readFile:
|
|
8557
|
+
const { resolve: resolve46 } = await import("path");
|
|
8558
|
+
const { readFile: readFile30 } = await import("fs/promises");
|
|
8546
8559
|
const { writeFileForce: writeFileForce2 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
|
|
8547
8560
|
const workspace = getWorkspaceParam(req.params.workspace);
|
|
8548
8561
|
const checklist = await readChecklist(todosDir2, workspace);
|
|
@@ -8558,10 +8571,10 @@ function createTodosRouter(todosDir2, broadcast) {
|
|
|
8558
8571
|
(e) => e.itemIds.every((id) => completedIds.has(id))
|
|
8559
8572
|
);
|
|
8560
8573
|
const archFile = archivePath2(todosDir2, workspace, checklist.archiveInterval);
|
|
8561
|
-
await ensureDir(
|
|
8574
|
+
await ensureDir(resolve46(todosDir2, "archive"));
|
|
8562
8575
|
let archContent = "";
|
|
8563
8576
|
if (await fileExists(archFile)) {
|
|
8564
|
-
archContent = await
|
|
8577
|
+
archContent = await readFile30(archFile, "utf-8");
|
|
8565
8578
|
archContent = archContent.trimEnd() + "\n\n";
|
|
8566
8579
|
} else {
|
|
8567
8580
|
archContent = `---
|
|
@@ -8630,7 +8643,7 @@ workspace: ${workspace}
|
|
|
8630
8643
|
router.patch("/:workspace/:id", async (req, res) => {
|
|
8631
8644
|
try {
|
|
8632
8645
|
const workspace = getWorkspaceParam(req.params.workspace);
|
|
8633
|
-
const result = await
|
|
8646
|
+
const result = await wsLock(workspace, async () => {
|
|
8634
8647
|
const checklist = await readChecklist(todosDir2, workspace);
|
|
8635
8648
|
const item = checklist.items.find((i) => i.id === req.params.id);
|
|
8636
8649
|
if (!item) return null;
|
|
@@ -8652,7 +8665,7 @@ workspace: ${workspace}
|
|
|
8652
8665
|
router.delete("/:workspace/:id", async (req, res) => {
|
|
8653
8666
|
try {
|
|
8654
8667
|
const workspace = getWorkspaceParam(req.params.workspace);
|
|
8655
|
-
const deleted = await
|
|
8668
|
+
const deleted = await wsLock(workspace, async () => {
|
|
8656
8669
|
const checklist = await readChecklist(todosDir2, workspace);
|
|
8657
8670
|
const idx = checklist.items.findIndex((i) => i.id === req.params.id);
|
|
8658
8671
|
if (idx === -1) return false;
|
|
@@ -8673,7 +8686,7 @@ workspace: ${workspace}
|
|
|
8673
8686
|
router.post("/:workspace/:id/start", async (req, res) => {
|
|
8674
8687
|
try {
|
|
8675
8688
|
const workspace = getWorkspaceParam(req.params.workspace);
|
|
8676
|
-
const result = await
|
|
8689
|
+
const result = await wsLock(workspace, async () => {
|
|
8677
8690
|
const checklist = await readChecklist(todosDir2, workspace);
|
|
8678
8691
|
const item = checklist.items.find((i) => i.id === req.params.id);
|
|
8679
8692
|
if (!item) return { error: "not_found" };
|
|
@@ -8700,7 +8713,7 @@ workspace: ${workspace}
|
|
|
8700
8713
|
router.post("/:workspace/:id/complete", async (req, res) => {
|
|
8701
8714
|
try {
|
|
8702
8715
|
const workspace = getWorkspaceParam(req.params.workspace);
|
|
8703
|
-
const result = await
|
|
8716
|
+
const result = await wsLock(workspace, async () => {
|
|
8704
8717
|
const checklist = await readChecklist(todosDir2, workspace);
|
|
8705
8718
|
const item = checklist.items.find((i) => i.id === req.params.id);
|
|
8706
8719
|
if (!item) return null;
|
|
@@ -8734,7 +8747,7 @@ workspace: ${workspace}
|
|
|
8734
8747
|
try {
|
|
8735
8748
|
const reason = req.body.reason || null;
|
|
8736
8749
|
const workspace = getWorkspaceParam(req.params.workspace);
|
|
8737
|
-
const result = await
|
|
8750
|
+
const result = await wsLock(workspace, async () => {
|
|
8738
8751
|
const checklist = await readChecklist(todosDir2, workspace);
|
|
8739
8752
|
const item = checklist.items.find((i) => i.id === req.params.id);
|
|
8740
8753
|
if (!item) return null;
|
|
@@ -8767,7 +8780,7 @@ workspace: ${workspace}
|
|
|
8767
8780
|
router.post("/:workspace/:id/reopen", async (req, res) => {
|
|
8768
8781
|
try {
|
|
8769
8782
|
const workspace = getWorkspaceParam(req.params.workspace);
|
|
8770
|
-
const result = await
|
|
8783
|
+
const result = await wsLock(workspace, async () => {
|
|
8771
8784
|
const checklist = await readChecklist(todosDir2, workspace);
|
|
8772
8785
|
const item = checklist.items.find((i) => i.id === req.params.id);
|
|
8773
8786
|
if (!item) return null;
|
|
@@ -8789,7 +8802,7 @@ workspace: ${workspace}
|
|
|
8789
8802
|
router.post("/:workspace/:id/unblock", async (req, res) => {
|
|
8790
8803
|
try {
|
|
8791
8804
|
const workspace = getWorkspaceParam(req.params.workspace);
|
|
8792
|
-
const result = await
|
|
8805
|
+
const result = await wsLock(workspace, async () => {
|
|
8793
8806
|
const checklist = await readChecklist(todosDir2, workspace);
|
|
8794
8807
|
const item = checklist.items.find((i) => i.id === req.params.id);
|
|
8795
8808
|
if (!item) return null;
|
|
@@ -8811,9 +8824,606 @@ workspace: ${workspace}
|
|
|
8811
8824
|
return router;
|
|
8812
8825
|
}
|
|
8813
8826
|
|
|
8827
|
+
// src/dashboard/api-project-todos.ts
|
|
8828
|
+
init_parser2();
|
|
8829
|
+
init_fs();
|
|
8830
|
+
init_paths();
|
|
8831
|
+
import { Router as Router6 } from "express";
|
|
8832
|
+
import { mkdir as mkdir2, readFile as readFile14 } from "fs/promises";
|
|
8833
|
+
import { resolve as resolve19 } from "path";
|
|
8834
|
+
var writeLocks2 = /* @__PURE__ */ new Map();
|
|
8835
|
+
function projLock(slug, fn) {
|
|
8836
|
+
const key = `proj:${slug}`;
|
|
8837
|
+
const prev = writeLocks2.get(key) ?? Promise.resolve();
|
|
8838
|
+
const next = prev.then(fn);
|
|
8839
|
+
writeLocks2.set(key, next.then(() => {
|
|
8840
|
+
}, () => {
|
|
8841
|
+
}));
|
|
8842
|
+
return next;
|
|
8843
|
+
}
|
|
8844
|
+
function getProjectIdParam(value) {
|
|
8845
|
+
if (Array.isArray(value)) return value[0] ?? "";
|
|
8846
|
+
return value ?? "";
|
|
8847
|
+
}
|
|
8848
|
+
function params(req) {
|
|
8849
|
+
return req.params;
|
|
8850
|
+
}
|
|
8851
|
+
async function projectExists(projectsDir2, slug) {
|
|
8852
|
+
return fileExists(resolve19(projectsDir2, slug, "project.md"));
|
|
8853
|
+
}
|
|
8854
|
+
async function ensureProjectTodosDir(projectsDir2, slug) {
|
|
8855
|
+
const todosDir2 = projectTodosDir(projectsDir2, slug);
|
|
8856
|
+
try {
|
|
8857
|
+
await mkdir2(todosDir2, { recursive: false });
|
|
8858
|
+
} catch (err2) {
|
|
8859
|
+
const code = err2.code;
|
|
8860
|
+
if (code === "EEXIST") return;
|
|
8861
|
+
if (code === "ENOENT") {
|
|
8862
|
+
const e = new Error("PROJECT_GONE");
|
|
8863
|
+
e.code = "PROJECT_GONE";
|
|
8864
|
+
throw e;
|
|
8865
|
+
}
|
|
8866
|
+
throw err2;
|
|
8867
|
+
}
|
|
8868
|
+
try {
|
|
8869
|
+
await mkdir2(resolve19(todosDir2, "archive"), { recursive: false });
|
|
8870
|
+
} catch (err2) {
|
|
8871
|
+
const code = err2.code;
|
|
8872
|
+
if (code === "EEXIST") return;
|
|
8873
|
+
if (code === "ENOENT") {
|
|
8874
|
+
const e = new Error("PROJECT_GONE");
|
|
8875
|
+
e.code = "PROJECT_GONE";
|
|
8876
|
+
throw e;
|
|
8877
|
+
}
|
|
8878
|
+
throw err2;
|
|
8879
|
+
}
|
|
8880
|
+
}
|
|
8881
|
+
function notFound(res, slug) {
|
|
8882
|
+
res.status(404).json({ error: `Project "${slug}" not found` });
|
|
8883
|
+
}
|
|
8884
|
+
function createProjectTodosRouter(projectsDir2, broadcast) {
|
|
8885
|
+
const router = Router6({ mergeParams: true });
|
|
8886
|
+
function broadcastUpdate(projectSlug) {
|
|
8887
|
+
broadcast({ type: "todos-updated", projectSlug, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
8888
|
+
}
|
|
8889
|
+
function validateProjectId(req, res, next) {
|
|
8890
|
+
const slug = getProjectIdParam(params(req).projectId);
|
|
8891
|
+
if (!slug || !isValidSlug(slug)) {
|
|
8892
|
+
res.status(400).json({ error: `Invalid project slug: "${slug}"` });
|
|
8893
|
+
return;
|
|
8894
|
+
}
|
|
8895
|
+
next();
|
|
8896
|
+
}
|
|
8897
|
+
router.use(validateProjectId);
|
|
8898
|
+
router.get("/", async (req, res) => {
|
|
8899
|
+
try {
|
|
8900
|
+
const slug = getProjectIdParam(params(req).projectId);
|
|
8901
|
+
if (!await projectExists(projectsDir2, slug)) {
|
|
8902
|
+
notFound(res, slug);
|
|
8903
|
+
return;
|
|
8904
|
+
}
|
|
8905
|
+
const todosDir2 = projectTodosDir(projectsDir2, slug);
|
|
8906
|
+
const checklist = await readChecklist(todosDir2, slug);
|
|
8907
|
+
res.json({
|
|
8908
|
+
workspace: checklist.workspace,
|
|
8909
|
+
archiveInterval: checklist.archiveInterval,
|
|
8910
|
+
items: checklist.items,
|
|
8911
|
+
counts: computeCounts(checklist.items)
|
|
8912
|
+
});
|
|
8913
|
+
} catch (error) {
|
|
8914
|
+
res.status(500).json({ error: error instanceof Error ? error.message : "Failed to get todos" });
|
|
8915
|
+
}
|
|
8916
|
+
});
|
|
8917
|
+
router.post("/", async (req, res) => {
|
|
8918
|
+
try {
|
|
8919
|
+
const slug = getProjectIdParam(params(req).projectId);
|
|
8920
|
+
const { description, tags } = req.body;
|
|
8921
|
+
if (!description || typeof description !== "string") {
|
|
8922
|
+
res.status(400).json({ error: "description is required" });
|
|
8923
|
+
return;
|
|
8924
|
+
}
|
|
8925
|
+
if (!await projectExists(projectsDir2, slug)) {
|
|
8926
|
+
notFound(res, slug);
|
|
8927
|
+
return;
|
|
8928
|
+
}
|
|
8929
|
+
const item = await projLock(slug, async () => {
|
|
8930
|
+
if (!await projectExists(projectsDir2, slug)) return null;
|
|
8931
|
+
await ensureProjectTodosDir(projectsDir2, slug);
|
|
8932
|
+
const todosDir2 = projectTodosDir(projectsDir2, slug);
|
|
8933
|
+
const checklist = await readChecklist(todosDir2, slug);
|
|
8934
|
+
const existingIds = new Set(checklist.items.map((i) => i.id));
|
|
8935
|
+
const id = generateUniqueId(existingIds);
|
|
8936
|
+
const newItem = {
|
|
8937
|
+
id,
|
|
8938
|
+
description,
|
|
8939
|
+
status: "open",
|
|
8940
|
+
tags: Array.isArray(tags) ? tags : [],
|
|
8941
|
+
session: null
|
|
8942
|
+
};
|
|
8943
|
+
checklist.workspace = slug;
|
|
8944
|
+
checklist.items.push(newItem);
|
|
8945
|
+
await writeChecklist(todosDir2, checklist);
|
|
8946
|
+
return newItem;
|
|
8947
|
+
});
|
|
8948
|
+
if (!item) {
|
|
8949
|
+
notFound(res, slug);
|
|
8950
|
+
return;
|
|
8951
|
+
}
|
|
8952
|
+
broadcastUpdate(slug);
|
|
8953
|
+
res.status(201).json(item);
|
|
8954
|
+
} catch (error) {
|
|
8955
|
+
if (error.code === "PROJECT_GONE") {
|
|
8956
|
+
notFound(res, getProjectIdParam(params(req).projectId));
|
|
8957
|
+
return;
|
|
8958
|
+
}
|
|
8959
|
+
res.status(500).json({ error: error instanceof Error ? error.message : "Failed to add todo" });
|
|
8960
|
+
}
|
|
8961
|
+
});
|
|
8962
|
+
router.post("/reorder", async (req, res) => {
|
|
8963
|
+
try {
|
|
8964
|
+
const slug = getProjectIdParam(params(req).projectId);
|
|
8965
|
+
const { ids } = req.body;
|
|
8966
|
+
if (!Array.isArray(ids) || !ids.every((id) => typeof id === "string")) {
|
|
8967
|
+
res.status(400).json({ error: "ids must be an array of strings" });
|
|
8968
|
+
return;
|
|
8969
|
+
}
|
|
8970
|
+
if (!await projectExists(projectsDir2, slug)) {
|
|
8971
|
+
notFound(res, slug);
|
|
8972
|
+
return;
|
|
8973
|
+
}
|
|
8974
|
+
const items = await projLock(slug, async () => {
|
|
8975
|
+
if (!await projectExists(projectsDir2, slug)) return null;
|
|
8976
|
+
await ensureProjectTodosDir(projectsDir2, slug);
|
|
8977
|
+
const todosDir2 = projectTodosDir(projectsDir2, slug);
|
|
8978
|
+
const checklist = await readChecklist(todosDir2, slug);
|
|
8979
|
+
const itemMap = new Map(checklist.items.map((i) => [i.id, i]));
|
|
8980
|
+
const reordered = [];
|
|
8981
|
+
for (const id of ids) {
|
|
8982
|
+
const item = itemMap.get(id);
|
|
8983
|
+
if (item) {
|
|
8984
|
+
reordered.push(item);
|
|
8985
|
+
itemMap.delete(id);
|
|
8986
|
+
}
|
|
8987
|
+
}
|
|
8988
|
+
for (const item of itemMap.values()) reordered.push(item);
|
|
8989
|
+
checklist.workspace = slug;
|
|
8990
|
+
checklist.items = reordered;
|
|
8991
|
+
await writeChecklist(todosDir2, checklist);
|
|
8992
|
+
return reordered;
|
|
8993
|
+
});
|
|
8994
|
+
if (!items) {
|
|
8995
|
+
notFound(res, slug);
|
|
8996
|
+
return;
|
|
8997
|
+
}
|
|
8998
|
+
broadcastUpdate(slug);
|
|
8999
|
+
res.json({ items });
|
|
9000
|
+
} catch (error) {
|
|
9001
|
+
if (error.code === "PROJECT_GONE") {
|
|
9002
|
+
notFound(res, getProjectIdParam(params(req).projectId));
|
|
9003
|
+
return;
|
|
9004
|
+
}
|
|
9005
|
+
res.status(500).json({ error: error instanceof Error ? error.message : "Failed to reorder todos" });
|
|
9006
|
+
}
|
|
9007
|
+
});
|
|
9008
|
+
router.get("/log", async (req, res) => {
|
|
9009
|
+
try {
|
|
9010
|
+
const slug = getProjectIdParam(params(req).projectId);
|
|
9011
|
+
if (!await projectExists(projectsDir2, slug)) {
|
|
9012
|
+
notFound(res, slug);
|
|
9013
|
+
return;
|
|
9014
|
+
}
|
|
9015
|
+
const todosDir2 = projectTodosDir(projectsDir2, slug);
|
|
9016
|
+
const log = await readLog(todosDir2, slug);
|
|
9017
|
+
res.json(log);
|
|
9018
|
+
} catch (error) {
|
|
9019
|
+
res.status(500).json({ error: error instanceof Error ? error.message : "Failed to get log" });
|
|
9020
|
+
}
|
|
9021
|
+
});
|
|
9022
|
+
router.post("/archive", async (req, res) => {
|
|
9023
|
+
try {
|
|
9024
|
+
const slug = getProjectIdParam(params(req).projectId);
|
|
9025
|
+
if (!await projectExists(projectsDir2, slug)) {
|
|
9026
|
+
notFound(res, slug);
|
|
9027
|
+
return;
|
|
9028
|
+
}
|
|
9029
|
+
const todosDir2 = projectTodosDir(projectsDir2, slug);
|
|
9030
|
+
await ensureProjectTodosDir(projectsDir2, slug);
|
|
9031
|
+
const checklist = await readChecklist(todosDir2, slug);
|
|
9032
|
+
const log = await readLog(todosDir2, slug);
|
|
9033
|
+
const completedIds = new Set(
|
|
9034
|
+
checklist.items.filter((i) => i.status === "completed").map((i) => i.id)
|
|
9035
|
+
);
|
|
9036
|
+
if (completedIds.size === 0) {
|
|
9037
|
+
res.json({ archived: 0, message: "No completed items to archive" });
|
|
9038
|
+
return;
|
|
9039
|
+
}
|
|
9040
|
+
const toArchive = log.entries.filter(
|
|
9041
|
+
(e) => e.itemIds.every((id) => completedIds.has(id))
|
|
9042
|
+
);
|
|
9043
|
+
const archFile = archivePath(todosDir2, slug, checklist.archiveInterval);
|
|
9044
|
+
let archContent = "";
|
|
9045
|
+
if (await fileExists(archFile)) {
|
|
9046
|
+
archContent = await readFile14(archFile, "utf-8");
|
|
9047
|
+
archContent = archContent.trimEnd() + "\n\n";
|
|
9048
|
+
} else {
|
|
9049
|
+
archContent = `---
|
|
9050
|
+
workspace: ${slug}
|
|
9051
|
+
---
|
|
9052
|
+
|
|
9053
|
+
# Archive
|
|
9054
|
+
|
|
9055
|
+
`;
|
|
9056
|
+
}
|
|
9057
|
+
const completedItems = checklist.items.filter((i) => completedIds.has(i.id));
|
|
9058
|
+
for (const item of completedItems) {
|
|
9059
|
+
archContent += `- [x] ${item.description} ${item.tags.map((t) => `#${t}`).join(" ")} [t:${item.id}]
|
|
9060
|
+
`;
|
|
9061
|
+
}
|
|
9062
|
+
archContent += "\n";
|
|
9063
|
+
for (const entry of toArchive) {
|
|
9064
|
+
archContent += `### ${entry.timestamp} \u2014 ${entry.itemIds.map((i) => `t:${i}`).join(", ")}
|
|
9065
|
+
`;
|
|
9066
|
+
if (entry.items) archContent += `**Items:** ${entry.items}
|
|
9067
|
+
`;
|
|
9068
|
+
if (entry.session) archContent += `**Session:** ${entry.session}
|
|
9069
|
+
`;
|
|
9070
|
+
if (entry.branch) archContent += `**Branch:** ${entry.branch}
|
|
9071
|
+
`;
|
|
9072
|
+
if (entry.summary) archContent += `**Summary:** ${entry.summary}
|
|
9073
|
+
`;
|
|
9074
|
+
if (entry.blockers) archContent += `**Blockers:** ${entry.blockers}
|
|
9075
|
+
`;
|
|
9076
|
+
archContent += "\n";
|
|
9077
|
+
}
|
|
9078
|
+
await writeFileForce(archFile, archContent);
|
|
9079
|
+
checklist.workspace = slug;
|
|
9080
|
+
checklist.items = checklist.items.filter((i) => !completedIds.has(i.id));
|
|
9081
|
+
await writeChecklist(todosDir2, checklist);
|
|
9082
|
+
broadcastUpdate(slug);
|
|
9083
|
+
res.json({ archived: completedIds.size, logEntries: toArchive.length });
|
|
9084
|
+
} catch (error) {
|
|
9085
|
+
if (error.code === "PROJECT_GONE") {
|
|
9086
|
+
notFound(res, getProjectIdParam(params(req).projectId));
|
|
9087
|
+
return;
|
|
9088
|
+
}
|
|
9089
|
+
res.status(500).json({ error: error instanceof Error ? error.message : "Failed to archive" });
|
|
9090
|
+
}
|
|
9091
|
+
});
|
|
9092
|
+
router.get("/log/:id", async (req, res) => {
|
|
9093
|
+
try {
|
|
9094
|
+
const slug = getProjectIdParam(params(req).projectId);
|
|
9095
|
+
if (!await projectExists(projectsDir2, slug)) {
|
|
9096
|
+
notFound(res, slug);
|
|
9097
|
+
return;
|
|
9098
|
+
}
|
|
9099
|
+
const todosDir2 = projectTodosDir(projectsDir2, slug);
|
|
9100
|
+
const log = await readLog(todosDir2, slug);
|
|
9101
|
+
const entries = log.entries.filter((e) => e.itemIds.includes(params(req).id ?? ""));
|
|
9102
|
+
res.json({ workspace: log.workspace, entries });
|
|
9103
|
+
} catch (error) {
|
|
9104
|
+
res.status(500).json({ error: error instanceof Error ? error.message : "Failed to get log" });
|
|
9105
|
+
}
|
|
9106
|
+
});
|
|
9107
|
+
router.get("/:id", async (req, res) => {
|
|
9108
|
+
try {
|
|
9109
|
+
const slug = getProjectIdParam(params(req).projectId);
|
|
9110
|
+
if (!await projectExists(projectsDir2, slug)) {
|
|
9111
|
+
notFound(res, slug);
|
|
9112
|
+
return;
|
|
9113
|
+
}
|
|
9114
|
+
const todosDir2 = projectTodosDir(projectsDir2, slug);
|
|
9115
|
+
const checklist = await readChecklist(todosDir2, slug);
|
|
9116
|
+
const item = checklist.items.find((i) => i.id === (params(req).id ?? ""));
|
|
9117
|
+
if (!item) {
|
|
9118
|
+
res.status(404).json({ error: `Todo "${params(req).id ?? ""}" not found` });
|
|
9119
|
+
return;
|
|
9120
|
+
}
|
|
9121
|
+
const log = await readLog(todosDir2, slug);
|
|
9122
|
+
const logEntries = log.entries.filter((e) => e.itemIds.includes(params(req).id ?? ""));
|
|
9123
|
+
res.json({ ...item, log: logEntries });
|
|
9124
|
+
} catch (error) {
|
|
9125
|
+
res.status(500).json({ error: error instanceof Error ? error.message : "Failed to get todo" });
|
|
9126
|
+
}
|
|
9127
|
+
});
|
|
9128
|
+
router.patch("/:id", async (req, res) => {
|
|
9129
|
+
try {
|
|
9130
|
+
const slug = getProjectIdParam(params(req).projectId);
|
|
9131
|
+
if (!await projectExists(projectsDir2, slug)) {
|
|
9132
|
+
notFound(res, slug);
|
|
9133
|
+
return;
|
|
9134
|
+
}
|
|
9135
|
+
const result = await projLock(slug, async () => {
|
|
9136
|
+
if (!await projectExists(projectsDir2, slug)) return "gone";
|
|
9137
|
+
await ensureProjectTodosDir(projectsDir2, slug);
|
|
9138
|
+
const todosDir2 = projectTodosDir(projectsDir2, slug);
|
|
9139
|
+
const checklist = await readChecklist(todosDir2, slug);
|
|
9140
|
+
const item = checklist.items.find((i) => i.id === (params(req).id ?? ""));
|
|
9141
|
+
if (!item) return null;
|
|
9142
|
+
if (req.body.description !== void 0) item.description = req.body.description;
|
|
9143
|
+
if (Array.isArray(req.body.tags)) item.tags = req.body.tags;
|
|
9144
|
+
checklist.workspace = slug;
|
|
9145
|
+
await writeChecklist(todosDir2, checklist);
|
|
9146
|
+
return { ...item };
|
|
9147
|
+
});
|
|
9148
|
+
if (result === "gone") {
|
|
9149
|
+
notFound(res, slug);
|
|
9150
|
+
return;
|
|
9151
|
+
}
|
|
9152
|
+
if (!result) {
|
|
9153
|
+
res.status(404).json({ error: `Todo "${params(req).id ?? ""}" not found` });
|
|
9154
|
+
return;
|
|
9155
|
+
}
|
|
9156
|
+
broadcastUpdate(slug);
|
|
9157
|
+
res.json(result);
|
|
9158
|
+
} catch (error) {
|
|
9159
|
+
if (error.code === "PROJECT_GONE") {
|
|
9160
|
+
notFound(res, getProjectIdParam(params(req).projectId));
|
|
9161
|
+
return;
|
|
9162
|
+
}
|
|
9163
|
+
res.status(500).json({ error: error instanceof Error ? error.message : "Failed to update todo" });
|
|
9164
|
+
}
|
|
9165
|
+
});
|
|
9166
|
+
router.delete("/:id", async (req, res) => {
|
|
9167
|
+
try {
|
|
9168
|
+
const slug = getProjectIdParam(params(req).projectId);
|
|
9169
|
+
if (!await projectExists(projectsDir2, slug)) {
|
|
9170
|
+
notFound(res, slug);
|
|
9171
|
+
return;
|
|
9172
|
+
}
|
|
9173
|
+
const deleted = await projLock(slug, async () => {
|
|
9174
|
+
if (!await projectExists(projectsDir2, slug)) return "gone";
|
|
9175
|
+
await ensureProjectTodosDir(projectsDir2, slug);
|
|
9176
|
+
const todosDir2 = projectTodosDir(projectsDir2, slug);
|
|
9177
|
+
const checklist = await readChecklist(todosDir2, slug);
|
|
9178
|
+
const idx = checklist.items.findIndex((i) => i.id === (params(req).id ?? ""));
|
|
9179
|
+
if (idx === -1) return false;
|
|
9180
|
+
checklist.items.splice(idx, 1);
|
|
9181
|
+
checklist.workspace = slug;
|
|
9182
|
+
await writeChecklist(todosDir2, checklist);
|
|
9183
|
+
return true;
|
|
9184
|
+
});
|
|
9185
|
+
if (deleted === "gone") {
|
|
9186
|
+
notFound(res, slug);
|
|
9187
|
+
return;
|
|
9188
|
+
}
|
|
9189
|
+
if (!deleted) {
|
|
9190
|
+
res.status(404).json({ error: `Todo "${params(req).id ?? ""}" not found` });
|
|
9191
|
+
return;
|
|
9192
|
+
}
|
|
9193
|
+
broadcastUpdate(slug);
|
|
9194
|
+
res.json({ deleted: params(req).id ?? "" });
|
|
9195
|
+
} catch (error) {
|
|
9196
|
+
if (error.code === "PROJECT_GONE") {
|
|
9197
|
+
notFound(res, getProjectIdParam(params(req).projectId));
|
|
9198
|
+
return;
|
|
9199
|
+
}
|
|
9200
|
+
res.status(500).json({ error: error instanceof Error ? error.message : "Failed to delete todo" });
|
|
9201
|
+
}
|
|
9202
|
+
});
|
|
9203
|
+
router.post("/:id/start", async (req, res) => {
|
|
9204
|
+
try {
|
|
9205
|
+
const slug = getProjectIdParam(params(req).projectId);
|
|
9206
|
+
if (!await projectExists(projectsDir2, slug)) {
|
|
9207
|
+
notFound(res, slug);
|
|
9208
|
+
return;
|
|
9209
|
+
}
|
|
9210
|
+
const result = await projLock(slug, async () => {
|
|
9211
|
+
if (!await projectExists(projectsDir2, slug)) return { error: "gone" };
|
|
9212
|
+
await ensureProjectTodosDir(projectsDir2, slug);
|
|
9213
|
+
const todosDir2 = projectTodosDir(projectsDir2, slug);
|
|
9214
|
+
const checklist = await readChecklist(todosDir2, slug);
|
|
9215
|
+
const item = checklist.items.find((i) => i.id === (params(req).id ?? ""));
|
|
9216
|
+
if (!item) return { error: "not_found" };
|
|
9217
|
+
if (item.status === "in_progress") return { error: "conflict", session: item.session };
|
|
9218
|
+
item.status = "in_progress";
|
|
9219
|
+
item.session = req.body.session || null;
|
|
9220
|
+
checklist.workspace = slug;
|
|
9221
|
+
await writeChecklist(todosDir2, checklist);
|
|
9222
|
+
return { item: { ...item } };
|
|
9223
|
+
});
|
|
9224
|
+
if ("error" in result) {
|
|
9225
|
+
if (result.error === "gone") {
|
|
9226
|
+
notFound(res, slug);
|
|
9227
|
+
return;
|
|
9228
|
+
}
|
|
9229
|
+
if (result.error === "not_found") {
|
|
9230
|
+
res.status(404).json({ error: `Todo "${params(req).id ?? ""}" not found` });
|
|
9231
|
+
return;
|
|
9232
|
+
}
|
|
9233
|
+
res.status(409).json({ error: `Todo is already in progress (session: ${result.session})` });
|
|
9234
|
+
return;
|
|
9235
|
+
}
|
|
9236
|
+
broadcastUpdate(slug);
|
|
9237
|
+
res.json(result.item);
|
|
9238
|
+
} catch (error) {
|
|
9239
|
+
if (error.code === "PROJECT_GONE") {
|
|
9240
|
+
notFound(res, getProjectIdParam(params(req).projectId));
|
|
9241
|
+
return;
|
|
9242
|
+
}
|
|
9243
|
+
res.status(500).json({ error: error instanceof Error ? error.message : "Failed to start todo" });
|
|
9244
|
+
}
|
|
9245
|
+
});
|
|
9246
|
+
router.post("/:id/complete", async (req, res) => {
|
|
9247
|
+
try {
|
|
9248
|
+
const slug = getProjectIdParam(params(req).projectId);
|
|
9249
|
+
if (!await projectExists(projectsDir2, slug)) {
|
|
9250
|
+
notFound(res, slug);
|
|
9251
|
+
return;
|
|
9252
|
+
}
|
|
9253
|
+
const result = await projLock(slug, async () => {
|
|
9254
|
+
if (!await projectExists(projectsDir2, slug)) return "gone";
|
|
9255
|
+
await ensureProjectTodosDir(projectsDir2, slug);
|
|
9256
|
+
const todosDir2 = projectTodosDir(projectsDir2, slug);
|
|
9257
|
+
const checklist = await readChecklist(todosDir2, slug);
|
|
9258
|
+
const item = checklist.items.find((i) => i.id === (params(req).id ?? ""));
|
|
9259
|
+
if (!item) return null;
|
|
9260
|
+
item.status = "completed";
|
|
9261
|
+
item.session = null;
|
|
9262
|
+
checklist.workspace = slug;
|
|
9263
|
+
await writeChecklist(todosDir2, checklist);
|
|
9264
|
+
const entry = {
|
|
9265
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9266
|
+
itemIds: [item.id],
|
|
9267
|
+
items: item.description,
|
|
9268
|
+
session: req.body.session || null,
|
|
9269
|
+
branch: req.body.branch || null,
|
|
9270
|
+
summary: req.body.summary || "Completed.",
|
|
9271
|
+
blockers: null,
|
|
9272
|
+
status: null
|
|
9273
|
+
};
|
|
9274
|
+
await appendLogEntry2(todosDir2, slug, entry);
|
|
9275
|
+
return { ...item };
|
|
9276
|
+
});
|
|
9277
|
+
if (result === "gone") {
|
|
9278
|
+
notFound(res, slug);
|
|
9279
|
+
return;
|
|
9280
|
+
}
|
|
9281
|
+
if (!result) {
|
|
9282
|
+
res.status(404).json({ error: `Todo "${params(req).id ?? ""}" not found` });
|
|
9283
|
+
return;
|
|
9284
|
+
}
|
|
9285
|
+
broadcastUpdate(slug);
|
|
9286
|
+
res.json(result);
|
|
9287
|
+
} catch (error) {
|
|
9288
|
+
if (error.code === "PROJECT_GONE") {
|
|
9289
|
+
notFound(res, getProjectIdParam(params(req).projectId));
|
|
9290
|
+
return;
|
|
9291
|
+
}
|
|
9292
|
+
res.status(500).json({ error: error instanceof Error ? error.message : "Failed to complete todo" });
|
|
9293
|
+
}
|
|
9294
|
+
});
|
|
9295
|
+
router.post("/:id/block", async (req, res) => {
|
|
9296
|
+
try {
|
|
9297
|
+
const slug = getProjectIdParam(params(req).projectId);
|
|
9298
|
+
const reason = req.body.reason || null;
|
|
9299
|
+
if (!await projectExists(projectsDir2, slug)) {
|
|
9300
|
+
notFound(res, slug);
|
|
9301
|
+
return;
|
|
9302
|
+
}
|
|
9303
|
+
const result = await projLock(slug, async () => {
|
|
9304
|
+
if (!await projectExists(projectsDir2, slug)) return "gone";
|
|
9305
|
+
await ensureProjectTodosDir(projectsDir2, slug);
|
|
9306
|
+
const todosDir2 = projectTodosDir(projectsDir2, slug);
|
|
9307
|
+
const checklist = await readChecklist(todosDir2, slug);
|
|
9308
|
+
const item = checklist.items.find((i) => i.id === (params(req).id ?? ""));
|
|
9309
|
+
if (!item) return null;
|
|
9310
|
+
item.status = "blocked";
|
|
9311
|
+
item.session = null;
|
|
9312
|
+
checklist.workspace = slug;
|
|
9313
|
+
await writeChecklist(todosDir2, checklist);
|
|
9314
|
+
const entry = {
|
|
9315
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9316
|
+
itemIds: [item.id],
|
|
9317
|
+
items: item.description,
|
|
9318
|
+
session: req.body.session || null,
|
|
9319
|
+
branch: null,
|
|
9320
|
+
summary: reason || "Blocked.",
|
|
9321
|
+
blockers: reason,
|
|
9322
|
+
status: "blocked"
|
|
9323
|
+
};
|
|
9324
|
+
await appendLogEntry2(todosDir2, slug, entry);
|
|
9325
|
+
return { ...item };
|
|
9326
|
+
});
|
|
9327
|
+
if (result === "gone") {
|
|
9328
|
+
notFound(res, slug);
|
|
9329
|
+
return;
|
|
9330
|
+
}
|
|
9331
|
+
if (!result) {
|
|
9332
|
+
res.status(404).json({ error: `Todo "${params(req).id ?? ""}" not found` });
|
|
9333
|
+
return;
|
|
9334
|
+
}
|
|
9335
|
+
broadcastUpdate(slug);
|
|
9336
|
+
res.json(result);
|
|
9337
|
+
} catch (error) {
|
|
9338
|
+
if (error.code === "PROJECT_GONE") {
|
|
9339
|
+
notFound(res, getProjectIdParam(params(req).projectId));
|
|
9340
|
+
return;
|
|
9341
|
+
}
|
|
9342
|
+
res.status(500).json({ error: error instanceof Error ? error.message : "Failed to block todo" });
|
|
9343
|
+
}
|
|
9344
|
+
});
|
|
9345
|
+
router.post("/:id/reopen", async (req, res) => {
|
|
9346
|
+
try {
|
|
9347
|
+
const slug = getProjectIdParam(params(req).projectId);
|
|
9348
|
+
if (!await projectExists(projectsDir2, slug)) {
|
|
9349
|
+
notFound(res, slug);
|
|
9350
|
+
return;
|
|
9351
|
+
}
|
|
9352
|
+
const result = await projLock(slug, async () => {
|
|
9353
|
+
if (!await projectExists(projectsDir2, slug)) return "gone";
|
|
9354
|
+
await ensureProjectTodosDir(projectsDir2, slug);
|
|
9355
|
+
const todosDir2 = projectTodosDir(projectsDir2, slug);
|
|
9356
|
+
const checklist = await readChecklist(todosDir2, slug);
|
|
9357
|
+
const item = checklist.items.find((i) => i.id === (params(req).id ?? ""));
|
|
9358
|
+
if (!item) return null;
|
|
9359
|
+
item.status = "open";
|
|
9360
|
+
item.session = null;
|
|
9361
|
+
checklist.workspace = slug;
|
|
9362
|
+
await writeChecklist(todosDir2, checklist);
|
|
9363
|
+
return { ...item };
|
|
9364
|
+
});
|
|
9365
|
+
if (result === "gone") {
|
|
9366
|
+
notFound(res, slug);
|
|
9367
|
+
return;
|
|
9368
|
+
}
|
|
9369
|
+
if (!result) {
|
|
9370
|
+
res.status(404).json({ error: `Todo "${params(req).id ?? ""}" not found` });
|
|
9371
|
+
return;
|
|
9372
|
+
}
|
|
9373
|
+
broadcastUpdate(slug);
|
|
9374
|
+
res.json(result);
|
|
9375
|
+
} catch (error) {
|
|
9376
|
+
if (error.code === "PROJECT_GONE") {
|
|
9377
|
+
notFound(res, getProjectIdParam(params(req).projectId));
|
|
9378
|
+
return;
|
|
9379
|
+
}
|
|
9380
|
+
res.status(500).json({ error: error instanceof Error ? error.message : "Failed to reopen todo" });
|
|
9381
|
+
}
|
|
9382
|
+
});
|
|
9383
|
+
router.post("/:id/unblock", async (req, res) => {
|
|
9384
|
+
try {
|
|
9385
|
+
const slug = getProjectIdParam(params(req).projectId);
|
|
9386
|
+
if (!await projectExists(projectsDir2, slug)) {
|
|
9387
|
+
notFound(res, slug);
|
|
9388
|
+
return;
|
|
9389
|
+
}
|
|
9390
|
+
const result = await projLock(slug, async () => {
|
|
9391
|
+
if (!await projectExists(projectsDir2, slug)) return "gone";
|
|
9392
|
+
await ensureProjectTodosDir(projectsDir2, slug);
|
|
9393
|
+
const todosDir2 = projectTodosDir(projectsDir2, slug);
|
|
9394
|
+
const checklist = await readChecklist(todosDir2, slug);
|
|
9395
|
+
const item = checklist.items.find((i) => i.id === (params(req).id ?? ""));
|
|
9396
|
+
if (!item) return null;
|
|
9397
|
+
item.status = "open";
|
|
9398
|
+
item.session = null;
|
|
9399
|
+
checklist.workspace = slug;
|
|
9400
|
+
await writeChecklist(todosDir2, checklist);
|
|
9401
|
+
return { ...item };
|
|
9402
|
+
});
|
|
9403
|
+
if (result === "gone") {
|
|
9404
|
+
notFound(res, slug);
|
|
9405
|
+
return;
|
|
9406
|
+
}
|
|
9407
|
+
if (!result) {
|
|
9408
|
+
res.status(404).json({ error: `Todo "${params(req).id ?? ""}" not found` });
|
|
9409
|
+
return;
|
|
9410
|
+
}
|
|
9411
|
+
broadcastUpdate(slug);
|
|
9412
|
+
res.json(result);
|
|
9413
|
+
} catch (error) {
|
|
9414
|
+
if (error.code === "PROJECT_GONE") {
|
|
9415
|
+
notFound(res, getProjectIdParam(params(req).projectId));
|
|
9416
|
+
return;
|
|
9417
|
+
}
|
|
9418
|
+
res.status(500).json({ error: error instanceof Error ? error.message : "Failed to unblock todo" });
|
|
9419
|
+
}
|
|
9420
|
+
});
|
|
9421
|
+
return router;
|
|
9422
|
+
}
|
|
9423
|
+
|
|
8814
9424
|
// src/dashboard/api-backup.ts
|
|
8815
9425
|
init_config2();
|
|
8816
|
-
import { Router as
|
|
9426
|
+
import { Router as Router7 } from "express";
|
|
8817
9427
|
|
|
8818
9428
|
// src/utils/github-backup.ts
|
|
8819
9429
|
init_paths();
|
|
@@ -8821,8 +9431,8 @@ init_fs();
|
|
|
8821
9431
|
init_config2();
|
|
8822
9432
|
import { execFile as execFile2 } from "child_process";
|
|
8823
9433
|
import { promisify as promisify2 } from "util";
|
|
8824
|
-
import { cp, mkdtemp, rm as rm2, readFile as
|
|
8825
|
-
import { resolve as
|
|
9434
|
+
import { cp, mkdtemp, rm as rm2, readFile as readFile15, writeFile as writeFile4, unlink as unlink3, stat, open, rename as rename3 } from "fs/promises";
|
|
9435
|
+
import { resolve as resolve20, join as join2 } from "path";
|
|
8826
9436
|
import { tmpdir } from "os";
|
|
8827
9437
|
var exec2 = promisify2(execFile2);
|
|
8828
9438
|
var VALID_CATEGORIES = ["projects", "playbooks", "todos", "servers", "config"];
|
|
@@ -8862,7 +9472,7 @@ async function resolveCategoryPath(category) {
|
|
|
8862
9472
|
case "servers":
|
|
8863
9473
|
return { sourcePath: serversDir(), repoPath: "servers", isFile: false };
|
|
8864
9474
|
case "config":
|
|
8865
|
-
return { sourcePath:
|
|
9475
|
+
return { sourcePath: resolve20(syntaurRoot(), "config.md"), repoPath: "config.md", isFile: true };
|
|
8866
9476
|
}
|
|
8867
9477
|
}
|
|
8868
9478
|
async function checkGitInstalled() {
|
|
@@ -8873,7 +9483,7 @@ async function checkGitInstalled() {
|
|
|
8873
9483
|
}
|
|
8874
9484
|
}
|
|
8875
9485
|
async function acquireLock() {
|
|
8876
|
-
const lockPath =
|
|
9486
|
+
const lockPath = resolve20(syntaurRoot(), LOCK_FILE_NAME);
|
|
8877
9487
|
await ensureDir(syntaurRoot());
|
|
8878
9488
|
try {
|
|
8879
9489
|
const handle = await open(lockPath, "wx");
|
|
@@ -8882,7 +9492,7 @@ async function acquireLock() {
|
|
|
8882
9492
|
return lockPath;
|
|
8883
9493
|
} catch (err2) {
|
|
8884
9494
|
if (err2.code === "EEXIST") {
|
|
8885
|
-
const pid = await
|
|
9495
|
+
const pid = await readFile15(lockPath, "utf-8").catch(() => "");
|
|
8886
9496
|
throw new Error(
|
|
8887
9497
|
`Backup operation already in progress (lock file at ${lockPath}, pid ${pid.trim() || "unknown"}). If stale, delete the file and retry.`
|
|
8888
9498
|
);
|
|
@@ -8920,7 +9530,7 @@ async function copyRecursive(src, dest) {
|
|
|
8920
9530
|
await ensureDir(dest);
|
|
8921
9531
|
await cp(src, dest, { recursive: true, force: true });
|
|
8922
9532
|
} else {
|
|
8923
|
-
await ensureDir(
|
|
9533
|
+
await ensureDir(resolve20(dest, ".."));
|
|
8924
9534
|
await cp(src, dest, { force: true });
|
|
8925
9535
|
}
|
|
8926
9536
|
}
|
|
@@ -8929,7 +9539,7 @@ function resolveCategoriesStrict(csv) {
|
|
|
8929
9539
|
return parseCategoriesStrict(parts);
|
|
8930
9540
|
}
|
|
8931
9541
|
async function readSanitizedConfig(configPath) {
|
|
8932
|
-
const content = await
|
|
9542
|
+
const content = await readFile15(configPath, "utf-8");
|
|
8933
9543
|
return content.replace(/^(\s*lastBackup:\s*).*$/m, "$1null").replace(/^(\s*lastRestore:\s*).*$/m, "$1null");
|
|
8934
9544
|
}
|
|
8935
9545
|
async function backupToGithub(overrides) {
|
|
@@ -8968,7 +9578,7 @@ async function backupToGithub(overrides) {
|
|
|
8968
9578
|
}
|
|
8969
9579
|
if (category === "config") {
|
|
8970
9580
|
const sanitized = await readSanitizedConfig(sourcePath);
|
|
8971
|
-
await ensureDir(
|
|
9581
|
+
await ensureDir(resolve20(destPath, ".."));
|
|
8972
9582
|
await writeFile4(destPath, sanitized, "utf-8");
|
|
8973
9583
|
} else {
|
|
8974
9584
|
await copyRecursive(sourcePath, destPath);
|
|
@@ -9022,7 +9632,7 @@ async function backupToGithub(overrides) {
|
|
|
9022
9632
|
}
|
|
9023
9633
|
async function safeRestoreCategory(localPath, repoSrcPath, isFile) {
|
|
9024
9634
|
if (isFile) {
|
|
9025
|
-
await ensureDir(
|
|
9635
|
+
await ensureDir(resolve20(localPath, ".."));
|
|
9026
9636
|
await cp(repoSrcPath, localPath, { force: true });
|
|
9027
9637
|
return;
|
|
9028
9638
|
}
|
|
@@ -9123,7 +9733,7 @@ async function restoreFromGithub(overrides) {
|
|
|
9123
9733
|
}
|
|
9124
9734
|
async function getBackupStatus() {
|
|
9125
9735
|
const config = await readConfig();
|
|
9126
|
-
const lockPath =
|
|
9736
|
+
const lockPath = resolve20(syntaurRoot(), LOCK_FILE_NAME);
|
|
9127
9737
|
const locked = await fileExists(lockPath);
|
|
9128
9738
|
return {
|
|
9129
9739
|
repo: config.backup?.repo ?? null,
|
|
@@ -9136,7 +9746,7 @@ async function getBackupStatus() {
|
|
|
9136
9746
|
|
|
9137
9747
|
// src/dashboard/api-backup.ts
|
|
9138
9748
|
function createBackupRouter() {
|
|
9139
|
-
const router =
|
|
9749
|
+
const router = Router7();
|
|
9140
9750
|
router.get("/", async (_req, res) => {
|
|
9141
9751
|
try {
|
|
9142
9752
|
const status = await getBackupStatus();
|
|
@@ -9460,7 +10070,7 @@ function createDashboardServer(options) {
|
|
|
9460
10070
|
(async () => {
|
|
9461
10071
|
try {
|
|
9462
10072
|
const configResult = await migrateLegacyConfig(
|
|
9463
|
-
|
|
10073
|
+
resolve21(syntaurRoot(), "config.md")
|
|
9464
10074
|
);
|
|
9465
10075
|
const projectResult = await migrateLegacyProjectFiles(projectsDir2);
|
|
9466
10076
|
const summary = summarizeMigration(projectResult, configResult);
|
|
@@ -9684,15 +10294,16 @@ function createDashboardServer(options) {
|
|
|
9684
10294
|
app.use("/api/agent-sessions", createAgentSessionsRouter(projectsDir2, broadcast, assignmentsDir2));
|
|
9685
10295
|
app.use("/api/playbooks", createPlaybooksRouter(playbooksDir3));
|
|
9686
10296
|
app.use("/api/todos", createTodosRouter(todosDir2, broadcast));
|
|
10297
|
+
app.use("/api/projects/:projectId/todos", createProjectTodosRouter(projectsDir2, broadcast));
|
|
9687
10298
|
app.use("/api/backup", createBackupRouter());
|
|
9688
10299
|
if (serveStaticUi && dashboardDistPath) {
|
|
9689
|
-
app.use("/assets", express.static(
|
|
10300
|
+
app.use("/assets", express.static(resolve21(dashboardDistPath, "assets")));
|
|
9690
10301
|
app.get("{*path}", async (req, res) => {
|
|
9691
10302
|
if (req.path.startsWith("/api") || req.path === "/ws" || req.path.startsWith("/assets")) {
|
|
9692
10303
|
res.status(404).json({ error: "Not Found" });
|
|
9693
10304
|
return;
|
|
9694
10305
|
}
|
|
9695
|
-
const indexPath =
|
|
10306
|
+
const indexPath = resolve21(dashboardDistPath, "index.html");
|
|
9696
10307
|
if (!await fileExists(indexPath)) {
|
|
9697
10308
|
res.status(503).send(
|
|
9698
10309
|
'Dashboard not built. Run "npm run build:dashboard" first.'
|
|
@@ -9730,7 +10341,7 @@ function createDashboardServer(options) {
|
|
|
9730
10341
|
}
|
|
9731
10342
|
});
|
|
9732
10343
|
server.listen(port, () => {
|
|
9733
|
-
const portFile =
|
|
10344
|
+
const portFile = resolve21(syntaurRoot(), "dashboard-port");
|
|
9734
10345
|
writeFile5(portFile, String(port), "utf-8").catch(() => {
|
|
9735
10346
|
});
|
|
9736
10347
|
resolvePromise();
|
|
@@ -9747,7 +10358,7 @@ function createDashboardServer(options) {
|
|
|
9747
10358
|
client.terminate();
|
|
9748
10359
|
}
|
|
9749
10360
|
clients.clear();
|
|
9750
|
-
const portFile =
|
|
10361
|
+
const portFile = resolve21(syntaurRoot(), "dashboard-port");
|
|
9751
10362
|
await unlink4(portFile).catch(() => {
|
|
9752
10363
|
});
|
|
9753
10364
|
server.closeAllConnections?.();
|
|
@@ -9827,8 +10438,8 @@ async function dashboardCommand(options) {
|
|
|
9827
10438
|
port = availablePort;
|
|
9828
10439
|
}
|
|
9829
10440
|
const thisFile = fileURLToPath2(import.meta.url);
|
|
9830
|
-
const packageRoot =
|
|
9831
|
-
const dashboardDist =
|
|
10441
|
+
const packageRoot = resolve22(dirname4(thisFile), "..");
|
|
10442
|
+
const dashboardDist = resolve22(packageRoot, "dashboard", "dist");
|
|
9832
10443
|
const server = createDashboardServer({
|
|
9833
10444
|
port,
|
|
9834
10445
|
projectsDir: projectsDir2,
|
|
@@ -9842,8 +10453,8 @@ async function dashboardCommand(options) {
|
|
|
9842
10453
|
await server.start();
|
|
9843
10454
|
let viteProcess = null;
|
|
9844
10455
|
if (mode === "dev") {
|
|
9845
|
-
const dashboardDir =
|
|
9846
|
-
const viteBin =
|
|
10456
|
+
const dashboardDir = resolve22(packageRoot, "dashboard");
|
|
10457
|
+
const viteBin = resolve22(dashboardDir, "node_modules", ".bin", "vite");
|
|
9847
10458
|
if (!await fileExists(viteBin)) {
|
|
9848
10459
|
console.error(
|
|
9849
10460
|
'Vite not found. Run "npm ci --prefix dashboard" first, or use the default bundled dashboard mode.'
|
|
@@ -9915,7 +10526,7 @@ async function dashboardCommand(options) {
|
|
|
9915
10526
|
init_paths();
|
|
9916
10527
|
init_fs();
|
|
9917
10528
|
init_config2();
|
|
9918
|
-
import { resolve as
|
|
10529
|
+
import { resolve as resolve23 } from "path";
|
|
9919
10530
|
init_lifecycle();
|
|
9920
10531
|
init_assignment_resolver();
|
|
9921
10532
|
async function runTransition(assignment, command, options = {}) {
|
|
@@ -9928,8 +10539,8 @@ async function runTransition(assignment, command, options = {}) {
|
|
|
9928
10539
|
if (!isValidSlug(assignment)) {
|
|
9929
10540
|
throw new Error(`Invalid assignment slug "${assignment}".`);
|
|
9930
10541
|
}
|
|
9931
|
-
const projectDir =
|
|
9932
|
-
const projectMdPath =
|
|
10542
|
+
const projectDir = resolve23(baseDir, options.project);
|
|
10543
|
+
const projectMdPath = resolve23(projectDir, "project.md");
|
|
9933
10544
|
if (!await fileExists(projectDir) || !await fileExists(projectMdPath)) {
|
|
9934
10545
|
throw new Error(`Project "${options.project}" not found at ${projectDir}.`);
|
|
9935
10546
|
}
|
|
@@ -9960,8 +10571,8 @@ async function runAssign(assignment, agent, options = {}) {
|
|
|
9960
10571
|
if (!isValidSlug(assignment)) {
|
|
9961
10572
|
throw new Error(`Invalid assignment slug "${assignment}".`);
|
|
9962
10573
|
}
|
|
9963
|
-
const projectDir =
|
|
9964
|
-
const projectMdPath =
|
|
10574
|
+
const projectDir = resolve23(baseDir, options.project);
|
|
10575
|
+
const projectMdPath = resolve23(projectDir, "project.md");
|
|
9965
10576
|
if (!await fileExists(projectDir) || !await fileExists(projectMdPath)) {
|
|
9966
10577
|
throw new Error(`Project "${options.project}" not found at ${projectDir}.`);
|
|
9967
10578
|
}
|
|
@@ -10047,7 +10658,7 @@ import {
|
|
|
10047
10658
|
readdir as readdir10,
|
|
10048
10659
|
symlink,
|
|
10049
10660
|
lstat,
|
|
10050
|
-
readFile as
|
|
10661
|
+
readFile as readFile16,
|
|
10051
10662
|
readlink,
|
|
10052
10663
|
rm as rm3,
|
|
10053
10664
|
unlink as unlink5,
|
|
@@ -10055,20 +10666,20 @@ import {
|
|
|
10055
10666
|
} from "fs/promises";
|
|
10056
10667
|
import { existsSync } from "fs";
|
|
10057
10668
|
import { homedir as homedir2 } from "os";
|
|
10058
|
-
import { basename, dirname as dirname6, isAbsolute as isAbsolute2, relative as relative2, resolve as
|
|
10669
|
+
import { basename, dirname as dirname6, isAbsolute as isAbsolute2, relative as relative2, resolve as resolve25 } from "path";
|
|
10059
10670
|
|
|
10060
10671
|
// src/utils/package-root.ts
|
|
10061
10672
|
init_fs();
|
|
10062
|
-
import { dirname as dirname5, resolve as
|
|
10673
|
+
import { dirname as dirname5, resolve as resolve24 } from "path";
|
|
10063
10674
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
10064
10675
|
async function findPackageRoot(expectedRelativePath) {
|
|
10065
10676
|
let currentDir = dirname5(fileURLToPath3(import.meta.url));
|
|
10066
10677
|
while (true) {
|
|
10067
|
-
const candidate =
|
|
10678
|
+
const candidate = resolve24(currentDir, expectedRelativePath);
|
|
10068
10679
|
if (await fileExists(candidate)) {
|
|
10069
10680
|
return currentDir;
|
|
10070
10681
|
}
|
|
10071
|
-
const parentDir =
|
|
10682
|
+
const parentDir = resolve24(currentDir, "..");
|
|
10072
10683
|
if (parentDir === currentDir) {
|
|
10073
10684
|
throw new Error(
|
|
10074
10685
|
`Could not locate package root containing ${expectedRelativePath}.`
|
|
@@ -10089,25 +10700,25 @@ function getPluginManifestRelativePath(pluginKind) {
|
|
|
10089
10700
|
}
|
|
10090
10701
|
function getDefaultPluginTargetDir(pluginKind) {
|
|
10091
10702
|
const home = homedir2();
|
|
10092
|
-
return pluginKind === "claude" ?
|
|
10703
|
+
return pluginKind === "claude" ? resolve25(home, ".claude", "plugins", "syntaur") : resolve25(home, "plugins", "syntaur");
|
|
10093
10704
|
}
|
|
10094
10705
|
function getDefaultMarketplacePath() {
|
|
10095
|
-
return
|
|
10706
|
+
return resolve25(homedir2(), ".agents", "plugins", "marketplace.json");
|
|
10096
10707
|
}
|
|
10097
10708
|
function getClaudeMarketplacesRoot() {
|
|
10098
|
-
return
|
|
10709
|
+
return resolve25(homedir2(), ".claude", "plugins", "marketplaces");
|
|
10099
10710
|
}
|
|
10100
10711
|
function getClaudeKnownMarketplacesPath() {
|
|
10101
|
-
return
|
|
10712
|
+
return resolve25(homedir2(), ".claude", "plugins", "known_marketplaces.json");
|
|
10102
10713
|
}
|
|
10103
10714
|
function getClaudeInstalledPluginsPath() {
|
|
10104
|
-
return
|
|
10715
|
+
return resolve25(homedir2(), ".claude", "plugins", "installed_plugins.json");
|
|
10105
10716
|
}
|
|
10106
10717
|
function getInstallMarkerPath(targetDir) {
|
|
10107
|
-
return
|
|
10718
|
+
return resolve25(targetDir, INSTALL_MARKER_FILENAME);
|
|
10108
10719
|
}
|
|
10109
10720
|
async function readPackageManifest(packageRoot) {
|
|
10110
|
-
const raw = await
|
|
10721
|
+
const raw = await readFile16(resolve25(packageRoot, "package.json"), "utf-8");
|
|
10111
10722
|
return JSON.parse(raw);
|
|
10112
10723
|
}
|
|
10113
10724
|
async function readJsonFileIfExists(pathValue) {
|
|
@@ -10115,7 +10726,7 @@ async function readJsonFileIfExists(pathValue) {
|
|
|
10115
10726
|
return null;
|
|
10116
10727
|
}
|
|
10117
10728
|
try {
|
|
10118
|
-
const raw = await
|
|
10729
|
+
const raw = await readFile16(pathValue, "utf-8");
|
|
10119
10730
|
return JSON.parse(raw);
|
|
10120
10731
|
} catch {
|
|
10121
10732
|
return null;
|
|
@@ -10123,15 +10734,15 @@ async function readJsonFileIfExists(pathValue) {
|
|
|
10123
10734
|
}
|
|
10124
10735
|
async function readClaudePluginManifest(pluginDir) {
|
|
10125
10736
|
return await readJsonFileIfExists(
|
|
10126
|
-
|
|
10737
|
+
resolve25(pluginDir, ".claude-plugin", "plugin.json")
|
|
10127
10738
|
) ?? {};
|
|
10128
10739
|
}
|
|
10129
10740
|
async function readPluginManifestName(targetDir, pluginKind) {
|
|
10130
|
-
const manifestPath =
|
|
10741
|
+
const manifestPath = resolve25(targetDir, getPluginManifestRelativePath(pluginKind));
|
|
10131
10742
|
if (!await fileExists(manifestPath)) {
|
|
10132
10743
|
return void 0;
|
|
10133
10744
|
}
|
|
10134
|
-
const raw = await
|
|
10745
|
+
const raw = await readFile16(manifestPath, "utf-8");
|
|
10135
10746
|
const parsed = JSON.parse(raw);
|
|
10136
10747
|
return parsed.name;
|
|
10137
10748
|
}
|
|
@@ -10141,7 +10752,7 @@ async function readInstallMetadata(targetDir) {
|
|
|
10141
10752
|
return null;
|
|
10142
10753
|
}
|
|
10143
10754
|
try {
|
|
10144
|
-
const raw = await
|
|
10755
|
+
const raw = await readFile16(markerPath, "utf-8");
|
|
10145
10756
|
return JSON.parse(raw);
|
|
10146
10757
|
} catch {
|
|
10147
10758
|
return null;
|
|
@@ -10154,7 +10765,7 @@ async function getInstallStatus(targetDir, pluginKind) {
|
|
|
10154
10765
|
const info = await lstat(targetDir);
|
|
10155
10766
|
if (info.isSymbolicLink()) {
|
|
10156
10767
|
const symlinkTarget = await readlink(targetDir);
|
|
10157
|
-
const resolvedTarget =
|
|
10768
|
+
const resolvedTarget = resolve25(dirname6(targetDir), symlinkTarget);
|
|
10158
10769
|
const manifestName2 = await readPluginManifestName(resolvedTarget, pluginKind);
|
|
10159
10770
|
return {
|
|
10160
10771
|
exists: true,
|
|
@@ -10200,7 +10811,7 @@ async function installLink(paths) {
|
|
|
10200
10811
|
await ensureDir(dirname6(paths.targetDir));
|
|
10201
10812
|
await rm3(paths.targetDir, { recursive: true, force: true });
|
|
10202
10813
|
await ensureDir(dirname6(paths.targetDir));
|
|
10203
|
-
await symlink(
|
|
10814
|
+
await symlink(resolve25(paths.sourceDir), paths.targetDir, "dir");
|
|
10204
10815
|
}
|
|
10205
10816
|
async function removeInstallMarker(targetDir) {
|
|
10206
10817
|
const markerPath = getInstallMarkerPath(targetDir);
|
|
@@ -10214,13 +10825,13 @@ function normalizeAbsoluteInstallPath(pathValue, label) {
|
|
|
10214
10825
|
if (!isAbsolute2(expanded)) {
|
|
10215
10826
|
throw new Error(`${label} must be an absolute path.`);
|
|
10216
10827
|
}
|
|
10217
|
-
return
|
|
10828
|
+
return resolve25(expanded);
|
|
10218
10829
|
}
|
|
10219
10830
|
async function resolvePluginPaths(pluginKind, targetDir) {
|
|
10220
10831
|
const packageRoot = await findPackageRoot(getPluginRelativePath(pluginKind));
|
|
10221
10832
|
return {
|
|
10222
10833
|
packageRoot,
|
|
10223
|
-
sourceDir:
|
|
10834
|
+
sourceDir: resolve25(packageRoot, getPluginRelativePath(pluginKind)),
|
|
10224
10835
|
targetDir: targetDir ?? getDefaultPluginTargetDir(pluginKind)
|
|
10225
10836
|
};
|
|
10226
10837
|
}
|
|
@@ -10324,8 +10935,8 @@ async function listClaudeMarketplaceCandidates() {
|
|
|
10324
10935
|
if (!entry.isDirectory()) {
|
|
10325
10936
|
continue;
|
|
10326
10937
|
}
|
|
10327
|
-
const candidateRoot =
|
|
10328
|
-
const manifestPath =
|
|
10938
|
+
const candidateRoot = resolve25(rootDir, entry.name);
|
|
10939
|
+
const manifestPath = resolve25(candidateRoot, ".claude-plugin", "marketplace.json");
|
|
10329
10940
|
if (!await fileExists(manifestPath)) {
|
|
10330
10941
|
continue;
|
|
10331
10942
|
}
|
|
@@ -10352,11 +10963,11 @@ async function listClaudeMarketplaceCandidates() {
|
|
|
10352
10963
|
if (!installLocation) {
|
|
10353
10964
|
continue;
|
|
10354
10965
|
}
|
|
10355
|
-
const candidateRoot =
|
|
10966
|
+
const candidateRoot = resolve25(expandHome(installLocation));
|
|
10356
10967
|
if (seen.has(candidateRoot)) {
|
|
10357
10968
|
continue;
|
|
10358
10969
|
}
|
|
10359
|
-
const manifestPath =
|
|
10970
|
+
const manifestPath = resolve25(candidateRoot, ".claude-plugin", "marketplace.json");
|
|
10360
10971
|
if (!await fileExists(manifestPath)) {
|
|
10361
10972
|
continue;
|
|
10362
10973
|
}
|
|
@@ -10384,7 +10995,7 @@ async function getPreferredClaudeMarketplace() {
|
|
|
10384
10995
|
name: candidate.name,
|
|
10385
10996
|
rootDir: candidate.rootDir,
|
|
10386
10997
|
manifestPath: candidate.manifestPath,
|
|
10387
|
-
targetDir:
|
|
10998
|
+
targetDir: resolve25(candidate.rootDir, "plugins", "syntaur")
|
|
10388
10999
|
};
|
|
10389
11000
|
}
|
|
10390
11001
|
async function registerKnownClaudeMarketplace(name, rootDir) {
|
|
@@ -10411,9 +11022,9 @@ async function ensureClaudeUserMarketplace() {
|
|
|
10411
11022
|
if (existing) {
|
|
10412
11023
|
return existing;
|
|
10413
11024
|
}
|
|
10414
|
-
const rootDir =
|
|
10415
|
-
const manifestPath =
|
|
10416
|
-
await ensureDir(
|
|
11025
|
+
const rootDir = resolve25(getClaudeMarketplacesRoot(), "user-plugins");
|
|
11026
|
+
const manifestPath = resolve25(rootDir, ".claude-plugin", "marketplace.json");
|
|
11027
|
+
await ensureDir(resolve25(rootDir, "plugins"));
|
|
10417
11028
|
if (!await fileExists(manifestPath)) {
|
|
10418
11029
|
const scaffold = {
|
|
10419
11030
|
plugins: []
|
|
@@ -10432,7 +11043,7 @@ async function ensureClaudeUserMarketplace() {
|
|
|
10432
11043
|
name: "user-plugins",
|
|
10433
11044
|
rootDir,
|
|
10434
11045
|
manifestPath,
|
|
10435
|
-
targetDir:
|
|
11046
|
+
targetDir: resolve25(rootDir, "plugins", "syntaur")
|
|
10436
11047
|
};
|
|
10437
11048
|
}
|
|
10438
11049
|
async function detectClaudeMarketplaceForTarget(targetDir) {
|
|
@@ -10442,7 +11053,7 @@ async function detectClaudeMarketplaceForTarget(targetDir) {
|
|
|
10442
11053
|
return null;
|
|
10443
11054
|
}
|
|
10444
11055
|
const rootDir = dirname6(pluginsDir);
|
|
10445
|
-
const manifestPath =
|
|
11056
|
+
const manifestPath = resolve25(rootDir, ".claude-plugin", "marketplace.json");
|
|
10446
11057
|
if (!await fileExists(manifestPath)) {
|
|
10447
11058
|
return null;
|
|
10448
11059
|
}
|
|
@@ -10458,7 +11069,7 @@ async function detectClaudeMarketplaceForTarget(targetDir) {
|
|
|
10458
11069
|
async function findManagedClaudeMarketplacePluginDir() {
|
|
10459
11070
|
const marketplaces = await listClaudeMarketplaceCandidates();
|
|
10460
11071
|
for (const marketplace of marketplaces) {
|
|
10461
|
-
const targetDir =
|
|
11072
|
+
const targetDir = resolve25(marketplace.rootDir, "plugins", "syntaur");
|
|
10462
11073
|
const status = await getInstallStatus(targetDir, "claude");
|
|
10463
11074
|
if (status.exists && status.managed) {
|
|
10464
11075
|
return targetDir;
|
|
@@ -10583,7 +11194,7 @@ async function installManagedPlugin(options) {
|
|
|
10583
11194
|
`${paths.targetDir} already exists and is not a Syntaur-managed install. Remove it manually before installing Syntaur there.`
|
|
10584
11195
|
);
|
|
10585
11196
|
}
|
|
10586
|
-
if (desiredMode === "link" && existing.exists && existing.installMode === "link" && existing.symlinkTarget ===
|
|
11197
|
+
if (desiredMode === "link" && existing.exists && existing.installMode === "link" && existing.symlinkTarget === resolve25(paths.sourceDir) && !force) {
|
|
10587
11198
|
return {
|
|
10588
11199
|
targetDir: paths.targetDir,
|
|
10589
11200
|
sourceDir: paths.sourceDir,
|
|
@@ -10635,7 +11246,7 @@ async function readMarketplaceFile(marketplacePath) {
|
|
|
10635
11246
|
plugins: []
|
|
10636
11247
|
};
|
|
10637
11248
|
}
|
|
10638
|
-
const raw = await
|
|
11249
|
+
const raw = await readFile16(marketplacePath, "utf-8");
|
|
10639
11250
|
const parsed = JSON.parse(raw);
|
|
10640
11251
|
return {
|
|
10641
11252
|
name: parsed.name ?? "local",
|
|
@@ -10796,13 +11407,13 @@ async function recommendMarketplacePath() {
|
|
|
10796
11407
|
return configuredOrManaged ?? getDefaultMarketplacePath();
|
|
10797
11408
|
}
|
|
10798
11409
|
async function isSyntaurDataInstalled() {
|
|
10799
|
-
return fileExists(
|
|
11410
|
+
return fileExists(resolve25(syntaurRoot(), "config.md"));
|
|
10800
11411
|
}
|
|
10801
11412
|
async function removeSyntaurData() {
|
|
10802
11413
|
await rm3(syntaurRoot(), { recursive: true, force: true });
|
|
10803
11414
|
}
|
|
10804
11415
|
async function getConfiguredProjectDir() {
|
|
10805
|
-
if (!await fileExists(
|
|
11416
|
+
if (!await fileExists(resolve25(syntaurRoot(), "config.md"))) {
|
|
10806
11417
|
return null;
|
|
10807
11418
|
}
|
|
10808
11419
|
return (await readConfig()).defaultProjectDir;
|
|
@@ -10871,8 +11482,8 @@ async function textPrompt(question, defaultValue) {
|
|
|
10871
11482
|
|
|
10872
11483
|
// src/utils/install-skills.ts
|
|
10873
11484
|
init_fs();
|
|
10874
|
-
import { readFile as
|
|
10875
|
-
import { dirname as dirname7, resolve as
|
|
11485
|
+
import { readFile as readFile17, readdir as readdir11, mkdir as mkdir3, copyFile, rm as rm4 } from "fs/promises";
|
|
11486
|
+
import { dirname as dirname7, resolve as resolve26, relative as relative3, join as join3 } from "path";
|
|
10876
11487
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
10877
11488
|
import { homedir as homedir3 } from "os";
|
|
10878
11489
|
var REQUIRED_SKILLS = [
|
|
@@ -10885,11 +11496,11 @@ var REQUIRED_SKILLS = [
|
|
|
10885
11496
|
];
|
|
10886
11497
|
function getVendoredSkillsDir() {
|
|
10887
11498
|
const here = dirname7(fileURLToPath4(import.meta.url));
|
|
10888
|
-
return
|
|
11499
|
+
return resolve26(here, "..", "vendor", "syntaur-skills", "skills");
|
|
10889
11500
|
}
|
|
10890
11501
|
function defaultSkillTargetDir(target) {
|
|
10891
|
-
if (target === "claude") return
|
|
10892
|
-
return
|
|
11502
|
+
if (target === "claude") return resolve26(homedir3(), ".claude", "skills");
|
|
11503
|
+
return resolve26(homedir3(), ".codex", "skills");
|
|
10893
11504
|
}
|
|
10894
11505
|
async function walkFiles(root) {
|
|
10895
11506
|
const out = [];
|
|
@@ -10909,7 +11520,7 @@ async function walkFiles(root) {
|
|
|
10909
11520
|
}
|
|
10910
11521
|
async function filesEqual(a, b) {
|
|
10911
11522
|
try {
|
|
10912
|
-
const [ba, bb] = await Promise.all([
|
|
11523
|
+
const [ba, bb] = await Promise.all([readFile17(a), readFile17(b)]);
|
|
10913
11524
|
if (ba.length !== bb.length) return false;
|
|
10914
11525
|
return ba.equals(bb);
|
|
10915
11526
|
} catch {
|
|
@@ -10917,7 +11528,7 @@ async function filesEqual(a, b) {
|
|
|
10917
11528
|
}
|
|
10918
11529
|
}
|
|
10919
11530
|
async function copyDir(srcDir, destDir) {
|
|
10920
|
-
await
|
|
11531
|
+
await mkdir3(destDir, { recursive: true });
|
|
10921
11532
|
const entries = await readdir11(srcDir, { withFileTypes: true });
|
|
10922
11533
|
for (const entry of entries) {
|
|
10923
11534
|
const src = join3(srcDir, entry.name);
|
|
@@ -10951,7 +11562,7 @@ async function installSkills(options) {
|
|
|
10951
11562
|
);
|
|
10952
11563
|
}
|
|
10953
11564
|
const results = [];
|
|
10954
|
-
await
|
|
11565
|
+
await mkdir3(targetRoot, { recursive: true });
|
|
10955
11566
|
for (const skill of REQUIRED_SKILLS) {
|
|
10956
11567
|
const srcDir = join3(source, skill);
|
|
10957
11568
|
const destDir = join3(targetRoot, skill);
|
|
@@ -11000,7 +11611,7 @@ async function uninstallSkills(options) {
|
|
|
11000
11611
|
if (!await fileExists(destDir)) continue;
|
|
11001
11612
|
const skillMd = join3(destDir, "SKILL.md");
|
|
11002
11613
|
if (!await fileExists(skillMd)) continue;
|
|
11003
|
-
const content = await
|
|
11614
|
+
const content = await readFile17(skillMd, "utf-8").catch(() => "");
|
|
11004
11615
|
const match = content.match(/^name:\s*(\S+)\s*$/m);
|
|
11005
11616
|
if (!match || match[1] !== skill) continue;
|
|
11006
11617
|
await rm4(destDir, { recursive: true, force: true });
|
|
@@ -11137,16 +11748,16 @@ async function installPluginCommand(options) {
|
|
|
11137
11748
|
// src/commands/install-statusline.ts
|
|
11138
11749
|
init_paths();
|
|
11139
11750
|
init_fs();
|
|
11140
|
-
import { readFile as
|
|
11141
|
-
import { resolve as
|
|
11751
|
+
import { readFile as readFile19, writeFile as writeFile8, copyFile as copyFile2, rm as rm5, stat as stat3, symlink as symlink2, unlink as unlink6, lstat as lstat2 } from "fs/promises";
|
|
11752
|
+
import { resolve as resolve28, dirname as dirname9 } from "path";
|
|
11142
11753
|
import { homedir as homedir4 } from "os";
|
|
11143
11754
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
11144
11755
|
|
|
11145
11756
|
// src/commands/configure-statusline.ts
|
|
11146
11757
|
init_paths();
|
|
11147
11758
|
init_fs();
|
|
11148
|
-
import { readFile as
|
|
11149
|
-
import { resolve as
|
|
11759
|
+
import { readFile as readFile18, writeFile as writeFile7 } from "fs/promises";
|
|
11760
|
+
import { resolve as resolve27, dirname as dirname8 } from "path";
|
|
11150
11761
|
import { spawnSync } from "child_process";
|
|
11151
11762
|
import { checkbox, input as input2, confirm } from "@inquirer/prompts";
|
|
11152
11763
|
var AVAILABLE_SEGMENTS = [
|
|
@@ -11167,12 +11778,12 @@ var PRESETS = {
|
|
|
11167
11778
|
tracker: { segments: ["git", "assignment", "external", "session"], separator: " \xB7 " }
|
|
11168
11779
|
};
|
|
11169
11780
|
function getConfigPath(installRoot) {
|
|
11170
|
-
return
|
|
11781
|
+
return resolve27(installRoot, "statusline.config.json");
|
|
11171
11782
|
}
|
|
11172
11783
|
async function readConfig2(path) {
|
|
11173
11784
|
if (!await fileExists(path)) return null;
|
|
11174
11785
|
try {
|
|
11175
|
-
const raw = await
|
|
11786
|
+
const raw = await readFile18(path, "utf-8");
|
|
11176
11787
|
const parsed = JSON.parse(raw);
|
|
11177
11788
|
if (!parsed || typeof parsed !== "object") return null;
|
|
11178
11789
|
const segments = Array.isArray(parsed.segments) ? parsed.segments.filter(isSegmentName) : [];
|
|
@@ -11325,7 +11936,7 @@ async function configureStatuslineCommand(options = {}) {
|
|
|
11325
11936
|
console.log(` segments: ${config.segments.join(", ")}`);
|
|
11326
11937
|
console.log(` separator: ${JSON.stringify(config.separator)}`);
|
|
11327
11938
|
if (config.wrap) console.log(` wrap: ${config.wrap}`);
|
|
11328
|
-
const script = options.statuslineScript ??
|
|
11939
|
+
const script = options.statuslineScript ?? resolve27(installRoot, "statusline.sh");
|
|
11329
11940
|
if (await fileExists(script)) {
|
|
11330
11941
|
console.log("");
|
|
11331
11942
|
console.log("Live preview:");
|
|
@@ -11356,11 +11967,11 @@ async function writeDefaultConfigIfMissing(installRoot) {
|
|
|
11356
11967
|
// src/commands/install-statusline.ts
|
|
11357
11968
|
function getPackageStatuslineSource() {
|
|
11358
11969
|
const here = dirname9(fileURLToPath5(import.meta.url));
|
|
11359
|
-
return
|
|
11970
|
+
return resolve28(here, "..", "statusline", "statusline.sh");
|
|
11360
11971
|
}
|
|
11361
11972
|
async function readSettingsJson(settingsPath) {
|
|
11362
11973
|
if (!await fileExists(settingsPath)) return {};
|
|
11363
|
-
const raw = await
|
|
11974
|
+
const raw = await readFile19(settingsPath, "utf-8");
|
|
11364
11975
|
if (raw.trim() === "") return {};
|
|
11365
11976
|
try {
|
|
11366
11977
|
const parsed = JSON.parse(raw);
|
|
@@ -11440,12 +12051,12 @@ async function installScript(sourceScript, destScript, link) {
|
|
|
11440
12051
|
}
|
|
11441
12052
|
async function installStatuslineCommand(options = {}) {
|
|
11442
12053
|
const mode = options.mode ?? "ask";
|
|
11443
|
-
const settingsPath = options.settingsPath ??
|
|
12054
|
+
const settingsPath = options.settingsPath ?? resolve28(homedir4(), ".claude", "settings.json");
|
|
11444
12055
|
const installRoot = options.installRoot ?? syntaurRoot();
|
|
11445
12056
|
const sourceScript = options.sourceScript ?? getPackageStatuslineSource();
|
|
11446
|
-
const destScript =
|
|
11447
|
-
const confPath =
|
|
11448
|
-
const backupPath =
|
|
12057
|
+
const destScript = resolve28(installRoot, "statusline.sh");
|
|
12058
|
+
const confPath = resolve28(installRoot, "statusline.conf");
|
|
12059
|
+
const backupPath = resolve28(installRoot, "statusline.backup.json");
|
|
11449
12060
|
if (!await fileExists(sourceScript)) {
|
|
11450
12061
|
throw new Error(
|
|
11451
12062
|
`Statusline source script not found at ${sourceScript}. Try re-installing syntaur (npm install -g syntaur) or pass --source-script explicitly.`
|
|
@@ -11481,7 +12092,7 @@ async function installStatuslineCommand(options = {}) {
|
|
|
11481
12092
|
if (parsed) {
|
|
11482
12093
|
wrapTarget = parsed;
|
|
11483
12094
|
} else {
|
|
11484
|
-
const wrapperPath =
|
|
12095
|
+
const wrapperPath = resolve28(installRoot, "statusline-wrapped.sh");
|
|
11485
12096
|
const wrapperBody = `#!/usr/bin/env bash
|
|
11486
12097
|
# Auto-generated by syntaur install-statusline.
|
|
11487
12098
|
# Executes the previously configured statusLine command.
|
|
@@ -11536,19 +12147,19 @@ async function chmodExec(path) {
|
|
|
11536
12147
|
}
|
|
11537
12148
|
}
|
|
11538
12149
|
async function uninstallStatuslineCommand(options = {}) {
|
|
11539
|
-
const settingsPath = options.settingsPath ??
|
|
12150
|
+
const settingsPath = options.settingsPath ?? resolve28(homedir4(), ".claude", "settings.json");
|
|
11540
12151
|
const installRoot = options.installRoot ?? syntaurRoot();
|
|
11541
|
-
const destScript =
|
|
11542
|
-
const confPath =
|
|
11543
|
-
const backupPath =
|
|
11544
|
-
const wrapperPath =
|
|
12152
|
+
const destScript = resolve28(installRoot, "statusline.sh");
|
|
12153
|
+
const confPath = resolve28(installRoot, "statusline.conf");
|
|
12154
|
+
const backupPath = resolve28(installRoot, "statusline.backup.json");
|
|
12155
|
+
const wrapperPath = resolve28(installRoot, "statusline-wrapped.sh");
|
|
11545
12156
|
const settings = await readSettingsJson(settingsPath);
|
|
11546
12157
|
const existing = extractExistingCommand(settings);
|
|
11547
12158
|
const ourCommand = `bash ${destScript}`;
|
|
11548
12159
|
let restored = null;
|
|
11549
12160
|
if (await fileExists(backupPath)) {
|
|
11550
12161
|
try {
|
|
11551
|
-
const raw = await
|
|
12162
|
+
const raw = await readFile19(backupPath, "utf-8");
|
|
11552
12163
|
const parsed = JSON.parse(raw);
|
|
11553
12164
|
const prev = parsed?.previousStatusLine;
|
|
11554
12165
|
if (prev && typeof prev === "object" && typeof prev.command === "string") {
|
|
@@ -11569,7 +12180,7 @@ async function uninstallStatuslineCommand(options = {}) {
|
|
|
11569
12180
|
await writeSettingsJson(settingsPath, settings);
|
|
11570
12181
|
}
|
|
11571
12182
|
if (!options.keepScript) {
|
|
11572
|
-
const configPath =
|
|
12183
|
+
const configPath = resolve28(installRoot, "statusline.config.json");
|
|
11573
12184
|
for (const path of [destScript, confPath, backupPath, wrapperPath, configPath]) {
|
|
11574
12185
|
try {
|
|
11575
12186
|
await rm5(path, { force: true });
|
|
@@ -11824,7 +12435,7 @@ async function setupCommand(options) {
|
|
|
11824
12435
|
}
|
|
11825
12436
|
|
|
11826
12437
|
// src/commands/uninstall.ts
|
|
11827
|
-
import { resolve as
|
|
12438
|
+
import { resolve as resolve29 } from "path";
|
|
11828
12439
|
init_paths();
|
|
11829
12440
|
function expandTargets(options) {
|
|
11830
12441
|
if (options.all) {
|
|
@@ -11904,7 +12515,7 @@ async function uninstallCommand(options) {
|
|
|
11904
12515
|
const configuredProjectDir = await getConfiguredProjectDir();
|
|
11905
12516
|
await removeSyntaurData();
|
|
11906
12517
|
console.log(`Removed ${syntaurRoot()}`);
|
|
11907
|
-
if (configuredProjectDir &&
|
|
12518
|
+
if (configuredProjectDir && resolve29(configuredProjectDir) !== resolve29(syntaurRoot(), "projects")) {
|
|
11908
12519
|
console.warn(
|
|
11909
12520
|
`Warning: config.md pointed to an external project directory (${configuredProjectDir}). That directory was not removed automatically.`
|
|
11910
12521
|
);
|
|
@@ -11919,7 +12530,7 @@ async function uninstallCommand(options) {
|
|
|
11919
12530
|
init_paths();
|
|
11920
12531
|
init_fs();
|
|
11921
12532
|
init_config2();
|
|
11922
|
-
import { resolve as
|
|
12533
|
+
import { resolve as resolve30 } from "path";
|
|
11923
12534
|
var SUPPORTED_FRAMEWORKS = ["cursor", "codex", "opencode"];
|
|
11924
12535
|
async function setupAdapterCommand(framework, options) {
|
|
11925
12536
|
if (!SUPPORTED_FRAMEWORKS.includes(framework)) {
|
|
@@ -11945,19 +12556,19 @@ async function setupAdapterCommand(framework, options) {
|
|
|
11945
12556
|
}
|
|
11946
12557
|
const config = await readConfig();
|
|
11947
12558
|
const baseDir = options.dir ? expandHome(options.dir) : config.defaultProjectDir;
|
|
11948
|
-
const projectDir =
|
|
11949
|
-
const assignmentDir =
|
|
12559
|
+
const projectDir = resolve30(baseDir, options.project);
|
|
12560
|
+
const assignmentDir = resolve30(
|
|
11950
12561
|
projectDir,
|
|
11951
12562
|
"assignments",
|
|
11952
12563
|
options.assignment
|
|
11953
12564
|
);
|
|
11954
|
-
const projectMdPath =
|
|
12565
|
+
const projectMdPath = resolve30(projectDir, "project.md");
|
|
11955
12566
|
if (!await fileExists(projectDir) || !await fileExists(projectMdPath)) {
|
|
11956
12567
|
throw new Error(
|
|
11957
12568
|
`Project "${options.project}" not found at ${projectDir}.`
|
|
11958
12569
|
);
|
|
11959
12570
|
}
|
|
11960
|
-
const assignmentMdPath =
|
|
12571
|
+
const assignmentMdPath = resolve30(assignmentDir, "assignment.md");
|
|
11961
12572
|
if (!await fileExists(assignmentDir) || !await fileExists(assignmentMdPath)) {
|
|
11962
12573
|
throw new Error(
|
|
11963
12574
|
`Assignment "${options.assignment}" not found at ${assignmentDir}.`
|
|
@@ -11985,15 +12596,15 @@ async function setupAdapterCommand(framework, options) {
|
|
|
11985
12596
|
}
|
|
11986
12597
|
}
|
|
11987
12598
|
if (framework === "cursor") {
|
|
11988
|
-
const protocolPath =
|
|
11989
|
-
const assignmentPath =
|
|
12599
|
+
const protocolPath = resolve30(cwd, ".cursor", "rules", "syntaur-protocol.mdc");
|
|
12600
|
+
const assignmentPath = resolve30(cwd, ".cursor", "rules", "syntaur-assignment.mdc");
|
|
11990
12601
|
await writeAdapterFile(protocolPath, renderCursorProtocol());
|
|
11991
12602
|
await writeAdapterFile(assignmentPath, renderCursorAssignment(rendererParams));
|
|
11992
12603
|
} else if (framework === "codex" || framework === "opencode") {
|
|
11993
|
-
const agentsPath =
|
|
12604
|
+
const agentsPath = resolve30(cwd, "AGENTS.md");
|
|
11994
12605
|
await writeAdapterFile(agentsPath, renderCodexAgents(rendererParams));
|
|
11995
12606
|
if (framework === "opencode") {
|
|
11996
|
-
const configPath =
|
|
12607
|
+
const configPath = resolve30(cwd, "opencode.json");
|
|
11997
12608
|
await writeAdapterFile(configPath, renderOpenCodeConfig({ projectDir }));
|
|
11998
12609
|
}
|
|
11999
12610
|
}
|
|
@@ -12018,7 +12629,7 @@ async function setupAdapterCommand(framework, options) {
|
|
|
12018
12629
|
init_paths();
|
|
12019
12630
|
init_fs();
|
|
12020
12631
|
init_config2();
|
|
12021
|
-
import { resolve as
|
|
12632
|
+
import { resolve as resolve31 } from "path";
|
|
12022
12633
|
async function trackSessionCommand(options) {
|
|
12023
12634
|
if (!options.agent) {
|
|
12024
12635
|
throw new Error("--agent <name> is required.");
|
|
@@ -12031,7 +12642,7 @@ async function trackSessionCommand(options) {
|
|
|
12031
12642
|
if (options.project) {
|
|
12032
12643
|
const config = await readConfig();
|
|
12033
12644
|
const baseDir = options.dir ? expandHome(options.dir) : config.defaultProjectDir;
|
|
12034
|
-
const projectDir =
|
|
12645
|
+
const projectDir = resolve31(baseDir, options.project);
|
|
12035
12646
|
if (!await fileExists(projectDir)) {
|
|
12036
12647
|
throw new Error(
|
|
12037
12648
|
`Project "${options.project}" not found at ${projectDir}.`
|
|
@@ -12086,7 +12697,7 @@ async function browseCommand(options) {
|
|
|
12086
12697
|
}
|
|
12087
12698
|
|
|
12088
12699
|
// src/commands/create-playbook.ts
|
|
12089
|
-
import { resolve as
|
|
12700
|
+
import { resolve as resolve33 } from "path";
|
|
12090
12701
|
init_timestamp();
|
|
12091
12702
|
init_paths();
|
|
12092
12703
|
init_fs();
|
|
@@ -12103,7 +12714,7 @@ async function createPlaybookCommand(name, options) {
|
|
|
12103
12714
|
}
|
|
12104
12715
|
const dir = playbooksDir();
|
|
12105
12716
|
await ensureDir(dir);
|
|
12106
|
-
const filePath =
|
|
12717
|
+
const filePath = resolve33(dir, `${slug}.md`);
|
|
12107
12718
|
if (await fileExists(filePath)) {
|
|
12108
12719
|
throw new Error(
|
|
12109
12720
|
`Playbook "${slug}" already exists at ${filePath}
|
|
@@ -12125,8 +12736,8 @@ init_paths();
|
|
|
12125
12736
|
init_fs();
|
|
12126
12737
|
init_parser();
|
|
12127
12738
|
init_config2();
|
|
12128
|
-
import { readdir as readdir12, readFile as
|
|
12129
|
-
import { resolve as
|
|
12739
|
+
import { readdir as readdir12, readFile as readFile20 } from "fs/promises";
|
|
12740
|
+
import { resolve as resolve34 } from "path";
|
|
12130
12741
|
async function listPlaybooksCommand(options = {}) {
|
|
12131
12742
|
const dir = playbooksDir();
|
|
12132
12743
|
if (!await fileExists(dir)) {
|
|
@@ -12141,8 +12752,8 @@ async function listPlaybooksCommand(options = {}) {
|
|
|
12141
12752
|
);
|
|
12142
12753
|
const rows = [];
|
|
12143
12754
|
for (const entry of mdFiles) {
|
|
12144
|
-
const filePath =
|
|
12145
|
-
const raw = await
|
|
12755
|
+
const filePath = resolve34(dir, entry.name);
|
|
12756
|
+
const raw = await readFile20(filePath, "utf-8");
|
|
12146
12757
|
const parsed = parsePlaybook(raw);
|
|
12147
12758
|
const slug = parsed.slug || entry.name.replace(/\.md$/, "");
|
|
12148
12759
|
const disabled = disabledSet.has(slug);
|
|
@@ -12222,28 +12833,44 @@ async function disablePlaybookCommand(slug) {
|
|
|
12222
12833
|
init_paths();
|
|
12223
12834
|
init_parser2();
|
|
12224
12835
|
init_fs();
|
|
12836
|
+
init_config2();
|
|
12225
12837
|
import { Command } from "commander";
|
|
12226
|
-
import { readFile as
|
|
12227
|
-
import { resolve as
|
|
12838
|
+
import { readFile as readFile21 } from "fs/promises";
|
|
12839
|
+
import { resolve as resolve35 } from "path";
|
|
12228
12840
|
var WORKSPACE_REGEX2 = /^[a-z0-9_][a-z0-9-]*$/;
|
|
12229
|
-
function
|
|
12230
|
-
|
|
12841
|
+
async function resolveScope(options) {
|
|
12842
|
+
const flagCount = [Boolean(options.project), Boolean(options.workspace), Boolean(options.global)].filter(Boolean).length;
|
|
12843
|
+
if (flagCount > 1) {
|
|
12844
|
+
throw new Error("Use at most one of --project, --workspace, --global.");
|
|
12845
|
+
}
|
|
12846
|
+
if (options.project) {
|
|
12847
|
+
if (!isValidSlug(options.project)) {
|
|
12848
|
+
throw new Error(`Invalid project slug: "${options.project}".`);
|
|
12849
|
+
}
|
|
12850
|
+
const config = await readConfig();
|
|
12851
|
+
const projectMd = resolve35(config.defaultProjectDir, options.project, "project.md");
|
|
12852
|
+
if (!await fileExists(projectMd)) {
|
|
12853
|
+
throw new Error(`Project "${options.project}" not found.`);
|
|
12854
|
+
}
|
|
12855
|
+
return { kind: "project", id: options.project, todosPath: projectTodosDir(config.defaultProjectDir, options.project) };
|
|
12856
|
+
}
|
|
12231
12857
|
if (options.workspace) {
|
|
12232
12858
|
if (!WORKSPACE_REGEX2.test(options.workspace)) {
|
|
12233
12859
|
throw new Error(`Invalid workspace name: "${options.workspace}". Use lowercase letters, numbers, hyphens, and underscores.`);
|
|
12234
12860
|
}
|
|
12235
|
-
return options.workspace;
|
|
12861
|
+
return { kind: "workspace", id: options.workspace, todosPath: todosDir() };
|
|
12236
12862
|
}
|
|
12237
|
-
return "_global";
|
|
12863
|
+
return { kind: "workspace", id: "_global", todosPath: todosDir() };
|
|
12238
12864
|
}
|
|
12239
12865
|
function nowISO() {
|
|
12240
12866
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
12241
12867
|
}
|
|
12242
12868
|
var todoCommand = new Command("todo").description("Manage quick todos");
|
|
12243
|
-
todoCommand.command("add").description("Add a new todo item").argument("<description>", "Todo description").option("--tags <tags>", "Comma-separated tags").option("--workspace <slug>", "Workspace slug").option("--global", "Use global todos").action(async (description, options) => {
|
|
12869
|
+
todoCommand.command("add").description("Add a new todo item").argument("<description>", "Todo description").option("--tags <tags>", "Comma-separated tags").option("--workspace <slug>", "Workspace slug").option("--project <slug>", "Project slug (mutually exclusive with --workspace/--global)").option("--global", "Use global todos").action(async (description, options) => {
|
|
12244
12870
|
try {
|
|
12245
|
-
const
|
|
12246
|
-
const
|
|
12871
|
+
const scope = await resolveScope(options);
|
|
12872
|
+
const todosPath = scope.todosPath;
|
|
12873
|
+
const workspace = scope.id;
|
|
12247
12874
|
const checklist = await readChecklist(todosPath, workspace);
|
|
12248
12875
|
const existingIds = new Set(checklist.items.map((i) => i.id));
|
|
12249
12876
|
const id = generateUniqueId(existingIds);
|
|
@@ -12257,10 +12884,11 @@ todoCommand.command("add").description("Add a new todo item").argument("<descrip
|
|
|
12257
12884
|
process.exit(1);
|
|
12258
12885
|
}
|
|
12259
12886
|
});
|
|
12260
|
-
todoCommand.command("list").description("List todo items").option("--tag <tag>", "Filter by tag").option("--status <status>", "Filter by status (open|in_progress|completed|blocked)").option("--workspace <slug>", "Workspace slug").option("--global", "Use global todos").action(async (options) => {
|
|
12887
|
+
todoCommand.command("list").description("List todo items").option("--tag <tag>", "Filter by tag").option("--status <status>", "Filter by status (open|in_progress|completed|blocked)").option("--workspace <slug>", "Workspace slug").option("--project <slug>", "Project slug (mutually exclusive with --workspace/--global)").option("--global", "Use global todos").action(async (options) => {
|
|
12261
12888
|
try {
|
|
12262
|
-
const
|
|
12263
|
-
const
|
|
12889
|
+
const scope = await resolveScope(options);
|
|
12890
|
+
const todosPath = scope.todosPath;
|
|
12891
|
+
const workspace = scope.id;
|
|
12264
12892
|
const checklist = await readChecklist(todosPath, workspace);
|
|
12265
12893
|
let items = checklist.items;
|
|
12266
12894
|
if (options.tag) {
|
|
@@ -12295,10 +12923,11 @@ ${counts.total} items: ${counts.open} open, ${counts.in_progress} active, ${coun
|
|
|
12295
12923
|
function findItem(items, id) {
|
|
12296
12924
|
return items.find((i) => i.id === id);
|
|
12297
12925
|
}
|
|
12298
|
-
todoCommand.command("start").description("Mark a todo as in-progress").argument("<id>", "Todo short ID (e.g. a3f1)").option("--session <session>", "Session ID").option("--workspace <slug>", "Workspace slug").option("--global", "Use global todos").action(async (id, options) => {
|
|
12926
|
+
todoCommand.command("start").description("Mark a todo as in-progress").argument("<id>", "Todo short ID (e.g. a3f1)").option("--session <session>", "Session ID").option("--workspace <slug>", "Workspace slug").option("--project <slug>", "Project slug (mutually exclusive with --workspace/--global)").option("--global", "Use global todos").action(async (id, options) => {
|
|
12299
12927
|
try {
|
|
12300
|
-
const
|
|
12301
|
-
const
|
|
12928
|
+
const scope = await resolveScope(options);
|
|
12929
|
+
const todosPath = scope.todosPath;
|
|
12930
|
+
const workspace = scope.id;
|
|
12302
12931
|
const checklist = await readChecklist(todosPath, workspace);
|
|
12303
12932
|
const item = findItem(checklist.items, id);
|
|
12304
12933
|
if (!item) {
|
|
@@ -12318,10 +12947,11 @@ todoCommand.command("start").description("Mark a todo as in-progress").argument(
|
|
|
12318
12947
|
process.exit(1);
|
|
12319
12948
|
}
|
|
12320
12949
|
});
|
|
12321
|
-
todoCommand.command("complete").description("Mark a todo as completed").argument("<id>", "Todo short ID").option("--summary <summary>", "Completion summary").option("--branch <branch>", "Git branch name").option("--session <session>", "Session ID").option("--workspace <slug>", "Workspace slug").option("--global", "Use global todos").action(async (id, options) => {
|
|
12950
|
+
todoCommand.command("complete").description("Mark a todo as completed").argument("<id>", "Todo short ID").option("--summary <summary>", "Completion summary").option("--branch <branch>", "Git branch name").option("--session <session>", "Session ID").option("--workspace <slug>", "Workspace slug").option("--project <slug>", "Project slug (mutually exclusive with --workspace/--global)").option("--global", "Use global todos").action(async (id, options) => {
|
|
12322
12951
|
try {
|
|
12323
|
-
const
|
|
12324
|
-
const
|
|
12952
|
+
const scope = await resolveScope(options);
|
|
12953
|
+
const todosPath = scope.todosPath;
|
|
12954
|
+
const workspace = scope.id;
|
|
12325
12955
|
const checklist = await readChecklist(todosPath, workspace);
|
|
12326
12956
|
const item = findItem(checklist.items, id);
|
|
12327
12957
|
if (!item) {
|
|
@@ -12348,10 +12978,11 @@ todoCommand.command("complete").description("Mark a todo as completed").argument
|
|
|
12348
12978
|
process.exit(1);
|
|
12349
12979
|
}
|
|
12350
12980
|
});
|
|
12351
|
-
todoCommand.command("block").description("Mark a todo as blocked").argument("<id>", "Todo short ID").requiredOption("--reason <reason>", "Blocking reason").option("--session <session>", "Session ID").option("--workspace <slug>", "Workspace slug").option("--global", "Use global todos").action(async (id, options) => {
|
|
12981
|
+
todoCommand.command("block").description("Mark a todo as blocked").argument("<id>", "Todo short ID").requiredOption("--reason <reason>", "Blocking reason").option("--session <session>", "Session ID").option("--workspace <slug>", "Workspace slug").option("--project <slug>", "Project slug (mutually exclusive with --workspace/--global)").option("--global", "Use global todos").action(async (id, options) => {
|
|
12352
12982
|
try {
|
|
12353
|
-
const
|
|
12354
|
-
const
|
|
12983
|
+
const scope = await resolveScope(options);
|
|
12984
|
+
const todosPath = scope.todosPath;
|
|
12985
|
+
const workspace = scope.id;
|
|
12355
12986
|
const checklist = await readChecklist(todosPath, workspace);
|
|
12356
12987
|
const item = findItem(checklist.items, id);
|
|
12357
12988
|
if (!item) {
|
|
@@ -12378,10 +13009,11 @@ todoCommand.command("block").description("Mark a todo as blocked").argument("<id
|
|
|
12378
13009
|
process.exit(1);
|
|
12379
13010
|
}
|
|
12380
13011
|
});
|
|
12381
|
-
todoCommand.command("unblock").description("Return a blocked todo to open").argument("<id>", "Todo short ID").option("--workspace <slug>", "Workspace slug").option("--global", "Use global todos").action(async (id, options) => {
|
|
13012
|
+
todoCommand.command("unblock").description("Return a blocked todo to open").argument("<id>", "Todo short ID").option("--workspace <slug>", "Workspace slug").option("--project <slug>", "Project slug (mutually exclusive with --workspace/--global)").option("--global", "Use global todos").action(async (id, options) => {
|
|
12382
13013
|
try {
|
|
12383
|
-
const
|
|
12384
|
-
const
|
|
13014
|
+
const scope = await resolveScope(options);
|
|
13015
|
+
const todosPath = scope.todosPath;
|
|
13016
|
+
const workspace = scope.id;
|
|
12385
13017
|
const checklist = await readChecklist(todosPath, workspace);
|
|
12386
13018
|
const item = findItem(checklist.items, id);
|
|
12387
13019
|
if (!item) {
|
|
@@ -12397,10 +13029,11 @@ todoCommand.command("unblock").description("Return a blocked todo to open").argu
|
|
|
12397
13029
|
process.exit(1);
|
|
12398
13030
|
}
|
|
12399
13031
|
});
|
|
12400
|
-
todoCommand.command("delete").description("Delete a todo item (no log entry)").argument("<id>", "Todo short ID").option("--workspace <slug>", "Workspace slug").option("--global", "Use global todos").action(async (id, options) => {
|
|
13032
|
+
todoCommand.command("delete").description("Delete a todo item (no log entry)").argument("<id>", "Todo short ID").option("--workspace <slug>", "Workspace slug").option("--project <slug>", "Project slug (mutually exclusive with --workspace/--global)").option("--global", "Use global todos").action(async (id, options) => {
|
|
12401
13033
|
try {
|
|
12402
|
-
const
|
|
12403
|
-
const
|
|
13034
|
+
const scope = await resolveScope(options);
|
|
13035
|
+
const todosPath = scope.todosPath;
|
|
13036
|
+
const workspace = scope.id;
|
|
12404
13037
|
const checklist = await readChecklist(todosPath, workspace);
|
|
12405
13038
|
const idx = checklist.items.findIndex((i) => i.id === id);
|
|
12406
13039
|
if (idx === -1) {
|
|
@@ -12416,10 +13049,11 @@ todoCommand.command("delete").description("Delete a todo item (no log entry)").a
|
|
|
12416
13049
|
process.exit(1);
|
|
12417
13050
|
}
|
|
12418
13051
|
});
|
|
12419
|
-
todoCommand.command("edit").description("Update a todo description").argument("<id>", "Todo short ID").argument("<description>", "New description").option("--workspace <slug>", "Workspace slug").option("--global", "Use global todos").action(async (id, description, options) => {
|
|
13052
|
+
todoCommand.command("edit").description("Update a todo description").argument("<id>", "Todo short ID").argument("<description>", "New description").option("--workspace <slug>", "Workspace slug").option("--project <slug>", "Project slug (mutually exclusive with --workspace/--global)").option("--global", "Use global todos").action(async (id, description, options) => {
|
|
12420
13053
|
try {
|
|
12421
|
-
const
|
|
12422
|
-
const
|
|
13054
|
+
const scope = await resolveScope(options);
|
|
13055
|
+
const todosPath = scope.todosPath;
|
|
13056
|
+
const workspace = scope.id;
|
|
12423
13057
|
const checklist = await readChecklist(todosPath, workspace);
|
|
12424
13058
|
const item = findItem(checklist.items, id);
|
|
12425
13059
|
if (!item) {
|
|
@@ -12434,10 +13068,11 @@ todoCommand.command("edit").description("Update a todo description").argument("<
|
|
|
12434
13068
|
process.exit(1);
|
|
12435
13069
|
}
|
|
12436
13070
|
});
|
|
12437
|
-
todoCommand.command("tag").description("Modify tags on a todo").argument("<id>", "Todo short ID").option("--add <tags>", "Tags to add (comma-separated)").option("--remove <tags>", "Tags to remove (comma-separated)").option("--workspace <slug>", "Workspace slug").option("--global", "Use global todos").action(async (id, options) => {
|
|
13071
|
+
todoCommand.command("tag").description("Modify tags on a todo").argument("<id>", "Todo short ID").option("--add <tags>", "Tags to add (comma-separated)").option("--remove <tags>", "Tags to remove (comma-separated)").option("--workspace <slug>", "Workspace slug").option("--project <slug>", "Project slug (mutually exclusive with --workspace/--global)").option("--global", "Use global todos").action(async (id, options) => {
|
|
12438
13072
|
try {
|
|
12439
|
-
const
|
|
12440
|
-
const
|
|
13073
|
+
const scope = await resolveScope(options);
|
|
13074
|
+
const todosPath = scope.todosPath;
|
|
13075
|
+
const workspace = scope.id;
|
|
12441
13076
|
const checklist = await readChecklist(todosPath, workspace);
|
|
12442
13077
|
const item = findItem(checklist.items, id);
|
|
12443
13078
|
if (!item) {
|
|
@@ -12461,10 +13096,11 @@ todoCommand.command("tag").description("Modify tags on a todo").argument("<id>",
|
|
|
12461
13096
|
process.exit(1);
|
|
12462
13097
|
}
|
|
12463
13098
|
});
|
|
12464
|
-
todoCommand.command("log").description("Show log entries").argument("[id]", "Optional todo short ID to filter").option("--workspace <slug>", "Workspace slug").option("--global", "Use global todos").action(async (id, options) => {
|
|
13099
|
+
todoCommand.command("log").description("Show log entries").argument("[id]", "Optional todo short ID to filter").option("--workspace <slug>", "Workspace slug").option("--project <slug>", "Project slug (mutually exclusive with --workspace/--global)").option("--global", "Use global todos").action(async (id, options) => {
|
|
12465
13100
|
try {
|
|
12466
|
-
const
|
|
12467
|
-
const
|
|
13101
|
+
const scope = await resolveScope(options);
|
|
13102
|
+
const todosPath = scope.todosPath;
|
|
13103
|
+
const workspace = scope.id;
|
|
12468
13104
|
const log = await readLog(todosPath, workspace);
|
|
12469
13105
|
let entries = log.entries;
|
|
12470
13106
|
if (id) {
|
|
@@ -12488,10 +13124,11 @@ ${entry.timestamp} \u2014 ${entry.itemIds.map((i) => `t:${i}`).join(", ")}`);
|
|
|
12488
13124
|
process.exit(1);
|
|
12489
13125
|
}
|
|
12490
13126
|
});
|
|
12491
|
-
todoCommand.command("archive").description("Archive completed todos and their log entries").option("--workspace <slug>", "Workspace slug").option("--global", "Use global todos").action(async (options) => {
|
|
13127
|
+
todoCommand.command("archive").description("Archive completed todos and their log entries").option("--workspace <slug>", "Workspace slug").option("--project <slug>", "Project slug (mutually exclusive with --workspace/--global)").option("--global", "Use global todos").action(async (options) => {
|
|
12492
13128
|
try {
|
|
12493
|
-
const
|
|
12494
|
-
const
|
|
13129
|
+
const scope = await resolveScope(options);
|
|
13130
|
+
const todosPath = scope.todosPath;
|
|
13131
|
+
const workspace = scope.id;
|
|
12495
13132
|
const checklist = await readChecklist(todosPath, workspace);
|
|
12496
13133
|
const log = await readLog(todosPath, workspace);
|
|
12497
13134
|
const completedIds = new Set(
|
|
@@ -12505,10 +13142,10 @@ todoCommand.command("archive").description("Archive completed todos and their lo
|
|
|
12505
13142
|
(e) => e.itemIds.every((id) => completedIds.has(id))
|
|
12506
13143
|
);
|
|
12507
13144
|
const archFile = archivePath(todosPath, workspace, checklist.archiveInterval);
|
|
12508
|
-
await ensureDir(
|
|
13145
|
+
await ensureDir(resolve35(todosPath, "archive"));
|
|
12509
13146
|
let archContent = "";
|
|
12510
13147
|
if (await fileExists(archFile)) {
|
|
12511
|
-
archContent = await
|
|
13148
|
+
archContent = await readFile21(archFile, "utf-8");
|
|
12512
13149
|
archContent = archContent.trimEnd() + "\n\n";
|
|
12513
13150
|
} else {
|
|
12514
13151
|
archContent = `---
|
|
@@ -12583,10 +13220,11 @@ workspace: ${workspace}
|
|
|
12583
13220
|
process.exit(1);
|
|
12584
13221
|
}
|
|
12585
13222
|
});
|
|
12586
|
-
todoCommand.command("promote").description("Promote a todo to a full assignment").argument("<id>", "Todo short ID").requiredOption("--project <slug>", "Target project slug").option("--workspace <slug>", "
|
|
13223
|
+
todoCommand.command("promote").description("Promote a todo to a full assignment").argument("<id>", "Todo short ID").requiredOption("--to-project <slug>", "Target project slug for the new assignment").option("--workspace <slug>", "Source workspace slug").option("--project <slug>", "Source project slug (mutually exclusive with --workspace/--global)").option("--global", "Use global todos").action(async (id, options) => {
|
|
12587
13224
|
try {
|
|
12588
|
-
const
|
|
12589
|
-
const
|
|
13225
|
+
const scope = await resolveScope(options);
|
|
13226
|
+
const todosPath = scope.todosPath;
|
|
13227
|
+
const workspace = scope.id;
|
|
12590
13228
|
const checklist = await readChecklist(todosPath, workspace);
|
|
12591
13229
|
const item = findItem(checklist.items, id);
|
|
12592
13230
|
if (!item) {
|
|
@@ -12602,13 +13240,13 @@ todoCommand.command("promote").description("Promote a todo to a full assignment"
|
|
|
12602
13240
|
items: item.description,
|
|
12603
13241
|
session: null,
|
|
12604
13242
|
branch: null,
|
|
12605
|
-
summary: `Promoted to assignment in project: ${options.
|
|
13243
|
+
summary: `Promoted to assignment in project: ${options.toProject}`,
|
|
12606
13244
|
blockers: null,
|
|
12607
13245
|
status: null
|
|
12608
13246
|
};
|
|
12609
13247
|
await appendLogEntry2(todosPath, workspace, entry);
|
|
12610
|
-
console.log(`Promoted [t:${id}] to assignment in project "${options.
|
|
12611
|
-
console.log(`Run: syntaur create-assignment --project ${options.
|
|
13248
|
+
console.log(`Promoted [t:${id}] to assignment in project "${options.toProject}".`);
|
|
13249
|
+
console.log(`Run: syntaur create-assignment --project ${options.toProject} "${item.description}"`);
|
|
12612
13250
|
} catch (error) {
|
|
12613
13251
|
console.error("Error:", error instanceof Error ? error.message : String(error));
|
|
12614
13252
|
process.exit(1);
|
|
@@ -12697,7 +13335,7 @@ import { Command as Command3 } from "commander";
|
|
|
12697
13335
|
|
|
12698
13336
|
// src/utils/doctor/index.ts
|
|
12699
13337
|
import { fileURLToPath as fileURLToPath7 } from "url";
|
|
12700
|
-
import { readFile as
|
|
13338
|
+
import { readFile as readFile25 } from "fs/promises";
|
|
12701
13339
|
import { dirname as dirname11, join as join5 } from "path";
|
|
12702
13340
|
|
|
12703
13341
|
// src/utils/doctor/context.ts
|
|
@@ -12705,11 +13343,11 @@ init_config2();
|
|
|
12705
13343
|
init_paths();
|
|
12706
13344
|
init_fs();
|
|
12707
13345
|
import Database2 from "better-sqlite3";
|
|
12708
|
-
import { resolve as
|
|
13346
|
+
import { resolve as resolve36 } from "path";
|
|
12709
13347
|
async function buildCheckContext(cwd = process.cwd()) {
|
|
12710
13348
|
const config = await readConfig();
|
|
12711
13349
|
const root = syntaurRoot();
|
|
12712
|
-
const dbPath =
|
|
13350
|
+
const dbPath = resolve36(root, "syntaur.db");
|
|
12713
13351
|
let db2 = null;
|
|
12714
13352
|
let dbError = null;
|
|
12715
13353
|
if (await fileExists(dbPath)) {
|
|
@@ -12743,8 +13381,8 @@ function closeCheckContext(ctx) {
|
|
|
12743
13381
|
// src/utils/doctor/checks/env.ts
|
|
12744
13382
|
init_fs();
|
|
12745
13383
|
init_paths();
|
|
12746
|
-
import { resolve as
|
|
12747
|
-
import { readFile as
|
|
13384
|
+
import { resolve as resolve37, isAbsolute as isAbsolute3 } from "path";
|
|
13385
|
+
import { readFile as readFile22, stat as stat4 } from "fs/promises";
|
|
12748
13386
|
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
12749
13387
|
import { dirname as dirname10, join as join4 } from "path";
|
|
12750
13388
|
var CATEGORY = "env";
|
|
@@ -12784,7 +13422,7 @@ var configValid = {
|
|
|
12784
13422
|
category: CATEGORY,
|
|
12785
13423
|
title: "~/.syntaur/config.md is valid",
|
|
12786
13424
|
async run(ctx) {
|
|
12787
|
-
const configPath =
|
|
13425
|
+
const configPath = resolve37(ctx.syntaurRoot, "config.md");
|
|
12788
13426
|
if (!await fileExists(configPath)) {
|
|
12789
13427
|
return {
|
|
12790
13428
|
id: this.id,
|
|
@@ -12801,7 +13439,7 @@ var configValid = {
|
|
|
12801
13439
|
autoFixable: false
|
|
12802
13440
|
};
|
|
12803
13441
|
}
|
|
12804
|
-
const content = await
|
|
13442
|
+
const content = await readFile22(configPath, "utf-8");
|
|
12805
13443
|
const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
12806
13444
|
if (!fmMatch || fmMatch[1].trim() === "") {
|
|
12807
13445
|
return {
|
|
@@ -13081,7 +13719,7 @@ async function readLocalPkg() {
|
|
|
13081
13719
|
for (let i = 0; i < 6; i++) {
|
|
13082
13720
|
const candidate = join4(dir, "package.json");
|
|
13083
13721
|
try {
|
|
13084
|
-
const text = await
|
|
13722
|
+
const text = await readFile22(candidate, "utf-8");
|
|
13085
13723
|
return JSON.parse(text);
|
|
13086
13724
|
} catch {
|
|
13087
13725
|
dir = dirname10(dir);
|
|
@@ -13133,7 +13771,7 @@ function versionGte(a, b) {
|
|
|
13133
13771
|
|
|
13134
13772
|
// src/utils/doctor/checks/structure.ts
|
|
13135
13773
|
init_fs();
|
|
13136
|
-
import { resolve as
|
|
13774
|
+
import { resolve as resolve38 } from "path";
|
|
13137
13775
|
import { readdir as readdir13, stat as stat5 } from "fs/promises";
|
|
13138
13776
|
var CATEGORY2 = "structure";
|
|
13139
13777
|
var KNOWN_TOP_LEVEL = /* @__PURE__ */ new Set([
|
|
@@ -13153,7 +13791,7 @@ var projectsDir = {
|
|
|
13153
13791
|
category: CATEGORY2,
|
|
13154
13792
|
title: "projects/ directory exists",
|
|
13155
13793
|
async run(ctx) {
|
|
13156
|
-
const p =
|
|
13794
|
+
const p = resolve38(ctx.syntaurRoot, "projects");
|
|
13157
13795
|
if (!await fileExists(p)) {
|
|
13158
13796
|
return {
|
|
13159
13797
|
id: this.id,
|
|
@@ -13178,7 +13816,7 @@ var playbooksDir2 = {
|
|
|
13178
13816
|
category: CATEGORY2,
|
|
13179
13817
|
title: "playbooks/ directory exists",
|
|
13180
13818
|
async run(ctx) {
|
|
13181
|
-
const p =
|
|
13819
|
+
const p = resolve38(ctx.syntaurRoot, "playbooks");
|
|
13182
13820
|
if (!await fileExists(p)) {
|
|
13183
13821
|
return {
|
|
13184
13822
|
id: this.id,
|
|
@@ -13203,7 +13841,7 @@ var todosDirValid = {
|
|
|
13203
13841
|
category: CATEGORY2,
|
|
13204
13842
|
title: "todos/ directory is readable (if present)",
|
|
13205
13843
|
async run(ctx) {
|
|
13206
|
-
const p =
|
|
13844
|
+
const p = resolve38(ctx.syntaurRoot, "todos");
|
|
13207
13845
|
if (!await fileExists(p)) {
|
|
13208
13846
|
return {
|
|
13209
13847
|
id: this.id,
|
|
@@ -13234,7 +13872,7 @@ var serversDirValid = {
|
|
|
13234
13872
|
category: CATEGORY2,
|
|
13235
13873
|
title: "servers/ directory is readable (if present)",
|
|
13236
13874
|
async run(ctx) {
|
|
13237
|
-
const p =
|
|
13875
|
+
const p = resolve38(ctx.syntaurRoot, "servers");
|
|
13238
13876
|
if (!await fileExists(p)) {
|
|
13239
13877
|
return {
|
|
13240
13878
|
id: this.id,
|
|
@@ -13279,7 +13917,7 @@ var knownFilesRecognized = {
|
|
|
13279
13917
|
title: this.title,
|
|
13280
13918
|
status: "warn",
|
|
13281
13919
|
detail: `unexpected top-level entries: ${unexpected.join(", ")}`,
|
|
13282
|
-
affected: unexpected.map((n) =>
|
|
13920
|
+
affected: unexpected.map((n) => resolve38(ctx.syntaurRoot, n)),
|
|
13283
13921
|
remediation: {
|
|
13284
13922
|
kind: "manual",
|
|
13285
13923
|
suggestion: "Review these entries \u2014 they may be leftover state from older versions",
|
|
@@ -13308,7 +13946,7 @@ function pass2(check) {
|
|
|
13308
13946
|
|
|
13309
13947
|
// src/utils/doctor/checks/project.ts
|
|
13310
13948
|
init_fs();
|
|
13311
|
-
import { resolve as
|
|
13949
|
+
import { resolve as resolve39 } from "path";
|
|
13312
13950
|
import { readdir as readdir14, stat as stat6 } from "fs/promises";
|
|
13313
13951
|
var CATEGORY3 = "project";
|
|
13314
13952
|
var REQUIRED_PROJECT_FILES = [
|
|
@@ -13338,10 +13976,10 @@ async function listProjects2(ctx) {
|
|
|
13338
13976
|
for (const e of entries) {
|
|
13339
13977
|
if (!e.isDirectory()) continue;
|
|
13340
13978
|
if (e.name.startsWith(".") || e.name.startsWith("_")) continue;
|
|
13341
|
-
const projectDir =
|
|
13979
|
+
const projectDir = resolve39(dir, e.name);
|
|
13342
13980
|
let looksLikeProject = false;
|
|
13343
13981
|
for (const marker of PROJECT_MARKERS) {
|
|
13344
|
-
if (await fileExists(
|
|
13982
|
+
if (await fileExists(resolve39(projectDir, marker))) {
|
|
13345
13983
|
looksLikeProject = true;
|
|
13346
13984
|
break;
|
|
13347
13985
|
}
|
|
@@ -13360,7 +13998,7 @@ var requiredFiles = {
|
|
|
13360
13998
|
for (const projectDir of projects) {
|
|
13361
13999
|
const missing = [];
|
|
13362
14000
|
for (const rel of REQUIRED_PROJECT_FILES) {
|
|
13363
|
-
const p =
|
|
14001
|
+
const p = resolve39(projectDir, rel);
|
|
13364
14002
|
if (!await fileExists(p)) missing.push(rel);
|
|
13365
14003
|
}
|
|
13366
14004
|
if (missing.length === 0) continue;
|
|
@@ -13370,7 +14008,7 @@ var requiredFiles = {
|
|
|
13370
14008
|
title: this.title,
|
|
13371
14009
|
status: "error",
|
|
13372
14010
|
detail: `project at ${projectDir} is missing: ${missing.join(", ")}`,
|
|
13373
|
-
affected: missing.map((m) =>
|
|
14011
|
+
affected: missing.map((m) => resolve39(projectDir, m)),
|
|
13374
14012
|
remediation: {
|
|
13375
14013
|
kind: "manual",
|
|
13376
14014
|
suggestion: "Recreate the missing scaffold files from templates",
|
|
@@ -13393,7 +14031,7 @@ var manifestStale = {
|
|
|
13393
14031
|
const projects = await listProjects2(ctx);
|
|
13394
14032
|
const results = [];
|
|
13395
14033
|
for (const projectDir of projects) {
|
|
13396
|
-
const manifestPath =
|
|
14034
|
+
const manifestPath = resolve39(projectDir, "manifest.md");
|
|
13397
14035
|
if (!await fileExists(manifestPath)) continue;
|
|
13398
14036
|
const manifestMtime = (await stat6(manifestPath)).mtimeMs;
|
|
13399
14037
|
const newestAssignment = await newestAssignmentMtime(projectDir);
|
|
@@ -13442,7 +14080,7 @@ var orphanFiles = {
|
|
|
13442
14080
|
title: this.title,
|
|
13443
14081
|
status: "warn",
|
|
13444
14082
|
detail: `project at ${projectDir} has unexpected entries: ${orphans.join(", ")}`,
|
|
13445
|
-
affected: orphans.map((o) =>
|
|
14083
|
+
affected: orphans.map((o) => resolve39(projectDir, o)),
|
|
13446
14084
|
autoFixable: false
|
|
13447
14085
|
});
|
|
13448
14086
|
}
|
|
@@ -13452,7 +14090,7 @@ var orphanFiles = {
|
|
|
13452
14090
|
};
|
|
13453
14091
|
var projectChecks = [requiredFiles, manifestStale, orphanFiles];
|
|
13454
14092
|
async function newestAssignmentMtime(projectDir) {
|
|
13455
|
-
const assignmentsRoot =
|
|
14093
|
+
const assignmentsRoot = resolve39(projectDir, "assignments");
|
|
13456
14094
|
if (!await fileExists(assignmentsRoot)) return 0;
|
|
13457
14095
|
let newest = 0;
|
|
13458
14096
|
let entries;
|
|
@@ -13463,7 +14101,7 @@ async function newestAssignmentMtime(projectDir) {
|
|
|
13463
14101
|
}
|
|
13464
14102
|
for (const e of entries) {
|
|
13465
14103
|
if (!e.isDirectory()) continue;
|
|
13466
|
-
const assignmentMd =
|
|
14104
|
+
const assignmentMd = resolve39(assignmentsRoot, e.name, "assignment.md");
|
|
13467
14105
|
try {
|
|
13468
14106
|
const s = await stat6(assignmentMd);
|
|
13469
14107
|
if (s.mtimeMs > newest) newest = s.mtimeMs;
|
|
@@ -13487,8 +14125,8 @@ init_fs();
|
|
|
13487
14125
|
init_parser();
|
|
13488
14126
|
init_types();
|
|
13489
14127
|
init_paths();
|
|
13490
|
-
import { resolve as
|
|
13491
|
-
import { readdir as readdir15, readFile as
|
|
14128
|
+
import { resolve as resolve40 } from "path";
|
|
14129
|
+
import { readdir as readdir15, readFile as readFile23 } from "fs/promises";
|
|
13492
14130
|
var CATEGORY4 = "assignment";
|
|
13493
14131
|
var STATUSES_REQUIRING_HANDOFF = /* @__PURE__ */ new Set(["review", "completed"]);
|
|
13494
14132
|
async function listAssignments(ctx) {
|
|
@@ -13499,16 +14137,16 @@ async function listAssignments(ctx) {
|
|
|
13499
14137
|
for (const m of projects) {
|
|
13500
14138
|
if (!m.isDirectory()) continue;
|
|
13501
14139
|
if (m.name.startsWith(".") || m.name.startsWith("_")) continue;
|
|
13502
|
-
const assignmentsDir2 =
|
|
14140
|
+
const assignmentsDir2 = resolve40(projectsDir2, m.name, "assignments");
|
|
13503
14141
|
if (!await fileExists(assignmentsDir2)) continue;
|
|
13504
14142
|
const entries = await readdir15(assignmentsDir2, { withFileTypes: true });
|
|
13505
14143
|
for (const a of entries) {
|
|
13506
14144
|
if (!a.isDirectory()) continue;
|
|
13507
14145
|
if (a.name.startsWith(".") || a.name.startsWith("_")) continue;
|
|
13508
|
-
const assignmentDir =
|
|
13509
|
-
const assignmentMd =
|
|
14146
|
+
const assignmentDir = resolve40(assignmentsDir2, a.name);
|
|
14147
|
+
const assignmentMd = resolve40(assignmentDir, "assignment.md");
|
|
13510
14148
|
const entry = {
|
|
13511
|
-
projectDir:
|
|
14149
|
+
projectDir: resolve40(projectsDir2, m.name),
|
|
13512
14150
|
projectSlug: m.name,
|
|
13513
14151
|
assignmentDir,
|
|
13514
14152
|
assignmentSlug: a.name,
|
|
@@ -13528,8 +14166,8 @@ async function listAssignments(ctx) {
|
|
|
13528
14166
|
for (const a of entries) {
|
|
13529
14167
|
if (!a.isDirectory()) continue;
|
|
13530
14168
|
if (a.name.startsWith(".") || a.name.startsWith("_")) continue;
|
|
13531
|
-
const assignmentDir =
|
|
13532
|
-
const assignmentMd =
|
|
14169
|
+
const assignmentDir = resolve40(standaloneRoot, a.name);
|
|
14170
|
+
const assignmentMd = resolve40(assignmentDir, "assignment.md");
|
|
13533
14171
|
const entry = {
|
|
13534
14172
|
projectDir: standaloneRoot,
|
|
13535
14173
|
projectSlug: null,
|
|
@@ -13607,7 +14245,7 @@ var invalidStatus = {
|
|
|
13607
14245
|
const allowed = configuredStatuses(ctx);
|
|
13608
14246
|
const results = [];
|
|
13609
14247
|
for (const a of withAssignmentMd) {
|
|
13610
|
-
const path =
|
|
14248
|
+
const path = resolve40(a.assignmentDir, "assignment.md");
|
|
13611
14249
|
const parsed = await parseSafe(path);
|
|
13612
14250
|
if (!parsed) continue;
|
|
13613
14251
|
if (!allowed.has(parsed.status)) {
|
|
@@ -13640,7 +14278,7 @@ var workspaceMissing = {
|
|
|
13640
14278
|
const terminal = terminalStatuses(ctx);
|
|
13641
14279
|
const results = [];
|
|
13642
14280
|
for (const a of withAssignmentMd) {
|
|
13643
|
-
const path =
|
|
14281
|
+
const path = resolve40(a.assignmentDir, "assignment.md");
|
|
13644
14282
|
const parsed = await parseSafe(path);
|
|
13645
14283
|
if (!parsed) continue;
|
|
13646
14284
|
if (terminal.has(parsed.status)) continue;
|
|
@@ -13687,12 +14325,12 @@ var requiredFilesByStatus = {
|
|
|
13687
14325
|
const { withAssignmentMd } = await listAssignments(ctx);
|
|
13688
14326
|
const results = [];
|
|
13689
14327
|
for (const a of withAssignmentMd) {
|
|
13690
|
-
const assignmentPath =
|
|
14328
|
+
const assignmentPath = resolve40(a.assignmentDir, "assignment.md");
|
|
13691
14329
|
const parsed = await parseSafe(assignmentPath);
|
|
13692
14330
|
if (!parsed) continue;
|
|
13693
14331
|
const missing = [];
|
|
13694
14332
|
if (STATUSES_REQUIRING_HANDOFF.has(parsed.status)) {
|
|
13695
|
-
const handoffPath =
|
|
14333
|
+
const handoffPath = resolve40(a.assignmentDir, "handoff.md");
|
|
13696
14334
|
if (!await fileExists(handoffPath)) missing.push("handoff.md");
|
|
13697
14335
|
}
|
|
13698
14336
|
if (missing.length === 0) continue;
|
|
@@ -13702,7 +14340,7 @@ var requiredFilesByStatus = {
|
|
|
13702
14340
|
title: this.title,
|
|
13703
14341
|
status: "warn",
|
|
13704
14342
|
detail: `${a.projectSlug}/${a.assignmentSlug} (status: ${parsed.status}) is missing ${missing.join(", ")}`,
|
|
13705
|
-
affected: missing.map((m) =>
|
|
14343
|
+
affected: missing.map((m) => resolve40(a.assignmentDir, m)),
|
|
13706
14344
|
remediation: {
|
|
13707
14345
|
kind: "manual",
|
|
13708
14346
|
suggestion: `Create the missing ${missing.join(" and ")} files for this assignment`,
|
|
@@ -13725,7 +14363,7 @@ var companionFilesScaffolded = {
|
|
|
13725
14363
|
for (const a of withAssignmentMd) {
|
|
13726
14364
|
const missing = [];
|
|
13727
14365
|
for (const filename of ["progress.md", "comments.md"]) {
|
|
13728
|
-
if (!await fileExists(
|
|
14366
|
+
if (!await fileExists(resolve40(a.assignmentDir, filename))) {
|
|
13729
14367
|
missing.push(filename);
|
|
13730
14368
|
}
|
|
13731
14369
|
}
|
|
@@ -13737,7 +14375,7 @@ var companionFilesScaffolded = {
|
|
|
13737
14375
|
title: this.title,
|
|
13738
14376
|
status: "warn",
|
|
13739
14377
|
detail: `${label} is missing ${missing.join(" and ")} (pre-v2.0 assignment \u2014 not required, but scaffolding them keeps the dashboard and CLIs consistent)`,
|
|
13740
|
-
affected: missing.map((m) =>
|
|
14378
|
+
affected: missing.map((m) => resolve40(a.assignmentDir, m)),
|
|
13741
14379
|
remediation: {
|
|
13742
14380
|
kind: "manual",
|
|
13743
14381
|
suggestion: `Create ${missing.join(" and ")} with the renderProgress/renderComments templates, or re-scaffold via the CLI`,
|
|
@@ -13770,7 +14408,7 @@ var typeDefinition = {
|
|
|
13770
14408
|
const { withAssignmentMd } = await listAssignments(ctx);
|
|
13771
14409
|
const results = [];
|
|
13772
14410
|
for (const a of withAssignmentMd) {
|
|
13773
|
-
const path =
|
|
14411
|
+
const path = resolve40(a.assignmentDir, "assignment.md");
|
|
13774
14412
|
const parsed = await parseSafe(path);
|
|
13775
14413
|
if (!parsed) continue;
|
|
13776
14414
|
if (!parsed.type) continue;
|
|
@@ -13804,7 +14442,7 @@ var projectFrontmatterMatchesContainer = {
|
|
|
13804
14442
|
const { withAssignmentMd } = await listAssignments(ctx);
|
|
13805
14443
|
const results = [];
|
|
13806
14444
|
for (const a of withAssignmentMd) {
|
|
13807
|
-
const path =
|
|
14445
|
+
const path = resolve40(a.assignmentDir, "assignment.md");
|
|
13808
14446
|
const parsed = await parseSafe(path);
|
|
13809
14447
|
if (!parsed) continue;
|
|
13810
14448
|
if (a.standalone) {
|
|
@@ -13859,7 +14497,7 @@ var assignmentChecks = [
|
|
|
13859
14497
|
];
|
|
13860
14498
|
async function parseSafe(path) {
|
|
13861
14499
|
try {
|
|
13862
|
-
const content = await
|
|
14500
|
+
const content = await readFile23(path, "utf-8");
|
|
13863
14501
|
return parseAssignmentFull(content);
|
|
13864
14502
|
} catch {
|
|
13865
14503
|
return null;
|
|
@@ -13878,7 +14516,7 @@ function pass4(check, detail) {
|
|
|
13878
14516
|
|
|
13879
14517
|
// src/utils/doctor/checks/dashboard.ts
|
|
13880
14518
|
init_fs();
|
|
13881
|
-
import { resolve as
|
|
14519
|
+
import { resolve as resolve41 } from "path";
|
|
13882
14520
|
var CATEGORY5 = "dashboard";
|
|
13883
14521
|
var dbReachable = {
|
|
13884
14522
|
id: "dashboard.db-reachable",
|
|
@@ -13892,7 +14530,7 @@ var dbReachable = {
|
|
|
13892
14530
|
title: this.title,
|
|
13893
14531
|
status: "error",
|
|
13894
14532
|
detail: `could not open syntaur.db: ${ctx.dbError ?? "unknown error"}`,
|
|
13895
|
-
affected: [
|
|
14533
|
+
affected: [resolve41(ctx.syntaurRoot, "syntaur.db")],
|
|
13896
14534
|
remediation: {
|
|
13897
14535
|
kind: "manual",
|
|
13898
14536
|
suggestion: "Start the dashboard once (`syntaur dashboard`) to initialize the DB, or restore it from backup",
|
|
@@ -13910,7 +14548,7 @@ var dbReachable = {
|
|
|
13910
14548
|
title: this.title,
|
|
13911
14549
|
status: "error",
|
|
13912
14550
|
detail: 'syntaur.db is missing the expected "sessions" table',
|
|
13913
|
-
affected: [
|
|
14551
|
+
affected: [resolve41(ctx.syntaurRoot, "syntaur.db")],
|
|
13914
14552
|
autoFixable: false
|
|
13915
14553
|
};
|
|
13916
14554
|
}
|
|
@@ -13922,7 +14560,7 @@ var dbReachable = {
|
|
|
13922
14560
|
title: this.title,
|
|
13923
14561
|
status: "error",
|
|
13924
14562
|
detail: `syntaur.db query failed: ${err2 instanceof Error ? err2.message : String(err2)}`,
|
|
13925
|
-
affected: [
|
|
14563
|
+
affected: [resolve41(ctx.syntaurRoot, "syntaur.db")],
|
|
13926
14564
|
autoFixable: false
|
|
13927
14565
|
};
|
|
13928
14566
|
}
|
|
@@ -13948,7 +14586,7 @@ var ghostSessions = {
|
|
|
13948
14586
|
const results = [];
|
|
13949
14587
|
for (const row of rows) {
|
|
13950
14588
|
if (!row.project_slug) continue;
|
|
13951
|
-
const projectPath =
|
|
14589
|
+
const projectPath = resolve41(projectsDir2, row.project_slug, "project.md");
|
|
13952
14590
|
if (!await fileExists(projectPath)) {
|
|
13953
14591
|
results.push({
|
|
13954
14592
|
id: this.id,
|
|
@@ -13967,7 +14605,7 @@ var ghostSessions = {
|
|
|
13967
14605
|
continue;
|
|
13968
14606
|
}
|
|
13969
14607
|
if (row.assignment_slug) {
|
|
13970
|
-
const assignmentPath =
|
|
14608
|
+
const assignmentPath = resolve41(
|
|
13971
14609
|
projectsDir2,
|
|
13972
14610
|
row.project_slug,
|
|
13973
14611
|
"assignments",
|
|
@@ -14124,8 +14762,8 @@ function skipped2(check, reason) {
|
|
|
14124
14762
|
init_fs();
|
|
14125
14763
|
init_parser();
|
|
14126
14764
|
init_types();
|
|
14127
|
-
import { resolve as
|
|
14128
|
-
import { readFile as
|
|
14765
|
+
import { resolve as resolve42 } from "path";
|
|
14766
|
+
import { readFile as readFile24 } from "fs/promises";
|
|
14129
14767
|
var CATEGORY7 = "workspace";
|
|
14130
14768
|
var ASSIGNMENT_FIELDS = ["projectSlug", "assignmentSlug", "projectDir", "assignmentDir"];
|
|
14131
14769
|
function hasAnyAssignmentField(ctx) {
|
|
@@ -14137,12 +14775,12 @@ function isStandaloneSession(ctx) {
|
|
|
14137
14775
|
return !hasAnyAssignmentField(ctx) && typeof ctx.sessionId === "string" && ctx.sessionId.length > 0;
|
|
14138
14776
|
}
|
|
14139
14777
|
async function loadContext(ctx) {
|
|
14140
|
-
const path =
|
|
14778
|
+
const path = resolve42(ctx.cwd, ".syntaur", "context.json");
|
|
14141
14779
|
if (!await fileExists(path)) {
|
|
14142
14780
|
return { data: null, path, exists: false, parseError: null };
|
|
14143
14781
|
}
|
|
14144
14782
|
try {
|
|
14145
|
-
const raw = await
|
|
14783
|
+
const raw = await readFile24(path, "utf-8");
|
|
14146
14784
|
return { data: JSON.parse(raw), path, exists: true, parseError: null };
|
|
14147
14785
|
} catch (err2) {
|
|
14148
14786
|
return {
|
|
@@ -14217,7 +14855,7 @@ var contextAssignmentResolves = {
|
|
|
14217
14855
|
if (!exists) return skipped3(this, "no context to resolve");
|
|
14218
14856
|
if (isStandaloneSession(data)) return skipped3(this, "standalone session context \u2014 no assignment to resolve");
|
|
14219
14857
|
if (!data?.assignmentDir) return skipped3(this, "context has no assignmentDir");
|
|
14220
|
-
const assignmentMd =
|
|
14858
|
+
const assignmentMd = resolve42(data.assignmentDir, "assignment.md");
|
|
14221
14859
|
if (!await fileExists(assignmentMd)) {
|
|
14222
14860
|
return {
|
|
14223
14861
|
id: this.id,
|
|
@@ -14246,10 +14884,10 @@ var contextTerminal = {
|
|
|
14246
14884
|
if (!exists) return skipped3(this, "no context to check");
|
|
14247
14885
|
if (isStandaloneSession(data)) return skipped3(this, "standalone session context \u2014 no assignment to check");
|
|
14248
14886
|
if (!data?.assignmentDir) return skipped3(this, "context has no assignmentDir");
|
|
14249
|
-
const assignmentMd =
|
|
14887
|
+
const assignmentMd = resolve42(data.assignmentDir, "assignment.md");
|
|
14250
14888
|
if (!await fileExists(assignmentMd)) return skipped3(this, "assignment file missing");
|
|
14251
14889
|
try {
|
|
14252
|
-
const content = await
|
|
14890
|
+
const content = await readFile24(assignmentMd, "utf-8");
|
|
14253
14891
|
const parsed = parseAssignmentFull(content);
|
|
14254
14892
|
const terminal = terminalStatuses2(ctx);
|
|
14255
14893
|
if (terminal.has(parsed.status)) {
|
|
@@ -14396,7 +15034,7 @@ async function readVersion() {
|
|
|
14396
15034
|
let dir = dirname11(here);
|
|
14397
15035
|
for (let i = 0; i < 6; i++) {
|
|
14398
15036
|
try {
|
|
14399
|
-
const raw = await
|
|
15037
|
+
const raw = await readFile25(join5(dir, "package.json"), "utf-8");
|
|
14400
15038
|
const parsed = JSON.parse(raw);
|
|
14401
15039
|
return typeof parsed.version === "string" ? parsed.version : null;
|
|
14402
15040
|
} catch {
|
|
@@ -14533,8 +15171,8 @@ var doctorCommand = new Command3("doctor").description("Diagnose Syntaur state a
|
|
|
14533
15171
|
init_paths();
|
|
14534
15172
|
init_fs();
|
|
14535
15173
|
init_config2();
|
|
14536
|
-
import { resolve as
|
|
14537
|
-
import { readFile as
|
|
15174
|
+
import { resolve as resolve43 } from "path";
|
|
15175
|
+
import { readFile as readFile26 } from "fs/promises";
|
|
14538
15176
|
init_timestamp();
|
|
14539
15177
|
init_assignment_resolver();
|
|
14540
15178
|
function shortId() {
|
|
@@ -14566,7 +15204,7 @@ async function commentCommand(target, text, options = {}) {
|
|
|
14566
15204
|
if (!isValidSlug(target)) {
|
|
14567
15205
|
throw new Error(`Invalid assignment slug "${target}".`);
|
|
14568
15206
|
}
|
|
14569
|
-
assignmentDir =
|
|
15207
|
+
assignmentDir = resolve43(baseDir, options.project, "assignments", target);
|
|
14570
15208
|
assignmentRef = target;
|
|
14571
15209
|
} else {
|
|
14572
15210
|
const resolved = await resolveAssignmentById(baseDir, assignmentsDir(), target);
|
|
@@ -14576,13 +15214,13 @@ async function commentCommand(target, text, options = {}) {
|
|
|
14576
15214
|
assignmentDir = resolved.assignmentDir;
|
|
14577
15215
|
assignmentRef = resolved.standalone ? resolved.id : resolved.assignmentSlug;
|
|
14578
15216
|
}
|
|
14579
|
-
const commentsPath =
|
|
15217
|
+
const commentsPath = resolve43(assignmentDir, "comments.md");
|
|
14580
15218
|
const timestamp = nowTimestamp();
|
|
14581
15219
|
const author = options.author ?? process.env.USER ?? "unknown";
|
|
14582
15220
|
let currentContent;
|
|
14583
15221
|
let currentCount = 0;
|
|
14584
15222
|
if (await fileExists(commentsPath)) {
|
|
14585
|
-
currentContent = await
|
|
15223
|
+
currentContent = await readFile26(commentsPath, "utf-8");
|
|
14586
15224
|
const countMatch = currentContent.match(/^entryCount:\s*(\d+)/m);
|
|
14587
15225
|
if (countMatch) currentCount = parseInt(countMatch[1], 10);
|
|
14588
15226
|
} else {
|
|
@@ -14619,8 +15257,8 @@ ${entry}`;
|
|
|
14619
15257
|
init_paths();
|
|
14620
15258
|
init_fs();
|
|
14621
15259
|
init_config2();
|
|
14622
|
-
import { resolve as
|
|
14623
|
-
import { readFile as
|
|
15260
|
+
import { resolve as resolve44 } from "path";
|
|
15261
|
+
import { readFile as readFile27 } from "fs/promises";
|
|
14624
15262
|
init_timestamp();
|
|
14625
15263
|
init_assignment_resolver();
|
|
14626
15264
|
function setTopLevelField3(content, key, value) {
|
|
@@ -14645,7 +15283,7 @@ async function requestCommand(target, text, options = {}) {
|
|
|
14645
15283
|
if (!isValidSlug(target)) {
|
|
14646
15284
|
throw new Error(`Invalid assignment slug "${target}".`);
|
|
14647
15285
|
}
|
|
14648
|
-
assignmentDir =
|
|
15286
|
+
assignmentDir = resolve44(baseDir, options.project, "assignments", target);
|
|
14649
15287
|
targetRef = target;
|
|
14650
15288
|
} else {
|
|
14651
15289
|
const resolved = await resolveAssignmentById(baseDir, assignmentsDir(), target);
|
|
@@ -14655,12 +15293,12 @@ async function requestCommand(target, text, options = {}) {
|
|
|
14655
15293
|
assignmentDir = resolved.assignmentDir;
|
|
14656
15294
|
targetRef = resolved.standalone ? resolved.id : resolved.assignmentSlug;
|
|
14657
15295
|
}
|
|
14658
|
-
const assignmentMdPath =
|
|
15296
|
+
const assignmentMdPath = resolve44(assignmentDir, "assignment.md");
|
|
14659
15297
|
if (!await fileExists(assignmentMdPath)) {
|
|
14660
15298
|
throw new Error(`assignment.md not found at ${assignmentMdPath}`);
|
|
14661
15299
|
}
|
|
14662
15300
|
const source = options.from ?? process.env.SYNTAUR_ASSIGNMENT ?? "unknown";
|
|
14663
|
-
let content = await
|
|
15301
|
+
let content = await readFile27(assignmentMdPath, "utf-8");
|
|
14664
15302
|
const todoLine = `- [ ] ${text.trim()} (from: ${source})`;
|
|
14665
15303
|
const todosHeading = /^## Todos\s*$/m;
|
|
14666
15304
|
if (todosHeading.test(content)) {
|
|
@@ -14728,20 +15366,20 @@ async function getDefaultCommandName() {
|
|
|
14728
15366
|
init_paths();
|
|
14729
15367
|
init_fs();
|
|
14730
15368
|
import { fileURLToPath as fileURLToPath9 } from "url";
|
|
14731
|
-
import { readFile as
|
|
14732
|
-
import { dirname as dirname13, join as join7, resolve as
|
|
15369
|
+
import { readFile as readFile29 } from "fs/promises";
|
|
15370
|
+
import { dirname as dirname13, join as join7, resolve as resolve45 } from "path";
|
|
14733
15371
|
import { spawn as spawn3 } from "child_process";
|
|
14734
15372
|
import { createInterface as createInterface2 } from "readline/promises";
|
|
14735
15373
|
|
|
14736
15374
|
// src/utils/version.ts
|
|
14737
15375
|
import { fileURLToPath as fileURLToPath8 } from "url";
|
|
14738
|
-
import { readFile as
|
|
15376
|
+
import { readFile as readFile28 } from "fs/promises";
|
|
14739
15377
|
import { dirname as dirname12, join as join6 } from "path";
|
|
14740
15378
|
async function readPackageVersion(scriptUrl) {
|
|
14741
15379
|
try {
|
|
14742
15380
|
const scriptPath = fileURLToPath8(scriptUrl);
|
|
14743
15381
|
const pkgRoot = dirname12(dirname12(scriptPath));
|
|
14744
|
-
const raw = await
|
|
15382
|
+
const raw = await readFile28(join6(pkgRoot, "package.json"), "utf-8");
|
|
14745
15383
|
const parsed = JSON.parse(raw);
|
|
14746
15384
|
return typeof parsed.version === "string" ? parsed.version : null;
|
|
14747
15385
|
} catch {
|
|
@@ -14750,7 +15388,7 @@ async function readPackageVersion(scriptUrl) {
|
|
|
14750
15388
|
}
|
|
14751
15389
|
|
|
14752
15390
|
// src/utils/npx-prompt.ts
|
|
14753
|
-
var STATE_FILE =
|
|
15391
|
+
var STATE_FILE = resolve45(syntaurRoot(), "npx-install.json");
|
|
14754
15392
|
var META_ARGS = /* @__PURE__ */ new Set(["-h", "--help", "-V", "--version", "help"]);
|
|
14755
15393
|
var GLOBAL_VERSION_TIMEOUT_MS = 2e3;
|
|
14756
15394
|
function isRunningViaNpx(scriptUrl) {
|
|
@@ -14771,7 +15409,7 @@ function isRunningViaNpx(scriptUrl) {
|
|
|
14771
15409
|
async function readState() {
|
|
14772
15410
|
if (!await fileExists(STATE_FILE)) return null;
|
|
14773
15411
|
try {
|
|
14774
|
-
const raw = await
|
|
15412
|
+
const raw = await readFile29(STATE_FILE, "utf-8");
|
|
14775
15413
|
return JSON.parse(raw);
|
|
14776
15414
|
} catch {
|
|
14777
15415
|
return null;
|
|
@@ -14830,7 +15468,7 @@ async function readGlobalVersion() {
|
|
|
14830
15468
|
try {
|
|
14831
15469
|
const manifestPath = join7(rootPath, "syntaur", "package.json");
|
|
14832
15470
|
if (!await fileExists(manifestPath)) return null;
|
|
14833
|
-
const raw = await
|
|
15471
|
+
const raw = await readFile29(manifestPath, "utf-8");
|
|
14834
15472
|
const parsed = JSON.parse(raw);
|
|
14835
15473
|
return typeof parsed.version === "string" ? parsed.version : null;
|
|
14836
15474
|
} catch {
|