@unbrained/pm-cli 2026.5.24 → 2026.5.27

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 (238) hide show
  1. package/CHANGELOG.md +946 -525
  2. package/README.md +2 -10
  3. package/dist/cli/bootstrap-args.d.ts +18 -1
  4. package/dist/cli/bootstrap-args.js +143 -3
  5. package/dist/cli/bootstrap-args.js.map +1 -1
  6. package/dist/cli/commander-usage.js +134 -11
  7. package/dist/cli/commander-usage.js.map +1 -1
  8. package/dist/cli/commands/append.js +4 -3
  9. package/dist/cli/commands/append.js.map +1 -1
  10. package/dist/cli/commands/claim.js +5 -4
  11. package/dist/cli/commands/claim.js.map +1 -1
  12. package/dist/cli/commands/close.js +4 -3
  13. package/dist/cli/commands/close.js.map +1 -1
  14. package/dist/cli/commands/completion.d.ts +2 -2
  15. package/dist/cli/commands/completion.js +109 -56
  16. package/dist/cli/commands/completion.js.map +1 -1
  17. package/dist/cli/commands/config.d.ts +1 -1
  18. package/dist/cli/commands/config.js +82 -4
  19. package/dist/cli/commands/config.js.map +1 -1
  20. package/dist/cli/commands/create.js +7 -272
  21. package/dist/cli/commands/create.js.map +1 -1
  22. package/dist/cli/commands/delete.js +4 -3
  23. package/dist/cli/commands/delete.js.map +1 -1
  24. package/dist/cli/commands/docs.d.ts +1 -12
  25. package/dist/cli/commands/docs.js +8 -312
  26. package/dist/cli/commands/docs.js.map +1 -1
  27. package/dist/cli/commands/extension/bundled-catalog.d.ts +14 -0
  28. package/dist/cli/commands/extension/bundled-catalog.js +268 -0
  29. package/dist/cli/commands/extension/bundled-catalog.js.map +1 -0
  30. package/dist/cli/commands/extension/doctor.d.ts +31 -0
  31. package/dist/cli/commands/extension/doctor.js +345 -0
  32. package/dist/cli/commands/extension/doctor.js.map +1 -0
  33. package/dist/cli/commands/extension/install-sources.d.ts +37 -0
  34. package/dist/cli/commands/extension/install-sources.js +384 -0
  35. package/dist/cli/commands/extension/install-sources.js.map +1 -0
  36. package/dist/cli/commands/extension/managed-state.d.ts +48 -0
  37. package/dist/cli/commands/extension/managed-state.js +172 -0
  38. package/dist/cli/commands/extension/managed-state.js.map +1 -0
  39. package/dist/cli/commands/extension/scaffold.d.ts +14 -0
  40. package/dist/cli/commands/extension/scaffold.js +169 -0
  41. package/dist/cli/commands/extension/scaffold.js.map +1 -0
  42. package/dist/cli/commands/extension/shared.d.ts +14 -0
  43. package/dist/cli/commands/extension/shared.js +106 -0
  44. package/dist/cli/commands/extension/shared.js.map +1 -0
  45. package/dist/cli/commands/extension.d.ts +36 -68
  46. package/dist/cli/commands/extension.js +143 -1422
  47. package/dist/cli/commands/extension.js.map +1 -1
  48. package/dist/cli/commands/files.d.ts +1 -12
  49. package/dist/cli/commands/files.js +11 -308
  50. package/dist/cli/commands/files.js.map +1 -1
  51. package/dist/cli/commands/get.js +4 -3
  52. package/dist/cli/commands/get.js.map +1 -1
  53. package/dist/cli/commands/health.js +17 -3
  54. package/dist/cli/commands/health.js.map +1 -1
  55. package/dist/cli/commands/history-redact.js +23 -18
  56. package/dist/cli/commands/history-redact.js.map +1 -1
  57. package/dist/cli/commands/history-repair.js +24 -18
  58. package/dist/cli/commands/history-repair.js.map +1 -1
  59. package/dist/cli/commands/legacy-none-tokens.d.ts +3 -0
  60. package/dist/cli/commands/legacy-none-tokens.js +39 -0
  61. package/dist/cli/commands/legacy-none-tokens.js.map +1 -0
  62. package/dist/cli/commands/linked-artifacts.d.ts +96 -0
  63. package/dist/cli/commands/linked-artifacts.js +335 -0
  64. package/dist/cli/commands/linked-artifacts.js.map +1 -0
  65. package/dist/cli/commands/linked-test-parsers.d.ts +28 -0
  66. package/dist/cli/commands/linked-test-parsers.js +192 -0
  67. package/dist/cli/commands/linked-test-parsers.js.map +1 -0
  68. package/dist/cli/commands/list.js +19 -4
  69. package/dist/cli/commands/list.js.map +1 -1
  70. package/dist/cli/commands/normalize.js +4 -3
  71. package/dist/cli/commands/normalize.js.map +1 -1
  72. package/dist/cli/commands/recurrence-parsers.d.ts +26 -0
  73. package/dist/cli/commands/recurrence-parsers.js +98 -0
  74. package/dist/cli/commands/recurrence-parsers.js.map +1 -0
  75. package/dist/cli/commands/restore.js +19 -8
  76. package/dist/cli/commands/restore.js.map +1 -1
  77. package/dist/cli/commands/search.js +5 -4
  78. package/dist/cli/commands/search.js.map +1 -1
  79. package/dist/cli/commands/test/linked-command-detection.d.ts +37 -0
  80. package/dist/cli/commands/test/linked-command-detection.js +200 -0
  81. package/dist/cli/commands/test/linked-command-detection.js.map +1 -0
  82. package/dist/cli/commands/test.d.ts +1 -2
  83. package/dist/cli/commands/test.js +7 -349
  84. package/dist/cli/commands/test.js.map +1 -1
  85. package/dist/cli/commands/update-many.js +4 -3
  86. package/dist/cli/commands/update-many.js.map +1 -1
  87. package/dist/cli/commands/update.js +62 -354
  88. package/dist/cli/commands/update.js.map +1 -1
  89. package/dist/cli/error-guidance.d.ts +1 -0
  90. package/dist/cli/error-guidance.js +6 -2
  91. package/dist/cli/error-guidance.js.map +1 -1
  92. package/dist/cli/main.d.ts +11 -0
  93. package/dist/cli/main.js +76 -28
  94. package/dist/cli/main.js.map +1 -1
  95. package/dist/cli/register-list-query.d.ts +4 -1
  96. package/dist/cli/register-list-query.js +242 -203
  97. package/dist/cli/register-list-query.js.map +1 -1
  98. package/dist/cli/register-mutation.js +24 -9
  99. package/dist/cli/register-mutation.js.map +1 -1
  100. package/dist/cli/register-operations.js +3 -3
  101. package/dist/cli/register-operations.js.map +1 -1
  102. package/dist/cli/register-setup.js +12 -7
  103. package/dist/cli/register-setup.js.map +1 -1
  104. package/dist/cli/registration-helpers.js +3 -2
  105. package/dist/cli/registration-helpers.js.map +1 -1
  106. package/dist/cli.js +4 -3
  107. package/dist/cli.js.map +1 -1
  108. package/dist/core/config/positional-value.d.ts +44 -0
  109. package/dist/core/config/positional-value.js +109 -0
  110. package/dist/core/config/positional-value.js.map +1 -0
  111. package/dist/core/extensions/extension-capability-aliases.d.ts +14 -0
  112. package/dist/core/extensions/extension-capability-aliases.js +159 -0
  113. package/dist/core/extensions/extension-capability-aliases.js.map +1 -0
  114. package/dist/core/extensions/extension-hook-runtime.d.ts +13 -0
  115. package/dist/core/extensions/extension-hook-runtime.js +414 -0
  116. package/dist/core/extensions/extension-hook-runtime.js.map +1 -0
  117. package/dist/core/extensions/extension-policy.d.ts +69 -0
  118. package/dist/core/extensions/extension-policy.js +481 -0
  119. package/dist/core/extensions/extension-policy.js.map +1 -0
  120. package/dist/core/extensions/extension-registries.d.ts +8 -0
  121. package/dist/core/extensions/extension-registries.js +52 -0
  122. package/dist/core/extensions/extension-registries.js.map +1 -0
  123. package/dist/core/extensions/extension-runtime-helpers.d.ts +6 -0
  124. package/dist/core/extensions/extension-runtime-helpers.js +29 -0
  125. package/dist/core/extensions/extension-runtime-helpers.js.map +1 -0
  126. package/dist/core/extensions/extension-types.d.ts +13 -39
  127. package/dist/core/extensions/extension-types.js +34 -2
  128. package/dist/core/extensions/extension-types.js.map +1 -1
  129. package/dist/core/extensions/index.d.ts +7 -0
  130. package/dist/core/extensions/index.js +11 -2
  131. package/dist/core/extensions/index.js.map +1 -1
  132. package/dist/core/extensions/loader.d.ts +4 -22
  133. package/dist/core/extensions/loader.js +22 -1139
  134. package/dist/core/extensions/loader.js.map +1 -1
  135. package/dist/core/history/drift-scan.d.ts +11 -0
  136. package/dist/core/history/drift-scan.js +114 -32
  137. package/dist/core/history/drift-scan.js.map +1 -1
  138. package/dist/core/history/history-rewrite.d.ts +43 -0
  139. package/dist/core/history/history-rewrite.js +48 -0
  140. package/dist/core/history/history-rewrite.js.map +1 -0
  141. package/dist/core/history/history.js +5 -4
  142. package/dist/core/history/history.js.map +1 -1
  143. package/dist/core/history/replay.js +4 -3
  144. package/dist/core/history/replay.js.map +1 -1
  145. package/dist/core/item/item-record.d.ts +19 -0
  146. package/dist/core/item/item-record.js +24 -0
  147. package/dist/core/item/item-record.js.map +1 -0
  148. package/dist/core/output/mutation-projection.d.ts +31 -0
  149. package/dist/core/output/mutation-projection.js +103 -0
  150. package/dist/core/output/mutation-projection.js.map +1 -0
  151. package/dist/core/output/output.d.ts +2 -0
  152. package/dist/core/output/output.js +5 -3
  153. package/dist/core/output/output.js.map +1 -1
  154. package/dist/core/schema/runtime-schema.js +8 -38
  155. package/dist/core/schema/runtime-schema.js.map +1 -1
  156. package/dist/core/search/vector-stores.js +46 -9
  157. package/dist/core/search/vector-stores.js.map +1 -1
  158. package/dist/core/sentry/helpers.d.ts +1 -1
  159. package/dist/core/sentry/helpers.js +20 -3
  160. package/dist/core/sentry/helpers.js.map +1 -1
  161. package/dist/core/shared/command-types.d.ts +1 -0
  162. package/dist/core/shared/command-types.js +2 -2
  163. package/dist/core/shared/command-types.js.map +1 -1
  164. package/dist/core/shared/constants.d.ts +10 -1
  165. package/dist/core/shared/constants.js +56 -58
  166. package/dist/core/shared/constants.js.map +1 -1
  167. package/dist/core/shared/primitives.d.ts +23 -0
  168. package/dist/core/shared/primitives.js +39 -2
  169. package/dist/core/shared/primitives.js.map +1 -1
  170. package/dist/core/store/front-matter-cache.d.ts +16 -2
  171. package/dist/core/store/front-matter-cache.js +99 -33
  172. package/dist/core/store/front-matter-cache.js.map +1 -1
  173. package/dist/core/store/item-store.js +8 -73
  174. package/dist/core/store/item-store.js.map +1 -1
  175. package/dist/mcp/server.js +76 -28
  176. package/dist/mcp/server.js.map +1 -1
  177. package/dist/sdk/cli-contracts/enum-contracts.d.ts +20 -0
  178. package/dist/sdk/cli-contracts/enum-contracts.js +156 -0
  179. package/dist/sdk/cli-contracts/enum-contracts.js.map +1 -0
  180. package/dist/sdk/cli-contracts/tool-option-contracts.d.ts +14 -0
  181. package/dist/sdk/cli-contracts/tool-option-contracts.js +243 -0
  182. package/dist/sdk/cli-contracts/tool-option-contracts.js.map +1 -0
  183. package/dist/sdk/cli-contracts/tool-parameter-tables.d.ts +11 -0
  184. package/dist/sdk/cli-contracts/tool-parameter-tables.js +901 -0
  185. package/dist/sdk/cli-contracts/tool-parameter-tables.js.map +1 -0
  186. package/dist/sdk/cli-contracts.d.ts +11 -33
  187. package/dist/sdk/cli-contracts.js +23 -1356
  188. package/dist/sdk/cli-contracts.js.map +1 -1
  189. package/dist/sdk/package-import-adapters.d.ts +74 -0
  190. package/dist/sdk/package-import-adapters.js +186 -0
  191. package/dist/sdk/package-import-adapters.js.map +1 -0
  192. package/dist/sdk/package-runtime-options.d.ts +26 -0
  193. package/dist/sdk/package-runtime-options.js +71 -0
  194. package/dist/sdk/package-runtime-options.js.map +1 -0
  195. package/dist/sdk/runtime.d.ts +2 -0
  196. package/dist/sdk/runtime.js +4 -2
  197. package/dist/sdk/runtime.js.map +1 -1
  198. package/docs/AGENT_GUIDE.md +6 -10
  199. package/docs/CLAUDE_CODE_PLUGIN.md +5 -28
  200. package/docs/CODEX_PLUGIN.md +5 -5
  201. package/docs/COMMANDS.md +19 -3
  202. package/docs/CONFIGURATION.md +15 -0
  203. package/docs/EXTENSIONS.md +4 -63
  204. package/docs/RELEASING.md +4 -4
  205. package/marketplace.json +7 -3
  206. package/package.json +9 -6
  207. package/packages/pm-beads/extensions/beads/index.js +2 -49
  208. package/packages/pm-beads/extensions/beads/index.ts +2 -54
  209. package/packages/pm-beads/extensions/beads/runtime-loader.js +86 -0
  210. package/packages/pm-beads/extensions/beads/runtime-loader.ts +88 -0
  211. package/packages/pm-beads/extensions/beads/runtime.js +26 -115
  212. package/packages/pm-beads/extensions/beads/runtime.ts +33 -132
  213. package/packages/pm-calendar/extensions/calendar/index.js +47 -2
  214. package/packages/pm-calendar/extensions/calendar/index.ts +52 -2
  215. package/packages/pm-calendar/extensions/calendar/runtime.js +1 -0
  216. package/packages/pm-calendar/extensions/calendar/runtime.ts +1 -0
  217. package/packages/pm-governance-audit/extensions/governance-audit/runtime.js +14 -41
  218. package/packages/pm-governance-audit/extensions/governance-audit/runtime.ts +25 -41
  219. package/packages/pm-guide-shell/extensions/guide-shell/runtime.js +10 -50
  220. package/packages/pm-guide-shell/extensions/guide-shell/runtime.ts +17 -50
  221. package/packages/pm-linked-test-adapters/extensions/linked-test-adapters/runtime.js +8 -40
  222. package/packages/pm-linked-test-adapters/extensions/linked-test-adapters/runtime.ts +10 -40
  223. package/packages/pm-search-advanced/extensions/search-advanced/index.js +1 -1
  224. package/packages/pm-search-advanced/extensions/search-advanced/runtime.js +4 -37
  225. package/packages/pm-search-advanced/extensions/search-advanced/runtime.ts +6 -37
  226. package/packages/pm-todos/extensions/todos/index.js +3 -50
  227. package/packages/pm-todos/extensions/todos/index.ts +3 -55
  228. package/packages/pm-todos/extensions/todos/runtime-loader.js +86 -0
  229. package/packages/pm-todos/extensions/todos/runtime-loader.ts +88 -0
  230. package/packages/pm-todos/extensions/todos/runtime.js +24 -117
  231. package/packages/pm-todos/extensions/todos/runtime.ts +32 -129
  232. package/plugins/pm-claude/README.md +2 -2
  233. package/plugins/pm-claude/commands/pm-planner.md +1 -15
  234. package/plugins/pm-claude/scripts/pm-mcp-server.mjs +5 -2
  235. package/plugins/pm-claude/skills/pm-planner/SKILL.md +3 -21
  236. package/plugins/pm-codex/scripts/pm-mcp-server.mjs +15 -6
  237. package/plugins/pm-codex/skills/pm-native/SKILL.md +1 -13
  238. package/PRD.md +0 -1734
@@ -1,6 +1,6 @@
1
1
  import fs from "node:fs/promises";
2
2
  import path from "node:path";
3
- import { DEPENDENCY_KIND_VALUES, EXIT_CODE, PmCliError, acquireLock, appendHistoryEntry, canonicalDocument, createHistoryEntry, generateItemId, getActiveExtensionRegistrations, getHistoryPath, getItemPath, getSettingsPath, isTimestampLiteral, locateItem, normalizeFrontMatter, normalizeItemId, normalizeRawItemId, normalizeStatusInput, nowIso, parseTags, pathExists, readSettings, removeFileIfExists, resolveItemTypeRegistry, resolvePmRoot, runActiveOnReadHooks, runActiveOnWriteHooks, serializeItemDocument, writeFileAtomic, } from "../../../../dist/sdk/index.js";
3
+ import { DEPENDENCY_KIND_VALUES, EXIT_CODE, PmCliError, canonicalDocument, commitImportedItem, generateItemId, getActiveExtensionRegistrations, getItemPath, isTimestampLiteral, locateItem, normalizeFrontMatter, normalizeItemId, normalizeRawItemId, nowIso, pathExists, readSettings, resolveItemTypeRegistry, resolvePmRoot, runActiveOnReadHooks, ensureTrackerInitialized, selectImportAuthor, toEstimatedMinutesValue, toImportPriority, toImportStatus, toImportTags, toNonEmptyImportString, } from "../../../../dist/sdk/index.js";
4
4
  const PRIMARY_AUTO_DISCOVERY_FILES = [
5
5
  ".beads/issues.jsonl",
6
6
  "issues.jsonl",
@@ -9,13 +9,13 @@ const UNSAFE_AUTO_DISCOVERY_FILES = [
9
9
  ".beads/sync_base.jsonl",
10
10
  "sync_base.jsonl",
11
11
  ];
12
- function toNonEmptyString(value) {
13
- if (typeof value !== "string") {
14
- return undefined;
15
- }
16
- const trimmed = value.trim();
17
- return trimmed.length > 0 ? trimmed : undefined;
18
- }
12
+ // Shared, behavior-identical value coercers are sourced from the SDK adapter
13
+ // surface; package-specific mappings (timestamps, item types, dependencies,
14
+ // linked artifacts) stay local below.
15
+ const toNonEmptyString = toNonEmptyImportString;
16
+ const toEstimatedMinutes = toEstimatedMinutesValue;
17
+ const toPriority = toImportPriority;
18
+ const toTags = toImportTags;
19
19
  function toIsoString(value) {
20
20
  const raw = toNonEmptyString(value);
21
21
  if (!raw) {
@@ -26,44 +26,6 @@ function toIsoString(value) {
26
26
  }
27
27
  return raw;
28
28
  }
29
- function toEstimatedMinutes(value) {
30
- if (typeof value === "number" && Number.isFinite(value) && value >= 0) {
31
- return value;
32
- }
33
- if (typeof value === "string" && value.trim().length > 0) {
34
- const parsed = Number(value);
35
- if (Number.isFinite(parsed) && parsed >= 0) {
36
- return parsed;
37
- }
38
- }
39
- return undefined;
40
- }
41
- function toPriority(value) {
42
- const fallback = 2;
43
- if (typeof value === "number" && Number.isInteger(value) && value >= 0 && value <= 4) {
44
- return value;
45
- }
46
- if (typeof value === "string" && value.trim().length > 0) {
47
- const parsed = Number(value);
48
- if (Number.isInteger(parsed) && parsed >= 0 && parsed <= 4) {
49
- return parsed;
50
- }
51
- }
52
- return fallback;
53
- }
54
- function toTags(value) {
55
- if (Array.isArray(value)) {
56
- const tags = value
57
- .filter((entry) => typeof entry === "string")
58
- .map((entry) => entry.trim().toLowerCase())
59
- .filter((entry) => entry.length > 0);
60
- return Array.from(new Set(tags)).sort((left, right) => left.localeCompare(right));
61
- }
62
- if (typeof value === "string") {
63
- return parseTags(value);
64
- }
65
- return [];
66
- }
67
29
  function toItemType(value) {
68
30
  const raw = toNonEmptyString(value);
69
31
  const normalized = raw?.toLowerCase();
@@ -86,16 +48,7 @@ function toItemType(value) {
86
48
  return { type: "Task", sourceType: raw };
87
49
  }
88
50
  }
89
- function toStatus(value) {
90
- const normalized = toNonEmptyString(value);
91
- if (normalized) {
92
- const canonical = normalizeStatusInput(normalized);
93
- if (canonical) {
94
- return canonical;
95
- }
96
- }
97
- return "open";
98
- }
51
+ const toStatus = toImportStatus;
99
52
  function toDependencyKind(value) {
100
53
  const raw = toNonEmptyString(value);
101
54
  const normalized = raw?.toLowerCase();
@@ -332,24 +285,8 @@ function toLinkedDocs(value) {
332
285
  }
333
286
  return docs.length > 0 ? docs : undefined;
334
287
  }
335
- function selectAuthor(explicitAuthor, settingsAuthor) {
336
- const candidate = explicitAuthor ?? process.env.PM_AUTHOR ?? settingsAuthor;
337
- const trimmed = candidate.trim();
338
- return trimmed.length > 0 ? trimmed : "unknown";
339
- }
340
- function ensureInitHasRun(pmRoot) {
341
- return pathExists(getSettingsPath(pmRoot)).then((exists) => {
342
- if (!exists) {
343
- throw new PmCliError(`Tracker is not initialized at ${pmRoot}. Run pm init first.`, EXIT_CODE.NOT_FOUND);
344
- }
345
- });
346
- }
347
- function emptyDocument() {
348
- return {
349
- metadata: {},
350
- body: "",
351
- };
352
- }
288
+ const selectAuthor = selectImportAuthor;
289
+ const ensureInitHasRun = ensureTrackerInitialized;
353
290
  function resolveInputPath(rawPath) {
354
291
  return path.isAbsolute(rawPath) ? rawPath : path.resolve(process.cwd(), rawPath);
355
292
  }
@@ -515,48 +452,22 @@ export async function runBeadsImport(options, global) {
515
452
  continue;
516
453
  }
517
454
  const itemPath = getItemPath(pmRoot, type, id, "toon", typeRegistry.type_to_folder);
518
- const historyPath = getHistoryPath(pmRoot, id);
519
- try {
520
- const releaseLock = await acquireLock(pmRoot, id, settings.locks.ttl_seconds, author);
521
- try {
522
- await writeFileAtomic(itemPath, serializeItemDocument(afterDocument, { format: "toon" }));
523
- try {
524
- const entry = createHistoryEntry({
525
- nowIso: nowIso(),
526
- author,
527
- op: "import",
528
- before: emptyDocument(),
529
- after: afterDocument,
530
- message,
531
- });
532
- await appendHistoryEntry(historyPath, entry);
533
- warnings.push(...(await runActiveOnWriteHooks({
534
- path: itemPath,
535
- scope: "project",
536
- op: "import",
537
- })), ...(await runActiveOnWriteHooks({
538
- path: historyPath,
539
- scope: "project",
540
- op: "import:history",
541
- })));
542
- }
543
- catch (error) {
544
- await removeFileIfExists(itemPath);
545
- throw error;
546
- }
547
- }
548
- finally {
549
- await releaseLock();
550
- }
551
- }
552
- catch (error) {
553
- if (error instanceof PmCliError && error.exitCode === EXIT_CODE.CONFLICT) {
554
- warnings.push(`beads_import_lock_conflict:${id}`);
555
- skipped += 1;
556
- continue;
557
- }
558
- throw error;
455
+ const commit = await commitImportedItem({
456
+ pmRoot,
457
+ id,
458
+ itemPath,
459
+ document: afterDocument,
460
+ author,
461
+ message,
462
+ settings,
463
+ conflictWarningPrefix: "beads_import_lock_conflict",
464
+ });
465
+ if (!commit.committed) {
466
+ warnings.push(commit.conflictWarning);
467
+ skipped += 1;
468
+ continue;
559
469
  }
470
+ warnings.push(...commit.writeWarnings);
560
471
  ids.push(id);
561
472
  imported += 1;
562
473
  }
@@ -4,36 +4,31 @@ import {
4
4
  DEPENDENCY_KIND_VALUES,
5
5
  EXIT_CODE,
6
6
  PmCliError,
7
- acquireLock,
8
- appendHistoryEntry,
9
7
  canonicalDocument,
10
- createHistoryEntry,
8
+ commitImportedItem,
11
9
  generateItemId,
12
10
  getActiveExtensionRegistrations,
13
- getHistoryPath,
14
11
  getItemPath,
15
- getSettingsPath,
16
12
  isTimestampLiteral,
17
13
  locateItem,
18
14
  normalizeFrontMatter,
19
15
  normalizeItemId,
20
16
  normalizeRawItemId,
21
- normalizeStatusInput,
22
17
  nowIso,
23
- parseTags,
24
18
  pathExists,
25
19
  readSettings,
26
- removeFileIfExists,
27
20
  resolveItemTypeRegistry,
28
21
  resolvePmRoot,
29
22
  runActiveOnReadHooks,
30
- runActiveOnWriteHooks,
31
- serializeItemDocument,
32
- writeFileAtomic,
23
+ ensureTrackerInitialized,
24
+ selectImportAuthor,
25
+ toEstimatedMinutesValue,
26
+ toImportPriority,
27
+ toImportStatus,
28
+ toImportTags,
29
+ toNonEmptyImportString,
33
30
  type Dependency,
34
31
  type GlobalOptions,
35
- type ItemDocument,
36
- type ItemMetadata,
37
32
  type ItemStatus,
38
33
  type ItemType,
39
34
  type LinkedDoc,
@@ -102,13 +97,13 @@ interface BeadsRecord extends Record<string, unknown> {
102
97
  docs?: unknown;
103
98
  }
104
99
 
105
- function toNonEmptyString(value: unknown): string | undefined {
106
- if (typeof value !== "string") {
107
- return undefined;
108
- }
109
- const trimmed = value.trim();
110
- return trimmed.length > 0 ? trimmed : undefined;
111
- }
100
+ // Shared, behavior-identical value coercers are sourced from the SDK adapter
101
+ // surface; package-specific mappings (timestamps, item types, dependencies,
102
+ // linked artifacts) stay local below.
103
+ const toNonEmptyString = toNonEmptyImportString;
104
+ const toEstimatedMinutes = toEstimatedMinutesValue;
105
+ const toPriority = toImportPriority;
106
+ const toTags = toImportTags;
112
107
 
113
108
  function toIsoString(value: unknown): string | undefined {
114
109
  const raw = toNonEmptyString(value);
@@ -121,47 +116,6 @@ function toIsoString(value: unknown): string | undefined {
121
116
  return raw;
122
117
  }
123
118
 
124
- function toEstimatedMinutes(value: unknown): number | undefined {
125
- if (typeof value === "number" && Number.isFinite(value) && value >= 0) {
126
- return value;
127
- }
128
- if (typeof value === "string" && value.trim().length > 0) {
129
- const parsed = Number(value);
130
- if (Number.isFinite(parsed) && parsed >= 0) {
131
- return parsed;
132
- }
133
- }
134
- return undefined;
135
- }
136
-
137
- function toPriority(value: unknown): 0 | 1 | 2 | 3 | 4 {
138
- const fallback: 0 | 1 | 2 | 3 | 4 = 2;
139
- if (typeof value === "number" && Number.isInteger(value) && value >= 0 && value <= 4) {
140
- return value as 0 | 1 | 2 | 3 | 4;
141
- }
142
- if (typeof value === "string" && value.trim().length > 0) {
143
- const parsed = Number(value);
144
- if (Number.isInteger(parsed) && parsed >= 0 && parsed <= 4) {
145
- return parsed as 0 | 1 | 2 | 3 | 4;
146
- }
147
- }
148
- return fallback;
149
- }
150
-
151
- function toTags(value: unknown): string[] {
152
- if (Array.isArray(value)) {
153
- const tags = value
154
- .filter((entry): entry is string => typeof entry === "string")
155
- .map((entry) => entry.trim().toLowerCase())
156
- .filter((entry) => entry.length > 0);
157
- return Array.from(new Set(tags)).sort((left, right) => left.localeCompare(right));
158
- }
159
- if (typeof value === "string") {
160
- return parseTags(value);
161
- }
162
- return [];
163
- }
164
-
165
119
  function toItemType(value: unknown): { type: ItemType; sourceType?: string } {
166
120
  const raw = toNonEmptyString(value);
167
121
  const normalized = raw?.toLowerCase();
@@ -185,16 +139,7 @@ function toItemType(value: unknown): { type: ItemType; sourceType?: string } {
185
139
  }
186
140
  }
187
141
 
188
- function toStatus(value: unknown): ItemStatus {
189
- const normalized = toNonEmptyString(value);
190
- if (normalized) {
191
- const canonical = normalizeStatusInput(normalized);
192
- if (canonical) {
193
- return canonical;
194
- }
195
- }
196
- return "open";
197
- }
142
+ const toStatus: (value: unknown) => ItemStatus = toImportStatus;
198
143
 
199
144
  function toDependencyKind(value: unknown): { kind: Dependency["kind"]; sourceKind?: string } {
200
145
  const raw = toNonEmptyString(value);
@@ -452,26 +397,8 @@ function toLinkedDocs(value: unknown): LinkedDoc[] | undefined {
452
397
  return docs.length > 0 ? docs : undefined;
453
398
  }
454
399
 
455
- function selectAuthor(explicitAuthor: string | undefined, settingsAuthor: string): string {
456
- const candidate = explicitAuthor ?? process.env.PM_AUTHOR ?? settingsAuthor;
457
- const trimmed = candidate.trim();
458
- return trimmed.length > 0 ? trimmed : "unknown";
459
- }
460
-
461
- function ensureInitHasRun(pmRoot: string): Promise<void> {
462
- return pathExists(getSettingsPath(pmRoot)).then((exists) => {
463
- if (!exists) {
464
- throw new PmCliError(`Tracker is not initialized at ${pmRoot}. Run pm init first.`, EXIT_CODE.NOT_FOUND);
465
- }
466
- });
467
- }
468
-
469
- function emptyDocument(): ItemDocument {
470
- return {
471
- metadata: {} as ItemMetadata,
472
- body: "",
473
- };
474
- }
400
+ const selectAuthor = selectImportAuthor;
401
+ const ensureInitHasRun = ensureTrackerInitialized;
475
402
 
476
403
  function resolveInputPath(rawPath: string): string {
477
404
  return path.isAbsolute(rawPath) ? rawPath : path.resolve(process.cwd(), rawPath);
@@ -667,48 +594,22 @@ export async function runBeadsImport(options: BeadsImportOptions, global: Global
667
594
  }
668
595
  const itemPath = getItemPath(pmRoot, type, id, "toon", typeRegistry.type_to_folder);
669
596
 
670
- const historyPath = getHistoryPath(pmRoot, id);
671
- try {
672
- const releaseLock = await acquireLock(pmRoot, id, settings.locks.ttl_seconds, author);
673
- try {
674
- await writeFileAtomic(itemPath, serializeItemDocument(afterDocument, { format: "toon" }));
675
- try {
676
- const entry = createHistoryEntry({
677
- nowIso: nowIso(),
678
- author,
679
- op: "import",
680
- before: emptyDocument(),
681
- after: afterDocument,
682
- message,
683
- });
684
- await appendHistoryEntry(historyPath, entry);
685
- warnings.push(
686
- ...(await runActiveOnWriteHooks({
687
- path: itemPath,
688
- scope: "project",
689
- op: "import",
690
- })),
691
- ...(await runActiveOnWriteHooks({
692
- path: historyPath,
693
- scope: "project",
694
- op: "import:history",
695
- })),
696
- );
697
- } catch (error: unknown) {
698
- await removeFileIfExists(itemPath);
699
- throw error;
700
- }
701
- } finally {
702
- await releaseLock();
703
- }
704
- } catch (error: unknown) {
705
- if (error instanceof PmCliError && error.exitCode === EXIT_CODE.CONFLICT) {
706
- warnings.push(`beads_import_lock_conflict:${id}`);
707
- skipped += 1;
708
- continue;
709
- }
710
- throw error;
597
+ const commit = await commitImportedItem({
598
+ pmRoot,
599
+ id,
600
+ itemPath,
601
+ document: afterDocument,
602
+ author,
603
+ message,
604
+ settings,
605
+ conflictWarningPrefix: "beads_import_lock_conflict",
606
+ });
607
+ if (!commit.committed) {
608
+ warnings.push(commit.conflictWarning);
609
+ skipped += 1;
610
+ continue;
711
611
  }
612
+ warnings.push(...commit.writeWarnings);
712
613
 
713
614
  ids.push(id);
714
615
  imported += 1;
@@ -1,5 +1,19 @@
1
1
  import { renderCalendarPackageOutput, runCalendarPackage } from "./runtime.js";
2
2
 
3
+ const CALENDAR_VIEW_NAMES = ["agenda", "day", "week", "month"];
4
+
5
+ // Standalone error class so the package stays self-contained when installed
6
+ // outside the pm-cli source tree. The class name "PmCliError" lines up with
7
+ // the Sentry beforeSend filter (isExpectedCliErrorEvent) so usage errors do
8
+ // not leak into Sentry as crashes.
9
+ class PmCliError extends Error {
10
+ constructor(message, exitCode) {
11
+ super(message);
12
+ this.name = "PmCliError";
13
+ this.exitCode = exitCode;
14
+ }
15
+ }
16
+
3
17
  export const manifest = {
4
18
  name: "builtin-calendar",
5
19
  version: "0.1.0",
@@ -31,6 +45,34 @@ const calendarFlags = [
31
45
  { long: "--format", value_name: "value", value_type: "string", description: "Calendar output override: markdown|toon|json." },
32
46
  ];
33
47
 
48
+ // The runtime lowercases `view` before validating (src/cli/commands/calendar.ts),
49
+ // so the unknown-alias / recovery-hint logic must match views case-insensitively
50
+ // or `pm calendar DAY ...` would wrongly flag DAY as unknown.
51
+ function normalizeCalendarView(arg) {
52
+ const normalized = arg.toLowerCase();
53
+ return CALENDAR_VIEW_NAMES.includes(normalized) ? normalized : null;
54
+ }
55
+
56
+ function buildPositionalViewError(positionalArgs) {
57
+ const received = positionalArgs.map((arg) => arg.trim()).filter((arg) => arg.length > 0);
58
+ const receivedList = received.join(", ");
59
+ // Check every received positional, not just the tail, so an invalid first
60
+ // positional (e.g. `pm calendar totally-bogus week`) is still surfaced.
61
+ const extras = received.filter((arg) => normalizeCalendarView(arg) === null);
62
+ // Fall back to the first valid view from `received` (normalized to canonical
63
+ // lowercase) for the recovery hint; recommend `agenda` only when none of the
64
+ // positionals are valid view names.
65
+ const recoveryView = received.map(normalizeCalendarView).find((view) => view !== null) ?? "agenda";
66
+ const hintLines = [`Calendar accepts at most one positional view (agenda|day|week|month), but received: ${receivedList}.`];
67
+ if (extras.length > 0) {
68
+ hintLines.push(`Unknown view alias(es): ${extras.join(", ")}.`);
69
+ }
70
+ hintLines.push("Use a single view, or pass extra arguments via flags:");
71
+ hintLines.push(` pm calendar ${recoveryView}`);
72
+ hintLines.push(` pm calendar --view ${recoveryView} --date +7d`);
73
+ return new PmCliError(hintLines.join("\n"), 2);
74
+ }
75
+
34
76
  function calendarCommand(name) {
35
77
  return {
36
78
  name,
@@ -44,10 +86,13 @@ function calendarCommand(name) {
44
86
  // are true positionals, so a positional view combined with --date/--from/etc.
45
87
  // must not be mistaken for multiple positional views.
46
88
  const firstFlagIndex = context.args.findIndex((arg) => arg.startsWith("-"));
47
- const positionalArgs = firstFlagIndex === -1 ? context.args : context.args.slice(0, firstFlagIndex);
89
+ const rawPositionalArgs = firstFlagIndex === -1 ? context.args : context.args.slice(0, firstFlagIndex);
90
+ // Drop empty/whitespace-only positionals (e.g. from `pm calendar agenda ""`
91
+ // when a shell variable is unset) so the count check stays meaningful.
92
+ const positionalArgs = rawPositionalArgs.filter((arg) => arg.trim().length > 0);
48
93
  const positionalView = positionalArgs[0]?.trim();
49
94
  if (positionalArgs.length > 1) {
50
- throw new Error("Calendar accepts at most one positional view: agenda|day|week|month.");
95
+ throw buildPositionalViewError(positionalArgs);
51
96
  }
52
97
  return runCalendarPackage(
53
98
  {
@@ -6,6 +6,21 @@ import type {
6
6
  import type { CalendarOptions } from "../../../../src/sdk/runtime.js";
7
7
  import { renderCalendarPackageOutput, runCalendarPackage } from "./runtime.js";
8
8
 
9
+ const CALENDAR_VIEW_NAMES = ["agenda", "day", "week", "month"] as const;
10
+
11
+ // Standalone error class so the package stays self-contained when installed
12
+ // outside the pm-cli source tree. The class name "PmCliError" lines up with
13
+ // the Sentry beforeSend filter (isExpectedCliErrorEvent) so usage errors do
14
+ // not leak into Sentry as crashes.
15
+ class PmCliError extends Error {
16
+ exitCode: number;
17
+ constructor(message: string, exitCode: number) {
18
+ super(message);
19
+ this.name = "PmCliError";
20
+ this.exitCode = exitCode;
21
+ }
22
+ }
23
+
9
24
  export const manifest = {
10
25
  name: "builtin-calendar",
11
26
  version: "0.1.0",
@@ -37,6 +52,38 @@ const calendarFlags = [
37
52
  { long: "--format", value_name: "value", value_type: "string", description: "Calendar output override: markdown|toon|json." },
38
53
  ] as const;
39
54
 
55
+ // The runtime lowercases `view` before validating (src/cli/commands/calendar.ts),
56
+ // so the unknown-alias / recovery-hint logic must match views case-insensitively
57
+ // or `pm calendar DAY ...` would wrongly flag DAY as unknown.
58
+ function normalizeCalendarView(arg: string): (typeof CALENDAR_VIEW_NAMES)[number] | null {
59
+ const normalized = arg.toLowerCase();
60
+ return (CALENDAR_VIEW_NAMES as readonly string[]).includes(normalized)
61
+ ? (normalized as (typeof CALENDAR_VIEW_NAMES)[number])
62
+ : null;
63
+ }
64
+
65
+ function buildPositionalViewError(positionalArgs: readonly string[]): PmCliError {
66
+ const received = positionalArgs.map((arg) => arg.trim()).filter((arg) => arg.length > 0);
67
+ const receivedList = received.join(", ");
68
+ // Check every received positional, not just the tail, so an invalid first
69
+ // positional (e.g. `pm calendar totally-bogus week`) is still surfaced.
70
+ const extras = received.filter((arg) => normalizeCalendarView(arg) === null);
71
+ // Fall back to the first valid view from `received` (normalized to canonical
72
+ // lowercase) for the recovery hint; recommend `agenda` only when none of the
73
+ // positionals are valid view names.
74
+ const recoveryView =
75
+ received.map(normalizeCalendarView).find((view): view is (typeof CALENDAR_VIEW_NAMES)[number] => view !== null) ??
76
+ "agenda";
77
+ const hintLines = [`Calendar accepts at most one positional view (agenda|day|week|month), but received: ${receivedList}.`];
78
+ if (extras.length > 0) {
79
+ hintLines.push(`Unknown view alias(es): ${extras.join(", ")}.`);
80
+ }
81
+ hintLines.push("Use a single view, or pass extra arguments via flags:");
82
+ hintLines.push(` pm calendar ${recoveryView}`);
83
+ hintLines.push(` pm calendar --view ${recoveryView} --date +7d`);
84
+ return new PmCliError(hintLines.join("\n"), 2);
85
+ }
86
+
40
87
  function calendarCommand(name: "calendar" | "cal"): CommandDefinition {
41
88
  return {
42
89
  name,
@@ -50,10 +97,13 @@ function calendarCommand(name: "calendar" | "cal"): CommandDefinition {
50
97
  // are true positionals, so a positional view combined with --date/--from/etc.
51
98
  // must not be mistaken for multiple positional views.
52
99
  const firstFlagIndex = context.args.findIndex((arg) => arg.startsWith("-"));
53
- const positionalArgs = firstFlagIndex === -1 ? context.args : context.args.slice(0, firstFlagIndex);
100
+ const rawPositionalArgs = firstFlagIndex === -1 ? context.args : context.args.slice(0, firstFlagIndex);
101
+ // Drop empty/whitespace-only positionals (e.g. from `pm calendar agenda ""`
102
+ // when a shell variable is unset) so the count check stays meaningful.
103
+ const positionalArgs = rawPositionalArgs.filter((arg) => arg.trim().length > 0);
54
104
  const positionalView = positionalArgs[0]?.trim();
55
105
  if (positionalArgs.length > 1) {
56
- throw new Error("Calendar accepts at most one positional view: agenda|day|week|month.");
106
+ throw buildPositionalViewError(positionalArgs);
57
107
  }
58
108
  return runCalendarPackage(
59
109
  {
@@ -90,6 +90,7 @@ function readPayloadGlobalOptions(payload) {
90
90
 
91
91
  export async function runCalendarPackage(options, global) {
92
92
  const loaded = await ensureCalendarCoreModule();
93
+ loaded.resolveCalendarOutputFormat(options, global);
93
94
  return loaded.runCalendar(options, global);
94
95
  }
95
96
 
@@ -99,6 +99,7 @@ function readPayloadGlobalOptions(payload: unknown): GlobalOptions {
99
99
 
100
100
  export async function runCalendarPackage(options: CalendarOptions, global: GlobalOptions): Promise<CalendarResult> {
101
101
  const loaded = await ensureCalendarCoreModule();
102
+ loaded.resolveCalendarOutputFormat(options, global);
102
103
  return loaded.runCalendar(options, global);
103
104
  }
104
105
 
@@ -29,7 +29,9 @@ async function loadGovernanceModule() {
29
29
  if (
30
30
  typeof loaded.runDedupeAudit === "function" &&
31
31
  typeof loaded.runCommentsAudit === "function" &&
32
- typeof loaded.runNormalize === "function"
32
+ typeof loaded.runNormalize === "function" &&
33
+ typeof loaded.readStringOption === "function" &&
34
+ typeof loaded.readBooleanOption === "function"
33
35
  ) {
34
36
  return loaded;
35
37
  }
@@ -41,41 +43,8 @@ async function loadGovernanceModule() {
41
43
  );
42
44
  }
43
45
 
44
- function readStringOption(options, key, aliases = []) {
45
- const keys = [key, ...aliases];
46
- for (const candidate of keys) {
47
- const value = options[candidate];
48
- if (typeof value === "string" && value.trim().length > 0) {
49
- return value;
50
- }
51
- }
52
- return undefined;
53
- }
54
-
55
- function readBooleanOption(options, key, aliases = []) {
56
- const keys = [key, ...aliases];
57
- for (const candidate of keys) {
58
- const value = options[candidate];
59
- if (value === undefined) {
60
- continue;
61
- }
62
- if (typeof value === "boolean") {
63
- return value;
64
- }
65
- if (typeof value === "string") {
66
- const normalized = value.trim().toLowerCase();
67
- if (normalized === "true" || normalized === "1" || normalized === "yes" || normalized === "on") {
68
- return true;
69
- }
70
- if (normalized === "false" || normalized === "0" || normalized === "no" || normalized === "off") {
71
- return false;
72
- }
73
- }
74
- }
75
- return undefined;
76
- }
77
-
78
- function normalizeDedupeAuditOptions(raw) {
46
+ function normalizeDedupeAuditOptions(sdk, raw) {
47
+ const readStringOption = sdk.readStringOption;
79
48
  return {
80
49
  mode: readStringOption(raw, "mode"),
81
50
  status: readStringOption(raw, "status"),
@@ -94,7 +63,9 @@ function normalizeDedupeAuditOptions(raw) {
94
63
  };
95
64
  }
96
65
 
97
- function normalizeCommentsAuditOptions(raw) {
66
+ function normalizeCommentsAuditOptions(sdk, raw) {
67
+ const readStringOption = sdk.readStringOption;
68
+ const readBooleanOption = sdk.readBooleanOption;
98
69
  return {
99
70
  status: readStringOption(raw, "status"),
100
71
  type: readStringOption(raw, "type"),
@@ -112,7 +83,9 @@ function normalizeCommentsAuditOptions(raw) {
112
83
  };
113
84
  }
114
85
 
115
- function normalizeNormalizeOptions(raw) {
86
+ function normalizeNormalizeOptions(sdk, raw) {
87
+ const readStringOption = sdk.readStringOption;
88
+ const readBooleanOption = sdk.readBooleanOption;
116
89
  return {
117
90
  status: readStringOption(raw, "filterStatus", ["filter_status", "status"]),
118
91
  list: {
@@ -145,15 +118,15 @@ function normalizeNormalizeOptions(raw) {
145
118
 
146
119
  export async function runDedupeAuditPackage(options, global) {
147
120
  const module = await ensureGovernanceModule();
148
- return module.runDedupeAudit(normalizeDedupeAuditOptions(options), global);
121
+ return module.runDedupeAudit(normalizeDedupeAuditOptions(module, options), global);
149
122
  }
150
123
 
151
124
  export async function runCommentsAuditPackage(options, global) {
152
125
  const module = await ensureGovernanceModule();
153
- return module.runCommentsAudit(normalizeCommentsAuditOptions(options), global);
126
+ return module.runCommentsAudit(normalizeCommentsAuditOptions(module, options), global);
154
127
  }
155
128
 
156
129
  export async function runNormalizePackage(options, global) {
157
130
  const module = await ensureGovernanceModule();
158
- return module.runNormalize(normalizeNormalizeOptions(options), global);
131
+ return module.runNormalize(normalizeNormalizeOptions(module, options), global);
159
132
  }