@unbrained/pm-cli 2026.5.1 → 2026.5.3-5

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 (265) hide show
  1. package/AGENTS.md +8 -1
  2. package/CHANGELOG.md +73 -4
  3. package/CONTRIBUTING.md +11 -5
  4. package/PRD.md +17 -1
  5. package/README.md +55 -1099
  6. package/SECURITY.md +6 -11
  7. package/dist/cli/bootstrap-args.d.ts +18 -0
  8. package/dist/cli/bootstrap-args.js +242 -0
  9. package/dist/cli/bootstrap-args.js.map +1 -0
  10. package/dist/cli/commander-usage.d.ts +17 -0
  11. package/dist/cli/commander-usage.js +178 -0
  12. package/dist/cli/commander-usage.js.map +1 -0
  13. package/dist/cli/commands/activity.d.ts +10 -0
  14. package/dist/cli/commands/activity.js +14 -10
  15. package/dist/cli/commands/activity.js.map +1 -1
  16. package/dist/cli/commands/aggregate.js.map +1 -1
  17. package/dist/cli/commands/append.js.map +1 -1
  18. package/dist/cli/commands/calendar.js +19 -34
  19. package/dist/cli/commands/calendar.js.map +1 -1
  20. package/dist/cli/commands/claim.js.map +1 -1
  21. package/dist/cli/commands/close.js.map +1 -1
  22. package/dist/cli/commands/comments-audit.js.map +1 -1
  23. package/dist/cli/commands/comments.js +1 -9
  24. package/dist/cli/commands/comments.js.map +1 -1
  25. package/dist/cli/commands/completion.js.map +1 -1
  26. package/dist/cli/commands/config.d.ts +21 -3
  27. package/dist/cli/commands/config.js +118 -2
  28. package/dist/cli/commands/config.js.map +1 -1
  29. package/dist/cli/commands/context.d.ts +90 -1
  30. package/dist/cli/commands/context.js +496 -12
  31. package/dist/cli/commands/context.js.map +1 -1
  32. package/dist/cli/commands/contracts.js.map +1 -1
  33. package/dist/cli/commands/create.js +2 -2
  34. package/dist/cli/commands/create.js.map +1 -1
  35. package/dist/cli/commands/dedupe-audit.js +2 -11
  36. package/dist/cli/commands/dedupe-audit.js.map +1 -1
  37. package/dist/cli/commands/delete.js.map +1 -1
  38. package/dist/cli/commands/deps.js.map +1 -1
  39. package/dist/cli/commands/docs.js.map +1 -1
  40. package/dist/cli/commands/extension.js.map +1 -1
  41. package/dist/cli/commands/files.js +14 -2
  42. package/dist/cli/commands/files.js.map +1 -1
  43. package/dist/cli/commands/gc.js.map +1 -1
  44. package/dist/cli/commands/get.js.map +1 -1
  45. package/dist/cli/commands/health.js +16 -12
  46. package/dist/cli/commands/health.js.map +1 -1
  47. package/dist/cli/commands/history.js +1 -9
  48. package/dist/cli/commands/history.js.map +1 -1
  49. package/dist/cli/commands/index.js.map +1 -1
  50. package/dist/cli/commands/init.js.map +1 -1
  51. package/dist/cli/commands/learnings.js +1 -9
  52. package/dist/cli/commands/learnings.js.map +1 -1
  53. package/dist/cli/commands/list.d.ts +1 -0
  54. package/dist/cli/commands/list.js +13 -31
  55. package/dist/cli/commands/list.js.map +1 -1
  56. package/dist/cli/commands/normalize.js +14 -23
  57. package/dist/cli/commands/normalize.js.map +1 -1
  58. package/dist/cli/commands/notes.js +1 -9
  59. package/dist/cli/commands/notes.js.map +1 -1
  60. package/dist/cli/commands/reindex.js +2 -7
  61. package/dist/cli/commands/reindex.js.map +1 -1
  62. package/dist/cli/commands/restore.js.map +1 -1
  63. package/dist/cli/commands/search.js +4 -35
  64. package/dist/cli/commands/search.js.map +1 -1
  65. package/dist/cli/commands/stats.js.map +1 -1
  66. package/dist/cli/commands/templates.js.map +1 -1
  67. package/dist/cli/commands/test-all.js.map +1 -1
  68. package/dist/cli/commands/test-runs.js +1 -11
  69. package/dist/cli/commands/test-runs.js.map +1 -1
  70. package/dist/cli/commands/test.js.map +1 -1
  71. package/dist/cli/commands/update-many.js +1 -6
  72. package/dist/cli/commands/update-many.js.map +1 -1
  73. package/dist/cli/commands/update.js +2 -2
  74. package/dist/cli/commands/update.js.map +1 -1
  75. package/dist/cli/commands/validate.js +23 -18
  76. package/dist/cli/commands/validate.js.map +1 -1
  77. package/dist/cli/error-guidance.d.ts +13 -0
  78. package/dist/cli/error-guidance.js +56 -6
  79. package/dist/cli/error-guidance.js.map +1 -1
  80. package/dist/cli/extension-command-help.d.ts +48 -0
  81. package/dist/cli/extension-command-help.js +389 -0
  82. package/dist/cli/extension-command-help.js.map +1 -0
  83. package/dist/cli/extension-command-options.js.map +1 -1
  84. package/dist/cli/help-content.js +9 -3
  85. package/dist/cli/help-content.js.map +1 -1
  86. package/dist/cli/help-json-payload.d.ts +25 -0
  87. package/dist/cli/help-json-payload.js +265 -0
  88. package/dist/cli/help-json-payload.js.map +1 -0
  89. package/dist/cli/main.js +1000 -4456
  90. package/dist/cli/main.js.map +1 -1
  91. package/dist/cli/migration-gates.d.ts +22 -0
  92. package/dist/cli/migration-gates.js +146 -0
  93. package/dist/cli/migration-gates.js.map +1 -0
  94. package/dist/cli/register-list-query.d.ts +2 -0
  95. package/dist/cli/register-list-query.js +317 -0
  96. package/dist/cli/register-list-query.js.map +1 -0
  97. package/dist/cli/register-mutation.d.ts +2 -0
  98. package/dist/cli/register-mutation.js +795 -0
  99. package/dist/cli/register-mutation.js.map +1 -0
  100. package/dist/cli/register-operations.d.ts +2 -0
  101. package/dist/cli/register-operations.js +610 -0
  102. package/dist/cli/register-operations.js.map +1 -0
  103. package/dist/cli/register-setup.d.ts +2 -0
  104. package/dist/cli/register-setup.js +334 -0
  105. package/dist/cli/register-setup.js.map +1 -0
  106. package/dist/cli/registration-helpers.d.ts +53 -0
  107. package/dist/cli/registration-helpers.js +669 -0
  108. package/dist/cli/registration-helpers.js.map +1 -0
  109. package/dist/cli/shared-parsers.d.ts +6 -0
  110. package/dist/cli/shared-parsers.js +40 -0
  111. package/dist/cli/shared-parsers.js.map +1 -0
  112. package/dist/cli.d.ts +1 -1
  113. package/dist/cli.js +3 -1
  114. package/dist/cli.js.map +1 -1
  115. package/dist/core/extensions/extension-types.d.ts +605 -0
  116. package/dist/core/extensions/extension-types.js +22 -0
  117. package/dist/core/extensions/extension-types.js.map +1 -0
  118. package/dist/core/extensions/index.js.map +1 -1
  119. package/dist/core/extensions/item-fields.js.map +1 -1
  120. package/dist/core/extensions/loader.d.ts +2 -586
  121. package/dist/core/extensions/loader.js +3 -21
  122. package/dist/core/extensions/loader.js.map +1 -1
  123. package/dist/core/extensions/runtime-registrations.js.map +1 -1
  124. package/dist/core/fs/fs-utils.js.map +1 -1
  125. package/dist/core/fs/index.js.map +1 -1
  126. package/dist/core/history/history-stream-policy.js.map +1 -1
  127. package/dist/core/history/history.js.map +1 -1
  128. package/dist/core/history/index.js.map +1 -1
  129. package/dist/core/item/id.js.map +1 -1
  130. package/dist/core/item/index.js.map +1 -1
  131. package/dist/core/item/item-format.js.map +1 -1
  132. package/dist/core/item/parent-reference-policy.js.map +1 -1
  133. package/dist/core/item/parse.js +6 -0
  134. package/dist/core/item/parse.js.map +1 -1
  135. package/dist/core/item/sprint-release-format.js.map +1 -1
  136. package/dist/core/item/status.js.map +1 -1
  137. package/dist/core/item/type-registry.js.map +1 -1
  138. package/dist/core/lock/index.js.map +1 -1
  139. package/dist/core/lock/lock.js +1 -6
  140. package/dist/core/lock/lock.js.map +1 -1
  141. package/dist/core/output/command-aware.js.map +1 -1
  142. package/dist/core/output/output.js.map +1 -1
  143. package/dist/core/schema/runtime-field-filters.js.map +1 -1
  144. package/dist/core/schema/runtime-field-values.js.map +1 -1
  145. package/dist/core/schema/runtime-schema.js.map +1 -1
  146. package/dist/core/search/cache.js +1 -7
  147. package/dist/core/search/cache.js.map +1 -1
  148. package/dist/core/search/embedding-batches.js +4 -0
  149. package/dist/core/search/embedding-batches.js.map +1 -1
  150. package/dist/core/search/http-client.d.ts +29 -0
  151. package/dist/core/search/http-client.js +64 -0
  152. package/dist/core/search/http-client.js.map +1 -0
  153. package/dist/core/search/providers.d.ts +3 -13
  154. package/dist/core/search/providers.js +19 -88
  155. package/dist/core/search/providers.js.map +1 -1
  156. package/dist/core/search/semantic-defaults.js +2 -7
  157. package/dist/core/search/semantic-defaults.js.map +1 -1
  158. package/dist/core/search/vector-stores.d.ts +4 -13
  159. package/dist/core/search/vector-stores.js +40 -93
  160. package/dist/core/search/vector-stores.js.map +1 -1
  161. package/dist/core/sentry/helpers.d.ts +27 -0
  162. package/dist/core/sentry/helpers.js +171 -0
  163. package/dist/core/sentry/helpers.js.map +1 -0
  164. package/dist/core/sentry/instrument.d.ts +25 -0
  165. package/dist/core/sentry/instrument.js +204 -0
  166. package/dist/core/sentry/instrument.js.map +1 -0
  167. package/dist/core/shared/command-types.js.map +1 -1
  168. package/dist/core/shared/conflict-markers.js.map +1 -1
  169. package/dist/core/shared/constants.d.ts +3 -0
  170. package/dist/core/shared/constants.js +58 -1
  171. package/dist/core/shared/constants.js.map +1 -1
  172. package/dist/core/shared/errors.js.map +1 -1
  173. package/dist/core/shared/index.d.ts +1 -0
  174. package/dist/core/shared/index.js +1 -0
  175. package/dist/core/shared/index.js.map +1 -1
  176. package/dist/core/shared/primitives.d.ts +13 -0
  177. package/dist/core/shared/primitives.js +33 -0
  178. package/dist/core/shared/primitives.js.map +1 -0
  179. package/dist/core/shared/serialization.js.map +1 -1
  180. package/dist/core/shared/text-normalization.js.map +1 -1
  181. package/dist/core/shared/time.js.map +1 -1
  182. package/dist/core/store/front-matter-cache.d.ts +6 -0
  183. package/dist/core/store/front-matter-cache.js +150 -0
  184. package/dist/core/store/front-matter-cache.js.map +1 -0
  185. package/dist/core/store/index.js.map +1 -1
  186. package/dist/core/store/item-format-migration.js.map +1 -1
  187. package/dist/core/store/item-store.js +46 -36
  188. package/dist/core/store/item-store.js.map +1 -1
  189. package/dist/core/store/paths.js.map +1 -1
  190. package/dist/core/store/settings.js +36 -0
  191. package/dist/core/store/settings.js.map +1 -1
  192. package/dist/core/telemetry/consent.js.map +1 -1
  193. package/dist/core/telemetry/observability.d.ts +24 -0
  194. package/dist/core/telemetry/observability.js +185 -0
  195. package/dist/core/telemetry/observability.js.map +1 -0
  196. package/dist/core/telemetry/runtime.d.ts +29 -3
  197. package/dist/core/telemetry/runtime.js +337 -25
  198. package/dist/core/telemetry/runtime.js.map +1 -1
  199. package/dist/core/test/background-runs.js.map +1 -1
  200. package/dist/core/test/item-test-run-tracking.js.map +1 -1
  201. package/dist/sdk/cli-contracts.js +28 -0
  202. package/dist/sdk/cli-contracts.js.map +1 -1
  203. package/dist/sdk/index.d.ts +1 -1
  204. package/dist/sdk/index.js.map +1 -1
  205. package/dist/types/index.js.map +1 -1
  206. package/dist/types.d.ts +21 -0
  207. package/dist/types.js +11 -0
  208. package/dist/types.js.map +1 -1
  209. package/docs/AGENT_GUIDE.md +125 -0
  210. package/docs/ARCHITECTURE.md +201 -478
  211. package/docs/COMMANDS.md +209 -0
  212. package/docs/CONFIGURATION.md +146 -0
  213. package/docs/EXTENSIONS.md +146 -645
  214. package/docs/QUICKSTART.md +108 -0
  215. package/docs/README.md +70 -0
  216. package/docs/RELEASING.md +92 -50
  217. package/docs/SDK.md +127 -68
  218. package/docs/TESTING.md +125 -0
  219. package/docs/examples/starter-extension/README.md +39 -25
  220. package/package.json +24 -11
  221. package/dist/command-types.d.ts +0 -1
  222. package/dist/command-types.js +0 -2
  223. package/dist/command-types.js.map +0 -1
  224. package/dist/constants.d.ts +0 -1
  225. package/dist/constants.js +0 -2
  226. package/dist/constants.js.map +0 -1
  227. package/dist/errors.d.ts +0 -1
  228. package/dist/errors.js +0 -2
  229. package/dist/errors.js.map +0 -1
  230. package/dist/fs-utils.d.ts +0 -1
  231. package/dist/fs-utils.js +0 -2
  232. package/dist/fs-utils.js.map +0 -1
  233. package/dist/history.d.ts +0 -1
  234. package/dist/history.js +0 -2
  235. package/dist/history.js.map +0 -1
  236. package/dist/id.d.ts +0 -1
  237. package/dist/id.js +0 -2
  238. package/dist/id.js.map +0 -1
  239. package/dist/item-format.d.ts +0 -1
  240. package/dist/item-format.js +0 -2
  241. package/dist/item-format.js.map +0 -1
  242. package/dist/item-store.d.ts +0 -1
  243. package/dist/item-store.js +0 -2
  244. package/dist/item-store.js.map +0 -1
  245. package/dist/lock.d.ts +0 -1
  246. package/dist/lock.js +0 -2
  247. package/dist/lock.js.map +0 -1
  248. package/dist/output.d.ts +0 -1
  249. package/dist/output.js +0 -2
  250. package/dist/output.js.map +0 -1
  251. package/dist/parse.d.ts +0 -1
  252. package/dist/parse.js +0 -2
  253. package/dist/parse.js.map +0 -1
  254. package/dist/paths.d.ts +0 -1
  255. package/dist/paths.js +0 -2
  256. package/dist/paths.js.map +0 -1
  257. package/dist/serialization.d.ts +0 -1
  258. package/dist/serialization.js +0 -2
  259. package/dist/serialization.js.map +0 -1
  260. package/dist/settings.d.ts +0 -1
  261. package/dist/settings.js +0 -2
  262. package/dist/settings.js.map +0 -1
  263. package/dist/time.d.ts +0 -1
  264. package/dist/time.js +0 -2
  265. package/dist/time.js.map +0 -1
@@ -1,734 +1,235 @@
1
- # pm-cli Extension Development Guide
1
+ # Extensions
2
2
 
3
- Extensions let you add commands, parser/preflight lifecycle control, core service overrides, renderers, importers, exporters, schema fields, item-type definitions, search providers, and lifecycle hooks to `pm-cli` without modifying core.
3
+ Extensions add commands, schema, renderers, importers/exporters, search adapters, lifecycle hooks, and selected runtime overrides without modifying core `pm-cli`.
4
4
 
5
- For a quick SDK-first authoring path, start with `docs/SDK.md`.
5
+ ## Agent Quick Context
6
+
7
+ - Use `pm extension init ./my-extension` for a starter scaffold.
8
+ - Use `@unbrained/pm-cli/sdk` for public extension APIs.
9
+ - Declare only the capabilities your extension uses.
10
+ - Run `pm extension doctor --detail deep --trace` for activation failures.
11
+ - Use `--no-extensions` to isolate core behavior during incident triage.
12
+
13
+ Tracked documentation work: [pm-1sb2](../.agents/pm/tasks/pm-1sb2.toon).
6
14
 
7
15
  ## Extension Locations
8
16
 
9
17
  | Scope | Path |
10
18
  |-------|------|
11
- | Global | `~/.pm-cli/extensions/<name>/` (override: `PM_GLOBAL_PATH/extensions/<name>/`) |
12
- | Project | `.agents/pm/extensions/<name>/` (override: `PM_PATH/extensions/<name>/`) |
13
-
14
- **Load order:** global → project. Project-local extensions take precedence over global when they declare the same command name or renderer key.
15
-
16
- ## Linked-Test Sandbox Parity
17
-
18
- `pm test --run` and `pm test-all` execute linked commands in temporary sandbox roots (`PM_PATH`, `PM_GLOBAL_PATH`) to avoid mutating live tracker data. Before command execution, the runtime seeds sandbox project/global `settings.json` and `extensions/` directories from the source roots.
19
-
20
- This preserves extension-defined schema behavior (including custom item type validation/filtering) while retaining sandbox isolation for linked-test execution.
21
-
22
- Linked-test runtime controls are additive: run-level `--env-set`/`--env-clear`/`--shared-host-safe` flags and per-linked-test metadata directives (`env_set`, `env_clear`, `shared_host_safe`) apply before sandbox-protected `PM_PATH`/`PM_GLOBAL_PATH` overrides.
23
-
24
- PM-command linked tests can opt into tracker-data parity via `--pm-context tracker` (default `schema` keeps isolated tracker data while still seeding settings/extensions). `--pm-context auto` automatically routes PM tracker-read commands through tracker-seeded context while keeping other commands in schema context. Per-linked-test metadata can override run-level mode with `pm_context_mode=schema|tracker|auto`. In default `schema` mode, PM tracker-read linked commands fail on context mismatch by default; strict guards remain available for other PM command shapes via `--fail-on-context-mismatch` plus `--fail-on-skipped` and `--require-assertions-for-pm`.
25
-
26
- Context ergonomics flags are additive:
27
-
28
- - `--check-context` emits deterministic preflight diagnostics/warnings (`context_preflight`) for PM command context mismatches
29
- - `--auto-pm-context` auto-remediates tracker-read mismatches by routing those commands to tracker context
30
-
31
- Per-run `execution_context` metadata includes resolved roots, item counts, mismatch signal, PM tracker-read classification, `requested_pm_context_mode`, and `auto_pm_context_applied`.
32
-
33
- Linked-test assertion metadata is optional and additive (`assert_stdout_contains`, `assert_stdout_regex`, `assert_stderr_contains`, `assert_stderr_regex`, `assert_stdout_min_lines`, `assert_json_field_equals`, `assert_json_field_gte`).
34
-
35
- ## Lifecycle Manager CLI
36
-
37
- `pm extension` is the canonical lifecycle manager for custom extensions.
19
+ | Project | `.agents/pm/extensions/<name>/` |
20
+ | Global | `~/.pm-cli/extensions/<name>/` |
38
21
 
39
- Top-level action and subcommand forms are flag-equivalent for lifecycle operations (for example `pm extension --doctor --detail deep --trace` and `pm extension doctor --detail deep --trace` resolve the same behavior).
22
+ Environment overrides:
40
23
 
41
- ### Actions
24
+ - `PM_PATH` changes project tracker root.
25
+ - `PM_GLOBAL_PATH` changes global profile root.
42
26
 
43
- Pass exactly one action flag:
27
+ Load order is global, then project. Project extensions take precedence when keys collide.
44
28
 
45
- - `--init` (alias: `--scaffold`)
46
- - `--install`
47
- - `--uninstall`
48
- - `--explore`
49
- - `--manage`
50
- - `--doctor`
51
- - `--adopt`
52
- - `--adopt-all`
53
- - `--activate`
54
- - `--deactivate`
29
+ ## Lifecycle Manager
55
30
 
56
- ### Scope selection
57
-
58
- - `--project` (default)
59
- - `--local` (alias for `--project`)
60
- - `--global`
61
-
62
- Project scope resolves against `.agents/pm/extensions`. Global scope resolves against `~/.pm-cli/extensions` (or `PM_GLOBAL_PATH/extensions`).
63
-
64
- ### Install source normalization
65
-
66
- `pm extension --install` accepts:
67
-
68
- - local directory paths
69
- - GitHub HTTPS URLs
70
- - `github.com/<owner>/<repo>[/path]` shorthand
71
- - `--gh <owner>/<repo>[/path]` (alias: `--github`)
72
- - optional `--ref <branch|tag|sha>` for GitHub sources
73
-
74
- When the source path is shorthand (for example `owner/repo/my-ext`), install resolution probes in this order:
75
-
76
- 1. `<clone>/<subpath>`
77
- 2. `<clone>/.agents/pm/extensions/<subpath>`
78
- 3. `<clone>/.custom/pm-extensions/<subpath>`
79
- 4. `<clone>/.custom/pm-extension/<subpath>`
80
-
81
- If no subpath is supplied, the resolver accepts either:
82
-
83
- - repo root containing one extension (`manifest.json` at root), or
84
- - exactly one extension under default roots (`.agents/pm/extensions`, `.custom/pm-extensions`, `.custom/pm-extension`)
85
-
86
- If multiple extension manifests are discovered, install fails with deterministic guidance to provide an explicit path.
87
-
88
- ### Requested equivalence examples
31
+ Scaffold:
89
32
 
90
33
  ```bash
91
- # Scaffold a starter extension project in your workspace
92
- pm extension --init ./my-extension
34
+ pm extension init ./my-extension
93
35
  pm extension scaffold ./my-extension
36
+ ```
94
37
 
95
- # Bundled aliases shipped with pm-cli (not auto-installed)
96
- pm extension --install --project beads
97
- pm extension --install --project todos
98
-
99
- # Equivalent bundled local paths
100
- pm extension --install --project .agents/pm/extensions/beads
101
- pm extension --install --project .agents/pm/extensions/todos
102
-
103
- # Custom roots
104
- pm extension --install --project https://github.com/unbraind/pm-cli/tree/main/.custom/pm-extensions/my-ext
105
- pm extension --install --project github.com/unbraind/pm-cli/.custom/pm-extensions/my-ext
106
- pm extension --install --project --gh unbraind/pm-cli/.custom/pm-extensions/my-ext
38
+ Install:
107
39
 
108
- # Single-extension repo or extension rooted at repository top-level
109
- pm extension --install --project https://github.com/unbraind/pm-cli
110
- pm extension --install --project github.com/unbraind/pm-cli
111
- pm extension --install --project --gh unbraind/pm-cli
40
+ ```bash
41
+ pm extension install ./my-extension --project
42
+ pm extension install github.com/unbraind/pm-cli/.agents/pm/extensions/todos --project
43
+ pm extension --install --project todos
112
44
  ```
113
45
 
114
- Canonical public shorthand examples in this repository use `unbraind/pm-cli`. For private repositories, make sure git authentication is configured before using `--gh` or `github.com/...` selectors.
46
+ Inspect and manage:
115
47
 
116
- ### Managed extension state
48
+ ```bash
49
+ pm extension explore --project
50
+ pm extension manage --project
51
+ pm extension doctor --detail summary
52
+ pm extension doctor --detail deep --trace
53
+ ```
117
54
 
118
- Each scope maintains a lifecycle state file:
55
+ Activate and deactivate:
119
56
 
120
- - `<scope-extension-root>/.managed-extensions.json`
57
+ ```bash
58
+ pm extension activate my-extension --project
59
+ pm extension deactivate my-extension --project
60
+ ```
121
61
 
122
- State records include deterministic source metadata (`local` or `github`), install timestamps, manifest summary, and update-check metadata.
62
+ Adopt unmanaged extensions:
123
63
 
124
- Lifecycle semantics:
64
+ ```bash
65
+ pm extension adopt my-extension --project
66
+ pm extension adopt-all --project
67
+ ```
125
68
 
126
- - Init/scaffold creates an idempotent starter extension project (`manifest.json`, `index.js`, `README.md`) and fails fast on conflicting pre-existing file contents.
127
- - Install copies/clones into the selected extension root, validates `manifest.json` and `entry`, updates managed state, and activates the extension in settings.
128
- - Uninstall removes extension files, removes managed-state entry, and clears settings references.
129
- - Activate/deactivate updates `settings.extensions.enabled[]` / `settings.extensions.disabled[]`.
130
- - Explore returns discovered extensions + compatibility/runtime status fields (`active`, `enabled`, `runtime_active`, `activation_status`) and managed state.
131
- - Manage performs GitHub update checks (`git ls-remote`) for managed GitHub entries and persists update metadata (`last_update_check_at`, `last_update_remote_commit`, `update_available`, `update_error`).
132
- - Manage supports `--runtime-probe` to opt into doctor-equivalent runtime activation semantics for `runtime_active`/`activation_status`.
133
- - Manage and doctor support `--fix-managed-state` to adopt unmanaged extensions before diagnostics/update checks.
134
- - Adopt records an already-installed unmanaged extension into managed state metadata without reinstalling files (supports local source metadata or explicit GitHub provenance via `--gh`/`--github` and optional `--ref`).
135
- - Adopt-all bulk-records all unmanaged installed extensions in selected scope into managed state metadata without reinstalling files.
136
- - Explore/manage extension rows include explicit update-check fields:
137
- - `update_check_status`: `checked`, `failed`, `skipped_unmanaged`, `skipped_non_github`, or `not_checked`
138
- - `update_check_reason`: deterministic reason/code for that status (for example `up_to_date`, `update_available`, `extension_not_managed`, or the update failure text)
139
- - Manage triage includes `update_check_status_totals`, `update_check_failed_total`, and update-health coverage signals (`update_health_coverage`, `update_health_partial`) for operator-friendly rollups.
140
- - Manage/doctor warning-code rollups include `extension_update_health_partial_coverage` only when unmanaged extensions are action-required for update-check coverage. Expected bundled/local unmanaged installs are surfaced as informational triage metadata.
141
- - Doctor consolidates diagnostics into summary/deep modes (`--detail summary|deep`) with normalized warning codes, canonical extension load roots, consistency diagnostics for active-vs-loaded project extensions, update-health coverage telemetry, remediation hints, strict warning exits (`--strict-exit`, alias `--fail-on-warn`), blocking-failure indicators (`blocking_failure_count`, `has_blocking_failures`), capability guidance metadata (`pm extension --doctor` or `pm extension doctor`), and capability contract metadata (`details.capability_contract`).
142
- - Doctor `--trace` (with `--detail deep`) includes actionable activation trace payloads for registration validation failures (method, command, registration index, expected schema, and sanitized received payload).
69
+ ## Install Sources
143
70
 
144
- ### Health integration
71
+ `pm extension install` accepts:
145
72
 
146
- `pm health` includes managed extension diagnostics for project and global scope:
73
+ - local directories
74
+ - GitHub HTTPS URLs
75
+ - `github.com/<owner>/<repo>[/path]`
76
+ - `--gh <owner>/<repo>[/path]`
77
+ - optional `--ref <branch|tag|sha>`
147
78
 
148
- - managed-state file path
149
- - managed entry count
150
- - managed entry summaries
151
- - managed-state read/schema warnings
152
- - update-coverage parity warnings (`extension_update_health_partial_coverage`) when loaded unmanaged extensions are action-required and update checks are partial
153
- - For focused extension triage, use `pm extension --doctor` for consolidated diagnostics without traversing full health payloads.
79
+ When a GitHub source omits a subpath, the installer accepts a repository root containing one extension manifest or exactly one extension under known extension roots.
154
80
 
155
81
  ## Manifest
156
82
 
157
- Every extension directory must contain a `manifest.json`:
83
+ Every extension has `manifest.json`:
158
84
 
159
85
  ```json
160
86
  {
161
87
  "name": "pm-ext-example",
162
88
  "version": "0.1.0",
163
- "entry": "./dist/index.js",
89
+ "entry": "./index.js",
164
90
  "priority": 100,
165
- "capabilities": [
166
- "commands",
167
- "parser",
168
- "preflight",
169
- "services",
170
- "renderers",
171
- "hooks",
172
- "schema",
173
- "importers",
174
- "search"
175
- ]
91
+ "capabilities": ["commands"]
176
92
  }
177
93
  ```
178
94
 
179
- - `entry` must resolve inside the extension directory (no path traversal).
180
- - `capabilities` declares what the extension will register. API calls that exceed declared capabilities fail activation deterministically.
181
- - Unknown capability names are ignored for gating but emit discovery diagnostics that include allowed-capability lists and nearest-match suggestions when confidence is high.
182
- - Legacy manifest capability aliases `migration` and `validation` are remapped to `schema` and emit consolidated warning `extension_capability_legacy_alias` so compatibility remains additive while migration intent stays visible.
95
+ Rules:
183
96
 
184
- ## SDK Dependency Setup
97
+ - `entry` must stay inside the extension directory.
98
+ - `capabilities` gates what the extension can register.
99
+ - Unknown capabilities emit guidance.
100
+ - Legacy capability aliases are normalized for compatibility with warnings.
185
101
 
186
- External extensions should declare an explicit dependency on the pm package so `@unbrained/pm-cli/sdk` resolves at runtime.
102
+ Capability names:
103
+
104
+ - `commands`
105
+ - `parser`
106
+ - `preflight`
107
+ - `services`
108
+ - `renderers`
109
+ - `hooks`
110
+ - `schema`
111
+ - `importers`
112
+ - `search`
113
+
114
+ ## Minimal Extension
115
+
116
+ `manifest.json`:
187
117
 
188
118
  ```json
189
119
  {
190
- "name": "my-pm-extension",
191
- "type": "module",
192
- "dependencies": {
193
- "@unbrained/pm-cli": "^2026.3.12"
194
- }
120
+ "name": "hello",
121
+ "version": "0.1.0",
122
+ "entry": "./index.js",
123
+ "capabilities": ["commands"]
195
124
  }
196
125
  ```
197
126
 
198
- This keeps extension imports stable and avoids relying on internal `src/core/...` paths.
127
+ `index.js`:
199
128
 
200
- ## Starter Extension Reference
201
-
202
- For a complete example that demonstrates all 9 capabilities via the public SDK, see:
203
-
204
- - `docs/examples/starter-extension/`
205
-
206
- ## Extension Module
207
-
208
- The entry module must export an `activate` function:
209
-
210
- ```ts
211
- import { defineExtension, type ExtensionApi } from "@unbrained/pm-cli/sdk";
129
+ ```js
130
+ import { defineExtension } from "@unbrained/pm-cli/sdk";
212
131
 
213
132
  export default defineExtension({
214
- activate(api: ExtensionApi): void {
215
- // register commands, hooks, renderers, etc.
133
+ activate(api) {
134
+ api.registerCommand({
135
+ name: "hello",
136
+ description: "Print a deterministic hello payload.",
137
+ intent: "verify extension command activation",
138
+ examples: ["pm hello"],
139
+ run: async () => ({ message: "hello" }),
140
+ });
216
141
  },
217
142
  });
218
143
  ```
219
144
 
220
- `activate` may be synchronous or return `Promise<void>`.
145
+ Run:
221
146
 
222
- ## API Reference
223
-
224
- ### `api.registerCommand(def)`
147
+ ```bash
148
+ pm extension install ./hello --project
149
+ pm hello
150
+ ```
225
151
 
226
- Register a new command handler path or replace an existing core command at dispatch time.
152
+ ## API Reference
227
153
 
228
- **New command path:**
154
+ Use [SDK](SDK.md) for typed examples. Runtime APIs include:
155
+
156
+ | API | Capability | Purpose |
157
+ |-----|------------|---------|
158
+ | `api.registerCommand(def)` | `commands` | add or replace command handlers |
159
+ | `api.registerParser(command, fn)` | `parser` | normalize args/options before dispatch |
160
+ | `api.registerPreflight(fn)` | `preflight` | influence mutation gate decisions |
161
+ | `api.registerService(name, fn)` | `services` | replace selected runtime services |
162
+ | `api.registerRenderer(format, fn)` | `renderers` | override TOON or JSON output |
163
+ | `api.registerImporter(name, fn)` | `importers` | add `<name> import` |
164
+ | `api.registerExporter(name, fn)` | `importers` | add `<name> export` |
165
+ | `api.registerFlags(command, flags)` | `schema` | describe dynamic command flags |
166
+ | `api.registerItemFields(fields)` | `schema` | add item metadata fields |
167
+ | `api.registerItemTypes(types)` | `schema` | add custom item types |
168
+ | `api.registerMigration(def)` | `schema` | add schema migrations |
169
+ | `api.registerSearchProvider(provider)` | `search` | add search provider |
170
+ | `api.registerVectorStoreAdapter(adapter)` | `search` | add vector adapter |
171
+ | `api.hooks.beforeCommand(fn)` | `hooks` | run before commands |
172
+ | `api.hooks.afterCommand(fn)` | `hooks` | run after commands |
173
+ | `api.hooks.onWrite(fn)` | `hooks` | observe writes |
174
+ | `api.hooks.onRead(fn)` | `hooks` | observe reads |
175
+ | `api.hooks.onIndex(fn)` | `hooks` | observe index operations |
176
+
177
+ ## Command Metadata
178
+
179
+ Dynamic commands should include human and machine metadata:
229
180
 
230
- ```ts
181
+ ```js
231
182
  api.registerCommand({
232
183
  name: "acme sync",
233
184
  action: "acme-sync",
234
- description: "Synchronize ACME assets into PM items.",
235
- intent: "Run a deterministic import/sync pass before release prep.",
236
- examples: ["pm acme sync --source ./assets.json --dry-run"],
237
- failure_hints: ["Ensure --source points to a readable JSON file."],
238
- arguments: [
239
- { name: "sourceId", required: false, description: "Optional source identifier override." },
240
- ],
185
+ description: "Synchronize ACME records into pm items.",
186
+ intent: "run deterministic import before release prep",
187
+ examples: ["pm acme sync --source ./records.json --dry-run"],
188
+ failure_hints: ["Ensure --source points to readable JSON."],
241
189
  flags: [
242
190
  { long: "--source", value_name: "path", description: "Input file path", required: true },
243
- { long: "--dry-run", description: "Preview without writing items" },
191
+ { long: "--dry-run", description: "Preview without writing", type: "boolean" }
244
192
  ],
245
- run: async (context) => {
246
- // context.command: normalized command path
247
- // context.args: string[] positional args
248
- // context.options: Record<string, unknown> command-scoped flags
249
- // context.global: GlobalOptions (--json/--quiet/--path/--no-extensions/--profile)
250
- // context.pm_root: resolved PM root
251
- return { ok: true, synced: 42 };
252
- },
253
- });
254
- ```
255
-
256
- If the command path matches a core command (for example `list-open`), the extension handler runs first and the core action is skipped. Command names are canonicalized (trimmed, lowercased, repeated whitespace collapsed). Handlers receive cloned snapshots so mutation cannot leak into caller state.
257
-
258
- Capability requirements:
259
-
260
- - `registerCommand(...)` requires `commands` capability.
261
- - Defining inline command metadata `flags` inside `registerCommand({ ... })` also requires `schema` capability (same gate as `registerFlags(...)`).
262
-
263
- Optional command metadata (`action`, `description`, `intent`, `examples`, `failure_hints`, `arguments`, and inline `flags`) is consumed by runtime help surfaces and contracts:
264
-
265
- - `pm <extension-command> --help` / `--help --json`
266
- - `pm contracts --command <extension-command>`
267
- - `pm contracts --action <extension-action>`
268
-
269
- For backward compatibility, command definitions that still use `handler` are accepted and mapped to `run`, with warning `extension_command_definition_legacy_handler_alias` to guide migration.
270
-
271
- **Override existing core command result:**
272
-
273
- ```ts
274
- api.registerCommand("list", (context) => {
275
- return {
276
- ...(context.result as Record<string, unknown>),
277
- _ext: "annotated",
278
- command: context.command,
279
- pm_root: context.pm_root,
280
- };
281
- });
282
- ```
283
-
284
- Result override callbacks are synchronous. Returning a Promise is ignored and emits `extension_command_override_async_unsupported:<layer>:<name>:<command>`.
285
-
286
- ### `api.registerParser(command, override)`
287
-
288
- Register command-scoped parser overrides for core or dynamic command paths. Parser overrides run before command handler dispatch and can rewrite `args`, `options`, and `global` values.
289
-
290
- ```ts
291
- api.registerParser("acme sync", (context) => {
292
- return {
293
- options: {
294
- ...context.options,
295
- limit: Number(context.options.limit),
296
- },
297
- };
298
- });
299
- ```
300
-
301
- Notes:
302
-
303
- - Requires `parser` capability.
304
- - Resolution is deterministic (last registered override for the command path wins).
305
- - Parser handlers can be async.
306
- - Failed parser overrides fall back to the original command context and emit deterministic warnings.
307
-
308
- ### `api.registerPreflight(override)`
309
-
310
- Register a preflight override to control command mutation gates and migration execution.
311
-
312
- ```ts
313
- api.registerPreflight((context) => ({
314
- enforce_item_format_gate: false,
315
- run_preflight_item_format_sync: false,
316
- run_extension_migrations: false,
317
- enforce_mandatory_migration_gate: false,
318
- }));
319
- ```
320
-
321
- `context.decision` contains the default gate/migration plan:
322
-
323
- - `enforce_item_format_gate`
324
- - `run_preflight_item_format_sync`
325
- - `run_extension_migrations`
326
- - `enforce_mandatory_migration_gate`
327
-
328
- Notes:
329
-
330
- - Requires `preflight` capability.
331
- - Only the latest registered preflight override is active (deterministic last-wins behavior).
332
- - Use this API carefully: disabling gates/migrations can bypass core safety rails.
333
-
334
- ### `api.registerService(service, override)`
335
-
336
- Register service-level overrides for deep runtime behavior.
337
-
338
- ```ts
339
- api.registerService("output_format", (context) => {
340
- return JSON.stringify({
341
- rendered_by: "acme-service",
342
- payload: context.payload.result,
343
- });
344
- });
345
- ```
346
-
347
- Supported service keys:
348
-
349
- - `output_format`
350
- - `error_format`
351
- - `help_format`
352
- - `lock_acquire`
353
- - `lock_release`
354
- - `history_append`
355
- - `item_store_write`
356
- - `item_store_delete`
357
-
358
- Notes:
359
-
360
- - Requires `services` capability.
361
- - Service resolution is deterministic (last registration for each service key wins).
362
- - `output_format` and `error_format` are synchronous call sites; async returns are ignored with deterministic warnings.
363
- - `error_format` receives the final rendered error string. When callers use `--json`, that string is a JSON error envelope.
364
- - `help_format` applies to text help/usage rendering paths; machine-readable `--json` errors and `--help --json` payloads bypass `help_format` and emit canonical JSON diagnostics/help data directly.
365
-
366
- ### `api.registerFlags(targetCommand, flags)`
367
-
368
- Declare flags for a command (displayed in `--help` for dynamic extension commands):
369
-
370
- ```ts
371
- api.registerFlags("acme sync", [
372
- { long: "--dry-run", short: "-d", description: "Simulate without writing" },
373
- { long: "--org", value_name: "name", description: "Organization name", required: true },
374
- { long: "--legacy-mode", enabled: false },
375
- { long: "--internal-debug", visible: false },
376
- ]);
377
- ```
378
-
379
- Supported metadata for dynamic extension help/contract rendering:
380
-
381
- - `required: true` appends a `[required]` marker in help.
382
- - `enabled: false` appends a `[disabled]` marker in help.
383
- - `visible: false` hides the flag from dynamic help output.
384
- - `type` / `value_type` (`string` | `number` | `boolean`) enables runtime loose-option coercion for matching command flags.
385
- - Validation contract: each entry must provide at least one of `long` or `short`; optional metadata fields must match expected scalar types.
386
-
387
- Core help output appends command-level guidance with compact defaults (`Intent` + one example) and supports deep help via `--explain`. Dynamic extension commands receive flag-level rendering from both `registerFlags(...)` and `registerCommand({ flags: [...] })`; provide explicit `description` text on each flag to keep help/contracts high-signal.
388
-
389
- ### `api.registerRenderer(format, renderer)`
390
-
391
- Override TOON or JSON output for a command:
392
-
393
- ```ts
394
- api.registerRenderer("toon", (context) => {
395
- if (context.command !== "stats") {
396
- return `noop: ${JSON.stringify(context.result)}`;
397
- }
398
- return customToonFormat(context.result);
399
- });
400
- ```
401
-
402
- Renderer overrides must return a string. Non-string return values are ignored and produce a deterministic `extension_renderer_invalid_result:<layer>:<name>:<format>` warning.
403
-
404
- Without a renderer override, core TOON fallback output renders the command payload directly and applies sparse compaction:
405
-
406
- - omits `null` and `undefined`
407
- - omits empty arrays and empty objects
408
- - preserves meaningful scalar values
409
-
410
- If your extension needs a different shape (or must include fields omitted by sparse fallback), register a TOON renderer override.
411
-
412
- ### `api.registerImporter(name, importer)`
413
-
414
- Register an importer (also wires `<name> import` command path):
415
-
416
- ```ts
417
- api.registerImporter("jira", async (context) => {
418
- // context.registration: normalized importer name
419
- // context.action: "import"
420
- // context.command: command path
421
- // context.options: parsed command flags
422
- // context.global: GlobalOptions
423
- // context.pm_root: resolved PM root
424
- return { ok: true, imported: 5, skipped: 0, ids: ["pm-xxxx"], warnings: [] };
425
- });
426
- ```
427
-
428
- ### `api.registerExporter(name, exporter)`
429
-
430
- Register an exporter (also wires `<name> export` command path):
431
-
432
- ```ts
433
- api.registerExporter("jira", async (context) => {
434
- // context.action: "export"
435
- return { ok: true, exported: 5, ids: ["pm-xxxx"], warnings: [] };
436
- });
437
- ```
438
-
439
- Notes:
440
-
441
- - `registerExporter(...)` uses the same capability gate as importers. Declare `importers` in `manifest.json` to use both `registerImporter(...)` and `registerExporter(...)`.
442
-
443
- ### `api.registerItemFields(fields)`
444
-
445
- Declare additional front-matter fields for schema-awareness:
446
-
447
- ```ts
448
- api.registerItemFields([
449
- { name: "acme_epic_id", type: "string", optional: true },
450
- ]);
451
- ```
452
-
453
- Validation contract: each field entry must include non-empty `name` and `type`; `optional` must be boolean when provided.
454
-
455
- ### `api.registerItemTypes(types)`
456
-
457
- Register custom item types and per-type create/type-option rules:
458
-
459
- ```ts
460
- api.registerItemTypes([
461
- {
462
- name: "Asset",
463
- folder: "assets",
464
- aliases: ["assets", "3d-asset"],
465
- required_create_fields: ["title", "description", "status", "priority", "message"],
466
- required_create_repeatables: [],
467
- command_option_policies: [
468
- { command: "create", option: "severity", enabled: false },
469
- { command: "create", option: "reporter", enabled: false },
470
- { command: "create", option: "goal", visible: false },
471
- { command: "update", option: "message", required: true },
472
- ],
473
- options: [
474
- {
475
- key: "category",
476
- values: ["Map", "Character", "Prop", "VFX"],
477
- required: true,
478
- aliases: ["asset_category"],
479
- },
480
- {
481
- key: "pipeline",
482
- values: ["Blockout", "Modeling", "Rigging", "Texturing", "Done"],
483
- },
484
- ],
485
- },
486
- ]);
487
- ```
488
-
489
- Validation contract highlights:
490
-
491
- - each type entry must include non-empty `name`
492
- - `aliases`, `required_create_fields`, and `required_create_repeatables` must be arrays of non-empty strings when provided
493
- - `options[]` entries require non-empty `key`
494
- - `command_option_policies[]` entries require non-empty `command` and `option`
495
- - optional boolean toggles (`enabled`, `required`, `visible`) must be booleans when provided
496
-
497
- Notes:
498
-
499
- - Requires `schema` capability in the extension manifest.
500
- - Type names and aliases are resolved by the runtime type registry and become available to `--type` filters and completion.
501
- - Option definitions are validated by `pm create` / `pm update` through `--type-option` flags.
502
- - `command_option_policies` are enforced by core create/update runtime and surfaced in policy-aware help sections.
503
-
504
- ### `api.registerMigration(def)`
505
-
506
- Declare a schema migration (tracked in `pm health`):
507
-
508
- ```ts
509
- api.registerMigration({
510
- id: "add-acme-epic-id",
511
- description: "Add acme_epic_id field to existing items",
512
- mandatory: false,
513
- status: "pending",
514
- run: async (pmRoot) => {
515
- // migrate items; update status to "applied" when done
516
- },
517
- });
518
- ```
519
-
520
- Validation contract: migration definitions must be objects; when provided, `id`/`description`/`status` must be strings, `mandatory` must be boolean, and `run` must be a function.
521
-
522
- Migrations with `mandatory: true` and `status` not `"applied"` block write commands until resolved (bypass with `--force`).
523
-
524
- ### `api.registerSearchProvider(provider)`
525
-
526
- Register a custom search provider:
527
-
528
- ```ts
529
- api.registerSearchProvider({
530
- name: "elastic",
531
- query: async (context) => {
532
- // context.query, context.mode, context.tokens
533
- // context.options, context.settings, context.documents
534
- return [{ id: "pm-xxxx", score: 0.95, matched_fields: ["provider:elastic"] }];
535
- },
536
- });
537
- ```
538
-
539
- Use `settings.search.provider` to select the active extension provider for live `pm search` execution.
540
-
541
- ### `api.registerVectorStoreAdapter(adapter)`
542
-
543
- Register a custom vector store:
544
-
545
- ```ts
546
- api.registerVectorStoreAdapter({
547
- name: "pinecone",
548
- upsert: async (context) => {
549
- // context.points, context.settings
550
- },
551
- query: async (context) => {
552
- // context.vector, context.limit, context.settings
553
- return [{ id: "pm-xxxx", score: 0.87 }];
554
- },
555
- });
556
- ```
557
-
558
- Use `settings.vector_store.adapter` to select the active extension adapter for `pm search` query and `pm reindex` upsert.
559
-
560
- ## Lifecycle Hooks
561
-
562
- Hooks run for every applicable core operation. Hook handlers receive cloned context snapshots — mutations do not leak back into caller state.
563
-
564
- ### `api.hooks.beforeCommand(hook)`
565
-
566
- Runs before any command executes:
567
-
568
- ```ts
569
- api.hooks.beforeCommand((ctx) => {
570
- // ctx.command: string
571
- // ctx.args: string[]
572
- // ctx.options: Record<string,unknown>
573
- // ctx.global: GlobalOptions
574
- // ctx.pm_root: string
575
- console.log(`[ext] before: ${ctx.command}`);
576
- });
577
- ```
578
-
579
- ### `api.hooks.afterCommand(hook)`
580
-
581
- Runs after a command completes (even on failure):
582
-
583
- ```ts
584
- api.hooks.afterCommand((ctx) => {
585
- // ctx.ok: boolean
586
- // ctx.result?: unknown
587
- // ctx.error?: string
588
- // same fields as beforeCommand
589
- });
590
- ```
591
-
592
- ### `api.hooks.onWrite(hook)`
593
-
594
- Runs before each item file write:
595
-
596
- ```ts
597
- api.hooks.onWrite((ctx) => {
598
- // ctx.path: string
599
- // ctx.scope: "project" | "global"
600
- // ctx.op: string (create, update, restore, etc.)
601
- });
602
- ```
603
-
604
- ### `api.hooks.onRead(hook)`
605
-
606
- Runs after each item file read:
607
-
608
- ```ts
609
- api.hooks.onRead((ctx) => {
610
- // ctx.path: string
611
- // ctx.scope: "project" | "global"
612
- });
613
- ```
614
-
615
- ### `api.hooks.onIndex(hook)`
616
-
617
- Runs during reindex/gc operations:
618
-
619
- ```ts
620
- api.hooks.onIndex((ctx) => {
621
- // ctx.mode: "keyword" | "semantic" | "hybrid" | "gc"
622
- // ctx.total_items?: number
193
+ run: async (context) => ({ ok: true, args: context.args }),
623
194
  });
624
195
  ```
625
196
 
626
- ## Health and Diagnostics
627
-
628
- `pm health` probes all loaded extensions and surfaces:
629
-
630
- - `extension_load_failed:<layer>:<name>` — manifest parse or module import error
631
- - `extension_load_failed_sdk_dependency_missing:<name>` — doctor detected missing `@unbrained/pm-cli` dependency resolution in extension runtime imports
632
- - `extension_load_failed_module_mode_mismatch:<name>` — doctor detected ESM/CJS mode mismatch (for example missing `"type": "module"` for ESM-style entrypoints)
633
- - `extension_activate_failed:<layer>:<name>` — exception in `activate()`
634
- - `extension_entry_outside_extension:<layer>:<name>` — entry path escapes directory
635
- - `extension_capability_unknown:<layer>:<name>:<capability>:allowed=<csv>:suggested=<capability|none>` — unknown capability in manifest with inline guidance (including legacy alias replacements where applicable)
636
- - `extension_capability_legacy_alias:<layer>:<name>:aliases=<csv>` — legacy manifest capability aliases were remapped (for example `migration`/`validation` -> `schema`)
637
- - `extension_command_definition_legacy_handler_alias:<layer>:<name>:<command>` — command definition used legacy `handler` key; runtime mapped it to `run`
638
- - collision warnings when multiple extensions target the same command/parser/preflight/service/renderer key (last registration wins)
197
+ Inline command flags require both `commands` and `schema` capabilities.
639
198
 
640
- `pm health` and `pm extension --doctor` also surface machine-readable capability contract metadata in diagnostics payloads:
199
+ ## Service and Preflight Safety
641
200
 
642
- - `details.capability_contract.version`
643
- - `details.capability_contract.capabilities`
644
- - `details.capability_contract.legacy_aliases`
645
- - parsed guidance entries in `details.capability_guidance` include `capability_contract_version`, `suggestion_source`, and `legacy_alias_target` when available
201
+ `parser`, `preflight`, and `services` are powerful. They can change command input, mutation gates, output formatting, lock behavior, history appends, and item-store writes. Only enable these capabilities for reviewed extensions.
646
202
 
647
- Use `pm health --json` to parse diagnostics programmatically.
648
-
649
- ## Disabling Extensions
203
+ For troubleshooting:
650
204
 
651
205
  ```bash
652
- pm --no-extensions list-open # disable all extensions for this invocation
653
- ```
654
-
655
- Or configure per-project in `.agents/pm/settings.json`:
656
-
657
- ```json
658
- {
659
- "extensions": {
660
- "disabled": ["pm-ext-example"]
661
- }
662
- }
663
- ```
664
-
665
- ## Example: Minimal Custom Command
666
-
667
- **`~/.pm-cli/extensions/hello/manifest.json`:**
668
-
669
- ```json
670
- {
671
- "name": "hello",
672
- "version": "0.1.0",
673
- "entry": "./index.js",
674
- "priority": 100,
675
- "capabilities": ["commands"]
676
- }
677
- ```
678
-
679
- **`~/.pm-cli/extensions/hello/index.js`:**
680
-
681
- ```js
682
- export function activate(api) {
683
- api.registerCommand({
684
- name: "hello",
685
- run: async (_context) => {
686
- return { message: "Hello from extension!" };
687
- },
688
- });
689
- }
690
- ```
691
-
692
- ```bash
693
- pm hello
694
- # => message: Hello from extension!
206
+ pm --no-extensions list-open
207
+ pm extension doctor --detail deep --trace
208
+ pm health --check-only
695
209
  ```
696
210
 
697
211
  ## Bundled Managed Extensions
698
212
 
699
- `pm-cli` ships two bundled extension sources in `.agents/pm/extensions`, but they are not auto-installed in project or global scope.
213
+ `pm-cli` ships bundled extension sources that are not auto-installed:
700
214
 
701
- | Extension | Alias | Commands (after install) | Purpose |
702
- |-----------|-------|---------------------------|---------|
703
- | `builtin-beads-import` | `beads` | `pm beads import` | Import Beads JSONL records into pm items |
704
- | `builtin-todos-import-export` | `todos` | `pm todos import`, `pm todos export` | Round-trip todos markdown format |
215
+ | Alias | Commands after install | Purpose |
216
+ |-------|------------------------|---------|
217
+ | `beads` | `pm beads import` | import Beads JSONL records |
218
+ | `todos` | `pm todos import`, `pm todos export` | round-trip todos markdown format |
705
219
 
706
- Install either via alias or explicit bundled path:
220
+ Install:
707
221
 
708
222
  ```bash
709
- # Alias installs
710
223
  pm extension --install --project beads
711
224
  pm extension --install --project todos
712
-
713
- # Equivalent local bundled paths
714
- pm extension --install --project .agents/pm/extensions/beads
715
- pm extension --install --project .agents/pm/extensions/todos
716
225
  ```
717
226
 
718
- The command paths `pm beads ...` and `pm todos ...` are available only after the extension is installed and active for the selected scope.
719
-
720
- ## Pi Agent Extension
227
+ ## Starter Extension
721
228
 
722
- The Pi wrapper implementation source remains at `.pi/extensions/pm-cli/index.ts` and is a Pi agent extension (not a pm-cli runtime extension managed by `pm extension`).
229
+ See [examples/starter-extension](examples/starter-extension/README.md) for a compact extension that demonstrates all capability categories through the public SDK.
723
230
 
724
- Current wrapper parity includes:
231
+ ## Pi Wrapper
725
232
 
726
- - `action: "calendar"` for `pm calendar` / `pm cal` (`view`, `date`, `from`, `to`, `past`, `type`, `tag`, `priority`, `status`, `assignee`, `sprint`, `release`, `limit`, `format`)
727
- - `create`/`update` reminder forwarding via repeatable `reminder` values (`at=<iso|relative>,text=<text>`)
728
- - `create`/`update` custom type-option forwarding via repeatable `typeOption` values
729
- - `create`/`update` audit controls including `allowAuditUpdate` and dependency-only `allowAuditDepUpdate`
730
- - linked-test context preflight/remediation forwarding via `checkContext` and `autoPmContext` (`test` / `test-all`)
731
- - cache cleanup forwarding via `dryRun` and repeatable `gcScope` (`gc`)
732
- - extension lifecycle forwarding via `extension-install`, `extension-uninstall`, `extension-explore`, `extension-manage`, `extension-doctor`, `extension-adopt`, `extension-adopt-all`, `extension-activate`, and `extension-deactivate` actions (`target`, `scope`, `github`, `ref`, `detail`, `trace`, `runtimeProbe`, `fixManagedState`, `strictExit`, `failOnWarn`)
233
+ The Pi wrapper source is `.pi/extensions/pm-cli/index.ts`. It is an agent wrapper, not a runtime extension managed by `pm extension`.
733
234
 
734
- See [AGENTS.md](../AGENTS.md) section 9 for full usage details.
235
+ Use [AGENTS.md](../AGENTS.md) for repository-specific Pi wrapper operating rules.