ralphctl 0.1.2 → 0.1.3
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-HGJCLWED.mjs → add-7LBVENXM.mjs} +6 -4
- package/dist/{add-MRGCS3US.mjs → add-DVEYDCTR.mjs} +6 -4
- package/dist/{chunk-NTWO2LXB.mjs → chunk-7LZ6GOGN.mjs} +13 -12
- package/dist/{chunk-JON4GCLR.mjs → chunk-DZ6HHTM5.mjs} +1 -1
- package/dist/chunk-EDJX7TT6.mjs +148 -0
- package/dist/{chunk-MNMQC36F.mjs → chunk-F2MMCTB5.mjs} +71 -77
- package/dist/{chunk-EKMZZRWI.mjs → chunk-LFDW6MWF.mjs} +65 -70
- package/dist/{chunk-LOR7QBXX.mjs → chunk-M7JV6MKD.mjs} +270 -349
- package/dist/chunk-OEUJDSHY.mjs +27 -0
- package/dist/{chunk-WGHJI3OI.mjs → chunk-PDI6HBZ7.mjs} +32 -37
- package/dist/{chunk-6PYTKGB5.mjs → chunk-W3TY22IS.mjs} +45 -39
- package/dist/{chunk-MRKOFVTM.mjs → chunk-YIB7QYU4.mjs} +102 -100
- package/dist/cli.mjs +761 -739
- package/dist/create-MQ4OHZAX.mjs +12 -0
- package/dist/{handle-UG5M2OON.mjs → handle-K2AZLTKU.mjs} +1 -1
- package/dist/{project-NT3L4FTB.mjs → project-Q4LKML42.mjs} +6 -4
- package/dist/{resolver-WSFWKACM.mjs → resolver-NH34HTB6.mjs} +27 -17
- package/dist/{sprint-4VHDLGFN.mjs → sprint-UHYXSEBJ.mjs} +8 -5
- package/dist/{wizard-LRELAN2J.mjs → wizard-MCDDXLGE.mjs} +45 -48
- package/package.json +2 -1
- package/dist/create-MG7E7PLQ.mjs +0 -10
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
unwrapOrThrow
|
|
4
|
+
} from "./chunk-OEUJDSHY.mjs";
|
|
2
5
|
import {
|
|
3
6
|
ConfigSchema,
|
|
4
|
-
FileNotFoundError,
|
|
5
7
|
SprintSchema,
|
|
6
8
|
TasksSchema,
|
|
7
|
-
ValidationError,
|
|
8
9
|
appendToFile,
|
|
9
10
|
assertSafeCwd,
|
|
10
11
|
ensureDir,
|
|
@@ -20,7 +21,14 @@ import {
|
|
|
20
21
|
readValidatedJson,
|
|
21
22
|
removeDir,
|
|
22
23
|
writeValidatedJson
|
|
23
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-W3TY22IS.mjs";
|
|
25
|
+
import {
|
|
26
|
+
LockError,
|
|
27
|
+
NoCurrentSprintError,
|
|
28
|
+
SprintNotFoundError,
|
|
29
|
+
SprintStatusError,
|
|
30
|
+
StorageError
|
|
31
|
+
} from "./chunk-EDJX7TT6.mjs";
|
|
24
32
|
import {
|
|
25
33
|
log
|
|
26
34
|
} from "./chunk-QBXHAXHI.mjs";
|
|
@@ -36,10 +44,10 @@ async function getConfig() {
|
|
|
36
44
|
if (!await fileExists(configPath)) {
|
|
37
45
|
return DEFAULT_CONFIG;
|
|
38
46
|
}
|
|
39
|
-
return readValidatedJson(configPath, ConfigSchema);
|
|
47
|
+
return unwrapOrThrow(await readValidatedJson(configPath, ConfigSchema));
|
|
40
48
|
}
|
|
41
49
|
async function saveConfig(config) {
|
|
42
|
-
await writeValidatedJson(getConfigPath(), config, ConfigSchema);
|
|
50
|
+
unwrapOrThrow(await writeValidatedJson(getConfigPath(), config, ConfigSchema));
|
|
43
51
|
}
|
|
44
52
|
async function getCurrentSprint() {
|
|
45
53
|
const config = await getConfig();
|
|
@@ -91,14 +99,7 @@ import { execSync } from "child_process";
|
|
|
91
99
|
// src/utils/file-lock.ts
|
|
92
100
|
import { mkdir, readFile, unlink, writeFile } from "fs/promises";
|
|
93
101
|
import { dirname } from "path";
|
|
94
|
-
|
|
95
|
-
lockPath;
|
|
96
|
-
constructor(message, lockPath) {
|
|
97
|
-
super(message);
|
|
98
|
-
this.name = "LockAcquisitionError";
|
|
99
|
-
this.lockPath = lockPath;
|
|
100
|
-
}
|
|
101
|
-
};
|
|
102
|
+
import { Result } from "typescript-result";
|
|
102
103
|
var parsed = parseInt(process.env["RALPHCTL_LOCK_TIMEOUT_MS"] ?? "", 10);
|
|
103
104
|
var LOCK_TIMEOUT_MS = parsed > 0 && parsed <= 36e5 ? parsed : 3e4;
|
|
104
105
|
var RETRY_DELAY_MS = 50;
|
|
@@ -138,12 +139,12 @@ async function acquireLock(filePath) {
|
|
|
138
139
|
try {
|
|
139
140
|
await mkdir(dirname(lockPath), { recursive: true });
|
|
140
141
|
await writeFile(lockPath, JSON.stringify(lockInfo), { flag: "wx", mode: 384 });
|
|
141
|
-
return async () => {
|
|
142
|
+
return Result.ok(async () => {
|
|
142
143
|
try {
|
|
143
144
|
await unlink(lockPath);
|
|
144
145
|
} catch {
|
|
145
146
|
}
|
|
146
|
-
};
|
|
147
|
+
});
|
|
147
148
|
} catch (err) {
|
|
148
149
|
if (err instanceof Error && "code" in err && err.code === "EEXIST") {
|
|
149
150
|
if (await isLockStale(lockPath)) {
|
|
@@ -157,15 +158,26 @@ async function acquireLock(filePath) {
|
|
|
157
158
|
await sleep(RETRY_DELAY_MS);
|
|
158
159
|
continue;
|
|
159
160
|
}
|
|
160
|
-
|
|
161
|
+
return Result.error(
|
|
162
|
+
new LockError(
|
|
163
|
+
`Failed to acquire lock: ${err instanceof Error ? err.message : String(err)}`,
|
|
164
|
+
lockPath,
|
|
165
|
+
err instanceof Error ? err : void 0
|
|
166
|
+
)
|
|
167
|
+
);
|
|
161
168
|
}
|
|
162
169
|
}
|
|
163
|
-
|
|
170
|
+
return Result.error(new LockError(`Failed to acquire lock after ${String(MAX_RETRIES)} retries`, lockPath));
|
|
164
171
|
}
|
|
165
172
|
async function withFileLock(filePath, fn) {
|
|
166
|
-
const
|
|
173
|
+
const lockResult = await acquireLock(filePath);
|
|
174
|
+
if (!lockResult.ok) {
|
|
175
|
+
return lockResult;
|
|
176
|
+
}
|
|
177
|
+
const release = lockResult.value;
|
|
167
178
|
try {
|
|
168
|
-
|
|
179
|
+
const value = await fn();
|
|
180
|
+
return Result.ok(value);
|
|
169
181
|
} finally {
|
|
170
182
|
await release();
|
|
171
183
|
}
|
|
@@ -188,9 +200,11 @@ ${projectMarker}${message}
|
|
|
188
200
|
|
|
189
201
|
`;
|
|
190
202
|
const progressPath = getProgressFilePath(id);
|
|
191
|
-
await withFileLock(progressPath, async () => {
|
|
192
|
-
await appendToFile(progressPath, entry);
|
|
203
|
+
const lockResult = await withFileLock(progressPath, async () => {
|
|
204
|
+
const appendResult = await appendToFile(progressPath, entry);
|
|
205
|
+
if (!appendResult.ok) throw appendResult.error;
|
|
193
206
|
});
|
|
207
|
+
if (!lockResult.ok) throw lockResult.error;
|
|
194
208
|
}
|
|
195
209
|
function isExecError(err) {
|
|
196
210
|
return err instanceof Error && typeof err["status"] === "number";
|
|
@@ -251,18 +265,19 @@ async function logBaselines(options) {
|
|
|
251
265
|
lines.push("");
|
|
252
266
|
lines.push("---");
|
|
253
267
|
lines.push("");
|
|
254
|
-
await appendToFile(getProgressFilePath(sprintId), lines.join("\n"));
|
|
268
|
+
const appendResult = await appendToFile(getProgressFilePath(sprintId), lines.join("\n"));
|
|
269
|
+
if (!appendResult.ok) throw appendResult.error;
|
|
255
270
|
}
|
|
256
271
|
async function getProgress(sprintId) {
|
|
257
272
|
const id = await resolveSprintId(sprintId);
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
if (err instanceof FileNotFoundError) {
|
|
273
|
+
const result = await readTextFile(getProgressFilePath(id));
|
|
274
|
+
if (!result.ok) {
|
|
275
|
+
if (result.error instanceof StorageError && result.error.cause?.code === "ENOENT") {
|
|
262
276
|
return "";
|
|
263
277
|
}
|
|
264
|
-
throw
|
|
278
|
+
throw result.error;
|
|
265
279
|
}
|
|
280
|
+
return result.value;
|
|
266
281
|
}
|
|
267
282
|
function summarizeProgressForContext(progress, projectPath, maxEntries = 3) {
|
|
268
283
|
const filtered = filterProgressByProject(progress, projectPath);
|
|
@@ -323,30 +338,6 @@ function filterProgressByProject(progress, projectPath) {
|
|
|
323
338
|
}
|
|
324
339
|
|
|
325
340
|
// src/store/sprint.ts
|
|
326
|
-
var SprintNotFoundError = class extends Error {
|
|
327
|
-
sprintId;
|
|
328
|
-
constructor(sprintId) {
|
|
329
|
-
super(`Sprint not found: ${sprintId}`);
|
|
330
|
-
this.name = "SprintNotFoundError";
|
|
331
|
-
this.sprintId = sprintId;
|
|
332
|
-
}
|
|
333
|
-
};
|
|
334
|
-
var SprintStatusError = class extends Error {
|
|
335
|
-
currentStatus;
|
|
336
|
-
operation;
|
|
337
|
-
constructor(message, currentStatus, operation) {
|
|
338
|
-
super(message);
|
|
339
|
-
this.name = "SprintStatusError";
|
|
340
|
-
this.currentStatus = currentStatus;
|
|
341
|
-
this.operation = operation;
|
|
342
|
-
}
|
|
343
|
-
};
|
|
344
|
-
var NoCurrentSprintError = class extends Error {
|
|
345
|
-
constructor() {
|
|
346
|
-
super("No sprint specified and no current sprint set.");
|
|
347
|
-
this.name = "NoCurrentSprintError";
|
|
348
|
-
}
|
|
349
|
-
};
|
|
350
341
|
function assertSprintStatus(sprint, allowedStatuses, operation) {
|
|
351
342
|
if (!allowedStatuses.includes(sprint.status)) {
|
|
352
343
|
const statusText = allowedStatuses.join(" or ");
|
|
@@ -391,15 +382,21 @@ async function createSprint(name) {
|
|
|
391
382
|
};
|
|
392
383
|
const sprintDir = getSprintDir(id);
|
|
393
384
|
await ensureDir(sprintDir);
|
|
394
|
-
await writeValidatedJson(getSprintFilePath(id), sprint, SprintSchema);
|
|
395
|
-
|
|
396
|
-
await
|
|
385
|
+
const writeSprintResult = await writeValidatedJson(getSprintFilePath(id), sprint, SprintSchema);
|
|
386
|
+
if (!writeSprintResult.ok) throw writeSprintResult.error;
|
|
387
|
+
const writeTasksResult = await writeValidatedJson(getTasksFilePath(id), [], TasksSchema);
|
|
388
|
+
if (!writeTasksResult.ok) throw writeTasksResult.error;
|
|
389
|
+
const appendResult = await appendToFile(
|
|
390
|
+
getProgressFilePath(id),
|
|
391
|
+
`# Sprint: ${displayName}
|
|
397
392
|
|
|
398
393
|
Created: ${now}
|
|
399
394
|
|
|
400
395
|
---
|
|
401
396
|
|
|
402
|
-
`
|
|
397
|
+
`
|
|
398
|
+
);
|
|
399
|
+
if (!appendResult.ok) throw appendResult.error;
|
|
403
400
|
return sprint;
|
|
404
401
|
}
|
|
405
402
|
async function findActiveSprint() {
|
|
@@ -411,25 +408,24 @@ async function getSprint(sprintId) {
|
|
|
411
408
|
if (!await fileExists(sprintPath)) {
|
|
412
409
|
throw new SprintNotFoundError(sprintId);
|
|
413
410
|
}
|
|
414
|
-
|
|
411
|
+
const result = await readValidatedJson(sprintPath, SprintSchema);
|
|
412
|
+
if (!result.ok) throw result.error;
|
|
413
|
+
return result.value;
|
|
415
414
|
}
|
|
416
415
|
async function saveSprint(sprint) {
|
|
417
|
-
await writeValidatedJson(getSprintFilePath(sprint.id), sprint, SprintSchema);
|
|
416
|
+
const result = await writeValidatedJson(getSprintFilePath(sprint.id), sprint, SprintSchema);
|
|
417
|
+
if (!result.ok) throw result.error;
|
|
418
418
|
}
|
|
419
419
|
async function listSprints() {
|
|
420
420
|
const sprintsDir = getSprintsDir();
|
|
421
421
|
const dirs = await listDirs(sprintsDir);
|
|
422
422
|
const sprints = [];
|
|
423
423
|
for (const dir of dirs) {
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
continue;
|
|
430
|
-
}
|
|
431
|
-
throw err;
|
|
432
|
-
}
|
|
424
|
+
const sprintPath = getSprintFilePath(dir);
|
|
425
|
+
if (!await fileExists(sprintPath)) continue;
|
|
426
|
+
const result = await readValidatedJson(sprintPath, SprintSchema);
|
|
427
|
+
if (!result.ok) continue;
|
|
428
|
+
sprints.push(result.value);
|
|
433
429
|
}
|
|
434
430
|
return sprints.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
|
|
435
431
|
}
|
|
@@ -439,7 +435,9 @@ async function activateSprint(sprintId) {
|
|
|
439
435
|
sprint.status = "active";
|
|
440
436
|
sprint.activatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
441
437
|
await saveSprint(sprint);
|
|
442
|
-
const
|
|
438
|
+
const tasksResult = await readValidatedJson(getTasksFilePath(sprintId), TasksSchema);
|
|
439
|
+
if (!tasksResult.ok) throw tasksResult.error;
|
|
440
|
+
const tasks = tasksResult.value;
|
|
443
441
|
const projectPaths = tasks.map((t) => t.projectPath).filter((p) => !!p);
|
|
444
442
|
if (projectPaths.length > 0) {
|
|
445
443
|
await logBaselines({
|
|
@@ -503,9 +501,6 @@ export {
|
|
|
503
501
|
logProgress,
|
|
504
502
|
getProgress,
|
|
505
503
|
summarizeProgressForContext,
|
|
506
|
-
SprintNotFoundError,
|
|
507
|
-
SprintStatusError,
|
|
508
|
-
NoCurrentSprintError,
|
|
509
504
|
assertSprintStatus,
|
|
510
505
|
createSprint,
|
|
511
506
|
findActiveSprint,
|