@unbrained/pm-cli 2026.3.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (245) hide show
  1. package/.pi/extensions/pm-cli/index.ts +778 -0
  2. package/AGENTS.md +475 -0
  3. package/LICENSE +21 -0
  4. package/PRD.md +1390 -0
  5. package/README.md +655 -0
  6. package/dist/cli/commands/activity.d.ts +14 -0
  7. package/dist/cli/commands/activity.js +80 -0
  8. package/dist/cli/commands/activity.js.map +1 -0
  9. package/dist/cli/commands/append.d.ts +13 -0
  10. package/dist/cli/commands/append.js +46 -0
  11. package/dist/cli/commands/append.js.map +1 -0
  12. package/dist/cli/commands/beads.d.ts +15 -0
  13. package/dist/cli/commands/beads.js +475 -0
  14. package/dist/cli/commands/beads.js.map +1 -0
  15. package/dist/cli/commands/claim.d.ts +19 -0
  16. package/dist/cli/commands/claim.js +79 -0
  17. package/dist/cli/commands/claim.js.map +1 -0
  18. package/dist/cli/commands/close.d.ts +12 -0
  19. package/dist/cli/commands/close.js +58 -0
  20. package/dist/cli/commands/close.js.map +1 -0
  21. package/dist/cli/commands/comments.d.ts +15 -0
  22. package/dist/cli/commands/comments.js +80 -0
  23. package/dist/cli/commands/comments.js.map +1 -0
  24. package/dist/cli/commands/completion.d.ts +10 -0
  25. package/dist/cli/commands/completion.js +469 -0
  26. package/dist/cli/commands/completion.js.map +1 -0
  27. package/dist/cli/commands/config.d.ts +15 -0
  28. package/dist/cli/commands/config.js +72 -0
  29. package/dist/cli/commands/config.js.map +1 -0
  30. package/dist/cli/commands/create.d.ts +60 -0
  31. package/dist/cli/commands/create.js +456 -0
  32. package/dist/cli/commands/create.js.map +1 -0
  33. package/dist/cli/commands/delete.d.ts +12 -0
  34. package/dist/cli/commands/delete.js +33 -0
  35. package/dist/cli/commands/delete.js.map +1 -0
  36. package/dist/cli/commands/docs.d.ts +16 -0
  37. package/dist/cli/commands/docs.js +113 -0
  38. package/dist/cli/commands/docs.js.map +1 -0
  39. package/dist/cli/commands/files.d.ts +17 -0
  40. package/dist/cli/commands/files.js +113 -0
  41. package/dist/cli/commands/files.js.map +1 -0
  42. package/dist/cli/commands/gc.d.ts +9 -0
  43. package/dist/cli/commands/gc.js +80 -0
  44. package/dist/cli/commands/gc.js.map +1 -0
  45. package/dist/cli/commands/get.d.ts +12 -0
  46. package/dist/cli/commands/get.js +28 -0
  47. package/dist/cli/commands/get.js.map +1 -0
  48. package/dist/cli/commands/health.d.ts +15 -0
  49. package/dist/cli/commands/health.js +288 -0
  50. package/dist/cli/commands/health.js.map +1 -0
  51. package/dist/cli/commands/history.d.ts +13 -0
  52. package/dist/cli/commands/history.js +72 -0
  53. package/dist/cli/commands/history.js.map +1 -0
  54. package/dist/cli/commands/index.d.ts +26 -0
  55. package/dist/cli/commands/index.js +27 -0
  56. package/dist/cli/commands/index.js.map +1 -0
  57. package/dist/cli/commands/init.d.ts +10 -0
  58. package/dist/cli/commands/init.js +59 -0
  59. package/dist/cli/commands/init.js.map +1 -0
  60. package/dist/cli/commands/install.d.ts +18 -0
  61. package/dist/cli/commands/install.js +87 -0
  62. package/dist/cli/commands/install.js.map +1 -0
  63. package/dist/cli/commands/list.d.ts +21 -0
  64. package/dist/cli/commands/list.js +137 -0
  65. package/dist/cli/commands/list.js.map +1 -0
  66. package/dist/cli/commands/reindex.d.ts +16 -0
  67. package/dist/cli/commands/reindex.js +154 -0
  68. package/dist/cli/commands/reindex.js.map +1 -0
  69. package/dist/cli/commands/restore.d.ts +20 -0
  70. package/dist/cli/commands/restore.js +208 -0
  71. package/dist/cli/commands/restore.js.map +1 -0
  72. package/dist/cli/commands/search.d.ts +45 -0
  73. package/dist/cli/commands/search.js +531 -0
  74. package/dist/cli/commands/search.js.map +1 -0
  75. package/dist/cli/commands/stats.d.ts +13 -0
  76. package/dist/cli/commands/stats.js +88 -0
  77. package/dist/cli/commands/stats.js.map +1 -0
  78. package/dist/cli/commands/test-all.d.ts +30 -0
  79. package/dist/cli/commands/test-all.js +157 -0
  80. package/dist/cli/commands/test-all.js.map +1 -0
  81. package/dist/cli/commands/test.d.ts +29 -0
  82. package/dist/cli/commands/test.js +492 -0
  83. package/dist/cli/commands/test.js.map +1 -0
  84. package/dist/cli/commands/update.d.ts +52 -0
  85. package/dist/cli/commands/update.js +467 -0
  86. package/dist/cli/commands/update.js.map +1 -0
  87. package/dist/cli/extension-command-options.d.ts +1 -0
  88. package/dist/cli/extension-command-options.js +76 -0
  89. package/dist/cli/extension-command-options.js.map +1 -0
  90. package/dist/cli/main.d.ts +2 -0
  91. package/dist/cli/main.js +1494 -0
  92. package/dist/cli/main.js.map +1 -0
  93. package/dist/cli.d.ts +2 -0
  94. package/dist/cli.js +3 -0
  95. package/dist/cli.js.map +1 -0
  96. package/dist/command-types.d.ts +1 -0
  97. package/dist/command-types.js +2 -0
  98. package/dist/command-types.js.map +1 -0
  99. package/dist/constants.d.ts +1 -0
  100. package/dist/constants.js +2 -0
  101. package/dist/constants.js.map +1 -0
  102. package/dist/core/extensions/builtins.d.ts +3 -0
  103. package/dist/core/extensions/builtins.js +47 -0
  104. package/dist/core/extensions/builtins.js.map +1 -0
  105. package/dist/core/extensions/index.d.ts +13 -0
  106. package/dist/core/extensions/index.js +88 -0
  107. package/dist/core/extensions/index.js.map +1 -0
  108. package/dist/core/extensions/loader.d.ts +301 -0
  109. package/dist/core/extensions/loader.js +917 -0
  110. package/dist/core/extensions/loader.js.map +1 -0
  111. package/dist/core/fs/fs-utils.d.ts +6 -0
  112. package/dist/core/fs/fs-utils.js +58 -0
  113. package/dist/core/fs/fs-utils.js.map +1 -0
  114. package/dist/core/fs/index.d.ts +1 -0
  115. package/dist/core/fs/index.js +2 -0
  116. package/dist/core/fs/index.js.map +1 -0
  117. package/dist/core/history/history.d.ts +12 -0
  118. package/dist/core/history/history.js +44 -0
  119. package/dist/core/history/history.js.map +1 -0
  120. package/dist/core/history/index.d.ts +1 -0
  121. package/dist/core/history/index.js +2 -0
  122. package/dist/core/history/index.js.map +1 -0
  123. package/dist/core/item/id.d.ts +3 -0
  124. package/dist/core/item/id.js +54 -0
  125. package/dist/core/item/id.js.map +1 -0
  126. package/dist/core/item/index.d.ts +3 -0
  127. package/dist/core/item/index.js +4 -0
  128. package/dist/core/item/index.js.map +1 -0
  129. package/dist/core/item/item-format.d.ts +9 -0
  130. package/dist/core/item/item-format.js +363 -0
  131. package/dist/core/item/item-format.js.map +1 -0
  132. package/dist/core/item/parse.d.ts +3 -0
  133. package/dist/core/item/parse.js +72 -0
  134. package/dist/core/item/parse.js.map +1 -0
  135. package/dist/core/lock/index.d.ts +1 -0
  136. package/dist/core/lock/index.js +2 -0
  137. package/dist/core/lock/index.js.map +1 -0
  138. package/dist/core/lock/lock.d.ts +1 -0
  139. package/dist/core/lock/lock.js +100 -0
  140. package/dist/core/lock/lock.js.map +1 -0
  141. package/dist/core/output/output.d.ts +7 -0
  142. package/dist/core/output/output.js +79 -0
  143. package/dist/core/output/output.js.map +1 -0
  144. package/dist/core/search/cache.d.ts +17 -0
  145. package/dist/core/search/cache.js +212 -0
  146. package/dist/core/search/cache.js.map +1 -0
  147. package/dist/core/search/embedding-batches.d.ts +7 -0
  148. package/dist/core/search/embedding-batches.js +54 -0
  149. package/dist/core/search/embedding-batches.js.map +1 -0
  150. package/dist/core/search/providers.d.ts +59 -0
  151. package/dist/core/search/providers.js +265 -0
  152. package/dist/core/search/providers.js.map +1 -0
  153. package/dist/core/search/vector-stores.d.ts +89 -0
  154. package/dist/core/search/vector-stores.js +546 -0
  155. package/dist/core/search/vector-stores.js.map +1 -0
  156. package/dist/core/shared/command-types.d.ts +7 -0
  157. package/dist/core/shared/command-types.js +2 -0
  158. package/dist/core/shared/command-types.js.map +1 -0
  159. package/dist/core/shared/constants.d.ts +19 -0
  160. package/dist/core/shared/constants.js +134 -0
  161. package/dist/core/shared/constants.js.map +1 -0
  162. package/dist/core/shared/errors.d.ts +4 -0
  163. package/dist/core/shared/errors.js +9 -0
  164. package/dist/core/shared/errors.js.map +1 -0
  165. package/dist/core/shared/index.d.ts +3 -0
  166. package/dist/core/shared/index.js +4 -0
  167. package/dist/core/shared/index.js.map +1 -0
  168. package/dist/core/shared/serialization.d.ts +3 -0
  169. package/dist/core/shared/serialization.js +70 -0
  170. package/dist/core/shared/serialization.js.map +1 -0
  171. package/dist/core/shared/time.d.ts +3 -0
  172. package/dist/core/shared/time.js +28 -0
  173. package/dist/core/shared/time.js.map +1 -0
  174. package/dist/core/store/index.d.ts +3 -0
  175. package/dist/core/store/index.js +4 -0
  176. package/dist/core/store/index.js.map +1 -0
  177. package/dist/core/store/item-store.d.ts +42 -0
  178. package/dist/core/store/item-store.js +186 -0
  179. package/dist/core/store/item-store.js.map +1 -0
  180. package/dist/core/store/paths.d.ts +8 -0
  181. package/dist/core/store/paths.js +29 -0
  182. package/dist/core/store/paths.js.map +1 -0
  183. package/dist/core/store/settings.d.ts +4 -0
  184. package/dist/core/store/settings.js +148 -0
  185. package/dist/core/store/settings.js.map +1 -0
  186. package/dist/errors.d.ts +1 -0
  187. package/dist/errors.js +2 -0
  188. package/dist/errors.js.map +1 -0
  189. package/dist/extensions/builtins/beads/index.d.ts +8 -0
  190. package/dist/extensions/builtins/beads/index.js +29 -0
  191. package/dist/extensions/builtins/beads/index.js.map +1 -0
  192. package/dist/extensions/builtins/todos/import-export.d.ts +26 -0
  193. package/dist/extensions/builtins/todos/import-export.js +460 -0
  194. package/dist/extensions/builtins/todos/import-export.js.map +1 -0
  195. package/dist/extensions/builtins/todos/index.d.ts +8 -0
  196. package/dist/extensions/builtins/todos/index.js +38 -0
  197. package/dist/extensions/builtins/todos/index.js.map +1 -0
  198. package/dist/fs-utils.d.ts +1 -0
  199. package/dist/fs-utils.js +2 -0
  200. package/dist/fs-utils.js.map +1 -0
  201. package/dist/history.d.ts +1 -0
  202. package/dist/history.js +2 -0
  203. package/dist/history.js.map +1 -0
  204. package/dist/id.d.ts +1 -0
  205. package/dist/id.js +2 -0
  206. package/dist/id.js.map +1 -0
  207. package/dist/item-format.d.ts +1 -0
  208. package/dist/item-format.js +2 -0
  209. package/dist/item-format.js.map +1 -0
  210. package/dist/item-store.d.ts +1 -0
  211. package/dist/item-store.js +2 -0
  212. package/dist/item-store.js.map +1 -0
  213. package/dist/lock.d.ts +1 -0
  214. package/dist/lock.js +2 -0
  215. package/dist/lock.js.map +1 -0
  216. package/dist/output.d.ts +1 -0
  217. package/dist/output.js +2 -0
  218. package/dist/output.js.map +1 -0
  219. package/dist/parse.d.ts +1 -0
  220. package/dist/parse.js +2 -0
  221. package/dist/parse.js.map +1 -0
  222. package/dist/paths.d.ts +1 -0
  223. package/dist/paths.js +2 -0
  224. package/dist/paths.js.map +1 -0
  225. package/dist/serialization.d.ts +1 -0
  226. package/dist/serialization.js +2 -0
  227. package/dist/serialization.js.map +1 -0
  228. package/dist/settings.d.ts +1 -0
  229. package/dist/settings.js +2 -0
  230. package/dist/settings.js.map +1 -0
  231. package/dist/time.d.ts +1 -0
  232. package/dist/time.js +2 -0
  233. package/dist/time.js.map +1 -0
  234. package/dist/types/index.d.ts +1 -0
  235. package/dist/types/index.js +2 -0
  236. package/dist/types/index.js.map +1 -0
  237. package/dist/types.d.ts +179 -0
  238. package/dist/types.js +21 -0
  239. package/dist/types.js.map +1 -0
  240. package/docs/ARCHITECTURE.md +246 -0
  241. package/docs/EXTENSIONS.md +329 -0
  242. package/docs/RELEASING.md +65 -0
  243. package/package.json +79 -0
  244. package/scripts/install.ps1 +112 -0
  245. package/scripts/install.sh +113 -0
@@ -0,0 +1,1494 @@
1
+ #!/usr/bin/env node
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { Command } from "commander";
6
+ import { runAppend, runActivity, runClaim, runClose, runComments, runCompletion, runConfig, runCreate, runDelete, runDocs, runFiles, runGet, runGc, runHealth, runHistory, runInit, runInstall, runList, runSearch, runReindex, runRestore, runRelease, runStats, runTest, runTestAll, runUpdate, } from "./commands/index.js";
7
+ import { activateExtensions, clearActiveExtensionHooks, createEmptyExtensionCommandRegistry, createEmptyExtensionHookRegistry, createEmptyExtensionRendererRegistry, loadExtensions, runActiveCommandHandler, runAfterCommandHooks, runBeforeCommandHooks, setActiveCommandContext, setActiveExtensionCommands, setActiveExtensionHooks, setActiveExtensionRenderers, } from "../core/extensions/index.js";
8
+ import { pathExists } from "../core/fs/fs-utils.js";
9
+ import { refreshSearchArtifactsForMutation } from "../core/search/cache.js";
10
+ import { EXIT_CODE } from "../core/shared/constants.js";
11
+ import { PmCliError } from "../core/shared/errors.js";
12
+ import { printError, printResult } from "../core/output/output.js";
13
+ import { getSettingsPath, resolvePmRoot } from "../core/store/paths.js";
14
+ import { readSettings } from "../core/store/settings.js";
15
+ import { getEnabledBuiltInExtensions } from "../core/extensions/builtins.js";
16
+ import { parseLooseCommandOptions } from "./extension-command-options.js";
17
+ function collect(value, previous) {
18
+ const next = previous ?? [];
19
+ next.push(value);
20
+ return next;
21
+ }
22
+ function getGlobalOptions(command) {
23
+ const opts = command.optsWithGlobals();
24
+ return {
25
+ json: Boolean(opts.json),
26
+ quiet: Boolean(opts.quiet),
27
+ path: typeof opts.path === "string" ? opts.path : undefined,
28
+ noExtensions: opts.extensions === false,
29
+ profile: Boolean(opts.profile),
30
+ };
31
+ }
32
+ function getCommandPath(command) {
33
+ const parts = [];
34
+ let current = command;
35
+ while (current?.parent) {
36
+ parts.unshift(current.name());
37
+ current = current.parent;
38
+ }
39
+ return parts.join(" ");
40
+ }
41
+ function normalizeExtensionCommandPath(commandPath) {
42
+ return commandPath
43
+ .trim()
44
+ .toLowerCase()
45
+ .split(/\s+/)
46
+ .filter((part) => part.length > 0)
47
+ .join(" ");
48
+ }
49
+ function toNonEmptyFlagString(value) {
50
+ if (typeof value !== "string") {
51
+ return null;
52
+ }
53
+ const trimmed = value.trim();
54
+ return trimmed.length > 0 ? trimmed : null;
55
+ }
56
+ function formatDynamicExtensionFlagHelpLine(definition) {
57
+ const longName = toNonEmptyFlagString(definition.long);
58
+ if (!longName || !longName.startsWith("--") || longName.length < 3) {
59
+ return null;
60
+ }
61
+ const shortName = toNonEmptyFlagString(definition.short);
62
+ const shortPrefix = shortName && shortName.startsWith("-") && !shortName.startsWith("--") ? `${shortName}, ` : "";
63
+ const valueName = toNonEmptyFlagString(definition.value_name);
64
+ const valueSuffix = valueName ? ` <${valueName}>` : "";
65
+ const description = toNonEmptyFlagString(definition.description) ?? "Extension-provided option.";
66
+ return `${shortPrefix}${longName}${valueSuffix} ${description}`;
67
+ }
68
+ function buildDynamicExtensionFlagHelp(definitions) {
69
+ const lines = [
70
+ ...new Set(definitions
71
+ .map(formatDynamicExtensionFlagHelpLine)
72
+ .filter((line) => line !== null)),
73
+ ].sort((left, right) => left.localeCompare(right));
74
+ if (lines.length === 0) {
75
+ return null;
76
+ }
77
+ return `\nExtension-provided flags:\n ${lines.join("\n ")}`;
78
+ }
79
+ function collectDynamicExtensionFlagHelpByCommand(registrations) {
80
+ const grouped = new Map();
81
+ for (const registration of registrations) {
82
+ const commandPath = normalizeExtensionCommandPath(registration.target_command);
83
+ if (commandPath.length === 0) {
84
+ continue;
85
+ }
86
+ const existing = grouped.get(commandPath) ?? [];
87
+ existing.push(...registration.flags);
88
+ grouped.set(commandPath, existing);
89
+ }
90
+ const entries = [...grouped.entries()].sort(([left], [right]) => left.localeCompare(right));
91
+ const helpByCommand = new Map();
92
+ for (const [commandPath, definitions] of entries) {
93
+ const helpText = buildDynamicExtensionFlagHelp(definitions);
94
+ if (!helpText) {
95
+ continue;
96
+ }
97
+ helpByCommand.set(commandPath, helpText);
98
+ }
99
+ return helpByCommand;
100
+ }
101
+ function findDirectChildCommand(parent, name) {
102
+ return parent.commands.find((entry) => entry.name() === name) ?? null;
103
+ }
104
+ function findCommandByPath(root, pathParts) {
105
+ let current = root;
106
+ for (const part of pathParts) {
107
+ const next = findDirectChildCommand(current, part);
108
+ if (!next) {
109
+ return null;
110
+ }
111
+ current = next;
112
+ }
113
+ return current;
114
+ }
115
+ function ensureCommandPath(root, pathParts) {
116
+ if (pathParts.length === 0) {
117
+ return null;
118
+ }
119
+ let current = root;
120
+ for (let index = 0; index < pathParts.length; index += 1) {
121
+ const part = pathParts[index];
122
+ const existing = findDirectChildCommand(current, part);
123
+ if (existing) {
124
+ current = existing;
125
+ continue;
126
+ }
127
+ const created = current.command(part);
128
+ if (index < pathParts.length - 1) {
129
+ created.description("Extension-provided command group.");
130
+ }
131
+ else {
132
+ created.description("Extension-provided command path.");
133
+ }
134
+ current = created;
135
+ }
136
+ return current;
137
+ }
138
+ function parseBootstrapPathToken(token, next) {
139
+ if (token === "--path") {
140
+ if (typeof next === "string" && next.length > 0) {
141
+ return {
142
+ consumed: 2,
143
+ pathValue: next,
144
+ };
145
+ }
146
+ return {
147
+ consumed: 1,
148
+ };
149
+ }
150
+ if (!token.startsWith("--path=")) {
151
+ return null;
152
+ }
153
+ const value = token.slice("--path=".length);
154
+ if (value.length > 0) {
155
+ return {
156
+ consumed: 1,
157
+ pathValue: value,
158
+ };
159
+ }
160
+ return {
161
+ consumed: 1,
162
+ };
163
+ }
164
+ function parseBootstrapGlobalOptions(argv) {
165
+ let pathValue;
166
+ let noExtensions = false;
167
+ let index = 0;
168
+ while (index < argv.length) {
169
+ const token = argv[index];
170
+ if (token === "--") {
171
+ break;
172
+ }
173
+ if (token === "--no-extensions") {
174
+ noExtensions = true;
175
+ index += 1;
176
+ continue;
177
+ }
178
+ const parsedPath = parseBootstrapPathToken(token, argv[index + 1]);
179
+ if (parsedPath) {
180
+ if (parsedPath.pathValue !== undefined) {
181
+ pathValue = parsedPath.pathValue;
182
+ }
183
+ index += parsedPath.consumed;
184
+ continue;
185
+ }
186
+ index += 1;
187
+ }
188
+ return {
189
+ path: pathValue,
190
+ noExtensions,
191
+ };
192
+ }
193
+ let activeExtensionHookContext = null;
194
+ function formatHookWarnings(warnings) {
195
+ return warnings.join(",");
196
+ }
197
+ function toNonEmptyString(value) {
198
+ if (typeof value !== "string") {
199
+ return undefined;
200
+ }
201
+ const trimmed = value.trim();
202
+ return trimmed.length > 0 ? trimmed : undefined;
203
+ }
204
+ function resolveMigrationId(definition, fallbackIndex) {
205
+ const explicit = toNonEmptyString(definition.id);
206
+ if (explicit) {
207
+ return explicit;
208
+ }
209
+ return `migration-${String(fallbackIndex + 1).padStart(3, "0")}`;
210
+ }
211
+ function resolveNormalizedMigrationStatus(definition) {
212
+ const normalized = toNonEmptyString(definition.status)?.toLowerCase();
213
+ return normalized ?? "pending";
214
+ }
215
+ function isMandatoryMigrationDefinition(definition) {
216
+ return definition.mandatory === true;
217
+ }
218
+ function compareMandatoryMigrationBlockers(left, right) {
219
+ const byLayer = left.layer.localeCompare(right.layer);
220
+ if (byLayer !== 0) {
221
+ return byLayer;
222
+ }
223
+ const byName = left.name.localeCompare(right.name);
224
+ if (byName !== 0) {
225
+ return byName;
226
+ }
227
+ return left.id.localeCompare(right.id);
228
+ }
229
+ function collectMandatoryMigrationBlockers(migrations) {
230
+ const blockers = [];
231
+ migrations.forEach((entry, index) => {
232
+ if (!isMandatoryMigrationDefinition(entry.definition)) {
233
+ return;
234
+ }
235
+ const status = resolveNormalizedMigrationStatus(entry.definition);
236
+ if (status === "applied") {
237
+ return;
238
+ }
239
+ blockers.push({
240
+ layer: entry.layer,
241
+ name: entry.name,
242
+ id: resolveMigrationId(entry.definition, index),
243
+ status,
244
+ });
245
+ });
246
+ blockers.sort(compareMandatoryMigrationBlockers);
247
+ return blockers;
248
+ }
249
+ function hasMutatingListValues(value) {
250
+ return Array.isArray(value) && value.length > 0;
251
+ }
252
+ function decideWriteGate(commandPath, options) {
253
+ const forceRequested = options.force === true;
254
+ switch (commandPath) {
255
+ case "create":
256
+ case "beads import":
257
+ case "todos import":
258
+ return {
259
+ isMutation: true,
260
+ forceCapable: false,
261
+ forceRequested: false,
262
+ };
263
+ case "restore":
264
+ case "update":
265
+ case "close":
266
+ case "delete":
267
+ case "append":
268
+ case "claim":
269
+ case "release":
270
+ return {
271
+ isMutation: true,
272
+ forceCapable: true,
273
+ forceRequested,
274
+ };
275
+ case "comments":
276
+ return {
277
+ isMutation: typeof options.add === "string",
278
+ forceCapable: true,
279
+ forceRequested,
280
+ };
281
+ case "files":
282
+ case "docs":
283
+ case "test":
284
+ return {
285
+ isMutation: hasMutatingListValues(options.add) || hasMutatingListValues(options.remove),
286
+ forceCapable: true,
287
+ forceRequested,
288
+ };
289
+ default:
290
+ return {
291
+ isMutation: false,
292
+ forceCapable: false,
293
+ forceRequested: false,
294
+ };
295
+ }
296
+ }
297
+ function enforceMandatoryMigrationWriteGate(commandPath, options, blockers) {
298
+ if (blockers.length === 0) {
299
+ return;
300
+ }
301
+ const decision = decideWriteGate(commandPath, options);
302
+ if (!decision.isMutation) {
303
+ return;
304
+ }
305
+ if (decision.forceCapable && decision.forceRequested) {
306
+ return;
307
+ }
308
+ const codes = blockers.map((entry) => `extension_migration_blocking:${entry.layer}:${entry.name}:${entry.id}:${entry.status}`);
309
+ const forceGuidance = decision.forceCapable
310
+ ? "Re-run this command with --force to bypass."
311
+ : "This command path does not support --force bypass.";
312
+ throw new PmCliError(`Write command "${commandPath}" blocked by unresolved mandatory extension migrations (${codes.join(",")}). ${forceGuidance}`, EXIT_CODE.CONFLICT);
313
+ }
314
+ function describeUnknownError(error) {
315
+ if (error instanceof PmCliError) {
316
+ return error.message;
317
+ }
318
+ if (error instanceof Error) {
319
+ return error.message;
320
+ }
321
+ return "Unknown failure";
322
+ }
323
+ function collectMutationItemIds(result) {
324
+ if (!result || typeof result !== "object") {
325
+ return [];
326
+ }
327
+ const record = result;
328
+ const ids = new Set();
329
+ const pushId = (value) => {
330
+ if (typeof value !== "string") {
331
+ return;
332
+ }
333
+ const normalized = value.trim();
334
+ if (normalized.length === 0) {
335
+ return;
336
+ }
337
+ ids.add(normalized);
338
+ };
339
+ pushId(record.id);
340
+ const item = record.item;
341
+ if (item && typeof item === "object") {
342
+ pushId(item.id);
343
+ }
344
+ const explicitIds = record.ids;
345
+ if (Array.isArray(explicitIds)) {
346
+ for (const candidate of explicitIds) {
347
+ pushId(candidate);
348
+ }
349
+ }
350
+ const items = record.items;
351
+ if (Array.isArray(items)) {
352
+ for (const candidate of items) {
353
+ if (!candidate || typeof candidate !== "object") {
354
+ continue;
355
+ }
356
+ pushId(candidate.id);
357
+ }
358
+ }
359
+ return [...ids].sort((left, right) => left.localeCompare(right));
360
+ }
361
+ async function invalidateSearchCachesForMutation(globalOptions, result) {
362
+ const pmRoot = resolvePmRoot(process.cwd(), globalOptions.path);
363
+ const refreshResult = await refreshSearchArtifactsForMutation(pmRoot, collectMutationItemIds(result));
364
+ if (globalOptions.profile && refreshResult.warnings.length > 0) {
365
+ printError(`profile:search_refresh_warnings=${formatHookWarnings(refreshResult.warnings)}`);
366
+ }
367
+ }
368
+ async function runAndClearAfterCommandHooks(outcome) {
369
+ const runtime = activeExtensionHookContext;
370
+ activeExtensionHookContext = null;
371
+ clearActiveExtensionHooks();
372
+ if (!runtime) {
373
+ return;
374
+ }
375
+ const hookWarnings = await runAfterCommandHooks(runtime.hooks, {
376
+ command: runtime.commandName,
377
+ args: runtime.commandArgs,
378
+ pm_root: runtime.pmRoot,
379
+ ok: outcome.ok,
380
+ error: outcome.error,
381
+ });
382
+ if (runtime.profileEnabled && hookWarnings.length > 0) {
383
+ printError(`profile:extensions hook_warnings=${formatHookWarnings(hookWarnings)}`);
384
+ }
385
+ }
386
+ async function maybeLoadRuntimeExtensions(command) {
387
+ const globalOptions = getGlobalOptions(command);
388
+ if (globalOptions.noExtensions) {
389
+ return null;
390
+ }
391
+ const pmRoot = resolvePmRoot(process.cwd(), globalOptions.path);
392
+ const settingsPath = getSettingsPath(pmRoot);
393
+ if (!(await pathExists(settingsPath))) {
394
+ return null;
395
+ }
396
+ try {
397
+ const settings = await readSettings(pmRoot);
398
+ const loadResult = await loadExtensions({
399
+ pmRoot,
400
+ settings,
401
+ cwd: process.cwd(),
402
+ noExtensions: globalOptions.noExtensions,
403
+ });
404
+ const loadedWithBuiltins = [...getEnabledBuiltInExtensions(settings), ...loadResult.loaded];
405
+ const activationResult = await activateExtensions({
406
+ ...loadResult,
407
+ loaded: loadedWithBuiltins,
408
+ });
409
+ if (globalOptions.profile) {
410
+ printError(`profile:extensions loaded=${loadedWithBuiltins.length} failed=${loadResult.failed.length} warnings=${loadResult.warnings.length} activation_failed=${activationResult.failed.length} hook_counts=before:${activationResult.hook_counts.before_command}|after:${activationResult.hook_counts.after_command}|write:${activationResult.hook_counts.on_write}|read:${activationResult.hook_counts.on_read}|index:${activationResult.hook_counts.on_index} command_overrides=${activationResult.command_override_count} command_handlers=${activationResult.command_handler_count} renderer_overrides=${activationResult.renderer_override_count}`);
411
+ if (activationResult.warnings.length > 0) {
412
+ printError(`profile:extensions activation_warnings=${formatHookWarnings(activationResult.warnings)}`);
413
+ }
414
+ }
415
+ const migrationBlockers = collectMandatoryMigrationBlockers(activationResult.registrations.migrations);
416
+ return {
417
+ hooks: activationResult.hooks,
418
+ commands: activationResult.commands,
419
+ renderers: activationResult.renderers,
420
+ pmRoot,
421
+ migrationBlockers,
422
+ };
423
+ }
424
+ catch (error) {
425
+ if (globalOptions.profile) {
426
+ const message = error instanceof Error ? error.message : String(error);
427
+ printError(`profile:extensions load_error=${message}`);
428
+ }
429
+ return {
430
+ hooks: createEmptyExtensionHookRegistry(),
431
+ commands: createEmptyExtensionCommandRegistry(),
432
+ renderers: createEmptyExtensionRendererRegistry(),
433
+ pmRoot,
434
+ migrationBlockers: [],
435
+ };
436
+ }
437
+ }
438
+ async function runRequiredExtensionCommand(command, options, globalOptions) {
439
+ const commandPath = getCommandPath(command);
440
+ const commandArgs = command.args.map(String);
441
+ const pmRoot = resolvePmRoot(process.cwd(), globalOptions.path);
442
+ setActiveCommandContext({
443
+ command: commandPath,
444
+ args: commandArgs,
445
+ options: { ...options },
446
+ global: { ...globalOptions },
447
+ pm_root: pmRoot,
448
+ });
449
+ const extensionCommandResult = await runActiveCommandHandler({
450
+ command: commandPath,
451
+ args: commandArgs,
452
+ options,
453
+ global: globalOptions,
454
+ pm_root: pmRoot,
455
+ });
456
+ if (globalOptions.profile && extensionCommandResult.warnings.length > 0) {
457
+ printError(`profile:extensions command_handler_warnings=${formatHookWarnings(extensionCommandResult.warnings)}`);
458
+ }
459
+ if (!extensionCommandResult.handled) {
460
+ if (extensionCommandResult.warnings.length > 0) {
461
+ const warningCode = extensionCommandResult.warnings[0];
462
+ throw new PmCliError(`Command "${commandPath}" failed in extension handler (${warningCode}).`, EXIT_CODE.GENERIC_FAILURE);
463
+ }
464
+ throw new PmCliError(`Command "${commandPath}" is provided by extensions and is not currently available.`, EXIT_CODE.NOT_FOUND);
465
+ }
466
+ return extensionCommandResult.result;
467
+ }
468
+ async function registerDynamicExtensionCommandPaths(rootProgram) {
469
+ const bootstrapGlobalOptions = parseBootstrapGlobalOptions(process.argv.slice(2));
470
+ if (bootstrapGlobalOptions.noExtensions) {
471
+ return;
472
+ }
473
+ const pmRoot = resolvePmRoot(process.cwd(), bootstrapGlobalOptions.path);
474
+ const settingsPath = getSettingsPath(pmRoot);
475
+ if (!(await pathExists(settingsPath))) {
476
+ return;
477
+ }
478
+ let settings;
479
+ try {
480
+ settings = await readSettings(pmRoot);
481
+ }
482
+ catch {
483
+ return;
484
+ }
485
+ let commandHandlers;
486
+ let commandFlagHelp;
487
+ try {
488
+ const loadResult = await loadExtensions({
489
+ pmRoot,
490
+ settings,
491
+ cwd: process.cwd(),
492
+ noExtensions: false,
493
+ });
494
+ const loadedWithBuiltins = [...getEnabledBuiltInExtensions(settings), ...loadResult.loaded];
495
+ const activationResult = await activateExtensions({
496
+ ...loadResult,
497
+ loaded: loadedWithBuiltins,
498
+ });
499
+ commandHandlers = [...new Set(activationResult.commands.handlers.map((entry) => normalizeExtensionCommandPath(entry.command)))]
500
+ .filter((entry) => entry.length > 0)
501
+ .sort((left, right) => left.localeCompare(right));
502
+ commandFlagHelp = collectDynamicExtensionFlagHelpByCommand(activationResult.registrations.flags);
503
+ }
504
+ catch {
505
+ return;
506
+ }
507
+ for (const commandPath of commandHandlers) {
508
+ const pathParts = commandPath.split(" ").filter((part) => part.length > 0);
509
+ if (pathParts.length === 0) {
510
+ continue;
511
+ }
512
+ if (findCommandByPath(rootProgram, pathParts)) {
513
+ continue;
514
+ }
515
+ const dynamicCommand = ensureCommandPath(rootProgram, pathParts);
516
+ if (!dynamicCommand) {
517
+ continue;
518
+ }
519
+ const flagHelp = commandFlagHelp.get(commandPath);
520
+ if (flagHelp) {
521
+ dynamicCommand.addHelpText("after", flagHelp);
522
+ }
523
+ dynamicCommand
524
+ .allowUnknownOption(true)
525
+ .allowExcessArguments(true)
526
+ .action(async (_options, command) => {
527
+ const globalOptions = getGlobalOptions(command);
528
+ const startedAt = Date.now();
529
+ const looseOptions = parseLooseCommandOptions(command.args.map(String));
530
+ const result = await runRequiredExtensionCommand(command, looseOptions, globalOptions);
531
+ printResult(result, globalOptions);
532
+ if (globalOptions.profile) {
533
+ printError(`profile:command=${commandPath} took_ms=${Date.now() - startedAt}`);
534
+ }
535
+ });
536
+ }
537
+ }
538
+ function normalizeCreateOptions(commandOptions) {
539
+ const estimatedMinutes = (typeof commandOptions.estimate === "string" ? commandOptions.estimate : undefined) ??
540
+ (typeof commandOptions.estimatedMinutes === "string" ? commandOptions.estimatedMinutes : undefined) ??
541
+ (typeof commandOptions.estimated_minutes === "string" ? commandOptions.estimated_minutes : undefined);
542
+ if (estimatedMinutes === undefined) {
543
+ throw new PmCliError("Missing required option --estimate/--estimated-minutes/--estimated_minutes", EXIT_CODE.USAGE);
544
+ }
545
+ const requiredString = (key, display) => {
546
+ const value = commandOptions[key];
547
+ if (typeof value !== "string") {
548
+ throw new PmCliError(`Missing required option ${display}`, EXIT_CODE.USAGE);
549
+ }
550
+ return value;
551
+ };
552
+ const requiredRepeatable = (key, display) => {
553
+ const value = commandOptions[key];
554
+ if (!Array.isArray(value) || value.length === 0) {
555
+ throw new PmCliError(`Missing required option ${display} (use 'none' for explicit empty)`, EXIT_CODE.USAGE);
556
+ }
557
+ return value;
558
+ };
559
+ return {
560
+ title: requiredString("title", "--title"),
561
+ description: requiredString("description", "--description"),
562
+ type: requiredString("type", "--type"),
563
+ status: requiredString("status", "--status"),
564
+ priority: requiredString("priority", "--priority"),
565
+ tags: requiredString("tags", "--tags"),
566
+ body: requiredString("body", "--body"),
567
+ deadline: requiredString("deadline", "--deadline"),
568
+ estimatedMinutes,
569
+ acceptanceCriteria: (typeof commandOptions.acceptanceCriteria === "string" ? commandOptions.acceptanceCriteria : undefined) ??
570
+ (typeof commandOptions.acceptance_criteria === "string" ? commandOptions.acceptance_criteria : undefined) ??
571
+ requiredString("ac", "--acceptance-criteria/--ac"),
572
+ definitionOfReady: (typeof commandOptions.definitionOfReady === "string" ? commandOptions.definitionOfReady : undefined) ??
573
+ (typeof commandOptions.definition_of_ready === "string" ? commandOptions.definition_of_ready : undefined),
574
+ order: typeof commandOptions.order === "string" ? commandOptions.order : undefined,
575
+ rank: typeof commandOptions.rank === "string" ? commandOptions.rank : undefined,
576
+ goal: typeof commandOptions.goal === "string" ? commandOptions.goal : undefined,
577
+ objective: typeof commandOptions.objective === "string" ? commandOptions.objective : undefined,
578
+ value: typeof commandOptions.value === "string" ? commandOptions.value : undefined,
579
+ impact: typeof commandOptions.impact === "string" ? commandOptions.impact : undefined,
580
+ outcome: typeof commandOptions.outcome === "string" ? commandOptions.outcome : undefined,
581
+ whyNow: (typeof commandOptions.whyNow === "string" ? commandOptions.whyNow : undefined) ??
582
+ (typeof commandOptions.why_now === "string" ? commandOptions.why_now : undefined),
583
+ author: requiredString("author", "--author"),
584
+ message: requiredString("message", "--message"),
585
+ assignee: requiredString("assignee", "--assignee"),
586
+ parent: typeof commandOptions.parent === "string" ? commandOptions.parent : undefined,
587
+ reviewer: typeof commandOptions.reviewer === "string" ? commandOptions.reviewer : undefined,
588
+ risk: typeof commandOptions.risk === "string" ? commandOptions.risk : undefined,
589
+ confidence: typeof commandOptions.confidence === "string" ? commandOptions.confidence : undefined,
590
+ sprint: typeof commandOptions.sprint === "string" ? commandOptions.sprint : undefined,
591
+ release: typeof commandOptions.release === "string" ? commandOptions.release : undefined,
592
+ blockedBy: (typeof commandOptions.blockedBy === "string" ? commandOptions.blockedBy : undefined) ??
593
+ (typeof commandOptions.blocked_by === "string" ? commandOptions.blocked_by : undefined),
594
+ blockedReason: (typeof commandOptions.blockedReason === "string" ? commandOptions.blockedReason : undefined) ??
595
+ (typeof commandOptions.blocked_reason === "string" ? commandOptions.blocked_reason : undefined),
596
+ unblockNote: (typeof commandOptions.unblockNote === "string" ? commandOptions.unblockNote : undefined) ??
597
+ (typeof commandOptions.unblock_note === "string" ? commandOptions.unblock_note : undefined),
598
+ reporter: typeof commandOptions.reporter === "string" ? commandOptions.reporter : undefined,
599
+ severity: typeof commandOptions.severity === "string" ? commandOptions.severity : undefined,
600
+ environment: typeof commandOptions.environment === "string" ? commandOptions.environment : undefined,
601
+ reproSteps: (typeof commandOptions.reproSteps === "string" ? commandOptions.reproSteps : undefined) ??
602
+ (typeof commandOptions.repro_steps === "string" ? commandOptions.repro_steps : undefined),
603
+ resolution: typeof commandOptions.resolution === "string" ? commandOptions.resolution : undefined,
604
+ expectedResult: (typeof commandOptions.expectedResult === "string" ? commandOptions.expectedResult : undefined) ??
605
+ (typeof commandOptions.expected_result === "string" ? commandOptions.expected_result : undefined),
606
+ actualResult: (typeof commandOptions.actualResult === "string" ? commandOptions.actualResult : undefined) ??
607
+ (typeof commandOptions.actual_result === "string" ? commandOptions.actual_result : undefined),
608
+ affectedVersion: (typeof commandOptions.affectedVersion === "string" ? commandOptions.affectedVersion : undefined) ??
609
+ (typeof commandOptions.affected_version === "string" ? commandOptions.affected_version : undefined),
610
+ fixedVersion: (typeof commandOptions.fixedVersion === "string" ? commandOptions.fixedVersion : undefined) ??
611
+ (typeof commandOptions.fixed_version === "string" ? commandOptions.fixed_version : undefined),
612
+ component: typeof commandOptions.component === "string" ? commandOptions.component : undefined,
613
+ regression: typeof commandOptions.regression === "string" ? commandOptions.regression : undefined,
614
+ customerImpact: (typeof commandOptions.customerImpact === "string" ? commandOptions.customerImpact : undefined) ??
615
+ (typeof commandOptions.customer_impact === "string" ? commandOptions.customer_impact : undefined),
616
+ dep: requiredRepeatable("dep", "--dep"),
617
+ comment: requiredRepeatable("comment", "--comment"),
618
+ note: requiredRepeatable("note", "--note"),
619
+ learning: requiredRepeatable("learning", "--learning"),
620
+ file: requiredRepeatable("file", "--file"),
621
+ test: requiredRepeatable("test", "--test"),
622
+ doc: requiredRepeatable("doc", "--doc"),
623
+ };
624
+ }
625
+ function normalizeUpdateOptions(commandOptions) {
626
+ const estimatedMinutes = (typeof commandOptions.estimate === "string" ? commandOptions.estimate : undefined) ??
627
+ (typeof commandOptions.estimatedMinutes === "string" ? commandOptions.estimatedMinutes : undefined) ??
628
+ (typeof commandOptions.estimated_minutes === "string" ? commandOptions.estimated_minutes : undefined);
629
+ return {
630
+ description: typeof commandOptions.description === "string" ? commandOptions.description : undefined,
631
+ status: typeof commandOptions.status === "string" ? commandOptions.status : undefined,
632
+ priority: typeof commandOptions.priority === "string" ? commandOptions.priority : undefined,
633
+ type: typeof commandOptions.type === "string" ? commandOptions.type : undefined,
634
+ tags: typeof commandOptions.tags === "string" ? commandOptions.tags : undefined,
635
+ deadline: typeof commandOptions.deadline === "string" ? commandOptions.deadline : undefined,
636
+ estimatedMinutes,
637
+ acceptanceCriteria: (typeof commandOptions.acceptanceCriteria === "string" ? commandOptions.acceptanceCriteria : undefined) ??
638
+ (typeof commandOptions.acceptance_criteria === "string" ? commandOptions.acceptance_criteria : undefined) ??
639
+ (typeof commandOptions.ac === "string" ? commandOptions.ac : undefined),
640
+ definitionOfReady: (typeof commandOptions.definitionOfReady === "string" ? commandOptions.definitionOfReady : undefined) ??
641
+ (typeof commandOptions.definition_of_ready === "string" ? commandOptions.definition_of_ready : undefined),
642
+ order: typeof commandOptions.order === "string" ? commandOptions.order : undefined,
643
+ rank: typeof commandOptions.rank === "string" ? commandOptions.rank : undefined,
644
+ goal: typeof commandOptions.goal === "string" ? commandOptions.goal : undefined,
645
+ objective: typeof commandOptions.objective === "string" ? commandOptions.objective : undefined,
646
+ value: typeof commandOptions.value === "string" ? commandOptions.value : undefined,
647
+ impact: typeof commandOptions.impact === "string" ? commandOptions.impact : undefined,
648
+ outcome: typeof commandOptions.outcome === "string" ? commandOptions.outcome : undefined,
649
+ whyNow: (typeof commandOptions.whyNow === "string" ? commandOptions.whyNow : undefined) ??
650
+ (typeof commandOptions.why_now === "string" ? commandOptions.why_now : undefined),
651
+ author: typeof commandOptions.author === "string" ? commandOptions.author : undefined,
652
+ message: typeof commandOptions.message === "string" ? commandOptions.message : undefined,
653
+ force: Boolean(commandOptions.force),
654
+ assignee: typeof commandOptions.assignee === "string" ? commandOptions.assignee : undefined,
655
+ parent: typeof commandOptions.parent === "string" ? commandOptions.parent : undefined,
656
+ reviewer: typeof commandOptions.reviewer === "string" ? commandOptions.reviewer : undefined,
657
+ risk: typeof commandOptions.risk === "string" ? commandOptions.risk : undefined,
658
+ confidence: typeof commandOptions.confidence === "string" ? commandOptions.confidence : undefined,
659
+ sprint: typeof commandOptions.sprint === "string" ? commandOptions.sprint : undefined,
660
+ release: typeof commandOptions.release === "string" ? commandOptions.release : undefined,
661
+ blockedBy: (typeof commandOptions.blockedBy === "string" ? commandOptions.blockedBy : undefined) ??
662
+ (typeof commandOptions.blocked_by === "string" ? commandOptions.blocked_by : undefined),
663
+ blockedReason: (typeof commandOptions.blockedReason === "string" ? commandOptions.blockedReason : undefined) ??
664
+ (typeof commandOptions.blocked_reason === "string" ? commandOptions.blocked_reason : undefined),
665
+ unblockNote: (typeof commandOptions.unblockNote === "string" ? commandOptions.unblockNote : undefined) ??
666
+ (typeof commandOptions.unblock_note === "string" ? commandOptions.unblock_note : undefined),
667
+ reporter: typeof commandOptions.reporter === "string" ? commandOptions.reporter : undefined,
668
+ severity: typeof commandOptions.severity === "string" ? commandOptions.severity : undefined,
669
+ environment: typeof commandOptions.environment === "string" ? commandOptions.environment : undefined,
670
+ reproSteps: (typeof commandOptions.reproSteps === "string" ? commandOptions.reproSteps : undefined) ??
671
+ (typeof commandOptions.repro_steps === "string" ? commandOptions.repro_steps : undefined),
672
+ resolution: typeof commandOptions.resolution === "string" ? commandOptions.resolution : undefined,
673
+ expectedResult: (typeof commandOptions.expectedResult === "string" ? commandOptions.expectedResult : undefined) ??
674
+ (typeof commandOptions.expected_result === "string" ? commandOptions.expected_result : undefined),
675
+ actualResult: (typeof commandOptions.actualResult === "string" ? commandOptions.actualResult : undefined) ??
676
+ (typeof commandOptions.actual_result === "string" ? commandOptions.actual_result : undefined),
677
+ affectedVersion: (typeof commandOptions.affectedVersion === "string" ? commandOptions.affectedVersion : undefined) ??
678
+ (typeof commandOptions.affected_version === "string" ? commandOptions.affected_version : undefined),
679
+ fixedVersion: (typeof commandOptions.fixedVersion === "string" ? commandOptions.fixedVersion : undefined) ??
680
+ (typeof commandOptions.fixed_version === "string" ? commandOptions.fixed_version : undefined),
681
+ component: typeof commandOptions.component === "string" ? commandOptions.component : undefined,
682
+ regression: typeof commandOptions.regression === "string" ? commandOptions.regression : undefined,
683
+ customerImpact: (typeof commandOptions.customerImpact === "string" ? commandOptions.customerImpact : undefined) ??
684
+ (typeof commandOptions.customer_impact === "string" ? commandOptions.customer_impact : undefined),
685
+ };
686
+ }
687
+ function normalizeListOptions(options) {
688
+ return {
689
+ type: typeof options.type === "string" ? options.type : undefined,
690
+ tag: typeof options.tag === "string" ? options.tag : undefined,
691
+ priority: typeof options.priority === "string" ? options.priority : undefined,
692
+ deadlineBefore: typeof options.deadlineBefore === "string" ? options.deadlineBefore : undefined,
693
+ deadlineAfter: typeof options.deadlineAfter === "string" ? options.deadlineAfter : undefined,
694
+ assignee: typeof options.assignee === "string" ? options.assignee : undefined,
695
+ sprint: typeof options.sprint === "string" ? options.sprint : undefined,
696
+ release: typeof options.release === "string" ? options.release : undefined,
697
+ limit: typeof options.limit === "string" ? options.limit : undefined,
698
+ };
699
+ }
700
+ function normalizeSearchOptions(options) {
701
+ return {
702
+ mode: typeof options.mode === "string" ? options.mode : undefined,
703
+ includeLinked: options.includeLinked === true ? true : undefined,
704
+ type: typeof options.type === "string" ? options.type : undefined,
705
+ tag: typeof options.tag === "string" ? options.tag : undefined,
706
+ priority: typeof options.priority === "string" ? options.priority : undefined,
707
+ deadlineBefore: typeof options.deadlineBefore === "string" ? options.deadlineBefore : undefined,
708
+ deadlineAfter: typeof options.deadlineAfter === "string" ? options.deadlineAfter : undefined,
709
+ limit: typeof options.limit === "string" ? options.limit : undefined,
710
+ };
711
+ }
712
+ function resolveCliVersion() {
713
+ try {
714
+ const currentFilePath = fileURLToPath(import.meta.url);
715
+ const packageJsonPath = path.resolve(path.dirname(currentFilePath), "../../package.json");
716
+ const raw = fs.readFileSync(packageJsonPath, "utf8");
717
+ const parsed = JSON.parse(raw);
718
+ return typeof parsed.version === "string" ? parsed.version : "0.0.0";
719
+ }
720
+ catch {
721
+ return "0.0.0";
722
+ }
723
+ }
724
+ const program = new Command();
725
+ program
726
+ .name("pm")
727
+ .description("Agent-friendly, git-native project management CLI.")
728
+ .version(resolveCliVersion())
729
+ .showHelpAfterError()
730
+ .allowExcessArguments(false)
731
+ .allowUnknownOption(false)
732
+ .option("--json", "Output JSON instead of TOON")
733
+ .option("--quiet", "Suppress stdout output")
734
+ .option("--path <dir>", "Override PM path for this command")
735
+ .option("--no-extensions", "Disable extension loading")
736
+ .option("--profile", "Print deterministic timing diagnostics")
737
+ .exitOverride();
738
+ program.hook("preAction", async (_thisCommand, actionCommand) => {
739
+ activeExtensionHookContext = null;
740
+ clearActiveExtensionHooks();
741
+ const runtimeExtensions = await maybeLoadRuntimeExtensions(actionCommand);
742
+ if (!runtimeExtensions) {
743
+ return;
744
+ }
745
+ const globalOptions = getGlobalOptions(actionCommand);
746
+ const commandPath = getCommandPath(actionCommand);
747
+ const commandArgs = actionCommand.args.map(String);
748
+ activeExtensionHookContext = {
749
+ hooks: runtimeExtensions.hooks,
750
+ commandName: commandPath,
751
+ commandArgs,
752
+ pmRoot: runtimeExtensions.pmRoot,
753
+ profileEnabled: Boolean(globalOptions.profile),
754
+ migrationBlockers: runtimeExtensions.migrationBlockers,
755
+ };
756
+ setActiveExtensionHooks(runtimeExtensions.hooks);
757
+ setActiveExtensionCommands(runtimeExtensions.commands);
758
+ setActiveExtensionRenderers(runtimeExtensions.renderers);
759
+ const commandOptions = actionCommand.optsWithGlobals();
760
+ setActiveCommandContext({
761
+ command: commandPath,
762
+ args: commandArgs,
763
+ options: { ...commandOptions },
764
+ global: { ...globalOptions },
765
+ pm_root: runtimeExtensions.pmRoot,
766
+ });
767
+ const hookWarnings = await runBeforeCommandHooks(runtimeExtensions.hooks, {
768
+ command: commandPath,
769
+ args: commandArgs,
770
+ pm_root: runtimeExtensions.pmRoot,
771
+ });
772
+ if (globalOptions.profile && hookWarnings.length > 0) {
773
+ printError(`profile:extensions hook_warnings=${formatHookWarnings(hookWarnings)}`);
774
+ }
775
+ enforceMandatoryMigrationWriteGate(commandPath, actionCommand.optsWithGlobals(), runtimeExtensions.migrationBlockers);
776
+ });
777
+ program.hook("postAction", async () => {
778
+ await runAndClearAfterCommandHooks({ ok: true });
779
+ });
780
+ program
781
+ .command("init")
782
+ .argument("[prefix]", "Optional id prefix")
783
+ .description("Initialize .agents/pm storage and settings.")
784
+ .action(async (prefix, _options, command) => {
785
+ const globalOptions = getGlobalOptions(command);
786
+ const startedAt = Date.now();
787
+ const result = await runInit(prefix, globalOptions);
788
+ printResult(result, globalOptions);
789
+ if (globalOptions.profile) {
790
+ printError(`profile:command=init took_ms=${Date.now() - startedAt}`);
791
+ }
792
+ });
793
+ program
794
+ .command("config")
795
+ .argument("<scope>", "Config scope: project|global")
796
+ .argument("<action>", "Config action: get|set")
797
+ .argument("<key>", "Config key: definition-of-done")
798
+ .option("--criterion <text>", "Definition-of-Done criterion (repeatable for set)", collect)
799
+ .description("Read or update deterministic pm settings.")
800
+ .action(async (scope, action, key, options, command) => {
801
+ const globalOptions = getGlobalOptions(command);
802
+ const startedAt = Date.now();
803
+ const criteria = Array.isArray(options.criterion) ? options.criterion : [];
804
+ const result = await runConfig(scope, action, key, {
805
+ criterion: criteria,
806
+ }, globalOptions);
807
+ printResult(result, globalOptions);
808
+ if (globalOptions.profile) {
809
+ printError(`profile:command=config took_ms=${Date.now() - startedAt}`);
810
+ }
811
+ });
812
+ program
813
+ .command("install")
814
+ .argument("<target>", "Install target: pi")
815
+ .option("--project", "Install Pi extension into resolved project root .pi/extensions (derived from --path, default)")
816
+ .option("--global", "Install Pi extension into global PI_CODING_AGENT_DIR or ~/.pi/agent")
817
+ .description("Install supported integrations (currently: pi).")
818
+ .action(async (target, options, command) => {
819
+ const globalOptions = getGlobalOptions(command);
820
+ const startedAt = Date.now();
821
+ const result = await runInstall(target, {
822
+ project: options.project === true,
823
+ global: options.global === true,
824
+ }, globalOptions);
825
+ printResult(result, globalOptions);
826
+ if (globalOptions.profile) {
827
+ printError(`profile:command=install took_ms=${Date.now() - startedAt}`);
828
+ }
829
+ });
830
+ program
831
+ .command("create")
832
+ .description("Create a new item with deterministic front matter and history entry.")
833
+ .requiredOption("--title, -t <value>", "Item title")
834
+ .requiredOption("--description, -d <value>", "Item description (allow empty string)")
835
+ .requiredOption("--type <value>", "Item type: Epic|Feature|Task|Chore|Issue")
836
+ .requiredOption("--status, -s <value>", "Item status")
837
+ .requiredOption("--priority, -p <value>", "Priority 0..4")
838
+ .requiredOption("--tags <value>", "Comma-separated tags, or 'none'")
839
+ .requiredOption("--body, -b <value>", "Item markdown body (allow empty string)")
840
+ .requiredOption("--deadline <value>", "ISO deadline, relative +6h/+1d/+2w, or none")
841
+ .option("--estimate, --estimated-minutes <value>", "Estimated minutes, or none")
842
+ .option("--estimated_minutes <value>", "Alias for --estimated-minutes")
843
+ .option("--acceptance-criteria <value>", "Acceptance criteria (allow empty string)")
844
+ .option("--acceptance_criteria <value>", "Alias for --acceptance-criteria")
845
+ .option("--ac <value>", "Alias for --acceptance-criteria")
846
+ .option("--definition-of-ready <value>", "Definition of ready (allow empty string, or none)")
847
+ .option("--definition_of_ready <value>", "Alias for --definition-of-ready")
848
+ .option("--order <value>", "Planning order/rank integer, or none")
849
+ .option("--rank <value>", "Alias for --order")
850
+ .option("--goal <value>", "Goal identifier, or none")
851
+ .option("--objective <value>", "Objective identifier, or none")
852
+ .option("--value <value>", "Business value summary, or none")
853
+ .option("--impact <value>", "Business impact summary, or none")
854
+ .option("--outcome <value>", "Expected outcome summary, or none")
855
+ .option("--why-now <value>", "Why-now rationale, or none")
856
+ .option("--why_now <value>", "Alias for --why-now")
857
+ .requiredOption("--author <value>", "Mutation author, or none")
858
+ .requiredOption("--message <value>", "History message (allow empty string)")
859
+ .requiredOption("--assignee <value>", "Item assignee, or none")
860
+ .option("--parent <value>", "Parent item ID, or none")
861
+ .option("--reviewer <value>", "Reviewer, or none")
862
+ .option("--risk <value>", "Risk level: low|med|medium|high|critical, or none (med persists as medium)")
863
+ .option("--confidence <value>", "Confidence level: 0..100|low|med|medium|high, or none (med persists as medium)")
864
+ .option("--sprint <value>", "Sprint identifier, or none")
865
+ .option("--release <value>", "Release identifier, or none")
866
+ .option("--blocked-by <value>", "Blocked-by item ID or reason, or none")
867
+ .option("--blocked_by <value>", "Alias for --blocked-by")
868
+ .option("--blocked-reason <value>", "Blocked reason, or none")
869
+ .option("--blocked_reason <value>", "Alias for --blocked-reason")
870
+ .option("--unblock-note <value>", "Unblock rationale note, or none")
871
+ .option("--unblock_note <value>", "Alias for --unblock-note")
872
+ .option("--reporter <value>", "Issue reporter, or none")
873
+ .option("--severity <value>", "Issue severity: low|med|medium|high|critical, or none (med persists as medium)")
874
+ .option("--environment <value>", "Issue environment context, or none")
875
+ .option("--repro-steps <value>", "Issue reproduction steps, or none")
876
+ .option("--repro_steps <value>", "Alias for --repro-steps")
877
+ .option("--resolution <value>", "Issue resolution summary, or none")
878
+ .option("--expected-result <value>", "Issue expected behavior, or none")
879
+ .option("--expected_result <value>", "Alias for --expected-result")
880
+ .option("--actual-result <value>", "Issue observed behavior, or none")
881
+ .option("--actual_result <value>", "Alias for --actual-result")
882
+ .option("--affected-version <value>", "Affected version identifier, or none")
883
+ .option("--affected_version <value>", "Alias for --affected-version")
884
+ .option("--fixed-version <value>", "Fixed version identifier, or none")
885
+ .option("--fixed_version <value>", "Alias for --fixed-version")
886
+ .option("--component <value>", "Issue component ownership, or none")
887
+ .option("--regression <value>", "Regression marker: true|false|1|0, or none")
888
+ .option("--customer-impact <value>", "Customer impact summary, or none")
889
+ .option("--customer_impact <value>", "Alias for --customer-impact")
890
+ .option("--dep <value>", "Seed dependency entry (required; use none for empty)", collect)
891
+ .option("--comment <value>", "Seed comment entry (required; use none for empty)", collect)
892
+ .option("--note <value>", "Seed note entry (required; use none for empty)", collect)
893
+ .option("--learning <value>", "Seed learning entry (required; use none for empty)", collect)
894
+ .option("--file <value>", "Seed linked file entry (required; use none for empty)", collect)
895
+ .option("--test <value>", "Seed linked test entry (required; use none for empty)", collect)
896
+ .option("--doc <value>", "Seed linked doc entry (required; use none for empty)", collect)
897
+ .action(async (options, command) => {
898
+ const globalOptions = getGlobalOptions(command);
899
+ const startedAt = Date.now();
900
+ const normalized = normalizeCreateOptions(options);
901
+ const result = await runCreate(normalized, globalOptions);
902
+ await invalidateSearchCachesForMutation(globalOptions, result);
903
+ printResult(result, globalOptions);
904
+ if (globalOptions.profile) {
905
+ printError(`profile:command=create took_ms=${Date.now() - startedAt}`);
906
+ }
907
+ });
908
+ function registerListCommand(name, description, status, excludeTerminal) {
909
+ program
910
+ .command(name)
911
+ .description(description)
912
+ .option("--type <value>", "Filter by item type")
913
+ .option("--tag <value>", "Filter by tag")
914
+ .option("--priority <value>", "Filter by priority")
915
+ .option("--deadline-before <value>", "Filter by deadline upper bound")
916
+ .option("--deadline-after <value>", "Filter by deadline lower bound")
917
+ .option("--assignee <value>", "Filter by assignee (use 'none' for unassigned)")
918
+ .option("--sprint <value>", "Filter by sprint")
919
+ .option("--release <value>", "Filter by release")
920
+ .option("--limit <n>", "Limit returned item count")
921
+ .action(async (options, command) => {
922
+ const globalOptions = getGlobalOptions(command);
923
+ const startedAt = Date.now();
924
+ const listOptions = normalizeListOptions(options);
925
+ if (excludeTerminal)
926
+ listOptions.excludeTerminal = true;
927
+ const result = await runList(status, listOptions, globalOptions);
928
+ printResult(result, globalOptions);
929
+ if (globalOptions.profile) {
930
+ printError(`profile:command=${name} took_ms=${Date.now() - startedAt}`);
931
+ }
932
+ });
933
+ }
934
+ registerListCommand("list", "List active items (excludes closed/canceled) with optional filters.", undefined, true);
935
+ registerListCommand("list-all", "List all items with optional filters.");
936
+ registerListCommand("list-draft", "List draft items with optional filters.", "draft");
937
+ registerListCommand("list-open", "List open items with optional filters.", "open");
938
+ registerListCommand("list-in-progress", "List in-progress items with optional filters.", "in_progress");
939
+ registerListCommand("list-blocked", "List blocked items with optional filters.", "blocked");
940
+ registerListCommand("list-closed", "List closed items with optional filters.", "closed");
941
+ registerListCommand("list-canceled", "List canceled items with optional filters.", "canceled");
942
+ program
943
+ .command("beads")
944
+ .description("Built-in Beads extension commands.")
945
+ .command("import")
946
+ .description("Import Beads JSONL records as PM items.")
947
+ .option("--file <path>", "Path to Beads JSONL file", ".beads/issues.jsonl")
948
+ .option("--author <value>", "Mutation author")
949
+ .option("--message <value>", "History message for import entries")
950
+ .action(async (options, command) => {
951
+ const globalOptions = getGlobalOptions(command);
952
+ const startedAt = Date.now();
953
+ const result = await runRequiredExtensionCommand(command, options, globalOptions);
954
+ await invalidateSearchCachesForMutation(globalOptions, result);
955
+ printResult(result, globalOptions);
956
+ if (globalOptions.profile) {
957
+ printError(`profile:command=beads import took_ms=${Date.now() - startedAt}`);
958
+ }
959
+ });
960
+ const todosCommand = program.command("todos").description("Built-in todos extension commands.");
961
+ todosCommand
962
+ .command("import")
963
+ .description("Import todos markdown files as PM items.")
964
+ .option("--folder <path>", "Path to todos markdown folder", ".pi/todos")
965
+ .option("--author <value>", "Mutation author")
966
+ .option("--message <value>", "History message for import entries")
967
+ .action(async (options, command) => {
968
+ const globalOptions = getGlobalOptions(command);
969
+ const startedAt = Date.now();
970
+ const result = await runRequiredExtensionCommand(command, options, globalOptions);
971
+ await invalidateSearchCachesForMutation(globalOptions, result);
972
+ printResult(result, globalOptions);
973
+ if (globalOptions.profile) {
974
+ printError(`profile:command=todos import took_ms=${Date.now() - startedAt}`);
975
+ }
976
+ });
977
+ todosCommand
978
+ .command("export")
979
+ .description("Export PM items to todos markdown files.")
980
+ .option("--folder <path>", "Path to todos markdown folder", ".pi/todos")
981
+ .action(async (options, command) => {
982
+ const globalOptions = getGlobalOptions(command);
983
+ const startedAt = Date.now();
984
+ const result = await runRequiredExtensionCommand(command, options, globalOptions);
985
+ printResult(result, globalOptions);
986
+ if (globalOptions.profile) {
987
+ printError(`profile:command=todos export took_ms=${Date.now() - startedAt}`);
988
+ }
989
+ });
990
+ program
991
+ .command("search")
992
+ .argument("<keywords>", "Keyword query string")
993
+ .description("Search items across keyword and optional semantic/hybrid modes.")
994
+ .option("--mode <value>", "Search mode: keyword|semantic|hybrid (default: hybrid when semantic config is available, else keyword)")
995
+ .option("--include-linked", "Include readable linked docs/files/tests content in keyword and hybrid lexical scoring")
996
+ .option("--type <value>", "Filter by item type")
997
+ .option("--tag <value>", "Filter by tag")
998
+ .option("--priority <value>", "Filter by priority")
999
+ .option("--deadline-before <value>", "Filter by deadline upper bound")
1000
+ .option("--deadline-after <value>", "Filter by deadline lower bound")
1001
+ .option("--limit <n>", "Limit returned item count")
1002
+ .action(async (keywords, options, command) => {
1003
+ const globalOptions = getGlobalOptions(command);
1004
+ const startedAt = Date.now();
1005
+ const result = await runSearch(keywords, normalizeSearchOptions(options), globalOptions);
1006
+ printResult(result, globalOptions);
1007
+ if (globalOptions.profile) {
1008
+ printError(`profile:command=search took_ms=${Date.now() - startedAt}`);
1009
+ }
1010
+ });
1011
+ program
1012
+ .command("reindex")
1013
+ .description("Rebuild deterministic search artifacts for keyword, semantic, and hybrid modes.")
1014
+ .option("--mode <value>", "Reindex mode: keyword|semantic|hybrid", "keyword")
1015
+ .action(async (options, command) => {
1016
+ const globalOptions = getGlobalOptions(command);
1017
+ const startedAt = Date.now();
1018
+ const result = await runReindex({
1019
+ mode: typeof options.mode === "string" ? options.mode : undefined,
1020
+ }, globalOptions);
1021
+ printResult(result, globalOptions);
1022
+ if (globalOptions.profile) {
1023
+ printError(`profile:command=reindex took_ms=${Date.now() - startedAt}`);
1024
+ }
1025
+ });
1026
+ program
1027
+ .command("get")
1028
+ .argument("<id>", "Item id")
1029
+ .description("Get item details by id.")
1030
+ .action(async (id, _options, command) => {
1031
+ const globalOptions = getGlobalOptions(command);
1032
+ const startedAt = Date.now();
1033
+ const result = await runGet(id, globalOptions);
1034
+ printResult(result, globalOptions);
1035
+ if (globalOptions.profile) {
1036
+ printError(`profile:command=get took_ms=${Date.now() - startedAt}`);
1037
+ }
1038
+ });
1039
+ program
1040
+ .command("history")
1041
+ .argument("<id>", "Item id")
1042
+ .option("--limit <n>", "Return only the latest n history entries")
1043
+ .description("Show append-only history entries for an item.")
1044
+ .action(async (id, options, command) => {
1045
+ const globalOptions = getGlobalOptions(command);
1046
+ const startedAt = Date.now();
1047
+ const result = await runHistory(id, {
1048
+ limit: typeof options.limit === "string" ? options.limit : undefined,
1049
+ }, globalOptions);
1050
+ printResult(result, globalOptions);
1051
+ if (globalOptions.profile) {
1052
+ printError(`profile:command=history took_ms=${Date.now() - startedAt}`);
1053
+ }
1054
+ });
1055
+ program
1056
+ .command("activity")
1057
+ .option("--limit <n>", "Return only the latest n activity entries")
1058
+ .description("Show recent activity across all item history streams.")
1059
+ .action(async (options, command) => {
1060
+ const globalOptions = getGlobalOptions(command);
1061
+ const startedAt = Date.now();
1062
+ const result = await runActivity({
1063
+ limit: typeof options.limit === "string" ? options.limit : undefined,
1064
+ }, globalOptions);
1065
+ printResult(result, globalOptions);
1066
+ if (globalOptions.profile) {
1067
+ printError(`profile:command=activity took_ms=${Date.now() - startedAt}`);
1068
+ }
1069
+ });
1070
+ program
1071
+ .command("restore")
1072
+ .argument("<id>", "Item id")
1073
+ .argument("<target>", "Restore target timestamp or version number")
1074
+ .option("--author <value>", "Mutation author")
1075
+ .option("--message <value>", "History message")
1076
+ .option("--force", "Force ownership/lock override")
1077
+ .description("Restore an item to a previous timestamp or version.")
1078
+ .action(async (id, target, options, command) => {
1079
+ const globalOptions = getGlobalOptions(command);
1080
+ const startedAt = Date.now();
1081
+ const result = await runRestore(id, target, {
1082
+ author: typeof options.author === "string" ? options.author : undefined,
1083
+ message: typeof options.message === "string" ? options.message : undefined,
1084
+ force: Boolean(options.force),
1085
+ }, globalOptions);
1086
+ await invalidateSearchCachesForMutation(globalOptions, result);
1087
+ printResult(result, globalOptions);
1088
+ if (globalOptions.profile) {
1089
+ printError(`profile:command=restore took_ms=${Date.now() - startedAt}`);
1090
+ }
1091
+ });
1092
+ program
1093
+ .command("update")
1094
+ .argument("<id>", "Item id")
1095
+ .description("Update item front-matter fields.")
1096
+ .option("--title, -t <value>", "Set title")
1097
+ .option("--description, -d <value>", "Set description")
1098
+ .option("--status, -s <value>", "Set status (use close command for closed)")
1099
+ .option("--priority, -p <value>", "Set priority")
1100
+ .option("--type <value>", "Set type")
1101
+ .option("--tags <value>", "Set comma-separated tags")
1102
+ .option("--deadline <value>", "Set deadline (or none)")
1103
+ .option("--estimate, --estimated-minutes <value>", "Set estimated minutes (or none)")
1104
+ .option("--estimated_minutes <value>", "Alias for --estimated-minutes")
1105
+ .option("--acceptance-criteria <value>", "Set acceptance criteria (or none)")
1106
+ .option("--acceptance_criteria <value>", "Alias for --acceptance-criteria")
1107
+ .option("--ac <value>", "Alias for --acceptance-criteria")
1108
+ .option("--definition-of-ready <value>", "Set definition of ready (or none)")
1109
+ .option("--definition_of_ready <value>", "Alias for --definition-of-ready")
1110
+ .option("--order <value>", "Set planning order/rank integer (or none)")
1111
+ .option("--rank <value>", "Alias for --order")
1112
+ .option("--goal <value>", "Set goal identifier (or none)")
1113
+ .option("--objective <value>", "Set objective identifier (or none)")
1114
+ .option("--value <value>", "Set business value summary (or none)")
1115
+ .option("--impact <value>", "Set business impact summary (or none)")
1116
+ .option("--outcome <value>", "Set expected outcome summary (or none)")
1117
+ .option("--why-now <value>", "Set why-now rationale (or none)")
1118
+ .option("--why_now <value>", "Alias for --why-now")
1119
+ .option("--author <value>", "Mutation author")
1120
+ .option("--message <value>", "Mutation message")
1121
+ .option("--assignee <value>", "Set assignee (or none)")
1122
+ .option("--parent <value>", "Set parent item ID (or none)")
1123
+ .option("--reviewer <value>", "Set reviewer (or none)")
1124
+ .option("--risk <value>", "Set risk level: low|med|medium|high|critical (or none; med persists as medium)")
1125
+ .option("--confidence <value>", "Set confidence level: 0..100|low|med|medium|high (or none; med persists as medium)")
1126
+ .option("--sprint <value>", "Set sprint identifier (or none)")
1127
+ .option("--release <value>", "Set release identifier (or none)")
1128
+ .option("--blocked-by <value>", "Set blocked-by item ID or reason (or none)")
1129
+ .option("--blocked_by <value>", "Alias for --blocked-by")
1130
+ .option("--blocked-reason <value>", "Set blocked reason (or none)")
1131
+ .option("--blocked_reason <value>", "Alias for --blocked-reason")
1132
+ .option("--unblock-note <value>", "Set unblock rationale note (or none)")
1133
+ .option("--unblock_note <value>", "Alias for --unblock-note")
1134
+ .option("--reporter <value>", "Set issue reporter (or none)")
1135
+ .option("--severity <value>", "Set issue severity: low|med|medium|high|critical (or none; med persists as medium)")
1136
+ .option("--environment <value>", "Set issue environment context (or none)")
1137
+ .option("--repro-steps <value>", "Set issue reproduction steps (or none)")
1138
+ .option("--repro_steps <value>", "Alias for --repro-steps")
1139
+ .option("--resolution <value>", "Set issue resolution summary (or none)")
1140
+ .option("--expected-result <value>", "Set issue expected behavior (or none)")
1141
+ .option("--expected_result <value>", "Alias for --expected-result")
1142
+ .option("--actual-result <value>", "Set issue observed behavior (or none)")
1143
+ .option("--actual_result <value>", "Alias for --actual-result")
1144
+ .option("--affected-version <value>", "Set affected version identifier (or none)")
1145
+ .option("--affected_version <value>", "Alias for --affected-version")
1146
+ .option("--fixed-version <value>", "Set fixed version identifier (or none)")
1147
+ .option("--fixed_version <value>", "Alias for --fixed-version")
1148
+ .option("--component <value>", "Set issue component ownership (or none)")
1149
+ .option("--regression <value>", "Set regression marker: true|false|1|0 (or none)")
1150
+ .option("--customer-impact <value>", "Set customer impact summary (or none)")
1151
+ .option("--customer_impact <value>", "Alias for --customer-impact")
1152
+ .option("--force", "Force ownership override")
1153
+ .action(async (id, options, command) => {
1154
+ const globalOptions = getGlobalOptions(command);
1155
+ const startedAt = Date.now();
1156
+ const result = await runUpdate(id, normalizeUpdateOptions(options), globalOptions);
1157
+ await invalidateSearchCachesForMutation(globalOptions, result);
1158
+ printResult(result, globalOptions);
1159
+ if (globalOptions.profile) {
1160
+ printError(`profile:command=update took_ms=${Date.now() - startedAt}`);
1161
+ }
1162
+ });
1163
+ program
1164
+ .command("close")
1165
+ .argument("<id>", "Item id")
1166
+ .argument("<text>", "Close reason text")
1167
+ .option("--author <value>", "Mutation author")
1168
+ .option("--message <value>", "History message")
1169
+ .option("--force", "Force ownership override")
1170
+ .description("Close an item with required reason text.")
1171
+ .action(async (id, text, options, command) => {
1172
+ const globalOptions = getGlobalOptions(command);
1173
+ const startedAt = Date.now();
1174
+ const result = await runClose(id, text, {
1175
+ author: typeof options.author === "string" ? options.author : undefined,
1176
+ message: typeof options.message === "string" ? options.message : undefined,
1177
+ force: Boolean(options.force),
1178
+ }, globalOptions);
1179
+ await invalidateSearchCachesForMutation(globalOptions, result);
1180
+ printResult(result, globalOptions);
1181
+ if (globalOptions.profile) {
1182
+ printError(`profile:command=close took_ms=${Date.now() - startedAt}`);
1183
+ }
1184
+ });
1185
+ program
1186
+ .command("delete")
1187
+ .argument("<id>", "Item id")
1188
+ .option("--author <value>", "Mutation author")
1189
+ .option("--message <value>", "History message")
1190
+ .option("--force", "Force ownership override")
1191
+ .description("Delete an item and append a delete history entry.")
1192
+ .action(async (id, options, command) => {
1193
+ const globalOptions = getGlobalOptions(command);
1194
+ const startedAt = Date.now();
1195
+ const result = await runDelete(id, {
1196
+ author: typeof options.author === "string" ? options.author : undefined,
1197
+ message: typeof options.message === "string" ? options.message : undefined,
1198
+ force: Boolean(options.force),
1199
+ }, globalOptions);
1200
+ await invalidateSearchCachesForMutation(globalOptions, result);
1201
+ printResult(result, globalOptions);
1202
+ if (globalOptions.profile) {
1203
+ printError(`profile:command=delete took_ms=${Date.now() - startedAt}`);
1204
+ }
1205
+ });
1206
+ program
1207
+ .command("append")
1208
+ .argument("<id>", "Item id")
1209
+ .requiredOption("--body <value>", "Text to append to body")
1210
+ .option("--author <value>", "Mutation author")
1211
+ .option("--message <value>", "Mutation message")
1212
+ .option("--force", "Force ownership override")
1213
+ .description("Append text to an item body.")
1214
+ .action(async (id, options, command) => {
1215
+ const globalOptions = getGlobalOptions(command);
1216
+ const startedAt = Date.now();
1217
+ const result = await runAppend(id, {
1218
+ body: typeof options.body === "string" ? options.body : "",
1219
+ author: typeof options.author === "string" ? options.author : undefined,
1220
+ message: typeof options.message === "string" ? options.message : undefined,
1221
+ force: Boolean(options.force),
1222
+ }, globalOptions);
1223
+ await invalidateSearchCachesForMutation(globalOptions, result);
1224
+ printResult(result, globalOptions);
1225
+ if (globalOptions.profile) {
1226
+ printError(`profile:command=append took_ms=${Date.now() - startedAt}`);
1227
+ }
1228
+ });
1229
+ program
1230
+ .command("comments")
1231
+ .argument("<id>", "Item id")
1232
+ .option("--add <text>", "Add one comment entry")
1233
+ .option("--limit <n>", "Return only latest n comments")
1234
+ .option("--author <value>", "Comment author")
1235
+ .option("--message <value>", "History message")
1236
+ .option("--force", "Force ownership override")
1237
+ .description("List or append comments for an item.")
1238
+ .action(async (id, options, command) => {
1239
+ const globalOptions = getGlobalOptions(command);
1240
+ const startedAt = Date.now();
1241
+ const result = await runComments(id, {
1242
+ add: typeof options.add === "string" ? options.add : undefined,
1243
+ limit: typeof options.limit === "string" ? options.limit : undefined,
1244
+ author: typeof options.author === "string" ? options.author : undefined,
1245
+ message: typeof options.message === "string" ? options.message : undefined,
1246
+ force: Boolean(options.force),
1247
+ }, globalOptions);
1248
+ if (typeof options.add === "string") {
1249
+ await invalidateSearchCachesForMutation(globalOptions, result);
1250
+ }
1251
+ printResult(result, globalOptions);
1252
+ if (globalOptions.profile) {
1253
+ printError(`profile:command=comments took_ms=${Date.now() - startedAt}`);
1254
+ }
1255
+ });
1256
+ program
1257
+ .command("files")
1258
+ .argument("<id>", "Item id")
1259
+ .option("--add <value>", "Add linked file entry", collect)
1260
+ .option("--remove <value>", "Remove linked file by path", collect)
1261
+ .option("--author <value>", "Mutation author")
1262
+ .option("--message <value>", "History message")
1263
+ .option("--force", "Force ownership override")
1264
+ .description("List/add/remove linked files.")
1265
+ .action(async (id, options, command) => {
1266
+ const globalOptions = getGlobalOptions(command);
1267
+ const startedAt = Date.now();
1268
+ const addValues = Array.isArray(options.add) ? options.add : [];
1269
+ const removeValues = Array.isArray(options.remove) ? options.remove : [];
1270
+ const result = await runFiles(id, {
1271
+ add: addValues,
1272
+ remove: removeValues,
1273
+ author: typeof options.author === "string" ? options.author : undefined,
1274
+ message: typeof options.message === "string" ? options.message : undefined,
1275
+ force: Boolean(options.force),
1276
+ }, globalOptions);
1277
+ if (addValues.length > 0 || removeValues.length > 0) {
1278
+ await invalidateSearchCachesForMutation(globalOptions, result);
1279
+ }
1280
+ printResult(result, globalOptions);
1281
+ if (globalOptions.profile) {
1282
+ printError(`profile:command=files took_ms=${Date.now() - startedAt}`);
1283
+ }
1284
+ });
1285
+ program
1286
+ .command("docs")
1287
+ .argument("<id>", "Item id")
1288
+ .option("--add <value>", "Add linked doc entry", collect)
1289
+ .option("--remove <value>", "Remove linked doc by path", collect)
1290
+ .option("--author <value>", "Mutation author")
1291
+ .option("--message <value>", "History message")
1292
+ .option("--force", "Force ownership override")
1293
+ .description("List/add/remove linked docs.")
1294
+ .action(async (id, options, command) => {
1295
+ const globalOptions = getGlobalOptions(command);
1296
+ const startedAt = Date.now();
1297
+ const addValues = Array.isArray(options.add) ? options.add : [];
1298
+ const removeValues = Array.isArray(options.remove) ? options.remove : [];
1299
+ const result = await runDocs(id, {
1300
+ add: addValues,
1301
+ remove: removeValues,
1302
+ author: typeof options.author === "string" ? options.author : undefined,
1303
+ message: typeof options.message === "string" ? options.message : undefined,
1304
+ force: Boolean(options.force),
1305
+ }, globalOptions);
1306
+ if (addValues.length > 0 || removeValues.length > 0) {
1307
+ await invalidateSearchCachesForMutation(globalOptions, result);
1308
+ }
1309
+ printResult(result, globalOptions);
1310
+ if (globalOptions.profile) {
1311
+ printError(`profile:command=docs took_ms=${Date.now() - startedAt}`);
1312
+ }
1313
+ });
1314
+ program
1315
+ .command("test")
1316
+ .argument("<id>", "Item id")
1317
+ .option("--add <value>", "Add linked test entry", collect)
1318
+ .option("--remove <value>", "Remove linked test entry by command/path", collect)
1319
+ .option("--run", "Run linked test commands")
1320
+ .option("--timeout <seconds>", "Default run timeout in seconds")
1321
+ .option("--author <value>", "Mutation author")
1322
+ .option("--message <value>", "History message")
1323
+ .option("--force", "Force ownership override")
1324
+ .description("List/add/remove linked tests and optionally run them.")
1325
+ .action(async (id, options, command) => {
1326
+ const globalOptions = getGlobalOptions(command);
1327
+ const startedAt = Date.now();
1328
+ const addValues = Array.isArray(options.add) ? options.add : [];
1329
+ const removeValues = Array.isArray(options.remove) ? options.remove : [];
1330
+ const result = await runTest(id, {
1331
+ add: addValues,
1332
+ remove: removeValues,
1333
+ run: Boolean(options.run),
1334
+ timeout: typeof options.timeout === "string" ? options.timeout : undefined,
1335
+ author: typeof options.author === "string" ? options.author : undefined,
1336
+ message: typeof options.message === "string" ? options.message : undefined,
1337
+ force: Boolean(options.force),
1338
+ }, globalOptions);
1339
+ if (addValues.length > 0 || removeValues.length > 0) {
1340
+ await invalidateSearchCachesForMutation(globalOptions, result);
1341
+ }
1342
+ printResult(result, globalOptions);
1343
+ if (globalOptions.profile) {
1344
+ printError(`profile:command=test took_ms=${Date.now() - startedAt}`);
1345
+ }
1346
+ });
1347
+ program
1348
+ .command("test-all")
1349
+ .description("Run linked test commands across many items.")
1350
+ .option("--status <value>", "Filter items by status before running tests")
1351
+ .option("--timeout <seconds>", "Default run timeout in seconds")
1352
+ .action(async (options, command) => {
1353
+ const globalOptions = getGlobalOptions(command);
1354
+ const startedAt = Date.now();
1355
+ const result = await runTestAll({
1356
+ status: typeof options.status === "string" ? options.status : undefined,
1357
+ timeout: typeof options.timeout === "string" ? options.timeout : undefined,
1358
+ }, globalOptions);
1359
+ printResult(result, globalOptions);
1360
+ if (result.failed > 0) {
1361
+ process.exitCode = EXIT_CODE.DEPENDENCY_FAILED;
1362
+ }
1363
+ if (globalOptions.profile) {
1364
+ printError(`profile:command=test-all took_ms=${Date.now() - startedAt}`);
1365
+ }
1366
+ });
1367
+ program
1368
+ .command("stats")
1369
+ .description("Show deterministic tracker statistics summary.")
1370
+ .action(async (_options, command) => {
1371
+ const globalOptions = getGlobalOptions(command);
1372
+ const startedAt = Date.now();
1373
+ const result = await runStats(globalOptions);
1374
+ printResult(result, globalOptions);
1375
+ if (globalOptions.profile) {
1376
+ printError(`profile:command=stats took_ms=${Date.now() - startedAt}`);
1377
+ }
1378
+ });
1379
+ program
1380
+ .command("health")
1381
+ .description("Show deterministic tracker health checks summary.")
1382
+ .action(async (_options, command) => {
1383
+ const globalOptions = getGlobalOptions(command);
1384
+ const startedAt = Date.now();
1385
+ const result = await runHealth(globalOptions);
1386
+ printResult(result, globalOptions);
1387
+ if (globalOptions.profile) {
1388
+ printError(`profile:command=health took_ms=${Date.now() - startedAt}`);
1389
+ }
1390
+ });
1391
+ program
1392
+ .command("gc")
1393
+ .description("Collect optional cache artifacts and report deterministic summary.")
1394
+ .action(async (_options, command) => {
1395
+ const globalOptions = getGlobalOptions(command);
1396
+ const startedAt = Date.now();
1397
+ const result = await runGc(globalOptions);
1398
+ printResult(result, globalOptions);
1399
+ if (globalOptions.profile) {
1400
+ printError(`profile:command=gc took_ms=${Date.now() - startedAt}`);
1401
+ }
1402
+ });
1403
+ program
1404
+ .command("claim")
1405
+ .argument("<id>", "Item id")
1406
+ .option("--author <value>", "Mutation author")
1407
+ .option("--message <value>", "History message")
1408
+ .option("--force", "Force claim override")
1409
+ .description("Claim ownership of an item.")
1410
+ .action(async (id, options, command) => {
1411
+ const globalOptions = getGlobalOptions(command);
1412
+ const startedAt = Date.now();
1413
+ const result = await runClaim(id, Boolean(options.force), globalOptions, {
1414
+ author: typeof options.author === "string" ? options.author : undefined,
1415
+ message: typeof options.message === "string" ? options.message : undefined,
1416
+ });
1417
+ await invalidateSearchCachesForMutation(globalOptions, result);
1418
+ printResult(result, globalOptions);
1419
+ if (globalOptions.profile) {
1420
+ printError(`profile:command=claim took_ms=${Date.now() - startedAt}`);
1421
+ }
1422
+ });
1423
+ program
1424
+ .command("release")
1425
+ .argument("<id>", "Item id")
1426
+ .option("--author <value>", "Mutation author")
1427
+ .option("--message <value>", "History message")
1428
+ .option("--force", "Force release override")
1429
+ .description("Release ownership of an item.")
1430
+ .action(async (id, options, command) => {
1431
+ const globalOptions = getGlobalOptions(command);
1432
+ const startedAt = Date.now();
1433
+ const result = await runRelease(id, Boolean(options.force), globalOptions, {
1434
+ author: typeof options.author === "string" ? options.author : undefined,
1435
+ message: typeof options.message === "string" ? options.message : undefined,
1436
+ });
1437
+ await invalidateSearchCachesForMutation(globalOptions, result);
1438
+ printResult(result, globalOptions);
1439
+ if (globalOptions.profile) {
1440
+ printError(`profile:command=release took_ms=${Date.now() - startedAt}`);
1441
+ }
1442
+ });
1443
+ program
1444
+ .command("completion")
1445
+ .argument("<shell>", "Shell type: bash, zsh, or fish")
1446
+ .description("Generate shell completion script for pm.")
1447
+ .action((shell, _options, command) => {
1448
+ const globalOptions = getGlobalOptions(command);
1449
+ const result = runCompletion(shell);
1450
+ if (globalOptions.json) {
1451
+ printResult(result, globalOptions);
1452
+ }
1453
+ else if (!globalOptions.quiet) {
1454
+ process.stdout.write(`${result.script}\n`);
1455
+ }
1456
+ if (globalOptions.profile) {
1457
+ printError(`profile:command=completion took_ms=0`);
1458
+ }
1459
+ });
1460
+ async function main() {
1461
+ try {
1462
+ await registerDynamicExtensionCommandPaths(program);
1463
+ await program.parseAsync(process.argv);
1464
+ }
1465
+ catch (error) {
1466
+ await runAndClearAfterCommandHooks({
1467
+ ok: false,
1468
+ error: describeUnknownError(error),
1469
+ });
1470
+ if (error instanceof PmCliError) {
1471
+ printError(error.message);
1472
+ process.exit(error.exitCode);
1473
+ }
1474
+ if (typeof error === "object" && error !== null && "code" in error) {
1475
+ const code = error.code;
1476
+ if (code === "commander.helpDisplayed") {
1477
+ process.exit(EXIT_CODE.SUCCESS);
1478
+ }
1479
+ if (code === "commander.version") {
1480
+ process.exit(EXIT_CODE.SUCCESS);
1481
+ }
1482
+ if (code?.startsWith("commander.")) {
1483
+ const message = error.message ?? "Invalid command usage";
1484
+ printError(message.replace(/\(outputHelp\)/g, "").trim());
1485
+ process.exit(EXIT_CODE.USAGE);
1486
+ }
1487
+ }
1488
+ const message = describeUnknownError(error);
1489
+ printError(message);
1490
+ process.exit(EXIT_CODE.GENERIC_FAILURE);
1491
+ }
1492
+ }
1493
+ void main();
1494
+ //# sourceMappingURL=main.js.map