@unbrained/pm-cli 2026.5.1 → 2026.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -4
- package/CONTRIBUTING.md +11 -5
- package/PRD.md +17 -1
- package/README.md +49 -1101
- package/SECURITY.md +6 -11
- package/dist/cli/commands/activity.d.ts +10 -0
- package/dist/cli/commands/activity.js +13 -1
- package/dist/cli/commands/activity.js.map +1 -1
- package/dist/cli/commands/aggregate.js.map +1 -1
- package/dist/cli/commands/append.js.map +1 -1
- package/dist/cli/commands/calendar.js +16 -5
- package/dist/cli/commands/calendar.js.map +1 -1
- package/dist/cli/commands/claim.js.map +1 -1
- package/dist/cli/commands/close.js.map +1 -1
- package/dist/cli/commands/comments-audit.js.map +1 -1
- package/dist/cli/commands/comments.js.map +1 -1
- package/dist/cli/commands/completion.js.map +1 -1
- package/dist/cli/commands/config.js.map +1 -1
- package/dist/cli/commands/context.js +22 -0
- package/dist/cli/commands/context.js.map +1 -1
- package/dist/cli/commands/contracts.js.map +1 -1
- package/dist/cli/commands/create.js +2 -2
- package/dist/cli/commands/create.js.map +1 -1
- package/dist/cli/commands/dedupe-audit.js.map +1 -1
- package/dist/cli/commands/delete.js.map +1 -1
- package/dist/cli/commands/deps.js.map +1 -1
- package/dist/cli/commands/docs.js.map +1 -1
- package/dist/cli/commands/extension.js.map +1 -1
- package/dist/cli/commands/files.js +14 -2
- package/dist/cli/commands/files.js.map +1 -1
- package/dist/cli/commands/gc.js.map +1 -1
- package/dist/cli/commands/get.js.map +1 -1
- package/dist/cli/commands/health.js +16 -12
- package/dist/cli/commands/health.js.map +1 -1
- package/dist/cli/commands/history.js.map +1 -1
- package/dist/cli/commands/index.js.map +1 -1
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/learnings.js.map +1 -1
- package/dist/cli/commands/list.d.ts +1 -0
- package/dist/cli/commands/list.js +10 -2
- package/dist/cli/commands/list.js.map +1 -1
- package/dist/cli/commands/normalize.js +5 -17
- package/dist/cli/commands/normalize.js.map +1 -1
- package/dist/cli/commands/notes.js.map +1 -1
- package/dist/cli/commands/reindex.js.map +1 -1
- package/dist/cli/commands/restore.js.map +1 -1
- package/dist/cli/commands/search.js.map +1 -1
- package/dist/cli/commands/stats.js.map +1 -1
- package/dist/cli/commands/templates.js.map +1 -1
- package/dist/cli/commands/test-all.js.map +1 -1
- package/dist/cli/commands/test-runs.js.map +1 -1
- package/dist/cli/commands/test.js.map +1 -1
- package/dist/cli/commands/update-many.js +1 -6
- package/dist/cli/commands/update-many.js.map +1 -1
- package/dist/cli/commands/update.js +2 -2
- package/dist/cli/commands/update.js.map +1 -1
- package/dist/cli/commands/validate.js +23 -18
- package/dist/cli/commands/validate.js.map +1 -1
- package/dist/cli/error-guidance.js +13 -2
- package/dist/cli/error-guidance.js.map +1 -1
- package/dist/cli/extension-command-options.js.map +1 -1
- package/dist/cli/help-content.js.map +1 -1
- package/dist/cli/main.js +29 -13
- package/dist/cli/main.js.map +1 -1
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +3 -1
- package/dist/cli.js.map +1 -1
- package/dist/core/extensions/extension-types.d.ts +605 -0
- package/dist/core/extensions/extension-types.js +22 -0
- package/dist/core/extensions/extension-types.js.map +1 -0
- package/dist/core/extensions/index.js.map +1 -1
- package/dist/core/extensions/item-fields.js.map +1 -1
- package/dist/core/extensions/loader.d.ts +2 -586
- package/dist/core/extensions/loader.js +3 -21
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/extensions/runtime-registrations.js.map +1 -1
- package/dist/core/fs/fs-utils.js.map +1 -1
- package/dist/core/fs/index.js.map +1 -1
- package/dist/core/history/history-stream-policy.js.map +1 -1
- package/dist/core/history/history.js.map +1 -1
- package/dist/core/history/index.js.map +1 -1
- package/dist/core/item/id.js.map +1 -1
- package/dist/core/item/index.js.map +1 -1
- package/dist/core/item/item-format.js.map +1 -1
- package/dist/core/item/parent-reference-policy.js.map +1 -1
- package/dist/core/item/parse.js +6 -0
- package/dist/core/item/parse.js.map +1 -1
- package/dist/core/item/sprint-release-format.js.map +1 -1
- package/dist/core/item/status.js.map +1 -1
- package/dist/core/item/type-registry.js.map +1 -1
- package/dist/core/lock/index.js.map +1 -1
- package/dist/core/lock/lock.js +1 -6
- package/dist/core/lock/lock.js.map +1 -1
- package/dist/core/output/command-aware.js.map +1 -1
- package/dist/core/output/output.js.map +1 -1
- package/dist/core/schema/runtime-field-filters.js.map +1 -1
- package/dist/core/schema/runtime-field-values.js.map +1 -1
- package/dist/core/schema/runtime-schema.js.map +1 -1
- package/dist/core/search/cache.js +1 -7
- package/dist/core/search/cache.js.map +1 -1
- package/dist/core/search/embedding-batches.js +4 -0
- package/dist/core/search/embedding-batches.js.map +1 -1
- package/dist/core/search/providers.js +1 -20
- package/dist/core/search/providers.js.map +1 -1
- package/dist/core/search/semantic-defaults.js.map +1 -1
- package/dist/core/search/vector-stores.d.ts +1 -0
- package/dist/core/search/vector-stores.js +23 -27
- package/dist/core/search/vector-stores.js.map +1 -1
- package/dist/core/sentry/helpers.d.ts +6 -0
- package/dist/core/sentry/helpers.js +73 -0
- package/dist/core/sentry/helpers.js.map +1 -0
- package/dist/core/sentry/instrument.d.ts +4 -0
- package/dist/core/sentry/instrument.js +173 -0
- package/dist/core/sentry/instrument.js.map +1 -0
- package/dist/core/shared/command-types.js.map +1 -1
- package/dist/core/shared/conflict-markers.js.map +1 -1
- package/dist/core/shared/constants.js.map +1 -1
- package/dist/core/shared/errors.js.map +1 -1
- package/dist/core/shared/index.d.ts +1 -0
- package/dist/core/shared/index.js +1 -0
- package/dist/core/shared/index.js.map +1 -1
- package/dist/core/shared/primitives.d.ts +13 -0
- package/dist/core/shared/primitives.js +33 -0
- package/dist/core/shared/primitives.js.map +1 -0
- package/dist/core/shared/serialization.js.map +1 -1
- package/dist/core/shared/text-normalization.js.map +1 -1
- package/dist/core/shared/time.js.map +1 -1
- package/dist/core/store/index.js.map +1 -1
- package/dist/core/store/item-format-migration.js.map +1 -1
- package/dist/core/store/item-store.js +44 -35
- package/dist/core/store/item-store.js.map +1 -1
- package/dist/core/store/paths.js.map +1 -1
- package/dist/core/store/settings.js.map +1 -1
- package/dist/core/telemetry/consent.js.map +1 -1
- package/dist/core/telemetry/runtime.d.ts +2 -0
- package/dist/core/telemetry/runtime.js +40 -13
- package/dist/core/telemetry/runtime.js.map +1 -1
- package/dist/core/test/background-runs.js.map +1 -1
- package/dist/core/test/item-test-run-tracking.js.map +1 -1
- package/dist/sdk/cli-contracts.js.map +1 -1
- package/dist/sdk/index.d.ts +1 -1
- package/dist/sdk/index.js.map +1 -1
- package/dist/types/index.js.map +1 -1
- package/dist/types.js.map +1 -1
- package/docs/AGENT_GUIDE.md +125 -0
- package/docs/ARCHITECTURE.md +195 -478
- package/docs/COMMANDS.md +199 -0
- package/docs/CONFIGURATION.md +146 -0
- package/docs/EXTENSIONS.md +146 -645
- package/docs/QUICKSTART.md +108 -0
- package/docs/README.md +70 -0
- package/docs/RELEASING.md +51 -36
- package/docs/SDK.md +127 -68
- package/docs/TESTING.md +125 -0
- package/docs/examples/starter-extension/README.md +39 -25
- package/package.json +17 -9
- package/dist/command-types.d.ts +0 -1
- package/dist/command-types.js +0 -2
- package/dist/command-types.js.map +0 -1
- package/dist/constants.d.ts +0 -1
- package/dist/constants.js +0 -2
- package/dist/constants.js.map +0 -1
- package/dist/errors.d.ts +0 -1
- package/dist/errors.js +0 -2
- package/dist/errors.js.map +0 -1
- package/dist/fs-utils.d.ts +0 -1
- package/dist/fs-utils.js +0 -2
- package/dist/fs-utils.js.map +0 -1
- package/dist/history.d.ts +0 -1
- package/dist/history.js +0 -2
- package/dist/history.js.map +0 -1
- package/dist/id.d.ts +0 -1
- package/dist/id.js +0 -2
- package/dist/id.js.map +0 -1
- package/dist/item-format.d.ts +0 -1
- package/dist/item-format.js +0 -2
- package/dist/item-format.js.map +0 -1
- package/dist/item-store.d.ts +0 -1
- package/dist/item-store.js +0 -2
- package/dist/item-store.js.map +0 -1
- package/dist/lock.d.ts +0 -1
- package/dist/lock.js +0 -2
- package/dist/lock.js.map +0 -1
- package/dist/output.d.ts +0 -1
- package/dist/output.js +0 -2
- package/dist/output.js.map +0 -1
- package/dist/parse.d.ts +0 -1
- package/dist/parse.js +0 -2
- package/dist/parse.js.map +0 -1
- package/dist/paths.d.ts +0 -1
- package/dist/paths.js +0 -2
- package/dist/paths.js.map +0 -1
- package/dist/serialization.d.ts +0 -1
- package/dist/serialization.js +0 -2
- package/dist/serialization.js.map +0 -1
- package/dist/settings.d.ts +0 -1
- package/dist/settings.js +0 -2
- package/dist/settings.js.map +0 -1
- package/dist/time.d.ts +0 -1
- package/dist/time.js +0 -2
- package/dist/time.js.map +0 -1
package/docs/EXTENSIONS.md
CHANGED
|
@@ -1,734 +1,235 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Extensions
|
|
2
2
|
|
|
3
|
-
Extensions
|
|
3
|
+
Extensions add commands, schema, renderers, importers/exporters, search adapters, lifecycle hooks, and selected runtime overrides without modifying core `pm-cli`.
|
|
4
4
|
|
|
5
|
-
|
|
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
|
-
|
|
|
12
|
-
|
|
|
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
|
-
|
|
22
|
+
Environment overrides:
|
|
40
23
|
|
|
41
|
-
|
|
24
|
+
- `PM_PATH` changes project tracker root.
|
|
25
|
+
- `PM_GLOBAL_PATH` changes global profile root.
|
|
42
26
|
|
|
43
|
-
|
|
27
|
+
Load order is global, then project. Project extensions take precedence when keys collide.
|
|
44
28
|
|
|
45
|
-
|
|
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
|
-
|
|
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
|
-
|
|
92
|
-
pm extension --init ./my-extension
|
|
34
|
+
pm extension init ./my-extension
|
|
93
35
|
pm extension scaffold ./my-extension
|
|
36
|
+
```
|
|
94
37
|
|
|
95
|
-
|
|
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
|
-
|
|
109
|
-
pm extension
|
|
110
|
-
pm extension
|
|
111
|
-
pm extension --install --project
|
|
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
|
-
|
|
46
|
+
Inspect and manage:
|
|
115
47
|
|
|
116
|
-
|
|
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
|
-
|
|
55
|
+
Activate and deactivate:
|
|
119
56
|
|
|
120
|
-
|
|
57
|
+
```bash
|
|
58
|
+
pm extension activate my-extension --project
|
|
59
|
+
pm extension deactivate my-extension --project
|
|
60
|
+
```
|
|
121
61
|
|
|
122
|
-
|
|
62
|
+
Adopt unmanaged extensions:
|
|
123
63
|
|
|
124
|
-
|
|
64
|
+
```bash
|
|
65
|
+
pm extension adopt my-extension --project
|
|
66
|
+
pm extension adopt-all --project
|
|
67
|
+
```
|
|
125
68
|
|
|
126
|
-
|
|
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
|
-
|
|
71
|
+
`pm extension install` accepts:
|
|
145
72
|
|
|
146
|
-
|
|
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
|
-
|
|
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
|
|
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": "./
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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": "
|
|
191
|
-
"
|
|
192
|
-
"
|
|
193
|
-
|
|
194
|
-
}
|
|
120
|
+
"name": "hello",
|
|
121
|
+
"version": "0.1.0",
|
|
122
|
+
"entry": "./index.js",
|
|
123
|
+
"capabilities": ["commands"]
|
|
195
124
|
}
|
|
196
125
|
```
|
|
197
126
|
|
|
198
|
-
|
|
127
|
+
`index.js`:
|
|
199
128
|
|
|
200
|
-
|
|
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
|
|
215
|
-
|
|
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
|
-
|
|
145
|
+
Run:
|
|
221
146
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
147
|
+
```bash
|
|
148
|
+
pm extension install ./hello --project
|
|
149
|
+
pm hello
|
|
150
|
+
```
|
|
225
151
|
|
|
226
|
-
|
|
152
|
+
## API Reference
|
|
227
153
|
|
|
228
|
-
|
|
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
|
-
```
|
|
181
|
+
```js
|
|
231
182
|
api.registerCommand({
|
|
232
183
|
name: "acme sync",
|
|
233
184
|
action: "acme-sync",
|
|
234
|
-
description: "Synchronize ACME
|
|
235
|
-
intent: "
|
|
236
|
-
examples: ["pm acme sync --source ./
|
|
237
|
-
failure_hints: ["Ensure --source points to
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
199
|
+
## Service and Preflight Safety
|
|
641
200
|
|
|
642
|
-
|
|
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
|
-
|
|
648
|
-
|
|
649
|
-
## Disabling Extensions
|
|
203
|
+
For troubleshooting:
|
|
650
204
|
|
|
651
205
|
```bash
|
|
652
|
-
pm --no-extensions list-open
|
|
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
|
|
213
|
+
`pm-cli` ships bundled extension sources that are not auto-installed:
|
|
700
214
|
|
|
701
|
-
|
|
|
702
|
-
|
|
703
|
-
| `
|
|
704
|
-
| `
|
|
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
|
|
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
|
-
|
|
719
|
-
|
|
720
|
-
## Pi Agent Extension
|
|
227
|
+
## Starter Extension
|
|
721
228
|
|
|
722
|
-
|
|
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
|
-
|
|
231
|
+
## Pi Wrapper
|
|
725
232
|
|
|
726
|
-
|
|
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
|
-
|
|
235
|
+
Use [AGENTS.md](../AGENTS.md) for repository-specific Pi wrapper operating rules.
|