@unbrained/pm-cli 2026.3.9 → 2026.3.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -5,651 +5,101 @@
5
5
  [![Node >=20](https://img.shields.io/node/v/%40unbrained%2Fpm-cli)](https://nodejs.org)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
7
7
 
8
- Agent-friendly, git-native project management for humans and coding agents.
8
+ `pm` is a git-native project management CLI for humans and coding agents. It stores work as plain Markdown files with JSON front matter, keeps append-only history, and supports safe collaboration.
9
9
 
10
- `pm` stores work items as plain markdown files with JSON front-matter, keeps append-only history, supports safe concurrent mutation with lock + claim semantics, and defaults to token-efficient TOON output.
10
+ ## Highlights
11
11
 
12
- ## Why `pm`
12
+ - Git-native items that stay reviewable in diffs
13
+ - Safe multi-agent workflows with claims, locks, and restore
14
+ - Deterministic output with TOON by default and `--json` when needed
15
+ - Optional search and extension support for more advanced setups
13
16
 
14
- - Git-native: every item is file-backed and reviewable in diffs.
15
- - Deterministic: stable output schema, key ordering, and filtering behavior.
16
- - Agent-optimized: claim/release ownership, append-only history, restore by version/timestamp.
17
- - Extensible: project + global extension loading with predictable precedence.
18
- - Search-ready: keyword search built-in, semantic/hybrid search optional.
17
+ ## Install
19
18
 
20
- ## Installation
21
-
22
- `pm-cli` targets Node.js 20+ and ships a `pm` executable via npm `bin`.
23
-
24
- ### npm (recommended)
19
+ `pm-cli` requires Node.js 20 or newer.
25
20
 
26
21
  ```bash
27
- npm i -g @unbrained/pm-cli
28
- pm --help
22
+ npm install -g @unbrained/pm-cli
29
23
  pm --version
24
+ pm --help
30
25
  ```
31
26
 
32
- Update to latest:
33
-
34
- ```bash
35
- npm i -g @unbrained/pm-cli@latest
36
- ```
37
-
38
- ### Project-local invocation
27
+ For project-local use:
39
28
 
40
29
  ```bash
41
30
  npx @unbrained/pm-cli --help
42
31
  ```
43
32
 
44
- ### Installer scripts
45
-
46
- ```bash
47
- # Linux/macOS
48
- bash scripts/install.sh
49
-
50
- # Windows PowerShell
51
- pwsh scripts/install.ps1
52
- ```
53
-
54
- Both installers are idempotent for update flows; rerun them to move to a newer version.
55
- Each installer verifies post-install CLI availability by resolving `pm` and running `pm --version` before reporting success.
56
- Installer default package target is `@unbrained/pm-cli`.
57
- Set `PM_CLI_PACKAGE` to override the package source when smoke-testing installer flows.
58
- Scoped package names such as `@scope/pkg` still honor `--version`, while literal specs (`file:`, URLs, local paths/tarballs, or already versioned package specs) are passed to npm unchanged.
59
-
60
- One-line bootstrap patterns (use with normal script-review caution):
61
-
62
- ```bash
63
- curl -fsSL https://raw.githubusercontent.com/unbraind/pm-cli/main/scripts/install.sh | bash
64
- ```
65
-
66
- ```powershell
67
- # safer PowerShell flow: download, inspect, then execute
68
- Invoke-WebRequest -Uri "https://raw.githubusercontent.com/unbraind/pm-cli/main/scripts/install.ps1" -OutFile "install.ps1"
69
- pwsh -File .\install.ps1
70
- ```
71
-
72
- During development in this repo:
73
-
74
- ```bash
75
- pnpm install
76
- pnpm build
77
- node dist/cli.js --help
78
- ```
79
-
80
- ## Maintainer Bootstrap (Dogfooding Runs)
81
-
82
- ```bash
83
- # maintainer identity for mutation history
84
- export PM_AUTHOR="maintainer-agent"
85
-
86
- # refresh global pm from this repository and verify availability
87
- npm install -g .
88
- pm --version
89
-
90
- # choose invocation based on whether global pm resolves to this build
91
- export PM_CMD="pm"
92
- # export PM_CMD="node dist/cli.js"
93
-
94
- # verify command surface before mutation work
95
- $PM_CMD --version
96
- $PM_CMD --help
97
- ```
98
-
99
- Use repository-default tracking for maintainer runs (do not set `PM_PATH`).
100
- For test runs only, always use sandboxed paths via `node scripts/run-tests.mjs <test|coverage>` so both `PM_PATH` and `PM_GLOBAL_PATH` are isolated.
101
-
102
- ## Quickstart
33
+ ## Quick Start
103
34
 
104
35
  ```bash
105
- # 1) initialize project tracker storage
106
36
  pm init
107
37
 
108
- # 2) create work item
109
38
  pm create \
110
- --title "Implement restore command" \
111
- --description "Rebuild item state from history replay." \
112
- --type Task \
39
+ --title "Fix Windows restore failure after stale lock cleanup" \
40
+ --description "Restore can fail on Windows when a stale lock is cleaned up during retry." \
41
+ --type Issue \
113
42
  --status open \
114
43
  --priority 1 \
115
- --tags "history,reliability" \
116
- --body "" \
117
- --deadline +1d \
118
- --estimate 90 \
119
- --acceptance-criteria "Restore reproduces canonical target bytes." \
120
- --author "steve" \
121
- --message "Seed restore task" \
122
- --assignee none \
123
- --dep "none" \
124
- --comment "author=steve,created_at=now,text=Seed restore workflow" \
125
- --note "author=steve,created_at=now,text=Implement replay and hash verification" \
126
- --learning "none" \
127
- --file "path=src/core/history/store.ts,scope=project,note=restore logic target" \
128
- --test "command=node scripts/run-tests.mjs test,scope=project,timeout_seconds=240,note=sandbox-safe regression" \
129
- --doc "path=PRD.md,scope=project,note=authoritative contract"
130
-
131
- # 3) list open work
132
- pm list-open --limit 20
133
-
134
- # 4) claim ownership
135
- pm claim pm-a1b2
136
-
137
- # 5) update + attach context
138
- pm update pm-a1b2 --status in_progress --acceptance-criteria "Exact replay by version/timestamp"
139
- pm files pm-a1b2 --add path=src/history.ts,scope=project
140
- pm test pm-a1b2 --add command="node scripts/run-tests.mjs test",scope=project,timeout_seconds=240
141
-
142
- # 6) close with evidence
143
- pm comments pm-a1b2 --add "Evidence: replay tests passed"
144
- pm close pm-a1b2 "Replay tests passed" --author "steve" --message "Close: replay tests passed"
145
-
146
- # 7) release ownership
147
- pm release pm-a1b2
148
- ```
149
-
150
- ## Storage Layout
151
-
152
- Default root: `.agents/pm` (override with `PM_PATH` or `--path`)
153
- Global extension root: `~/.pm-cli` (override with `PM_GLOBAL_PATH`)
154
-
155
- ```text
156
- .agents/pm/
157
- settings.json
158
- epics/
159
- features/
160
- tasks/
161
- chores/
162
- issues/
163
- history/
164
- index/
165
- search/
166
- extensions/
167
- locks/
168
- ```
169
-
170
- ## Repository Structure
171
-
172
- ```text
173
- src/
174
- cli/
175
- main.ts
176
- commands/
177
- core/
178
- fs/
179
- history/
180
- item/
181
- lock/
182
- output/
183
- store/
184
- types/
185
- tests/
186
- unit/
187
- integration/
188
- scripts/
189
- install.sh
190
- install.ps1
191
- run-tests.mjs
192
- docs/
193
- ARCHITECTURE.md
194
- EXTENSIONS.md
195
- .pi/
196
- extensions/
197
- pm-cli/
198
- .github/workflows/
199
- ci.yml
200
- nightly.yml
201
- ```
202
-
203
- ## Developer Docs
204
-
205
- - [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) — Internal architecture, source layout, mutation contract, history/restore, search, and testing.
206
- - [docs/EXTENSIONS.md](docs/EXTENSIONS.md) — Extension development guide: manifest format, API reference, hook lifecycle, built-in extensions.
207
- - [docs/RELEASING.md](docs/RELEASING.md) — Maintainer release runbook for calendar versioning, CI gates, npm publish, and GitHub Releases.
208
-
209
- ## Item File Format
210
-
211
- Each item is stored as `<id>.md` under the folder matching its type:
212
-
213
- - `Epic` -> `epics/`
214
- - `Feature` -> `features/`
215
- - `Task` -> `tasks/`
216
- - `Chore` -> `chores/`
217
- - `Issue` -> `issues/`
218
-
219
- Format:
220
-
221
- 1. JSON front-matter object (not YAML)
222
- 2. blank line
223
- 3. optional markdown body
224
-
225
- ## Commands
226
-
227
- ### Core (implemented in v0.1)
228
-
229
- - `pm init [PREFIX]`
230
- - `pm install pi [--project|--global]` (install bundled Pi extension to `<project-root>/.pi/extensions/pm-cli/index.ts`, where `project-root` is derived from `--path` when provided, otherwise current working directory; global scope uses `PI_CODING_AGENT_DIR/extensions/pm-cli/index.ts`)
231
- - `pm list` (excludes terminal statuses `closed`/`canceled` by default — the active working-set view), `pm list-all` (all statuses including terminal)
232
- - `pm list-draft`, `pm list-open`, `pm list-in-progress`, `pm list-blocked`, `pm list-closed`, `pm list-canceled`
233
- - `pm get <ID>`
234
- - `pm search <keywords>` (keyword + semantic + hybrid modes; `--include-linked` expands keyword/hybrid lexical scoring with linked content)
235
- - `pm search` shared filters enforce canonical values: `--type` must be `Epic|Feature|Task|Chore|Issue` and `--priority` must be an integer `0..4`
236
- - `pm search --limit 0` returns a deterministic empty result set (after mode/config validation) without embedding/vector query execution
237
- - `pm reindex` (keyword/semantic/hybrid cache artifact rebuild; semantic/hybrid perform provider embedding generation + vector upsert)
238
- - `pm history <ID>`, `pm activity [--limit]`
239
- - `pm restore <ID> <TIMESTAMP|VERSION>`
240
- - `pm config <project|global> set definition-of-done --criterion <text>`
241
- - `pm config <project|global> get definition-of-done`
242
- - `pm create`, `pm update <ID>`, `pm append <ID>`, `pm close <ID> <TEXT>`, `pm delete <ID>`
243
- - `pm claim <ID>`, `pm release <ID>`
244
- - `pm comments <ID>`
245
- - `pm files <ID>`, `pm test <ID>`, `pm docs <ID>`
246
- - `pm test-all`
247
- - `pm stats`
248
- - `pm health`
249
- - `pm gc`
250
- - `pm beads import [--file <path>]` (built-in Beads extension command, import-only)
251
- - `pm todos import [--folder <path>]` (built-in todos extension command)
252
- - `pm todos export [--folder <path>]` (built-in todos extension command)
253
- - `pm completion <bash|zsh|fish>` (generate shell tab-completion script)
254
- - extension-only command paths return not-found when no handler is registered, and generic failure when a matched handler throws; profile diagnostics include deterministic warning codes like `extension_command_handler_failed:<layer>:<name>:<command>`
255
- - extension command names are canonicalized (trimmed, lowercased, repeated internal whitespace collapsed) before registration and dispatch so equivalent command paths resolve deterministically
256
- - `pm test <ID> --add` rejects linked commands that invoke `pm test-all` (including global-flag and package-spec launcher forms like `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
257
- - `pm test <ID> --run` skips legacy linked commands that invoke `pm test-all` (including global-flag and package-spec launcher forms such as `npx`, `pnpm dlx`, and `npm exec` launcher variants) and reports deterministic skip diagnostics
258
- - `pm test <ID> --add` rejects sandbox-unsafe test-runner commands (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, so each direct runner segment must be explicitly sandboxed
259
- - `pm test-all` deduplicates identical linked command/path entries per invocation (keyed by scope+normalized command or scope+path), reports duplicates as skipped, and uses the maximum `timeout_seconds` when duplicate keys disagree on timeout metadata
260
-
261
- ### `pm list` vs `pm list-all`
262
-
263
- - `pm list` — active working-set view: excludes `closed` and `canceled` items by default. Useful for day-to-day use to see what needs attention.
264
- - `pm list-all` — full inventory: includes all items regardless of status. Useful for auditing and historical review.
265
-
266
- Both commands accept the same filter flags; `pm list` applies the terminal-status exclusion before any other filters.
267
-
268
- ### `pm list` filters
269
-
270
- All `list*` commands accept these filter flags:
271
-
272
- - `--type <value>` — `Epic|Feature|Task|Chore|Issue`
273
- - `--tag <value>` — exact tag match (case-insensitive)
274
- - `--priority <value>` — integer `0..4`
275
- - `--deadline-before <value>` — ISO or relative deadline upper bound
276
- - `--deadline-after <value>` — ISO or relative deadline lower bound
277
- - `--assignee <value>` — exact match on `assignee` field; use `none` to filter for unassigned items
278
- - `--sprint <value>` — exact match on `sprint` field
279
- - `--release <value>` — exact match on `release` field
280
- - `--limit <n>` — max items returned
281
-
282
- ### Roadmap (post-v0.1 / partial areas)
283
-
284
- - semantic/hybrid search enhancements (advanced hybrid relevance tuning, incremental embedding refresh, adapter optimizations)
285
- - Pi agent extension advanced ergonomics (higher-level workflow presets and additional tooling integrations)
286
-
287
- ### Global flags
288
-
289
- - `--json` output JSON instead of TOON
290
- - `--quiet` suppress stdout (errors still on stderr)
291
- - `--path <dir>` override PM path for invocation
292
- - `--no-extensions` disable extension loading
293
- - `--profile` print timing diagnostics
294
- - `--version` print CLI version
295
-
296
- ### `pm create` explicit-field contract
297
-
298
- `pm create` accepts explicit flags for all schema fields (including optional ones) so callers can always pass complete intent:
299
-
300
- - required scalar flags:
301
- - `--title/-t`
302
- - `--description/-d` (explicit empty allowed)
303
- - `--type`
304
- - `--status/-s`
305
- - `--priority/-p` (`0..4`)
306
- - `--tags` (explicit empty allowed)
307
- - `--body/-b` (explicit empty allowed)
308
- - `--deadline` (ISO, relative, or none)
309
- - `--estimate/--estimated-minutes/--estimated_minutes` (supports `0`)
310
- - `--acceptance-criteria`, `--acceptance_criteria`, `--ac` (explicit empty allowed)
311
- - `--author` (fallbacks still exist, but explicit is recommended)
312
- - `--message`
313
- - `--assignee` (explicit; use `none` to clear)
314
- - optional scalar flags (use `none` to unset):
315
- - `--parent` (item ID reference)
316
- - `--reviewer`
317
- - `--risk` (`low|med|medium|high|critical`; `med` persists as `medium`)
318
- - `--confidence` (`0..100|low|med|medium|high`; `med` persists as `medium`)
319
- - `--sprint`
320
- - `--release`
321
- - `--blocked-by/--blocked_by` (item ID or free-text)
322
- - `--blocked-reason/--blocked_reason`
323
- - `--unblock-note/--unblock_note` (unblock rationale note)
324
- - `--reporter`
325
- - `--severity` (`low|med|medium|high|critical`; `med` persists as `medium`)
326
- - `--environment`
327
- - `--repro-steps/--repro_steps`
328
- - `--resolution`
329
- - `--expected-result/--expected_result`
330
- - `--actual-result/--actual_result`
331
- - `--affected-version/--affected_version`
332
- - `--fixed-version/--fixed_version`
333
- - `--component`
334
- - `--regression` (`true|false|1|0`)
335
- - `--customer-impact/--customer_impact`
336
- - `--definition-of-ready/--definition_of_ready` (explicit empty allowed)
337
- - `--order/--rank` (integer rank/order)
338
- - `--goal`
339
- - `--objective`
340
- - `--value`
341
- - `--impact`
342
- - `--outcome`
343
- - `--why-now/--why_now`
344
- - required repeatable seed flags (pass each at least once; use `none` for explicit empty intent):
345
- - `--dep`
346
- - `--comment`
347
- - `--note`
348
- - `--learning`
349
- - `--file`
350
- - `--test`
351
- - `--doc`
352
-
353
- Explicit unset behavior:
354
-
355
- - scalar `none` means unset/omit that optional field
356
- - repeatable seed value `none` means explicit empty list intent
357
- - explicit unset intent is recorded in mutation history message metadata
358
-
359
- ### `pm update` explicit-field contract
360
-
361
- `pm update <ID>` accepts explicit mutation flags for canonical front-matter fields:
362
-
363
- - `--title/-t`
364
- - `--description/-d`
365
- - `--status/-s` (supports non-terminal values and `canceled`; use `pm close <ID> <TEXT>` for closure)
366
- - `--priority/-p`
367
- - `--type`
368
- - `--tags`
369
- - `--deadline`
370
- - `--estimate/--estimated-minutes/--estimated_minutes`
371
- - `--acceptance-criteria`, `--acceptance_criteria`, `--ac`
372
- - `--assignee`
373
- - `--parent`
374
- - `--reviewer`
375
- - `--risk` (`low|med|medium|high|critical`; `med` persists as `medium`)
376
- - `--confidence` (`0..100|low|med|medium|high`; `med` persists as `medium`)
377
- - `--sprint`
378
- - `--release`
379
- - `--blocked-by/--blocked_by`
380
- - `--blocked-reason/--blocked_reason`
381
- - `--unblock-note/--unblock_note`
382
- - `--reporter`
383
- - `--severity` (`low|med|medium|high|critical`; `med` persists as `medium`)
384
- - `--environment`
385
- - `--repro-steps/--repro_steps`
386
- - `--resolution`
387
- - `--expected-result/--expected_result`
388
- - `--actual-result/--actual_result`
389
- - `--affected-version/--affected_version`
390
- - `--fixed-version/--fixed_version`
391
- - `--component`
392
- - `--regression` (`true|false|1|0`)
393
- - `--customer-impact/--customer_impact`
394
- - `--definition-of-ready/--definition_of_ready`
395
- - `--order/--rank`
396
- - `--goal`
397
- - `--objective`
398
- - `--value`
399
- - `--impact`
400
- - `--outcome`
401
- - `--why-now/--why_now`
402
- - `--author`
403
- - `--message`
404
- - `--force`
405
-
406
- ### Exit codes
407
-
408
- - `0` success
409
- - `1` generic failure
410
- - `2` invalid usage/arguments
411
- - `3` not found
412
- - `4` conflict (lock/claim)
413
- - `5` dependency failed (e.g. test-all failure)
414
-
415
- ## Output Modes
416
-
417
- Default output is TOON (token-efficient, deterministic).
418
- Use `--json` for machine pipelines expecting JSON.
419
-
420
- Examples:
421
-
422
- ```bash
423
- pm list-open --limit 5
424
- pm list-open --limit 5 --json
425
- pm get pm-a1b2 --quiet; echo $?
426
- ```
427
-
428
- ## Configuration
429
-
430
- Primary config: `.agents/pm/settings.json`
431
-
432
- Typical keys:
433
-
434
- - `id_prefix`
435
- - `author_default`
436
- - `locks.ttl_seconds`
437
- - `output.default_format`
438
- - `workflow.definition_of_done`
439
- - `extensions.enabled / disabled`
440
- - `search.score_threshold`
441
- - `search.hybrid_semantic_weight`
442
- - `search.max_results`
443
- - `search.embedding_model`
444
- - `search.embedding_batch_size`
445
- - `search.scanner_max_batch_retries`
446
- - `search.tuning` (optional object)
447
- - provider + vector-store blocks
448
-
449
- `search.score_threshold` defaults to `0` and applies mode-specific minimum-score filtering (`keyword` raw lexical score, `semantic` vector score, `hybrid` normalized blended score).
450
- `search.hybrid_semantic_weight` defaults to `0.7` and controls hybrid semantic-vs-lexical blending (`0..1`).
451
- `search.tuning` optionally overrides deterministic lexical weighting (`title_exact_bonus`, `title_weight`, `description_weight`, `tags_weight`, `status_weight`, `body_weight`, `comments_weight`, `notes_weight`, `learnings_weight`, `dependencies_weight`, `linked_content_weight`) for keyword mode and the hybrid lexical component; invalid/negative values fall back to defaults.
452
- `workflow.definition_of_done` defaults to `[]` and stores deterministic team-level close-readiness criteria strings. The baseline config command surface is:
453
-
454
- ```bash
455
- pm config project set definition-of-done \
456
- --criterion "tests pass" \
457
- --criterion "linked files/tests/docs present"
458
-
459
- pm config project get definition-of-done
460
- pm config global get definition-of-done --json
461
- ```
462
-
463
- ### Environment variables
464
-
465
- - `PM_PATH` - project storage override
466
- - `PM_GLOBAL_PATH` - global extension root override
467
- - `PM_AUTHOR` - default mutation author
468
-
469
- Precedence:
470
-
471
- 1. CLI flags
472
- 2. env vars
473
- 3. settings.json
474
- 4. defaults
475
-
476
- ## Search and Extension System
477
-
478
- Keyword search is part of the implemented command surface, `pm search --include-linked` expands keyword scoring across readable linked docs/files/tests content while enforcing scope-root containment (`scope=project` and `scope=global` paths must stay within their allowed roots after both resolve-path and symlink-resolved-realpath checks; out-of-scope or realpath-escape paths are skipped), and `pm reindex` rebuilds deterministic keyword cache artifacts (`index/manifest.json` and `search/embeddings.jsonl`). Provider abstraction baseline is also in place for deterministic OpenAI/Ollama configuration resolution, request-target resolution (including OpenAI-compatible `base_url` normalization for root, `/v1`, and explicit `/embeddings` forms), provider-specific 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). Vector-store abstraction baseline is also in place for deterministic Qdrant/LanceDB configuration resolution, request-target planning, request payload/response normalization, deterministic Qdrant request-execution helper behavior, deterministic LanceDB local query/upsert/delete helper behavior, deterministic local snapshot persistence + reload across process boundaries, and deterministic query-hit ordering normalization (score descending with id ascending tie-break).
479
-
480
- Command-path semantic/hybrid baseline is now implemented: `pm reindex --mode semantic|hybrid` generates provider embeddings for canonical item corpus records and upserts vectors to the active store, while `pm search --mode semantic|hybrid` executes vector-query ranking with deterministic hybrid lexical+semantic blending in hybrid mode. Semantic embedding generation runs in deterministic batches using `settings.search.embedding_batch_size`, and each embedding batch retries failures up to `settings.search.scanner_max_batch_retries` before surfacing deterministic warnings/errors. Keyword/hybrid lexical scoring includes a deterministic exact-title token boost (full-token title matches receive additive lexical bonus weight) plus configurable multi-factor lexical tuning through `settings.search.tuning` (`title_exact_bonus`, `title_weight`, `description_weight`, `tags_weight`, `status_weight`, `body_weight`, `comments_weight`, `notes_weight`, `learnings_weight`, `dependencies_weight`, `linked_content_weight`; invalid/negative values fall back to defaults). Search scoring also honors `settings.search.score_threshold` as a mode-aware minimum score filter (`keyword` raw lexical score, `semantic` vector score, `hybrid` normalized blended score), and hybrid blending weight is configurable with `settings.search.hybrid_semantic_weight` (`0..1`, default `0.7`). Successful item-mutation command paths now invalidate stale keyword cache artifacts (`index/manifest.json` and `search/embeddings.jsonl`) and perform best-effort semantic embedding refresh for affected item IDs when embedding-provider and vector-store configuration are available; missing/deleted affected IDs trigger best-effort vector pruning from the active store. Refresh failures degrade to deterministic warnings. Broader advanced semantic/hybrid relevance tuning remains roadmap work.
481
-
482
- Built-in extension command handlers now provide import/export adapters: `pm beads import [--file <path>]` (import-only) ingests Beads JSONL records into PM items with deterministic defaults and `op: "import"` history entries, while `pm todos import|export [--folder <path>]` maps todos markdown files (JSON front-matter + body) to and from PM items using deterministic defaults for missing PM fields, preserves explicit imported IDs verbatim including hierarchical suffixes such as `pm-legacy.1.2`, and preserves canonical optional `ItemFrontMatter` metadata when present, including planning/workflow fields (`definition_of_ready`, `order`, `goal`, `objective`, `value`, `impact`, `outcome`, `why_now`, `reviewer`, `risk`, `confidence`, `sprint`, `release`, `blocked_by`, `blocked_reason`, `unblock_note`) and issue fields (`reporter`, `severity`, `environment`, `repro_steps`, `resolution`, `expected_result`, `actual_result`, `affected_version`, `fixed_version`, `component`, `regression`, `customer_impact`). `confidence`, `risk`, and `severity` aliases normalize deterministically (`med` -> `medium`). The Pi integration contract is provided as a Pi agent extension module at `.pi/extensions/pm-cli/index.ts`, which registers a `pm` tool for action-based invocations and returns `content` + `details` envelopes. Current Pi wrapper action coverage includes the 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 aliases (`beads-import`, `todos-import`, `todos-export`) and workflow presets (`start-task`, `pause-task`, `close-task`). For create/update parity, the wrapper accepts camelCase counterparts for the canonical CLI scalar metadata surface, completion parity field `shell` (`action=completion` -> `pm completion <shell>`), workflow/planning fields (`parent`, `reviewer`, `risk`, `confidence`, `sprint`, `release`, `blockedBy`, `blockedReason`, `unblockNote`, `definitionOfReady`, `order`, `goal`, `objective`, `value`, `impact`, `outcome`, `whyNow`) and issue fields (`reporter`, `severity`, `environment`, `reproSteps`, `resolution`, `expectedResult`, `actualResult`, `affectedVersion`, `fixedVersion`, `component`, `regression`, `customerImpact`), and forwards them deterministically to the corresponding `pm create`/`pm update` flags. Runtime extension loading includes deterministic manifest discovery, settings-aware enable/disable filtering, global-to-project precedence, extension-entry sandbox enforcement (entry paths and resolved symlink targets must remain inside their extension directory), and failure-isolated imports. `pm health` extension checks run the same load/activation probe used at runtime, including enabled built-in extensions, and surface deterministic diagnostics for manifest/entry warnings plus load and activation failures (for example `extension_entry_outside_extension:<layer>:<name>`, `extension_load_failed:<layer>:<name>`, and `extension_activate_failed:<layer>:<name>`).
483
-
484
- Hook lifecycle baseline includes `activate(api)` registration with deterministic ordering, registration-time hook handler validation (non-function payloads fail extension activation deterministically), per-hook context snapshot isolation so hook-side mutation cannot leak across callbacks or back into caller state, command-lifecycle `beforeCommand`/`afterCommand` execution with failure containment (including `afterCommand` dispatch on failed commands with `ok=false` and error context), and runtime read/write/index hook dispatch 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.
485
-
486
- Extension API baseline now includes deterministic command result override registration for existing core commands, command-handler registration for declared command paths (including built-in `beads import` and `todos import|export` paths plus extension-defined non-core command paths surfaced at runtime), command-handler execution with cloned `args`/`options`/`global` snapshots to prevent mutation leakage back into caller command state, command-override execution with cloned command `args`/`options`/`global` snapshots plus `pm_root` metadata and isolated prior-result snapshots, renderer execution with the same cloned command-context snapshots plus isolated result snapshots, renderer override registration for `toon`/`json` output formatting with safe fallback to built-in rendering on failures, and registration-time validation plus metadata capture for `registerFlags`, `registerItemFields`, `registerMigration`, `registerImporter`, `registerExporter`, `registerSearchProvider`, and `registerVectorStoreAdapter`. `registerImporter`/`registerExporter` registrations now also wire deterministic extension command-handler paths `<name> import` and `<name> export` (canonicalized with trim + lowercase + internal-whitespace collapse), and those handlers execute with the same isolated command-context snapshots as explicit `registerCommand` handlers. Dynamically surfaced extension command paths now include deterministic help sections derived from `registerFlags` metadata while preserving loose option parsing for runtime dispatch, with parser hardening that ignores unsafe prototype keys (`__proto__`, `constructor`, `prototype`) and uses null-prototype option maps before handing parsed options to extension handlers. Extension API and hook registration calls now enforce manifest capability declarations (`commands`, `renderers`, `hooks`, `schema`, `importers`, `search`) and fail activation deterministically when registrations exceed declared capabilities. Unknown capability names are ignored for registration gating and emit deterministic discovery diagnostics `extension_capability_unknown:<layer>:<name>:<capability>`. Activation diagnostics now include deterministic registration summaries for these registries, and health diagnostics include deterministic migration status summaries derived from registered migration definitions (`status=\"failed\"` -> failed, `status=\"applied\"` -> applied, otherwise pending). Core write command paths now enforce deterministic mandatory-migration blocking when registered migration definitions declare `mandatory=true` and status is not `applied` (case-insensitive), with explicit `--force` bypass support on force-capable write commands. Broader runtime wiring for other newly registered definitions remains tracked in `PRD.md`.
487
-
488
- ## Pi Agent Extension
489
-
490
- `pm-cli` ships a Pi agent extension source module at `.pi/extensions/pm-cli/index.ts`.
491
-
492
- Install it via `pm` (recommended):
493
-
494
- ```bash
495
- # current project scope (default)
496
- pm install pi
497
-
498
- # explicit project scope
499
- pm install pi --project
500
-
501
- # global Pi scope (~/.pi/agent unless PI_CODING_AGENT_DIR is set)
502
- pm install pi --global
503
- ```
504
-
505
- Load it in Pi:
506
-
507
- ```bash
508
- pi -e ./.pi/extensions/pm-cli/index.ts
509
- ```
510
-
511
- Or place/copy it into a Pi auto-discovery folder such as `.pi/extensions/`.
512
-
513
- The extension registers one tool, `pm`, with action-based parameters and returns:
514
-
515
- - `content: [{ type: "text", text: "..." }]`
516
- - `details: { ... }`
517
-
518
- For search parity, wrapper parameters support `includeLinked` and map it to `pm search --include-linked`.
519
- For project tracking access in Pi TUI, run Pi from the project root (so `pm` resolves the repo `.agents/pm`), or pass wrapper `path` to target another PM store.
520
- For command-shape parity, explicit empty-string values are forwarded for empty-allowed flags (for example `--description ""` and `--body ""`) instead of being dropped.
521
- For numeric-flag parity, wrapper parameters accept either JSON numbers or strings for `priority`, `estimate`, `limit`, and `timeout`, and stringify them before CLI invocation.
522
- For claim/release parity, wrapper parameters `author`, `message`, and `force` are forwarded to `pm claim|release --author/--message/--force`.
523
- For packaging resilience (implemented), the wrapper attempts `pm` first and falls back to `node <package-root>/dist/cli.js` when `pm` is unavailable.
524
-
525
- ## Shell Completion
526
-
527
- `pm` supports tab-completion for bash, zsh, and fish shells.
528
-
529
- ### Bash
530
-
531
- ```bash
532
- # Add to ~/.bashrc or ~/.bash_profile
533
- eval "$(pm completion bash)"
534
- ```
535
-
536
- ### Zsh
537
-
538
- ```bash
539
- # Add to ~/.zshrc
540
- eval "$(pm completion zsh)"
541
- ```
542
-
543
- ### Fish
544
-
545
- ```bash
546
- # Generate and save the completion file
547
- pm completion fish > ~/.config/fish/completions/pm.fish
548
- ```
549
-
550
- ### JSON output
551
-
552
- ```bash
553
- pm completion bash --json
554
- # => { "shell": "bash", "script": "...", "setup_hint": "..." }
555
- ```
556
-
557
- Completion covers all `pm` subcommands, their flags, and common argument values (item types, statuses, priorities, search modes, shell names).
558
-
559
- ## FAQ
560
-
561
- ### Why JSON front-matter instead of YAML?
562
-
563
- Deterministic parsing/serialization and fewer parser ambiguities for agent tooling.
564
-
565
- ### Why TOON by default?
566
-
567
- TOON reduces token usage and keeps structure predictable for LLM workflows.
568
-
569
- ### Can I use `pm` without semantic search?
570
-
571
- Yes. Keyword mode is always available.
572
-
573
- ### Is a database required?
574
-
575
- No for core tracking. Core is file-backed. Vector DB is optional for semantic search.
576
-
577
- ### Can I restore previous versions?
578
-
579
- Yes. `pm` supports restoring an item to a prior state by timestamp or history version:
580
-
581
- ```bash
582
- pm restore <ID> <TIMESTAMP|VERSION>
583
- ```
584
-
585
- Restore replays append-only history to the target point, rewrites the item atomically, and appends a new `restore` history event.
586
-
587
- ## Troubleshooting
588
-
589
- ### Item not found
590
-
591
- - Use normalized id and check:
592
- - `pm list-all --limit 100 --json`
593
-
594
- ### Command appears missing
595
-
596
- - Check `pm --help` for the implemented command surface in this version.
597
- - Confirm whether the command is listed under "Roadmap (post-v0.1 / partial areas)".
598
-
599
- ## Testing and Coverage Policy
600
-
601
- - All tests must run with a sandbox `PM_PATH` (never the repository's real `.agents/pm`).
602
- - PM-linked test execution should use `node scripts/run-tests.mjs <test|coverage> [-- <vitest args...>]` so both `PM_PATH` and `PM_GLOBAL_PATH` are sandboxed per run; forwarded args target Vitest directly (for example: `node scripts/run-tests.mjs test -- tests/unit/health-command.spec.ts`).
603
- - `pm test <ID>` linked command entries must not invoke `pm test-all` (including global-flag and package-spec launcher forms like `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`); the CLI rejects recursive orchestration entries at add-time.
604
- - `pm test <ID> --run` defensively skips legacy linked command entries that invoke `pm test-all` (including global-flag and package-spec launcher forms such as `npx`, `pnpm dlx`, and `npm exec` launcher variants) and reports deterministic skipped results.
605
- - `pm test <ID>` linked test-runner command entries must use `node scripts/run-tests.mjs ...` or explicitly set both `PM_PATH` and `PM_GLOBAL_PATH`; the CLI rejects sandbox-unsafe variants at add-time, including unsandboxed package-manager run-script forms like `npm run test` / `pnpm run test` and chained direct test-runner segments that are not explicitly sandboxed.
606
- - `pm test-all` runs each unique linked command/path key once per invocation and marks duplicates as skipped for deterministic orchestration output; duplicate-key timeout conflicts resolve to the maximum `timeout_seconds` for that key.
607
- - Integration tests spawn the built CLI (`node dist/cli.js ...`) with test-specific `PM_PATH`, `PM_GLOBAL_PATH`, and `PM_AUTHOR`.
608
- - Coverage thresholds are enforced at `100%` for lines, branches, functions, and statements.
609
- - `pm` project data in `.agents/pm` is reserved for living planning/logging only.
610
-
611
- ## Community and Governance Files
612
-
613
- Release-ready repository baseline includes:
614
-
615
- - `LICENSE` (MIT)
616
- - `CHANGELOG.md` (Keep a Changelog + SemVer note + `[Unreleased]`)
617
- - `CONTRIBUTING.md` (development and contribution workflow)
618
- - `SECURITY.md` (security reporting policy)
619
- - `CODE_OF_CONDUCT.md` (contributor behavior baseline)
620
-
621
- ## Release Readiness Checklist
622
-
623
- ```bash
624
- pnpm install
625
- pnpm build
626
- pnpm typecheck
627
- pnpm test
628
- pnpm test:coverage
629
- pnpm version:check
630
- pnpm security:scan
631
- node scripts/run-tests.mjs coverage
632
- pnpm smoke:npx
633
- ```
634
-
635
- Manual smoke checks:
636
-
637
- - install packed tarball globally and run `pm --help`
638
- - run `bash scripts/install.sh`
639
- - run `pwsh scripts/install.ps1`
640
-
641
- ### Automated Release
642
-
643
- Pushing a version tag triggers the automated npm publish workflow:
644
-
645
- ```bash
646
- git tag v2026.3.9
647
- git push origin v2026.3.9
648
- ```
649
-
650
- The `.github/workflows/release.yml` workflow runs full validation, enforces calendar version policy, and publishes `@unbrained/pm-cli` to npm only when all checks pass. It uses the `release` GitHub Environment and requires the `NPM_TOKEN` environment secret.
651
-
652
- ## Project Status
653
-
654
- Release-hardening is active.
655
- `PRD.md`, `AGENTS.md`, and this README define the current public and contributor contracts.
44
+ --tags "windows,locks,restore,release" \
45
+ --body "Users can reproduce this on Windows after an interrupted restore. Add retry logging and verify restore succeeds after stale lock cleanup." \
46
+ --deadline +3d \
47
+ --estimate 180 \
48
+ --acceptance-criteria "Restore succeeds after stale lock cleanup on Windows and regression coverage is added." \
49
+ --definition-of-ready "Owner, reproduction steps, and affected files are identified." \
50
+ --order 7 \
51
+ --goal "Release readiness" \
52
+ --objective "Stabilize restore under lock contention" \
53
+ --value "Reduces failed recovery workflows during real incidents" \
54
+ --impact "Fewer blocked releases and clearer operator recovery steps" \
55
+ --outcome "Restore completes reliably after stale lock cleanup" \
56
+ --why-now "The bug affects recovery flows and can block release work." \
57
+ --author "alex-maintainer" \
58
+ --message "Create restore failure issue with full metadata" \
59
+ --assignee "alex-maintainer" \
60
+ --parent "pm-release" \
61
+ --reviewer "sam-reviewer" \
62
+ --risk high \
63
+ --confidence medium \
64
+ --sprint "2026-W11" \
65
+ --release "v2026.3" \
66
+ --blocked-by "pm-locks" \
67
+ --blocked-reason "Need the lock cleanup refactor merged first" \
68
+ --unblock-note "Rebase once the lock cleanup patch lands" \
69
+ --reporter "qa-bot" \
70
+ --severity high \
71
+ --environment "windows-11 node-25 npm-global-install" \
72
+ --repro-steps "1) Interrupt restore after lock creation 2) Retry restore 3) Observe stale-lock cleanup fail on Windows" \
73
+ --resolution "Add a retry after stale-lock cleanup and log the recovery path" \
74
+ --expected-result "Restore retries cleanly and completes after stale-lock cleanup." \
75
+ --actual-result "Restore exits with a lock error after cleanup on Windows." \
76
+ --affected-version "2026.3.9" \
77
+ --fixed-version "2026.3.10" \
78
+ --component "core/locks" \
79
+ --regression true \
80
+ --customer-impact "Maintainers can be blocked from recovering work during release prep." \
81
+ --dep "id=pm-locks,kind=blocks,author=alex-maintainer,created_at=now" \
82
+ --comment "author=alex-maintainer,created_at=now,text=Initial triage confirms the Windows-only stale-lock recovery failure." \
83
+ --note "author=alex-maintainer,created_at=now,text=Investigate lock cleanup timing in the restore retry path." \
84
+ --learning "author=alex-maintainer,created_at=now,text=Windows file-handle timing needs a retry window after stale-lock cleanup." \
85
+ --file "path=src/core/lock/lock-store.ts,scope=project,note=likely stale-lock retry fix" \
86
+ --test "command=node scripts/run-tests.mjs test -- tests/integration/cli.integration.spec.ts,scope=project,timeout_seconds=900,note=restore lock regression coverage" \
87
+ --doc "path=docs/ARCHITECTURE.md,scope=project,note=lock and restore design context"
88
+
89
+ pm list-open --limit 10
90
+ pm claim <item-id>
91
+ ```
92
+
93
+ From there, use `pm update`, `pm comments`, `pm files`, `pm test`, `pm search`, and `pm close` as work progresses.
94
+
95
+ ## Documentation
96
+
97
+ - [Architecture](docs/ARCHITECTURE.md)
98
+ - [Extensions](docs/EXTENSIONS.md)
99
+ - [Contributing](CONTRIBUTING.md)
100
+ - [Security Policy](SECURITY.md)
101
+ - [Changelog](CHANGELOG.md)
102
+
103
+ ## License
104
+
105
+ MIT. See [LICENSE](LICENSE).