project-mgr 0.3.2 → 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/CHANGELOG.md CHANGED
@@ -1,5 +1,39 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.5.0 - 2026-10-29
4
+
5
+ ### Added
6
+
7
+ - Store new snapshot next work as `nextTodoUids` references. `add --next` creates Todos and those refs in one transaction; `add --next-todo` pins existing UIDs.
8
+ - Reject mixed prose and refs, duplicate UIDs, combining `--next` with `--next-todo`, cross-project refs, and project changes that conflict with any historical reference.
9
+ - Export version 4 carries refs and every Todo they need, including unassigned Todos referenced by a project export.
10
+ - `todos --status open` lists open work. With `--project`, current snapshot pins are ordered first.
11
+ - Dashboard activity history and project cards show referenced Todos with live status.
12
+
13
+ ### Changed
14
+
15
+ - 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.
16
+
17
+ ### Fixed
18
+
19
+ - Detach the dashboard browser launch so a long-lived browser does not keep the process alive.
20
+ - 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.
21
+ - 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.
22
+
23
+ ## 0.4.0 - 2026-10-28
24
+
25
+ ### Added
26
+
27
+ - Add independent Todos with optional project/source links, per-item completion, cancellation, reopening, and CLI commands.
28
+ - Create Todos atomically from new next steps and explicitly convert legacy steps without rewriting history.
29
+ - Add dashboard activity completion controls and project-filtered Todo views.
30
+ - Preserve Todo state in version-3 exports and enforce same-origin local JSON writes.
31
+
32
+ ### Changed
33
+
34
+ - Guide agents to ask before recording multiple sub-projects, either under one project name or with snapshot tags.
35
+ - Sync the Chinese README with CLI help, activity completion, and Todo documentation.
36
+
3
37
  ## 0.3.2 - 2026-10-16
4
38
 
5
39
  - Add general and per-command CLI help, no-argument help, and package version aliases without opening the database.
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
  {
@@ -333,3 +333,52 @@ Activity cards display Completed with a follow-up link and retain original detai
333
333
  Export now emits a version-2 bundle `{ "version": 2, "entries": [...], "completions": [...] }`. JSONL emits one complete bundle per line. Entries retain UUIDs and recording times; events retain UUIDs and per-target predecessor links, including revocations. Import accepts legacy entry objects/arrays/JSONL and version-2 bundles, checks identity conflicts, and applies each file atomically. Re-importing the same bundle is idempotent. Event order is significant: import events in exported order; divergent histories fail instead of silently merging. Older clients cannot import version-2 bundles. Migration adds UUIDs to existing snapshots without changing their content.
334
334
 
335
335
  Independently migrated copies of legacy data receive different UUIDs. Cross-import may fail on an existing `externalRef`; no implicit identity adoption occurs. Restore a bundle into a fresh database for a faithful migration. Older writers are rejected if they omit UUIDs. With an existing `externalRef`, `add` retains its existing snapshot and applies explicitly supplied `--completes` links to that snapshot; identical active links are no-ops. Use `complete` to make after-the-fact reconciliation explicit.
336
+
337
+ ### Todos and dashboard actions
338
+
339
+ Todos own actionable work. A new status snapshot stores ordered `nextTodoUids`
340
+ and does not persist next-step prose. `add --next` creates one Todo per title
341
+ and stores those UIDs in the same transaction. Repeating the same text on a
342
+ later snapshot creates new Todos; pass `--next-todo` to keep existing work.
343
+ Do not combine `--next` with `--next-todo`. Duplicate UIDs in one list are rejected.
344
+
345
+ ```sh
346
+ pmgr add --project demo --status active --summary "Review queued" \
347
+ --next "Collect logs" --next "Write the note" --source agent --json
348
+ pmgr todo-add --title "Unassigned idea" --json
349
+ pmgr add --project demo --status active --summary "Pin the idea" \
350
+ --next-todo <todo-uuid> --source agent --json
351
+ pmgr todos --project demo --status open --json
352
+ pmgr todo-update --uid <todo-uuid> --status completed --json
353
+ pmgr todo-update --uid <todo-uuid> --status cancelled --json
354
+ ```
355
+
356
+ `todos --status open` answers open work from Todos, not from string next steps.
357
+ With `--project`, UIDs pinned on the current snapshot are ordered first.
358
+ Completing or cancelling a Todo updates every resolved view and does not rewrite
359
+ snapshots. Omitting a UID from a newer snapshot unpins it and does not change
360
+ the Todo status.
361
+
362
+ Legacy snapshots keep string `nextSteps` and omit `nextTodoUids`. Convert each
363
+ index explicitly; conversion does not append a snapshot. While any source index
364
+ is still unconverted, a completion follow-up copies the full string list and
365
+ `nextStepsSourceUid`. After every index has a Todo, the follow-up materializes
366
+ `nextTodoUids` in source-index order and does not create Todos or change status.
367
+ A snapshot never stores both prose and refs.
368
+
369
+ `current` and `history` include stored `nextTodoUids` plus live `nextTodos`
370
+ (`uid`, `title`, `status`, `project`). That view is the Todo now, not its state
371
+ at the snapshot time. `at` returns the stored snapshot, including refs when
372
+ present, and does not add the live view.
373
+
374
+ A snapshot may reference Todos that are unassigned or already in that project.
375
+ If a Todo appears in any `nextTodoUids`, or is linked to a source entry, its
376
+ project cannot be changed. Unpinning the current snapshot does not lift that lock.
377
+
378
+ Exports that contain refs use version 4 and include every referenced Todo.
379
+ Version 3 remains the Todo bundle without refs. Version 2 and legacy imports
380
+ do not invent open work. Dangling, duplicate, mixed, or cross-project refs fail
381
+ the whole import. Dashboard writes stay same-origin localhost JSON. Activity
382
+ cards still offer **Mark completed**, which appends a follow-up and does not
383
+ complete the project or its Todos. Adding a Todo from the dashboard does not
384
+ pin it; pinning requires a new snapshot.
package/README_CN.md CHANGED
@@ -127,6 +127,18 @@ pnpm start history --project test-project
127
127
 
128
128
  自动化测试会使用内存数据库或系统临时目录中的 SQLite 文件,不会读写默认数据库。`PMGR_DB_PATH=:memory:` 也可用于单条命令,但每个 CLI 命令都是独立进程,命令结束后数据就会消失,不适合连续手工测试。
129
129
 
130
+ ## CLI 帮助与版本
131
+
132
+ ```bash
133
+ pmgr # 显示通用帮助
134
+ pmgr -h # 也可使用 --help 或 help
135
+ pmgr add --help # 命令用法与可用选项
136
+ pmgr help history # 等价于 history -h
137
+ pmgr -v # 也可使用 -V、--version 或 version
138
+ ```
139
+
140
+ 帮助与版本命令会成功退出,且不会打开或创建数据库。版本输出来自已安装包的元数据。也支持兼容形式 `pmgr status <command> --help`。
141
+
130
142
  ## 快速开始
131
143
 
132
144
  追加一条项目状态:
@@ -186,7 +198,7 @@ pnpm start at \
186
198
  - `--external-ref`:全局幂等键。重复写入同一项目时返回原记录;若被其他项目占用则报错。
187
199
  - `--json`:输出稳定 JSON。
188
200
 
189
- ## 项目元数据与模糊查询
201
+ ### 项目元数据与模糊查询
190
202
 
191
203
  可通过 `set-project` 设置或更新项目的名称、简介与备注,或使用 `--clear-description` / `--clear-notes` 清空:
192
204
 
@@ -211,7 +223,7 @@ pnpm start projects --query "mvp" --json
211
223
 
212
224
  ## 批量导入
213
225
 
214
- `import` 接受一个 JSON 对象、JSON 数组,或每行一个对象的 JSONL 文件。所有记录先校验,再在同一个 SQLite 事务中写入;任一记录失败时整批回滚。
226
+ `import` 接受一个 JSON 对象、JSON 数组,或每行一个对象的 JSONL 文件。所有记录先校验,再在同一个 SQLite 事务中写入;任一记录失败时整批回滚。下面的示例是旧格式条目:字符串 `nextSteps` 会保留,且不会创建待办。用 `add` 写入的新快照会改为存储 `nextTodoUids`。
215
227
 
216
228
  ```json
217
229
  {
@@ -298,3 +310,68 @@ pnpm build
298
310
  ```
299
311
 
300
312
  Statements、branches、functions、lines 的覆盖率门槛均为 100%。详细设计见[架构文档](docs/architecture.md)。
313
+
314
+ ## 补全更早的动态
315
+
316
+ 项目状态和动态是否完成是两回事。关联条目前,先查看 `pmgr history --project demo --json`。`status` 是原始快照;`effectiveStatus` 计入显式的后续完成;`isCurrent` 标识该项目的最新快照。缺少 `completedBy` 并不表示这条动态尚未完成。
317
+
318
+ ```bash
319
+ # 一次更新可完成多条更早的记录(本地数字 ID 或可移植 UUID)。
320
+ pmgr add --project demo --status active --summary "部署已验收" \
321
+ --completes 12 --completes 15 --source agent --json
322
+ # 关联一条已有的后续记录,或不删除历史地纠正错误关联。
323
+ pmgr complete --entry 12 --by 18 --source agent --json
324
+ pmgr revoke-completion --entry 12 --by 18 --reason "验收仍未完成" --source agent --json
325
+ ```
326
+
327
+ 只关联那些全部未完成工作(包括阻塞和下一步)都已解决的条目。同一项目内的关联要求后续记录的发生时间严格更晚;发生时间相同的关联会被拒绝。每个目标只能有一个有效的完成来源。重复同一关联是空操作;要替换时需先撤销。`add --completes` 会原子写入快照、元数据和关联。已有历史不会自动对账。
328
+
329
+ 动态卡片会显示已完成,并链到后续动态;原始详情保留在可展开的「原始记录」中。「当前状态」和「已有后续更新」只表示快照新旧,与是否完成相互独立。`/api/entries/<id-or-uid>` 和 `/?entry=<uid>` 即使不在初始动态页,也能打开一条记录。日期筛选选择的是快照;完成标注反映的是当前结果。`at` 仍然返回不含完成信息的历史项目快照;按历史时点查询完成状态暂不支持。
330
+
331
+ 导出现在会输出 version 2 包 `{ "version": 2, "entries": [...], "completions": [...] }`。JSONL 每行输出一个完整包。条目保留 UUID 和记录时间;事件保留 UUID,以及每个目标的前驱链接(含撤销)。导入同时接受旧版条目对象、数组、JSONL 和 version 2 包,会检查身份冲突,并按文件原子应用。重复导入同一包是幂等的。事件顺序有意义:须按导出顺序导入;历史不一致时导入失败,不会静默合并。旧客户端无法导入 version 2 包。迁移会给已有快照补上 UUID,不改变其内容。
332
+
333
+ 各自独立迁移的旧数据副本会得到不同的 UUID。跨库导入可能因已存在的 `externalRef` 失败,不会隐式沿用对方身份。要忠实迁移,请把包恢复到一个全新数据库。省略 UUID 的旧写入会被拒绝。若 `externalRef` 已存在,`add` 会保留原快照,并把显式传入的 `--completes` 关联应用到该快照;相同且仍然有效的关联是空操作。事后补关联请使用 `complete`,让对账显式发生。
334
+
335
+ ### 待办与仪表板操作
336
+
337
+ 待办是具体工作的来源。新的状态快照按顺序存储 `nextTodoUids`,不再把下一步原文写入快照。
338
+ `add --next` 会为每条标题创建一个待办,并在同一事务里写入这些 UID。
339
+ 之后的快照如果再次输入相同文本,会创建新的待办;要沿用已有工作,请传 `--next-todo`。
340
+ 不要把 `--next` 和 `--next-todo` 写在同一次 `add` 里。同一列表里的重复 UID 会被拒绝。
341
+
342
+ ```sh
343
+ pmgr add --project demo --status active --summary "已排队复查" \
344
+ --next "收集日志" --next "写说明" --source agent --json
345
+ pmgr todo-add --title "未归属的想法" --json
346
+ pmgr add --project demo --status active --summary "钉住这个想法" \
347
+ --next-todo <todo-uuid> --source agent --json
348
+ pmgr todos --project demo --status open --json
349
+ pmgr todo-update --uid <todo-uuid> --status completed --json
350
+ pmgr todo-update --uid <todo-uuid> --status cancelled --json
351
+ ```
352
+
353
+ `todos --status open` 从待办而不是字符串下一步回答「还有哪些未完成工作」。
354
+ 加上 `--project` 时,当前快照钉住的 UID 会排在前面。
355
+ 完成或取消待办会立刻反映在所有解析视图上,且不会改写历史快照。
356
+ 新快照不再列入某个 UID,只表示它不再是当前下一步,不会改变该待办的状态。
357
+
358
+ 旧快照保留字符串 `nextSteps`,并省略 `nextTodoUids`。转换仍按来源下标显式进行,且不会追加快照。
359
+ 只要还有来源下标未转换,完成活动时的后续快照会复制完整字符串列表和 `nextStepsSourceUid`。
360
+ 每个下标都已有待办之后,后续快照才按原下标顺序写入 `nextTodoUids`,不新建待办,也不改变状态。
361
+ 一条快照不能同时保存原文和引用。
362
+
363
+ `current` 和 `history` 会带上存储的 `nextTodoUids`,以及实时的 `nextTodos`(`uid`、`title`、`status`、`project`)。
364
+ 这个视图是待办的当前状态,不是快照发生时的历史状态。
365
+ `at` 返回当时的快照;如果存了引用就包含引用,但不会附加实时视图。
366
+
367
+ 快照只能引用未归属的待办,或已经属于该项目的待办。
368
+ 待办一旦出现在任意 `nextTodoUids` 中,或关联了来源动态,就不能再改项目。
369
+ 从当前快照拿掉引用,也不会解除这个限制。
370
+
371
+ 带引用的导出使用 version 4,并包含被引用的待办。
372
+ 没有引用、但有待办的导出仍是 version 3。
373
+ version 2 和旧版导入不会凭空创建待办。
374
+ 悬空、重复、混用或跨项目引用会使整批导入失败。
375
+ 仪表板写入仍只接受同源的 localhost JSON 请求。
376
+ 动态卡片上的 **标为已完成** 会追加一条后续动态,既不完成项目,也不完成待办。
377
+ 在仪表板里新增待办不会把它钉进当前下一步;钉住需要再写一条快照。
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, at, current, history, importLedger, exportLedger, changeCompletion, listProjects, openLedger, parseTime, setProjectMetadata, startServer, ValidationError, } from "./index.js";
5
+ import { addStatus, createTodo, 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",
@@ -12,7 +12,14 @@ const booleanFlags = new Set([
12
12
  "help",
13
13
  "version",
14
14
  ]);
15
- const repeats = new Set(["next", "blocker", "decision", "tag", "completes"]);
15
+ const repeats = new Set([
16
+ "next",
17
+ "next-todo",
18
+ "blocker",
19
+ "decision",
20
+ "tag",
21
+ "completes",
22
+ ]);
16
23
  function parse(argv) {
17
24
  const command = [];
18
25
  const flags = Object.create(null);
@@ -58,6 +65,7 @@ const allowedFlags = new Map([
58
65
  "progress",
59
66
  "blocker",
60
67
  "next",
68
+ "next-todo",
61
69
  "decision",
62
70
  "tag",
63
71
  "at",
@@ -67,6 +75,9 @@ const allowedFlags = new Map([
67
75
  "json",
68
76
  ]),
69
77
  ],
78
+ ["todo-add", new Set(["title", "project", "entry", "json"])],
79
+ ["todos", new Set(["project", "status", "json"])],
80
+ ["todo-update", new Set(["uid", "status", "project", "json"])],
70
81
  ["complete", new Set(["entry", "by", "reason", "source", "json"])],
71
82
  ["revoke-completion", new Set(["entry", "by", "reason", "source", "json"])],
72
83
  ["import", new Set(["file", "json"])],
@@ -94,9 +105,21 @@ const allowedFlags = new Map([
94
105
  ["serve", new Set(["port", "host", "open", "no-open", "json"])],
95
106
  ]);
96
107
  const commandHelp = {
108
+ "todo-add": [
109
+ "Create an independent or linked Todo",
110
+ "--title <text> [--project <name>] [--entry <uid>] [--json]",
111
+ ],
112
+ todos: [
113
+ "List Todos",
114
+ "[--project <name>] [--status open|completed|cancelled] [--json]",
115
+ ],
116
+ "todo-update": [
117
+ "Change Todo status or assign a project",
118
+ "--uid <uuid> [--status open|completed|cancelled] [--project <name>] [--json]",
119
+ ],
97
120
  add: [
98
121
  "Append a status snapshot",
99
- "--project <name> --summary <text> [options]",
122
+ "--project <name> --summary <text> [--next <text> | --next-todo <uid>] [options]",
100
123
  ],
101
124
  complete: [
102
125
  "Link a completed activity to its follow-up",
@@ -289,10 +312,45 @@ export function run(argv, write = process.stdout.write.bind(process.stdout)) {
289
312
  const json = flags.json === true;
290
313
  try {
291
314
  switch (action) {
315
+ case "todo-add":
316
+ output(createTodo(db, {
317
+ title: stringFlag(flags, "title", true),
318
+ project: stringFlag(flags, "project"),
319
+ entryUid: stringFlag(flags, "entry"),
320
+ }), json, write);
321
+ break;
322
+ case "todos": {
323
+ const status = stringFlag(flags, "status");
324
+ if (status !== undefined &&
325
+ status !== "open" &&
326
+ status !== "completed" &&
327
+ status !== "cancelled")
328
+ throw new ValidationError("--status must be open, completed, or cancelled");
329
+ const todos = listTodos(db, stringFlag(flags, "project"), status === undefined
330
+ ? {}
331
+ : { status: status });
332
+ if (json)
333
+ output(todos, true, write);
334
+ else
335
+ write(todos
336
+ .map((t) => `${t.uid}\t${t.status}\t${t.project ?? "-"}\t${t.title}`)
337
+ .join("\n") + "\n");
338
+ break;
339
+ }
340
+ case "todo-update":
341
+ output(updateTodo(db, stringFlag(flags, "uid", true), {
342
+ status: stringFlag(flags, "status"),
343
+ project: stringFlag(flags, "project"),
344
+ }), json, write);
345
+ break;
292
346
  case "add": {
293
347
  const project = stringFlag(flags, "project", true);
294
348
  const description = stringFlag(flags, "description");
295
349
  const notes = stringFlag(flags, "notes");
350
+ const next = listFlag(flags, "next");
351
+ const nextTodo = listFlag(flags, "next-todo");
352
+ if (next.length > 0 && nextTodo.length > 0)
353
+ throw new ValidationError("cannot combine --next and --next-todo");
296
354
  const result = db
297
355
  .transaction(() => {
298
356
  if (description !== undefined || notes !== undefined) {
@@ -304,7 +362,8 @@ export function run(argv, write = process.stdout.write.bind(process.stdout)) {
304
362
  summary: stringFlag(flags, "summary", true),
305
363
  progress: stringFlag(flags, "progress"),
306
364
  blockers: listFlag(flags, "blocker"),
307
- nextSteps: listFlag(flags, "next"),
365
+ nextSteps: next,
366
+ ...(nextTodo.length > 0 ? { nextTodoUids: nextTodo } : {}),
308
367
  decisions: listFlag(flags, "decision"),
309
368
  tags: listFlag(flags, "tag"),
310
369
  occurredAt: stringFlag(flags, "at"),
package/dist/db.d.ts CHANGED
@@ -36,11 +36,14 @@ export type CompletionEvent = {
36
36
  export declare function getEntry(db: Ledger, ref: string): StatusEntry | undefined;
37
37
  export declare function changeCompletion(db: Ledger, targetRef: string, byRef: string, action?: CompletionEvent["action"], reason?: string, source?: Source): CompletionEvent;
38
38
  export declare function exportLedger(db: Ledger, project?: string): {
39
- version: number;
39
+ todos?: Todo[] | undefined;
40
+ version: 3 | 2 | 4;
40
41
  entries: {
41
42
  recordedAt: string;
42
43
  progress?: string | undefined;
43
44
  externalRef?: string | undefined;
45
+ nextStepsSourceUid?: string | undefined;
46
+ nextTodoUids?: string[] | undefined;
44
47
  uid: string;
45
48
  project: string;
46
49
  status: import("./domain.js").ProjectStatus;
@@ -55,3 +58,20 @@ export declare function exportLedger(db: Ledger, project?: string): {
55
58
  completions: CompletionEvent[];
56
59
  };
57
60
  export declare function importLedger(db: Ledger, values: unknown[]): InsertResult[];
61
+ export type Todo = {
62
+ uid: string;
63
+ title: string;
64
+ project: string | null;
65
+ entryUid: string | null;
66
+ stepIndex: number | null;
67
+ status: "open" | "completed" | "cancelled";
68
+ createdAt: string;
69
+ completedAt: string | null;
70
+ };
71
+ export declare function listTodos(db: Ledger, project?: string, options?: {
72
+ status?: Todo["status"];
73
+ }): Todo[];
74
+ export declare function createTodo(db: Ledger, value: unknown): Todo;
75
+ export declare function convertNextStep(db: Ledger, ref: string, index: number): Todo;
76
+ export declare function updateTodo(db: Ledger, uid: string, value: unknown): Todo;
77
+ export declare function completeActivity(db: Ledger, ref: string): StatusEntry;