@unbrained/pm-cli 2026.5.10 → 2026.5.11

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 (124) hide show
  1. package/.claude-plugin/marketplace.json +4 -4
  2. package/.pi/README.md +10 -1
  3. package/.pi/agents/pm-triage-agent.md +19 -0
  4. package/.pi/agents/pm-verification-agent.md +21 -0
  5. package/.pi/chains/pm-native-delivery.chain.md +11 -0
  6. package/.pi/extensions/pm-cli/index.js +276 -36
  7. package/.pi/skills/pm-native/SKILL.md +6 -2
  8. package/CHANGELOG.md +7 -0
  9. package/README.md +9 -1
  10. package/dist/cli/argv-utils.d.ts +5 -0
  11. package/dist/cli/argv-utils.js +34 -0
  12. package/dist/cli/argv-utils.js.map +1 -0
  13. package/dist/cli/bootstrap-args.d.ts +15 -0
  14. package/dist/cli/bootstrap-args.js +211 -0
  15. package/dist/cli/bootstrap-args.js.map +1 -1
  16. package/dist/cli/commander-usage.js +109 -3
  17. package/dist/cli/commander-usage.js.map +1 -1
  18. package/dist/cli/commands/completion.js +7 -3
  19. package/dist/cli/commands/completion.js.map +1 -1
  20. package/dist/cli/commands/contracts.d.ts +19 -0
  21. package/dist/cli/commands/contracts.js +33 -1
  22. package/dist/cli/commands/contracts.js.map +1 -1
  23. package/dist/cli/commands/create.js +112 -51
  24. package/dist/cli/commands/create.js.map +1 -1
  25. package/dist/cli/commands/docs.js +9 -2
  26. package/dist/cli/commands/docs.js.map +1 -1
  27. package/dist/cli/commands/extension.d.ts +3 -1
  28. package/dist/cli/commands/extension.js +174 -2
  29. package/dist/cli/commands/extension.js.map +1 -1
  30. package/dist/cli/commands/files.js +9 -2
  31. package/dist/cli/commands/files.js.map +1 -1
  32. package/dist/cli/commands/init.d.ts +2 -0
  33. package/dist/cli/commands/init.js +21 -1
  34. package/dist/cli/commands/init.js.map +1 -1
  35. package/dist/cli/commands/metadata-normalizers.d.ts +4 -0
  36. package/dist/cli/commands/metadata-normalizers.js +37 -0
  37. package/dist/cli/commands/metadata-normalizers.js.map +1 -0
  38. package/dist/cli/commands/reindex.js +173 -135
  39. package/dist/cli/commands/reindex.js.map +1 -1
  40. package/dist/cli/commands/search.js +16 -6
  41. package/dist/cli/commands/search.js.map +1 -1
  42. package/dist/cli/commands/test.js +9 -2
  43. package/dist/cli/commands/test.js.map +1 -1
  44. package/dist/cli/commands/update.js +70 -39
  45. package/dist/cli/commands/update.js.map +1 -1
  46. package/dist/cli/error-guidance.d.ts +9 -1
  47. package/dist/cli/error-guidance.js +147 -6
  48. package/dist/cli/error-guidance.js.map +1 -1
  49. package/dist/cli/help-json-payload.js +11 -1
  50. package/dist/cli/help-json-payload.js.map +1 -1
  51. package/dist/cli/main.js +69 -6
  52. package/dist/cli/main.js.map +1 -1
  53. package/dist/cli/register-setup.js +14 -0
  54. package/dist/cli/register-setup.js.map +1 -1
  55. package/dist/cli/telemetry-flush.d.ts +2 -0
  56. package/dist/cli/telemetry-flush.js +4 -0
  57. package/dist/cli/telemetry-flush.js.map +1 -0
  58. package/dist/cli.js +1 -2
  59. package/dist/cli.js.map +1 -1
  60. package/dist/core/extensions/extension-types.d.ts +72 -0
  61. package/dist/core/extensions/extension-types.js +24 -0
  62. package/dist/core/extensions/extension-types.js.map +1 -1
  63. package/dist/core/extensions/loader.d.ts +1 -0
  64. package/dist/core/extensions/loader.js +766 -7
  65. package/dist/core/extensions/loader.js.map +1 -1
  66. package/dist/core/lock/lock.js +2 -0
  67. package/dist/core/lock/lock.js.map +1 -1
  68. package/dist/core/sentry/instrument.d.ts +15 -0
  69. package/dist/core/sentry/instrument.js +35 -3
  70. package/dist/core/sentry/instrument.js.map +1 -1
  71. package/dist/core/shared/constants.js +20 -0
  72. package/dist/core/shared/constants.js.map +1 -1
  73. package/dist/core/shared/errors.d.ts +8 -0
  74. package/dist/core/shared/errors.js.map +1 -1
  75. package/dist/core/shared/levenshtein.d.ts +1 -0
  76. package/dist/core/shared/levenshtein.js +37 -0
  77. package/dist/core/shared/levenshtein.js.map +1 -0
  78. package/dist/core/store/paths.js +34 -1
  79. package/dist/core/store/paths.js.map +1 -1
  80. package/dist/core/store/settings.js +210 -1
  81. package/dist/core/store/settings.js.map +1 -1
  82. package/dist/core/telemetry/runtime.d.ts +1 -0
  83. package/dist/core/telemetry/runtime.js +102 -3
  84. package/dist/core/telemetry/runtime.js.map +1 -1
  85. package/dist/mcp/server.js +3 -1
  86. package/dist/mcp/server.js.map +1 -1
  87. package/dist/pi/native.js +57 -4
  88. package/dist/pi/native.js.map +1 -1
  89. package/dist/sdk/cli-contracts.d.ts +21 -1
  90. package/dist/sdk/cli-contracts.js +250 -0
  91. package/dist/sdk/cli-contracts.js.map +1 -1
  92. package/dist/sdk/index.d.ts +12 -1
  93. package/dist/sdk/index.js +8 -1
  94. package/dist/sdk/index.js.map +1 -1
  95. package/dist/types.d.ts +41 -0
  96. package/dist/types.js.map +1 -1
  97. package/docs/CLAUDE_CODE_PLUGIN.md +39 -0
  98. package/docs/EXTENSIONS.md +687 -0
  99. package/docs/MIGRATION_CLI_SIMPLIFICATION.md +64 -0
  100. package/docs/PI_PACKAGE.md +95 -10
  101. package/docs/SDK.md +441 -0
  102. package/docs/examples/ci/github-actions-pm-extension-gate.yml +53 -0
  103. package/docs/examples/ci/gitlab-ci-pm-extension-gate.yml +41 -0
  104. package/docs/examples/ci/jenkins-pm-extension-gate.Jenkinsfile +45 -0
  105. package/docs/examples/policy-restricted-extension/README.md +74 -0
  106. package/docs/examples/policy-restricted-extension/index.js +21 -0
  107. package/docs/examples/policy-restricted-extension/manifest.json +21 -0
  108. package/docs/examples/policy-restricted-extension/package.json +8 -0
  109. package/docs/examples/sdk-app-embedding/README.md +39 -0
  110. package/docs/examples/sdk-app-embedding/package.json +9 -0
  111. package/docs/examples/sdk-app-embedding/run-embedded-pm.mjs +61 -0
  112. package/docs/examples/sdk-contract-consumer/README.md +57 -0
  113. package/docs/examples/sdk-contract-consumer/inspect-contracts.mjs +47 -0
  114. package/docs/examples/sdk-contract-consumer/package.json +10 -0
  115. package/docs/examples/starter-extension/README.md +57 -42
  116. package/docs/examples/starter-extension/manifest.json +15 -0
  117. package/marketplace.json +3 -3
  118. package/package.json +1 -1
  119. package/plugins/pm-cli-claude/.claude-plugin/plugin.json +2 -2
  120. package/plugins/pm-cli-claude/README.md +55 -14
  121. package/plugins/pm-cli-claude/agents/pm-delivery-chain.md +88 -0
  122. package/plugins/pm-cli-claude/agents/pm-triage-agent.md +83 -0
  123. package/plugins/pm-cli-claude/agents/pm-verification-agent.md +88 -0
  124. package/plugins/pm-cli-claude/hooks/session-start.mjs +87 -22
@@ -1,19 +1,19 @@
1
1
  {
2
- "name": "pm-cli",
2
+ "name": "pm",
3
3
  "owner": {
4
4
  "name": "unbrained",
5
5
  "url": "https://github.com/unbraind/pm-cli"
6
6
  },
7
7
  "metadata": {
8
8
  "description": "Official marketplace for pm CLI — native git-based project management for Claude Code and AI coding agents.",
9
- "version": "1.0.0"
9
+ "version": "1.3.0"
10
10
  },
11
11
  "plugins": [
12
12
  {
13
13
  "name": "pm-cli",
14
14
  "source": "./plugins/pm-cli-claude",
15
- "description": "Native pm CLI integration for Claude Code — 18 MCP tools, workflow skills, slash commands, session context injection, and a coordination subagent for git-based project management without leaving Claude Code.",
16
- "version": "1.0.0",
15
+ "description": "Native pm CLI integration for Claude Code — 18 MCP tools, 5 workflow skills, 14 slash commands, 3 subagents, hybrid TUI task tracking, session context injection, and coordination subagents for git-based project management without leaving Claude Code.",
16
+ "version": "1.3.0",
17
17
  "author": {
18
18
  "name": "unbrained",
19
19
  "url": "https://github.com/unbraind/pm-cli"
package/.pi/README.md CHANGED
@@ -19,8 +19,17 @@ pi -e .
19
19
 
20
20
  Resources exposed by `package.json`:
21
21
 
22
- - `.pi/extensions/pm-cli/index.js` — native Pi extension registering the `pm` tool and slash commands.
22
+ - `.pi/extensions/pm-cli/index.js` — native Pi extension registering the `pm` tool, custom TUI renderers, autocomplete, status/widget UI, and slash commands.
23
23
  - `.pi/skills/*` — Pi skills for native pm workflows and release validation.
24
24
  - `.pi/prompts/*` — prompt templates for pm-tracked work.
25
+ - `.pi/agents/*` and `.pi/chains/*` — optional pi-subagents setup for pm triage and verification workflows in repositories that use subagents.
25
26
 
26
27
  The extension imports the built package from `dist/`, so run `pnpm build` before local install or before publishing.
28
+
29
+ Interactive commands:
30
+
31
+ - `/pm-board [limit]` — dashboard panel for active pm context.
32
+ - `/pm-item <id>` and `/pm-history <id>` — item details/history panels.
33
+ - `/pm-actions` and `/pm-workflows` — native action list and workflow reminders.
34
+
35
+ The `pm` tool should be preferred over shelling out to `pm`; it calls pm command modules directly in-process.
@@ -0,0 +1,19 @@
1
+ ---
2
+ name: pm-triage-agent
3
+ description: Native pm triage agent for Pi. Use to inspect context, search for duplicates, select or create tracker lineage, and hand off an implementation-ready pm item without shelling out to the pm CLI.
4
+ tools: pm,read,grep,find,ls
5
+ skills: pm-native,pm-user
6
+ ---
7
+
8
+ # pm Triage Agent
9
+
10
+ Use the native `pm` tool only for pm operations.
11
+
12
+ Workflow:
13
+ 1. Run `pm` action `context` with `limit: 10`.
14
+ 2. Run `pm` action `search` with the user's key terms.
15
+ 3. Run `pm` actions `list-open` and `list-in-progress`.
16
+ 4. If an item exists, recommend reusing it and claim/start only when asked.
17
+ 5. If no item exists, identify parent lineage and propose a create payload with duplicate-check evidence.
18
+
19
+ Output a concise handoff with item id, rationale, recommended next action, and exact native `pm` tool calls.
@@ -0,0 +1,21 @@
1
+ ---
2
+ name: pm-verification-agent
3
+ description: Native pm verification agent for Pi. Use to inspect linked files/tests/docs, run sandbox-safe linked tests through pm, validate close readiness, and produce closure evidence.
4
+ tools: pm,bash,read,grep,find,ls
5
+ skills: pm-native,pm-release
6
+ ---
7
+
8
+ # pm Verification Agent
9
+
10
+ Use the native `pm` tool for pm mutations and linked-test orchestration.
11
+ Use bash only for non-pm project commands such as `pnpm build` or `gh run list`.
12
+
13
+ Workflow:
14
+ 1. Read the target item with `pm` action `get`.
15
+ 2. Check linked files/docs/tests and acceptance criteria.
16
+ 3. Run `pm` action `test` with `run: true` or equivalent linked-test options when available.
17
+ 4. Run targeted project validation requested by the parent.
18
+ 5. Add a `pm` comment summarizing evidence.
19
+ 6. Recommend close/release only if verification is clean.
20
+
21
+ Output failures with exact commands, item id, and next remediation step.
@@ -0,0 +1,11 @@
1
+ ---
2
+ name: pm-native-delivery
3
+ description: Triage, implement, and verify a pm-tracked change using native pm operations in Pi.
4
+ steps:
5
+ - agent: pm-triage-agent
6
+ task: "Triage the requested work and identify the canonical pm item for: {task}"
7
+ - agent: worker
8
+ task: "Implement the approved scope from the triage handoff. Use native pm for tracker operations, link changed files/tests/docs, and keep edits scoped. Original request: {task}\n\nTriage handoff:\n{previous}"
9
+ - agent: pm-verification-agent
10
+ task: "Verify the implementation and produce pm closure evidence. Original request: {task}\n\nImplementation handoff:\n{previous}"
11
+ ---
@@ -1,32 +1,9 @@
1
- import { PM_TOOL_ACTIONS } from "../../../dist/sdk/cli-contracts.js";
1
+ import { PM_PI_TOOL_PARAMETERS_SCHEMA, PM_TOOL_ACTIONS } from "../../../dist/sdk/cli-contracts.js";
2
2
  import { runNativePmAction } from "../../../dist/pi/native.js";
3
3
 
4
- const PM_PI_TOOL_PARAMETERS_SCHEMA = {
5
- type: "object",
4
+ const PM_PI_TOOL_PARAMETERS_SCHEMA_COMPAT = {
5
+ ...PM_PI_TOOL_PARAMETERS_SCHEMA,
6
6
  additionalProperties: true,
7
- required: ["action"],
8
- description: "Parameters for the native pm Pi tool. Extra properties are forwarded to the selected pm action.",
9
- properties: {
10
- action: {
11
- type: "string",
12
- description: "pm action to execute, for example context, search, get, create, update, files, docs, test, validate, or close-task.",
13
- },
14
- id: { type: "string", description: "pm item id for item-scoped actions." },
15
- text: { type: "string", description: "Text payload for comment-like actions or close reasons." },
16
- title: { type: "string", description: "Title for create actions." },
17
- description: { type: "string", description: "Description for create/update actions." },
18
- query: { type: "string", description: "Search query text." },
19
- limit: { type: "string", description: "Result limit. Numeric strings are accepted." },
20
- author: { type: "string", description: "Explicit pm author for mutations." },
21
- path: { type: "string", description: "pm data path override or linked file path, depending on action." },
22
- scope: { type: "string", description: "Link scope such as project." },
23
- command: { type: "string", description: "Linked test command or shell completion target, depending on action." },
24
- options: {
25
- type: "object",
26
- additionalProperties: true,
27
- description: "Advanced command options object forwarded to the selected pm action.",
28
- },
29
- },
30
7
  };
31
8
 
32
9
  function contentText(result) {
@@ -34,6 +11,155 @@ function contentText(result) {
34
11
  return JSON.stringify(result, null, 2);
35
12
  }
36
13
 
14
+ function firstText(result) {
15
+ const entry = Array.isArray(result?.content) ? result.content.find((part) => part?.type === "text") : undefined;
16
+ return typeof entry?.text === "string" ? entry.text : "";
17
+ }
18
+
19
+ function truncatePlain(value, width) {
20
+ const text = String(value ?? "");
21
+ let visible = 0;
22
+ let output = "";
23
+ for (let index = 0; index < text.length; index += 1) {
24
+ const char = text[index];
25
+ if (char === "\u001b") {
26
+ const match = text.slice(index).match(/^\u001b\[[0-9;?]*[ -/]*[@-~]/);
27
+ if (match) {
28
+ output += match[0];
29
+ index += match[0].length - 1;
30
+ continue;
31
+ }
32
+ }
33
+ if (visible >= Math.max(0, width - 1)) return `${output}…`;
34
+ output += char;
35
+ visible += 1;
36
+ }
37
+ return output;
38
+ }
39
+
40
+ function statusIcon(status, theme) {
41
+ const normalized = String(status ?? "").toLowerCase();
42
+ if (normalized === "closed") return theme.fg("success", "✓");
43
+ if (normalized === "blocked") return theme.fg("warning", "!");
44
+ if (normalized === "in_progress") return theme.fg("accent", "▶");
45
+ if (normalized === "canceled") return theme.fg("dim", "×");
46
+ return theme.fg("dim", "○");
47
+ }
48
+
49
+ class PmPanelComponent {
50
+ constructor(title, lines, theme, onClose) {
51
+ this.title = title;
52
+ this.lines = lines;
53
+ this.theme = theme;
54
+ this.onClose = onClose;
55
+ this.cachedWidth = undefined;
56
+ this.cachedLines = undefined;
57
+ }
58
+
59
+ handleInput(data) {
60
+ if (data === "\u001b" || data === "\u0003" || data === "q" || data === "Q") {
61
+ this.onClose?.();
62
+ }
63
+ }
64
+
65
+ render(width) {
66
+ if (this.cachedLines && this.cachedWidth === width) return this.cachedLines;
67
+ const theme = this.theme;
68
+ const usable = Math.max(20, width);
69
+ const border = theme.fg("borderMuted", "─".repeat(Math.max(0, usable)));
70
+ const rendered = [
71
+ border,
72
+ truncatePlain(`${theme.fg("accent", theme.bold(` pm ${this.title} `))}${theme.fg("dim", "q/esc closes")}`, usable),
73
+ border,
74
+ ...this.lines.map((line) => truncatePlain(line, usable)),
75
+ border,
76
+ ];
77
+ this.cachedWidth = width;
78
+ this.cachedLines = rendered;
79
+ return rendered;
80
+ }
81
+
82
+ invalidate() {
83
+ this.cachedWidth = undefined;
84
+ this.cachedLines = undefined;
85
+ }
86
+ }
87
+
88
+ function makeItemLine(item, theme) {
89
+ const id = theme.fg("accent", item.id ?? "unknown");
90
+ const type = theme.fg("muted", item.type ?? "Item");
91
+ const status = statusIcon(item.status, theme);
92
+ const priority = item.priority === undefined ? "" : theme.fg("dim", ` p${item.priority}`);
93
+ const owner = item.assignee ? theme.fg("dim", ` @${item.assignee}`) : "";
94
+ return `${status} ${id} ${type}${priority}${owner} ${item.title ?? "(untitled)"}`;
95
+ }
96
+
97
+ function contextLines(result, theme) {
98
+ const lines = [];
99
+ const summary = result?.summary;
100
+ if (summary) {
101
+ lines.push(
102
+ `${theme.fg("muted", "summary")} active=${summary.active_items ?? 0} open=${summary.open ?? 0} in_progress=${summary.in_progress ?? 0} blocked=${summary.blocked ?? 0}`,
103
+ );
104
+ }
105
+ const high = Array.isArray(result?.high_level) ? result.high_level : [];
106
+ const low = Array.isArray(result?.low_level) ? result.low_level : [];
107
+ if (high.length > 0) {
108
+ lines.push("", theme.fg("accent", "High level"));
109
+ lines.push(...high.map((item) => makeItemLine(item, theme)));
110
+ }
111
+ if (low.length > 0) {
112
+ lines.push("", theme.fg("accent", "Tasks"));
113
+ lines.push(...low.map((item) => makeItemLine(item, theme)));
114
+ }
115
+ if (lines.length === 0) lines.push(theme.fg("dim", "No pm context items found."));
116
+ return lines;
117
+ }
118
+
119
+ function listLines(result, theme) {
120
+ const items = Array.isArray(result?.items) ? result.items : Array.isArray(result) ? result : [];
121
+ if (items.length === 0) return [theme.fg("dim", "No items.")];
122
+ return items.map((entry) => makeItemLine(entry.item ?? entry, theme));
123
+ }
124
+
125
+ function historyLines(result, theme) {
126
+ const entries = Array.isArray(result?.history) ? result.history : Array.isArray(result?.entries) ? result.entries : [];
127
+ if (entries.length === 0) return [theme.fg("dim", "No history entries.")];
128
+ return entries.slice(0, 30).map((entry) => {
129
+ const at = entry.timestamp ?? entry.created_at ?? "";
130
+ const op = entry.op ?? entry.operation ?? entry.type ?? "history";
131
+ const author = entry.author ? ` ${theme.fg("dim", `@${entry.author}`)}` : "";
132
+ const message = entry.message ?? entry.text ?? entry.reason ?? "";
133
+ return `${theme.fg("muted", at)} ${theme.fg("accent", op)}${author} ${message}`;
134
+ });
135
+ }
136
+
137
+ function getItemLines(result, theme) {
138
+ const item = result?.item ?? result;
139
+ if (!item || typeof item !== "object") return [theme.fg("dim", "No item details.")];
140
+ const lines = [makeItemLine(item, theme)];
141
+ if (item.description) lines.push("", item.description);
142
+ if (item.acceptance_criteria) lines.push("", `${theme.fg("accent", "Acceptance")}: ${item.acceptance_criteria}`);
143
+ if (Array.isArray(item.comments) && item.comments.length > 0) {
144
+ lines.push("", theme.fg("accent", "Recent comments"));
145
+ for (const comment of item.comments.slice(-5)) {
146
+ lines.push(`${theme.fg("muted", comment.created_at ?? "")} ${comment.author ?? "unknown"}: ${comment.text ?? ""}`);
147
+ }
148
+ }
149
+ return lines;
150
+ }
151
+
152
+ function resultLines(details, theme) {
153
+ const action = details?.action;
154
+ const result = details?.result;
155
+ if (action === "context") return contextLines(result, theme);
156
+ if (String(action ?? "").startsWith("list") || action === "search") return listLines(result, theme);
157
+ if (action === "history" || action === "activity") return historyLines(result, theme);
158
+ if (action === "get") return getItemLines(result, theme);
159
+ const raw = contentText(result);
160
+ return raw.split("\n").slice(0, 40);
161
+ }
162
+
37
163
  function errorDetails(error) {
38
164
  return {
39
165
  message: error instanceof Error ? error.message : String(error),
@@ -41,19 +167,42 @@ function errorDetails(error) {
41
167
  };
42
168
  }
43
169
 
170
+ function uiTheme(ctx) {
171
+ return ctx.ui?.theme ?? {
172
+ fg: (_name, text) => String(text),
173
+ bold: (text) => String(text),
174
+ };
175
+ }
176
+
177
+ async function showPanel(ctx, title, lines, overlay = true) {
178
+ if (!ctx.hasUI || typeof ctx.ui?.custom !== "function") {
179
+ ctx.ui.notify(lines.join("\n"), "info");
180
+ return;
181
+ }
182
+ await ctx.ui.custom((_tui, theme, _keybindings, done) => new PmPanelComponent(title, lines, theme, () => done(undefined)), {
183
+ overlay,
184
+ overlayOptions: { anchor: "right-center", width: "70%", minWidth: 60, maxHeight: "85%", margin: 1 },
185
+ });
186
+ }
187
+
188
+ async function runForCommand(ctx, params) {
189
+ return runNativePmAction({ cwd: ctx.cwd, ...params });
190
+ }
191
+
44
192
  export function createPmToolDefinition() {
45
193
  return {
46
194
  name: "pm",
47
195
  label: "pm",
48
196
  description:
49
- "Use pm natively from Pi without shelling out to the pm CLI. Supports pm project context, search, lifecycle mutations, links, tests, validation, extension management, templates, calendar, and audit workflows.",
197
+ "Use pm natively from Pi without shelling out to the pm CLI. Supports pm project context, search, lifecycle mutations, links, tests, validation, extension management, templates, calendar, guide, audit, beads, todos, and managed test-run workflows.",
50
198
  promptSnippet: "Run native pm project-management operations without bash or the pm CLI.",
51
199
  promptGuidelines: [
52
200
  "Use the pm tool instead of bash pm commands for project-management operations when this tool is available.",
53
201
  "Use pm action=context/list-open/list-in-progress/search before creating new work items.",
54
202
  "For mutations, set author explicitly and link changed files/tests/docs through pm actions before closing work.",
203
+ "Use pm action=contracts or guide when you need exact action/flag support instead of guessing parameters.",
55
204
  ],
56
- parameters: PM_PI_TOOL_PARAMETERS_SCHEMA,
205
+ parameters: PM_PI_TOOL_PARAMETERS_SCHEMA_COMPAT,
57
206
  async execute(_toolCallId, params, _signal, onUpdate, ctx) {
58
207
  onUpdate?.({ content: [{ type: "text", text: `Running native pm action: ${params.action}` }] });
59
208
  try {
@@ -67,6 +216,20 @@ export function createPmToolDefinition() {
67
216
  throw new Error(`pm ${params.action ?? "action"} failed: ${details.message}`);
68
217
  }
69
218
  },
219
+ renderCall(args, theme) {
220
+ const action = args?.action ?? "action";
221
+ const target = args?.id ?? args?.query ?? args?.target ?? args?.title ?? "";
222
+ return new PmPanelComponent("tool", [`${theme.fg("toolTitle", theme.bold("pm"))} ${theme.fg("accent", action)} ${theme.fg("dim", target)}`], theme);
223
+ },
224
+ renderResult(result, { expanded, isPartial }, theme) {
225
+ if (isPartial) return new PmPanelComponent("running", [theme.fg("warning", "Running…")], theme);
226
+ const details = result?.details;
227
+ if (!details?.ok) return new PmPanelComponent("result", [firstText(result) || contentText(result)], theme);
228
+ const lines = resultLines(details, theme);
229
+ const visible = expanded ? lines : lines.slice(0, 12);
230
+ if (!expanded && lines.length > visible.length) visible.push(theme.fg("dim", `… ${lines.length - visible.length} more; expand tool output for details`));
231
+ return new PmPanelComponent(String(details.action ?? "result"), visible, theme);
232
+ },
70
233
  };
71
234
  }
72
235
 
@@ -75,8 +238,38 @@ export function registerPmCommands(pi) {
75
238
  description: "Show pm context snapshot using the native pm integration",
76
239
  handler: async (args, ctx) => {
77
240
  const limit = args?.trim() || "10";
78
- const result = await runNativePmAction({ cwd: ctx.cwd, action: "context", limit, json: false });
79
- ctx.ui.notify(contentText(result), "info");
241
+ const result = await runForCommand(ctx, { action: "context", limit, json: true });
242
+ await showPanel(ctx, "context", contextLines(result, uiTheme(ctx)));
243
+ },
244
+ });
245
+
246
+ pi.registerCommand("pm-board", {
247
+ description: "Open an interactive pm dashboard panel: /pm-board [limit]",
248
+ handler: async (args, ctx) => {
249
+ const limit = args?.trim() || "12";
250
+ const result = await runForCommand(ctx, { action: "context", limit, depth: "standard", json: true });
251
+ await showPanel(ctx, "board", contextLines(result, uiTheme(ctx)));
252
+ },
253
+ });
254
+
255
+ pi.registerCommand("pm-item", {
256
+ description: "Open pm item details: /pm-item <id>",
257
+ getArgumentCompletions: () => null,
258
+ handler: async (args, ctx) => {
259
+ const id = args?.trim();
260
+ if (!id) return ctx.ui.notify("Usage: /pm-item <id>", "error");
261
+ const result = await runForCommand(ctx, { action: "get", id, json: true });
262
+ await showPanel(ctx, id, getItemLines(result, uiTheme(ctx)));
263
+ },
264
+ });
265
+
266
+ pi.registerCommand("pm-history", {
267
+ description: "Open pm item history/activity panel: /pm-history <id>",
268
+ handler: async (args, ctx) => {
269
+ const id = args?.trim();
270
+ if (!id) return ctx.ui.notify("Usage: /pm-history <id>", "error");
271
+ const result = await runForCommand(ctx, { action: "history", id, limit: 30, json: true });
272
+ await showPanel(ctx, `${id} history`, historyLines(result, uiTheme(ctx)));
80
273
  },
81
274
  });
82
275
 
@@ -85,7 +278,7 @@ export function registerPmCommands(pi) {
85
278
  handler: async (args, ctx) => {
86
279
  const id = args?.trim();
87
280
  if (!id) return ctx.ui.notify("Usage: /pm-start <id>", "error");
88
- const result = await runNativePmAction({ cwd: ctx.cwd, action: "start-task", id, author: "pi-agent" });
281
+ const result = await runForCommand(ctx, { action: "start-task", id, author: "pi-agent" });
89
282
  ctx.ui.notify(contentText(result), "success");
90
283
  },
91
284
  });
@@ -96,7 +289,7 @@ export function registerPmCommands(pi) {
96
289
  const [id, ...reasonParts] = (args ?? "").trim().split(/\s+/);
97
290
  const reason = reasonParts.join(" ");
98
291
  if (!id || !reason) return ctx.ui.notify("Usage: /pm-close <id> <reason>", "error");
99
- const result = await runNativePmAction({ cwd: ctx.cwd, action: "close-task", id, text: reason, author: "pi-agent", validateClose: "warn" });
292
+ const result = await runForCommand(ctx, { action: "close-task", id, text: reason, author: "pi-agent", validateClose: "warn" });
100
293
  ctx.ui.notify(contentText(result), "success");
101
294
  },
102
295
  });
@@ -104,7 +297,21 @@ export function registerPmCommands(pi) {
104
297
  pi.registerCommand("pm-actions", {
105
298
  description: "List native pm tool actions",
106
299
  handler: async (_args, ctx) => {
107
- ctx.ui.notify(PM_TOOL_ACTIONS.join(", "), "info");
300
+ await showPanel(ctx, "actions", PM_TOOL_ACTIONS.map((action) => `• ${action}`));
301
+ },
302
+ });
303
+
304
+ pi.registerCommand("pm-workflows", {
305
+ description: "Show native pm workflow shortcuts and bundled Pi resources",
306
+ handler: async (_args, ctx) => {
307
+ await showPanel(ctx, "workflows", [
308
+ "1. /pm-board to inspect focus/context items.",
309
+ "2. Use the pm tool action=search/list-open/list-in-progress before creating work.",
310
+ "3. /pm-start <id> or pm action=start-task to claim and move in_progress.",
311
+ "4. Use pm action=files/docs/test to link implementation evidence.",
312
+ "5. Run pm action=test/validate and close with pm action=close-task.",
313
+ "Bundled resources: pm-native and pm-release skills, pm-workflow prompt, pm subagent templates in .pi/agents.",
314
+ ]);
108
315
  },
109
316
  });
110
317
  }
@@ -122,14 +329,14 @@ function patchPmToolParametersInProviderPayload(payload) {
122
329
  const parameters = tool.parameters;
123
330
  if (!parameters || parameters.type !== "object") {
124
331
  changed = true;
125
- return { ...tool, parameters: PM_PI_TOOL_PARAMETERS_SCHEMA };
332
+ return { ...tool, parameters: PM_PI_TOOL_PARAMETERS_SCHEMA_COMPAT };
126
333
  }
127
334
  }
128
335
  if (tool.function?.name === "pm") {
129
336
  const parameters = tool.function.parameters;
130
337
  if (!parameters || parameters.type !== "object") {
131
338
  changed = true;
132
- return { ...tool, function: { ...tool.function, parameters: PM_PI_TOOL_PARAMETERS_SCHEMA } };
339
+ return { ...tool, function: { ...tool.function, parameters: PM_PI_TOOL_PARAMETERS_SCHEMA_COMPAT } };
133
340
  }
134
341
  }
135
342
  return tool;
@@ -137,11 +344,44 @@ function patchPmToolParametersInProviderPayload(payload) {
137
344
  return changed ? { ...payload, tools } : undefined;
138
345
  }
139
346
 
347
+ function installPmAutocomplete(ctx) {
348
+ if (typeof ctx.ui?.addAutocompleteProvider !== "function") return;
349
+ ctx.ui.addAutocompleteProvider((current) => ({
350
+ async getSuggestions(lines, line, col, options) {
351
+ const beforeCursor = (lines[line] ?? "").slice(0, col);
352
+ const match = beforeCursor.match(/(?:^|[\s(])@(pm-[a-z0-9-]*)$/i);
353
+ if (!match) return current.getSuggestions(lines, line, col, options);
354
+ try {
355
+ const result = await runNativePmAction({ cwd: ctx.cwd, action: "list-open", limit: 20, json: true });
356
+ const items = Array.isArray(result?.items) ? result.items : [];
357
+ return {
358
+ prefix: `@${match[1] ?? ""}`,
359
+ items: items.map((item) => ({ value: `@${item.id}`, label: item.id, description: item.title ?? item.status ?? "pm item" })),
360
+ };
361
+ } catch {
362
+ return current.getSuggestions(lines, line, col, options);
363
+ }
364
+ },
365
+ applyCompletion(lines, line, col, item, prefix) {
366
+ return current.applyCompletion(lines, line, col, item, prefix);
367
+ },
368
+ shouldTriggerFileCompletion(lines, line, col) {
369
+ return current.shouldTriggerFileCompletion?.(lines, line, col) ?? true;
370
+ },
371
+ }));
372
+ }
373
+
140
374
  export default function pmCliPiExtension(pi) {
141
375
  pi.registerTool(createPmToolDefinition());
142
376
  pi.on("before_provider_request", (event) => patchPmToolParametersInProviderPayload(event.payload));
143
377
  registerPmCommands(pi);
144
378
  pi.on("session_start", async (_event, ctx) => {
145
- ctx.ui.setStatus?.("pm", "pm native");
379
+ ctx.ui.setStatus?.("pm", ctx.ui.theme?.fg ? ctx.ui.theme.fg("accent", "pm native") : "pm native");
380
+ ctx.ui.setWidget?.("pm-native", ["pm native ready • /pm-board • /pm-actions • @pm-id autocomplete"], { placement: "belowEditor" });
381
+ installPmAutocomplete(ctx);
382
+ });
383
+ pi.on("session_shutdown", async (_event, ctx) => {
384
+ ctx.ui.setStatus?.("pm", undefined);
385
+ ctx.ui.setWidget?.("pm-native", undefined);
146
386
  });
147
387
  }
@@ -11,7 +11,7 @@ metadata:
11
11
 
12
12
  # pm Native for Pi
13
13
 
14
- Use the `pm` tool for project-management operations. Do not run `pm ...` through bash when the native tool is available.
14
+ Use the `pm` tool for project-management operations. Do not run `pm ...` through bash when the native tool is available. In interactive Pi, use `/pm-board`, `/pm-item <id>`, `/pm-history <id>`, `/pm-actions`, and `/pm-workflows` when the user would benefit from seeing tracker state in the TUI.
15
15
 
16
16
  ## Required Loop
17
17
 
@@ -27,6 +27,7 @@ Use the `pm` tool for project-management operations. Do not run `pm ...` through
27
27
  - action `test` with sandbox-safe commands
28
28
  5. Verify with action `test`, `validate`, and project test commands when appropriate.
29
29
  6. Close with action `close-task` or `close`, then release if needed.
30
+ 7. For exact feature support, call `pm` action `contracts` or `guide` instead of guessing flags.
30
31
 
31
32
  ## Common Tool Calls
32
33
 
@@ -36,5 +37,8 @@ Use the `pm` tool for project-management operations. Do not run `pm ...` through
36
37
  - Link file: `{ "action": "files", "id": "pm-1234", "add": ["path=src/file.ts,scope=project,note=implementation"], "author": "pi-agent" }`
37
38
  - Add comment: `{ "action": "comments", "id": "pm-1234", "add": ["Evidence: build and tests passed"], "author": "pi-agent" }`
38
39
  - Close: `{ "action": "close-task", "id": "pm-1234", "text": "All acceptance criteria met", "author": "pi-agent", "validateClose": "warn" }`
40
+ - Extension lifecycle: `{ "action": "extension", "install": true, "target": "todos", "scope": "project" }`
41
+ - Templates: `{ "action": "templates-list" }`
42
+ - Managed tests: `{ "action": "test-runs-list" }`
39
43
 
40
- Use `pm guide` topics through action `guide` for deeper command docs.
44
+ Use `pm guide` topics through action `guide` for deeper command docs. The bundled Pi package also provides project subagent definitions (`pm-triage-agent`, `pm-verification-agent`) and a `pm-native-delivery` chain for installations that use pi-subagents.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [2026.5.11] - 2026-05-11
11
+
12
+ ### Added
13
+ - Claude Code plugin v1.3.0: added `pm-triage-agent`, `pm-verification-agent`, and `pm-delivery-chain` subagents for full end-to-end pm workflow orchestration without leaving Claude Code.
14
+ - Claude Code plugin: registered `pm` as the canonical marketplace ID so the plugin installs via `/plugin install pm-cli@pm` (both `pm` and `pm-cli` marketplace IDs resolve to the same plugin).
15
+ - Claude Code plugin: rewrote session-start hook to use native `dist/pi/native.js` modules when available (repo checkout), falling back to `npx @unbrained/pm-cli` — no global `pm` CLI required.
16
+
10
17
  ## [2026.5.10] - 2026-05-10
11
18
 
12
19
  ### Changed
package/README.md CHANGED
@@ -51,13 +51,21 @@ Project-local invocation also works:
51
51
  npx @unbrained/pm-cli --help
52
52
  ```
53
53
 
54
+ For Claude Code, install the native plugin (no `pm` CLI required):
55
+
56
+ ```
57
+ /plugin install pm-cli@pm
58
+ ```
59
+
60
+ This registers 18 MCP tools, 5 workflow skills, 14 slash commands, 3 subagents, hybrid TUI tracking, and a session-start context hook — all without shelling out to the `pm` CLI.
61
+
54
62
  For Pi, install the native package integration after publish:
55
63
 
56
64
  ```bash
57
65
  pi install npm:@unbrained/pm-cli
58
66
  ```
59
67
 
60
- This registers a native `pm` tool, Pi skills, and prompt templates without requiring Pi to run the `pm` shell command.
68
+ This registers a native `pm` tool, custom TUI panels/renderers (`/pm-board`, `/pm-item`, `/pm-history`), Pi skills, prompt templates, and optional pi-subagents setup without requiring Pi to run the `pm` shell command.
61
69
 
62
70
  ## 60 Second Example
63
71
 
@@ -0,0 +1,5 @@
1
+ export declare function normalizeLongFlag(flag: string): string;
2
+ export declare function normalizeLongOptionFlag(token: string): string | undefined;
3
+ export declare function extractProvidedOptionFlags(argv: string[]): string[];
4
+ export declare function quoteCommandArg(arg: string): string;
5
+ export declare function renderPmCommand(argv: string[]): string;
@@ -0,0 +1,34 @@
1
+ export function normalizeLongFlag(flag) {
2
+ return `--${flag
3
+ .replace(/^--?/, "")
4
+ .replace(/_/g, "-")
5
+ .replace(/([a-z0-9])([A-Z])/g, "$1-$2")
6
+ .toLowerCase()}`;
7
+ }
8
+ export function normalizeLongOptionFlag(token) {
9
+ if (!token.startsWith("--")) {
10
+ return undefined;
11
+ }
12
+ const key = token.includes("=") ? token.slice(0, token.indexOf("=")) : token;
13
+ return normalizeLongFlag(key);
14
+ }
15
+ export function extractProvidedOptionFlags(argv) {
16
+ const provided = new Set();
17
+ for (const token of argv) {
18
+ const normalized = normalizeLongOptionFlag(token);
19
+ if (normalized) {
20
+ provided.add(normalized);
21
+ }
22
+ }
23
+ return [...provided].sort((left, right) => left.localeCompare(right));
24
+ }
25
+ export function quoteCommandArg(arg) {
26
+ if (/^[A-Za-z0-9._:/@=-]+$/.test(arg)) {
27
+ return arg;
28
+ }
29
+ return `"${arg.replace(/(["\\$`])/g, "\\$1")}"`;
30
+ }
31
+ export function renderPmCommand(argv) {
32
+ return `pm ${argv.map((token) => quoteCommandArg(token)).join(" ")}`;
33
+ }
34
+ //# sourceMappingURL=argv-utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"argv-utils.js","sourceRoot":"/","sources":["cli/argv-utils.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,iBAAiB,CAAC,IAAY;IAC5C,OAAO,KAAK,IAAI;SACb,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;SACnB,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC;SAClB,OAAO,CAAC,oBAAoB,EAAE,OAAO,CAAC;SACtC,WAAW,EAAE,EAAE,CAAC;AACrB,CAAC;AAED,MAAM,UAAU,uBAAuB,CAAC,KAAa;IACnD,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QAC5B,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,MAAM,GAAG,GAAG,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;IAC7E,OAAO,iBAAiB,CAAC,GAAG,CAAC,CAAC;AAChC,CAAC;AAED,MAAM,UAAU,0BAA0B,CAAC,IAAc;IACvD,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAU,CAAC;IACnC,KAAK,MAAM,KAAK,IAAI,IAAI,EAAE,CAAC;QACzB,MAAM,UAAU,GAAG,uBAAuB,CAAC,KAAK,CAAC,CAAC;QAClD,IAAI,UAAU,EAAE,CAAC;YACf,QAAQ,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QAC3B,CAAC;IACH,CAAC;IACD,OAAO,CAAC,GAAG,QAAQ,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC;AACxE,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,GAAW;IACzC,IAAI,uBAAuB,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QACtC,OAAO,GAAG,CAAC;IACb,CAAC;IACD,OAAO,IAAI,GAAG,CAAC,OAAO,CAAC,YAAY,EAAE,MAAM,CAAC,GAAG,CAAC;AAClD,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,IAAc;IAC5C,OAAO,MAAM,IAAI,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;AACvE,CAAC","sourcesContent":["export function normalizeLongFlag(flag: string): string {\n return `--${flag\n .replace(/^--?/, \"\")\n .replace(/_/g, \"-\")\n .replace(/([a-z0-9])([A-Z])/g, \"$1-$2\")\n .toLowerCase()}`;\n}\n\nexport function normalizeLongOptionFlag(token: string): string | undefined {\n if (!token.startsWith(\"--\")) {\n return undefined;\n }\n const key = token.includes(\"=\") ? token.slice(0, token.indexOf(\"=\")) : token;\n return normalizeLongFlag(key);\n}\n\nexport function extractProvidedOptionFlags(argv: string[]): string[] {\n const provided = new Set<string>();\n for (const token of argv) {\n const normalized = normalizeLongOptionFlag(token);\n if (normalized) {\n provided.add(normalized);\n }\n }\n return [...provided].sort((left, right) => left.localeCompare(right));\n}\n\nexport function quoteCommandArg(arg: string): string {\n if (/^[A-Za-z0-9._:/@=-]+$/.test(arg)) {\n return arg;\n }\n return `\"${arg.replace(/([\"\\\\$`])/g, \"\\\\$1\")}\"`;\n}\n\nexport function renderPmCommand(argv: string[]): string {\n return `pm ${argv.map((token) => quoteCommandArg(token)).join(\" \")}`;\n}\n"]}
@@ -15,4 +15,19 @@ export declare function parseBootstrapHelpRequest(argv: string[]): BootstrapHelp
15
15
  export declare function parseBootstrapCommandName(argv: string[]): string | undefined;
16
16
  export declare function applyBootstrapPagerPolicy(argv: string[]): void;
17
17
  export declare function normalizeLegacyExtensionActionSyntax(argv: string[]): string[];
18
+ type BootstrapNormalizationReason = "legacy_extension_action" | "flag_alias" | "flag_typo" | "bare_key_value";
19
+ type BootstrapNormalizationConfidence = "high" | "medium";
20
+ export interface BootstrapNormalizationEvent {
21
+ from: string;
22
+ to: string[];
23
+ reason: BootstrapNormalizationReason;
24
+ confidence: BootstrapNormalizationConfidence;
25
+ }
26
+ export interface BootstrapInvocationNormalizationResult {
27
+ argv: string[];
28
+ commandName: string | undefined;
29
+ trace: BootstrapNormalizationEvent[];
30
+ }
31
+ export declare function normalizeBootstrapInvocation(argv: string[]): BootstrapInvocationNormalizationResult;
18
32
  export declare function parseBootstrapTypeValue(argv: string[]): string | undefined;
33
+ export {};