@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.
Files changed (245) hide show
  1. package/.pi/extensions/pm-cli/index.ts +778 -0
  2. package/AGENTS.md +475 -0
  3. package/LICENSE +21 -0
  4. package/PRD.md +1390 -0
  5. package/README.md +655 -0
  6. package/dist/cli/commands/activity.d.ts +14 -0
  7. package/dist/cli/commands/activity.js +80 -0
  8. package/dist/cli/commands/activity.js.map +1 -0
  9. package/dist/cli/commands/append.d.ts +13 -0
  10. package/dist/cli/commands/append.js +46 -0
  11. package/dist/cli/commands/append.js.map +1 -0
  12. package/dist/cli/commands/beads.d.ts +15 -0
  13. package/dist/cli/commands/beads.js +475 -0
  14. package/dist/cli/commands/beads.js.map +1 -0
  15. package/dist/cli/commands/claim.d.ts +19 -0
  16. package/dist/cli/commands/claim.js +79 -0
  17. package/dist/cli/commands/claim.js.map +1 -0
  18. package/dist/cli/commands/close.d.ts +12 -0
  19. package/dist/cli/commands/close.js +58 -0
  20. package/dist/cli/commands/close.js.map +1 -0
  21. package/dist/cli/commands/comments.d.ts +15 -0
  22. package/dist/cli/commands/comments.js +80 -0
  23. package/dist/cli/commands/comments.js.map +1 -0
  24. package/dist/cli/commands/completion.d.ts +10 -0
  25. package/dist/cli/commands/completion.js +469 -0
  26. package/dist/cli/commands/completion.js.map +1 -0
  27. package/dist/cli/commands/config.d.ts +15 -0
  28. package/dist/cli/commands/config.js +72 -0
  29. package/dist/cli/commands/config.js.map +1 -0
  30. package/dist/cli/commands/create.d.ts +60 -0
  31. package/dist/cli/commands/create.js +456 -0
  32. package/dist/cli/commands/create.js.map +1 -0
  33. package/dist/cli/commands/delete.d.ts +12 -0
  34. package/dist/cli/commands/delete.js +33 -0
  35. package/dist/cli/commands/delete.js.map +1 -0
  36. package/dist/cli/commands/docs.d.ts +16 -0
  37. package/dist/cli/commands/docs.js +113 -0
  38. package/dist/cli/commands/docs.js.map +1 -0
  39. package/dist/cli/commands/files.d.ts +17 -0
  40. package/dist/cli/commands/files.js +113 -0
  41. package/dist/cli/commands/files.js.map +1 -0
  42. package/dist/cli/commands/gc.d.ts +9 -0
  43. package/dist/cli/commands/gc.js +80 -0
  44. package/dist/cli/commands/gc.js.map +1 -0
  45. package/dist/cli/commands/get.d.ts +12 -0
  46. package/dist/cli/commands/get.js +28 -0
  47. package/dist/cli/commands/get.js.map +1 -0
  48. package/dist/cli/commands/health.d.ts +15 -0
  49. package/dist/cli/commands/health.js +288 -0
  50. package/dist/cli/commands/health.js.map +1 -0
  51. package/dist/cli/commands/history.d.ts +13 -0
  52. package/dist/cli/commands/history.js +72 -0
  53. package/dist/cli/commands/history.js.map +1 -0
  54. package/dist/cli/commands/index.d.ts +26 -0
  55. package/dist/cli/commands/index.js +27 -0
  56. package/dist/cli/commands/index.js.map +1 -0
  57. package/dist/cli/commands/init.d.ts +10 -0
  58. package/dist/cli/commands/init.js +59 -0
  59. package/dist/cli/commands/init.js.map +1 -0
  60. package/dist/cli/commands/install.d.ts +18 -0
  61. package/dist/cli/commands/install.js +87 -0
  62. package/dist/cli/commands/install.js.map +1 -0
  63. package/dist/cli/commands/list.d.ts +21 -0
  64. package/dist/cli/commands/list.js +137 -0
  65. package/dist/cli/commands/list.js.map +1 -0
  66. package/dist/cli/commands/reindex.d.ts +16 -0
  67. package/dist/cli/commands/reindex.js +154 -0
  68. package/dist/cli/commands/reindex.js.map +1 -0
  69. package/dist/cli/commands/restore.d.ts +20 -0
  70. package/dist/cli/commands/restore.js +208 -0
  71. package/dist/cli/commands/restore.js.map +1 -0
  72. package/dist/cli/commands/search.d.ts +45 -0
  73. package/dist/cli/commands/search.js +531 -0
  74. package/dist/cli/commands/search.js.map +1 -0
  75. package/dist/cli/commands/stats.d.ts +13 -0
  76. package/dist/cli/commands/stats.js +88 -0
  77. package/dist/cli/commands/stats.js.map +1 -0
  78. package/dist/cli/commands/test-all.d.ts +30 -0
  79. package/dist/cli/commands/test-all.js +157 -0
  80. package/dist/cli/commands/test-all.js.map +1 -0
  81. package/dist/cli/commands/test.d.ts +29 -0
  82. package/dist/cli/commands/test.js +492 -0
  83. package/dist/cli/commands/test.js.map +1 -0
  84. package/dist/cli/commands/update.d.ts +52 -0
  85. package/dist/cli/commands/update.js +467 -0
  86. package/dist/cli/commands/update.js.map +1 -0
  87. package/dist/cli/extension-command-options.d.ts +1 -0
  88. package/dist/cli/extension-command-options.js +76 -0
  89. package/dist/cli/extension-command-options.js.map +1 -0
  90. package/dist/cli/main.d.ts +2 -0
  91. package/dist/cli/main.js +1494 -0
  92. package/dist/cli/main.js.map +1 -0
  93. package/dist/cli.d.ts +2 -0
  94. package/dist/cli.js +3 -0
  95. package/dist/cli.js.map +1 -0
  96. package/dist/command-types.d.ts +1 -0
  97. package/dist/command-types.js +2 -0
  98. package/dist/command-types.js.map +1 -0
  99. package/dist/constants.d.ts +1 -0
  100. package/dist/constants.js +2 -0
  101. package/dist/constants.js.map +1 -0
  102. package/dist/core/extensions/builtins.d.ts +3 -0
  103. package/dist/core/extensions/builtins.js +47 -0
  104. package/dist/core/extensions/builtins.js.map +1 -0
  105. package/dist/core/extensions/index.d.ts +13 -0
  106. package/dist/core/extensions/index.js +88 -0
  107. package/dist/core/extensions/index.js.map +1 -0
  108. package/dist/core/extensions/loader.d.ts +301 -0
  109. package/dist/core/extensions/loader.js +917 -0
  110. package/dist/core/extensions/loader.js.map +1 -0
  111. package/dist/core/fs/fs-utils.d.ts +6 -0
  112. package/dist/core/fs/fs-utils.js +58 -0
  113. package/dist/core/fs/fs-utils.js.map +1 -0
  114. package/dist/core/fs/index.d.ts +1 -0
  115. package/dist/core/fs/index.js +2 -0
  116. package/dist/core/fs/index.js.map +1 -0
  117. package/dist/core/history/history.d.ts +12 -0
  118. package/dist/core/history/history.js +44 -0
  119. package/dist/core/history/history.js.map +1 -0
  120. package/dist/core/history/index.d.ts +1 -0
  121. package/dist/core/history/index.js +2 -0
  122. package/dist/core/history/index.js.map +1 -0
  123. package/dist/core/item/id.d.ts +3 -0
  124. package/dist/core/item/id.js +54 -0
  125. package/dist/core/item/id.js.map +1 -0
  126. package/dist/core/item/index.d.ts +3 -0
  127. package/dist/core/item/index.js +4 -0
  128. package/dist/core/item/index.js.map +1 -0
  129. package/dist/core/item/item-format.d.ts +9 -0
  130. package/dist/core/item/item-format.js +363 -0
  131. package/dist/core/item/item-format.js.map +1 -0
  132. package/dist/core/item/parse.d.ts +3 -0
  133. package/dist/core/item/parse.js +72 -0
  134. package/dist/core/item/parse.js.map +1 -0
  135. package/dist/core/lock/index.d.ts +1 -0
  136. package/dist/core/lock/index.js +2 -0
  137. package/dist/core/lock/index.js.map +1 -0
  138. package/dist/core/lock/lock.d.ts +1 -0
  139. package/dist/core/lock/lock.js +100 -0
  140. package/dist/core/lock/lock.js.map +1 -0
  141. package/dist/core/output/output.d.ts +7 -0
  142. package/dist/core/output/output.js +79 -0
  143. package/dist/core/output/output.js.map +1 -0
  144. package/dist/core/search/cache.d.ts +17 -0
  145. package/dist/core/search/cache.js +212 -0
  146. package/dist/core/search/cache.js.map +1 -0
  147. package/dist/core/search/embedding-batches.d.ts +7 -0
  148. package/dist/core/search/embedding-batches.js +54 -0
  149. package/dist/core/search/embedding-batches.js.map +1 -0
  150. package/dist/core/search/providers.d.ts +59 -0
  151. package/dist/core/search/providers.js +265 -0
  152. package/dist/core/search/providers.js.map +1 -0
  153. package/dist/core/search/vector-stores.d.ts +89 -0
  154. package/dist/core/search/vector-stores.js +546 -0
  155. package/dist/core/search/vector-stores.js.map +1 -0
  156. package/dist/core/shared/command-types.d.ts +7 -0
  157. package/dist/core/shared/command-types.js +2 -0
  158. package/dist/core/shared/command-types.js.map +1 -0
  159. package/dist/core/shared/constants.d.ts +19 -0
  160. package/dist/core/shared/constants.js +134 -0
  161. package/dist/core/shared/constants.js.map +1 -0
  162. package/dist/core/shared/errors.d.ts +4 -0
  163. package/dist/core/shared/errors.js +9 -0
  164. package/dist/core/shared/errors.js.map +1 -0
  165. package/dist/core/shared/index.d.ts +3 -0
  166. package/dist/core/shared/index.js +4 -0
  167. package/dist/core/shared/index.js.map +1 -0
  168. package/dist/core/shared/serialization.d.ts +3 -0
  169. package/dist/core/shared/serialization.js +70 -0
  170. package/dist/core/shared/serialization.js.map +1 -0
  171. package/dist/core/shared/time.d.ts +3 -0
  172. package/dist/core/shared/time.js +28 -0
  173. package/dist/core/shared/time.js.map +1 -0
  174. package/dist/core/store/index.d.ts +3 -0
  175. package/dist/core/store/index.js +4 -0
  176. package/dist/core/store/index.js.map +1 -0
  177. package/dist/core/store/item-store.d.ts +42 -0
  178. package/dist/core/store/item-store.js +186 -0
  179. package/dist/core/store/item-store.js.map +1 -0
  180. package/dist/core/store/paths.d.ts +8 -0
  181. package/dist/core/store/paths.js +29 -0
  182. package/dist/core/store/paths.js.map +1 -0
  183. package/dist/core/store/settings.d.ts +4 -0
  184. package/dist/core/store/settings.js +148 -0
  185. package/dist/core/store/settings.js.map +1 -0
  186. package/dist/errors.d.ts +1 -0
  187. package/dist/errors.js +2 -0
  188. package/dist/errors.js.map +1 -0
  189. package/dist/extensions/builtins/beads/index.d.ts +8 -0
  190. package/dist/extensions/builtins/beads/index.js +29 -0
  191. package/dist/extensions/builtins/beads/index.js.map +1 -0
  192. package/dist/extensions/builtins/todos/import-export.d.ts +26 -0
  193. package/dist/extensions/builtins/todos/import-export.js +460 -0
  194. package/dist/extensions/builtins/todos/import-export.js.map +1 -0
  195. package/dist/extensions/builtins/todos/index.d.ts +8 -0
  196. package/dist/extensions/builtins/todos/index.js +38 -0
  197. package/dist/extensions/builtins/todos/index.js.map +1 -0
  198. package/dist/fs-utils.d.ts +1 -0
  199. package/dist/fs-utils.js +2 -0
  200. package/dist/fs-utils.js.map +1 -0
  201. package/dist/history.d.ts +1 -0
  202. package/dist/history.js +2 -0
  203. package/dist/history.js.map +1 -0
  204. package/dist/id.d.ts +1 -0
  205. package/dist/id.js +2 -0
  206. package/dist/id.js.map +1 -0
  207. package/dist/item-format.d.ts +1 -0
  208. package/dist/item-format.js +2 -0
  209. package/dist/item-format.js.map +1 -0
  210. package/dist/item-store.d.ts +1 -0
  211. package/dist/item-store.js +2 -0
  212. package/dist/item-store.js.map +1 -0
  213. package/dist/lock.d.ts +1 -0
  214. package/dist/lock.js +2 -0
  215. package/dist/lock.js.map +1 -0
  216. package/dist/output.d.ts +1 -0
  217. package/dist/output.js +2 -0
  218. package/dist/output.js.map +1 -0
  219. package/dist/parse.d.ts +1 -0
  220. package/dist/parse.js +2 -0
  221. package/dist/parse.js.map +1 -0
  222. package/dist/paths.d.ts +1 -0
  223. package/dist/paths.js +2 -0
  224. package/dist/paths.js.map +1 -0
  225. package/dist/serialization.d.ts +1 -0
  226. package/dist/serialization.js +2 -0
  227. package/dist/serialization.js.map +1 -0
  228. package/dist/settings.d.ts +1 -0
  229. package/dist/settings.js +2 -0
  230. package/dist/settings.js.map +1 -0
  231. package/dist/time.d.ts +1 -0
  232. package/dist/time.js +2 -0
  233. package/dist/time.js.map +1 -0
  234. package/dist/types/index.d.ts +1 -0
  235. package/dist/types/index.js +2 -0
  236. package/dist/types/index.js.map +1 -0
  237. package/dist/types.d.ts +179 -0
  238. package/dist/types.js +21 -0
  239. package/dist/types.js.map +1 -0
  240. package/docs/ARCHITECTURE.md +246 -0
  241. package/docs/EXTENSIONS.md +329 -0
  242. package/docs/RELEASING.md +65 -0
  243. package/package.json +79 -0
  244. package/scripts/install.ps1 +112 -0
  245. 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.