ralphctl 0.2.5 → 0.3.1
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/dist/add-CIM72NE3.mjs +18 -0
- package/dist/add-GX7P7XTT.mjs +16 -0
- package/dist/bootstrap-FMHG6DRY.mjs +11 -0
- package/dist/chunk-3QBEBKMZ.mjs +103 -0
- package/dist/{chunk-EDJX7TT6.mjs → chunk-57UWLHRH.mjs} +22 -2
- package/dist/chunk-747KW2RW.mjs +24 -0
- package/dist/chunk-7JLZQICD.mjs +228 -0
- package/dist/{chunk-7TG3EAQ2.mjs → chunk-CFUVE2BP.mjs} +1 -5
- package/dist/chunk-CSC4TBJB.mjs +5546 -0
- package/dist/{chunk-IB6OCKZW.mjs → chunk-CTP2A436.mjs} +60 -55
- package/dist/{chunk-UBPZHHCD.mjs → chunk-D2YGPLIV.mjs} +84 -41
- package/dist/chunk-EPDR6VO5.mjs +5109 -0
- package/dist/{chunk-QBXHAXHI.mjs → chunk-FKMKOWLA.mjs} +154 -208
- package/dist/{chunk-OEUJDSHY.mjs → chunk-IWXBJD2D.mjs} +1 -1
- package/dist/chunk-JOQO4HMM.mjs +269 -0
- package/dist/{chunk-EUNAUHC3.mjs → chunk-NUYQK5MN.mjs} +80 -29
- package/dist/{chunk-JRFOUFD3.mjs → chunk-YCDUVPRT.mjs} +32 -52
- package/dist/cli.mjs +171 -3996
- package/dist/create-7WFSCMP4.mjs +15 -0
- package/dist/{handle-TA4MYNQJ.mjs → handle-BBAZJ44Y.mjs} +2 -2
- package/dist/mount-U7QXVB5Q.mjs +6804 -0
- package/dist/{project-YONEJICR.mjs → project-2IE7VWDB.mjs} +9 -5
- package/dist/prompts/harness-context.md +3 -3
- package/dist/prompts/ideate-auto.md +8 -10
- package/dist/prompts/ideate.md +3 -2
- package/dist/prompts/plan-auto.md +12 -12
- package/dist/prompts/plan-common.md +47 -19
- package/dist/prompts/plan-interactive.md +8 -8
- package/dist/prompts/signals-evaluation.md +1 -1
- package/dist/prompts/sprint-feedback.md +48 -0
- package/dist/prompts/task-evaluation-resume.md +12 -5
- package/dist/prompts/task-evaluation.md +37 -33
- package/dist/prompts/task-execution.md +33 -24
- package/dist/prompts/ticket-refine.md +6 -5
- package/dist/prompts/validation-checklist.md +10 -10
- package/dist/{resolver-RXEY6EJE.mjs → resolver-EOE5WUMV.mjs} +5 -5
- package/dist/{sprint-FGLWYWKX.mjs → sprint-OGOFEJJH.mjs} +7 -9
- package/dist/start-WG7VMEB2.mjs +17 -0
- package/package.json +15 -13
- package/dist/add-3T225IX5.mjs +0 -16
- package/dist/add-6A5432U2.mjs +0 -16
- package/dist/chunk-742XQ7FL.mjs +0 -551
- package/dist/chunk-7LZ6GOGN.mjs +0 -53
- package/dist/chunk-CSICORGV.mjs +0 -4333
- package/dist/chunk-DUU5346E.mjs +0 -59
- package/dist/create-MYGOWO2F.mjs +0 -12
- package/dist/multiline-OHSNFCRG.mjs +0 -40
- package/dist/wizard-XZ7OGBCJ.mjs +0 -193
- package/schemas/config.schema.json +0 -30
- package/schemas/ideate-output.schema.json +0 -22
- package/schemas/projects.schema.json +0 -58
- package/schemas/requirements-output.schema.json +0 -24
- package/schemas/sprint.schema.json +0 -109
- package/schemas/task-import.schema.json +0 -56
- package/schemas/tasks.schema.json +0 -98
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
assertSprintStatus,
|
|
4
|
+
resolveSprintId
|
|
5
|
+
} from "./chunk-YCDUVPRT.mjs";
|
|
6
|
+
import {
|
|
7
|
+
unwrapOrThrow
|
|
8
|
+
} from "./chunk-IWXBJD2D.mjs";
|
|
9
|
+
import {
|
|
10
|
+
SprintSchema,
|
|
11
|
+
generateUuid8,
|
|
12
|
+
getSprintFilePath,
|
|
13
|
+
readValidatedJson,
|
|
14
|
+
writeValidatedJson
|
|
15
|
+
} from "./chunk-CTP2A436.mjs";
|
|
16
|
+
import {
|
|
17
|
+
IssueFetchError,
|
|
18
|
+
TicketNotFoundError
|
|
19
|
+
} from "./chunk-57UWLHRH.mjs";
|
|
20
|
+
|
|
21
|
+
// src/integration/persistence/ticket.ts
|
|
22
|
+
async function getSprintData(sprintId) {
|
|
23
|
+
const id = await resolveSprintId(sprintId);
|
|
24
|
+
const result = await readValidatedJson(getSprintFilePath(id), SprintSchema);
|
|
25
|
+
if (!result.ok) throw result.error;
|
|
26
|
+
return result.value;
|
|
27
|
+
}
|
|
28
|
+
async function saveSprintData(sprint) {
|
|
29
|
+
const result = await writeValidatedJson(getSprintFilePath(sprint.id), sprint, SprintSchema);
|
|
30
|
+
if (!result.ok) throw result.error;
|
|
31
|
+
}
|
|
32
|
+
async function addTicket(input, sprintId) {
|
|
33
|
+
const sprint = await getSprintData(sprintId);
|
|
34
|
+
assertSprintStatus(sprint, ["draft"], "add tickets");
|
|
35
|
+
const ticket = {
|
|
36
|
+
id: generateUuid8(),
|
|
37
|
+
title: input.title,
|
|
38
|
+
description: input.description,
|
|
39
|
+
link: input.link,
|
|
40
|
+
affectedRepoIds: input.affectedRepoIds,
|
|
41
|
+
requirementStatus: "pending"
|
|
42
|
+
};
|
|
43
|
+
sprint.tickets.push(ticket);
|
|
44
|
+
await saveSprintData(sprint);
|
|
45
|
+
return ticket;
|
|
46
|
+
}
|
|
47
|
+
async function updateTicket(ticketId, updates, sprintId) {
|
|
48
|
+
const sprint = await getSprintData(sprintId);
|
|
49
|
+
assertSprintStatus(sprint, ["draft"], "update tickets");
|
|
50
|
+
const ticketIdx = sprint.tickets.findIndex((t) => t.id === ticketId);
|
|
51
|
+
if (ticketIdx === -1) {
|
|
52
|
+
throw new TicketNotFoundError(ticketId);
|
|
53
|
+
}
|
|
54
|
+
const ticket = sprint.tickets[ticketIdx];
|
|
55
|
+
if (!ticket) {
|
|
56
|
+
throw new TicketNotFoundError(ticketId);
|
|
57
|
+
}
|
|
58
|
+
if (updates.title !== void 0) {
|
|
59
|
+
ticket.title = updates.title;
|
|
60
|
+
}
|
|
61
|
+
if (updates.description !== void 0) {
|
|
62
|
+
ticket.description = updates.description || void 0;
|
|
63
|
+
}
|
|
64
|
+
if (updates.link !== void 0) {
|
|
65
|
+
ticket.link = updates.link || void 0;
|
|
66
|
+
}
|
|
67
|
+
await saveSprintData(sprint);
|
|
68
|
+
return ticket;
|
|
69
|
+
}
|
|
70
|
+
async function removeTicket(ticketId, sprintId) {
|
|
71
|
+
const sprint = await getSprintData(sprintId);
|
|
72
|
+
assertSprintStatus(sprint, ["draft"], "remove tickets");
|
|
73
|
+
const index = sprint.tickets.findIndex((t) => t.id === ticketId);
|
|
74
|
+
if (index === -1) {
|
|
75
|
+
throw new TicketNotFoundError(ticketId);
|
|
76
|
+
}
|
|
77
|
+
sprint.tickets.splice(index, 1);
|
|
78
|
+
await saveSprintData(sprint);
|
|
79
|
+
}
|
|
80
|
+
async function listTickets(sprintId) {
|
|
81
|
+
const sprint = await getSprintData(sprintId);
|
|
82
|
+
return sprint.tickets;
|
|
83
|
+
}
|
|
84
|
+
async function getTicket(ticketId, sprintId) {
|
|
85
|
+
const sprint = await getSprintData(sprintId);
|
|
86
|
+
const ticket = sprint.tickets.find((t) => t.id === ticketId);
|
|
87
|
+
if (!ticket) {
|
|
88
|
+
throw new TicketNotFoundError(ticketId);
|
|
89
|
+
}
|
|
90
|
+
return ticket;
|
|
91
|
+
}
|
|
92
|
+
function allRequirementsApproved(tickets) {
|
|
93
|
+
return tickets.length > 0 && tickets.every((t) => t.requirementStatus === "approved");
|
|
94
|
+
}
|
|
95
|
+
function getPendingRequirements(tickets) {
|
|
96
|
+
return tickets.filter((t) => t.requirementStatus === "pending");
|
|
97
|
+
}
|
|
98
|
+
function formatTicketDisplay(ticket) {
|
|
99
|
+
return `[${ticket.id}] ${ticket.title}`;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// src/domain/strings.ts
|
|
103
|
+
function truncate(str, max) {
|
|
104
|
+
if (str.length <= max) return str;
|
|
105
|
+
if (max <= 1) return "\u2026".slice(0, Math.max(0, max));
|
|
106
|
+
return str.slice(0, max - 1) + "\u2026";
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// src/integration/external/issue-fetch.ts
|
|
110
|
+
import { spawnSync } from "child_process";
|
|
111
|
+
import { Result } from "typescript-result";
|
|
112
|
+
var MAX_COMMENTS = 20;
|
|
113
|
+
function parseIssueUrl(url) {
|
|
114
|
+
let parsed;
|
|
115
|
+
try {
|
|
116
|
+
parsed = new URL(url);
|
|
117
|
+
} catch {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
const segments = parsed.pathname.split("/").filter(Boolean);
|
|
124
|
+
if (parsed.hostname === "github.com") {
|
|
125
|
+
const owner = segments[0];
|
|
126
|
+
const repo = segments[1];
|
|
127
|
+
if (segments.length >= 4 && segments[2] === "issues" && owner && repo) {
|
|
128
|
+
const num = Number(segments[3]);
|
|
129
|
+
if (Number.isInteger(num) && num > 0) {
|
|
130
|
+
return { host: "github", hostname: parsed.hostname, owner, repo, number: num };
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
const dashIdx = segments.indexOf("-");
|
|
136
|
+
if (dashIdx >= 2 && segments[dashIdx + 1] === "issues") {
|
|
137
|
+
const num = Number(segments[dashIdx + 2]);
|
|
138
|
+
if (Number.isInteger(num) && num > 0) {
|
|
139
|
+
const repo = segments[dashIdx - 1];
|
|
140
|
+
if (repo) {
|
|
141
|
+
const owner = segments.slice(0, dashIdx - 1).join("/");
|
|
142
|
+
return { host: "gitlab", hostname: parsed.hostname, owner, repo, number: num };
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
function fetchGitHubIssueResult(parsed) {
|
|
149
|
+
const result = spawnSync(
|
|
150
|
+
"gh",
|
|
151
|
+
[
|
|
152
|
+
"issue",
|
|
153
|
+
"view",
|
|
154
|
+
String(parsed.number),
|
|
155
|
+
"--repo",
|
|
156
|
+
`${parsed.owner}/${parsed.repo}`,
|
|
157
|
+
"--json",
|
|
158
|
+
"title,body,comments"
|
|
159
|
+
],
|
|
160
|
+
{ encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], timeout: 3e4 }
|
|
161
|
+
);
|
|
162
|
+
if (result.status !== 0) {
|
|
163
|
+
const stderr = result.stderr.trim();
|
|
164
|
+
return Result.error(new IssueFetchError(`gh issue view failed: ${stderr || "unknown error"}`));
|
|
165
|
+
}
|
|
166
|
+
const data = JSON.parse(result.stdout);
|
|
167
|
+
const comments = (data.comments ?? []).slice(-MAX_COMMENTS).map((c) => ({
|
|
168
|
+
author: c.author?.login ?? "unknown",
|
|
169
|
+
createdAt: c.createdAt ?? "",
|
|
170
|
+
body: c.body ?? ""
|
|
171
|
+
}));
|
|
172
|
+
return Result.ok({
|
|
173
|
+
title: data.title ?? "",
|
|
174
|
+
body: data.body ?? "",
|
|
175
|
+
comments,
|
|
176
|
+
url: `https://${parsed.hostname}/${parsed.owner}/${parsed.repo}/issues/${String(parsed.number)}`
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
function fetchGitLabIssueResult(parsed) {
|
|
180
|
+
const result = spawnSync(
|
|
181
|
+
"glab",
|
|
182
|
+
["issue", "view", String(parsed.number), "--repo", `${parsed.owner}/${parsed.repo}`, "--output", "json"],
|
|
183
|
+
{ encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], timeout: 3e4 }
|
|
184
|
+
);
|
|
185
|
+
if (result.status !== 0) {
|
|
186
|
+
const stderr = result.stderr.trim();
|
|
187
|
+
return Result.error(new IssueFetchError(`glab issue view failed: ${stderr || "unknown error"}`));
|
|
188
|
+
}
|
|
189
|
+
const data = JSON.parse(result.stdout);
|
|
190
|
+
const notesResult = spawnSync(
|
|
191
|
+
"glab",
|
|
192
|
+
["issue", "note", "list", String(parsed.number), "--repo", `${parsed.owner}/${parsed.repo}`, "--output", "json"],
|
|
193
|
+
{ encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], timeout: 3e4 }
|
|
194
|
+
);
|
|
195
|
+
let comments = [];
|
|
196
|
+
if (notesResult.status === 0 && notesResult.stdout.trim()) {
|
|
197
|
+
try {
|
|
198
|
+
const notes = JSON.parse(notesResult.stdout);
|
|
199
|
+
comments = notes.slice(-MAX_COMMENTS).map((n) => ({
|
|
200
|
+
author: n.author?.username ?? "unknown",
|
|
201
|
+
createdAt: n.created_at ?? "",
|
|
202
|
+
body: n.body ?? ""
|
|
203
|
+
}));
|
|
204
|
+
} catch {
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return Result.ok({
|
|
208
|
+
title: data.title ?? "",
|
|
209
|
+
body: data.description ?? "",
|
|
210
|
+
comments,
|
|
211
|
+
url: `https://${parsed.hostname}/${parsed.owner}/${parsed.repo}/-/issues/${String(parsed.number)}`
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
function fetchIssueResult(parsed) {
|
|
215
|
+
if (parsed.host === "github") {
|
|
216
|
+
return fetchGitHubIssueResult(parsed);
|
|
217
|
+
}
|
|
218
|
+
return fetchGitLabIssueResult(parsed);
|
|
219
|
+
}
|
|
220
|
+
function fetchIssue(parsed) {
|
|
221
|
+
return unwrapOrThrow(fetchIssueResult(parsed));
|
|
222
|
+
}
|
|
223
|
+
function fetchIssueFromUrl(url) {
|
|
224
|
+
const parsed = parseIssueUrl(url);
|
|
225
|
+
if (!parsed) return null;
|
|
226
|
+
return fetchIssue(parsed);
|
|
227
|
+
}
|
|
228
|
+
function formatIssueContext(data) {
|
|
229
|
+
const lines = [];
|
|
230
|
+
lines.push("## Source Issue Data");
|
|
231
|
+
lines.push("");
|
|
232
|
+
lines.push(`> Fetched live from ${data.url}`);
|
|
233
|
+
lines.push("");
|
|
234
|
+
lines.push(`**Title:** ${data.title}`);
|
|
235
|
+
lines.push("");
|
|
236
|
+
if (data.body) {
|
|
237
|
+
lines.push("**Body:**");
|
|
238
|
+
lines.push("");
|
|
239
|
+
lines.push(data.body);
|
|
240
|
+
lines.push("");
|
|
241
|
+
}
|
|
242
|
+
if (data.comments.length > 0) {
|
|
243
|
+
lines.push(`**Comments (${String(data.comments.length)}):**`);
|
|
244
|
+
lines.push("");
|
|
245
|
+
for (const comment of data.comments) {
|
|
246
|
+
const timestamp = comment.createdAt ? ` (${comment.createdAt})` : "";
|
|
247
|
+
lines.push(`---`);
|
|
248
|
+
lines.push(`**@${comment.author}**${timestamp}:`);
|
|
249
|
+
lines.push("");
|
|
250
|
+
lines.push(comment.body);
|
|
251
|
+
lines.push("");
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
return lines.join("\n");
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export {
|
|
258
|
+
addTicket,
|
|
259
|
+
updateTicket,
|
|
260
|
+
removeTicket,
|
|
261
|
+
listTickets,
|
|
262
|
+
getTicket,
|
|
263
|
+
allRequirementsApproved,
|
|
264
|
+
getPendingRequirements,
|
|
265
|
+
formatTicketDisplay,
|
|
266
|
+
truncate,
|
|
267
|
+
fetchIssueFromUrl,
|
|
268
|
+
formatIssueContext
|
|
269
|
+
};
|
|
@@ -3,35 +3,51 @@ import {
|
|
|
3
3
|
ProjectsSchema,
|
|
4
4
|
expandTilde,
|
|
5
5
|
fileExists,
|
|
6
|
+
generateUuid8,
|
|
6
7
|
getProjectsFilePath,
|
|
7
8
|
readValidatedJson,
|
|
8
9
|
validateProjectPath,
|
|
9
10
|
writeValidatedJson
|
|
10
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-CTP2A436.mjs";
|
|
11
12
|
import {
|
|
12
13
|
ParseError,
|
|
13
14
|
ProjectExistsError,
|
|
14
15
|
ProjectNotFoundError,
|
|
15
16
|
ValidationError
|
|
16
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-57UWLHRH.mjs";
|
|
17
18
|
|
|
18
|
-
// src/
|
|
19
|
+
// src/integration/persistence/project.ts
|
|
19
20
|
import { basename, resolve } from "path";
|
|
20
21
|
function migrateProjectIfNeeded(project) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
24
|
-
if (project.paths) {
|
|
22
|
+
const id = project.id ?? generateUuid8();
|
|
23
|
+
if (project.paths && !project.repositories) {
|
|
25
24
|
return {
|
|
25
|
+
id,
|
|
26
26
|
name: project.name,
|
|
27
27
|
displayName: project.displayName,
|
|
28
28
|
repositories: project.paths.map((p) => ({
|
|
29
|
+
id: generateUuid8(),
|
|
29
30
|
name: basename(p),
|
|
30
31
|
path: resolve(expandTilde(p))
|
|
31
32
|
})),
|
|
32
33
|
description: project.description
|
|
33
34
|
};
|
|
34
35
|
}
|
|
36
|
+
if (project.repositories) {
|
|
37
|
+
return {
|
|
38
|
+
id,
|
|
39
|
+
name: project.name,
|
|
40
|
+
displayName: project.displayName,
|
|
41
|
+
repositories: project.repositories.map((r) => ({
|
|
42
|
+
id: r.id ?? generateUuid8(),
|
|
43
|
+
name: r.name,
|
|
44
|
+
path: r.path,
|
|
45
|
+
checkScript: r.checkScript,
|
|
46
|
+
checkTimeout: r.checkTimeout
|
|
47
|
+
})),
|
|
48
|
+
description: project.description
|
|
49
|
+
};
|
|
50
|
+
}
|
|
35
51
|
throw new ParseError(`Invalid project data: no paths or repositories for ${project.name}`);
|
|
36
52
|
}
|
|
37
53
|
async function listProjects() {
|
|
@@ -42,7 +58,11 @@ async function listProjects() {
|
|
|
42
58
|
const { readFile } = await import("fs/promises");
|
|
43
59
|
const content = await readFile(filePath, "utf-8");
|
|
44
60
|
const rawData = JSON.parse(content);
|
|
45
|
-
const needsMigration = rawData.some((p) =>
|
|
61
|
+
const needsMigration = rawData.some((p) => {
|
|
62
|
+
if (!p.id) return true;
|
|
63
|
+
if (p.paths && !p.repositories) return true;
|
|
64
|
+
return (p.repositories ?? []).some((r) => !r.id);
|
|
65
|
+
});
|
|
46
66
|
if (needsMigration) {
|
|
47
67
|
const migrated = rawData.map(migrateProjectIfNeeded);
|
|
48
68
|
const validated = ProjectsSchema.parse(migrated);
|
|
@@ -76,17 +96,37 @@ async function getProject(name) {
|
|
|
76
96
|
}
|
|
77
97
|
return project;
|
|
78
98
|
}
|
|
99
|
+
async function getProjectById(id) {
|
|
100
|
+
const projects = await listProjects();
|
|
101
|
+
const project = projects.find((p) => p.id === id);
|
|
102
|
+
if (!project) {
|
|
103
|
+
throw new ProjectNotFoundError(id);
|
|
104
|
+
}
|
|
105
|
+
return project;
|
|
106
|
+
}
|
|
107
|
+
async function getRepoById(repoId) {
|
|
108
|
+
const projects = await listProjects();
|
|
109
|
+
for (const project of projects) {
|
|
110
|
+
const repo = project.repositories.find((r) => r.id === repoId);
|
|
111
|
+
if (repo) return { project, repo };
|
|
112
|
+
}
|
|
113
|
+
throw new ValidationError(`Repository not found: ${repoId}`, "repoId");
|
|
114
|
+
}
|
|
115
|
+
async function resolveRepoPath(repoId) {
|
|
116
|
+
const { repo } = await getRepoById(repoId);
|
|
117
|
+
return repo.path;
|
|
118
|
+
}
|
|
79
119
|
async function projectExists(name) {
|
|
80
120
|
const projects = await listProjects();
|
|
81
121
|
return projects.some((p) => p.name === name);
|
|
82
122
|
}
|
|
83
|
-
async function createProject(
|
|
123
|
+
async function createProject(input) {
|
|
84
124
|
const projects = await listProjects();
|
|
85
|
-
if (projects.some((p) => p.name ===
|
|
86
|
-
throw new ProjectExistsError(
|
|
125
|
+
if (projects.some((p) => p.name === input.name)) {
|
|
126
|
+
throw new ProjectExistsError(input.name);
|
|
87
127
|
}
|
|
88
128
|
const pathErrors = [];
|
|
89
|
-
for (const repo of
|
|
129
|
+
for (const repo of input.repositories) {
|
|
90
130
|
const resolved = resolve(expandTilde(repo.path));
|
|
91
131
|
const validation = await validateProjectPath(resolved);
|
|
92
132
|
if (!validation.ok) {
|
|
@@ -98,12 +138,20 @@ async function createProject(project) {
|
|
|
98
138
|
${pathErrors.join("\n")}`, "repositories");
|
|
99
139
|
}
|
|
100
140
|
const normalizedProject = {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
141
|
+
id: input.id ?? generateUuid8(),
|
|
142
|
+
name: input.name,
|
|
143
|
+
displayName: input.displayName,
|
|
144
|
+
description: input.description,
|
|
145
|
+
repositories: input.repositories.map((repo) => {
|
|
146
|
+
const resolvedPath = resolve(expandTilde(repo.path));
|
|
147
|
+
return {
|
|
148
|
+
id: repo.id ?? generateUuid8(),
|
|
149
|
+
name: repo.name && repo.name.length > 0 ? repo.name : basename(resolvedPath),
|
|
150
|
+
path: resolvedPath,
|
|
151
|
+
checkScript: repo.checkScript,
|
|
152
|
+
checkTimeout: repo.checkTimeout
|
|
153
|
+
};
|
|
154
|
+
})
|
|
107
155
|
};
|
|
108
156
|
projects.push(normalizedProject);
|
|
109
157
|
const writeResult = await writeValidatedJson(getProjectsFilePath(), projects, ProjectsSchema);
|
|
@@ -130,9 +178,11 @@ async function updateProject(name, updates) {
|
|
|
130
178
|
${pathErrors.join("\n")}`, "repositories");
|
|
131
179
|
}
|
|
132
180
|
updates.repositories = updates.repositories.map((repo) => ({
|
|
133
|
-
|
|
134
|
-
name: repo.name
|
|
135
|
-
path: resolve(expandTilde(repo.path))
|
|
181
|
+
id: repo.id.length > 0 ? repo.id : generateUuid8(),
|
|
182
|
+
name: repo.name && repo.name.length > 0 ? repo.name : basename(resolve(expandTilde(repo.path))),
|
|
183
|
+
path: resolve(expandTilde(repo.path)),
|
|
184
|
+
checkScript: repo.checkScript,
|
|
185
|
+
checkTimeout: repo.checkTimeout
|
|
136
186
|
}));
|
|
137
187
|
}
|
|
138
188
|
const existingProject = projects[index];
|
|
@@ -140,6 +190,7 @@ ${pathErrors.join("\n")}`, "repositories");
|
|
|
140
190
|
throw new ProjectNotFoundError(name);
|
|
141
191
|
}
|
|
142
192
|
const updatedProject = {
|
|
193
|
+
id: existingProject.id,
|
|
143
194
|
name: existingProject.name,
|
|
144
195
|
displayName: updates.displayName ?? existingProject.displayName,
|
|
145
196
|
repositories: updates.repositories ?? existingProject.repositories,
|
|
@@ -160,10 +211,6 @@ async function removeProject(name) {
|
|
|
160
211
|
const writeResult = await writeValidatedJson(getProjectsFilePath(), projects, ProjectsSchema);
|
|
161
212
|
if (!writeResult.ok) throw writeResult.error;
|
|
162
213
|
}
|
|
163
|
-
async function getProjectRepos(name) {
|
|
164
|
-
const project = await getProject(name);
|
|
165
|
-
return project.repositories;
|
|
166
|
-
}
|
|
167
214
|
async function addProjectRepo(name, repo) {
|
|
168
215
|
const project = await getProject(name);
|
|
169
216
|
const resolvedPath = resolve(expandTilde(repo.path));
|
|
@@ -175,9 +222,11 @@ async function addProjectRepo(name, repo) {
|
|
|
175
222
|
return project;
|
|
176
223
|
}
|
|
177
224
|
const normalizedRepo = {
|
|
178
|
-
|
|
179
|
-
name: repo.name
|
|
180
|
-
path: resolvedPath
|
|
225
|
+
id: repo.id ?? generateUuid8(),
|
|
226
|
+
name: repo.name && repo.name.length > 0 ? repo.name : basename(resolvedPath),
|
|
227
|
+
path: resolvedPath,
|
|
228
|
+
checkScript: repo.checkScript,
|
|
229
|
+
checkTimeout: repo.checkTimeout
|
|
181
230
|
};
|
|
182
231
|
return updateProject(name, {
|
|
183
232
|
repositories: [...project.repositories, normalizedRepo]
|
|
@@ -199,11 +248,13 @@ async function removeProjectRepo(name, path) {
|
|
|
199
248
|
export {
|
|
200
249
|
listProjects,
|
|
201
250
|
getProject,
|
|
251
|
+
getProjectById,
|
|
252
|
+
getRepoById,
|
|
253
|
+
resolveRepoPath,
|
|
202
254
|
projectExists,
|
|
203
255
|
createProject,
|
|
204
256
|
updateProject,
|
|
205
257
|
removeProject,
|
|
206
|
-
getProjectRepos,
|
|
207
258
|
addProjectRepo,
|
|
208
259
|
removeProjectRepo
|
|
209
260
|
};
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
log
|
|
4
|
+
} from "./chunk-FKMKOWLA.mjs";
|
|
2
5
|
import {
|
|
3
6
|
unwrapOrThrow
|
|
4
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-IWXBJD2D.mjs";
|
|
5
8
|
import {
|
|
6
9
|
ConfigSchema,
|
|
7
10
|
SprintSchema,
|
|
@@ -10,6 +13,7 @@ import {
|
|
|
10
13
|
assertSafeCwd,
|
|
11
14
|
ensureDir,
|
|
12
15
|
fileExists,
|
|
16
|
+
generateSprintId,
|
|
13
17
|
getConfigPath,
|
|
14
18
|
getProgressFilePath,
|
|
15
19
|
getSprintDir,
|
|
@@ -21,19 +25,16 @@ import {
|
|
|
21
25
|
readValidatedJson,
|
|
22
26
|
removeDir,
|
|
23
27
|
writeValidatedJson
|
|
24
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-CTP2A436.mjs";
|
|
25
29
|
import {
|
|
26
30
|
LockError,
|
|
27
31
|
NoCurrentSprintError,
|
|
28
32
|
SprintNotFoundError,
|
|
29
33
|
SprintStatusError,
|
|
30
34
|
StorageError
|
|
31
|
-
} from "./chunk-
|
|
32
|
-
import {
|
|
33
|
-
log
|
|
34
|
-
} from "./chunk-QBXHAXHI.mjs";
|
|
35
|
+
} from "./chunk-57UWLHRH.mjs";
|
|
35
36
|
|
|
36
|
-
// src/
|
|
37
|
+
// src/integration/persistence/config.ts
|
|
37
38
|
var DEFAULT_EVALUATION_ITERATIONS = 1;
|
|
38
39
|
var DEFAULT_CONFIG = {
|
|
39
40
|
currentSprint: null,
|
|
@@ -87,26 +88,10 @@ async function setEvaluationIterations(iterations) {
|
|
|
87
88
|
await saveConfig(config);
|
|
88
89
|
}
|
|
89
90
|
|
|
90
|
-
// src/
|
|
91
|
-
import { randomBytes } from "crypto";
|
|
92
|
-
function generateUuid8() {
|
|
93
|
-
return randomBytes(4).toString("hex");
|
|
94
|
-
}
|
|
95
|
-
function slugify(input, maxLength = 40) {
|
|
96
|
-
return input.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").replace(/-{2,}/g, "-").slice(0, maxLength).replace(/-$/, "");
|
|
97
|
-
}
|
|
98
|
-
function generateSprintId(name) {
|
|
99
|
-
const now = /* @__PURE__ */ new Date();
|
|
100
|
-
const date = now.toISOString().slice(0, 10).replace(/-/g, "");
|
|
101
|
-
const time = now.toISOString().slice(11, 19).replace(/:/g, "");
|
|
102
|
-
const slug = name ? slugify(name) : generateUuid8();
|
|
103
|
-
return `${date}-${time}-${slug || generateUuid8()}`;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// src/store/progress.ts
|
|
91
|
+
// src/integration/persistence/progress.ts
|
|
107
92
|
import { execSync } from "child_process";
|
|
108
93
|
|
|
109
|
-
// src/
|
|
94
|
+
// src/integration/persistence/file-lock.ts
|
|
110
95
|
import { mkdir, readFile, unlink, writeFile } from "fs/promises";
|
|
111
96
|
import { dirname } from "path";
|
|
112
97
|
import { Result } from "typescript-result";
|
|
@@ -193,7 +178,7 @@ async function withFileLock(filePath, fn) {
|
|
|
193
178
|
}
|
|
194
179
|
}
|
|
195
180
|
|
|
196
|
-
// src/
|
|
181
|
+
// src/integration/persistence/progress.ts
|
|
197
182
|
async function logProgress(message, options = {}) {
|
|
198
183
|
const id = await resolveSprintId(options.sprintId);
|
|
199
184
|
const sprint = await getSprint(id);
|
|
@@ -347,7 +332,7 @@ function filterProgressByProject(progress, projectPath) {
|
|
|
347
332
|
return filtered.join("\n---\n") + "\n\n---\n\n";
|
|
348
333
|
}
|
|
349
334
|
|
|
350
|
-
// src/
|
|
335
|
+
// src/integration/persistence/sprint.ts
|
|
351
336
|
function assertSprintStatus(sprint, allowedStatuses, operation) {
|
|
352
337
|
if (!allowedStatuses.includes(sprint.status)) {
|
|
353
338
|
const statusText = allowedStatuses.join(" or ");
|
|
@@ -375,13 +360,15 @@ Hint: ${hint}` : "";
|
|
|
375
360
|
);
|
|
376
361
|
}
|
|
377
362
|
}
|
|
378
|
-
async function createSprint(
|
|
363
|
+
async function createSprint(input) {
|
|
364
|
+
const { projectId, name } = input;
|
|
379
365
|
const id = generateSprintId(name);
|
|
380
366
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
381
367
|
const displayName = name ?? id.slice(16);
|
|
382
368
|
const sprint = {
|
|
383
369
|
id,
|
|
384
370
|
name: displayName,
|
|
371
|
+
projectId,
|
|
385
372
|
status: "draft",
|
|
386
373
|
createdAt: now,
|
|
387
374
|
activatedAt: null,
|
|
@@ -409,10 +396,6 @@ Created: ${now}
|
|
|
409
396
|
if (!appendResult.ok) throw appendResult.error;
|
|
410
397
|
return sprint;
|
|
411
398
|
}
|
|
412
|
-
async function findActiveSprint() {
|
|
413
|
-
const sprints = await listSprints();
|
|
414
|
-
return sprints.find((s) => s.status === "active") ?? null;
|
|
415
|
-
}
|
|
416
399
|
async function getSprint(sprintId) {
|
|
417
400
|
const sprintPath = getSprintFilePath(sprintId);
|
|
418
401
|
if (!await fileExists(sprintPath)) {
|
|
@@ -447,17 +430,23 @@ async function activateSprint(sprintId) {
|
|
|
447
430
|
await saveSprint(sprint);
|
|
448
431
|
const tasksResult = await readValidatedJson(getTasksFilePath(sprintId), TasksSchema);
|
|
449
432
|
if (!tasksResult.ok) throw tasksResult.error;
|
|
450
|
-
const
|
|
451
|
-
|
|
452
|
-
if (projectPaths.length > 0) {
|
|
453
|
-
await logBaselines({
|
|
454
|
-
sprintId,
|
|
455
|
-
sprintName: sprint.name,
|
|
456
|
-
projectPaths
|
|
457
|
-
});
|
|
458
|
-
}
|
|
433
|
+
const _tasks = tasksResult.value;
|
|
434
|
+
void _tasks;
|
|
459
435
|
return sprint;
|
|
460
436
|
}
|
|
437
|
+
async function logSprintBaselines(sprint, resolvePath) {
|
|
438
|
+
const tasksResult = await readValidatedJson(getTasksFilePath(sprint.id), TasksSchema);
|
|
439
|
+
if (!tasksResult.ok) throw tasksResult.error;
|
|
440
|
+
const repoIds = [...new Set(tasksResult.value.map((t) => t.repoId))];
|
|
441
|
+
const paths = [];
|
|
442
|
+
for (const repoId of repoIds) {
|
|
443
|
+
const p = await resolvePath(repoId);
|
|
444
|
+
if (p) paths.push(p);
|
|
445
|
+
}
|
|
446
|
+
if (paths.length > 0) {
|
|
447
|
+
await logBaselines({ sprintId: sprint.id, sprintName: sprint.name, projectPaths: paths });
|
|
448
|
+
}
|
|
449
|
+
}
|
|
461
450
|
async function closeSprint(sprintId) {
|
|
462
451
|
const sprint = await getSprint(sprintId);
|
|
463
452
|
assertSprintStatus(sprint, ["active"], "close");
|
|
@@ -480,13 +469,6 @@ async function getCurrentSprintOrThrow() {
|
|
|
480
469
|
}
|
|
481
470
|
return getSprint(currentSprintId);
|
|
482
471
|
}
|
|
483
|
-
async function getActiveSprintOrThrow() {
|
|
484
|
-
const activeSprint = await findActiveSprint();
|
|
485
|
-
if (!activeSprint) {
|
|
486
|
-
throw new NoCurrentSprintError();
|
|
487
|
-
}
|
|
488
|
-
return activeSprint;
|
|
489
|
-
}
|
|
490
472
|
async function resolveSprintId(sprintId) {
|
|
491
473
|
if (sprintId) {
|
|
492
474
|
return sprintId;
|
|
@@ -499,8 +481,8 @@ async function resolveSprintId(sprintId) {
|
|
|
499
481
|
}
|
|
500
482
|
|
|
501
483
|
export {
|
|
502
|
-
DEFAULT_EVALUATION_ITERATIONS,
|
|
503
484
|
getConfig,
|
|
485
|
+
saveConfig,
|
|
504
486
|
getCurrentSprint,
|
|
505
487
|
setCurrentSprint,
|
|
506
488
|
getAiProvider,
|
|
@@ -509,21 +491,19 @@ export {
|
|
|
509
491
|
setEditor,
|
|
510
492
|
getEvaluationIterations,
|
|
511
493
|
setEvaluationIterations,
|
|
512
|
-
generateUuid8,
|
|
513
494
|
withFileLock,
|
|
514
495
|
logProgress,
|
|
515
496
|
getProgress,
|
|
516
497
|
summarizeProgressForContext,
|
|
517
498
|
assertSprintStatus,
|
|
518
499
|
createSprint,
|
|
519
|
-
findActiveSprint,
|
|
520
500
|
getSprint,
|
|
521
501
|
saveSprint,
|
|
522
502
|
listSprints,
|
|
523
503
|
activateSprint,
|
|
504
|
+
logSprintBaselines,
|
|
524
505
|
closeSprint,
|
|
525
506
|
deleteSprint,
|
|
526
507
|
getCurrentSprintOrThrow,
|
|
527
|
-
getActiveSprintOrThrow,
|
|
528
508
|
resolveSprintId
|
|
529
509
|
};
|