@unbrained/pm-cli 2026.3.9
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/.pi/extensions/pm-cli/index.ts +778 -0
- package/AGENTS.md +475 -0
- package/LICENSE +21 -0
- package/PRD.md +1390 -0
- package/README.md +655 -0
- package/dist/cli/commands/activity.d.ts +14 -0
- package/dist/cli/commands/activity.js +80 -0
- package/dist/cli/commands/activity.js.map +1 -0
- package/dist/cli/commands/append.d.ts +13 -0
- package/dist/cli/commands/append.js +46 -0
- package/dist/cli/commands/append.js.map +1 -0
- package/dist/cli/commands/beads.d.ts +15 -0
- package/dist/cli/commands/beads.js +475 -0
- package/dist/cli/commands/beads.js.map +1 -0
- package/dist/cli/commands/claim.d.ts +19 -0
- package/dist/cli/commands/claim.js +79 -0
- package/dist/cli/commands/claim.js.map +1 -0
- package/dist/cli/commands/close.d.ts +12 -0
- package/dist/cli/commands/close.js +58 -0
- package/dist/cli/commands/close.js.map +1 -0
- package/dist/cli/commands/comments.d.ts +15 -0
- package/dist/cli/commands/comments.js +80 -0
- package/dist/cli/commands/comments.js.map +1 -0
- package/dist/cli/commands/completion.d.ts +10 -0
- package/dist/cli/commands/completion.js +469 -0
- package/dist/cli/commands/completion.js.map +1 -0
- package/dist/cli/commands/config.d.ts +15 -0
- package/dist/cli/commands/config.js +72 -0
- package/dist/cli/commands/config.js.map +1 -0
- package/dist/cli/commands/create.d.ts +60 -0
- package/dist/cli/commands/create.js +456 -0
- package/dist/cli/commands/create.js.map +1 -0
- package/dist/cli/commands/delete.d.ts +12 -0
- package/dist/cli/commands/delete.js +33 -0
- package/dist/cli/commands/delete.js.map +1 -0
- package/dist/cli/commands/docs.d.ts +16 -0
- package/dist/cli/commands/docs.js +113 -0
- package/dist/cli/commands/docs.js.map +1 -0
- package/dist/cli/commands/files.d.ts +17 -0
- package/dist/cli/commands/files.js +113 -0
- package/dist/cli/commands/files.js.map +1 -0
- package/dist/cli/commands/gc.d.ts +9 -0
- package/dist/cli/commands/gc.js +80 -0
- package/dist/cli/commands/gc.js.map +1 -0
- package/dist/cli/commands/get.d.ts +12 -0
- package/dist/cli/commands/get.js +28 -0
- package/dist/cli/commands/get.js.map +1 -0
- package/dist/cli/commands/health.d.ts +15 -0
- package/dist/cli/commands/health.js +288 -0
- package/dist/cli/commands/health.js.map +1 -0
- package/dist/cli/commands/history.d.ts +13 -0
- package/dist/cli/commands/history.js +72 -0
- package/dist/cli/commands/history.js.map +1 -0
- package/dist/cli/commands/index.d.ts +26 -0
- package/dist/cli/commands/index.js +27 -0
- package/dist/cli/commands/index.js.map +1 -0
- package/dist/cli/commands/init.d.ts +10 -0
- package/dist/cli/commands/init.js +59 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/install.d.ts +18 -0
- package/dist/cli/commands/install.js +87 -0
- package/dist/cli/commands/install.js.map +1 -0
- package/dist/cli/commands/list.d.ts +21 -0
- package/dist/cli/commands/list.js +137 -0
- package/dist/cli/commands/list.js.map +1 -0
- package/dist/cli/commands/reindex.d.ts +16 -0
- package/dist/cli/commands/reindex.js +154 -0
- package/dist/cli/commands/reindex.js.map +1 -0
- package/dist/cli/commands/restore.d.ts +20 -0
- package/dist/cli/commands/restore.js +208 -0
- package/dist/cli/commands/restore.js.map +1 -0
- package/dist/cli/commands/search.d.ts +45 -0
- package/dist/cli/commands/search.js +531 -0
- package/dist/cli/commands/search.js.map +1 -0
- package/dist/cli/commands/stats.d.ts +13 -0
- package/dist/cli/commands/stats.js +88 -0
- package/dist/cli/commands/stats.js.map +1 -0
- package/dist/cli/commands/test-all.d.ts +30 -0
- package/dist/cli/commands/test-all.js +157 -0
- package/dist/cli/commands/test-all.js.map +1 -0
- package/dist/cli/commands/test.d.ts +29 -0
- package/dist/cli/commands/test.js +492 -0
- package/dist/cli/commands/test.js.map +1 -0
- package/dist/cli/commands/update.d.ts +52 -0
- package/dist/cli/commands/update.js +467 -0
- package/dist/cli/commands/update.js.map +1 -0
- package/dist/cli/extension-command-options.d.ts +1 -0
- package/dist/cli/extension-command-options.js +76 -0
- package/dist/cli/extension-command-options.js.map +1 -0
- package/dist/cli/main.d.ts +2 -0
- package/dist/cli/main.js +1494 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +3 -0
- package/dist/cli.js.map +1 -0
- package/dist/command-types.d.ts +1 -0
- package/dist/command-types.js +2 -0
- package/dist/command-types.js.map +1 -0
- package/dist/constants.d.ts +1 -0
- package/dist/constants.js +2 -0
- package/dist/constants.js.map +1 -0
- package/dist/core/extensions/builtins.d.ts +3 -0
- package/dist/core/extensions/builtins.js +47 -0
- package/dist/core/extensions/builtins.js.map +1 -0
- package/dist/core/extensions/index.d.ts +13 -0
- package/dist/core/extensions/index.js +88 -0
- package/dist/core/extensions/index.js.map +1 -0
- package/dist/core/extensions/loader.d.ts +301 -0
- package/dist/core/extensions/loader.js +917 -0
- package/dist/core/extensions/loader.js.map +1 -0
- package/dist/core/fs/fs-utils.d.ts +6 -0
- package/dist/core/fs/fs-utils.js +58 -0
- package/dist/core/fs/fs-utils.js.map +1 -0
- package/dist/core/fs/index.d.ts +1 -0
- package/dist/core/fs/index.js +2 -0
- package/dist/core/fs/index.js.map +1 -0
- package/dist/core/history/history.d.ts +12 -0
- package/dist/core/history/history.js +44 -0
- package/dist/core/history/history.js.map +1 -0
- package/dist/core/history/index.d.ts +1 -0
- package/dist/core/history/index.js +2 -0
- package/dist/core/history/index.js.map +1 -0
- package/dist/core/item/id.d.ts +3 -0
- package/dist/core/item/id.js +54 -0
- package/dist/core/item/id.js.map +1 -0
- package/dist/core/item/index.d.ts +3 -0
- package/dist/core/item/index.js +4 -0
- package/dist/core/item/index.js.map +1 -0
- package/dist/core/item/item-format.d.ts +9 -0
- package/dist/core/item/item-format.js +363 -0
- package/dist/core/item/item-format.js.map +1 -0
- package/dist/core/item/parse.d.ts +3 -0
- package/dist/core/item/parse.js +72 -0
- package/dist/core/item/parse.js.map +1 -0
- package/dist/core/lock/index.d.ts +1 -0
- package/dist/core/lock/index.js +2 -0
- package/dist/core/lock/index.js.map +1 -0
- package/dist/core/lock/lock.d.ts +1 -0
- package/dist/core/lock/lock.js +100 -0
- package/dist/core/lock/lock.js.map +1 -0
- package/dist/core/output/output.d.ts +7 -0
- package/dist/core/output/output.js +79 -0
- package/dist/core/output/output.js.map +1 -0
- package/dist/core/search/cache.d.ts +17 -0
- package/dist/core/search/cache.js +212 -0
- package/dist/core/search/cache.js.map +1 -0
- package/dist/core/search/embedding-batches.d.ts +7 -0
- package/dist/core/search/embedding-batches.js +54 -0
- package/dist/core/search/embedding-batches.js.map +1 -0
- package/dist/core/search/providers.d.ts +59 -0
- package/dist/core/search/providers.js +265 -0
- package/dist/core/search/providers.js.map +1 -0
- package/dist/core/search/vector-stores.d.ts +89 -0
- package/dist/core/search/vector-stores.js +546 -0
- package/dist/core/search/vector-stores.js.map +1 -0
- package/dist/core/shared/command-types.d.ts +7 -0
- package/dist/core/shared/command-types.js +2 -0
- package/dist/core/shared/command-types.js.map +1 -0
- package/dist/core/shared/constants.d.ts +19 -0
- package/dist/core/shared/constants.js +134 -0
- package/dist/core/shared/constants.js.map +1 -0
- package/dist/core/shared/errors.d.ts +4 -0
- package/dist/core/shared/errors.js +9 -0
- package/dist/core/shared/errors.js.map +1 -0
- package/dist/core/shared/index.d.ts +3 -0
- package/dist/core/shared/index.js +4 -0
- package/dist/core/shared/index.js.map +1 -0
- package/dist/core/shared/serialization.d.ts +3 -0
- package/dist/core/shared/serialization.js +70 -0
- package/dist/core/shared/serialization.js.map +1 -0
- package/dist/core/shared/time.d.ts +3 -0
- package/dist/core/shared/time.js +28 -0
- package/dist/core/shared/time.js.map +1 -0
- package/dist/core/store/index.d.ts +3 -0
- package/dist/core/store/index.js +4 -0
- package/dist/core/store/index.js.map +1 -0
- package/dist/core/store/item-store.d.ts +42 -0
- package/dist/core/store/item-store.js +186 -0
- package/dist/core/store/item-store.js.map +1 -0
- package/dist/core/store/paths.d.ts +8 -0
- package/dist/core/store/paths.js +29 -0
- package/dist/core/store/paths.js.map +1 -0
- package/dist/core/store/settings.d.ts +4 -0
- package/dist/core/store/settings.js +148 -0
- package/dist/core/store/settings.js.map +1 -0
- package/dist/errors.d.ts +1 -0
- package/dist/errors.js +2 -0
- package/dist/errors.js.map +1 -0
- package/dist/extensions/builtins/beads/index.d.ts +8 -0
- package/dist/extensions/builtins/beads/index.js +29 -0
- package/dist/extensions/builtins/beads/index.js.map +1 -0
- package/dist/extensions/builtins/todos/import-export.d.ts +26 -0
- package/dist/extensions/builtins/todos/import-export.js +460 -0
- package/dist/extensions/builtins/todos/import-export.js.map +1 -0
- package/dist/extensions/builtins/todos/index.d.ts +8 -0
- package/dist/extensions/builtins/todos/index.js +38 -0
- package/dist/extensions/builtins/todos/index.js.map +1 -0
- package/dist/fs-utils.d.ts +1 -0
- package/dist/fs-utils.js +2 -0
- package/dist/fs-utils.js.map +1 -0
- package/dist/history.d.ts +1 -0
- package/dist/history.js +2 -0
- package/dist/history.js.map +1 -0
- package/dist/id.d.ts +1 -0
- package/dist/id.js +2 -0
- package/dist/id.js.map +1 -0
- package/dist/item-format.d.ts +1 -0
- package/dist/item-format.js +2 -0
- package/dist/item-format.js.map +1 -0
- package/dist/item-store.d.ts +1 -0
- package/dist/item-store.js +2 -0
- package/dist/item-store.js.map +1 -0
- package/dist/lock.d.ts +1 -0
- package/dist/lock.js +2 -0
- package/dist/lock.js.map +1 -0
- package/dist/output.d.ts +1 -0
- package/dist/output.js +2 -0
- package/dist/output.js.map +1 -0
- package/dist/parse.d.ts +1 -0
- package/dist/parse.js +2 -0
- package/dist/parse.js.map +1 -0
- package/dist/paths.d.ts +1 -0
- package/dist/paths.js +2 -0
- package/dist/paths.js.map +1 -0
- package/dist/serialization.d.ts +1 -0
- package/dist/serialization.js +2 -0
- package/dist/serialization.js.map +1 -0
- package/dist/settings.d.ts +1 -0
- package/dist/settings.js +2 -0
- package/dist/settings.js.map +1 -0
- package/dist/time.d.ts +1 -0
- package/dist/time.js +2 -0
- package/dist/time.js.map +1 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types.d.ts +179 -0
- package/dist/types.js +21 -0
- package/dist/types.js.map +1 -0
- package/docs/ARCHITECTURE.md +246 -0
- package/docs/EXTENSIONS.md +329 -0
- package/docs/RELEASING.md +65 -0
- package/package.json +79 -0
- package/scripts/install.ps1 +112 -0
- package/scripts/install.sh +113 -0
package/PRD.md
ADDED
|
@@ -0,0 +1,1390 @@
|
|
|
1
|
+
# pm-cli Product Requirements Document (PRD)
|
|
2
|
+
|
|
3
|
+
Status: Draft v1 (authoritative for implementation)
|
|
4
|
+
Project: `pm` / `pm-cli`
|
|
5
|
+
Last Updated: 2026-02-19
|
|
6
|
+
|
|
7
|
+
## 1) Problem Statement
|
|
8
|
+
|
|
9
|
+
Coding agents and humans need a shared project-management system that is:
|
|
10
|
+
|
|
11
|
+
- Git-native (diffable, reviewable, branch-friendly)
|
|
12
|
+
- Deterministic (stable machine-readable output for automation)
|
|
13
|
+
- Robust under concurrent edits (claiming + lock safety)
|
|
14
|
+
- Extensible (project-local and global custom behavior)
|
|
15
|
+
- Token-efficient for LLM workflows (TOON by default, JSON fallback)
|
|
16
|
+
|
|
17
|
+
Existing trackers either rely on hosted backends, store state in non-diff-friendly formats, or do not provide first-class agent ergonomics for claiming, dependencies, history replay, and deterministic output.
|
|
18
|
+
|
|
19
|
+
## 2) Goals
|
|
20
|
+
|
|
21
|
+
- Build a cross-platform TypeScript CLI named `pm`.
|
|
22
|
+
- Store all core tracker data in project-local files under `.agents/pm` by default.
|
|
23
|
+
- Model work as first-class items: `Epic`, `Feature`, `Task`, `Chore`, `Issue`.
|
|
24
|
+
- Support full item lifecycle operations, deterministic listing/filtering, and rich metadata.
|
|
25
|
+
- Provide append-only item history with patch-level restore.
|
|
26
|
+
- Provide safe mutation under concurrent access (claim/release + lock + atomic writes).
|
|
27
|
+
- Default stdout to TOON; support `--json` parity for every command.
|
|
28
|
+
- Provide extension architecture for commands, schema, rendering, import/export, search adapters, and hooks.
|
|
29
|
+
- Ship built-in extensions:
|
|
30
|
+
- Beads import
|
|
31
|
+
- todos.ts import/export
|
|
32
|
+
- Pi agent extension wrapper module
|
|
33
|
+
- Provide optional semantic search with provider + vector-store adapters.
|
|
34
|
+
|
|
35
|
+
## 3) Explicit Non-Goals
|
|
36
|
+
|
|
37
|
+
- No required UI/TUI (CLI-first only).
|
|
38
|
+
- No required remote control plane for core tracker.
|
|
39
|
+
- No required database for core tracker (file-backed core is mandatory).
|
|
40
|
+
- Export to Beads is not required in v1 (import only).
|
|
41
|
+
|
|
42
|
+
## 4) Authoritative Inputs and Design Findings
|
|
43
|
+
|
|
44
|
+
### 4.1 Local authoritative references analyzed
|
|
45
|
+
|
|
46
|
+
1. `todos.ts` (local Pi extension implementation)
|
|
47
|
+
2. `.beads/issues.jsonl` (local Beads-style JSONL data)
|
|
48
|
+
|
|
49
|
+
### 4.2 Upstream inspirations analyzed (conceptual only)
|
|
50
|
+
|
|
51
|
+
- mitsuhiko todos extension
|
|
52
|
+
- beads repository/docs
|
|
53
|
+
- TOON docs/spec guidance for LLM output conventions
|
|
54
|
+
|
|
55
|
+
### 4.3 Key findings adopted
|
|
56
|
+
|
|
57
|
+
From `todos.ts`:
|
|
58
|
+
|
|
59
|
+
- Item file format = JSON front-matter at file start, blank line, then markdown body.
|
|
60
|
+
- ID normalization accepts optional `#` and optional prefix.
|
|
61
|
+
- Claim/release is represented in-record (`assignee`).
|
|
62
|
+
- Locking model:
|
|
63
|
+
- lock file created with exclusive open (`wx`)
|
|
64
|
+
- TTL-based stale-lock handling
|
|
65
|
+
- lock metadata includes PID/owner/timestamp
|
|
66
|
+
- Safe-write ergonomics should provide clear conflict errors.
|
|
67
|
+
|
|
68
|
+
From local Beads JSONL:
|
|
69
|
+
|
|
70
|
+
- `issue_type`, `priority`, `status`, `created_at`, `updated_at` are strongly present.
|
|
71
|
+
- Common extra fields include: `description`, `acceptance_criteria`, `notes`, `comments`, `dependencies`, `close_reason`, `estimated_minutes`.
|
|
72
|
+
- Dependency records frequently carry relation kinds (`blocks`, `parent-child`, `discovered-from`, `related`), timestamps, and author.
|
|
73
|
+
- IDs may include hierarchical suffixes (`prefix-hash.1.2`), so importer must preserve non-flat IDs.
|
|
74
|
+
|
|
75
|
+
From TOON guidance:
|
|
76
|
+
|
|
77
|
+
- Show structure directly, keep deterministic layout, and preserve strict machine parseability.
|
|
78
|
+
- Keep output schema stable and field ordering deterministic.
|
|
79
|
+
- JSON fallback should be exact semantic equivalent of TOON output object.
|
|
80
|
+
|
|
81
|
+
## 5) Core Concepts
|
|
82
|
+
|
|
83
|
+
### 5.1 Item Types (canonical)
|
|
84
|
+
|
|
85
|
+
- `Epic`
|
|
86
|
+
- `Feature`
|
|
87
|
+
- `Task`
|
|
88
|
+
- `Chore`
|
|
89
|
+
- `Issue`
|
|
90
|
+
|
|
91
|
+
### 5.2 Status lifecycle
|
|
92
|
+
|
|
93
|
+
Allowed values:
|
|
94
|
+
|
|
95
|
+
- `draft`
|
|
96
|
+
- `open`
|
|
97
|
+
- `in_progress`
|
|
98
|
+
- `blocked`
|
|
99
|
+
- `closed`
|
|
100
|
+
- `canceled`
|
|
101
|
+
|
|
102
|
+
Lifecycle rules:
|
|
103
|
+
|
|
104
|
+
- Any non-terminal status may transition to `canceled` via `pm update <ID> --status canceled`.
|
|
105
|
+
- Any non-terminal status may transition to `closed` only via `pm close <ID> <TEXT>`.
|
|
106
|
+
- `pm update <ID> --status closed` is invalid usage and returns exit code `2`.
|
|
107
|
+
- `closed` and `canceled` are terminal unless explicitly restored or reopened.
|
|
108
|
+
- `close` command must write `close_reason`.
|
|
109
|
+
- `claim` on terminal status fails unless explicitly overridden by `--force`.
|
|
110
|
+
|
|
111
|
+
### 5.3 Ownership model
|
|
112
|
+
|
|
113
|
+
- Ownership marker is `assignee`.
|
|
114
|
+
- `pm claim <id>` sets ownership to current mutation author identity.
|
|
115
|
+
- `pm release <id>` clears ownership.
|
|
116
|
+
- Mutations against items assigned to another assignee return conflict unless `--force`.
|
|
117
|
+
|
|
118
|
+
### 5.4 Dependencies model
|
|
119
|
+
|
|
120
|
+
Each dependency entry:
|
|
121
|
+
|
|
122
|
+
- `id: string`
|
|
123
|
+
- `kind: "blocks" | "parent" | "child" | "related" | "discovered_from"`
|
|
124
|
+
- `created_at: ISO timestamp`
|
|
125
|
+
- `author?: string`
|
|
126
|
+
|
|
127
|
+
Semantics:
|
|
128
|
+
|
|
129
|
+
- `blocks`: this item blocks target item OR is blocked by target based on command context; CLI sugar resolves direction.
|
|
130
|
+
- `parent` / `child`: hierarchy graph links.
|
|
131
|
+
- `related`: non-blocking relation.
|
|
132
|
+
- `discovered_from`: provenance trail.
|
|
133
|
+
|
|
134
|
+
### 5.5 Notes, learnings, comments
|
|
135
|
+
|
|
136
|
+
These are append-friendly audit fields:
|
|
137
|
+
|
|
138
|
+
- `comments`: user-visible conversational updates.
|
|
139
|
+
- `notes`: implementation observations.
|
|
140
|
+
- `learnings`: post-task durable findings.
|
|
141
|
+
|
|
142
|
+
All append operations produce history entries.
|
|
143
|
+
|
|
144
|
+
## 6) On-Disk Storage Layout
|
|
145
|
+
|
|
146
|
+
Default project root: `.agents/pm`
|
|
147
|
+
Override for command invocation: `PM_PATH` or `--path`.
|
|
148
|
+
|
|
149
|
+
Global extension root: `~/.pm-cli`
|
|
150
|
+
Override: `PM_GLOBAL_PATH`.
|
|
151
|
+
|
|
152
|
+
Required baseline:
|
|
153
|
+
|
|
154
|
+
```text
|
|
155
|
+
.agents/pm/
|
|
156
|
+
settings.json
|
|
157
|
+
epics/
|
|
158
|
+
<id>.md
|
|
159
|
+
features/
|
|
160
|
+
<id>.md
|
|
161
|
+
tasks/
|
|
162
|
+
<id>.md
|
|
163
|
+
chores/
|
|
164
|
+
<id>.md
|
|
165
|
+
issues/
|
|
166
|
+
<id>.md
|
|
167
|
+
history/
|
|
168
|
+
<id>.jsonl
|
|
169
|
+
index/
|
|
170
|
+
manifest.json
|
|
171
|
+
search/
|
|
172
|
+
embeddings.jsonl
|
|
173
|
+
extensions/
|
|
174
|
+
...
|
|
175
|
+
locks/
|
|
176
|
+
<id>.lock
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Notes:
|
|
180
|
+
|
|
181
|
+
- `index/manifest.json` and `search/embeddings.jsonl` are optional caches and can be rebuilt.
|
|
182
|
+
- `history/<id>.jsonl` is append-only and required once item exists.
|
|
183
|
+
- `locks/` is the canonical lock location for v1.
|
|
184
|
+
|
|
185
|
+
### 6.1 Source layout for release-ready maintainability
|
|
186
|
+
|
|
187
|
+
Implementation source tree MUST separate CLI wiring from domain logic:
|
|
188
|
+
|
|
189
|
+
```text
|
|
190
|
+
src/
|
|
191
|
+
cli/
|
|
192
|
+
main.ts
|
|
193
|
+
commands/
|
|
194
|
+
core/
|
|
195
|
+
fs/
|
|
196
|
+
history/
|
|
197
|
+
item/
|
|
198
|
+
lock/
|
|
199
|
+
output/
|
|
200
|
+
store/
|
|
201
|
+
types/
|
|
202
|
+
tests/
|
|
203
|
+
unit/
|
|
204
|
+
integration/
|
|
205
|
+
scripts/
|
|
206
|
+
install.sh
|
|
207
|
+
install.ps1
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
Constraints:
|
|
211
|
+
|
|
212
|
+
- Public CLI entry remains stable through npm `bin` mapping (`pm` -> built CLI entry).
|
|
213
|
+
- Deterministic serialization semantics are unchanged by module movement.
|
|
214
|
+
- Integration tests execute built CLI in subprocesses against temporary sandbox paths only.
|
|
215
|
+
|
|
216
|
+
## 7) Item File Format
|
|
217
|
+
|
|
218
|
+
Each item is one markdown file at `<type-folder>/<id>.md`.
|
|
219
|
+
|
|
220
|
+
Format:
|
|
221
|
+
|
|
222
|
+
1. JSON object front-matter (pretty-printed, 2-space indent, stable key order).
|
|
223
|
+
2. One blank line.
|
|
224
|
+
3. Optional markdown body.
|
|
225
|
+
|
|
226
|
+
### 7.1 Canonical front-matter schema
|
|
227
|
+
|
|
228
|
+
Required fields:
|
|
229
|
+
|
|
230
|
+
- `id: string`
|
|
231
|
+
- `title: string`
|
|
232
|
+
- `description: string`
|
|
233
|
+
- `tags: string[]`
|
|
234
|
+
- `status: "draft" | "open" | "in_progress" | "blocked" | "closed" | "canceled"`
|
|
235
|
+
- `priority: 0 | 1 | 2 | 3 | 4`
|
|
236
|
+
- `type: "Epic" | "Feature" | "Task" | "Chore" | "Issue"`
|
|
237
|
+
- `created_at: ISO string`
|
|
238
|
+
- `updated_at: ISO string`
|
|
239
|
+
|
|
240
|
+
Optional fields:
|
|
241
|
+
|
|
242
|
+
- `assignee?: string`
|
|
243
|
+
- `deadline?: ISO string` (relative input resolved to ISO at write time)
|
|
244
|
+
- `dependencies?: Dependency[]`
|
|
245
|
+
- `comments?: Comment[]`
|
|
246
|
+
- `author?: string`
|
|
247
|
+
- `acceptance_criteria?: string`
|
|
248
|
+
- `definition_of_ready?: string`
|
|
249
|
+
- `order?: number`
|
|
250
|
+
- `goal?: string`
|
|
251
|
+
- `objective?: string`
|
|
252
|
+
- `value?: string`
|
|
253
|
+
- `impact?: string`
|
|
254
|
+
- `outcome?: string`
|
|
255
|
+
- `why_now?: string`
|
|
256
|
+
- `notes?: LogNote[]`
|
|
257
|
+
- `learnings?: LogNote[]`
|
|
258
|
+
- `files?: LinkedFile[]`
|
|
259
|
+
- `tests?: LinkedTest[]`
|
|
260
|
+
- `docs?: LinkedDoc[]`
|
|
261
|
+
- `estimated_minutes?: number`
|
|
262
|
+
- `parent?: string` (item ID reference; shorthand for a `kind=parent` dependency)
|
|
263
|
+
- `reviewer?: string`
|
|
264
|
+
- `risk?: "low" | "medium" | "high" | "critical"`
|
|
265
|
+
- `confidence?: 0..100 | "low" | "medium" | "high"`
|
|
266
|
+
- `sprint?: string`
|
|
267
|
+
- `release?: string`
|
|
268
|
+
- `blocked_by?: string` (item ID reference or free-text reason)
|
|
269
|
+
- `blocked_reason?: string`
|
|
270
|
+
- `unblock_note?: string`
|
|
271
|
+
- `reporter?: string`
|
|
272
|
+
- `severity?: "low" | "medium" | "high" | "critical"`
|
|
273
|
+
- `environment?: string`
|
|
274
|
+
- `repro_steps?: string`
|
|
275
|
+
- `resolution?: string`
|
|
276
|
+
- `expected_result?: string`
|
|
277
|
+
- `actual_result?: string`
|
|
278
|
+
- `affected_version?: string`
|
|
279
|
+
- `fixed_version?: string`
|
|
280
|
+
- `component?: string`
|
|
281
|
+
- `regression?: boolean`
|
|
282
|
+
- `customer_impact?: string`
|
|
283
|
+
- `close_reason?: string`
|
|
284
|
+
|
|
285
|
+
Types:
|
|
286
|
+
|
|
287
|
+
- `Dependency = { id: string; kind: "blocks" | "parent" | "child" | "related" | "discovered_from"; created_at: string; author?: string }`
|
|
288
|
+
- `Comment = { created_at: string; author: string; text: string }`
|
|
289
|
+
- `LogNote = { created_at: string; author: string; text: string }`
|
|
290
|
+
- `LinkedFile = { path: string; scope: "project" | "global"; note?: string }`
|
|
291
|
+
- `LinkedTest = { command?: string; path?: string; scope: "project" | "global"; timeout_seconds?: number; note?: string }`
|
|
292
|
+
- `LinkedDoc = { path: string; scope: "project" | "global"; note?: string }`
|
|
293
|
+
- `IssueSeverity = "low" | "medium" | "high" | "critical"`
|
|
294
|
+
|
|
295
|
+
### 7.2 Canonical key order
|
|
296
|
+
|
|
297
|
+
Keys MUST serialize in this order:
|
|
298
|
+
|
|
299
|
+
1. `id`
|
|
300
|
+
2. `title`
|
|
301
|
+
3. `description`
|
|
302
|
+
4. `type`
|
|
303
|
+
5. `status`
|
|
304
|
+
6. `priority`
|
|
305
|
+
7. `tags`
|
|
306
|
+
8. `created_at`
|
|
307
|
+
9. `updated_at`
|
|
308
|
+
10. `deadline`
|
|
309
|
+
11. `assignee`
|
|
310
|
+
12. `author`
|
|
311
|
+
13. `estimated_minutes`
|
|
312
|
+
14. `acceptance_criteria`
|
|
313
|
+
15. `definition_of_ready`
|
|
314
|
+
16. `order`
|
|
315
|
+
17. `goal`
|
|
316
|
+
18. `objective`
|
|
317
|
+
19. `value`
|
|
318
|
+
20. `impact`
|
|
319
|
+
21. `outcome`
|
|
320
|
+
22. `why_now`
|
|
321
|
+
23. `parent`
|
|
322
|
+
24. `reviewer`
|
|
323
|
+
25. `risk`
|
|
324
|
+
26. `confidence`
|
|
325
|
+
27. `sprint`
|
|
326
|
+
28. `release`
|
|
327
|
+
29. `blocked_by`
|
|
328
|
+
30. `blocked_reason`
|
|
329
|
+
31. `unblock_note`
|
|
330
|
+
32. `reporter`
|
|
331
|
+
33. `severity`
|
|
332
|
+
34. `environment`
|
|
333
|
+
35. `repro_steps`
|
|
334
|
+
36. `resolution`
|
|
335
|
+
37. `expected_result`
|
|
336
|
+
38. `actual_result`
|
|
337
|
+
39. `affected_version`
|
|
338
|
+
40. `fixed_version`
|
|
339
|
+
41. `component`
|
|
340
|
+
42. `regression`
|
|
341
|
+
43. `customer_impact`
|
|
342
|
+
44. `dependencies`
|
|
343
|
+
45. `comments`
|
|
344
|
+
46. `notes`
|
|
345
|
+
47. `learnings`
|
|
346
|
+
48. `files`
|
|
347
|
+
49. `tests`
|
|
348
|
+
50. `docs`
|
|
349
|
+
51. `close_reason`
|
|
350
|
+
|
|
351
|
+
Unset optional fields are omitted.
|
|
352
|
+
|
|
353
|
+
### 7.3 Determinism rules
|
|
354
|
+
|
|
355
|
+
- `updated_at` MUST change for every mutation.
|
|
356
|
+
- Relative deadlines (`+6h`, `+1d`, `+2w`) resolve on write and persist as absolute ISO.
|
|
357
|
+
- `tags` sorted lexicographically, deduplicated.
|
|
358
|
+
- `risk` CLI input alias `med` normalizes to canonical stored value `medium`.
|
|
359
|
+
- `confidence` CLI input accepts integers `0..100` or `low|med|medium|high`; `med` persists as `medium`.
|
|
360
|
+
- `severity` CLI input alias `med` normalizes to canonical stored value `medium`.
|
|
361
|
+
- `dependencies`, `comments`, `notes`, `learnings` sorted by `created_at` ascending; stable tie-break by text/id.
|
|
362
|
+
- `files` sorted by `scope` asc, then `path` asc, then `note` asc.
|
|
363
|
+
- `tests` sorted by `scope` asc, then `path` asc, then `command` asc, then `timeout_seconds` asc, then `note` asc.
|
|
364
|
+
- `docs` sorted by `scope` asc, then `path` asc, then `note` asc.
|
|
365
|
+
- Paths normalized to forward-slash logical form for storage while preserving OS-correct access at runtime.
|
|
366
|
+
- For optional create/update fields, explicit unset intent is supported via sentinel values:
|
|
367
|
+
- scalar option value `none` (case-insensitive) means "unset/omit field"
|
|
368
|
+
- these intents MUST be represented in `changed_fields` and history `message`.
|
|
369
|
+
|
|
370
|
+
### 7.4 Example item file
|
|
371
|
+
|
|
372
|
+
```markdown
|
|
373
|
+
{
|
|
374
|
+
"id": "pm-a1b2",
|
|
375
|
+
"title": "Implement restore command",
|
|
376
|
+
"description": "Add full RFC6902 replay restore with hash verification.",
|
|
377
|
+
"type": "Task",
|
|
378
|
+
"status": "in_progress",
|
|
379
|
+
"priority": 1,
|
|
380
|
+
"tags": [
|
|
381
|
+
"history",
|
|
382
|
+
"reliability"
|
|
383
|
+
],
|
|
384
|
+
"created_at": "2026-02-17T10:00:00.000Z",
|
|
385
|
+
"updated_at": "2026-02-17T11:15:03.120Z",
|
|
386
|
+
"assignee": "maintainer-agent",
|
|
387
|
+
"author": "steve",
|
|
388
|
+
"acceptance_criteria": "Restore reproduces exact file content at target version.",
|
|
389
|
+
"dependencies": [
|
|
390
|
+
{
|
|
391
|
+
"id": "pm-9c8d",
|
|
392
|
+
"kind": "blocks",
|
|
393
|
+
"created_at": "2026-02-17T10:02:31.000Z",
|
|
394
|
+
"author": "steve"
|
|
395
|
+
}
|
|
396
|
+
],
|
|
397
|
+
"tests": [
|
|
398
|
+
{
|
|
399
|
+
"command": "pnpm test history",
|
|
400
|
+
"scope": "project",
|
|
401
|
+
"timeout_seconds": 90
|
|
402
|
+
}
|
|
403
|
+
]
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
Implement strict replay logic and integrity checks.
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
## 8) ID Strategy
|
|
410
|
+
|
|
411
|
+
### 8.1 Format
|
|
412
|
+
|
|
413
|
+
- Default prefix: `pm-`
|
|
414
|
+
- Init-time custom prefix supported via `pm init [PREFIX]`
|
|
415
|
+
- Canonical generated leaf: `<prefix><token>` where token is short lowercase base32/base36.
|
|
416
|
+
- Valid imported IDs may include hierarchical suffixes (`.1`, `.1.2`) and MUST be preserved.
|
|
417
|
+
|
|
418
|
+
### 8.2 Generation
|
|
419
|
+
|
|
420
|
+
- Generate cryptographically secure random bytes.
|
|
421
|
+
- Encode to lowercase base32/base36 token (default length 4 for readability).
|
|
422
|
+
- Validate non-existence in all type folders.
|
|
423
|
+
- Retry with bounded attempts; on repeated collision, increase token length.
|
|
424
|
+
|
|
425
|
+
### 8.3 Normalization
|
|
426
|
+
|
|
427
|
+
Input normalization MUST:
|
|
428
|
+
|
|
429
|
+
- Trim whitespace
|
|
430
|
+
- Accept optional leading `#`
|
|
431
|
+
- Accept ID with or without configured prefix
|
|
432
|
+
- Return canonical stored ID string
|
|
433
|
+
|
|
434
|
+
Examples (prefix `pm-`):
|
|
435
|
+
|
|
436
|
+
- `#a1b2` -> `pm-a1b2`
|
|
437
|
+
- `a1b2` -> `pm-a1b2`
|
|
438
|
+
- `pm-a1b2` -> `pm-a1b2`
|
|
439
|
+
- `PM-A1B2` -> `pm-a1b2`
|
|
440
|
+
|
|
441
|
+
## 9) History and Restore (Hard Requirement)
|
|
442
|
+
|
|
443
|
+
### 9.1 History file
|
|
444
|
+
|
|
445
|
+
Path: `.agents/pm/history/<id>.jsonl`
|
|
446
|
+
Append-only; never rewritten for normal operations.
|
|
447
|
+
|
|
448
|
+
Each line:
|
|
449
|
+
|
|
450
|
+
- `ts: ISO timestamp`
|
|
451
|
+
- `author: string`
|
|
452
|
+
- `op: string` (`create`, `update`, `append`, `comment_add`, `files_add`, `restore`, etc.)
|
|
453
|
+
- `patch: RFC6902[]` (from previous state to next state on canonical document object)
|
|
454
|
+
- `before_hash: string`
|
|
455
|
+
- `after_hash: string`
|
|
456
|
+
- `message?: string`
|
|
457
|
+
|
|
458
|
+
Canonical patch document shape:
|
|
459
|
+
|
|
460
|
+
```json
|
|
461
|
+
{
|
|
462
|
+
"front_matter": { "...": "..." },
|
|
463
|
+
"body": "markdown text"
|
|
464
|
+
}
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
### 9.2 Hashing
|
|
468
|
+
|
|
469
|
+
- Hash algorithm: SHA-256
|
|
470
|
+
- Input: canonical JSON serialization of patch document (stable key order, UTF-8 LF)
|
|
471
|
+
- Digest format: lowercase hex
|
|
472
|
+
|
|
473
|
+
### 9.3 Restore algorithm
|
|
474
|
+
|
|
475
|
+
`pm restore <ID> <TIMESTAMP|VERSION>`
|
|
476
|
+
|
|
477
|
+
1. Resolve item and load full history.
|
|
478
|
+
2. Replay patches from initial create through target version/timestamp.
|
|
479
|
+
3. Rebuild exact canonical document (`front_matter` + `body`).
|
|
480
|
+
4. Write item atomically.
|
|
481
|
+
5. Append a `restore` history event with patch from pre-restore state to restored state.
|
|
482
|
+
|
|
483
|
+
Guarantees:
|
|
484
|
+
|
|
485
|
+
- History is immutable (restore appends, never rewrites old entries).
|
|
486
|
+
- Restored item bytes match canonical serialization of target state exactly.
|
|
487
|
+
|
|
488
|
+
## 10) Concurrency, Claiming, Locking, Safe Writes
|
|
489
|
+
|
|
490
|
+
### 10.1 Assignee identity
|
|
491
|
+
|
|
492
|
+
- If `--author` is provided for a mutating command, that value is the active assignee identity.
|
|
493
|
+
- Else if `PM_AUTHOR` is set, use it.
|
|
494
|
+
- Else use `settings.author_default`.
|
|
495
|
+
- Else fallback to `"unknown"`.
|
|
496
|
+
|
|
497
|
+
### 10.2 Lock file format
|
|
498
|
+
|
|
499
|
+
Path: `.agents/pm/locks/<id>.lock`
|
|
500
|
+
|
|
501
|
+
```json
|
|
502
|
+
{
|
|
503
|
+
"id": "pm-a1b2",
|
|
504
|
+
"pid": 12345,
|
|
505
|
+
"owner": "maintainer-agent",
|
|
506
|
+
"created_at": "2026-02-17T11:15:03.120Z",
|
|
507
|
+
"ttl_seconds": 1800
|
|
508
|
+
}
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
### 10.3 Lock behavior
|
|
512
|
+
|
|
513
|
+
- Acquire lock via exclusive open.
|
|
514
|
+
- If lock exists and not stale -> conflict exit code `4`.
|
|
515
|
+
- If stale:
|
|
516
|
+
- without `--force`: conflict with stale-lock hint
|
|
517
|
+
- with `--force`: steal lock and continue
|
|
518
|
+
|
|
519
|
+
### 10.4 Atomic write contract
|
|
520
|
+
|
|
521
|
+
For any mutation:
|
|
522
|
+
|
|
523
|
+
1. Acquire lock.
|
|
524
|
+
2. Read current item.
|
|
525
|
+
3. Compute `before_hash`.
|
|
526
|
+
4. Apply mutation to in-memory canonical model.
|
|
527
|
+
5. Update `updated_at`.
|
|
528
|
+
6. Compute patch and `after_hash`.
|
|
529
|
+
7. Write item to temp file in same filesystem.
|
|
530
|
+
8. `rename` temp -> target (atomic replace).
|
|
531
|
+
9. Append history line atomically.
|
|
532
|
+
10. Release lock.
|
|
533
|
+
|
|
534
|
+
If any step fails, return non-zero exit code and preserve prior item bytes.
|
|
535
|
+
|
|
536
|
+
## 11) Command Surface and Exit Codes
|
|
537
|
+
|
|
538
|
+
### 11.1 Global flags (all commands)
|
|
539
|
+
|
|
540
|
+
- `--json` output JSON instead of TOON
|
|
541
|
+
- `--quiet` suppress stdout
|
|
542
|
+
- `--path <dir>` override project root path for invocation
|
|
543
|
+
- `--no-extensions` disable extension loading
|
|
544
|
+
- `--profile` print deterministic timing diagnostics (stderr)
|
|
545
|
+
- `--version` print CLI version
|
|
546
|
+
|
|
547
|
+
### 11.2 Exit codes
|
|
548
|
+
|
|
549
|
+
- `0` success
|
|
550
|
+
- `1` generic failure
|
|
551
|
+
- `2` usage / invalid args
|
|
552
|
+
- `3` not found
|
|
553
|
+
- `4` conflict (claim/lock/ownership)
|
|
554
|
+
- `5` dependency failed (for orchestration/test-all failures)
|
|
555
|
+
|
|
556
|
+
### 11.3 Core commands (required for v0.1 release-ready scope)
|
|
557
|
+
|
|
558
|
+
- `pm init [<PREFIX>]`
|
|
559
|
+
- `pm install pi [--project|--global]`
|
|
560
|
+
- `pm list`
|
|
561
|
+
- `pm list-all`
|
|
562
|
+
- `pm list-draft`
|
|
563
|
+
- `pm list-open`
|
|
564
|
+
- `pm list-in-progress`
|
|
565
|
+
- `pm list-blocked`
|
|
566
|
+
- `pm list-closed`
|
|
567
|
+
- `pm list-canceled`
|
|
568
|
+
- `pm get <ID>`
|
|
569
|
+
- `pm search <keywords>`
|
|
570
|
+
- `pm reindex`
|
|
571
|
+
- `pm create`
|
|
572
|
+
- `pm update <ID>`
|
|
573
|
+
- `pm append <ID>`
|
|
574
|
+
- `pm claim <ID>`
|
|
575
|
+
- `pm release <ID>`
|
|
576
|
+
- `pm delete <ID>`
|
|
577
|
+
- `pm comments <ID>`
|
|
578
|
+
- `pm files <ID>`
|
|
579
|
+
- `pm docs <ID>`
|
|
580
|
+
- `pm test <ID>`
|
|
581
|
+
- `pm test-all`
|
|
582
|
+
- `pm stats`
|
|
583
|
+
- `pm health`
|
|
584
|
+
- `pm gc`
|
|
585
|
+
- `pm history <ID>`
|
|
586
|
+
- `pm activity`
|
|
587
|
+
- `pm restore <ID> <TIMESTAMP|VERSION>`
|
|
588
|
+
- `pm config <project|global> set definition-of-done --criterion <text>`
|
|
589
|
+
- `pm config <project|global> get definition-of-done`
|
|
590
|
+
- `pm close <ID> <TEXT>`
|
|
591
|
+
- `pm beads import [--file <path>]`
|
|
592
|
+
- `pm todos import [--folder <path>]`
|
|
593
|
+
- `pm todos export [--folder <path>]`
|
|
594
|
+
- `pm completion <bash|zsh|fish>`
|
|
595
|
+
|
|
596
|
+
Roadmap commands (post-v0.1, tracked but not release blockers):
|
|
597
|
+
|
|
598
|
+
- No additional command-path roadmap entries are currently defined.
|
|
599
|
+
|
|
600
|
+
### 11.4 Extended flags (minimum)
|
|
601
|
+
|
|
602
|
+
Mutating `create` (all schema fields MUST be passable explicitly):
|
|
603
|
+
|
|
604
|
+
- `--title`, `-t` (required)
|
|
605
|
+
- `--description`, `-d` (required; empty string allowed when explicitly passed)
|
|
606
|
+
- `--type` (required: `Epic|Feature|Task|Chore|Issue`)
|
|
607
|
+
- `--status`, `-s` (required)
|
|
608
|
+
- `--priority`, `-p` (required: `0..4`)
|
|
609
|
+
- `--tags` (required; explicit empty allowed)
|
|
610
|
+
- `--body`, `-b` (required; explicit empty allowed)
|
|
611
|
+
- `--deadline` (explicit; accepts ISO, relative `+6h/+1d/+2w`, or none)
|
|
612
|
+
- `--estimate`, `--estimated-minutes`, `--estimated_minutes` (explicit; accepts `0`)
|
|
613
|
+
- `--acceptance-criteria`, `--acceptance_criteria`, `--ac` (explicit; empty allowed)
|
|
614
|
+
- `--author` (explicit; fallback `PM_AUTHOR`/settings allowed)
|
|
615
|
+
- `--message` (explicit history message; empty allowed)
|
|
616
|
+
- `--assignee` (explicit; use `none` to unset)
|
|
617
|
+
- `--parent` (optional; item ID reference or `none`)
|
|
618
|
+
- `--reviewer` (optional; or `none`)
|
|
619
|
+
- `--risk` (optional; `low|med|medium|high|critical` or `none`; `med` persists as `medium`)
|
|
620
|
+
- `--confidence` (optional; `0..100|low|med|medium|high` or `none`; `med` persists as `medium`)
|
|
621
|
+
- `--sprint` (optional; or `none`)
|
|
622
|
+
- `--release` (optional; or `none`)
|
|
623
|
+
- `--blocked-by`, `--blocked_by` (optional; item ID or free-text, or `none`)
|
|
624
|
+
- `--blocked-reason`, `--blocked_reason` (optional; or `none`)
|
|
625
|
+
- `--unblock-note`, `--unblock_note` (optional; unblock rationale note, or `none`)
|
|
626
|
+
- `--reporter` (optional; issue reporter, or `none`)
|
|
627
|
+
- `--severity` (optional; `low|med|medium|high|critical`, or `none`; `med` persists as `medium`)
|
|
628
|
+
- `--environment` (optional; issue environment context, or `none`)
|
|
629
|
+
- `--repro-steps`, `--repro_steps` (optional; issue reproduction steps, or `none`)
|
|
630
|
+
- `--resolution` (optional; issue resolution summary, or `none`)
|
|
631
|
+
- `--expected-result`, `--expected_result` (optional; issue expected behavior, or `none`)
|
|
632
|
+
- `--actual-result`, `--actual_result` (optional; issue observed behavior, or `none`)
|
|
633
|
+
- `--affected-version`, `--affected_version` (optional; impacted version identifier, or `none`)
|
|
634
|
+
- `--fixed-version`, `--fixed_version` (optional; fixed version identifier, or `none`)
|
|
635
|
+
- `--component` (optional; owning component, or `none`)
|
|
636
|
+
- `--regression` (optional; boolean `true|false|1|0`, or `none`)
|
|
637
|
+
- `--customer-impact`, `--customer_impact` (optional; customer impact summary, or `none`)
|
|
638
|
+
- `--definition-of-ready`, `--definition_of_ready` (optional; explicit empty allowed; use `none` to unset)
|
|
639
|
+
- `--order`, `--rank` (optional; integer rank/order, or `none`)
|
|
640
|
+
- `--goal` (optional; or `none`)
|
|
641
|
+
- `--objective` (optional; or `none`)
|
|
642
|
+
- `--value` (optional; or `none`)
|
|
643
|
+
- `--impact` (optional; or `none`)
|
|
644
|
+
- `--outcome` (optional; or `none`)
|
|
645
|
+
- `--why-now`, `--why_now` (optional; or `none`)
|
|
646
|
+
|
|
647
|
+
Mutating `create` flags (repeatable, each required at least once; use `none` for explicit empty intent):
|
|
648
|
+
|
|
649
|
+
- `--dep` value format: `id=<id>,kind=<blocks|parent|child|related|discovered_from>,author=<a>,created_at=<iso|now>`
|
|
650
|
+
- `--comment` value format: `author=<a>,created_at=<iso|now>,text=<t>`
|
|
651
|
+
- `--note` value format: `author=<a>,created_at=<iso|now>,text=<t>`
|
|
652
|
+
- `--learning` value format: `author=<a>,created_at=<iso|now>,text=<t>`
|
|
653
|
+
- `--file` value format: `path=<p>,scope=<project|global>,note=<n?>`
|
|
654
|
+
- `--test` value format: `command=<c?>,path=<p?>,scope=<project|global>,timeout_seconds=<n?>,note=<n?>`
|
|
655
|
+
- `--doc` value format: `path=<p>,scope=<project|global>,note=<n?>`
|
|
656
|
+
|
|
657
|
+
Mutating `update` (v0.1 baseline):
|
|
658
|
+
|
|
659
|
+
- `--title`, `-t`
|
|
660
|
+
- `--description`, `-d`
|
|
661
|
+
- `--status`, `-s`
|
|
662
|
+
- `--priority`, `-p`
|
|
663
|
+
- `--type`
|
|
664
|
+
- `--tags`
|
|
665
|
+
- `--deadline`
|
|
666
|
+
- `--estimate`, `--estimated-minutes`, `--estimated_minutes`
|
|
667
|
+
- `--acceptance-criteria`, `--acceptance_criteria`, `--ac`
|
|
668
|
+
- `--assignee`
|
|
669
|
+
- `--parent`
|
|
670
|
+
- `--reviewer`
|
|
671
|
+
- `--risk` (`low|med|medium|high|critical`; `med` persists as `medium`)
|
|
672
|
+
- `--confidence` (`0..100|low|med|medium|high`; `med` persists as `medium`)
|
|
673
|
+
- `--sprint`
|
|
674
|
+
- `--release`
|
|
675
|
+
- `--blocked-by`, `--blocked_by`
|
|
676
|
+
- `--blocked-reason`, `--blocked_reason`
|
|
677
|
+
- `--unblock-note`, `--unblock_note`
|
|
678
|
+
- `--reporter`
|
|
679
|
+
- `--severity` (`low|med|medium|high|critical`; `med` persists as `medium`)
|
|
680
|
+
- `--environment`
|
|
681
|
+
- `--repro-steps`, `--repro_steps`
|
|
682
|
+
- `--resolution`
|
|
683
|
+
- `--expected-result`, `--expected_result`
|
|
684
|
+
- `--actual-result`, `--actual_result`
|
|
685
|
+
- `--affected-version`, `--affected_version`
|
|
686
|
+
- `--fixed-version`, `--fixed_version`
|
|
687
|
+
- `--component`
|
|
688
|
+
- `--regression` (`true|false|1|0`)
|
|
689
|
+
- `--customer-impact`, `--customer_impact`
|
|
690
|
+
- `--definition-of-ready`, `--definition_of_ready`
|
|
691
|
+
- `--order`, `--rank`
|
|
692
|
+
- `--goal`
|
|
693
|
+
- `--objective`
|
|
694
|
+
- `--value`
|
|
695
|
+
- `--impact`
|
|
696
|
+
- `--outcome`
|
|
697
|
+
- `--why-now`, `--why_now`
|
|
698
|
+
- `--author`
|
|
699
|
+
- `--message`
|
|
700
|
+
|
|
701
|
+
`pm update` status semantics:
|
|
702
|
+
|
|
703
|
+
- `--status` supports all non-terminal values plus `canceled`.
|
|
704
|
+
- `--status closed` is not supported; callers must use `pm close <ID> <TEXT>` so `close_reason` is always captured.
|
|
705
|
+
|
|
706
|
+
List/search filters:
|
|
707
|
+
|
|
708
|
+
- `--type`
|
|
709
|
+
- `--tag`
|
|
710
|
+
- `--priority`
|
|
711
|
+
- `--deadline-before`
|
|
712
|
+
- `--deadline-after`
|
|
713
|
+
- `--assignee` (exact match on `assignee` field; use `none` to filter for unassigned items)
|
|
714
|
+
- `--sprint` (exact match on `sprint` field)
|
|
715
|
+
- `--release` (exact match on `release` field)
|
|
716
|
+
|
|
717
|
+
Mutation safety:
|
|
718
|
+
|
|
719
|
+
- `--author`
|
|
720
|
+
- `--message`
|
|
721
|
+
- `--force`
|
|
722
|
+
|
|
723
|
+
### 11.5 Command input/output contracts
|
|
724
|
+
|
|
725
|
+
All commands return deterministic top-level objects (TOON by default, JSON with `--json`).
|
|
726
|
+
|
|
727
|
+
| Command | Key inputs | Output object |
|
|
728
|
+
| --- | --- | --- |
|
|
729
|
+
| `pm init [PREFIX]` | optional prefix, `--path` | `{ ok, path, settings, created_dirs, warnings }` |
|
|
730
|
+
| `pm install pi [--project\|--global]` | install target (`pi`) + optional scope flags (`--project` default writes to `<project-root>/.pi/extensions/pm-cli/index.ts` where `project-root` is derived from `--path` when provided, otherwise current working directory; `--global` uses `PI_CODING_AGENT_DIR` or `~/.pi/agent`) | `{ ok, target, scope, source_path, destination_path, overwritten, warnings }` |
|
|
731
|
+
| `pm list` | optional filter flags; excludes terminal statuses (`closed`, `canceled`) by default | `{ items, count, filters, now }` |
|
|
732
|
+
| `pm list-all` | optional filter flags; includes all statuses including terminal | `{ items, count, filters, now }` |
|
|
733
|
+
| `pm list-draft` | optional type/tag/priority/deadline/assignee/sprint/release filters | `{ items, count, filters, now }` |
|
|
734
|
+
| `pm list-open` | optional type/tag/priority/deadline/assignee/sprint/release filters | `{ items, count, filters, now }` |
|
|
735
|
+
| `pm list-in-progress` | same as above | `{ items, count, filters, now }` |
|
|
736
|
+
| `pm list-blocked` | same as above | `{ items, count, filters, now }` |
|
|
737
|
+
| `pm list-closed` | same as above | `{ items, count, filters, now }` |
|
|
738
|
+
| `pm list-canceled` | same as above | `{ items, count, filters, now }` |
|
|
739
|
+
| `pm get <ID>` | normalized id | `{ item, body, linked: { files, tests, docs } }` |
|
|
740
|
+
| `pm search <keywords>` | keyword query + optional mode/include-linked/limit filters | `{ query, mode, items, count, filters, now }` |
|
|
741
|
+
| `pm reindex` | optional `--mode` (`keyword|semantic|hybrid` baseline) | `{ ok, mode, total_items, artifacts, warnings, generated_at }` |
|
|
742
|
+
| `pm beads import --file <path?>` | optional Beads JSONL source path (defaults to `.beads/issues.jsonl`) | `{ ok, source, imported, skipped, ids, warnings }` |
|
|
743
|
+
| `pm todos import --folder <path?>` | optional todos markdown source folder (defaults to `.pi/todos`); preserves canonical optional `ItemFrontMatter` metadata when present and applies deterministic defaults for missing PM fields | `{ ok, folder, imported, skipped, ids, warnings }` |
|
|
744
|
+
| `pm todos export --folder <path?>` | optional todos markdown destination folder (defaults to `.pi/todos`) | `{ ok, folder, exported, ids, warnings }` |
|
|
745
|
+
| `pm create ...` | required title + schema flags | `{ item, changed_fields, warnings }` |
|
|
746
|
+
| `pm update <ID> ...` | id + patch-like flags (`--status closed` is rejected; use `pm close <ID> <TEXT>`) | `{ item, changed_fields, warnings }` |
|
|
747
|
+
| `pm delete <ID>` | id + optional `--author`/`--message`/`--force` | `{ item, changed_fields, warnings }` |
|
|
748
|
+
| `pm close <ID> <TEXT>` | id + close reason text + optional `--author/--message/--force` | `{ item, changed_fields, warnings }` |
|
|
749
|
+
| `pm append <ID> --body` | id + appended markdown | `{ item, appended, changed_fields }` |
|
|
750
|
+
| `pm claim <ID>` | id, optional `--author`/`--message`/`--force` | `{ item, claimed_by, previous_assignee, forced }` |
|
|
751
|
+
| `pm release <ID>` | id, optional `--author`/`--message`/`--force` | `{ item, released_by, previous_assignee, forced }` |
|
|
752
|
+
| `pm comments <ID> --add/--limit` | id + comment text/limit | `{ id, comments, count }` |
|
|
753
|
+
| `pm files <ID> --add/--remove` | id + file refs | `{ id, files, changed, count }` |
|
|
754
|
+
| `pm test <ID> --add/--remove/--run` | id + test refs/options (reject recursive `test-all` linked commands at add-time, including global-flag and package-spec launcher forms such as `pm --json test-all`, `npx @unbrained/pm-cli@latest --json test-all`, `pnpm dlx @unbrained/pm-cli@latest --json test-all`, and `npm exec -- @unbrained/pm-cli@latest --json test-all`, defensively skip legacy recursive entries at run-time, and reject sandbox-unsafe test-runner commands including unsandboxed direct package-manager run-script forms such as `npm run test`/`pnpm run test` and chained direct runner segments evaluated independently) | `{ id, tests, run_results, changed, count }` |
|
|
755
|
+
| `pm test-all --status --timeout` | optional status filter; duplicate linked command/path entries are deduped per invocation (keyed by scope+normalized command or scope+path) and reported as skipped; when duplicate keys carry different `timeout_seconds`, execution uses deterministic maximum timeout for that key | `{ totals, failed, passed, skipped, results }` |
|
|
756
|
+
| `pm stats` | none | `{ totals, by_type, by_status, generated_at }` |
|
|
757
|
+
| `pm health` | none | `{ ok, checks, warnings, generated_at }` |
|
|
758
|
+
| `pm gc` | none | `{ ok, removed, retained, warnings, generated_at }` |
|
|
759
|
+
| `pm docs <ID> --add/--remove` | id + doc refs | `{ id, docs, changed, count }` |
|
|
760
|
+
| `pm history <ID> --limit` | id + optional limit | `{ id, history, count, limit }` |
|
|
761
|
+
| `pm activity --limit` | optional limit | `{ activity, count, limit }` |
|
|
762
|
+
| `pm restore <ID> <TIMESTAMP\|VERSION>` | id + restore target + optional `--author/--message/--force` | `{ item, restored_from, changed_fields, warnings }` |
|
|
763
|
+
| `pm completion <shell>` | `bash`, `zsh`, or `fish`; non-JSON output is the raw script suitable for eval or pipe; JSON output is `{ shell, script, setup_hint }` | `{ shell, script, setup_hint }` |
|
|
764
|
+
|
|
765
|
+
Roadmap output contracts remain defined in this PRD for extension areas and advanced search tuning that are still out of v0.1 release scope.
|
|
766
|
+
|
|
767
|
+
## 12) Canonical Output Objects (TOON-first)
|
|
768
|
+
|
|
769
|
+
All commands return a deterministic top-level object with stable key order.
|
|
770
|
+
|
|
771
|
+
Examples:
|
|
772
|
+
|
|
773
|
+
- `list*`:
|
|
774
|
+
- `{ items, count, filters, now }`
|
|
775
|
+
- `search`:
|
|
776
|
+
- `{ query, mode, items, count, filters, now }`
|
|
777
|
+
- `get`:
|
|
778
|
+
- `{ item, body, linked: { files, tests, docs } }`
|
|
779
|
+
- `create/update/delete`:
|
|
780
|
+
- `{ item, changed_fields, warnings }`
|
|
781
|
+
- `append`:
|
|
782
|
+
- `{ item, appended, changed_fields }`
|
|
783
|
+
- `test-all`:
|
|
784
|
+
- `{ totals, failed, passed, skipped, results }`
|
|
785
|
+
- roadmap examples (advanced semantic/hybrid tuning expansion) remain post-v0.1.
|
|
786
|
+
|
|
787
|
+
Determinism requirements:
|
|
788
|
+
|
|
789
|
+
- Stable key order in every object.
|
|
790
|
+
- Stable array order for `items` (default sort: non-terminal before terminal, then priority asc, then updated_at desc, then id asc).
|
|
791
|
+
- `pm list` excludes terminal statuses (`closed`, `canceled`) by default; `pm list-all` includes all statuses.
|
|
792
|
+
- TOON and JSON contain same logical content.
|
|
793
|
+
- `--quiet` prints nothing to stdout but still uses exit codes.
|
|
794
|
+
|
|
795
|
+
## 13) Search Architecture
|
|
796
|
+
|
|
797
|
+
### 13.0 Command contract (implemented baseline)
|
|
798
|
+
|
|
799
|
+
`pm search <keywords>` is implemented across keyword, semantic, and hybrid modes with deterministic ordering. The baseline command searches core item corpus fields, supports vector-query execution when configured, and returns stable TOON/JSON output parity.
|
|
800
|
+
|
|
801
|
+
Initial flags:
|
|
802
|
+
|
|
803
|
+
- `--mode <keyword|semantic|hybrid>` (all modes implemented baseline; advanced semantic/hybrid tuning planned)
|
|
804
|
+
- `--include-linked` (keyword mode and hybrid lexical component: include readable linked docs/files/tests content in corpus scoring)
|
|
805
|
+
- `--limit <n>`
|
|
806
|
+
- `--limit 0` is valid and returns a deterministic empty result set (after mode/config validation) without executing embedding/vector query requests
|
|
807
|
+
- shared list-like filters where applicable (`--type`, `--tag`, `--priority`, `--deadline-before`, `--deadline-after`)
|
|
808
|
+
- shared `--type` and `--priority` filters follow canonical validation (`--type` in `Epic|Feature|Task|Chore|Issue`, `--priority` integer `0..4`)
|
|
809
|
+
|
|
810
|
+
### 13.1 Modes
|
|
811
|
+
|
|
812
|
+
- `keyword` (always available)
|
|
813
|
+
- `semantic` (when embedding provider + vector store configured)
|
|
814
|
+
- `hybrid` (default if semantic available)
|
|
815
|
+
|
|
816
|
+
### 13.2 Keyword corpus fields
|
|
817
|
+
|
|
818
|
+
- `title`
|
|
819
|
+
- `description`
|
|
820
|
+
- `tags`
|
|
821
|
+
- `status`
|
|
822
|
+
- `body`
|
|
823
|
+
- `comments[].text`
|
|
824
|
+
- `notes[].text`
|
|
825
|
+
- `learnings[].text`
|
|
826
|
+
- dependency IDs/kinds
|
|
827
|
+
|
|
828
|
+
Keyword/hybrid lexical scoring baseline also applies a deterministic exact-title token boost:
|
|
829
|
+
|
|
830
|
+
- each query token found as a full token in `title` contributes an additional lexical bonus
|
|
831
|
+
- bonus is additive with existing weighted occurrence scoring and keeps deterministic tie-break ordering unchanged
|
|
832
|
+
|
|
833
|
+
`--include-linked` lexical baseline (keyword + hybrid lexical component):
|
|
834
|
+
|
|
835
|
+
- linked docs/files/tests content (project/global scope resolution, best-effort reads)
|
|
836
|
+
- linked-content reads are root-bounded by scope:
|
|
837
|
+
- `scope=project`: resolved path and symlink-resolved realpath must remain within project root
|
|
838
|
+
- `scope=global`: resolved path and symlink-resolved realpath must remain within global root
|
|
839
|
+
- out-of-scope paths or realpath escapes are ignored deterministically
|
|
840
|
+
|
|
841
|
+
### 13.3 Reindex baseline + semantic execution baseline
|
|
842
|
+
|
|
843
|
+
- `pm reindex` baseline behavior rebuilds deterministic keyword cache artifacts:
|
|
844
|
+
- `index/manifest.json` (indexed item metadata summary)
|
|
845
|
+
- `search/embeddings.jsonl` (line-delimited keyword corpus records)
|
|
846
|
+
- `pm reindex --mode semantic|hybrid` baseline generates deterministic provider embeddings for canonical item corpus records and upserts vector records to the active vector store.
|
|
847
|
+
- Semantic embedding generation in `pm reindex --mode semantic|hybrid` and mutation-triggered refresh paths executes in deterministic batches sized by `search.embedding_batch_size`, and each batch retries failed embedding requests up to `search.scanner_max_batch_retries` before surfacing deterministic warnings/errors.
|
|
848
|
+
- Successful item-mutation command paths invalidate stale keyword cache artifacts (`index/manifest.json` and `search/embeddings.jsonl`) as best-effort non-fatal cleanup before the next explicit `reindex`.
|
|
849
|
+
- Successful item-mutation command paths also perform best-effort semantic embedding refresh for affected item IDs when embedding-provider and vector-store configuration are available; when an affected ID no longer exists (for example after delete), refresh attempts prune the stale vector entry from the active store. Refresh failures degrade to deterministic warnings.
|
|
850
|
+
- Settings support:
|
|
851
|
+
- `score_threshold`
|
|
852
|
+
- `hybrid_semantic_weight`
|
|
853
|
+
- `max_results`
|
|
854
|
+
- `embedding_model`
|
|
855
|
+
- `embedding_batch_size`
|
|
856
|
+
- `scanner_max_batch_retries`
|
|
857
|
+
- `tuning` (optional object: `title_exact_bonus`, `title_weight`, `description_weight`, `tags_weight`, `status_weight`, `body_weight`, `comments_weight`, `notes_weight`, `learnings_weight`, `dependencies_weight`, `linked_content_weight`)
|
|
858
|
+
- `search.score_threshold` runtime semantics:
|
|
859
|
+
- keyword mode compares against raw lexical score
|
|
860
|
+
- semantic mode compares against vector similarity score
|
|
861
|
+
- hybrid mode compares against normalized blended score (`0..1`) after lexical+semantic combination
|
|
862
|
+
- default `0` preserves all positive-score hits
|
|
863
|
+
- `search.hybrid_semantic_weight` runtime semantics:
|
|
864
|
+
- numeric range `0..1` (out-of-range or non-numeric values fall back to default)
|
|
865
|
+
- hybrid combined score uses: `(semantic_normalized * hybrid_semantic_weight) + (keyword_normalized * (1 - hybrid_semantic_weight))`
|
|
866
|
+
- default `0.7` keeps semantic ranking primary while preserving deterministic lexical influence
|
|
867
|
+
- `search.tuning` runtime semantics:
|
|
868
|
+
- optional object controlling deterministic multi-factor lexical weighting in keyword mode and the hybrid lexical component
|
|
869
|
+
- non-numeric/negative tuning values fall back to deterministic defaults per field
|
|
870
|
+
- default weights when unset: `title_exact_bonus=10`, `title_weight=8`, `description_weight=5`, `tags_weight=6`, `status_weight=2`, `body_weight=1`, `comments_weight=1`, `notes_weight=1`, `learnings_weight=1`, `dependencies_weight=3`, `linked_content_weight=1`
|
|
871
|
+
|
|
872
|
+
### 13.4 Providers and vector stores (semantic/hybrid execution baseline)
|
|
873
|
+
|
|
874
|
+
Embedding providers:
|
|
875
|
+
|
|
876
|
+
- OpenAI-compatible (`base_url`, `api_key`, `model`)
|
|
877
|
+
- Ollama (`base_url`, `model`)
|
|
878
|
+
|
|
879
|
+
Implemented baseline:
|
|
880
|
+
|
|
881
|
+
- Deterministic provider-configuration resolution exists in core search runtime plumbing.
|
|
882
|
+
- OpenAI/Ollama provider blocks are normalized from settings and surfaced through a provider abstraction layer for command-time validation, request-target resolution (including OpenAI-compatible `base_url` normalization for root, `/v1`, and explicit `/embeddings` forms), request payload/response normalization (including deterministic OpenAI data-entry index ordering), deterministic request-execution helper behavior, deterministic per-request normalized-input deduplication with output fan-out back to original input cardinality/order, and deterministic embedding cardinality validation (normalized input count must match returned vector count after dedupe expansion).
|
|
883
|
+
- `pm search --mode semantic|hybrid` and `pm reindex --mode semantic|hybrid` use this abstraction for deterministic semantic/hybrid execution (embedding generation/request handling) after configuration validation.
|
|
884
|
+
|
|
885
|
+
Vector stores:
|
|
886
|
+
|
|
887
|
+
- Qdrant (`url`, `api_key?`)
|
|
888
|
+
- LanceDB (`path`)
|
|
889
|
+
|
|
890
|
+
Implemented baseline:
|
|
891
|
+
|
|
892
|
+
- Deterministic vector-store configuration resolution for Qdrant and LanceDB is available in core search runtime plumbing.
|
|
893
|
+
- Qdrant/LanceDB settings blocks are normalized from `settings.json` and surfaced through a vector-store abstraction layer for command-time validation.
|
|
894
|
+
- Request-target planning, request payload/response normalization, deterministic Qdrant request-execution helper behavior, deterministic LanceDB local query/upsert execution helper behavior, and deterministic query-hit ordering normalization (score desc, id asc tie-break) are available through this abstraction layer.
|
|
895
|
+
- `pm search --mode semantic|hybrid` and `pm reindex --mode semantic|hybrid` use this abstraction for deterministic vector query/upsert execution after configuration validation.
|
|
896
|
+
|
|
897
|
+
## 14) Extension Architecture
|
|
898
|
+
|
|
899
|
+
### 14.1 Locations
|
|
900
|
+
|
|
901
|
+
- Global: `~/.pm-cli/extensions` (or `PM_GLOBAL_PATH/extensions`)
|
|
902
|
+
- Project: `.agents/pm/extensions` (or `PM_PATH/extensions`)
|
|
903
|
+
|
|
904
|
+
### 14.2 Load order and precedence
|
|
905
|
+
|
|
906
|
+
1. Core built-ins
|
|
907
|
+
2. Global extensions
|
|
908
|
+
3. Project extensions
|
|
909
|
+
|
|
910
|
+
Precedence:
|
|
911
|
+
|
|
912
|
+
- Later load can override earlier by explicit command/renderer/hook keys.
|
|
913
|
+
- Project overrides global by default.
|
|
914
|
+
- Priority field in manifest may alter local ordering within same layer.
|
|
915
|
+
|
|
916
|
+
### 14.3 Extension manifest (minimum)
|
|
917
|
+
|
|
918
|
+
```json
|
|
919
|
+
{
|
|
920
|
+
"name": "pm-ext-example",
|
|
921
|
+
"version": "0.1.0",
|
|
922
|
+
"entry": "./dist/index.js",
|
|
923
|
+
"priority": 100,
|
|
924
|
+
"capabilities": [
|
|
925
|
+
"commands",
|
|
926
|
+
"schema",
|
|
927
|
+
"renderers",
|
|
928
|
+
"importers",
|
|
929
|
+
"search",
|
|
930
|
+
"hooks"
|
|
931
|
+
]
|
|
932
|
+
}
|
|
933
|
+
```
|
|
934
|
+
|
|
935
|
+
Capability declarations are enforced during extension activation. API registrations and
|
|
936
|
+
hook registrations must match declared capabilities (`commands`, `renderers`, `hooks`,
|
|
937
|
+
`schema`, `importers`, `search`) or activation fails with deterministic
|
|
938
|
+
`extension_activate_failed:<layer>:<name>` diagnostics.
|
|
939
|
+
Unknown capability names are ignored for registration gating and produce deterministic
|
|
940
|
+
discovery diagnostics `extension_capability_unknown:<layer>:<name>:<capability>`.
|
|
941
|
+
|
|
942
|
+
### 14.4 Extension API contracts (v1 draft)
|
|
943
|
+
|
|
944
|
+
v0.1 implemented baseline (release-hardening in progress):
|
|
945
|
+
|
|
946
|
+
- `activate(api)` hook registration surface is available.
|
|
947
|
+
- `api.hooks.beforeCommand/afterCommand/onWrite/onRead/onIndex` dispatch is deterministic with failure containment and per-hook context snapshot isolation.
|
|
948
|
+
- Hook registration APIs (`api.hooks.beforeCommand/afterCommand/onWrite/onRead/onIndex`) require function handlers; invalid payloads throw during extension activation and surface deterministic `extension_activate_failed:<layer>:<name>` warnings.
|
|
949
|
+
- `api.registerCommand(name, override)` supports deterministic overrides for existing core command results before output rendering; override execution receives cloned command `args`/`options`/`global` snapshots, `pm_root`, and a cloned prior result payload so extensions can apply contextual overrides without mutating caller fallback state.
|
|
950
|
+
- `api.registerCommand({ name, run })` supports deterministic extension command handlers for declared command paths, including dynamically surfaced non-core extension command paths (for example `beads import` and `acme sync`) with precedence-safe dispatch.
|
|
951
|
+
- Extension command-handler execution receives cloned `args`/`options`/`global` snapshots so handler-side mutation cannot leak into caller runtime command state.
|
|
952
|
+
- Registered extension command names are canonicalized with trim + lowercase + internal-whitespace collapse before storage and dispatch matching, ensuring equivalent command paths resolve deterministically.
|
|
953
|
+
- Required extension-command dispatch semantics are deterministic: no matched handler returns command-not-found for extension-only paths, while a matched handler throw returns generic failure with warning code `extension_command_handler_failed:<layer>:<name>:<command>`.
|
|
954
|
+
- `api.registerRenderer(format, renderer)` supports deterministic `toon`/`json` output overrides; renderer execution receives isolated command context snapshots (`command`, `args`, `options`, `global`, `pm_root`) plus an isolated result snapshot so failed renderer-side mutation cannot alter core fallback output.
|
|
955
|
+
- Extension API registration baseline now includes deterministic registration-time validation and metadata capture for `api.registerFlags`, `api.registerItemFields`, `api.registerMigration`, `api.registerImporter`, `api.registerExporter`, `api.registerSearchProvider`, and `api.registerVectorStoreAdapter`.
|
|
956
|
+
- `api.registerImporter(name, importer)` and `api.registerExporter(name, exporter)` now provide runtime command wiring in addition to metadata capture: each registration deterministically exposes extension command-handler paths `<name> import` and `<name> export` (canonicalized with trim + lowercase + internal-whitespace collapse) and executes through the same isolated command-handler context snapshots used by `api.registerCommand({ name, run })`.
|
|
957
|
+
- Dynamically surfaced extension command paths now render deterministic help metadata derived from registered `api.registerFlags(...)` definitions while preserving loose option parsing behavior for runtime command dispatch.
|
|
958
|
+
- Extension API and hook registration calls enforce manifest capability declarations (`commands`, `renderers`, `hooks`, `schema`, `importers`, `search`) and fail activation deterministically when an extension registers outside its declared capabilities.
|
|
959
|
+
- Extension activation diagnostics include deterministic registration counts and metadata summaries for the above registries (flags, item fields, migrations, importers, exporters, search providers, and vector store adapters), `pm health` exposes deterministic migration status summaries from registered migration definitions (`status="failed"` -> failed, `status="applied"` -> applied, any other/missing status -> pending), and core write command paths enforce deterministic mandatory-migration gating (`mandatory=true` + status not `"applied"` -> unresolved blocker, with `--force` bypass on force-capable write commands).
|
|
960
|
+
|
|
961
|
+
Full v1 draft surface (broader runtime wiring for remaining newly registered definitions beyond dynamic command help and importer/exporter command-path mapping remains roadmap):
|
|
962
|
+
|
|
963
|
+
```ts
|
|
964
|
+
export interface PmExtension {
|
|
965
|
+
manifest: ExtensionManifest;
|
|
966
|
+
activate(api: ExtensionApi): Promise<void> | void;
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
export interface ExtensionApi {
|
|
970
|
+
registerCommand(def: CommandDefinition): void;
|
|
971
|
+
registerFlags(targetCommand: string, flags: FlagDefinition[]): void;
|
|
972
|
+
registerItemFields(fields: SchemaFieldDefinition[]): void;
|
|
973
|
+
registerMigration(def: SchemaMigrationDefinition): void;
|
|
974
|
+
registerRenderer(format: "toon" | "json", renderer: Renderer): void;
|
|
975
|
+
registerImporter(name: string, importer: Importer): void;
|
|
976
|
+
registerExporter(name: string, exporter: Exporter): void;
|
|
977
|
+
registerSearchProvider(provider: SearchProvider): void;
|
|
978
|
+
registerVectorStoreAdapter(adapter: VectorStoreAdapter): void;
|
|
979
|
+
hooks: {
|
|
980
|
+
beforeCommand(hook: BeforeCommandHook): void;
|
|
981
|
+
afterCommand(hook: AfterCommandHook): void;
|
|
982
|
+
onWrite(hook: OnWriteHook): void;
|
|
983
|
+
onRead(hook: OnReadHook): void;
|
|
984
|
+
onIndex(hook: OnIndexHook): void;
|
|
985
|
+
};
|
|
986
|
+
}
|
|
987
|
+
```
|
|
988
|
+
|
|
989
|
+
### 14.5 Failure isolation and safety
|
|
990
|
+
|
|
991
|
+
- Extension load failure must not corrupt core data.
|
|
992
|
+
- Failed extension is marked unhealthy and reported via `pm health`.
|
|
993
|
+
- `pm health` extension checks must run safe runtime load and activation probes (including enabled built-in extensions) and emit deterministic warning codes for import/activation failures (for example `extension_load_failed:<layer>:<name>` and `extension_activate_failed:<layer>:<name>`).
|
|
994
|
+
- Extension manifest `entry` paths must resolve within the extension directory after canonical path resolution (including symlink targets); traversal/escape paths are rejected with deterministic diagnostics (for example `extension_entry_outside_extension:<layer>:<name>`).
|
|
995
|
+
- Core commands remain functional unless extension is explicitly required by invoked command.
|
|
996
|
+
|
|
997
|
+
### 14.6 Schema extension migrations
|
|
998
|
+
|
|
999
|
+
- Extensions adding front-matter fields must provide forward migrations.
|
|
1000
|
+
- Migration definitions are versioned and idempotent.
|
|
1001
|
+
- `pm health` reports deterministic migration status summaries:
|
|
1002
|
+
- `applied`: registered migrations whose definition status is `"applied"` (case-insensitive).
|
|
1003
|
+
- `pending`: registered migrations whose definition status is neither `"failed"` nor `"applied"` (or is missing).
|
|
1004
|
+
- `failed`: registered migrations whose definition status is `"failed"` (case-insensitive), with optional reason from `reason`, `error`, or `message` metadata.
|
|
1005
|
+
- Core write command paths are blocked when unresolved mandatory migrations are present from active extension registrations.
|
|
1006
|
+
- Mandatory migrations are definitions with `mandatory: true`.
|
|
1007
|
+
- Mandatory migration resolution is deterministic: `status` equal to `"applied"` (case-insensitive) is treated as resolved; any other/missing status is unresolved.
|
|
1008
|
+
- Force-capable write commands may bypass the guard with explicit `--force`; write commands without `--force` remain blocked until blockers resolve.
|
|
1009
|
+
|
|
1010
|
+
## 15) Built-in Extensions Required in v1
|
|
1011
|
+
|
|
1012
|
+
### A) Beads import
|
|
1013
|
+
|
|
1014
|
+
Command:
|
|
1015
|
+
|
|
1016
|
+
- `pm beads import [--file <path>]`
|
|
1017
|
+
|
|
1018
|
+
Current baseline status (release-hardening):
|
|
1019
|
+
|
|
1020
|
+
- Command is extension-packaged through a built-in `activate(api)` module using `api.registerCommand({ name, run })` for the `beads import` command path, with no core-command fallback path.
|
|
1021
|
+
|
|
1022
|
+
Behavior:
|
|
1023
|
+
|
|
1024
|
+
- Parse Beads JSONL records.
|
|
1025
|
+
- Map Beads fields to PM schema.
|
|
1026
|
+
- Preserve IDs and timestamps where possible.
|
|
1027
|
+
- Append history with `op: "import"`.
|
|
1028
|
+
- Default input path is `.beads/issues.jsonl` when `--file` is not provided.
|
|
1029
|
+
- Invalid JSONL lines or duplicate IDs are skipped with deterministic warnings.
|
|
1030
|
+
|
|
1031
|
+
### B) todos.ts import/export
|
|
1032
|
+
|
|
1033
|
+
Commands:
|
|
1034
|
+
|
|
1035
|
+
- `pm todos import [--folder <path>]`
|
|
1036
|
+
- `pm todos export [--folder <path>]`
|
|
1037
|
+
|
|
1038
|
+
Current baseline status (release-hardening):
|
|
1039
|
+
|
|
1040
|
+
- Commands are extension-packaged through a built-in `activate(api)` module using `api.registerCommand({ name, run })` for `todos import` and `todos export` command paths.
|
|
1041
|
+
|
|
1042
|
+
Behavior:
|
|
1043
|
+
|
|
1044
|
+
- Read/write todos markdown format (JSON front-matter + body).
|
|
1045
|
+
- Field mapping:
|
|
1046
|
+
- `title -> title`
|
|
1047
|
+
- `body -> body`
|
|
1048
|
+
- imported IDs, including hierarchical suffixes such as `pm-legacy.1.2`, are preserved verbatim when provided in todos front matter
|
|
1049
|
+
- canonical PM front-matter fields round-trip when present, including planning/workflow metadata (`definition_of_ready`, `order`, `goal`, `objective`, `value`, `impact`, `outcome`, `why_now`, `reviewer`, `risk`, `confidence`, `sprint`, `release`, `blocked_by`, `blocked_reason`, `unblock_note`) and issue metadata (`reporter`, `severity`, `environment`, `repro_steps`, `resolution`, `expected_result`, `actual_result`, `affected_version`, `fixed_version`, `component`, `regression`, `customer_impact`)
|
|
1050
|
+
- `confidence`, `risk`, and `severity` text aliases normalize deterministically (`med -> medium`)
|
|
1051
|
+
- Missing PM fields get deterministic defaults:
|
|
1052
|
+
- `description = ""`
|
|
1053
|
+
- `priority = 2`
|
|
1054
|
+
- `type = "Task"`
|
|
1055
|
+
- `updated_at = created_at (or now if missing)`
|
|
1056
|
+
|
|
1057
|
+
### C) Pi tool wrapper
|
|
1058
|
+
|
|
1059
|
+
Current baseline status (release-hardening):
|
|
1060
|
+
|
|
1061
|
+
- Implemented as a Pi agent extension source module at `.pi/extensions/pm-cli/index.ts` (outside the `pm` CLI command surface).
|
|
1062
|
+
- Registers one Pi tool named `pm` via Pi's extension API (`registerTool`) and maps `action` + command-shaped fields to `pm` CLI invocations.
|
|
1063
|
+
- Action dispatch currently covers the full v0.1 command-aligned set (`init`, `config`, `create`, `list`, `list-all`, `list-draft`, `list-open`, `list-in-progress`, `list-blocked`, `list-closed`, `list-canceled`, `get`, `search`, `reindex`, `history`, `activity`, `restore`, `update`, `close`, `delete`, `append`, `comments`, `files`, `docs`, `test`, `test-all`, `stats`, `health`, `gc`, `completion`, `claim`, `release`) plus extension action aliases (`beads-import`, `todos-import`, `todos-export`) and workflow presets (`start-task`, `pause-task`, `close-task`).
|
|
1064
|
+
- Invocation fallback order is deterministic for distribution resilience: attempt `pm` first, then fallback to packaged `node <package-root>/dist/cli.js` when `pm` is unavailable.
|
|
1065
|
+
|
|
1066
|
+
- Expose one tool `pm`.
|
|
1067
|
+
- Parameters include:
|
|
1068
|
+
- `action` enum mapped to CLI commands and workflow presets
|
|
1069
|
+
- common fields (`id`, `title`, `status`, `tags`, `body`, etc.)
|
|
1070
|
+
- completion parity field `shell` (`action=completion` -> `pm completion <shell>`)
|
|
1071
|
+
- search-specific parity fields including `mode` and `includeLinked` (`--include-linked`)
|
|
1072
|
+
- claim/release metadata parity fields including `author`, `message`, and `force` (`--author`, `--message`, `--force`)
|
|
1073
|
+
- create/update scalar parity fields using camelCase wrapper parameters that forward to the canonical CLI flags for planning/workflow metadata (`parent`, `reviewer`, `risk`, `confidence`, `sprint`, `release`, `blockedBy`, `blockedReason`, `unblockNote`, `definitionOfReady`, `order`, `goal`, `objective`, `value`, `impact`, `outcome`, `whyNow`) and issue metadata (`reporter`, `severity`, `environment`, `reproSteps`, `resolution`, `expectedResult`, `actualResult`, `affectedVersion`, `fixedVersion`, `component`, `regression`, `customerImpact`)
|
|
1074
|
+
- explicit empty-string passthrough for empty-allowed CLI flags (for example `--description ""` and `--body ""`)
|
|
1075
|
+
- numeric scalar parity for numeric CLI flags: wrapper accepts either JSON numbers or strings for `priority`, `estimate`, `limit`, and `timeout`, then stringifies values for deterministic CLI argument emission
|
|
1076
|
+
- Return object:
|
|
1077
|
+
- `content: [{ type: "text", text: <TOON or JSON string> }]`
|
|
1078
|
+
- `details: <structured object>`
|
|
1079
|
+
|
|
1080
|
+
Wrapper behavior must remain aligned with CLI semantics and exit conditions.
|
|
1081
|
+
|
|
1082
|
+
## 16) Security and Data Integrity
|
|
1083
|
+
|
|
1084
|
+
- All writes are lock-protected + atomic.
|
|
1085
|
+
- Never partially write item or history line.
|
|
1086
|
+
- Validate and normalize path inputs to prevent traversal.
|
|
1087
|
+
- `pm search --include-linked` must enforce scope-root containment on linked content reads using both resolved-path and symlink-resolved-realpath checks, and ignore linked paths that escape allowed roots.
|
|
1088
|
+
- Extension manifest `entry` paths must not escape their owning extension directory.
|
|
1089
|
+
- Dynamic extension command loose-option parsing must ignore unsafe prototype keys (`__proto__`, `constructor`, `prototype`) and use null-prototype option maps before passing option snapshots to extension command handlers.
|
|
1090
|
+
- Never execute linked test commands without explicit `--run`.
|
|
1091
|
+
- Reject linked test command entries that invoke `pm test-all` (including global-flag and package-spec launcher variants such as `pm --json test-all`, `npx @unbrained/pm-cli@latest --json test-all`, `pnpm dlx @unbrained/pm-cli@latest --json test-all`, and `npm exec -- @unbrained/pm-cli@latest --json test-all`) to prevent recursive orchestration loops.
|
|
1092
|
+
- `pm test <ID> --run` defensively skips legacy linked command entries that invoke `pm test-all` (including global-flag and package-spec launcher variants such as `npx`, `pnpm dlx`, and `npm exec` launcher forms) and records deterministic skipped results.
|
|
1093
|
+
- Reject linked test-runner command entries (for example `pnpm test`, `pnpm test:coverage`, `npm test`, `npm run test`, `pnpm run test`, `yarn run test`, `bun run test`, `vitest`) unless they use `node scripts/run-tests.mjs ...` or explicitly set both `PM_PATH` and `PM_GLOBAL_PATH`; chained direct test-runner segments are validated independently and rejected when not explicitly sandboxed.
|
|
1094
|
+
- `pm test-all` executes each unique linked command/path key at most once per run; duplicate entries are reported as skipped to keep totals deterministic while avoiding redundant execution. Duplicate-key timeout conflicts resolve deterministically to the maximum `timeout_seconds` value for that key.
|
|
1095
|
+
- Optional providers use explicit settings; secrets come from env or settings with documented precedence.
|
|
1096
|
+
- Restore must verify replay hashes and fail loudly on mismatch.
|
|
1097
|
+
|
|
1098
|
+
## 17) Configuration
|
|
1099
|
+
|
|
1100
|
+
`settings.json` baseline keys:
|
|
1101
|
+
|
|
1102
|
+
- `version`
|
|
1103
|
+
- `id_prefix`
|
|
1104
|
+
- `author_default`
|
|
1105
|
+
- `locks.ttl_seconds`
|
|
1106
|
+
- `output.default_format`
|
|
1107
|
+
- `workflow.definition_of_done[]`
|
|
1108
|
+
- `extensions.enabled[]`
|
|
1109
|
+
- `extensions.disabled[]`
|
|
1110
|
+
- `search.score_threshold`
|
|
1111
|
+
- `search.hybrid_semantic_weight`
|
|
1112
|
+
- `search.max_results`
|
|
1113
|
+
- `search.embedding_model`
|
|
1114
|
+
- `search.embedding_batch_size`
|
|
1115
|
+
- `search.scanner_max_batch_retries`
|
|
1116
|
+
- `search.tuning` (optional object)
|
|
1117
|
+
- `providers.openai`
|
|
1118
|
+
- `providers.ollama`
|
|
1119
|
+
- `vector_store.qdrant`
|
|
1120
|
+
- `vector_store.lancedb`
|
|
1121
|
+
|
|
1122
|
+
`search.score_threshold` defaults to `0` and applies mode-specific minimum-score filtering as defined in section `13.3`.
|
|
1123
|
+
`search.hybrid_semantic_weight` defaults to `0.7` and controls semantic-vs-lexical blend weight in hybrid mode as defined in section `13.3`.
|
|
1124
|
+
`search.tuning` is optional; when unset or partially invalid, lexical scoring defaults remain deterministic (`title_exact_bonus=10`, `title_weight=8`, `description_weight=5`, `tags_weight=6`, `status_weight=2`, `body_weight=1`, `comments_weight=1`, `notes_weight=1`, `learnings_weight=1`, `dependencies_weight=3`, `linked_content_weight=1`).
|
|
1125
|
+
|
|
1126
|
+
Default `settings.json` object written by `pm init`:
|
|
1127
|
+
|
|
1128
|
+
```json
|
|
1129
|
+
{
|
|
1130
|
+
"version": 1,
|
|
1131
|
+
"id_prefix": "pm-",
|
|
1132
|
+
"author_default": "",
|
|
1133
|
+
"locks": {
|
|
1134
|
+
"ttl_seconds": 1800
|
|
1135
|
+
},
|
|
1136
|
+
"output": {
|
|
1137
|
+
"default_format": "toon"
|
|
1138
|
+
},
|
|
1139
|
+
"workflow": {
|
|
1140
|
+
"definition_of_done": []
|
|
1141
|
+
},
|
|
1142
|
+
"extensions": {
|
|
1143
|
+
"enabled": [],
|
|
1144
|
+
"disabled": []
|
|
1145
|
+
},
|
|
1146
|
+
"search": {
|
|
1147
|
+
"score_threshold": 0,
|
|
1148
|
+
"hybrid_semantic_weight": 0.7,
|
|
1149
|
+
"max_results": 50,
|
|
1150
|
+
"embedding_model": "",
|
|
1151
|
+
"embedding_batch_size": 32,
|
|
1152
|
+
"scanner_max_batch_retries": 3
|
|
1153
|
+
},
|
|
1154
|
+
"providers": {
|
|
1155
|
+
"openai": {
|
|
1156
|
+
"base_url": "",
|
|
1157
|
+
"api_key": "",
|
|
1158
|
+
"model": ""
|
|
1159
|
+
},
|
|
1160
|
+
"ollama": {
|
|
1161
|
+
"base_url": "",
|
|
1162
|
+
"model": ""
|
|
1163
|
+
}
|
|
1164
|
+
},
|
|
1165
|
+
"vector_store": {
|
|
1166
|
+
"qdrant": {
|
|
1167
|
+
"url": "",
|
|
1168
|
+
"api_key": ""
|
|
1169
|
+
},
|
|
1170
|
+
"lancedb": {
|
|
1171
|
+
"path": ""
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
```
|
|
1176
|
+
|
|
1177
|
+
Definition-of-Done config baseline:
|
|
1178
|
+
|
|
1179
|
+
- `pm config project set definition-of-done --criterion <text>` replaces the project-level criteria list in `.agents/pm/settings.json`.
|
|
1180
|
+
- `pm config global set definition-of-done --criterion <text>` replaces the global criteria list in `~/.pm-cli/settings.json` (or `PM_GLOBAL_PATH/settings.json`).
|
|
1181
|
+
- `pm config <project|global> get definition-of-done` returns the currently effective list for the selected scope with deterministic TOON/JSON output.
|
|
1182
|
+
- Empty criteria are rejected; duplicate criteria are deduplicated with lexicographic ordering.
|
|
1183
|
+
|
|
1184
|
+
Notes:
|
|
1185
|
+
|
|
1186
|
+
- Key order in file output MUST remain exactly as shown above.
|
|
1187
|
+
|
|
1188
|
+
Env precedence:
|
|
1189
|
+
|
|
1190
|
+
1. CLI flags
|
|
1191
|
+
2. Environment variables
|
|
1192
|
+
3. `settings.json`
|
|
1193
|
+
4. hard defaults
|
|
1194
|
+
|
|
1195
|
+
## 18) Testing Strategy and CI
|
|
1196
|
+
|
|
1197
|
+
Release-ready test policy:
|
|
1198
|
+
|
|
1199
|
+
- Test runner: Vitest for both unit and integration suites.
|
|
1200
|
+
- Coverage gates: 100% for lines, branches, functions, and statements.
|
|
1201
|
+
- CI guard: fail build when any coverage metric drops below 100%.
|
|
1202
|
+
|
|
1203
|
+
Sandbox safety requirements (hard):
|
|
1204
|
+
|
|
1205
|
+
- Tests MUST NOT read/write the repository's real `.agents/pm`.
|
|
1206
|
+
- Every test suite uses temporary sandbox storage via `PM_PATH`.
|
|
1207
|
+
- PM-driven test execution MUST use a sandbox wrapper command (`node scripts/run-tests.mjs test|coverage`) that creates a temporary directory, sets both `PM_PATH` and `PM_GLOBAL_PATH`, runs the requested test command, and cleans up the sandbox afterward.
|
|
1208
|
+
- `pm test <ID> --add` MUST enforce this by rejecting sandbox-unsafe test-runner command entries at add-time unless they use `node scripts/run-tests.mjs ...` or explicitly set both `PM_PATH` and `PM_GLOBAL_PATH`; this includes unsandboxed direct package-manager run-script variants (for example `npm run test` and `pnpm run test`) and chained direct test-runner segments that are not explicitly sandboxed.
|
|
1209
|
+
- `pm test <ID> --run` MUST defensively skip legacy linked command entries that invoke `pm test-all` (including global-flag and package-spec launcher variants such as `pm --json test-all`, `npx @unbrained/pm-cli@latest --json test-all`, `pnpm dlx @unbrained/pm-cli@latest --json test-all`, and `npm exec -- @unbrained/pm-cli@latest --json test-all`) and surface deterministic skipped diagnostics.
|
|
1210
|
+
- Integration tests spawn built CLI subprocesses (`node dist/cli.js ...`) with explicit
|
|
1211
|
+
`PM_PATH`, `PM_GLOBAL_PATH`, and `PM_AUTHOR`.
|
|
1212
|
+
- Temporary sandbox directories must be cleaned up after each test/suite.
|
|
1213
|
+
|
|
1214
|
+
Required unit coverage areas:
|
|
1215
|
+
|
|
1216
|
+
- Parser/serializer round-trip and key ordering determinism.
|
|
1217
|
+
- ID normalization/generation behavior.
|
|
1218
|
+
- Deadline and `none` token parsing.
|
|
1219
|
+
- History patch + hash generation.
|
|
1220
|
+
- Lock conflict/stale-lock behavior.
|
|
1221
|
+
|
|
1222
|
+
Required integration coverage areas:
|
|
1223
|
+
|
|
1224
|
+
- `init` idempotency.
|
|
1225
|
+
- `create` full-flag.
|
|
1226
|
+
- `list*` filtering contracts and deterministic ordering.
|
|
1227
|
+
- `get`, `update`, `append`, `claim`, `release`, `delete`.
|
|
1228
|
+
- `comments`, `files`, `docs`, `test`, and `test-all`.
|
|
1229
|
+
- `history` and `activity` deterministic retrieval commands.
|
|
1230
|
+
|
|
1231
|
+
CI requirements:
|
|
1232
|
+
|
|
1233
|
+
- `pnpm build`
|
|
1234
|
+
- `pnpm typecheck`
|
|
1235
|
+
- `pnpm test`
|
|
1236
|
+
- `pnpm test:coverage` (must satisfy 100% thresholds)
|
|
1237
|
+
- `node scripts/run-tests.mjs coverage` for pm-linked regression execution in automation-safe mode
|
|
1238
|
+
- Optional artifact upload for coverage reports
|
|
1239
|
+
|
|
1240
|
+
Community/release documentation requirements:
|
|
1241
|
+
|
|
1242
|
+
- `LICENSE` (MIT) at repository root.
|
|
1243
|
+
- `CHANGELOG.md` at repository root using Keep a Changelog format with an `[Unreleased]` section and explicit SemVer note.
|
|
1244
|
+
- `CONTRIBUTING.md` at repository root (or `.github/`) with setup, sandbox-safe testing, and contribution workflow.
|
|
1245
|
+
- `SECURITY.md` policy with reporting expectations.
|
|
1246
|
+
- `CODE_OF_CONDUCT.md` baseline contributor conduct policy.
|
|
1247
|
+
|
|
1248
|
+
## 19) Dependency Policy (Minimal, Justified)
|
|
1249
|
+
|
|
1250
|
+
Core should prefer Node standard library and a minimal set:
|
|
1251
|
+
|
|
1252
|
+
- `commander` (CLI arg parsing, help generation)
|
|
1253
|
+
- `@toon-format/toon` (TOON encode/decode)
|
|
1254
|
+
- `fast-json-patch` (RFC6902 diff/apply)
|
|
1255
|
+
- `zod` (runtime schema validation for settings/extensions/import payloads)
|
|
1256
|
+
- `undici` (HTTP for embedding providers, if needed by core)
|
|
1257
|
+
|
|
1258
|
+
Optional adapters can introduce optional peer dependencies (Qdrant/LanceDB clients) loaded lazily through extension boundaries.
|
|
1259
|
+
|
|
1260
|
+
## 20) Risks and Mitigations
|
|
1261
|
+
|
|
1262
|
+
Highest-risk areas:
|
|
1263
|
+
|
|
1264
|
+
1. History/restore correctness
|
|
1265
|
+
- Mitigation: hash verification + replay tests + golden fixtures.
|
|
1266
|
+
2. Extension override complexity
|
|
1267
|
+
- Mitigation: explicit precedence rules + deterministic registration order + health checks.
|
|
1268
|
+
3. Semantic indexing drift
|
|
1269
|
+
- Mitigation: mutation-triggered re-embed + periodic `reindex` + index manifest checksums.
|
|
1270
|
+
|
|
1271
|
+
## 21) Milestone Implementation Plan (Release Hardening)
|
|
1272
|
+
|
|
1273
|
+
### Milestone 0 - Foundations
|
|
1274
|
+
|
|
1275
|
+
Checklist:
|
|
1276
|
+
|
|
1277
|
+
- [x] Project scaffolding, CLI entrypoint, config loader
|
|
1278
|
+
- [x] Deterministic serializer utilities
|
|
1279
|
+
- [x] Error model + exit code mapping
|
|
1280
|
+
|
|
1281
|
+
Definition of Done:
|
|
1282
|
+
|
|
1283
|
+
- `pm --help` and `pm init --help` render
|
|
1284
|
+
- config/env precedence tested
|
|
1285
|
+
|
|
1286
|
+
### Milestone 1 - Core Item CRUD + Locking
|
|
1287
|
+
|
|
1288
|
+
Checklist:
|
|
1289
|
+
|
|
1290
|
+
- [x] Item schema model + validation
|
|
1291
|
+
- [x] Parser/serializer for markdown item files
|
|
1292
|
+
- [x] ID generation + normalization
|
|
1293
|
+
- [x] Lock acquire/release with TTL and conflict handling
|
|
1294
|
+
- [x] Core commands: init/create/get/update/append/claim/release/close/delete complete
|
|
1295
|
+
|
|
1296
|
+
Definition of Done:
|
|
1297
|
+
|
|
1298
|
+
- Full CRUD lifecycle works with atomic writes and conflict exit codes
|
|
1299
|
+
- deterministic output in TOON/JSON
|
|
1300
|
+
|
|
1301
|
+
### Milestone 2 - History + Restore
|
|
1302
|
+
|
|
1303
|
+
Checklist:
|
|
1304
|
+
|
|
1305
|
+
- [x] RFC6902 patch generation per mutation
|
|
1306
|
+
- [x] Append-only history writer
|
|
1307
|
+
- [x] `history` and `activity` commands
|
|
1308
|
+
- [x] `restore` by timestamp/version with replay + hash validation
|
|
1309
|
+
|
|
1310
|
+
Definition of Done:
|
|
1311
|
+
|
|
1312
|
+
- Replay reproduces exact prior item state in tests
|
|
1313
|
+
- restore appends `restore` history event
|
|
1314
|
+
|
|
1315
|
+
### Milestone 3 - Query + Operations
|
|
1316
|
+
|
|
1317
|
+
Checklist:
|
|
1318
|
+
|
|
1319
|
+
- [x] list/list-* filters and deterministic sort
|
|
1320
|
+
- [x] comments/files/docs/test commands
|
|
1321
|
+
- [x] test-all orchestration + dependency-failed exit handling
|
|
1322
|
+
- [x] stats/health/gc command baseline
|
|
1323
|
+
|
|
1324
|
+
Definition of Done:
|
|
1325
|
+
|
|
1326
|
+
- Command matrix complete and deterministic
|
|
1327
|
+
- docs-linked operations tested
|
|
1328
|
+
|
|
1329
|
+
### Milestone 4 - Search
|
|
1330
|
+
|
|
1331
|
+
Checklist:
|
|
1332
|
+
|
|
1333
|
+
- [x] keyword indexing + search command (keyword command surface + deterministic reindex artifact rebuild implemented; deterministic exact-title token boost and configurable multi-factor lexical tuning via `search.tuning` implemented; `--limit 0` short-circuit implemented; advanced relevance tuning is post-v0.1 roadmap)
|
|
1334
|
+
- [x] embedding provider abstraction (deterministic provider configuration resolution, request-target planning including OpenAI-compatible `base_url` normalization for root/`/v1`/`/embeddings`, provider-specific request payload/response normalization with deterministic OpenAI data-entry index ordering, deterministic request-execution helper behavior, deterministic embedding cardinality validation, deterministic per-request normalized-input dedupe with output fan-out, configurable batch sizing and per-batch retry, command-path embedding execution, and mutation-triggered embedding refresh are implemented; additional advanced provider optimizations are post-v0.1 roadmap)
|
|
1335
|
+
- [x] vector store adapters (Qdrant/LanceDB deterministic configuration resolution, request-target planning, request payload/response normalization, deterministic request-execution helpers, deterministic LanceDB local query/upsert/delete execution helper behavior, deterministic local snapshot persistence + reload across process boundaries, query-hit ordering normalization, and command-path vector query/upsert integration implemented; broader adapter optimization is post-v0.1 roadmap)
|
|
1336
|
+
- [x] hybrid ranking + include-linked option (`--include-linked` lexical baseline implemented for keyword mode and hybrid lexical blending; deterministic hybrid lexical+semantic blend with configurable `search.hybrid_semantic_weight` implemented; deterministic exact-title token lexical boost implemented; configurable multi-factor lexical tuning via `search.tuning` implemented; broader advanced semantic/hybrid tuning is post-v0.1 roadmap)
|
|
1337
|
+
- [x] reindex command (keyword baseline complete; semantic/hybrid embedding+vector upsert implemented; mutation command paths invalidate stale keyword artifacts, trigger best-effort semantic embedding refresh for affected item IDs, and prune vectors for missing/deleted IDs when semantic configuration is available)
|
|
1338
|
+
|
|
1339
|
+
Definition of Done:
|
|
1340
|
+
|
|
1341
|
+
- Search works in keyword-only and semantic/hybrid mode
|
|
1342
|
+
- item mutations trigger search-index freshness via deterministic cache invalidation plus best-effort semantic embedding refresh for affected item IDs when semantic configuration is available, including pruning vectors for missing/deleted affected IDs, with explicit reindex workflows retained for full rebuilds
|
|
1343
|
+
|
|
1344
|
+
### Milestone 5 - Extension System + Built-ins
|
|
1345
|
+
|
|
1346
|
+
Checklist:
|
|
1347
|
+
|
|
1348
|
+
- [x] extension manifest loader + sandboxed execution boundary (deterministic manifest discovery, precedence, failure-isolated runtime loading, realpath/symlink-resolved entry containment enforcement, command-handler context snapshot isolation for `args`/`options`/`global`, per-hook context snapshot isolation, and dynamic extension command loose-option parsing hardening (null-prototype option maps + prototype-pollution key rejection) are implemented; broader command sandbox API surface is post-v0.1 roadmap)
|
|
1349
|
+
- [x] hook lifecycle (extension `activate(api)` baseline with deterministic hook registration is implemented; registration now validates hook handlers as functions at activation time, per-hook context snapshot isolation prevents mutation leakage across hook callbacks and caller state, and `beforeCommand`/`afterCommand` command-lifecycle execution plus baseline read/write/index call-site wiring for core item-store reads/writes, create/restore item and history writes, settings read/write operations, history/activity history-directory scans and history-stream reads, health history-directory scans plus history-stream path dispatch, search item/linked reads, reindex flows, stats/health/gc command file-system paths (including `pm gc` onIndex dispatch with mode `gc` and deterministic cache-target totals), lock file read/write/unlink operations, init directory bootstrap ensure-write dispatch, and built-in beads/todos import-export source/item/history file operations are implemented)
|
|
1350
|
+
- [x] renderer and command extension points (deterministic core-command override + renderer override registration/dispatch is implemented with failure containment, extension command handlers for declared command paths including dynamically surfaced non-core paths are implemented, dynamic command help now surfaces `registerFlags` metadata deterministically, deep snapshot isolation for override/renderer result contexts is implemented, and override/renderer execution now includes cloned command `args`/`options`/`global` snapshots plus `pm_root` metadata for contextual deterministic extension output behavior)
|
|
1351
|
+
- [x] built-in beads import extension (built-in extension command-handler packaging, Beads JSONL field mapping, deterministic defaults, `op: "import"` history entries, and parity polish implemented)
|
|
1352
|
+
- [x] built-in todos import/export extension (built-in extension command-handler packaging, todos markdown round-trip, canonical optional metadata preservation including planning/workflow and issue fields, hierarchical ID preservation, and `med` alias normalization implemented)
|
|
1353
|
+
- [x] built-in Pi tool wrapper extension (Pi agent extension module at `.pi/extensions/pm-cli/index.ts` with full v0.1 action dispatch parity, including `completion` + `shell` mapping, camelCase parameter surface for all canonical scalar metadata, explicit empty-string passthrough, numeric-flag stringification, claim/release parity, packaged CLI fallback, and distribution packaging polish implemented)
|
|
1354
|
+
|
|
1355
|
+
Definition of Done:
|
|
1356
|
+
|
|
1357
|
+
- Project/global precedence verified
|
|
1358
|
+
- failing extension reported in `pm health` without core corruption
|
|
1359
|
+
|
|
1360
|
+
### Milestone 6 - Hardening + Release Readiness
|
|
1361
|
+
|
|
1362
|
+
Checklist:
|
|
1363
|
+
|
|
1364
|
+
- [x] CI matrix finalized (ubuntu/macos/windows Node 20, ubuntu Node 22, ubuntu Node 24)
|
|
1365
|
+
- [x] fixture corpus for restore/import/search
|
|
1366
|
+
- [x] command help and README examples validated in tests
|
|
1367
|
+
- [x] repository layout refactor (`src/cli`, `src/core`, `src/types`)
|
|
1368
|
+
- [x] sandboxed integration harness (`withTempPmPath`)
|
|
1369
|
+
- [x] sandboxed pm-runner (`scripts/run-tests.mjs`) for `pm test` and `pm test-all` safety
|
|
1370
|
+
- [x] installer scripts (`scripts/install.sh`, `scripts/install.ps1`) with post-install `pm --version` availability verification
|
|
1371
|
+
- [x] npm packaging allowlist + prepublish build guard
|
|
1372
|
+
- [x] community docs baseline (`LICENSE`, `CHANGELOG.md`, `CONTRIBUTING.md`, `SECURITY.md`, `CODE_OF_CONDUCT.md`)
|
|
1373
|
+
- [x] shell completion command (`pm completion bash|zsh|fish`)
|
|
1374
|
+
- [x] automated npm release workflow (`.github/workflows/release.yml`) triggered on `v*.*.*` tags
|
|
1375
|
+
|
|
1376
|
+
Definition of Done:
|
|
1377
|
+
|
|
1378
|
+
- All required commands and tests passing
|
|
1379
|
+
- docs and behavior aligned with this PRD
|
|
1380
|
+
|
|
1381
|
+
## 22) Open Assumptions and Clarifications Captured
|
|
1382
|
+
|
|
1383
|
+
- Imported Beads dependency types outside canonical set are mapped best-effort:
|
|
1384
|
+
- `parent-child` -> `parent`/`child` directional mapping based on source context
|
|
1385
|
+
- unknown values retained in import metadata notes if lossy mapping is required
|
|
1386
|
+
- Hierarchical IDs from imports are preserved verbatim; new IDs generated by core default to flat `prefix-token`.
|
|
1387
|
+
- TOON formatting follows deterministic encoding with stable object keys; internal serializer may use a thin compatibility layer to ensure strict consistency across Node versions.
|
|
1388
|
+
- For `create`, `before_hash` is computed from canonical empty document: `{ "front_matter": {}, "body": "" }`.
|
|
1389
|
+
- If create item write succeeds but history append fails, implementation MUST rollback the new item file before returning failure.
|
|
1390
|
+
- ID normalization helper behavior (`#` prefix, missing configured prefix, case-insensitive input) is required in core utilities even before all commands expose it.
|