@unbrained/pm-cli 2026.8.27 → 2026.8.28

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 (105) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/CHANGELOG.md +25 -1
  3. package/dist/cli/error-guidance.js +61 -7
  4. package/dist/cli/register-annotations.js +58 -41
  5. package/dist/cli-bundle/bundle-manifest.json +121 -121
  6. package/dist/cli-bundle/chunks/{chunk-SH6P7FXI.js → chunk-BY2FQ2NI.js} +2 -2
  7. package/dist/cli-bundle/chunks/{chunk-TNX6HC54.js → chunk-E73FDIWT.js} +2 -2
  8. package/dist/cli-bundle/chunks/{chunk-SHMDY36D.js → chunk-FEVBFFCQ.js} +2 -2
  9. package/dist/cli-bundle/chunks/{chunk-CVBBGWW5.js → chunk-M7OXRQE3.js} +10 -6
  10. package/dist/cli-bundle/chunks/{chunk-R4ETAOJC.js → chunk-NBCBFVZI.js} +2 -2
  11. package/dist/cli-bundle/chunks/{chunk-SKXLJIEK.js → chunk-NTXZHRKA.js} +45 -45
  12. package/dist/cli-bundle/chunks/{chunk-52EKTW6V.js → chunk-QE6WQXFO.js} +2 -2
  13. package/dist/cli-bundle/chunks/{chunk-MFNTKMTI.js → chunk-TIQ6AMH2.js} +2 -2
  14. package/dist/cli-bundle/chunks/chunk-X2RROGZE.js +2 -0
  15. package/dist/cli-bundle/chunks/{chunk-OS27HHBN.js → chunk-XRVVYRRO.js} +28 -28
  16. package/dist/cli-bundle/chunks/{chunk-3OO3W6FW.js → chunk-XWEQGHHG.js} +2 -2
  17. package/dist/cli-bundle/chunks/{register-list-query-EUWM6VII.js → register-list-query-J35ZPQQ5.js} +2 -2
  18. package/dist/cli-bundle/chunks/{register-mutation-FD4HSAVU.js → register-mutation-J6XJJOGU.js} +4 -4
  19. package/dist/cli-bundle/chunks/{register-operations-HRMNFEC3.js → register-operations-AE3JEMFT.js} +2 -2
  20. package/dist/cli-bundle/chunks/{register-setup-33GNICLX.js → register-setup-OQERLLWE.js} +2 -2
  21. package/dist/cli-bundle/focused-chunks/{chunk-4BR5UU52.js → chunk-4ZDRZYYJ.js} +41 -41
  22. package/dist/cli-bundle/focused-chunks/{chunk-MXTYGECH.js → chunk-AHAM2HAU.js} +2 -2
  23. package/dist/cli-bundle/focused-chunks/{chunk-SUBSWYW3.js → chunk-JZYPPMXF.js} +2 -2
  24. package/dist/cli-bundle/focused-chunks/chunk-LLNTHF5X.js +2 -0
  25. package/dist/cli-bundle/focused-chunks/chunk-LYFWQMVC.js +2 -0
  26. package/dist/cli-bundle/focused-chunks/{chunk-EKX37ZHA.js → chunk-THEPQMLX.js} +2 -2
  27. package/dist/cli-bundle/focused-chunks/chunk-YJLDHJOD.js +2 -0
  28. package/dist/cli-bundle/main.js +14 -14
  29. package/dist/cli-bundle/sdk-authoring.js +1 -1
  30. package/dist/cli-bundle/sdk-contracts.js +2 -2
  31. package/dist/cli-bundle/sdk-core.js +26 -26
  32. package/dist/cli-bundle/sdk-governance.js +1 -1
  33. package/dist/cli-bundle/sdk-merge.js +20 -20
  34. package/dist/cli-bundle/sdk-query.js +1 -1
  35. package/dist/cli-bundle/sdk-runtime.js +1 -1
  36. package/dist/cli-bundle/sdk-testing.js +1 -1
  37. package/dist/cli-bundle/sdk.js +10 -10
  38. package/dist/mcp/tool-definitions.js +5 -5
  39. package/dist/sdk/agent/command-recovery.js +3 -3
  40. package/dist/sdk/agent/task-transcript-contracts.d.ts +52 -0
  41. package/dist/sdk/agent/task-transcript-contracts.js +198 -0
  42. package/dist/sdk/annotations.d.ts +5 -2
  43. package/dist/sdk/annotations.js +66 -36
  44. package/dist/sdk/cli-bootstrap.d.ts +2 -8
  45. package/dist/sdk/cli-bootstrap.js +7 -67
  46. package/dist/sdk/cli-contracts/bootstrap-command-scanner.d.ts +23 -0
  47. package/dist/sdk/cli-contracts/bootstrap-command-scanner.js +80 -0
  48. package/dist/sdk/cli-contracts/flag-contracts.js +5 -2
  49. package/dist/sdk/cli-contracts/flag-lexicon-contracts.js +5 -5
  50. package/dist/sdk/cli-contracts/tool-parameter-tables.js +15 -2
  51. package/dist/sdk/cli-contracts/tool-schema.d.ts +1 -1
  52. package/dist/sdk/cli-contracts/tool-schema.js +16 -3
  53. package/dist/sdk/comments.d.ts +4 -0
  54. package/dist/sdk/comments.js +2 -2
  55. package/dist/sdk/completion.js +9 -5
  56. package/dist/sdk/contracts.d.ts +1 -0
  57. package/dist/sdk/contracts.js +3 -2
  58. package/dist/sdk/extension/install-sources.d.ts +13 -0
  59. package/dist/sdk/extension/install-sources.js +62 -30
  60. package/dist/sdk/generated/generated-error-code-catalog-part-1.js +26 -2
  61. package/dist/sdk/generated/generated-error-code-catalog-part-2.js +26 -14
  62. package/dist/sdk/index.d.ts +1 -0
  63. package/dist/sdk/index.js +3 -2
  64. package/dist/sdk/learnings.d.ts +4 -0
  65. package/dist/sdk/learnings.js +7 -4
  66. package/dist/sdk/lifecycle/close.js +4 -3
  67. package/dist/sdk/notes.d.ts +4 -0
  68. package/dist/sdk/notes.js +2 -2
  69. package/dist/sdk/runtime-input.js +5 -2
  70. package/dist/sdk/runtime.d.ts +6 -6
  71. package/dist/sdk/runtime.js +8 -8
  72. package/docs/COMMANDS.md +4 -3
  73. package/docs/OUTPUT_TOKEN_ACCOUNTING.md +20 -7
  74. package/docs/RELEASING.md +9 -1
  75. package/docs/SDK.md +12 -0
  76. package/docs/SDK_CONTEXT_INTEGRITY.md +12 -1
  77. package/docs/SDK_RUNTIME_BOUNDARIES.md +10 -0
  78. package/docs/TESTING.md +6 -2
  79. package/docs/agent-task-token-baseline.json +97 -11
  80. package/docs/agent-task-transcripts.json +211 -0
  81. package/docs/generated/FLAG_LEXICON_BUDGETS.md +3 -3
  82. package/docs/generated/REFUSAL_CLOSURE_CENSUS.md +6 -3
  83. package/docs/performance/cli-transport-overhead.md +10 -2
  84. package/marketplace.json +2 -2
  85. package/package.json +2 -2
  86. package/packages/pm-beads/package.json +1 -1
  87. package/packages/pm-calendar/package.json +1 -1
  88. package/packages/pm-command-kit/package.json +1 -1
  89. package/packages/pm-digital-twin/package.json +1 -1
  90. package/packages/pm-governance-audit/package.json +1 -1
  91. package/packages/pm-guide-shell/package.json +1 -1
  92. package/packages/pm-kanban/package.json +1 -1
  93. package/packages/pm-lifecycle-hooks/package.json +1 -1
  94. package/packages/pm-linked-test-adapters/package.json +1 -1
  95. package/packages/pm-search-advanced/package.json +1 -1
  96. package/packages/pm-templates/package.json +1 -1
  97. package/packages/pm-todos/package.json +1 -1
  98. package/packages/pm-vcs/package.json +1 -1
  99. package/plugins/pm-claude/.claude-plugin/plugin.json +1 -1
  100. package/plugins/pm-codex/.codex-plugin/plugin.json +1 -1
  101. package/sdk/public-surface.json +197 -25
  102. package/dist/cli-bundle/chunks/chunk-QTO7USTH.js +0 -2
  103. package/dist/cli-bundle/focused-chunks/chunk-2AGZ5BRT.js +0 -2
  104. package/dist/cli-bundle/focused-chunks/chunk-AQ5IYEZZ.js +0 -2
  105. package/dist/cli-bundle/focused-chunks/chunk-YHWHX6YY.js +0 -2
@@ -6,14 +6,14 @@
6
6
  },
7
7
  "metadata": {
8
8
  "description": "Official marketplace for pm CLI — native git-based project management for Claude Code and AI coding agents.",
9
- "version": "2026.8.27"
9
+ "version": "2026.8.28"
10
10
  },
11
11
  "plugins": [
12
12
  {
13
13
  "name": "pm-claude",
14
14
  "source": "./plugins/pm-claude",
15
15
  "description": "Native pm CLI integration for Claude Code — 28 MCP tools, 5 workflow skills, 14 slash commands, 4 subagents, hybrid TUI task tracking, session context injection, and coordination subagents for git-based project management without leaving Claude Code.",
16
- "version": "2026.8.27",
16
+ "version": "2026.8.28",
17
17
  "author": {
18
18
  "name": "unbrained",
19
19
  "url": "https://github.com/unbraind/pm-cli"
package/CHANGELOG.md CHANGED
@@ -1,5 +1,29 @@
1
1
  # Changelog
2
2
 
3
+ ## 2026.8.28 - 2026-08-28
4
+
5
+ ### Fixed
6
+
7
+ - Repository test runner inherits production Sentry and turns negative CLI fixtures into release-blocking incidents ([pm-5ug5xq](https://github.com/unbraind/pm-cli/blob/main/.agents/pm/issues/pm-5ug5xq.toon))
8
+ - Broad squash commits overflow hosted-analysis tree lookup and falsely lose reviewed PR provenance ([pm-gactwj](https://github.com/unbraind/pm-cli/blob/main/.agents/pm/issues/pm-gactwj.toon))
9
+ - Golden agent-transcript replay: measure tokens-per-completed-task as a CI-visible DX regression gate ([pm-8pnj](https://github.com/unbraind/pm-cli/blob/main/.agents/pm/features/pm-8pnj.toon))
10
+ - Unknown-option refusal textual flags bypass structured global-option filtering ([pm-x4wbui](https://github.com/unbraind/pm-cli/blob/main/.agents/pm/issues/pm-x4wbui.toon))
11
+ - Agent-task transcript recovery accepts a refusal as a successful retry ([pm-o360q6](https://github.com/unbraind/pm-cli/blob/main/.agents/pm/issues/pm-o360q6.toon))
12
+ - Close lifecycle bypasses reproducible process clock ([pm-hmpu4p](https://github.com/unbraind/pm-cli/blob/main/.agents/pm/issues/pm-hmpu4p.toon))
13
+ - GH-1132: opt-in idempotent annotation append (ifAbsent/--if-absent) prevents retry-driven context duplication; duplicate appends remain available by default ([pm-ea1yh2](https://github.com/unbraind/pm-cli/blob/main/.agents/pm/issues/pm-ea1yh2.toon))
14
+ - Agent-task transcripts accept declared no-effect and partial-effect successful exits ([pm-wanwyc](https://github.com/unbraind/pm-cli/blob/main/.agents/pm/issues/pm-wanwyc.toon))
15
+ - Agent-task baselines accept missing task and composite token ceilings ([pm-aksavu](https://github.com/unbraind/pm-cli/blob/main/.agents/pm/issues/pm-aksavu.toon))
16
+ - Agent-task closeout claims lack linked full-verification commands ([pm-ervmsc](https://github.com/unbraind/pm-cli/blob/main/.agents/pm/issues/pm-ervmsc.toon))
17
+ - CLI transport-floor RSS admission false-fails on single-sample page noise ([pm-pz49xc](https://github.com/unbraind/pm-cli/blob/main/.agents/pm/issues/pm-pz49xc.toon))
18
+ - Unknown-option refusals identify the actual rejected flag and fail closed at delimiters ([pm-fo8g7j](https://github.com/unbraind/pm-cli/blob/main/.agents/pm/issues/pm-fo8g7j.toon))
19
+ - Agent-task gate accepts incidental substrings as required-field completeness ([pm-o7u08u](https://github.com/unbraind/pm-cli/blob/main/.agents/pm/issues/pm-o7u08u.toon))
20
+ - Completed-task transcripts require terminal success, exclusive refusal metadata, and complete recovery ([pm-wa39rl](https://github.com/unbraind/pm-cli/blob/main/.agents/pm/issues/pm-wa39rl.toon))
21
+ - Agent-task token replay trusts missing or incorrect accounting estimates ([pm-ok2kdn](https://github.com/unbraind/pm-cli/blob/main/.agents/pm/issues/pm-ok2kdn.toon))
22
+
23
+ ### Security
24
+
25
+ - GH-1131: macOS Node 24 nightly cannot create the Skills-over-MCP special-file socket fixture ([pm-k23c4c](https://github.com/unbraind/pm-cli/blob/main/.agents/pm/issues/pm-k23c4c.toon))
26
+
3
27
  ## 2026.8.27 - 2026-08-27
4
28
 
5
29
  ### Added
@@ -887,10 +911,10 @@
887
911
 
888
912
  ### Other
889
913
 
914
+ - CLI transport overhead budget: gate the per-invocation bootstrap floor and the CLI-vs-SDK delta, not just absolute scale numbers ([pm-yse5dt](https://github.com/unbraind/pm-cli/blob/main/.agents/pm/tasks/pm-yse5dt.toon))
890
915
  - CI/CD + test-suite performance: in-process CLI runner and dedupe redundant matrix legs ([pm-7rlp](https://github.com/unbraind/pm-cli/blob/main/.agents/pm/chores/pm-7rlp.toon))
891
916
  - ADR: agent identity model — stable author namespace plus structured harness, model, and session provenance ([pm-qwuber](https://github.com/unbraind/pm-cli/blob/main/.agents/pm/decisions/pm-qwuber.toon))
892
917
  - Public SDK surface shape: 881 exports behind one flat entrypoint with no capability tiering and a 250ms eager import cost ([pm-38bskj](https://github.com/unbraind/pm-cli/blob/main/.agents/pm/tasks/pm-38bskj.toon))
893
- - CLI transport overhead budget: gate the per-invocation bootstrap floor and the CLI-vs-SDK delta, not just absolute scale numbers ([pm-yse5dt](https://github.com/unbraind/pm-cli/blob/main/.agents/pm/tasks/pm-yse5dt.toon))
894
918
 
895
919
  ## 2026.7.25 - 2026-07-25
896
920
 
@@ -1,9 +1,10 @@
1
1
 
2
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="bb31fb1f-bf61-5eab-aa63-5d04fbac282f")}catch(e){}}();
2
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="ca79853d-468c-5522-b5f9-6509c66e3d9b")}catch(e){}}();
3
3
  import { resolveRecoveryCommandName } from "../sdk/agent/command-recovery.js";
4
4
  import { projectPmDiagnosticOutput, projectPmDiagnosticText, } from "../sdk/cli-contracts/agent-output-contracts.js";
5
5
  import { renderPmCommand } from "./argv-utils.js";
6
6
  import { discoverNearbyPmRoot } from "../sdk/tracker-root-discovery.js";
7
+ import { stripGlobalBootstrapTokens } from "../sdk/cli-bootstrap.js";
7
8
  /** Compact an error envelope to fields that change the caller's next action. */
8
9
  export function projectLeanErrorEnvelope(envelope) {
9
10
  const { why: _why, title: _title, ...actionable } = envelope;
@@ -307,15 +308,36 @@ function attachStructuredGuidanceDetails(payload, message) {
307
308
  function resolveRefusalCandidateFlag(message, normalizedArgs) {
308
309
  if (message.flag)
309
310
  return message.flag;
310
- return message.recovery?.provided_fields?.find((field) => field !== "--json" &&
311
- field !== "--quiet" &&
312
- normalizedArgs.includes(field));
311
+ const isAdmissibleCandidate = (flag) => {
312
+ const canonicalFlag = flag.split("=", 1)[0];
313
+ return (stripGlobalBootstrapTokens([canonicalFlag]).length > 0 &&
314
+ !["--help", "--no-changed-fields", "--version"].includes(canonicalFlag) &&
315
+ normalizedArgs.some((argument) => argument === canonicalFlag ||
316
+ argument.startsWith(`${canonicalFlag}=`)));
317
+ };
318
+ const mentionedFlag = [
319
+ ...`${message.title} ${message.happened}`.matchAll(/--[A-Za-z0-9][A-Za-z0-9_-]*/gu),
320
+ ]
321
+ .map((match) => match[0])
322
+ .find((flag) => isAdmissibleCandidate(flag));
323
+ if (mentionedFlag)
324
+ return mentionedFlag;
325
+ return message.recovery?.provided_fields
326
+ ?.map((field) => field.split("=", 1)[0])
327
+ .find((field) => isAdmissibleCandidate(field));
313
328
  }
314
329
  function resolveRefusalRejectedValue(message, normalizedArgs, candidateFlag) {
315
330
  if (message.value !== undefined)
316
331
  return message.value;
317
332
  if (candidateFlag) {
318
- return normalizedArgs[normalizedArgs.indexOf(candidateFlag) + 1];
333
+ const candidateArgument = normalizedArgs.find((argument) => argument === candidateFlag || argument.startsWith(`${candidateFlag}=`));
334
+ if (candidateArgument?.startsWith(`${candidateFlag}=`)) {
335
+ return candidateArgument.slice(candidateFlag.length + 1);
336
+ }
337
+ const candidateIndex = normalizedArgs.indexOf(candidateFlag);
338
+ return candidateIndex >= 0
339
+ ? normalizedArgs[candidateIndex + 1]
340
+ : undefined;
319
341
  }
320
342
  const allowedValues = message.recovery?.allowed_values;
321
343
  if (!allowedValues?.length)
@@ -941,6 +963,34 @@ function buildUnknownOptionNextSteps(optionName, commandName, suggestions, candi
941
963
  : undefined,
942
964
  ].filter((entry) => typeof entry === "string");
943
965
  }
966
+ /**
967
+ * Publish a shell-free correction only when removing the unknown token leaves
968
+ * positional ownership unambiguous; explicit producer guidance stays authoritative.
969
+ */
970
+ function resolveUnknownOptionRetry(context, optionName) {
971
+ if (context.suggestedRetryCommand !== undefined) {
972
+ return { retryCommand: context.suggestedRetryCommand };
973
+ }
974
+ const normalizedArgs = context.normalizedInvocationArgs;
975
+ if (normalizedArgs === undefined)
976
+ return {};
977
+ if (normalizedArgs.filter((argument) => argument === optionName || argument.startsWith(`${optionName}=`)).length !== 1) {
978
+ return {};
979
+ }
980
+ const optionIndex = normalizedArgs.findIndex((argument) => argument === optionName || argument.startsWith(`${optionName}=`));
981
+ if (optionIndex < 0 ||
982
+ normalizedArgs.includes("--") ||
983
+ (!normalizedArgs[optionIndex]?.includes("=") &&
984
+ optionIndex !== normalizedArgs.length - 1 &&
985
+ normalizedArgs[optionIndex + 1]?.startsWith("-") !== true)) {
986
+ return {};
987
+ }
988
+ const suggestedRetryArgs = stripGlobalBootstrapTokens(normalizedArgs.filter((_, index) => index !== optionIndex));
989
+ return {
990
+ retryCommand: renderPmCommand(suggestedRetryArgs),
991
+ suggestedRetryArgs,
992
+ };
993
+ }
944
994
  function buildUnknownOptionGuidance(message, commandName, context) {
945
995
  const unknownOption = message.match(/unknown option '([^']+)'/);
946
996
  if (!unknownOption) {
@@ -949,7 +999,7 @@ function buildUnknownOptionGuidance(message, commandName, context) {
949
999
  const guidanceContext = context ?? {};
950
1000
  const optionName = unknownOption[1];
951
1001
  const suggestions = normalizeOptionFlags(guidanceContext.unknownOptionSuggestions) ?? [];
952
- const retryCommand = guidanceContext.suggestedRetryCommand;
1002
+ const { retryCommand, suggestedRetryArgs } = resolveUnknownOptionRetry(guidanceContext, optionName);
953
1003
  if (commandName === "update" &&
954
1004
  (optionName === "--file" || optionName === "--doc")) {
955
1005
  return buildUnsupportedUpdateOptionGuidance(optionName, context, suggestions);
@@ -966,10 +1016,14 @@ function buildUnknownOptionGuidance(message, commandName, context) {
966
1016
  happened: `Commander does not recognize option ${optionName} for this command path.`,
967
1017
  required: UNKNOWN_OPTION_REQUIRED_BY_SCOPE[candidateContext.optionScope],
968
1018
  why: "Option contracts are command-specific and intentionally validated.",
1019
+ flag: optionName,
1020
+ value: optionName,
969
1021
  examples,
970
1022
  nextSteps,
971
1023
  recovery: buildCommanderRecoveryPayload(context, {
972
1024
  suggested_flags: suggestions.length > 0 ? suggestions : undefined,
1025
+ suggested_retry: retryCommand,
1026
+ suggested_retry_args: suggestedRetryArgs,
973
1027
  candidate_commands: candidateContext.otherCommands,
974
1028
  candidate_commands_total: candidateContext.candidateTotal || undefined,
975
1029
  candidate_commands_truncated: guidanceContext.unknownOptionOtherCommandsTruncated,
@@ -1297,4 +1351,4 @@ export const _testOnly = {
1297
1351
  resolveKnownPackageCommandHint,
1298
1352
  };
1299
1353
  //# sourceMappingURL=error-guidance.js.map
1300
- //# debugId=bb31fb1f-bf61-5eab-aa63-5d04fbac282f
1354
+ //# debugId=ca79853d-468c-5522-b5f9-6509c66e3d9b
@@ -1,5 +1,5 @@
1
1
 
2
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="263a15b4-5537-5e11-b0c0-3da453ee0357")}catch(e){}}();
2
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="775518a4-ff3e-5a54-8fbd-b2c4863e23db")}catch(e){}}();
3
3
  import { EXIT_CODE, PmCliError, } from "../sdk/runtime-primitives.js";
4
4
  import { renderPmCommand } from "../sdk/command-line.js";
5
5
  import { runComments } from "./commands/comments.js";
@@ -93,13 +93,33 @@ function resolveCommentSources(text, options) {
93
93
  deleteIndex !== undefined,
94
94
  };
95
95
  }
96
- function resolveSingleTextSource(label, positional, options) {
96
+ /** Resolves every mutation-bearing source for notes and learnings once. */
97
+ function resolveSingleAnnotationSources(label, positional, options) {
97
98
  const addFromOption = resolveAnnotationTextAliases(label, options, label === "note" ? ["add", "text", "note"] : ["add", "text"]);
98
99
  const addFromPositional = typeof positional === "string" ? positional : undefined;
99
100
  if (addFromOption !== undefined && addFromPositional !== undefined) {
100
101
  throw new PmCliError(`Specify ${label} text either as positional [text] or with --add, not both`, EXIT_CODE.USAGE);
101
102
  }
102
- return addFromOption ?? addFromPositional;
103
+ const add = addFromOption ?? addFromPositional;
104
+ const addJson = label === "note" ? readOptionString(options, "addJson") : undefined;
105
+ const readFromStdin = options.stdin === true;
106
+ const readFromFile = readOptionString(options, "file");
107
+ const editIndex = typeof options.edit === "number" ? options.edit : undefined;
108
+ const deleteIndex = typeof options.delete === "number" ? options.delete : undefined;
109
+ return {
110
+ add,
111
+ addJson,
112
+ readFromStdin,
113
+ readFromFile,
114
+ editIndex,
115
+ deleteIndex,
116
+ isMutation: typeof add === "string" ||
117
+ addJson !== undefined ||
118
+ readFromStdin ||
119
+ readFromFile !== undefined ||
120
+ editIndex !== undefined ||
121
+ deleteIndex !== undefined,
122
+ };
103
123
  }
104
124
  async function runCommentsAction(id, text, options, command) {
105
125
  const globalOptions = getGlobalOptions(command);
@@ -114,12 +134,13 @@ async function runCommentsAction(id, text, options, command) {
114
134
  delete: sources.deleteIndex,
115
135
  limit: readOptionString(options, "limit"),
116
136
  fullHistory: options.fullHistory === true,
137
+ ifAbsent: options.ifAbsent === true,
117
138
  author: readOptionString(options, "author"),
118
139
  message: readOptionString(options, "message"),
119
140
  ownershipAppendBypass: options.ownershipAppendBypass === true,
120
141
  force: Boolean(options.force),
121
142
  }, globalOptions);
122
- if (sources.isMutation) {
143
+ if (sources.isMutation && result.changed !== false) {
123
144
  await invalidateSearchCachesForMutation(globalOptions, result);
124
145
  }
125
146
  printAnnotationResult("comments", result, globalOptions, startedAt);
@@ -128,30 +149,26 @@ async function runNotesAction(id, text, options, command) {
128
149
  const globalOptions = getGlobalOptions(command);
129
150
  const startedAt = Date.now();
130
151
  assertNoTransposedAnnotationAction("notes", id, text, options);
131
- const add = resolveSingleTextSource("note", text, options);
152
+ const sources = resolveSingleAnnotationSources("note", text, options);
132
153
  const result = await runNotes(id, {
133
- add,
134
- addJson: readOptionString(options, "addJson"),
135
- stdin: options.stdin === true,
136
- file: readOptionString(options, "file"),
137
- edit: typeof options.edit === "number" ? options.edit : undefined,
138
- delete: typeof options.delete === "number" ? options.delete : undefined,
154
+ add: sources.add,
155
+ addJson: sources.addJson,
156
+ stdin: sources.readFromStdin,
157
+ file: sources.readFromFile,
158
+ edit: sources.editIndex,
159
+ delete: sources.deleteIndex,
139
160
  limit: readOptionString(options, "limit"),
140
161
  since: readOptionString(options, "since"),
141
162
  eventType: readOptionString(options, "eventType"),
142
163
  includeMeta: options.includeMeta === true,
143
164
  fullHistory: options.fullHistory === true,
165
+ ifAbsent: options.ifAbsent === true,
144
166
  author: readOptionString(options, "author"),
145
167
  message: readOptionString(options, "message"),
146
168
  ownershipAppendBypass: options.ownershipAppendBypass === true,
147
169
  force: Boolean(options.force),
148
170
  }, globalOptions);
149
- if (typeof add === "string" ||
150
- typeof options.addJson === "string" ||
151
- options.stdin === true ||
152
- typeof options.file === "string" ||
153
- typeof options.edit === "number" ||
154
- typeof options.delete === "number") {
171
+ if (sources.isMutation && result.changed !== false) {
155
172
  await invalidateSearchCachesForMutation(globalOptions, result);
156
173
  }
157
174
  printAnnotationResult("notes", result, globalOptions, startedAt);
@@ -160,25 +177,22 @@ async function runLearningsAction(id, text, options, command) {
160
177
  const globalOptions = getGlobalOptions(command);
161
178
  const startedAt = Date.now();
162
179
  assertNoTransposedAnnotationAction("learnings", id, text, options);
163
- const add = resolveSingleTextSource("learning", text, options);
180
+ const sources = resolveSingleAnnotationSources("learning", text, options);
164
181
  const result = await runLearnings(id, {
165
- add,
166
- stdin: options.stdin === true,
167
- file: readOptionString(options, "file"),
168
- edit: typeof options.edit === "number" ? options.edit : undefined,
169
- delete: typeof options.delete === "number" ? options.delete : undefined,
182
+ add: sources.add,
183
+ stdin: sources.readFromStdin,
184
+ file: sources.readFromFile,
185
+ edit: sources.editIndex,
186
+ delete: sources.deleteIndex,
170
187
  limit: readOptionString(options, "limit"),
171
188
  fullHistory: options.fullHistory === true,
189
+ ifAbsent: options.ifAbsent === true,
172
190
  author: readOptionString(options, "author"),
173
191
  message: readOptionString(options, "message"),
174
192
  ownershipAppendBypass: options.ownershipAppendBypass === true,
175
193
  force: Boolean(options.force),
176
194
  }, globalOptions);
177
- if (typeof add === "string" ||
178
- options.stdin === true ||
179
- typeof options.file === "string" ||
180
- typeof options.edit === "number" ||
181
- typeof options.delete === "number") {
195
+ if (sources.isMutation && result.changed !== false) {
182
196
  await invalidateSearchCachesForMutation(globalOptions, result);
183
197
  }
184
198
  printAnnotationResult("learnings", result, globalOptions, startedAt);
@@ -195,14 +209,15 @@ export function registerAnnotationCommands(program, parsePositiveInteger) {
195
209
  .command("comments")
196
210
  .argument("<id>", "Item id")
197
211
  .argument("[text]", "Optional comment text shorthand (equivalent to --add)")
198
- .option("--add <text>", "Add one comment entry (plain text fallback, text=<value>, markdown pairs, or - for stdin; CSV-like key fragments are preserved as plain text unless text is explicit)")
212
+ .option("--add <text>", "Append comment text; use text=<value> for structured input or - for stdin")
199
213
  .option("--stdin", "Read comment text from stdin (supports multiline markdown)")
200
214
  .option("--file <path>", "Read comment text from a file or stdin (-)")
201
- .option("--edit <index>", "Replace the comment at 1-based <index> (replacement text from positional [text], --add, --stdin, or --file)", parsePositiveInteger("--edit"))
215
+ .option("--edit <index>", "Replace one comment using positional text, --add, --stdin, or --file", parsePositiveInteger("--edit"))
202
216
  .option("--delete <index>", "Delete the comment at 1-based <index>", parsePositiveInteger("--delete"))
203
217
  .option("--limit <n>", "Return only latest n comments")
204
- .option("--full-history", "Return the complete post-mutation comment history instead of a bounded receipt")
205
- .option("--author [value]", "Comment author (optional; falls back to PM_AUTHOR/settings)")
218
+ .option("--full-history", "Return complete comment history instead of a bounded mutation receipt")
219
+ .option("--if-absent", "Append only when resolved author and text are absent")
220
+ .option("--author [value]", "Comment author; defaults to PM_AUTHOR or settings")
206
221
  .option("--message <value>", "History message")
207
222
  .option("--force", "Force ownership override")
208
223
  .description("List, add, edit, or delete comments for an item.")
@@ -213,10 +228,10 @@ export function registerAnnotationCommands(program, parsePositiveInteger) {
213
228
  const notesCommand = program
214
229
  .command("notes")
215
230
  .argument("<id>", "Item id")
216
- .argument("[text]", "Optional note text shorthand (equivalent to --add; use - for stdin)")
231
+ .argument("[text]", "Optional note text shorthand for --add")
217
232
  .option("--add <text>", "Add a text note (- reads stdin)")
218
233
  .option("--note <text>", "Alias for --add")
219
- .option("--add-json <json>", "Append a merge-safe JSON event")
234
+ .option("--add-json <json>", "Append a JSON event")
220
235
  .option("--stdin", "Read note text from stdin")
221
236
  .option("--file <path>", "Read note from file or stdin (-)")
222
237
  .option("--edit <index>", "Replace a 1-based note using the selected text input", parsePositiveInteger("--edit"))
@@ -225,7 +240,8 @@ export function registerAnnotationCommands(program, parsePositiveInteger) {
225
240
  .option("--since <timestamp>", "Filter JSON events from this ISO time")
226
241
  .option("--event-type <value>", "Filter JSON events by top-level type")
227
242
  .option("--include-meta", "Include count and truncation metadata")
228
- .option("--full-history", "Return the complete post-mutation note history instead of a bounded receipt")
243
+ .option("--full-history", "Return complete note history instead of a bounded receipt")
244
+ .option("--if-absent", "Append only when resolved author and text are absent")
229
245
  .option("--author [value]", "Author; defaults to PM_AUTHOR/settings")
230
246
  .option("--message <value>", "History message")
231
247
  .option("--force", "Force ownership override")
@@ -235,14 +251,15 @@ export function registerAnnotationCommands(program, parsePositiveInteger) {
235
251
  const learningsCommand = program
236
252
  .command("learnings")
237
253
  .argument("<id>", "Item id")
238
- .argument("[text]", "Optional learning text shorthand (equivalent to --add; use - for stdin)")
239
- .option("--add <text>", "Add one learning entry (plain text fallback, text=<value>, markdown pairs, or - for stdin; CSV-like key fragments are preserved as plain text unless text is explicit)")
240
- .option("--stdin", "Read learning text from stdin (supports multiline markdown)")
254
+ .argument("[text]", "Optional learning text shorthand for --add")
255
+ .option("--add <text>", "Append learning text; use text=<value> or - for stdin")
256
+ .option("--stdin", "Read learning text from stdin")
241
257
  .option("--file <path>", "Read learning text from a file or stdin (-)")
242
- .option("--edit <index>", "Replace the learning at 1-based <index> (replacement text from positional [text], --add, --stdin, or --file)", parsePositiveInteger("--edit"))
258
+ .option("--edit <index>", "Replace one learning using positional text, --add, --stdin, or --file", parsePositiveInteger("--edit"))
243
259
  .option("--delete <index>", "Delete the learning at 1-based <index>", parsePositiveInteger("--delete"))
244
260
  .option("--limit <n>", "Return only latest n learnings")
245
- .option("--full-history", "Return the complete post-mutation learning history instead of a bounded receipt")
261
+ .option("--full-history", "Return complete learning history instead of a bounded receipt")
262
+ .option("--if-absent", "Append only when resolved author and text are absent")
246
263
  .option("--author [value]", "Learning author (optional; falls back to PM_AUTHOR/settings)")
247
264
  .option("--message <value>", "History message")
248
265
  .option("--force", "Force ownership override")
@@ -251,4 +268,4 @@ export function registerAnnotationCommands(program, parsePositiveInteger) {
251
268
  addHiddenOption(learningsCommand, "--text <text>", "Alias for --add", false);
252
269
  }
253
270
  //# sourceMappingURL=register-annotations.js.map
254
- //# debugId=263a15b4-5537-5e11-b0c0-3da453ee0357
271
+ //# debugId=775518a4-ff3e-5a54-8fbd-b2c4863e23db