project-mgr 0.4.0 → 0.5.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/CHANGELOG.md +36 -0
- package/README.md +64 -14
- package/README_CN.md +52 -12
- package/dist/cli.js +111 -11
- package/dist/db.d.ts +37 -3
- package/dist/db.js +476 -52
- package/dist/domain.d.ts +13 -3
- package/dist/domain.js +26 -4
- package/dist/server.js +23 -14
- package/dist/web/script.js +21 -2
- package/dist/web/styles.d.ts +1 -1
- package/dist/web/styles.js +2 -0
- package/package.json +1 -1
- package/skills/project-mgr/SKILL.md +38 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,41 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.5.1 - 2026-11-02
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- Export `openWork(db, project?)` and expose it through `pmgr next` / `pmgr status next`, combining open pins, unpinned Todos, and unconverted legacy steps.
|
|
8
|
+
- Add live `openWork` to CLI `current --json` and show open work with pin markers in text mode. Historical reads retain their previous shape.
|
|
9
|
+
- Add repeatable `add --done-todo` / `--cancel-todo` for atomic closures and `todo-convert` by entry/index, project, or all current legacy sources.
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- **CLI behavior change:** plain `add` now keeps open pins; `--next` and `--next-todo` append (first-occurrence uid order). Use `--replace-next` to replace or clear pins without changing Todo status. Domain `addStatus` still defaults to replace; imports/exports stay unchanged. Fixed-uid re-pin of an already-carried pin remains a documented L2 limitation for direct API callers.
|
|
14
|
+
- Reuse prior open Todos for exact trimmed `--next` titles, including unassigned Todos already pinned on the project, preferring pinned work, then the oldest Todo, while preserving provenance. Duplicate new titles in one write still create separate Todos.
|
|
15
|
+
- Make `pmgr next --json` the daily checklist and guide agents to keep, finish, or explicitly retire work before adding new actions. Legacy carry preserves unconverted strings until explicit conversion. No automatic cancellation.
|
|
16
|
+
- CLI `current` builds open work in one scan and groups it per project instead of re-querying each project.
|
|
17
|
+
- Translate the new Todo/open-work section of `README_CN.md` into Chinese and document the L2 fixed-UID retry limitation in the architecture notes.
|
|
18
|
+
|
|
19
|
+
## 0.5.0 - 2026-10-29
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
|
|
23
|
+
- Store new snapshot next work as `nextTodoUids` references. `add --next` creates Todos and those refs in one transaction; `add --next-todo` pins existing UIDs.
|
|
24
|
+
- Reject mixed prose and refs, duplicate UIDs, combining `--next` with `--next-todo`, cross-project refs, and project changes that conflict with any historical reference.
|
|
25
|
+
- Export version 4 carries refs and every Todo they need, including unassigned Todos referenced by a project export.
|
|
26
|
+
- `todos --status open` lists open work. With `--project`, current snapshot pins are ordered first.
|
|
27
|
+
- Dashboard activity history and project cards show referenced Todos with live status.
|
|
28
|
+
|
|
29
|
+
### Changed
|
|
30
|
+
|
|
31
|
+
- New snapshots no longer persist next-step prose. Legacy string `nextSteps` stay readable. Per-index conversion does not rewrite history; completion copies the full string list until every source index has a Todo, then materializes refs without creating Todos or changing their status.
|
|
32
|
+
|
|
33
|
+
### Fixed
|
|
34
|
+
|
|
35
|
+
- Detach the dashboard browser launch so a long-lived browser does not keep the process alive.
|
|
36
|
+
- Reject dangling and cross-project `nextTodoUids` from `importStatuses`, and require imported refs to name Todos in the same payload. Entry-only imports declare no Todos, so they cannot reference existing rows.
|
|
37
|
+
- Treat a fixed-UID retry of `--next` text as the same write after it has been stored as refs. Portable imports still require an exact snapshot match.
|
|
38
|
+
|
|
3
39
|
## 0.4.0 - 2026-10-28
|
|
4
40
|
|
|
5
41
|
### Added
|
package/README.md
CHANGED
|
@@ -225,7 +225,7 @@ pnpm start projects --query "mvp" --json
|
|
|
225
225
|
|
|
226
226
|
## Bulk import
|
|
227
227
|
|
|
228
|
-
`import` accepts a JSON object, a JSON array, or a JSONL file containing one object per line. Every record is validated before the whole batch is written in one SQLite transaction; any failure rolls back the entire batch.
|
|
228
|
+
`import` accepts a JSON object, a JSON array, or a JSONL file containing one object per line. Every record is validated before the whole batch is written in one SQLite transaction; any failure rolls back the entire batch. The example below is a legacy entry: string `nextSteps` are kept and do not create Todos. New snapshots created with `add` store `nextTodoUids` instead.
|
|
229
229
|
|
|
230
230
|
```json
|
|
231
231
|
{
|
|
@@ -336,22 +336,72 @@ Independently migrated copies of legacy data receive different UUIDs. Cross-impo
|
|
|
336
336
|
|
|
337
337
|
### Todos and dashboard actions
|
|
338
338
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
339
|
+
Todos own actionable work. A new status snapshot stores ordered `nextTodoUids`
|
|
340
|
+
and does not persist next-step prose. CLI `add` now carries open pins by default;
|
|
341
|
+
plain `add` no longer clears them, and `--next` appends new work. An exact,
|
|
342
|
+
case-sensitive title (after trimming) reuses a prior open Todo in that project
|
|
343
|
+
or an unassigned Todo already pinned on it, preferring a current pin, then the oldest Todo. Provenance stays unchanged.
|
|
344
|
+
Duplicate new titles within one write still create separate Todos.
|
|
345
|
+
Use `--next-todo` to pin an existing UID explicitly.
|
|
346
|
+
Do not combine `--next` with `--next-todo`. Duplicate UIDs in one list are rejected.
|
|
345
347
|
|
|
346
348
|
```sh
|
|
347
|
-
pmgr
|
|
349
|
+
pmgr add --project demo --status active --summary "Review queued" \
|
|
350
|
+
--next "Collect logs" --next "Write the note" --source agent --json
|
|
348
351
|
pmgr todo-add --title "Unassigned idea" --json
|
|
349
|
-
pmgr
|
|
352
|
+
pmgr add --project demo --status active --summary "Pin the idea" \
|
|
353
|
+
--next-todo <todo-uuid> --source agent --json
|
|
354
|
+
pmgr next --project demo --json
|
|
350
355
|
pmgr todo-update --uid <todo-uuid> --status completed --json
|
|
356
|
+
pmgr todo-update --uid <todo-uuid> --status cancelled --json
|
|
351
357
|
```
|
|
352
358
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
359
|
+
The canonical daily checklist is `pmgr next --json` (also `pmgr status next`).
|
|
360
|
+
Do not combine `nextSteps`, `nextTodos`, and `todos` by hand. Before every `add`,
|
|
361
|
+
run `pmgr next --project P --json` and decide each item: keep it (do nothing;
|
|
362
|
+
existing pins carry), finish it with `--done-todo <uid>`, or retire superseded
|
|
363
|
+
work with `--cancel-todo <uid>`. Both close flags are repeatable and run in the
|
|
364
|
+
same transaction as the snapshot. Unpinned work remains visible in the backlog.
|
|
365
|
+
Use `--next` only for new actions, never status descriptions; put blockers in
|
|
366
|
+
`--blocker`. No Todo is automatically cancelled by age or similarity.
|
|
367
|
+
|
|
368
|
+
`--replace-next` restores replacement behavior: without next work it unpins
|
|
369
|
+
everything; with next work it pins only those UIDs. It never closes Todos.
|
|
370
|
+
The domain `addStatus` API still defaults to `nextMode: "replace"`; CLI `add`
|
|
371
|
+
passes `"carry"` unless `--replace-next` is present. Imports stay strict.
|
|
372
|
+
|
|
373
|
+
Legacy snapshots retain strings until explicitly converted. A plain carry-mode
|
|
374
|
+
`add` preserves the full legacy list and `nextStepsSourceUid` while any index is
|
|
375
|
+
unconverted; adding new next work then requires conversion or `--replace-next`.
|
|
376
|
+
Conversion is idempotent and appends no snapshot:
|
|
377
|
+
|
|
378
|
+
```sh
|
|
379
|
+
pmgr todo-convert --entry <id-or-uid> --index 0 --json
|
|
380
|
+
pmgr todo-convert --project demo --json
|
|
381
|
+
# Back up before an explicit, user-run conversion of every current legacy source.
|
|
382
|
+
pmgr export --format json > backup.json
|
|
383
|
+
pmgr todo-convert --all --json
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
Once all source indices have Todos, the next carry-mode `add` materializes refs
|
|
387
|
+
in index order, filtering out closed Todos. Historical snapshots stay unchanged.
|
|
388
|
+
`next` combines converted Todos and unconverted strings without duplicates.
|
|
389
|
+
|
|
390
|
+
`current --json` adds live `openWork` for each project's checklist, while its
|
|
391
|
+
stored `nextSteps`, `nextTodoUids`, and live `nextTodos` retain their previous
|
|
392
|
+
meaning, including closed pins. Text `current` prints open work with `[pinned]`
|
|
393
|
+
markers. `history`, `at`, and `getEntry` do not gain `openWork`; `history` text
|
|
394
|
+
and `completeActivity` follow-up copying stay unchanged. `at` returns stored
|
|
395
|
+
snapshots without live Todo resolution.
|
|
396
|
+
|
|
397
|
+
A snapshot may reference Todos that are unassigned or already in that project.
|
|
398
|
+
If a Todo appears in any `nextTodoUids`, or is linked to a source entry, its
|
|
399
|
+
project cannot be changed. Unpinning the current snapshot does not lift that lock.
|
|
400
|
+
|
|
401
|
+
Exports that contain refs use version 4 and include every referenced Todo.
|
|
402
|
+
Version 3 remains the Todo bundle without refs. Version 2 and legacy imports
|
|
403
|
+
do not invent open work. Dangling, duplicate, mixed, or cross-project refs fail
|
|
404
|
+
the whole import. Dashboard writes stay same-origin localhost JSON. Activity
|
|
405
|
+
cards still offer **Mark completed**, which appends a follow-up and does not
|
|
406
|
+
complete the project or its Todos. Adding a Todo from the dashboard does not
|
|
407
|
+
pin it; pinning requires a new snapshot.
|
package/README_CN.md
CHANGED
|
@@ -223,7 +223,7 @@ pnpm start projects --query "mvp" --json
|
|
|
223
223
|
|
|
224
224
|
## 批量导入
|
|
225
225
|
|
|
226
|
-
`import` 接受一个 JSON 对象、JSON 数组,或每行一个对象的 JSONL 文件。所有记录先校验,再在同一个 SQLite
|
|
226
|
+
`import` 接受一个 JSON 对象、JSON 数组,或每行一个对象的 JSONL 文件。所有记录先校验,再在同一个 SQLite 事务中写入;任一记录失败时整批回滚。下面的示例是旧格式条目:字符串 `nextSteps` 会保留,且不会创建待办。用 `add` 写入的新快照会改为存储 `nextTodoUids`。
|
|
227
227
|
|
|
228
228
|
```json
|
|
229
229
|
{
|
|
@@ -334,20 +334,60 @@ pmgr revoke-completion --entry 12 --by 18 --reason "验收仍未完成" --source
|
|
|
334
334
|
|
|
335
335
|
### 待办与仪表板操作
|
|
336
336
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
337
|
+
待办是具体工作的来源。新的状态快照按顺序存储 `nextTodoUids`,不再把下一步原文写入快照。
|
|
338
|
+
CLI `add` 现在默认沿用仍处于 open 的钉住项:不带下一步的 `add` 不再清空它们,`--next` 会追加新工作。
|
|
339
|
+
去掉首尾空白后,标题与该项目已有的 open 待办(或已钉在该项目上的未归属待办)完全一致(区分大小写)时,会复用那个待办:优先已钉住的,其次最早创建的。来源信息保持不变。
|
|
340
|
+
同一次写入里重复的新标题仍会各自创建待办。要显式钉住已有 UID,请传 `--next-todo`。
|
|
341
|
+
不要把 `--next` 和 `--next-todo` 写在同一次 `add` 里。同一列表里的重复 UID 会被拒绝。
|
|
341
342
|
|
|
342
343
|
```sh
|
|
343
|
-
pmgr
|
|
344
|
+
pmgr add --project demo --status active --summary "已排队复查" \
|
|
345
|
+
--next "收集日志" --next "写说明" --source agent --json
|
|
344
346
|
pmgr todo-add --title "未归属的想法" --json
|
|
345
|
-
pmgr
|
|
347
|
+
pmgr add --project demo --status active --summary "钉住这个想法" \
|
|
348
|
+
--next-todo <todo-uuid> --source agent --json
|
|
349
|
+
pmgr next --project demo --json
|
|
346
350
|
pmgr todo-update --uid <todo-uuid> --status completed --json
|
|
351
|
+
pmgr todo-update --uid <todo-uuid> --status cancelled --json
|
|
347
352
|
```
|
|
348
353
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
+
每日清单统一使用 `pmgr next --json`(也可写作 `pmgr status next`),不要手动拼接 `nextSteps`、`nextTodos` 和 `todos`。
|
|
355
|
+
每次 `add` 之前先运行 `pmgr next --project P --json`,逐条决定:保留(什么都不做,已钉住的会自动沿用)、用 `--done-todo <uid>` 完成,或用 `--cancel-todo <uid>` 退役被取代的工作。
|
|
356
|
+
这两个关闭参数可重复,并与快照在同一事务中执行。未钉住的工作仍会作为积压项显示。
|
|
357
|
+
`--next` 只写新的行动,不写状态描述;阻塞写进 `--blocker`。系统不会按时间或相似度自动取消任何待办。
|
|
358
|
+
|
|
359
|
+
`--replace-next` 恢复替换语义:不带下一步时取消全部钉住,带下一步时只钉住这些 UID。它不会关闭待办。
|
|
360
|
+
领域层 `addStatus` 默认仍是 `nextMode: "replace"`;CLI `add` 除非带 `--replace-next`,否则传 `"carry"`。导入仍保持严格校验。
|
|
361
|
+
已知限制:领域 API 用固定 UID 重新钉住一个已沿用的待办时,原样重试会报 `conflicting entry uid`。CLI 不传固定 UID,`--external-ref` 重放不受影响。
|
|
362
|
+
|
|
363
|
+
旧快照保留字符串 `nextSteps`,并省略 `nextTodoUids`,直到显式转换。
|
|
364
|
+
只要还有来源下标未转换,沿用模式的 `add`(以及完成活动时的后续快照)会复制完整字符串列表和 `nextStepsSourceUid`;此时要写入新的下一步,需先转换或使用 `--replace-next`。
|
|
365
|
+
转换是幂等的,不会追加快照:
|
|
366
|
+
|
|
367
|
+
```sh
|
|
368
|
+
pmgr todo-convert --entry <id-or-uid> --index 0 --json
|
|
369
|
+
pmgr todo-convert --project demo --json
|
|
370
|
+
# 对所有当前旧快照做显式转换前,先备份;仅在用户要求时执行。
|
|
371
|
+
pmgr export --format json > backup.json
|
|
372
|
+
pmgr todo-convert --all --json
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
每个下标都已有待办之后,下一次沿用模式的 `add` 会按原下标顺序写入引用,并过滤掉已关闭的待办。历史快照保持不变。
|
|
376
|
+
`next` 会合并已转换的待办和未转换的字符串,不会重复。
|
|
377
|
+
|
|
378
|
+
`current --json` 为每个项目增加实时的 `openWork` 清单;存储的 `nextSteps`、`nextTodoUids` 以及实时的 `nextTodos` 含义不变,仍包含已关闭的钉住项。
|
|
379
|
+
文本模式的 `current` 会列出未完成工作,并用 `[pinned]` 标出钉住项。
|
|
380
|
+
`history`、`at` 和 `getEntry` 不带 `openWork`;`history` 文本输出和完成活动的后续快照复制规则不变。
|
|
381
|
+
`at` 返回当时存储的快照,不附加实时待办状态。
|
|
382
|
+
|
|
383
|
+
快照只能引用未归属的待办,或已经属于该项目的待办。
|
|
384
|
+
待办一旦出现在任意 `nextTodoUids` 中,或关联了来源动态,就不能再改项目。
|
|
385
|
+
从当前快照拿掉引用,也不会解除这个限制。
|
|
386
|
+
|
|
387
|
+
带引用的导出使用 version 4,并包含被引用的待办。
|
|
388
|
+
没有引用、但有待办的导出仍是 version 3。
|
|
389
|
+
version 2 和旧版导入不会凭空创建待办。
|
|
390
|
+
悬空、重复、混用或跨项目引用会使整批导入失败。
|
|
391
|
+
仪表板写入仍只接受同源的 localhost JSON 请求。
|
|
392
|
+
动态卡片上的 **标为已完成** 会追加一条后续动态,既不完成项目,也不完成待办。
|
|
393
|
+
在仪表板里新增待办不会把它钉进当前下一步;钉住需要再写一条快照。
|
package/dist/cli.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { readFileSync, realpathSync } from "node:fs";
|
|
3
3
|
import { basename } from "node:path";
|
|
4
4
|
import { pathToFileURL } from "node:url";
|
|
5
|
-
import { addStatus, createTodo, listTodos, updateTodo, at, current, history, importLedger, exportLedger, changeCompletion, listProjects, openLedger, parseTime, setProjectMetadata, startServer, ValidationError, } from "./index.js";
|
|
5
|
+
import { addStatus, createTodo, convertNextStep, getEntry, openWork, openWorkByProject, listTodos, updateTodo, at, current, history, importLedger, exportLedger, changeCompletion, listProjects, openLedger, parseTime, setProjectMetadata, startServer, ValidationError, } from "./index.js";
|
|
6
6
|
const booleanFlags = new Set([
|
|
7
7
|
"json",
|
|
8
8
|
"clear-description",
|
|
@@ -11,8 +11,19 @@ const booleanFlags = new Set([
|
|
|
11
11
|
"no-open",
|
|
12
12
|
"help",
|
|
13
13
|
"version",
|
|
14
|
+
"replace-next",
|
|
15
|
+
"all",
|
|
16
|
+
]);
|
|
17
|
+
const repeats = new Set([
|
|
18
|
+
"next",
|
|
19
|
+
"next-todo",
|
|
20
|
+
"blocker",
|
|
21
|
+
"decision",
|
|
22
|
+
"tag",
|
|
23
|
+
"completes",
|
|
24
|
+
"done-todo",
|
|
25
|
+
"cancel-todo",
|
|
14
26
|
]);
|
|
15
|
-
const repeats = new Set(["next", "blocker", "decision", "tag", "completes"]);
|
|
16
27
|
function parse(argv) {
|
|
17
28
|
const command = [];
|
|
18
29
|
const flags = Object.create(null);
|
|
@@ -58,6 +69,10 @@ const allowedFlags = new Map([
|
|
|
58
69
|
"progress",
|
|
59
70
|
"blocker",
|
|
60
71
|
"next",
|
|
72
|
+
"next-todo",
|
|
73
|
+
"replace-next",
|
|
74
|
+
"done-todo",
|
|
75
|
+
"cancel-todo",
|
|
61
76
|
"decision",
|
|
62
77
|
"tag",
|
|
63
78
|
"at",
|
|
@@ -68,7 +83,9 @@ const allowedFlags = new Map([
|
|
|
68
83
|
]),
|
|
69
84
|
],
|
|
70
85
|
["todo-add", new Set(["title", "project", "entry", "json"])],
|
|
71
|
-
["todos", new Set(["project", "json"])],
|
|
86
|
+
["todos", new Set(["project", "status", "json"])],
|
|
87
|
+
["next", new Set(["project", "json"])],
|
|
88
|
+
["todo-convert", new Set(["entry", "index", "project", "all", "json"])],
|
|
72
89
|
["todo-update", new Set(["uid", "status", "project", "json"])],
|
|
73
90
|
["complete", new Set(["entry", "by", "reason", "source", "json"])],
|
|
74
91
|
["revoke-completion", new Set(["entry", "by", "reason", "source", "json"])],
|
|
@@ -97,18 +114,26 @@ const allowedFlags = new Map([
|
|
|
97
114
|
["serve", new Set(["port", "host", "open", "no-open", "json"])],
|
|
98
115
|
]);
|
|
99
116
|
const commandHelp = {
|
|
117
|
+
next: ["List all open work, pinned first", "[--project <name>] [--json]"],
|
|
118
|
+
"todo-convert": [
|
|
119
|
+
"Convert legacy next steps without appending a snapshot",
|
|
120
|
+
"--entry <id-or-uid> --index <n> | --project <name> | --all [--json]",
|
|
121
|
+
],
|
|
100
122
|
"todo-add": [
|
|
101
123
|
"Create an independent or linked Todo",
|
|
102
124
|
"--title <text> [--project <name>] [--entry <uid>] [--json]",
|
|
103
125
|
],
|
|
104
|
-
todos: [
|
|
126
|
+
todos: [
|
|
127
|
+
"List Todos",
|
|
128
|
+
"[--project <name>] [--status open|completed|cancelled] [--json]",
|
|
129
|
+
],
|
|
105
130
|
"todo-update": [
|
|
106
131
|
"Change Todo status or assign a project",
|
|
107
132
|
"--uid <uuid> [--status open|completed|cancelled] [--project <name>] [--json]",
|
|
108
133
|
],
|
|
109
134
|
add: [
|
|
110
135
|
"Append a status snapshot",
|
|
111
|
-
"--project <name> --summary <text> [options]",
|
|
136
|
+
"--project <name> --summary <text> [--next <text> | --next-todo <uid>] [--replace-next] [--done-todo <uid>] [--cancel-todo <uid>] [options]",
|
|
112
137
|
],
|
|
113
138
|
complete: [
|
|
114
139
|
"Link a completed activity to its follow-up",
|
|
@@ -182,7 +207,7 @@ function validateInvocation(command, flags) {
|
|
|
182
207
|
const action = command[statusCommand ? 1 : 0];
|
|
183
208
|
const allowed = typeof action === "string" ? allowedFlags.get(action) : undefined;
|
|
184
209
|
if (typeof action !== "string" || !allowed)
|
|
185
|
-
throw new ValidationError("usage: pmgr <add|complete|revoke-completion|import|current|projects|list|set-project|history|at|export|serve> (or: pmgr status <action>)");
|
|
210
|
+
throw new ValidationError("usage: pmgr <add|next|todo-add|todos|todo-update|todo-convert|complete|revoke-completion|import|current|projects|list|set-project|history|at|export|serve> (or: pmgr status <action>)");
|
|
186
211
|
const positional = command.slice(statusCommand ? 2 : 1);
|
|
187
212
|
if (action === "import") {
|
|
188
213
|
if (positional.length > 1)
|
|
@@ -221,7 +246,7 @@ function output(value, json, write) {
|
|
|
221
246
|
write(`${JSON.stringify(value)}\n`);
|
|
222
247
|
else if (Array.isArray(value)) {
|
|
223
248
|
const rows = value
|
|
224
|
-
.map((entry) => `${entry.project}\t${entry.effectiveStatus ?? entry.status}\t${entry.occurredAt}\t${entry.summary}${entry.completedBy ? ` (completed by #${entry.completedBy.id}; original: ${entry.status})` : ""}`)
|
|
249
|
+
.map((entry) => `${entry.project}\t${entry.effectiveStatus ?? entry.status}\t${entry.occurredAt}\t${entry.summary}${entry.completedBy ? ` (completed by #${entry.completedBy.id}; original: ${entry.status})` : ""}${entry.openWork ? entry.openWork.map((item) => `\n - ${item.title}${item.pinned ? " [pinned]" : ""}`).join("") : ""}`)
|
|
225
250
|
.join("\n");
|
|
226
251
|
write(`${rows || "No entries found."}\n`);
|
|
227
252
|
}
|
|
@@ -301,6 +326,50 @@ export function run(argv, write = process.stdout.write.bind(process.stdout)) {
|
|
|
301
326
|
const json = flags.json === true;
|
|
302
327
|
try {
|
|
303
328
|
switch (action) {
|
|
329
|
+
case "next": {
|
|
330
|
+
const items = openWork(db, stringFlag(flags, "project"));
|
|
331
|
+
if (json)
|
|
332
|
+
output(items, true, write);
|
|
333
|
+
else
|
|
334
|
+
write(items.length
|
|
335
|
+
? items
|
|
336
|
+
.map((item) => `${item.project ?? "-"}\t${item.kind === "todo" ? item.uid : `${item.entryUid}#${item.stepIndex}`}\t${item.pinned ? "pinned" : "-"}\t${item.title}`)
|
|
337
|
+
.join("\n") + "\n"
|
|
338
|
+
: "No open work.\n");
|
|
339
|
+
break;
|
|
340
|
+
}
|
|
341
|
+
case "todo-convert": {
|
|
342
|
+
const ref = stringFlag(flags, "entry");
|
|
343
|
+
const project = stringFlag(flags, "project");
|
|
344
|
+
if (Number(ref !== undefined) +
|
|
345
|
+
Number(project !== undefined) +
|
|
346
|
+
Number(flags.all === true) !==
|
|
347
|
+
1)
|
|
348
|
+
throw new ValidationError("use exactly one of --entry, --project, or --all");
|
|
349
|
+
if ((ref === undefined) !== (flags.index === undefined))
|
|
350
|
+
throw new ValidationError("--entry requires --index; --index requires --entry");
|
|
351
|
+
const todos = db
|
|
352
|
+
.transaction(() => {
|
|
353
|
+
if (ref !== undefined)
|
|
354
|
+
return [convertNextStep(db, ref, numberFlag(flags, "index", 0))];
|
|
355
|
+
return current(db, project).flatMap((entry) => {
|
|
356
|
+
if (entry.nextTodoUids !== undefined)
|
|
357
|
+
return [];
|
|
358
|
+
const source = entry.nextStepsSourceUid
|
|
359
|
+
? getEntry(db, entry.nextStepsSourceUid)
|
|
360
|
+
: entry;
|
|
361
|
+
return source.nextSteps.map((_, index) => convertNextStep(db, source.uid, index));
|
|
362
|
+
});
|
|
363
|
+
})
|
|
364
|
+
.immediate();
|
|
365
|
+
if (json)
|
|
366
|
+
output(todos, true, write);
|
|
367
|
+
else
|
|
368
|
+
write(todos
|
|
369
|
+
.map((todo) => `${todo.uid}\t${todo.status}\t${todo.project}\t${todo.title}`)
|
|
370
|
+
.join("\n") + "\n");
|
|
371
|
+
break;
|
|
372
|
+
}
|
|
304
373
|
case "todo-add":
|
|
305
374
|
output(createTodo(db, {
|
|
306
375
|
title: stringFlag(flags, "title", true),
|
|
@@ -309,7 +378,15 @@ export function run(argv, write = process.stdout.write.bind(process.stdout)) {
|
|
|
309
378
|
}), json, write);
|
|
310
379
|
break;
|
|
311
380
|
case "todos": {
|
|
312
|
-
const
|
|
381
|
+
const status = stringFlag(flags, "status");
|
|
382
|
+
if (status !== undefined &&
|
|
383
|
+
status !== "open" &&
|
|
384
|
+
status !== "completed" &&
|
|
385
|
+
status !== "cancelled")
|
|
386
|
+
throw new ValidationError("--status must be open, completed, or cancelled");
|
|
387
|
+
const todos = listTodos(db, stringFlag(flags, "project"), status === undefined
|
|
388
|
+
? {}
|
|
389
|
+
: { status: status });
|
|
313
390
|
if (json)
|
|
314
391
|
output(todos, true, write);
|
|
315
392
|
else
|
|
@@ -328,6 +405,10 @@ export function run(argv, write = process.stdout.write.bind(process.stdout)) {
|
|
|
328
405
|
const project = stringFlag(flags, "project", true);
|
|
329
406
|
const description = stringFlag(flags, "description");
|
|
330
407
|
const notes = stringFlag(flags, "notes");
|
|
408
|
+
const next = listFlag(flags, "next");
|
|
409
|
+
const nextTodo = listFlag(flags, "next-todo");
|
|
410
|
+
if (next.length > 0 && nextTodo.length > 0)
|
|
411
|
+
throw new ValidationError("cannot combine --next and --next-todo");
|
|
331
412
|
const result = db
|
|
332
413
|
.transaction(() => {
|
|
333
414
|
if (description !== undefined || notes !== undefined) {
|
|
@@ -339,7 +420,19 @@ export function run(argv, write = process.stdout.write.bind(process.stdout)) {
|
|
|
339
420
|
summary: stringFlag(flags, "summary", true),
|
|
340
421
|
progress: stringFlag(flags, "progress"),
|
|
341
422
|
blockers: listFlag(flags, "blocker"),
|
|
342
|
-
nextSteps:
|
|
423
|
+
nextSteps: next,
|
|
424
|
+
nextMode: flags["replace-next"] === true ? "replace" : "carry",
|
|
425
|
+
closeTodos: [
|
|
426
|
+
...listFlag(flags, "done-todo").map((uid) => ({
|
|
427
|
+
uid,
|
|
428
|
+
status: "completed",
|
|
429
|
+
})),
|
|
430
|
+
...listFlag(flags, "cancel-todo").map((uid) => ({
|
|
431
|
+
uid,
|
|
432
|
+
status: "cancelled",
|
|
433
|
+
})),
|
|
434
|
+
],
|
|
435
|
+
...(nextTodo.length > 0 ? { nextTodoUids: nextTodo } : {}),
|
|
343
436
|
decisions: listFlag(flags, "decision"),
|
|
344
437
|
tags: listFlag(flags, "tag"),
|
|
345
438
|
occurredAt: stringFlag(flags, "at"),
|
|
@@ -360,9 +453,16 @@ export function run(argv, write = process.stdout.write.bind(process.stdout)) {
|
|
|
360
453
|
command[command[0] === "status" ? 2 : 1] ??
|
|
361
454
|
"")).map((result) => result.entry), json, write);
|
|
362
455
|
break;
|
|
363
|
-
case "current":
|
|
364
|
-
|
|
456
|
+
case "current": {
|
|
457
|
+
const project = stringFlag(flags, "project");
|
|
458
|
+
// One open-work scan for all projects, grouped per project.
|
|
459
|
+
const { groups } = openWorkByProject(db, project);
|
|
460
|
+
output(current(db, project).map((entry) => ({
|
|
461
|
+
...entry,
|
|
462
|
+
openWork: groups.get(entry.project),
|
|
463
|
+
})), json, write);
|
|
365
464
|
break;
|
|
465
|
+
}
|
|
366
466
|
case "projects":
|
|
367
467
|
case "list":
|
|
368
468
|
outputProjects(listProjects(db, { query: stringFlag(flags, "query") }), json, write);
|
package/dist/db.d.ts
CHANGED
|
@@ -1,10 +1,35 @@
|
|
|
1
1
|
import Database from "better-sqlite3";
|
|
2
|
-
import { type ProjectMetadata, type ProjectOverview, type Source, type StatusEntry } from "./domain.js";
|
|
2
|
+
import { type ProjectMetadata, type ProjectOverview, type Source, type StatusEntry, type StatusEntryInput } from "./domain.js";
|
|
3
3
|
export type Ledger = Database.Database;
|
|
4
4
|
export type InsertResult = {
|
|
5
5
|
entry: StatusEntry;
|
|
6
6
|
inserted: boolean;
|
|
7
7
|
};
|
|
8
|
+
export type AddStatusInput = StatusEntryInput & {
|
|
9
|
+
nextMode?: "carry" | "replace";
|
|
10
|
+
closeTodos?: {
|
|
11
|
+
uid: string;
|
|
12
|
+
status: "completed" | "cancelled";
|
|
13
|
+
}[];
|
|
14
|
+
};
|
|
15
|
+
export type OpenWorkItem = {
|
|
16
|
+
kind: "todo";
|
|
17
|
+
uid: string;
|
|
18
|
+
title: string;
|
|
19
|
+
project: string | null;
|
|
20
|
+
pinned: boolean;
|
|
21
|
+
pinIndex: number | null;
|
|
22
|
+
createdAt: string;
|
|
23
|
+
entryUid: string | null;
|
|
24
|
+
} | {
|
|
25
|
+
kind: "legacy-step";
|
|
26
|
+
project: string;
|
|
27
|
+
entryUid: string;
|
|
28
|
+
stepIndex: number;
|
|
29
|
+
title: string;
|
|
30
|
+
pinned: true;
|
|
31
|
+
pinIndex: number;
|
|
32
|
+
};
|
|
8
33
|
export type HistoryOptions = {
|
|
9
34
|
since?: string;
|
|
10
35
|
until?: string;
|
|
@@ -20,6 +45,12 @@ export declare function listProjects(db: Ledger, options?: {
|
|
|
20
45
|
export declare function addStatus(db: Ledger, value: unknown, defaultSource?: Source, completes?: string[]): InsertResult;
|
|
21
46
|
export declare function importStatuses(db: Ledger, values: unknown[]): InsertResult[];
|
|
22
47
|
export declare function current(db: Ledger, project?: string): StatusEntry[];
|
|
48
|
+
/** Per-project open work from one current() and one Todo scan; no cross-project dedupe. */
|
|
49
|
+
export declare function openWorkByProject(db: Ledger, project?: string): {
|
|
50
|
+
groups: Map<string, OpenWorkItem[]>;
|
|
51
|
+
unassigned: Todo[];
|
|
52
|
+
};
|
|
53
|
+
export declare function openWork(db: Ledger, project?: string): OpenWorkItem[];
|
|
23
54
|
export declare function history(db: Ledger, project?: string, options?: HistoryOptions): StatusEntry[];
|
|
24
55
|
export declare function at(db: Ledger, project: string, occurredAt: string): StatusEntry | undefined;
|
|
25
56
|
export declare function countEntries(db: Ledger, project?: string): number;
|
|
@@ -37,12 +68,13 @@ export declare function getEntry(db: Ledger, ref: string): StatusEntry | undefin
|
|
|
37
68
|
export declare function changeCompletion(db: Ledger, targetRef: string, byRef: string, action?: CompletionEvent["action"], reason?: string, source?: Source): CompletionEvent;
|
|
38
69
|
export declare function exportLedger(db: Ledger, project?: string): {
|
|
39
70
|
todos?: Todo[] | undefined;
|
|
40
|
-
version:
|
|
71
|
+
version: 3 | 2 | 4;
|
|
41
72
|
entries: {
|
|
42
73
|
recordedAt: string;
|
|
43
74
|
progress?: string | undefined;
|
|
44
75
|
externalRef?: string | undefined;
|
|
45
76
|
nextStepsSourceUid?: string | undefined;
|
|
77
|
+
nextTodoUids?: string[] | undefined;
|
|
46
78
|
uid: string;
|
|
47
79
|
project: string;
|
|
48
80
|
status: import("./domain.js").ProjectStatus;
|
|
@@ -67,7 +99,9 @@ export type Todo = {
|
|
|
67
99
|
createdAt: string;
|
|
68
100
|
completedAt: string | null;
|
|
69
101
|
};
|
|
70
|
-
export declare function listTodos(db: Ledger, project?: string
|
|
102
|
+
export declare function listTodos(db: Ledger, project?: string, options?: {
|
|
103
|
+
status?: Todo["status"];
|
|
104
|
+
}): Todo[];
|
|
71
105
|
export declare function createTodo(db: Ledger, value: unknown): Todo;
|
|
72
106
|
export declare function convertNextStep(db: Ledger, ref: string, index: number): Todo;
|
|
73
107
|
export declare function updateTodo(db: Ledger, uid: string, value: unknown): Todo;
|