@dennisrongo/dsh-todo 0.4.0 → 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/README.md +132 -26
- package/lib/bin.js +57 -14
- package/lib/cli.js +57 -14
- package/lib/client.js +217 -34
- package/lib/index.js +421 -11
- package/lib/launch.js +109 -0
- package/lib/scan.js +276 -0
- package/lib/suggest.js +93 -0
- package/lib/typert.host.js +59 -1
- package/package.json +5 -3
package/lib/cli.js
CHANGED
|
@@ -22,6 +22,14 @@ function normalizeLabel(raw) {
|
|
|
22
22
|
const text = raw.replace(/\s+/g, " ").trim().slice(0, MAX_LABEL);
|
|
23
23
|
return text.length > 0 ? text : void 0;
|
|
24
24
|
}
|
|
25
|
+
var RELEASE_LABEL_RE = /^\d+(\.\d+){0,2}$/;
|
|
26
|
+
var SPRINT_LABEL_RE = /^\d+(\.\d+)?$/;
|
|
27
|
+
function normalizeVersionLabel(raw, field) {
|
|
28
|
+
const label = normalizeLabel(raw);
|
|
29
|
+
if (label === void 0) return void 0;
|
|
30
|
+
const pattern = field === "release" ? RELEASE_LABEL_RE : SPRINT_LABEL_RE;
|
|
31
|
+
return pattern.test(label) ? label : void 0;
|
|
32
|
+
}
|
|
25
33
|
var DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
|
|
26
34
|
function normalizeDueDate(raw) {
|
|
27
35
|
if (typeof raw !== "string") return void 0;
|
|
@@ -34,6 +42,8 @@ function normalizeDueDate(raw) {
|
|
|
34
42
|
var MAX_TEXT = 500;
|
|
35
43
|
var MAX_DESC = 5e3;
|
|
36
44
|
var MAX_LABEL = 60;
|
|
45
|
+
var SUGGESTIONS_DIR = ".dsh";
|
|
46
|
+
var SUGGESTIONS_FILE = `${SUGGESTIONS_DIR}/suggestions.json`;
|
|
37
47
|
|
|
38
48
|
// src/db.ts
|
|
39
49
|
var DOT_DSH = ".dsh";
|
|
@@ -76,6 +86,7 @@ function migrateSchema(db) {
|
|
|
76
86
|
add("release", "release TEXT");
|
|
77
87
|
add("sprint", "sprint TEXT");
|
|
78
88
|
add("due_date", "due_date TEXT");
|
|
89
|
+
add("session_id", "session_id TEXT");
|
|
79
90
|
if (addedTitle && columns.has("text")) {
|
|
80
91
|
db.exec("UPDATE todo SET title = text WHERE title IS NULL");
|
|
81
92
|
}
|
|
@@ -91,7 +102,7 @@ function readList(db) {
|
|
|
91
102
|
const updatedAt = Number(db.prepare("SELECT value FROM meta WHERE key = 'updatedAt'").get()?.value ?? 0);
|
|
92
103
|
const rows = db.prepare(
|
|
93
104
|
`SELECT id, title, description, status, priority, release, sprint, due_date,
|
|
94
|
-
created_at, completed_at, archived_at
|
|
105
|
+
session_id, created_at, completed_at, archived_at
|
|
95
106
|
FROM todo ORDER BY position ASC`
|
|
96
107
|
).all();
|
|
97
108
|
const text = (v) => v === null || v === void 0 ? void 0 : String(v);
|
|
@@ -106,6 +117,7 @@ function readList(db) {
|
|
|
106
117
|
...normalizeLabel(row.release) !== void 0 ? { release: normalizeLabel(row.release) } : {},
|
|
107
118
|
...normalizeLabel(row.sprint) !== void 0 ? { sprint: normalizeLabel(row.sprint) } : {},
|
|
108
119
|
...normalizeDueDate(row.due_date) !== void 0 ? { dueDate: normalizeDueDate(row.due_date) } : {},
|
|
120
|
+
...text(row.session_id) !== void 0 ? { sessionId: text(row.session_id) } : {},
|
|
109
121
|
createdAt: Number(row.created_at),
|
|
110
122
|
...row.completed_at !== null && row.completed_at !== void 0 ? { completedAt: Number(row.completed_at) } : {},
|
|
111
123
|
...row.archived_at !== null && row.archived_at !== void 0 ? { archivedAt: Number(row.archived_at) } : {}
|
|
@@ -119,8 +131,8 @@ function writeList(db, items, revision, updatedAt = Date.now()) {
|
|
|
119
131
|
db.prepare("DELETE FROM todo").run();
|
|
120
132
|
const insert = db.prepare(
|
|
121
133
|
`INSERT INTO todo (id, title, description, status, priority, release, sprint, due_date,
|
|
122
|
-
text, done, created_at, completed_at, archived_at, position)
|
|
123
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
134
|
+
session_id, text, done, created_at, completed_at, archived_at, position)
|
|
135
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
124
136
|
);
|
|
125
137
|
items.forEach((item, index) => {
|
|
126
138
|
insert.run(
|
|
@@ -132,6 +144,7 @@ function writeList(db, items, revision, updatedAt = Date.now()) {
|
|
|
132
144
|
item.release ?? null,
|
|
133
145
|
item.sprint ?? null,
|
|
134
146
|
item.dueDate ?? null,
|
|
147
|
+
item.sessionId ?? null,
|
|
135
148
|
item.title,
|
|
136
149
|
item.status === "done" ? 1 : 0,
|
|
137
150
|
item.createdAt,
|
|
@@ -189,10 +202,13 @@ var CliError = class extends Error {
|
|
|
189
202
|
/**
|
|
190
203
|
* @param message - human-readable reason.
|
|
191
204
|
* @param code - process exit code, from {@link EXIT}.
|
|
205
|
+
* @param details - extra machine-readable fields merged into the `--json`
|
|
206
|
+
* error payload, so an agent can correct itself without parsing the sentence.
|
|
192
207
|
*/
|
|
193
|
-
constructor(message, code = EXIT.usage) {
|
|
208
|
+
constructor(message, code = EXIT.usage, details = {}) {
|
|
194
209
|
super(message);
|
|
195
210
|
this.code = code;
|
|
211
|
+
this.details = details;
|
|
196
212
|
this.name = "CliError";
|
|
197
213
|
}
|
|
198
214
|
};
|
|
@@ -210,6 +226,16 @@ function oneOf(options, key, allowed) {
|
|
|
210
226
|
}
|
|
211
227
|
return raw;
|
|
212
228
|
}
|
|
229
|
+
function assertLabel(field, raw) {
|
|
230
|
+
if (raw === void 0 || raw === "") return;
|
|
231
|
+
if (normalizeVersionLabel(raw, field) !== void 0) return;
|
|
232
|
+
const shape = field === "release" ? "a version number like 1.5 or 0.5.1 (up to three numbers)" : "a decimal number like 1.5 (one dot at most)";
|
|
233
|
+
throw new CliError(
|
|
234
|
+
`--${field} must be ${shape} (got "${raw}") \u2014 nothing was saved`,
|
|
235
|
+
EXIT.usage,
|
|
236
|
+
{ field, expected: shape, got: raw }
|
|
237
|
+
);
|
|
238
|
+
}
|
|
213
239
|
function resolveWorkspace(options, cwd) {
|
|
214
240
|
return resolve2(str(options, "workspace") ?? cwd);
|
|
215
241
|
}
|
|
@@ -272,6 +298,7 @@ function formatItem(item) {
|
|
|
272
298
|
if (item.release) meta.push(`release=${item.release}`);
|
|
273
299
|
if (item.sprint) meta.push(`sprint=${item.sprint}`);
|
|
274
300
|
if (item.dueDate) meta.push(`due=${item.dueDate}`);
|
|
301
|
+
if (item.sessionId) meta.push(`session=${item.sessionId}`);
|
|
275
302
|
if (isArchived(item)) meta.push("archived");
|
|
276
303
|
return bits.join(" ") + (meta.length ? ` (${meta.join(" ")})` : "");
|
|
277
304
|
}
|
|
@@ -297,9 +324,11 @@ Options
|
|
|
297
324
|
|
|
298
325
|
--status <s> ${STATUSES.join("|")}
|
|
299
326
|
--priority <p> ${PRIORITIES.join("|")}
|
|
300
|
-
--release <
|
|
301
|
-
--sprint <
|
|
327
|
+
--release <n[.n[.n]]> e.g. 1.5 or 0.5.1 (empty string clears)
|
|
328
|
+
--sprint <n[.n]> e.g. 24 (empty string clears)
|
|
302
329
|
--due <YYYY-MM-DD> Calendar day (empty string clears)
|
|
330
|
+
--session <id> Harness session working the task (update only;
|
|
331
|
+
empty string clears)
|
|
303
332
|
--description <text> Body text (empty string clears)
|
|
304
333
|
--title <text> Rename (update only)
|
|
305
334
|
|
|
@@ -309,8 +338,8 @@ Ids may be given as any unambiguous prefix.
|
|
|
309
338
|
|
|
310
339
|
Examples
|
|
311
340
|
dsh-todo list --open --json
|
|
312
|
-
dsh-todo add "Fix token refresh" --priority p0 --release
|
|
313
|
-
dsh-todo update t1a2 --status in-progress --sprint
|
|
341
|
+
dsh-todo add "Fix token refresh" --priority p0 --release 1.5 --due 2026-03-14
|
|
342
|
+
dsh-todo update t1a2 --status in-progress --sprint 24
|
|
314
343
|
dsh-todo done t1a2
|
|
315
344
|
`;
|
|
316
345
|
function run(parsed, cwd, now = Date.now, rand = Math.random) {
|
|
@@ -347,6 +376,7 @@ function run(parsed, cwd, now = Date.now, rand = Math.random) {
|
|
|
347
376
|
`release ${item.release ?? "-"}`,
|
|
348
377
|
`sprint ${item.sprint ?? "-"}`,
|
|
349
378
|
`due ${item.dueDate ?? "-"}`,
|
|
379
|
+
`session ${item.sessionId ?? "-"}`,
|
|
350
380
|
`created ${new Date(item.createdAt).toISOString()}`,
|
|
351
381
|
...item.completedAt ? [`completed ${new Date(item.completedAt).toISOString()}`] : [],
|
|
352
382
|
...item.archivedAt ? [`archived ${new Date(item.archivedAt).toISOString()}`] : [],
|
|
@@ -358,8 +388,12 @@ function run(parsed, cwd, now = Date.now, rand = Math.random) {
|
|
|
358
388
|
const title = positional.join(" ").trim();
|
|
359
389
|
if (!title) throw new CliError("add needs a title");
|
|
360
390
|
const description = str(options, "description");
|
|
361
|
-
const
|
|
362
|
-
const
|
|
391
|
+
const releaseRaw = str(options, "release");
|
|
392
|
+
const sprintRaw = str(options, "sprint");
|
|
393
|
+
assertLabel("release", releaseRaw);
|
|
394
|
+
assertLabel("sprint", sprintRaw);
|
|
395
|
+
const release = normalizeVersionLabel(releaseRaw, "release");
|
|
396
|
+
const sprint = normalizeVersionLabel(sprintRaw, "sprint");
|
|
363
397
|
const dueRaw = str(options, "due");
|
|
364
398
|
if (dueRaw !== void 0 && dueRaw !== "" && normalizeDueDate(dueRaw) === void 0) {
|
|
365
399
|
throw new CliError(`--due must be a real calendar date as YYYY-MM-DD (got "${dueRaw}")`);
|
|
@@ -388,10 +422,13 @@ function run(parsed, cwd, now = Date.now, rand = Math.random) {
|
|
|
388
422
|
const release = str(options, "release");
|
|
389
423
|
const sprint = str(options, "sprint");
|
|
390
424
|
const due = str(options, "due");
|
|
425
|
+
const session = str(options, "session");
|
|
391
426
|
if (due !== void 0 && due !== "" && normalizeDueDate(due) === void 0) {
|
|
392
427
|
throw new CliError(`--due must be a real calendar date as YYYY-MM-DD (got "${due}")`);
|
|
393
428
|
}
|
|
394
|
-
|
|
429
|
+
assertLabel("release", release);
|
|
430
|
+
assertLabel("sprint", sprint);
|
|
431
|
+
if (status === void 0 && priority === void 0 && title === void 0 && description === void 0 && release === void 0 && sprint === void 0 && due === void 0 && session === void 0) {
|
|
395
432
|
throw new CliError("update needs at least one field to change");
|
|
396
433
|
}
|
|
397
434
|
let updated;
|
|
@@ -413,7 +450,7 @@ function run(parsed, cwd, now = Date.now, rand = Math.random) {
|
|
|
413
450
|
}
|
|
414
451
|
for (const [key, raw] of [["release", release], ["sprint", sprint]]) {
|
|
415
452
|
if (raw === void 0) continue;
|
|
416
|
-
const label =
|
|
453
|
+
const label = normalizeVersionLabel(raw, key);
|
|
417
454
|
if (label !== void 0) next[key] = label;
|
|
418
455
|
else delete next[key];
|
|
419
456
|
}
|
|
@@ -422,6 +459,10 @@ function run(parsed, cwd, now = Date.now, rand = Math.random) {
|
|
|
422
459
|
if (value !== void 0) next.dueDate = value;
|
|
423
460
|
else delete next.dueDate;
|
|
424
461
|
}
|
|
462
|
+
if (session !== void 0) {
|
|
463
|
+
if (session) next.sessionId = session.slice(0, MAX_LABEL);
|
|
464
|
+
else delete next.sessionId;
|
|
465
|
+
}
|
|
425
466
|
updated = next;
|
|
426
467
|
return next;
|
|
427
468
|
});
|
|
@@ -487,12 +528,14 @@ function main(argv, cwd = process.cwd()) {
|
|
|
487
528
|
const wantsJson = parsed.options.json === true;
|
|
488
529
|
try {
|
|
489
530
|
const outcome = run(parsed, cwd);
|
|
490
|
-
|
|
531
|
+
const json = outcome.json !== null && typeof outcome.json === "object" && !Array.isArray(outcome.json) ? { ok: true, ...outcome.json } : { ok: true, result: outcome.json };
|
|
532
|
+
console.log(wantsJson ? JSON.stringify(json, null, 2) : outcome.text);
|
|
491
533
|
return EXIT.ok;
|
|
492
534
|
} catch (error) {
|
|
493
535
|
const message = error instanceof Error ? error.message : String(error);
|
|
494
536
|
const code = error instanceof CliError ? error.code : 1;
|
|
495
|
-
|
|
537
|
+
const details = error instanceof CliError ? error.details : {};
|
|
538
|
+
if (wantsJson) console.log(JSON.stringify({ ok: false, error: message, code, ...details }, null, 2));
|
|
496
539
|
else console.error(`dsh-todo: ${message}`);
|
|
497
540
|
return code;
|
|
498
541
|
}
|