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.
Files changed (55) hide show
  1. package/dist/add-CIM72NE3.mjs +18 -0
  2. package/dist/add-GX7P7XTT.mjs +16 -0
  3. package/dist/bootstrap-FMHG6DRY.mjs +11 -0
  4. package/dist/chunk-3QBEBKMZ.mjs +103 -0
  5. package/dist/{chunk-EDJX7TT6.mjs → chunk-57UWLHRH.mjs} +22 -2
  6. package/dist/chunk-747KW2RW.mjs +24 -0
  7. package/dist/chunk-7JLZQICD.mjs +228 -0
  8. package/dist/{chunk-7TG3EAQ2.mjs → chunk-CFUVE2BP.mjs} +1 -5
  9. package/dist/chunk-CSC4TBJB.mjs +5546 -0
  10. package/dist/{chunk-IB6OCKZW.mjs → chunk-CTP2A436.mjs} +60 -55
  11. package/dist/{chunk-UBPZHHCD.mjs → chunk-D2YGPLIV.mjs} +84 -41
  12. package/dist/chunk-EPDR6VO5.mjs +5109 -0
  13. package/dist/{chunk-QBXHAXHI.mjs → chunk-FKMKOWLA.mjs} +154 -208
  14. package/dist/{chunk-OEUJDSHY.mjs → chunk-IWXBJD2D.mjs} +1 -1
  15. package/dist/chunk-JOQO4HMM.mjs +269 -0
  16. package/dist/{chunk-EUNAUHC3.mjs → chunk-NUYQK5MN.mjs} +80 -29
  17. package/dist/{chunk-JRFOUFD3.mjs → chunk-YCDUVPRT.mjs} +32 -52
  18. package/dist/cli.mjs +171 -3996
  19. package/dist/create-7WFSCMP4.mjs +15 -0
  20. package/dist/{handle-TA4MYNQJ.mjs → handle-BBAZJ44Y.mjs} +2 -2
  21. package/dist/mount-U7QXVB5Q.mjs +6804 -0
  22. package/dist/{project-YONEJICR.mjs → project-2IE7VWDB.mjs} +9 -5
  23. package/dist/prompts/harness-context.md +3 -3
  24. package/dist/prompts/ideate-auto.md +8 -10
  25. package/dist/prompts/ideate.md +3 -2
  26. package/dist/prompts/plan-auto.md +12 -12
  27. package/dist/prompts/plan-common.md +47 -19
  28. package/dist/prompts/plan-interactive.md +8 -8
  29. package/dist/prompts/signals-evaluation.md +1 -1
  30. package/dist/prompts/sprint-feedback.md +48 -0
  31. package/dist/prompts/task-evaluation-resume.md +12 -5
  32. package/dist/prompts/task-evaluation.md +37 -33
  33. package/dist/prompts/task-execution.md +33 -24
  34. package/dist/prompts/ticket-refine.md +6 -5
  35. package/dist/prompts/validation-checklist.md +10 -10
  36. package/dist/{resolver-RXEY6EJE.mjs → resolver-EOE5WUMV.mjs} +5 -5
  37. package/dist/{sprint-FGLWYWKX.mjs → sprint-OGOFEJJH.mjs} +7 -9
  38. package/dist/start-WG7VMEB2.mjs +17 -0
  39. package/package.json +15 -13
  40. package/dist/add-3T225IX5.mjs +0 -16
  41. package/dist/add-6A5432U2.mjs +0 -16
  42. package/dist/chunk-742XQ7FL.mjs +0 -551
  43. package/dist/chunk-7LZ6GOGN.mjs +0 -53
  44. package/dist/chunk-CSICORGV.mjs +0 -4333
  45. package/dist/chunk-DUU5346E.mjs +0 -59
  46. package/dist/create-MYGOWO2F.mjs +0 -12
  47. package/dist/multiline-OHSNFCRG.mjs +0 -40
  48. package/dist/wizard-XZ7OGBCJ.mjs +0 -193
  49. package/schemas/config.schema.json +0 -30
  50. package/schemas/ideate-output.schema.json +0 -22
  51. package/schemas/projects.schema.json +0 -58
  52. package/schemas/requirements-output.schema.json +0 -24
  53. package/schemas/sprint.schema.json +0 -109
  54. package/schemas/task-import.schema.json +0 -56
  55. 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-IB6OCKZW.mjs";
11
+ } from "./chunk-CTP2A436.mjs";
11
12
  import {
12
13
  ParseError,
13
14
  ProjectExistsError,
14
15
  ProjectNotFoundError,
15
16
  ValidationError
16
- } from "./chunk-EDJX7TT6.mjs";
17
+ } from "./chunk-57UWLHRH.mjs";
17
18
 
18
- // src/store/project.ts
19
+ // src/integration/persistence/project.ts
19
20
  import { basename, resolve } from "path";
20
21
  function migrateProjectIfNeeded(project) {
21
- if (project.repositories) {
22
- return project;
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) => p.paths && !p.repositories);
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(project) {
123
+ async function createProject(input) {
84
124
  const projects = await listProjects();
85
- if (projects.some((p) => p.name === project.name)) {
86
- throw new ProjectExistsError(project.name);
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 project.repositories) {
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
- ...project,
102
- repositories: project.repositories.map((repo) => ({
103
- ...repo,
104
- name: repo.name || basename(repo.path),
105
- path: resolve(expandTilde(repo.path))
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
- ...repo,
134
- name: repo.name || basename(repo.path),
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
- ...repo,
179
- name: repo.name || basename(resolvedPath),
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-OEUJDSHY.mjs";
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-IB6OCKZW.mjs";
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-EDJX7TT6.mjs";
32
- import {
33
- log
34
- } from "./chunk-QBXHAXHI.mjs";
35
+ } from "./chunk-57UWLHRH.mjs";
35
36
 
36
- // src/store/config.ts
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/utils/ids.ts
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/utils/file-lock.ts
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/store/progress.ts
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/store/sprint.ts
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(name) {
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 tasks = tasksResult.value;
451
- const projectPaths = tasks.map((t) => t.projectPath).filter((p) => !!p);
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
  };