@unbrained/pm-cli 2026.11.3 → 2026.11.4

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 (203) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +10 -0
  3. package/dist/cli/error-guidance.d.ts +14 -35
  4. package/dist/cli/error-guidance.js +2 -2
  5. package/dist/cli/expected-revisions.d.ts +55 -0
  6. package/dist/cli/expected-revisions.js +2 -0
  7. package/dist/cli/main.js +2 -2
  8. package/dist/cli/runtime/invocation-options.js +1 -1
  9. package/dist/cli-bundle/bundle-manifest.json +1 -1
  10. package/dist/cli-bundle/chunks/{chunk-UYNMKUGA.js → chunk-2GW662DP.js} +2 -2
  11. package/dist/cli-bundle/chunks/{chunk-32BCU6HN.js → chunk-3OXQGGRD.js} +46 -46
  12. package/dist/cli-bundle/chunks/chunk-5BVZ32G7.js +2 -0
  13. package/dist/cli-bundle/chunks/chunk-5S6RT5LX.js +9 -0
  14. package/dist/cli-bundle/chunks/chunk-5Z6LWFDL.js +31 -0
  15. package/dist/cli-bundle/chunks/{chunk-MQJXZX3Q.js → chunk-6T2V3UIK.js} +2 -2
  16. package/dist/cli-bundle/chunks/{chunk-5JZMF4M5.js → chunk-7PGNAHNW.js} +2 -2
  17. package/dist/cli-bundle/chunks/chunk-7RCI7AAA.js +20 -0
  18. package/dist/cli-bundle/chunks/chunk-AQ5UHHQS.js +2 -0
  19. package/dist/cli-bundle/chunks/chunk-BK2XAQVC.js +2 -0
  20. package/dist/cli-bundle/chunks/{chunk-TZ3D3DPQ.js → chunk-BRIPGUAO.js} +2 -2
  21. package/dist/cli-bundle/chunks/chunk-BWAXA6S4.js +6 -0
  22. package/dist/cli-bundle/chunks/{chunk-5IPXDZKU.js → chunk-CERNIZLB.js} +66 -66
  23. package/dist/cli-bundle/chunks/chunk-DSCFJTHB.js +122 -0
  24. package/dist/cli-bundle/chunks/{chunk-JPKIUWLG.js → chunk-L4BKTPJ6.js} +2 -2
  25. package/dist/cli-bundle/chunks/{chunk-Y7H55PCY.js → chunk-NPEDFOVQ.js} +2 -2
  26. package/dist/cli-bundle/chunks/{chunk-2CBHQXYJ.js → chunk-ODMJ3HIB.js} +2 -2
  27. package/dist/cli-bundle/chunks/{chunk-DEJHTFEF.js → chunk-QN7VCOUU.js} +2 -2
  28. package/dist/cli-bundle/chunks/chunk-QQHMNUZT.js +2 -0
  29. package/dist/cli-bundle/chunks/chunk-SRDO2NJU.js +2 -0
  30. package/dist/cli-bundle/chunks/chunk-T56MB6SF.js +2 -0
  31. package/dist/cli-bundle/chunks/chunk-TX2W6G37.js +3 -0
  32. package/dist/cli-bundle/chunks/item-revision-UAZOBP2Q.js +2 -0
  33. package/dist/cli-bundle/chunks/item-transaction-N5F4LXYV.js +2 -0
  34. package/dist/cli-bundle/chunks/mutation-admission-QZQI2NN5.js +2 -0
  35. package/dist/cli-bundle/chunks/{register-list-query-I6AZA3IX.js → register-list-query-P3NOKQUA.js} +2 -2
  36. package/dist/cli-bundle/chunks/{register-mutation-3DAXCLXR.js → register-mutation-A6I5K5N6.js} +2 -2
  37. package/dist/cli-bundle/chunks/register-operations-SPKB3TNM.js +2 -0
  38. package/dist/cli-bundle/chunks/register-setup-ZZRHJ4IS.js +2 -0
  39. package/dist/cli-bundle/chunks/{stage-7H36OTDJ.js → stage-L3T725DS.js} +2 -2
  40. package/dist/cli-bundle/focused-chunks/chunk-5NGNO54M.js +44 -0
  41. package/dist/cli-bundle/focused-chunks/{chunk-ZQRCVRGS.js → chunk-A2246ZM7.js} +54 -54
  42. package/dist/cli-bundle/focused-chunks/chunk-B6U44WPW.js +2 -0
  43. package/dist/cli-bundle/focused-chunks/chunk-BAW3DGQO.js +91 -0
  44. package/dist/cli-bundle/focused-chunks/chunk-BHNY3KPJ.js +2 -0
  45. package/dist/cli-bundle/focused-chunks/chunk-C3O6JJPO.js +2 -0
  46. package/dist/cli-bundle/focused-chunks/{chunk-LKN2WGGB.js → chunk-C7UX2ZI3.js} +2 -2
  47. package/dist/cli-bundle/focused-chunks/{chunk-YVLAP3BL.js → chunk-CBG67HUP.js} +2 -2
  48. package/dist/cli-bundle/focused-chunks/{chunk-TYHDZUL4.js → chunk-CO2DNPH7.js} +2 -2
  49. package/dist/cli-bundle/focused-chunks/chunk-CXTDEQ3N.js +8 -0
  50. package/dist/cli-bundle/focused-chunks/chunk-FEPE2CE4.js +2 -0
  51. package/dist/cli-bundle/focused-chunks/{chunk-RY4QEMAE.js → chunk-FFO7RBLN.js} +2 -2
  52. package/dist/cli-bundle/focused-chunks/{chunk-I5FODKYB.js → chunk-FXFDVM5O.js} +2 -2
  53. package/dist/cli-bundle/focused-chunks/chunk-I5LGPUWH.js +2 -0
  54. package/dist/cli-bundle/focused-chunks/chunk-J3YOATXR.js +80 -0
  55. package/dist/cli-bundle/focused-chunks/{chunk-HOXIIZEO.js → chunk-J6FDCAMH.js} +2 -2
  56. package/dist/cli-bundle/focused-chunks/chunk-J7M52SCZ.js +2 -0
  57. package/dist/cli-bundle/focused-chunks/chunk-JQS7TYQ6.js +12 -0
  58. package/dist/cli-bundle/focused-chunks/chunk-MEJKELWC.js +26 -0
  59. package/dist/cli-bundle/focused-chunks/chunk-PMKR4MJI.js +2 -0
  60. package/dist/cli-bundle/focused-chunks/chunk-PNPYQDHU.js +2 -0
  61. package/dist/cli-bundle/focused-chunks/chunk-Q4OIRSKL.js +2 -0
  62. package/dist/cli-bundle/focused-chunks/{chunk-F3X6UWMI.js → chunk-QEVJ3Y67.js} +2 -2
  63. package/dist/cli-bundle/focused-chunks/{chunk-HJ27QLZV.js → chunk-SDLIUZEL.js} +2 -2
  64. package/dist/cli-bundle/focused-chunks/chunk-TFYTTDAK.js +2 -0
  65. package/dist/cli-bundle/focused-chunks/chunk-W3CSX6GI.js +3 -0
  66. package/dist/cli-bundle/focused-chunks/{chunk-D4XQOMG4.js → chunk-Y5GOUUYJ.js} +2 -2
  67. package/dist/cli-bundle/focused-chunks/item-revision-L6GVT42D.js +2 -0
  68. package/dist/cli-bundle/focused-chunks/item-transaction-GYGP264N.js +2 -0
  69. package/dist/cli-bundle/focused-chunks/mutation-admission-UWTH56OD.js +2 -0
  70. package/dist/cli-bundle/focused-chunks/{stage-4SQHVI3O.js → stage-WRSNJ7VI.js} +2 -2
  71. package/dist/cli-bundle/main.js +25 -25
  72. package/dist/cli-bundle/sdk-authoring.js +1 -1
  73. package/dist/cli-bundle/sdk-contracts.js +5 -5
  74. package/dist/cli-bundle/sdk-core.js +1 -1
  75. package/dist/cli-bundle/sdk-governance.js +1 -1
  76. package/dist/cli-bundle/sdk-graph.js +1 -1
  77. package/dist/cli-bundle/sdk-merge.js +57 -57
  78. package/dist/cli-bundle/sdk-query.js +1 -1
  79. package/dist/cli-bundle/sdk-runtime.js +1 -1
  80. package/dist/cli-bundle/sdk-testing.js +1 -1
  81. package/dist/cli-bundle/sdk.js +11 -11
  82. package/dist/core/config/positional-value.d.ts +1 -1
  83. package/dist/core/config/positional-value.js +1 -1
  84. package/dist/core/extensions/reserved-host-flags.js +1 -1
  85. package/dist/core/history/history-rewrite.js +1 -1
  86. package/dist/core/shared/command-types.d.ts +7 -0
  87. package/dist/core/store/item-mutation-precondition.d.ts +70 -6
  88. package/dist/core/store/item-mutation-precondition.js +1 -1
  89. package/dist/core/store/item-store.js +1 -1
  90. package/dist/core/store/settings-validator.js +1 -1
  91. package/dist/core/store/settings.js +1 -1
  92. package/dist/sdk/cli-contracts/bootstrap-command-scanner.js +1 -1
  93. package/dist/sdk/cli-contracts/completeness.js +1 -1
  94. package/dist/sdk/cli-contracts/enum-contracts.d.ts +11 -0
  95. package/dist/sdk/cli-contracts/enum-contracts.js +1 -1
  96. package/dist/sdk/cli-contracts/flag-lexicon-contracts.js +1 -1
  97. package/dist/sdk/cli-contracts/registration-helpers.js +1 -1
  98. package/dist/sdk/cli-contracts/tool-parameter-tables.js +1 -1
  99. package/dist/sdk/cli-contracts/tool-schema.js +1 -1
  100. package/dist/sdk/cli-program.js +1 -1
  101. package/dist/sdk/config.d.ts +1 -1
  102. package/dist/sdk/config.js +1 -1
  103. package/dist/sdk/environment/project-runtime-compatibility.js +1 -1
  104. package/dist/sdk/generated/generated-error-code-catalog-part-1.js +1 -1
  105. package/dist/sdk/generated/generated-error-code-catalog-part-2.js +1 -1
  106. package/dist/sdk/governance/validate.js +1 -1
  107. package/dist/sdk/history/salvage.js +3 -3
  108. package/dist/sdk/history-compact.d.ts +7 -1
  109. package/dist/sdk/history-compact.js +1 -1
  110. package/dist/sdk/history-repair.d.ts +2 -1
  111. package/dist/sdk/history-repair.js +1 -1
  112. package/dist/sdk/index.d.ts +3 -0
  113. package/dist/sdk/index.js +1 -1
  114. package/dist/sdk/lifecycle/checkpoint-rollback.d.ts +10 -2
  115. package/dist/sdk/lifecycle/checkpoint-rollback.js +1 -1
  116. package/dist/sdk/lifecycle/claim.js +1 -1
  117. package/dist/sdk/lifecycle/close-many.d.ts +11 -2
  118. package/dist/sdk/lifecycle/close-many.js +1 -1
  119. package/dist/sdk/lifecycle/close.d.ts +15 -0
  120. package/dist/sdk/lifecycle/close.js +1 -1
  121. package/dist/sdk/lifecycle/item-revision.d.ts +162 -0
  122. package/dist/sdk/lifecycle/item-revision.js +2 -0
  123. package/dist/sdk/lifecycle/restore.js +1 -1
  124. package/dist/sdk/lifecycle/start-blockers.d.ts +52 -0
  125. package/dist/sdk/lifecycle/start-blockers.js +2 -0
  126. package/dist/sdk/lifecycle/task-composition.d.ts +11 -0
  127. package/dist/sdk/lifecycle/task-composition.js +1 -1
  128. package/dist/sdk/lifecycle/update-many.d.ts +11 -2
  129. package/dist/sdk/lifecycle/update-many.js +1 -1
  130. package/dist/sdk/lifecycle/update.js +2 -2
  131. package/dist/sdk/public-surface.json +1 -1
  132. package/dist/sdk/query/declared-blockers.d.ts +16 -0
  133. package/dist/sdk/query/declared-blockers.js +2 -0
  134. package/dist/sdk/query/get.d.ts +7 -0
  135. package/dist/sdk/query/get.js +1 -1
  136. package/dist/sdk/query/projection-contracts.js +1 -1
  137. package/dist/sdk/runtime-input.d.ts +12 -0
  138. package/dist/sdk/runtime-input.js +1 -1
  139. package/dist/sdk/runtime.d.ts +10 -0
  140. package/dist/sdk/runtime.js +1 -1
  141. package/dist/sdk/structured-mutations.js +1 -1
  142. package/dist/sdk/test/batch.js +2 -2
  143. package/dist/sdk/test/execution.js +2 -2
  144. package/dist/sdk/test/linked-command-detection.js +1 -1
  145. package/dist/sdk/workspace-transaction.d.ts +17 -6
  146. package/dist/sdk/workspace-transaction.js +1 -1
  147. package/dist/types.d.ts +6 -0
  148. package/dist/types.js +1 -1
  149. package/docs/COMMANDS.md +91 -0
  150. package/docs/CONFIGURATION.md +1 -0
  151. package/docs/SDK.md +110 -1
  152. package/docs/agent-task-token-baseline.json +1 -1
  153. package/docs/generated/FLAG_LEXICON_BUDGETS.md +111 -111
  154. package/docs/generated/REFUSAL_CLOSURE_CENSUS.md +12 -3
  155. package/marketplace.json +1 -1
  156. package/package.json +1 -1
  157. package/packages/pm-beads/package.json +1 -1
  158. package/packages/pm-calendar/package.json +1 -1
  159. package/packages/pm-command-kit/package.json +1 -1
  160. package/packages/pm-digital-twin/package.json +1 -1
  161. package/packages/pm-governance-audit/package.json +1 -1
  162. package/packages/pm-guide-shell/package.json +1 -1
  163. package/packages/pm-kanban/package.json +1 -1
  164. package/packages/pm-lifecycle-hooks/package.json +1 -1
  165. package/packages/pm-linked-test-adapters/package.json +1 -1
  166. package/packages/pm-search-advanced/package.json +1 -1
  167. package/packages/pm-templates/package.json +1 -1
  168. package/packages/pm-todos/package.json +1 -1
  169. package/packages/pm-vcs/package.json +1 -1
  170. package/plugins/pm-claude/.claude-plugin/plugin.json +1 -1
  171. package/plugins/pm-claude/package.json +1 -1
  172. package/plugins/pm-codex/.codex-plugin/plugin.json +1 -1
  173. package/plugins/pm-codex/package.json +1 -1
  174. package/plugins/pm-codex/plugin.json +1 -1
  175. package/runtime-dependencies.json +1 -1
  176. package/dist/cli-bundle/chunks/chunk-7ZEFHFAU.js +0 -2
  177. package/dist/cli-bundle/chunks/chunk-B6UTLYLM.js +0 -3
  178. package/dist/cli-bundle/chunks/chunk-HBKD5HZB.js +0 -2
  179. package/dist/cli-bundle/chunks/chunk-L6LCLC37.js +0 -2
  180. package/dist/cli-bundle/chunks/chunk-NR4KABHC.js +0 -31
  181. package/dist/cli-bundle/chunks/chunk-PWD5RY2D.js +0 -2
  182. package/dist/cli-bundle/chunks/chunk-XRMYA4R7.js +0 -20
  183. package/dist/cli-bundle/chunks/chunk-YWS4XV4I.js +0 -6
  184. package/dist/cli-bundle/chunks/chunk-ZOAJNPBL.js +0 -129
  185. package/dist/cli-bundle/chunks/item-transaction-ZHLJTBD7.js +0 -2
  186. package/dist/cli-bundle/chunks/mutation-admission-ZQMRMGIB.js +0 -2
  187. package/dist/cli-bundle/chunks/register-operations-G3QGGQ5Z.js +0 -2
  188. package/dist/cli-bundle/chunks/register-setup-LDXHMNOT.js +0 -2
  189. package/dist/cli-bundle/focused-chunks/chunk-3XGKBVJ3.js +0 -2
  190. package/dist/cli-bundle/focused-chunks/chunk-4HY6EGPB.js +0 -2
  191. package/dist/cli-bundle/focused-chunks/chunk-AK2CI2GI.js +0 -2
  192. package/dist/cli-bundle/focused-chunks/chunk-HUXZCPWT.js +0 -26
  193. package/dist/cli-bundle/focused-chunks/chunk-IKXJ57XA.js +0 -8
  194. package/dist/cli-bundle/focused-chunks/chunk-MOY6KZGJ.js +0 -44
  195. package/dist/cli-bundle/focused-chunks/chunk-PWUTFKSI.js +0 -2
  196. package/dist/cli-bundle/focused-chunks/chunk-PZRB7IXH.js +0 -91
  197. package/dist/cli-bundle/focused-chunks/chunk-WSYPVBKS.js +0 -80
  198. package/dist/cli-bundle/focused-chunks/chunk-WXLG7HZ5.js +0 -3
  199. package/dist/cli-bundle/focused-chunks/chunk-WZDFCVLX.js +0 -12
  200. package/dist/cli-bundle/focused-chunks/chunk-YGO3FWA2.js +0 -2
  201. package/dist/cli-bundle/focused-chunks/chunk-ZYXQACPB.js +0 -2
  202. package/dist/cli-bundle/focused-chunks/item-transaction-PZ2FQ6B2.js +0 -2
  203. package/dist/cli-bundle/focused-chunks/mutation-admission-XOOK6XFH.js +0 -2
@@ -1,2 +1,2 @@
1
- (function(){try{var e=typeof window<"u"?window:typeof global<"u"?global:typeof globalThis<"u"?globalThis:typeof self<"u"?self:{},n=new e.Error().stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="e631dc72-c452-5596-8213-7e75b3b250a3")}catch{}})();import{readFileSync}from"node:fs";import path from"node:path";import{EXIT_CODE}from"../../core/shared/constants.js";import{PmCliError}from"../../core/shared/errors.js";const PM_SUBCOMMANDS_WITH_ITEM_REFERENCE=new Set(["get","history","restore","update","close","delete","append","claim","release","comments","notes","learnings","files","docs","deps","test"]),PM_GLOBAL_FLAGS_WITH_VALUE=new Set(["--pm-path","--path","--author"]),PM_ITEM_REFERENCE_FLAGS_WITH_VALUE={get:new Set(["--depth","--fields","--tree-depth","--tree_depth","--at","--format"]),history:new Set(["--limit","--field","--format"]),restore:new Set(["--message"]),update:new Set(["--title","-t","--description","-d","--body","-b","--status","-s","--close-reason","--close_reason","--priority","-p","--type","--tags","--add-tags","--add_tags","--remove-tags","--remove_tags","--deadline","--estimated-minutes","--estimate","--estimated_minutes","--acceptance-criteria","--ac","--acceptance_criteria","--definition-of-ready","--definition_of_ready","--order","--rank","--goal","--objective","--value","--impact","--outcome","--why-now","--why_now","--message","--assignee","--parent","--reviewer","--risk","--confidence","--sprint","--release","--blocked-by","--blocked_by","--blocked-reason","--blocked_reason","--unblock-note","--unblock_note","--reporter","--severity","--environment","--repro-steps","--repro_steps","--resolution","--expected-result","--expected_result","--expected","--actual-result","--actual_result","--actual","--affected-version","--affected_version","--fixed-version","--fixed_version","--component","--regression","--customer-impact","--customer_impact","--dep","--dep-remove","--dep_remove","--comment","--note","--learning","--file","--test","--doc","--reminder","--event","--type-option","--type_option","--field","--unset","--body-file"]),close:new Set(["--reason","-r","--close-reason","--duplicate-of","-d","--message","-m","--validate-close","--resolution","--expected-result","--expected_result","--expected","--actual-result","--actual_result","--actual"]),delete:new Set(["--message"]),append:new Set(["--body","--text","--message"]),claim:new Set(["--assignee","--message","--type","--tag","--priority","--assignee-filter","--parent","--sprint","--release","--max-attempts"]),release:new Set(["--assignee","--message"]),comments:new Set(["--add","--body","--comment","--file","--edit","--delete","--limit","--message"]),notes:new Set(["--add","--file","--edit","--delete","--limit","--message"]),learnings:new Set(["--add","--file","--edit","--delete","--limit","--message"]),files:new Set(["--add","--add-glob","--remove","--migrate","--note","--message"]),docs:new Set(["--add","--add-glob","--remove","--migrate","--note","--message"]),deps:new Set(["--format","--max-depth","--collapse","--node-limit","--edge-limit","--token-budget","--cursor","--direction","--kind"]),test:new Set(["--add","--add-json","--remove","--match","--only-index","--timeout","--env-set","--env-clear","--pm-context","--message"])},NPX_FLAGS_WITH_VALUE=new Set(["-p","--package"]),NPX_COMMAND_STRING_FLAGS=new Set(["-c","--call"]),PNPM_GLOBAL_FLAGS_WITH_VALUE=new Set(["-c","-C","--config","--dir","--filter","--workspace-dir"]),NPM_GLOBAL_FLAGS_WITH_VALUE=new Set(["-C","--prefix","--userconfig","--cache"]),YARN_GLOBAL_FLAGS_WITH_VALUE=new Set(["--cwd"]),BUN_GLOBAL_FLAGS_WITH_VALUE=new Set(["--cwd"]),NPM_EXEC_SUBCOMMANDS=new Set(["exec","x"]),SCRIPT_RUN_SUBCOMMANDS=new Set(["run","run-script"]),SCRIPT_RUN_FLAGS_WITH_VALUE=new Set(["-C","--dir","--cwd","-w","--workspace","--filter"]);function splitNormalizedCommandSegments(normalizedCommand){return normalizedCommand.split(/&&|\|\||\||;/).map(segment=>segment.trim()).filter(segment=>segment.length>0)}function extractPmInvocationArgsFromSegment(segment,cwd=process.cwd()){const tokens=stripLeadingEnvAssignments(segment.split(" ").filter(token=>token.length>0));if(tokens.length===0)return null;const[executable,...args]=tokens;if(isPmExecutableToken(executable)||isPmCliScriptToken(executable,cwd))return args;if(executable==="node"&&args[0]&&isPmCliScriptToken(args[0],cwd))return args.slice(1);const invocation=executable==="npx"||executable==="bunx"?parseNpxCommand(args):executable==="pnpm"?parsePnpmDlxCommand(args):executable==="npm"?parseNpmExecCommand(args):null;return invocation&&(isPmExecutableToken(invocation.command)||isPmCliPackageToken(invocation.command))?invocation.args:null}const extractReferencedPmItemIdsFromCommand=(command,idPrefix="pm")=>{const lowered=idPrefix.trim().toLowerCase();let prefixEnd=lowered.length;for(;prefixEnd>0&&lowered[prefixEnd-1]==="-";)prefixEnd-=1;const normalizedPrefix=lowered.slice(0,prefixEnd);if(normalizedPrefix.length===0)return[];const normalizedCommand=command.trim().replaceAll("\\","/").replaceAll('"',"").replaceAll("'","").replaceAll(/\s+/g," ").toLowerCase(),ids=new Set;for(const segment of splitNormalizedCommandSegments(normalizedCommand)){const invocationArgs=extractPmInvocationArgsFromSegment(segment),context=invocationArgs?resolvePmSubcommandContext(invocationArgs):null,candidate=context&&PM_SUBCOMMANDS_WITH_ITEM_REFERENCE.has(context.subcommand)?firstPositionalToken(context.remaining,PM_ITEM_REFERENCE_FLAGS_WITH_VALUE[context.subcommand]):void 0,normalizedCandidate=candidate?.trim().toLowerCase();normalizedCandidate?.startsWith(`${normalizedPrefix}-`)===!0&&normalizedCandidate.length>normalizedPrefix.length+1&&ids.add(candidate)}return[...ids].sort((left,right)=>left.localeCompare(right))};function stripLeadingEnvAssignments(tokens){let start=0;for(tokens[start]==="env"&&(start+=1);start<tokens.length;){const token=tokens[start];if(/^(?:[a-z_][a-z0-9_]*|\$env:[a-z_][a-z0-9_]*)=.*/.test(token)){start+=1;continue}break}return tokens.slice(start)}function firstPmSubcommand(args){let index=0;for(;index<args.length;){const token=args[index];if(token==="--"){index+=1;continue}if(token.startsWith("-")){if(PM_GLOBAL_FLAGS_WITH_VALUE.has(token)){index+=2;continue}index+=1;continue}return token}}function isPmExecutableToken(token){return token==="pm"||token==="pm.cmd"||token==="pm.exe"||token.endsWith("/pm")||token.endsWith("/pm.cmd")||token.endsWith("/pm.exe")}function normalizePackageSpecifier(token){const trimmed=token.trim();if(!trimmed.startsWith("@")){const versionSeparator2=trimmed.indexOf("@");return versionSeparator2===-1?trimmed:trimmed.slice(0,versionSeparator2)}const scopeSeparator=trimmed.indexOf("/");if(scopeSeparator===-1)return trimmed;const versionSeparator=trimmed.indexOf("@",scopeSeparator+1);return versionSeparator===-1?trimmed:trimmed.slice(0,versionSeparator)}function isPmCliPackageToken(token){const normalizedSpecifier=normalizePackageSpecifier(token);return normalizedSpecifier==="pm-cli"||normalizedSpecifier.endsWith("/pm-cli")||token==="pm-cli"||token.endsWith("/pm-cli")}function isPmCliScriptToken(token,cwd=process.cwd()){const normalized=token.replaceAll("\\","/");if(normalized!=="dist/cli.js"&&normalized!=="./dist/cli.js"&&!normalized.endsWith("/dist/cli.js"))return!1;const scriptPath=path.resolve(cwd,token);try{return JSON.parse(readFileSync(path.join(path.dirname(path.dirname(scriptPath)),"package.json"),"utf8")).name==="@unbrained/pm-cli"}catch{return!1}}function parseNpxCommand(tokens){let index=0;for(;index<tokens.length;){const token=tokens[index];if(token==="--"){index+=1;break}if(!token.startsWith("-"))break;const inlineCommandMatch=/^(?:-c|--call)=(.*)$/.exec(token);if(inlineCommandMatch){const command2=inlineCommandMatch[1];return command2?{command:command2,args:tokens.slice(index+1)}:null}if(NPX_COMMAND_STRING_FLAGS.has(token)){index+=1;break}index+=NPX_FLAGS_WITH_VALUE.has(token)&&!token.includes("=")?2:1}const command=tokens[index];return command?{command,args:tokens.slice(index+1)}:null}function parseLauncherSubcommand(tokens,flagsWithValue){let index=0;for(;index<tokens.length;){const token=tokens[index];if(token==="--"){index+=1;continue}if(token.startsWith("-")){if(token.includes("=")){index+=1;continue}if(flagsWithValue.has(token)){index+=2;continue}index+=1;continue}return{subcommand:token,args:tokens.slice(index+1)}}return null}function parsePnpmDlxCommand(tokens){const parsed=parseLauncherSubcommand(tokens,PNPM_GLOBAL_FLAGS_WITH_VALUE);return parsed?.subcommand!=="dlx"?null:parseNpxCommand(parsed.args)}function parseNpmExecCommand(tokens){const parsed=parseLauncherSubcommand(tokens,NPM_GLOBAL_FLAGS_WITH_VALUE);return!parsed||!NPM_EXEC_SUBCOMMANDS.has(parsed.subcommand)?null:parseNpxCommand(parsed.args)}function resolvePmSubcommandContext(args){let index=0;for(;index<args.length;){const token=args[index];if(token==="--"){index+=1;continue}if(token.startsWith("-")){if(PM_GLOBAL_FLAGS_WITH_VALUE.has(token)){index+=2;continue}index+=1;continue}return{subcommand:token,remaining:args.slice(index+1)}}return null}function firstPositionalToken(tokens,flagsWithValue=new Set){for(let index=0;index<tokens.length;index+=1){const token=tokens[index];if(token==="--")return tokens[index+1];if(!token.startsWith("-"))return token;!token.includes("=")&&(PM_GLOBAL_FLAGS_WITH_VALUE.has(token)||flagsWithValue.has(token))&&(index+=1)}}function parsedLauncherInvokesRecursiveTestAll(parsed){return!parsed||!isPmExecutableToken(parsed.command)&&!isPmCliPackageToken(parsed.command)?!1:firstPmSubcommand(parsed.args)==="test-all"}const SHELL_COMMAND_FLAG_PATTERNS=new Map([...["sh","bash","dash","zsh"].map(executable=>[executable,/^(?:-c|-[a-z]*c[a-z]*)$/u]),...["cmd","cmd.exe"].map(executable=>[executable,/^\/[ck]$/u]),...["powershell","powershell.exe","pwsh","pwsh.exe"].map(executable=>[executable,/^-(?:c|command)$/u])]),PACKAGE_EXEC_EXECUTABLES=new Set(["npx","bunx"]);function segmentInvokesRecursiveTestAll(segment){const rawTokens=segment.split(" ").filter(token=>token.length>0),tokens=stripLeadingEnvAssignments(rawTokens);if(tokens.length===0)return!1;const[executable,...args]=tokens,shellCommandFlagPattern=SHELL_COMMAND_FLAG_PATTERNS.get(executable);if(shellCommandFlagPattern){const commandFlagIndex=args.findIndex(arg=>shellCommandFlagPattern.test(arg));return commandFlagIndex>=0?segmentInvokesRecursiveTestAll(args.slice(commandFlagIndex+1).join(" ")):!1}return isPmExecutableToken(executable)||isPmCliScriptToken(executable)?firstPmSubcommand(args)==="test-all":executable==="node"&&args.length>0&&isPmCliScriptToken(args[0])?firstPmSubcommand(args.slice(1))==="test-all":PACKAGE_EXEC_EXECUTABLES.has(executable)?parsedLauncherInvokesRecursiveTestAll(parseNpxCommand(args)):executable==="pnpm"?parsedLauncherInvokesRecursiveTestAll(parsePnpmDlxCommand(args)):executable==="npm"?parsedLauncherInvokesRecursiveTestAll(parseNpmExecCommand(args)):!1}function invokesRecursiveTestAllCommand(command){const normalized=normalizeCommandForValidation(command);return splitNormalizedCommandSegments(normalized).some(segment=>segmentInvokesRecursiveTestAll(segment))}function assertNoRecursiveTestAllCommand(command){if(invokesRecursiveTestAllCommand(command))throw new PmCliError('Linked test commands must not invoke "pm test-all"; this creates recursive orchestration.',EXIT_CODE.USAGE)}function normalizeCommandForValidation(command){return command.trim().replaceAll("\\","/").replaceAll('"',"").replaceAll("'","").replaceAll(/\s+/g," ").toLowerCase()}export{BUN_GLOBAL_FLAGS_WITH_VALUE,NPM_EXEC_SUBCOMMANDS,NPM_GLOBAL_FLAGS_WITH_VALUE,NPX_COMMAND_STRING_FLAGS,NPX_FLAGS_WITH_VALUE,PM_GLOBAL_FLAGS_WITH_VALUE,PM_ITEM_REFERENCE_FLAGS_WITH_VALUE,PNPM_GLOBAL_FLAGS_WITH_VALUE,SCRIPT_RUN_FLAGS_WITH_VALUE,SCRIPT_RUN_SUBCOMMANDS,SHELL_COMMAND_FLAG_PATTERNS,YARN_GLOBAL_FLAGS_WITH_VALUE,assertNoRecursiveTestAllCommand,extractPmInvocationArgsFromSegment,extractReferencedPmItemIdsFromCommand,firstPmSubcommand,firstPositionalToken,invokesRecursiveTestAllCommand,isPmCliPackageToken,isPmCliScriptToken,isPmExecutableToken,normalizeCommandForValidation,normalizePackageSpecifier,parseLauncherSubcommand,parseNpmExecCommand,parseNpxCommand,parsePnpmDlxCommand,resolvePmSubcommandContext,segmentInvokesRecursiveTestAll,splitNormalizedCommandSegments,stripLeadingEnvAssignments};
1
+ (function(){try{var e=typeof window<"u"?window:typeof global<"u"?global:typeof globalThis<"u"?globalThis:typeof self<"u"?self:{},n=new e.Error().stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="d88c7294-44f1-5b65-9467-f94ed2db11ca")}catch{}})();import{readFileSync}from"node:fs";import path from"node:path";import{EXIT_CODE}from"../../core/shared/constants.js";import{PmCliError}from"../../core/shared/errors.js";const PM_SUBCOMMANDS_WITH_ITEM_REFERENCE=new Set(["get","history","restore","update","close","delete","append","claim","release","comments","notes","learnings","files","docs","deps","test"]),PM_GLOBAL_FLAGS_WITH_VALUE=new Set(["--pm-path","--path","--author","--expect-hash"]),PM_ITEM_REFERENCE_FLAGS_WITH_VALUE={get:new Set(["--depth","--fields","--tree-depth","--tree_depth","--at","--format"]),history:new Set(["--limit","--field","--format"]),restore:new Set(["--message"]),update:new Set(["--title","-t","--description","-d","--body","-b","--status","-s","--close-reason","--close_reason","--priority","-p","--type","--tags","--add-tags","--add_tags","--remove-tags","--remove_tags","--deadline","--estimated-minutes","--estimate","--estimated_minutes","--acceptance-criteria","--ac","--acceptance_criteria","--definition-of-ready","--definition_of_ready","--order","--rank","--goal","--objective","--value","--impact","--outcome","--why-now","--why_now","--message","--assignee","--parent","--reviewer","--risk","--confidence","--sprint","--release","--blocked-by","--blocked_by","--blocked-reason","--blocked_reason","--unblock-note","--unblock_note","--reporter","--severity","--environment","--repro-steps","--repro_steps","--resolution","--expected-result","--expected_result","--expected","--actual-result","--actual_result","--actual","--affected-version","--affected_version","--fixed-version","--fixed_version","--component","--regression","--customer-impact","--customer_impact","--dep","--dep-remove","--dep_remove","--comment","--note","--learning","--file","--test","--doc","--reminder","--event","--type-option","--type_option","--field","--unset","--body-file"]),close:new Set(["--reason","-r","--close-reason","--duplicate-of","-d","--message","-m","--validate-close","--resolution","--expected-result","--expected_result","--expected","--actual-result","--actual_result","--actual"]),delete:new Set(["--message"]),append:new Set(["--body","--text","--message"]),claim:new Set(["--assignee","--message","--type","--tag","--priority","--assignee-filter","--parent","--sprint","--release","--max-attempts"]),release:new Set(["--assignee","--message"]),comments:new Set(["--add","--body","--comment","--file","--edit","--delete","--limit","--message"]),notes:new Set(["--add","--file","--edit","--delete","--limit","--message"]),learnings:new Set(["--add","--file","--edit","--delete","--limit","--message"]),files:new Set(["--add","--add-glob","--remove","--migrate","--note","--message"]),docs:new Set(["--add","--add-glob","--remove","--migrate","--note","--message"]),deps:new Set(["--format","--max-depth","--collapse","--node-limit","--edge-limit","--token-budget","--cursor","--direction","--kind"]),test:new Set(["--add","--add-json","--remove","--match","--only-index","--timeout","--env-set","--env-clear","--pm-context","--message"])},NPX_FLAGS_WITH_VALUE=new Set(["-p","--package"]),NPX_COMMAND_STRING_FLAGS=new Set(["-c","--call"]),PNPM_GLOBAL_FLAGS_WITH_VALUE=new Set(["-c","-C","--config","--dir","--filter","--workspace-dir"]),NPM_GLOBAL_FLAGS_WITH_VALUE=new Set(["-C","--prefix","--userconfig","--cache"]),YARN_GLOBAL_FLAGS_WITH_VALUE=new Set(["--cwd"]),BUN_GLOBAL_FLAGS_WITH_VALUE=new Set(["--cwd"]),NPM_EXEC_SUBCOMMANDS=new Set(["exec","x"]),SCRIPT_RUN_SUBCOMMANDS=new Set(["run","run-script"]),SCRIPT_RUN_FLAGS_WITH_VALUE=new Set(["-C","--dir","--cwd","-w","--workspace","--filter"]);function splitNormalizedCommandSegments(normalizedCommand){return normalizedCommand.split(/&&|\|\||\||;/).map(segment=>segment.trim()).filter(segment=>segment.length>0)}function extractPmInvocationArgsFromSegment(segment,cwd=process.cwd()){const tokens=stripLeadingEnvAssignments(segment.split(" ").filter(token=>token.length>0));if(tokens.length===0)return null;const[executable,...args]=tokens;if(isPmExecutableToken(executable)||isPmCliScriptToken(executable,cwd))return args;if(executable==="node"&&args[0]&&isPmCliScriptToken(args[0],cwd))return args.slice(1);const invocation=executable==="npx"||executable==="bunx"?parseNpxCommand(args):executable==="pnpm"?parsePnpmDlxCommand(args):executable==="npm"?parseNpmExecCommand(args):null;return invocation&&(isPmExecutableToken(invocation.command)||isPmCliPackageToken(invocation.command))?invocation.args:null}const extractReferencedPmItemIdsFromCommand=(command,idPrefix="pm")=>{const lowered=idPrefix.trim().toLowerCase();let prefixEnd=lowered.length;for(;prefixEnd>0&&lowered[prefixEnd-1]==="-";)prefixEnd-=1;const normalizedPrefix=lowered.slice(0,prefixEnd);if(normalizedPrefix.length===0)return[];const normalizedCommand=command.trim().replaceAll("\\","/").replaceAll('"',"").replaceAll("'","").replaceAll(/\s+/g," ").toLowerCase(),ids=new Set;for(const segment of splitNormalizedCommandSegments(normalizedCommand)){const invocationArgs=extractPmInvocationArgsFromSegment(segment),context=invocationArgs?resolvePmSubcommandContext(invocationArgs):null,candidate=context&&PM_SUBCOMMANDS_WITH_ITEM_REFERENCE.has(context.subcommand)?firstPositionalToken(context.remaining,PM_ITEM_REFERENCE_FLAGS_WITH_VALUE[context.subcommand]):void 0,normalizedCandidate=candidate?.trim().toLowerCase();normalizedCandidate?.startsWith(`${normalizedPrefix}-`)===!0&&normalizedCandidate.length>normalizedPrefix.length+1&&ids.add(candidate)}return[...ids].sort((left,right)=>left.localeCompare(right))};function stripLeadingEnvAssignments(tokens){let start=0;for(tokens[start]==="env"&&(start+=1);start<tokens.length;){const token=tokens[start];if(/^(?:[a-z_][a-z0-9_]*|\$env:[a-z_][a-z0-9_]*)=.*/.test(token)){start+=1;continue}break}return tokens.slice(start)}function firstPmSubcommand(args){let index=0;for(;index<args.length;){const token=args[index];if(token==="--"){index+=1;continue}if(token.startsWith("-")){if(PM_GLOBAL_FLAGS_WITH_VALUE.has(token)){index+=2;continue}index+=1;continue}return token}}function isPmExecutableToken(token){return token==="pm"||token==="pm.cmd"||token==="pm.exe"||token.endsWith("/pm")||token.endsWith("/pm.cmd")||token.endsWith("/pm.exe")}function normalizePackageSpecifier(token){const trimmed=token.trim();if(!trimmed.startsWith("@")){const versionSeparator2=trimmed.indexOf("@");return versionSeparator2===-1?trimmed:trimmed.slice(0,versionSeparator2)}const scopeSeparator=trimmed.indexOf("/");if(scopeSeparator===-1)return trimmed;const versionSeparator=trimmed.indexOf("@",scopeSeparator+1);return versionSeparator===-1?trimmed:trimmed.slice(0,versionSeparator)}function isPmCliPackageToken(token){const normalizedSpecifier=normalizePackageSpecifier(token);return normalizedSpecifier==="pm-cli"||normalizedSpecifier.endsWith("/pm-cli")||token==="pm-cli"||token.endsWith("/pm-cli")}function isPmCliScriptToken(token,cwd=process.cwd()){const normalized=token.replaceAll("\\","/");if(normalized!=="dist/cli.js"&&normalized!=="./dist/cli.js"&&!normalized.endsWith("/dist/cli.js"))return!1;const scriptPath=path.resolve(cwd,token);try{return JSON.parse(readFileSync(path.join(path.dirname(path.dirname(scriptPath)),"package.json"),"utf8")).name==="@unbrained/pm-cli"}catch{return!1}}function parseNpxCommand(tokens){let index=0;for(;index<tokens.length;){const token=tokens[index];if(token==="--"){index+=1;break}if(!token.startsWith("-"))break;const inlineCommandMatch=/^(?:-c|--call)=(.*)$/.exec(token);if(inlineCommandMatch){const command2=inlineCommandMatch[1];return command2?{command:command2,args:tokens.slice(index+1)}:null}if(NPX_COMMAND_STRING_FLAGS.has(token)){index+=1;break}index+=NPX_FLAGS_WITH_VALUE.has(token)&&!token.includes("=")?2:1}const command=tokens[index];return command?{command,args:tokens.slice(index+1)}:null}function parseLauncherSubcommand(tokens,flagsWithValue){let index=0;for(;index<tokens.length;){const token=tokens[index];if(token==="--"){index+=1;continue}if(token.startsWith("-")){if(token.includes("=")){index+=1;continue}if(flagsWithValue.has(token)){index+=2;continue}index+=1;continue}return{subcommand:token,args:tokens.slice(index+1)}}return null}function parsePnpmDlxCommand(tokens){const parsed=parseLauncherSubcommand(tokens,PNPM_GLOBAL_FLAGS_WITH_VALUE);return parsed?.subcommand!=="dlx"?null:parseNpxCommand(parsed.args)}function parseNpmExecCommand(tokens){const parsed=parseLauncherSubcommand(tokens,NPM_GLOBAL_FLAGS_WITH_VALUE);return!parsed||!NPM_EXEC_SUBCOMMANDS.has(parsed.subcommand)?null:parseNpxCommand(parsed.args)}function resolvePmSubcommandContext(args){let index=0;for(;index<args.length;){const token=args[index];if(token==="--"){index+=1;continue}if(token.startsWith("-")){if(PM_GLOBAL_FLAGS_WITH_VALUE.has(token)){index+=2;continue}index+=1;continue}return{subcommand:token,remaining:args.slice(index+1)}}return null}function firstPositionalToken(tokens,flagsWithValue=new Set){for(let index=0;index<tokens.length;index+=1){const token=tokens[index];if(token==="--")return tokens[index+1];if(!token.startsWith("-"))return token;!token.includes("=")&&(PM_GLOBAL_FLAGS_WITH_VALUE.has(token)||flagsWithValue.has(token))&&(index+=1)}}function parsedLauncherInvokesRecursiveTestAll(parsed){return!parsed||!isPmExecutableToken(parsed.command)&&!isPmCliPackageToken(parsed.command)?!1:firstPmSubcommand(parsed.args)==="test-all"}const SHELL_COMMAND_FLAG_PATTERNS=new Map([...["sh","bash","dash","zsh"].map(executable=>[executable,/^(?:-c|-[a-z]*c[a-z]*)$/u]),...["cmd","cmd.exe"].map(executable=>[executable,/^\/[ck]$/u]),...["powershell","powershell.exe","pwsh","pwsh.exe"].map(executable=>[executable,/^-(?:c|command)$/u])]),PACKAGE_EXEC_EXECUTABLES=new Set(["npx","bunx"]);function segmentInvokesRecursiveTestAll(segment){const rawTokens=segment.split(" ").filter(token=>token.length>0),tokens=stripLeadingEnvAssignments(rawTokens);if(tokens.length===0)return!1;const[executable,...args]=tokens,shellCommandFlagPattern=SHELL_COMMAND_FLAG_PATTERNS.get(executable);if(shellCommandFlagPattern){const commandFlagIndex=args.findIndex(arg=>shellCommandFlagPattern.test(arg));return commandFlagIndex>=0?segmentInvokesRecursiveTestAll(args.slice(commandFlagIndex+1).join(" ")):!1}return isPmExecutableToken(executable)||isPmCliScriptToken(executable)?firstPmSubcommand(args)==="test-all":executable==="node"&&args.length>0&&isPmCliScriptToken(args[0])?firstPmSubcommand(args.slice(1))==="test-all":PACKAGE_EXEC_EXECUTABLES.has(executable)?parsedLauncherInvokesRecursiveTestAll(parseNpxCommand(args)):executable==="pnpm"?parsedLauncherInvokesRecursiveTestAll(parsePnpmDlxCommand(args)):executable==="npm"?parsedLauncherInvokesRecursiveTestAll(parseNpmExecCommand(args)):!1}function invokesRecursiveTestAllCommand(command){const normalized=normalizeCommandForValidation(command);return splitNormalizedCommandSegments(normalized).some(segment=>segmentInvokesRecursiveTestAll(segment))}function assertNoRecursiveTestAllCommand(command){if(invokesRecursiveTestAllCommand(command))throw new PmCliError('Linked test commands must not invoke "pm test-all"; this creates recursive orchestration.',EXIT_CODE.USAGE)}function normalizeCommandForValidation(command){return command.trim().replaceAll("\\","/").replaceAll('"',"").replaceAll("'","").replaceAll(/\s+/g," ").toLowerCase()}export{BUN_GLOBAL_FLAGS_WITH_VALUE,NPM_EXEC_SUBCOMMANDS,NPM_GLOBAL_FLAGS_WITH_VALUE,NPX_COMMAND_STRING_FLAGS,NPX_FLAGS_WITH_VALUE,PM_GLOBAL_FLAGS_WITH_VALUE,PM_ITEM_REFERENCE_FLAGS_WITH_VALUE,PNPM_GLOBAL_FLAGS_WITH_VALUE,SCRIPT_RUN_FLAGS_WITH_VALUE,SCRIPT_RUN_SUBCOMMANDS,SHELL_COMMAND_FLAG_PATTERNS,YARN_GLOBAL_FLAGS_WITH_VALUE,assertNoRecursiveTestAllCommand,extractPmInvocationArgsFromSegment,extractReferencedPmItemIdsFromCommand,firstPmSubcommand,firstPositionalToken,invokesRecursiveTestAllCommand,isPmCliPackageToken,isPmCliScriptToken,isPmExecutableToken,normalizeCommandForValidation,normalizePackageSpecifier,parseLauncherSubcommand,parseNpmExecCommand,parseNpxCommand,parsePnpmDlxCommand,resolvePmSubcommandContext,segmentInvokesRecursiveTestAll,splitNormalizedCommandSegments,stripLeadingEnvAssignments};
2
2
  //# sourceMappingURL=linked-command-detection.js.map
@@ -41,7 +41,7 @@ stepId?:string;
41
41
  }
42
42
  /** Options accepted by the public workspace transaction coordinator. */
43
43
  export interface CommitWorkspaceTransactionOptions {
44
- /** Tracker root that owns the journal and workspace-wide writer lock. */
44
+ /** Tracker root that owns the journal and the lock that serializes transaction coordinators. */
45
45
  pmRoot:string;
46
46
  /** Stable idempotency key reused to recover an interrupted transaction. */
47
47
  transactionId:string;
@@ -53,7 +53,7 @@ steps:readonly WorkspaceTransactionStep[];
53
53
  recoveryData?:WorkspaceTransactionJsonValue;
54
54
  /** Lock lifetime in seconds; size this above the longest expected attempt. */
55
55
  lockTtlSeconds?:number;
56
- /** Maximum time to wait for the workspace writer lock. */
56
+ /** Maximum time to wait for the transaction coordinator lock. */
57
57
  lockWaitMs?:number;
58
58
  /** Optional transition observer used by telemetry and deterministic crash tests. */
59
59
  onTransition?(context:WorkspaceTransactionTransitionContext):void | Promise<void>;
@@ -118,10 +118,21 @@ supersededBy?:string;
118
118
  /** Validate transaction identity before preview staging or acquiring any writer lock. */
119
119
  export declare function validateWorkspaceTransactionIdentity(options:Pick<CommitWorkspaceTransactionOptions,"pmRoot" | "transactionId" | "author">):Pick<CommitWorkspaceTransactionOptions,"pmRoot" | "transactionId" | "author">;
120
120
  /**
121
- * Commit an ordered set of idempotent SDK mutations under one workspace writer
122
- * lock. The durable journal resumes interrupted forward work, while ordinary
123
- * failures append reverse-order compensations. This provides atomic logical
124
- * replay without deleting or rewriting immutable domain histories.
121
+ * Commit an ordered set of idempotent SDK mutations under one workspace
122
+ * transaction lock. The durable journal resumes interrupted forward work, while
123
+ * ordinary failures append reverse-order compensations. This provides atomic
124
+ * logical replay without deleting or rewriting immutable domain histories.
125
+ *
126
+ * Isolation (GH-1620): the lock serializes transaction coordinators only.
127
+ * Ordinary SDK, CLI and MCP writers take per-item locks and do not wait for it,
128
+ * so a peer can commit between a step's `inspect()` and `apply()`. A step that
129
+ * decides from what it read must guard its write: take the decision data and
130
+ * `item_hash` from one `get()` in `inspect()`, then run `apply()` inside
131
+ * `withExpectedItemRevisions()`, which refuses with `item_revision_conflict`.
132
+ * The coordinator invokes `compensate()` (including during recovery) without an
133
+ * expected revision. Built-in item compensations detect intervening writes, but a
134
+ * custom adapter must fence or reconcile its own compensation, or it can restore
135
+ * a stale value over a peer's write.
125
136
  */
126
137
  export declare function commitWorkspaceTransaction(options:CommitWorkspaceTransactionOptions):Promise<WorkspaceTransactionCommitResult>;
127
138
  /** Read an atomically published journal without changing tracker state; undefined means no retained receipt. */
@@ -1,3 +1,3 @@
1
- (function(){try{var e=typeof window<"u"?window:typeof global<"u"?global:typeof globalThis<"u"?globalThis:typeof self<"u"?self:{},n=new e.Error().stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="a8daf042-f74c-5f51-b7e8-f8ff4fb8fe8e")}catch{}})();import{mkdir,readFile}from"node:fs/promises";import path from"node:path";import{createHash}from"node:crypto";import{appendWorkspaceAuditEvent,getWorkspaceHistoryPath,WORKSPACE_HISTORY_ID}from"../core/history/workspace-history.js";import{readHistoryEntries}from"./history-read.js";import{acquireLock}from"../core/lock/lock.js";import{isFileMissingError,writeFileAtomic}from"../core/fs/fs-utils.js";import{nowIso}from"../core/shared/time.js";import{EXIT_CODE}from"../core/shared/constants.js";import{PmCliError}from"../core/shared/errors.js";class WorkspaceTransactionInterruptedError extends Error{constructor(message="Workspace transaction interrupted"){super(message),this.name="WorkspaceTransactionInterruptedError"}}const WORKSPACE_TRANSACTION_LOCK_ID="sdk-workspace-transaction",TRANSACTION_ID_PATTERN=/^[a-zA-Z0-9._-]+$/,DEFAULT_LOCK_TTL_SECONDS=30,DEFAULT_LOCK_WAIT_MS=3e3;function requiredIdentifier(value,label){if(typeof value!="string")throw new TypeError(`${label} must be a string`);const normalized=value.trim();if(!normalized||!TRANSACTION_ID_PATTERN.test(normalized))throw new TypeError(`${label} must match [a-zA-Z0-9._-]+`);return normalized}function requiredText(value,label){if(typeof value!="string")throw new TypeError(`${label} must be a string`);const normalized=value.trim();if(!normalized)throw new TypeError(`${label} must be non-empty`);return normalized}function validateWorkspaceTransactionIdentity(options){return{pmRoot:requiredText(options.pmRoot,"Transaction pmRoot"),transactionId:requiredIdentifier(options.transactionId,"Transaction id"),author:requiredText(options.author,"Transaction author")}}function positiveInteger(value,fallback,label){const resolved=value??fallback;if(!Number.isInteger(resolved)||resolved<=0)throw new TypeError(`${label} must be a positive integer`);return resolved}function journalPath(pmRoot,transactionId){return path.join(pmRoot,"transactions","sdk",`${transactionId}.json`)}async function writeJournal(pmRoot,journal){journal.updatedAt=nowIso();const target=journalPath(pmRoot,journal.transactionId);await mkdir(path.dirname(target),{recursive:!0}),await writeFileAtomic(target,`${JSON.stringify(journal,null,2)}
1
+ (function(){try{var e=typeof window<"u"?window:typeof global<"u"?global:typeof globalThis<"u"?globalThis:typeof self<"u"?self:{},n=new e.Error().stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="24a18484-0b8b-55d7-aa23-08a23b8d78ce")}catch{}})();import{mkdir,readFile}from"node:fs/promises";import path from"node:path";import{createHash}from"node:crypto";import{appendWorkspaceAuditEvent,getWorkspaceHistoryPath,WORKSPACE_HISTORY_ID}from"../core/history/workspace-history.js";import{readHistoryEntries}from"./history-read.js";import{acquireLock}from"../core/lock/lock.js";import{isFileMissingError,writeFileAtomic}from"../core/fs/fs-utils.js";import{nowIso}from"../core/shared/time.js";import{EXIT_CODE}from"../core/shared/constants.js";import{PmCliError}from"../core/shared/errors.js";class WorkspaceTransactionInterruptedError extends Error{constructor(message="Workspace transaction interrupted"){super(message),this.name="WorkspaceTransactionInterruptedError"}}const WORKSPACE_TRANSACTION_LOCK_ID="sdk-workspace-transaction",TRANSACTION_ID_PATTERN=/^[a-zA-Z0-9._-]+$/,DEFAULT_LOCK_TTL_SECONDS=30,DEFAULT_LOCK_WAIT_MS=3e3;function requiredIdentifier(value,label){if(typeof value!="string")throw new TypeError(`${label} must be a string`);const normalized=value.trim();if(!normalized||!TRANSACTION_ID_PATTERN.test(normalized))throw new TypeError(`${label} must match [a-zA-Z0-9._-]+`);return normalized}function requiredText(value,label){if(typeof value!="string")throw new TypeError(`${label} must be a string`);const normalized=value.trim();if(!normalized)throw new TypeError(`${label} must be non-empty`);return normalized}function validateWorkspaceTransactionIdentity(options){return{pmRoot:requiredText(options.pmRoot,"Transaction pmRoot"),transactionId:requiredIdentifier(options.transactionId,"Transaction id"),author:requiredText(options.author,"Transaction author")}}function positiveInteger(value,fallback,label){const resolved=value??fallback;if(!Number.isInteger(resolved)||resolved<=0)throw new TypeError(`${label} must be a positive integer`);return resolved}function journalPath(pmRoot,transactionId){return path.join(pmRoot,"transactions","sdk",`${transactionId}.json`)}async function writeJournal(pmRoot,journal){journal.updatedAt=nowIso();const target=journalPath(pmRoot,journal.transactionId);await mkdir(path.dirname(target),{recursive:!0}),await writeFileAtomic(target,`${JSON.stringify(journal,null,2)}
2
2
  `)}function hasValidJournalMetadata(journal){return Number.isInteger(journal.attempt)&&typeof journal.author=="string"&&typeof journal.createdAt=="string"&&typeof journal.updatedAt=="string"&&hasValidAbandonment(journal)}function hasValidAbandonment(journal){const retirement=journal.abandonment;return retirement===void 0?journal.schemaVersion===1&&journal.status!=="abandoned":journal.schemaVersion!==2||retirement===null||typeof retirement!="object"||journal.status==="committed"?!1:typeof retirement.reason=="string"&&retirement.reason.trim().length>0&&typeof retirement.author=="string"&&retirement.author.trim().length>0&&(retirement.supersededBy===void 0||typeof retirement.supersededBy=="string"&&TRANSACTION_ID_PATTERN.test(retirement.supersededBy)&&retirement.supersededBy!==journal.transactionId)}function createResultMap(source){return Object.assign(Object.create(null),source)}function cloneJournalValue(value,label){try{const serialized=JSON.stringify(value,(_key,nestedValue)=>{if(typeof nestedValue=="number"&&!Number.isFinite(nestedValue))throw new TypeError(`${label} contains a non-finite number`);if(nestedValue===void 0||typeof nestedValue=="function"||typeof nestedValue=="symbol")throw new TypeError(`${label} contains a non-JSON value`);return nestedValue});return JSON.parse(serialized)}catch(error){throw new TypeError(`${label} must be JSON serializable`,{cause:error})}}function cloneOptionalJournalValue(value,label){return value===void 0?void 0:cloneJournalValue(value,label)}function setJournalEntry(target,stepId,value,label){const cloned=cloneOptionalJournalValue(value,label);cloned===void 0?delete target[stepId]:target[stepId]=cloned}function isJournalMap(value){return value!==null&&typeof value=="object"&&!Array.isArray(value)}function hasUniqueKnownStepIds(value,stepIds){return Array.isArray(value)&&new Set(value).size===value.length&&value.every(stepId=>typeof stepId=="string"&&stepIds.includes(stepId))}function hasValidJournalLifecycle(journal,stepIds){if(!hasUniqueKnownStepIds(journal.startedStepIds,stepIds)||!hasUniqueKnownStepIds(journal.completedStepIds,stepIds)||!isJournalMap(journal.results)||!isJournalMap(journal.compensationData))return!1;const startedStepIds=journal.startedStepIds,started=new Set(startedStepIds),completedStepIds=journal.completedStepIds,completed=new Set(completedStepIds);return Object.keys(journal.compensationData).some(stepId=>!started.has(stepId))||Object.keys(journal.results).some(stepId=>!completed.has(stepId))?!1:journal.status==="committed"?stepIds.every(stepId=>completed.has(stepId)):!["compensated","abandoned"].includes(journal.status??"")||completed.size===0&&started.size===0}function invalidJournal(transactionId,detail){return new PmCliError(`Workspace transaction ${transactionId} journal is invalid (${detail})`,EXIT_CODE.CONFLICT,{code:"transaction_journal_invalid",required:"A valid durable journal is required before transaction recovery can continue.",why:"Corrupt journal data cannot establish which steps applied or still require compensation.",nextSteps:["Preserve the journal and inspect its integrity or restore a verified backup before retrying.","Do not switch to a new transaction id to bypass an unreadable journal; previously applied work may remain."]})}function journalPlanMismatch(transactionId){return new PmCliError(`Workspace transaction ${transactionId} journal does not match the supplied plan`,EXIT_CODE.CONFLICT,{code:"transaction_plan_mismatch",required:"Replay the original plan or safely abandon the interrupted transaction before replacing it.",why:"A journal records the plan it is resuming; applying a different plan under the same id could skip or duplicate steps.",nextSteps:["Re-run with the original document to resume or replay it.","For item batches use client.itemTransactions.status(transactionId), then client.itemTransactions.abandon(transactionId, { reason }) before committing a changed plan with a new transactionId.","Run pm ops gc --scope transactions --dry-run to inspect retention; active and explicitly abandoned journals remain protected."]})}function matchesJournalSteps(journal,recorded,requested){return requested===void 0||journal.abandonment!==void 0||journal.status==="compensated"||recorded.length===requested.length&&recorded.every((id,index)=>id===requested[index])}function parseJournal(raw,transactionId,stepIds){let parsed;try{parsed=JSON.parse(raw)}catch{throw invalidJournal(transactionId,"invalid JSON")}if(!parsed||typeof parsed!="object"||Array.isArray(parsed))throw invalidJournal(transactionId,"expected an object");const journal=parsed;if(!((journal.schemaVersion===1||journal.schemaVersion===2)&&journal.transactionId===transactionId))throw invalidJournal(transactionId,"schema version or transaction id does not match the journal file");const recordedStepIds=journal.stepIds;if(!Array.isArray(recordedStepIds)||!hasUniqueKnownStepIds(recordedStepIds,recordedStepIds)||!hasValidJournalLifecycle(journal,recordedStepIds)||!hasValidJournalMetadata(journal)||typeof journal.status!="string"||!["applying","compensating","compensated","committed","abandoned"].includes(journal.status))throw invalidJournal(transactionId,"invalid lifecycle, metadata, status or step ids");if(!matchesJournalSteps(journal,recordedStepIds,stepIds))throw journalPlanMismatch(transactionId);return{...journal,results:createResultMap(journal.results),compensationData:createResultMap(journal.compensationData)}}async function prepareJournalForApply(options,transactionId,author,stepIds,existing){let journal=existing;const recovered=journal!==void 0;if(journal?.status==="compensating"&&await compensateAppliedSteps(options,journal),journal===void 0||journal.status==="compensated"){const timestamp=nowIso();journal={schemaVersion:1,transactionId,author,status:"applying",attempt:(journal?.attempt??0)+1,createdAt:journal?.createdAt??timestamp,updatedAt:timestamp,stepIds,startedStepIds:[],completedStepIds:[],results:createResultMap(),compensationData:createResultMap(),...options.recoveryData===void 0?{}:{recoveryData:cloneJournalValue(options.recoveryData,"Transaction recovery data")}},await writeJournal(options.pmRoot,journal),await emitTransition(options,journal,"prepared")}return{journal,recovered}}async function applyPreparedTransaction(options,journal,recovered){try{await discoverAppliedSteps(journal,options.steps),await writeJournal(options.pmRoot,journal);const completed=new Set(journal.completedStepIds),started=new Set(journal.startedStepIds);for(const step of options.steps){if(completed.has(step.id))continue;const inspection=await step.inspect();let result=cloneOptionalJournalValue(inspection.result,`Step ${step.id} result`);if(inspection.state!=="applied"){const compensationData=await step.prepareCompensation?.();setJournalEntry(journal.compensationData,step.id,compensationData,`Step ${step.id} compensation data`),started.add(step.id),journal.startedStepIds=options.steps.map(candidate=>candidate.id).filter(stepId=>started.has(stepId)),await writeJournal(options.pmRoot,journal);const appliedResult=await step.apply();result=cloneOptionalJournalValue(appliedResult,`Step ${step.id} result`)}await emitTransition(options,journal,"step_applied",step.id),completed.add(step.id),journal.completedStepIds=options.steps.map(candidate=>candidate.id).filter(stepId=>completed.has(stepId)),setJournalEntry(journal.results,step.id,result,`Step ${step.id} result`),await writeJournal(options.pmRoot,journal),await emitTransition(options,journal,"step_recorded",step.id)}return await emitTransition(options,journal,"committing"),journal.status="committed",await writeJournal(options.pmRoot,journal),await emitTransition(options,journal,"committed"),{transactionId:journal.transactionId,status:"committed",recovered,results:createResultMap(journal.results)}}catch(error){if(error instanceof WorkspaceTransactionInterruptedError||journal.status==="committed")throw error;try{await compensateAppliedSteps(options,journal)}catch(compensationError){throw new AggregateError([error,compensationError],`Workspace transaction failed and compensation failed: ${String(compensationError)}`,{cause:compensationError})}throw error}}async function loadJournal(pmRoot,transactionId,stepIds){try{return parseJournal(await readFile(journalPath(pmRoot,transactionId),"utf8"),transactionId,stepIds)}catch(error){if(error!==null&&typeof error=="object"&&"code"in error&&isFileMissingError(error))return;throw error}}async function emitTransition(options,journal,transition,stepId){await options.onTransition?.({transactionId:journal.transactionId,attempt:journal.attempt,transition,...stepId===void 0?{}:{stepId}})}async function discoverAppliedSteps(journal,steps){const completed=new Set,results=createResultMap();for(const step of steps){const inspection=await step.inspect();inspection.state==="applied"&&(completed.add(step.id),setJournalEntry(results,step.id,inspection.result,`Step ${step.id} result`))}journal.results=results,journal.completedStepIds=steps.map(step=>step.id).filter(stepId=>completed.has(stepId))}async function compensateAppliedSteps(options,journal){journal.status="compensating",await writeJournal(options.pmRoot,journal),await emitTransition(options,journal,"compensating");const completed=new Set(journal.completedStepIds),started=new Set(journal.startedStepIds);for(const step of[...options.steps].reverse()){if(!started.has(step.id)&&!completed.has(step.id))continue;let shouldCompensate;try{const state=(await step.inspect()).state;shouldCompensate=started.has(step.id)&&(state==="applied"||state==="pending"&&step.compensatePending===!0)}catch{shouldCompensate=started.has(step.id)}shouldCompensate&&(await emitTransition(options,journal,"step_compensating",step.id),await step.compensate(journal.compensationData[step.id])),completed.delete(step.id),started.delete(step.id),journal.completedStepIds=options.steps.map(candidate=>candidate.id).filter(stepId=>completed.has(stepId)),journal.startedStepIds=options.steps.map(candidate=>candidate.id).filter(stepId=>started.has(stepId)),delete journal.results[step.id],delete journal.compensationData[step.id],await writeJournal(options.pmRoot,journal),await emitTransition(options,journal,"step_compensated",step.id)}journal.status="compensated",await writeJournal(options.pmRoot,journal),await emitTransition(options,journal,"compensated")}async function commitWorkspaceTransaction(options){const{pmRoot,transactionId,author}=validateWorkspaceTransactionIdentity(options),lockTtlSeconds=positiveInteger(options.lockTtlSeconds,DEFAULT_LOCK_TTL_SECONDS,"Transaction lock TTL"),lockWaitMs=positiveInteger(options.lockWaitMs,DEFAULT_LOCK_WAIT_MS,"Transaction lock wait"),steps=[...options.steps];if(steps.length===0)throw new TypeError("Workspace transaction requires at least one step");const stepIds=steps.map(step=>{const stepId=requiredIdentifier(step.id,"Transaction step id");if(stepId!==step.id)throw new TypeError("Transaction step id must not contain surrounding whitespace");return stepId});if(new Set(stepIds).size!==stepIds.length)throw new TypeError("Workspace transaction step ids must be unique");const stableOptions={...options,pmRoot,transactionId,author,steps,lockTtlSeconds,lockWaitMs},release=await acquireLock(pmRoot,WORKSPACE_TRANSACTION_LOCK_ID,lockTtlSeconds,author,!1,!1,lockWaitMs);try{const existing=await loadJournal(pmRoot,transactionId,stepIds);if(existing?.status==="abandoned"||existing?.abandonment!==void 0)throw retiredTransaction(existing);const recovered=existing!==void 0;if(existing?.status==="committed")return{transactionId,status:"committed",recovered:!0,results:createResultMap(existing.results)};const prepared=await prepareJournalForApply(stableOptions,transactionId,author,stepIds,existing);return await applyPreparedTransaction(stableOptions,prepared.journal,recovered||prepared.recovered)}finally{await release()}}function retiredTransaction(journal){const pending=journal.status!=="abandoned",supersededBy=journal.abandonment.supersededBy,retry=supersededBy===void 0?`Retry abandonment without supersededBy to finish retirement of ${JSON.stringify(journal.transactionId)}.`:`Retry abandonment with supersededBy ${JSON.stringify(supersededBy)} to finish retirement of ${JSON.stringify(journal.transactionId)}.`;return new PmCliError(pending?`Transaction ${journal.transactionId} retirement is incomplete`:`Transaction ${journal.transactionId} has been abandoned`,EXIT_CODE.CONFLICT,{code:"transaction_abandoned",required:pending?retry:"Use a new transactionId for the replacement plan.",why:pending?"Forward replay is fenced, but the recorded retirement has not finished. Preserve its replacement identity.":"An explicitly retired transaction identity cannot be resumed.",...pending?{retirement:{state:"pending",superseded_by:supersededBy??null},nextSteps:[retry]}:{}})}async function readWorkspaceTransaction(pmRoot,transactionId){const identity=validateWorkspaceTransactionIdentity({pmRoot,transactionId,author:"reader"});return loadJournal(identity.pmRoot,identity.transactionId)}function workspaceTransactionPlanDigest(journal){return createHash("sha256").update(JSON.stringify(journal.stepIds)).digest("hex")}async function abandonWorkspaceTransaction(options){const identity=validateWorkspaceTransactionIdentity(options),reason=requiredText(options.reason,"Abandonment reason"),supersededBy=options.supersededBy===void 0?void 0:requiredIdentifier(options.supersededBy,"Replacement transaction id");if(supersededBy===identity.transactionId)throw new TypeError("Replacement transaction id must differ from the abandoned id");const lockTtlSeconds=positiveInteger(options.lockTtlSeconds,DEFAULT_LOCK_TTL_SECONDS,"Transaction lock TTL"),lockWaitMs=positiveInteger(options.lockWaitMs,DEFAULT_LOCK_WAIT_MS,"Transaction lock wait"),release=await acquireLock(identity.pmRoot,WORKSPACE_TRANSACTION_LOCK_ID,lockTtlSeconds,identity.author,!1,!1,lockWaitMs);try{const journal=await loadJournal(identity.pmRoot,identity.transactionId);if(journal===void 0)throw new PmCliError(`Transaction ${identity.transactionId} was not found`,EXIT_CODE.NOT_FOUND);if(journal.status==="committed")throw new PmCliError(`Transaction ${identity.transactionId} is committed and cannot be abandoned`,EXIT_CODE.CONFLICT,{code:"transaction_committed",required:"Plan a separate explicit corrective mutation.",why:"Recovery cannot silently undo committed work."});if(journal.abandonment!==void 0&&journal.abandonment.supersededBy!==supersededBy)throw retiredTransaction(journal);if(journal.status==="abandoned")return journal;const steps=options.resolveSteps(structuredClone(journal));if(JSON.stringify(steps.map(step=>step.id))!==JSON.stringify(journal.stepIds))throw journalPlanMismatch(identity.transactionId);if(journal.abandonment===void 0){if(supersededBy!==void 0&&await loadJournal(identity.pmRoot,supersededBy)!==void 0)throw new PmCliError(`Replacement transaction ${supersededBy} already exists`,EXIT_CODE.CONFLICT);journal.schemaVersion=2,journal.abandonment={reason,author:identity.author,...supersededBy===void 0?{}:{supersededBy}},await writeJournal(identity.pmRoot,journal)}return journal.status!=="compensated"&&await compensateAppliedSteps({...options,...identity,steps},journal),await auditTransactionAbandonment(journal,identity.pmRoot,lockTtlSeconds,lockWaitMs),journal.status="abandoned",await writeJournal(identity.pmRoot,journal),journal}finally{await release()}}async function auditTransactionAbandonment(journal,pmRoot,lockTtlSeconds,lockWaitMs){const planDigest=workspaceTransactionPlanDigest(journal);if((await readHistoryEntries(getWorkspaceHistoryPath(pmRoot),WORKSPACE_HISTORY_ID)).some(entry=>entry.op==="transaction_abandon"&&entry.context?.transaction_id===journal.transactionId&&entry.context?.attempt===journal.attempt&&entry.context?.plan_digest===planDigest))return;const abandonment=journal.abandonment;await appendWorkspaceAuditEvent({pmRoot,author:abandonment.author,lockTtlSeconds,lockWaitMs,op:"transaction_abandon",message:abandonment.reason,context:{transaction_id:journal.transactionId,attempt:journal.attempt,plan_digest:planDigest,...abandonment.supersededBy===void 0?{}:{superseded_by:abandonment.supersededBy}}})}export{WorkspaceTransactionInterruptedError,abandonWorkspaceTransaction,commitWorkspaceTransaction,readWorkspaceTransaction,validateWorkspaceTransactionIdentity,workspaceTransactionPlanDigest};
3
3
  //# sourceMappingURL=workspace-transaction.js.map
package/dist/types.d.ts CHANGED
@@ -89,6 +89,10 @@ export type GovernanceCloseValidationDefault = (typeof GOVERNANCE_CLOSE_VALIDATI
89
89
  export declare const GOVERNANCE_WORKFLOW_ENFORCEMENT_VALUES:readonly ["off","warn","strict"];
90
90
  /** Restricts governance workflow enforcement values accepted by command, SDK, and storage contracts. */
91
91
  export type GovernanceWorkflowEnforcement = (typeof GOVERNANCE_WORKFLOW_ENFORCEMENT_VALUES)[number];
92
+ /** Supported values for starting an item whose declared blocker is still open. */
93
+ export declare const GOVERNANCE_START_BLOCKER_ENFORCEMENT_VALUES:readonly ["off","warn","strict"];
94
+ /** Start-with-open-blockers admission mode: refuse (`strict`, default), warn, or ignore. */
95
+ export type GovernanceStartBlockerEnforcement = (typeof GOVERNANCE_START_BLOCKER_ENFORCEMENT_VALUES)[number];
92
96
  /** Supported create/copy near-duplicate governance modes. */
93
97
  export declare const GOVERNANCE_DUPLICATE_DETECTION_MODE_VALUES:readonly ["off","advisory","strict"];
94
98
  /** Restricts create/copy near-duplicate governance behavior. */
@@ -945,6 +949,8 @@ force_required_for_stale_lock:boolean;
945
949
  create_default_type?:string;
946
950
  /** Per-type allowed-transition enforcement mode for `pm update --status`. Read raw from settings (not preset-derived) so existing projects are unaffected when unset; defaults to "off". */
947
951
  workflow_enforcement?:GovernanceWorkflowEnforcement;
952
+ /** Admission for moving an item with an open declared blocker into progress. Read raw (not preset-derived); unset means "strict". */
953
+ start_blocker_enforcement?:GovernanceStartBlockerEnforcement;
948
954
  /** Create/copy near-duplicate enforcement mode. */
949
955
  duplicate_detection_mode:GovernanceDuplicateDetectionMode;
950
956
  /** Inclusive similarity floor on the zero-to-one scale. */
package/dist/types.js CHANGED
@@ -1,2 +1,2 @@
1
- (function(){try{var e=typeof window<"u"?window:typeof global<"u"?global:typeof globalThis<"u"?globalThis:typeof self<"u"?self:{},n=new e.Error().stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="bc25122b-94e0-510e-bdbd-b10e3c255d68")}catch{}})();const BUILTIN_ITEM_TYPE_VALUES=["Epic","Feature","Task","Chore","Issue","Decision","Event","Reminder","Milestone","Meeting","Plan"],ITEM_TYPE_VALUES=BUILTIN_ITEM_TYPE_VALUES,STATUS_VALUES=["draft","open","in_progress","blocked","closed","canceled"],RUNTIME_STATUS_ROLE_VALUES=["draft","active","blocked","terminal","terminal_done","terminal_canceled","default_open","default_close","default_cancel"],RUNTIME_FIELD_TYPE_VALUES=["string","number","boolean","string_array","array","object"],RUNTIME_FIELD_COMMAND_VALUES=["create","update","update_many","list","search","calendar","context"],RUNTIME_UNKNOWN_FIELD_POLICY_VALUES=["allow","warn","reject"],DEPENDENCY_KIND_VALUES=["blocks","parent","child","parent_child","child_of","related","related_to","discovered_from","blocked_by","incident_from","epic","implements","recurs_from","supersedes","task","verifies"],SCOPE_VALUES=["project","global"],RISK_VALUES=["low","medium","high","critical"],ISSUE_SEVERITY_VALUES=["low","medium","high","critical"],CONFIDENCE_TEXT_VALUES=["low","medium","high"],ITEM_FORMAT_VALUES=["toon","json_markdown"],SPRINT_RELEASE_FORMAT_POLICY_VALUES=["warn","strict_error"],PARENT_REFERENCE_POLICY_VALUES=["warn","strict_error"],VALIDATE_METADATA_PROFILE_VALUES=["core","strict","custom"],GOVERNANCE_PRESET_VALUES=["minimal","default","strict","custom"],GOVERNANCE_OWNERSHIP_ENFORCEMENT_VALUES=["none","warn","strict"],GOVERNANCE_CREATE_MODE_DEFAULT_VALUES=["progressive","strict"],GOVERNANCE_CLOSE_VALIDATION_DEFAULT_VALUES=["off","warn","strict"],GOVERNANCE_WORKFLOW_ENFORCEMENT_VALUES=["off","warn","strict"],GOVERNANCE_DUPLICATE_DETECTION_MODE_VALUES=["off","advisory","strict"],VALIDATE_METADATA_REQUIRED_FIELD_VALUES=["author","acceptance_criteria","estimated_minutes","close_reason","reviewer","risk","confidence","sprint","release"],RECURRENCE_FREQUENCY_VALUES=["daily","weekly","monthly","yearly"],RECURRENCE_WEEKDAY_VALUES=["mon","tue","wed","thu","fri","sat","sun"];function weekdayOrderIndex(value){return RECURRENCE_WEEKDAY_VALUES.indexOf(value)}const PLAN_MODE_VALUES=["draft","research","review","approved","executing","paused","completed","superseded"],PLAN_STEP_STATUS_VALUES=["pending","in_progress","completed","blocked","skipped","superseded"],PLAN_HARNESS_VALUES=["codex","claude-code","cursor","generic"],PLAN_STEP_LINK_KIND_VALUES=["related","blocks","blocked_by","depends_on","discovered_from","implements","recurs_from","verifies","supersedes"],CONTEXT_DEPTH_VALUES=["brief","standard","deep","full"],CONTEXT_SECTION_VALUES=["hierarchy","activity","progress","recently_created","unparented","blockers","files","workload","staleness","tests"];export{BUILTIN_ITEM_TYPE_VALUES,CONFIDENCE_TEXT_VALUES,CONTEXT_DEPTH_VALUES,CONTEXT_SECTION_VALUES,DEPENDENCY_KIND_VALUES,GOVERNANCE_CLOSE_VALIDATION_DEFAULT_VALUES,GOVERNANCE_CREATE_MODE_DEFAULT_VALUES,GOVERNANCE_DUPLICATE_DETECTION_MODE_VALUES,GOVERNANCE_OWNERSHIP_ENFORCEMENT_VALUES,GOVERNANCE_PRESET_VALUES,GOVERNANCE_WORKFLOW_ENFORCEMENT_VALUES,ISSUE_SEVERITY_VALUES,ITEM_FORMAT_VALUES,ITEM_TYPE_VALUES,PARENT_REFERENCE_POLICY_VALUES,PLAN_HARNESS_VALUES,PLAN_MODE_VALUES,PLAN_STEP_LINK_KIND_VALUES,PLAN_STEP_STATUS_VALUES,RECURRENCE_FREQUENCY_VALUES,RECURRENCE_WEEKDAY_VALUES,RISK_VALUES,RUNTIME_FIELD_COMMAND_VALUES,RUNTIME_FIELD_TYPE_VALUES,RUNTIME_STATUS_ROLE_VALUES,RUNTIME_UNKNOWN_FIELD_POLICY_VALUES,SCOPE_VALUES,SPRINT_RELEASE_FORMAT_POLICY_VALUES,STATUS_VALUES,VALIDATE_METADATA_PROFILE_VALUES,VALIDATE_METADATA_REQUIRED_FIELD_VALUES,weekdayOrderIndex};
1
+ (function(){try{var e=typeof window<"u"?window:typeof global<"u"?global:typeof globalThis<"u"?globalThis:typeof self<"u"?self:{},n=new e.Error().stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="b181d38e-7120-5d34-98d9-fd9b4619b9d9")}catch{}})();const BUILTIN_ITEM_TYPE_VALUES=["Epic","Feature","Task","Chore","Issue","Decision","Event","Reminder","Milestone","Meeting","Plan"],ITEM_TYPE_VALUES=BUILTIN_ITEM_TYPE_VALUES,STATUS_VALUES=["draft","open","in_progress","blocked","closed","canceled"],RUNTIME_STATUS_ROLE_VALUES=["draft","active","blocked","terminal","terminal_done","terminal_canceled","default_open","default_close","default_cancel"],RUNTIME_FIELD_TYPE_VALUES=["string","number","boolean","string_array","array","object"],RUNTIME_FIELD_COMMAND_VALUES=["create","update","update_many","list","search","calendar","context"],RUNTIME_UNKNOWN_FIELD_POLICY_VALUES=["allow","warn","reject"],DEPENDENCY_KIND_VALUES=["blocks","parent","child","parent_child","child_of","related","related_to","discovered_from","blocked_by","incident_from","epic","implements","recurs_from","supersedes","task","verifies"],SCOPE_VALUES=["project","global"],RISK_VALUES=["low","medium","high","critical"],ISSUE_SEVERITY_VALUES=["low","medium","high","critical"],CONFIDENCE_TEXT_VALUES=["low","medium","high"],ITEM_FORMAT_VALUES=["toon","json_markdown"],SPRINT_RELEASE_FORMAT_POLICY_VALUES=["warn","strict_error"],PARENT_REFERENCE_POLICY_VALUES=["warn","strict_error"],VALIDATE_METADATA_PROFILE_VALUES=["core","strict","custom"],GOVERNANCE_PRESET_VALUES=["minimal","default","strict","custom"],GOVERNANCE_OWNERSHIP_ENFORCEMENT_VALUES=["none","warn","strict"],GOVERNANCE_CREATE_MODE_DEFAULT_VALUES=["progressive","strict"],GOVERNANCE_CLOSE_VALIDATION_DEFAULT_VALUES=["off","warn","strict"],GOVERNANCE_WORKFLOW_ENFORCEMENT_VALUES=["off","warn","strict"],GOVERNANCE_START_BLOCKER_ENFORCEMENT_VALUES=["off","warn","strict"],GOVERNANCE_DUPLICATE_DETECTION_MODE_VALUES=["off","advisory","strict"],VALIDATE_METADATA_REQUIRED_FIELD_VALUES=["author","acceptance_criteria","estimated_minutes","close_reason","reviewer","risk","confidence","sprint","release"],RECURRENCE_FREQUENCY_VALUES=["daily","weekly","monthly","yearly"],RECURRENCE_WEEKDAY_VALUES=["mon","tue","wed","thu","fri","sat","sun"];function weekdayOrderIndex(value){return RECURRENCE_WEEKDAY_VALUES.indexOf(value)}const PLAN_MODE_VALUES=["draft","research","review","approved","executing","paused","completed","superseded"],PLAN_STEP_STATUS_VALUES=["pending","in_progress","completed","blocked","skipped","superseded"],PLAN_HARNESS_VALUES=["codex","claude-code","cursor","generic"],PLAN_STEP_LINK_KIND_VALUES=["related","blocks","blocked_by","depends_on","discovered_from","implements","recurs_from","verifies","supersedes"],CONTEXT_DEPTH_VALUES=["brief","standard","deep","full"],CONTEXT_SECTION_VALUES=["hierarchy","activity","progress","recently_created","unparented","blockers","files","workload","staleness","tests"];export{BUILTIN_ITEM_TYPE_VALUES,CONFIDENCE_TEXT_VALUES,CONTEXT_DEPTH_VALUES,CONTEXT_SECTION_VALUES,DEPENDENCY_KIND_VALUES,GOVERNANCE_CLOSE_VALIDATION_DEFAULT_VALUES,GOVERNANCE_CREATE_MODE_DEFAULT_VALUES,GOVERNANCE_DUPLICATE_DETECTION_MODE_VALUES,GOVERNANCE_OWNERSHIP_ENFORCEMENT_VALUES,GOVERNANCE_PRESET_VALUES,GOVERNANCE_START_BLOCKER_ENFORCEMENT_VALUES,GOVERNANCE_WORKFLOW_ENFORCEMENT_VALUES,ISSUE_SEVERITY_VALUES,ITEM_FORMAT_VALUES,ITEM_TYPE_VALUES,PARENT_REFERENCE_POLICY_VALUES,PLAN_HARNESS_VALUES,PLAN_MODE_VALUES,PLAN_STEP_LINK_KIND_VALUES,PLAN_STEP_STATUS_VALUES,RECURRENCE_FREQUENCY_VALUES,RECURRENCE_WEEKDAY_VALUES,RISK_VALUES,RUNTIME_FIELD_COMMAND_VALUES,RUNTIME_FIELD_TYPE_VALUES,RUNTIME_STATUS_ROLE_VALUES,RUNTIME_UNKNOWN_FIELD_POLICY_VALUES,SCOPE_VALUES,SPRINT_RELEASE_FORMAT_POLICY_VALUES,STATUS_VALUES,VALIDATE_METADATA_PROFILE_VALUES,VALIDATE_METADATA_REQUIRED_FIELD_VALUES,weekdayOrderIndex};
2
2
  //# sourceMappingURL=types.js.map
package/docs/COMMANDS.md CHANGED
@@ -963,6 +963,97 @@ pm release <id>
963
963
  `--author` on claim/release flows, not a metadata update. Use `--force` only when
964
964
  explicitly overriding terminal-state or lock conflicts.
965
965
 
966
+ Starting work respects declared blockers
967
+ ([pm-urzubi](../.agents/pm/issues/pm-urzubi.toon)). `pm update <id> --status
968
+ in_progress`, `pm claim <id> --start`, `pm claim --next --start` and the
969
+ `start-task` action refuse with `start_blocked_by_open_dependency` (exit 4)
970
+ when the item moves into the start status while a `blocked_by` target is still
971
+ open. An item already in that status is not rechecked, so `claim --start` on it
972
+ succeeds. The check runs before any write: the
973
+ explicit `claim <id> --start` judges it under the claim's item lock, and its
974
+ status update judges again under its own lock. When the first check refuses,
975
+ nothing is written. When a blocker opens between the two writes, the start
976
+ refuses and, if it took the item from no owner, releases that claim. The release
977
+ is fenced by assignee and claim principal, so another principal's takeover is
978
+ never cleared; principal is pm's unit of ownership, so a concurrent reclaim by
979
+ the same principal is indistinguishable from the original claim. If the item already had an owner, or that
980
+ release cannot run, the claim stays and the refusal's `why` reports the partial
981
+ completion. The
982
+ refusal lists the open blockers in `details.open_blockers` and suggests the
983
+ exact `pm update` that drops the edge. When no single command can do that
984
+ safely (clearing the `blocked_by` scalar would also drop an independent
985
+ `blocked_by` edge, or a legacy row's short id, alias kind or external locator
986
+ would not match a removal selector; see
987
+ [pm-4yxzvd](../.agents/pm/issues/pm-4yxzvd.toon)), it suggests inspecting the
988
+ item instead. The check uses the effective start status, including the
989
+ registered `in_progress` fallback when the workflow names `open`.
990
+ A terminal blocker never blocks, and an update that removes the blocker in the
991
+ same call is admitted. `--force` starts anyway and records
992
+ `start_blocker_override:<id>:<blockers>`. `governance.start_blocker_enforcement`
993
+ selects `strict` (default), `warn` (start and record
994
+ `start_with_open_blockers:<id>:<blockers>`), or `off`. A blocker whose record
995
+ cannot be judged (an invalid or non-UTF-8 document, an unsupported item format,
996
+ or an ambiguous or conflicting identity) refuses under `strict` with that typed
997
+ error. `warn` and `--force` still admit the start and record
998
+ `start_blocker_unverifiable:<id>:<code>`. Filesystem failures and merge-conflict
999
+ markers always propagate.
1000
+
1001
+ ### Expected revisions (`--expect-hash`)
1002
+
1003
+ Read-decide-write agents guard a write with the revision they decided on
1004
+ ([pm-gh1576](../.agents/pm/features/pm-gh1576.toon)). `pm get <id> --json`
1005
+ reports `item_hash`. The global `--expect-hash <item_hash>` flag makes every
1006
+ locked write the command makes to that item refuse with `item_revision_conflict`
1007
+ (exit 4) if the item changed since:
1008
+
1009
+ ```bash
1010
+ H=$(pm get pm-a1b2 --json | jq -r .item_hash)
1011
+ pm close pm-a1b2 "Delivered by PR 1" --expect-hash "$H"
1012
+ H1=$(pm get pm-a1b2 --json | jq -r .item_hash)
1013
+ H2=$(pm get pm-c3d4 --json | jq -r .item_hash)
1014
+ pm update-many --ids pm-a1b2,pm-c3d4 --priority 1 \
1015
+ --expect-hash "pm-a1b2=$H1" --expect-hash "pm-c3d4=$H2"
1016
+ ```
1017
+
1018
+ A bare hash binds to the command's `<id>` argument (for `plan`, the id after
1019
+ the subcommand; `plan create` has none). `<id>=<item_hash>`
1020
+ (repeatable or comma-separated) names targets explicitly and is required for
1021
+ commands without one item argument. The refusal reports `expected_revision`,
1022
+ `current_revision`, and `details.changes_since` (the newest 10 history entries
1023
+ since the declared revision, with their top-level `fields`). The refused write
1024
+ is suppressed; writes the same command already committed (for example the
1025
+ claim of a composed `claim --start`) stay durable, and the message then says
1026
+ so ("Earlier writes by this same invocation already committed"). Read commands refuse the flag with
1027
+ `expect_hash_requires_mutation`, and an empty value (such as an unset `$H`)
1028
+ refuses with `expect_hash_invalid` instead of running unguarded. Bulk
1029
+ `update-many`/`close-many` fail only stale rows, forward and `--rollback` alike, each
1030
+ carrying `error_code`, `expected_revision`, `current_revision` and
1031
+ `details.changes_since`. When `close` auto-unblocks a guarded dependent whose revision
1032
+ moved, the close stays committed and the command refuses with that dependent's
1033
+ `item_revision_conflict` once the fan-out finishes. `side_effects` names every
1034
+ write that committed first: the close and each dependent already unblocked. `close-many` reports such a row as `closed` (`update-many` closing
1035
+ through `--status`, as `updated`), with the dependent's conflict under
1036
+ `dependent_revision_conflict`. A guarded `restore` refuses to recover
1037
+ over a live file that cannot be read as a document; an unguarded restore still
1038
+ recovers it. `deps` only reads, so it refuses the flag. History maintenance
1039
+ (`history-redact`, `history-repair` including `--salvage-tail`, `history-compact`)
1040
+ is checked under the stream lock. Its bulk forms (`history-compact --ids`,
1041
+ `history-repair --all`) carry the same typed conflict fields on refused rows. An
1042
+ item file whose embedded id differs from its physical file name (even by case on a
1043
+ case-sensitive filesystem) binds no revision: reading or declaring one refuses with
1044
+ `item_identity_conflict` until the identity is repaired. `validate --auto-fix`/`--prune-missing` refuses typed once every fix
1045
+ was attempted. When an extension parser or preflight override rewrites the
1046
+ invocation's tracker or arguments, a guarded invocation refuses with
1047
+ `expect_hash_invocation_rewritten`, since which item it writes cannot be proved.
1048
+ The same refusal covers a parser pass during final dispatch, which must also keep
1049
+ the guard admissible (adding `--background` late refuses with
1050
+ `expect_hash_background_unsupported`). Inventory commands
1051
+ (`package inventory`, `extension inventory`) never write items, so they validate
1052
+ the flag and then refuse with `expect_hash_requires_mutation`. An item the command never wrote produces the
1053
+ warning `expect_hash_unchecked:<id>:no_locked_write_reached_this_item`. MCP
1054
+ tools accept the same tokens as `expectHash`; the SDK exposes
1055
+ `withExpectedItemRevisions` (see [SDK](SDK.md#expected-item-revisions)).
1056
+
966
1057
  ## Logs
967
1058
 
968
1059
  ```bash
@@ -103,6 +103,7 @@ If the existing `settings.json` is valid JSON but fails schema validation (for e
103
103
  | `item_types.definitions[]` | custom item types and type options |
104
104
  | `governance.create_default_type` | default `--type` used by `pm create "title"` when `--type` is omitted (defaults to `Task`); must resolve to a known item type |
105
105
  | `governance.workflow_enforcement` | per-type transition enforcement mode for `pm update --status` (`off` default, `warn`, or `strict`) |
106
+ | `governance.start_blocker_enforcement` | starting an item (`update --status in_progress`, `claim --start`) while a declared `blocked_by` target is open: `strict` (default; refuse unless `--force`), `warn`, or `off` |
106
107
  | `governance.duplicate_detection_mode` | create/copy similarity policy: `off`, `advisory`, or `strict`; the minimal preset uses `off`, while the default and strict presets use `strict` |
107
108
  | `governance.duplicate_detection_threshold` | inclusive likely-duplicate score from `0` through `1` (default `0.8`) |
108
109
  | `governance.duplicate_detection_limit` | maximum matches attached to a create/copy advisory from `0` through `20` (default `3`) |
package/docs/SDK.md CHANGED
@@ -1307,8 +1307,21 @@ atomic evidence-backed completion tracked by
1307
1307
  commands that must coordinate several SDK mutations. A plan supplies a stable
1308
1308
  transaction id plus ordered steps. Each step can inspect its durable state,
1309
1309
  apply an idempotent forward mutation, and append an idempotent compensation.
1310
- The coordinator serializes transaction writers with one workspace lock and
1310
+ The coordinator serializes transaction coordinators with one workspace lock and
1311
1311
  persists `.agents/pm/transactions/sdk/<transaction-id>.json` after every step.
1312
+ That lock does not exclude ordinary writers ([GH-1620](https://github.com/unbraind/pm-cli/issues/1620)):
1313
+ `update`, `close`, `pm comments --add` and every other SDK, CLI or MCP mutation
1314
+ take per-item locks, so a peer can commit between a step's `inspect()` and
1315
+ `apply()`. A step that decides from what it read guards its write with an
1316
+ [expected item revision](#expected-item-revisions): in `inspect()`, take both
1317
+ the decision data and `item_hash` from one `get()`, then run the write inside
1318
+ `withExpectedItemRevisions()`. The
1319
+ write then refuses with `item_revision_conflict` instead of overwriting the
1320
+ peer's change, and the transaction compensates. The coordinator invokes
1321
+ `compensate()`, including during recovery, without an expected revision.
1322
+ Built-in item compensations detect intervening writes. A custom adapter must
1323
+ fence or reconcile its own compensation, or it can restore a stale value over a
1324
+ peer's write.
1312
1325
  Before invoking a pending step, it also persists that step's durable ownership
1313
1326
  marker. Recovery and compensation therefore distinguish mutations begun by
1314
1327
  this transaction from matching domain state that existed before the attempt.
@@ -1348,6 +1361,102 @@ failures and the journal keeps recovery data. Repeated compensation recognizes
1348
1361
  its durable marker even when a peer adds context afterward. Legacy timestamp
1349
1362
  receipts remain supported, with ambiguous partial effects refused.
1350
1363
 
1364
+ ### Expected item revisions
1365
+
1366
+ Expected-revision preconditions are tracked by
1367
+ [pm-gh1576](../.agents/pm/features/pm-gh1576.toon).
1368
+
1369
+ Read-decide-write agents and packages guard a write with the revision their
1370
+ decision was based on. Every current `pm get` / `PmClient.get` read reports
1371
+ `item_hash`, the SHA-256 of the canonical item document and the same value as
1372
+ the item's latest history `after_hash` (a field-projected read includes it only
1373
+ when the selection names the bare `item_hash`, for example `--fields title,tags,item_hash`;
1374
+ the item-qualified `item.item_hash` still projects a custom metadata field of that name).
1375
+ `readItemRevision(id)` returns it alone. Take the hash from the same read your
1376
+ decision uses: a separate read could observe a newer revision than the data you
1377
+ decided on. Run the write inside `withExpectedItemRevisions`:
1378
+
1379
+ ```ts
1380
+ import { PmClient, isItemRevisionConflict } from "@unbrained/pm-cli/sdk";
1381
+
1382
+ const pm = new PmClient({ pmRoot });
1383
+ // Decide from the same read that reports the revision, never from a separate one.
1384
+ const read = await pm.get(id);
1385
+ const reason = `Delivered by ${read.item.tags?.find((tag) => tag.startsWith("delivery-pr:"))}`;
1386
+ try {
1387
+ const { result, unchecked } = await pm.withExpectedItemRevisions(
1388
+ [{ id, itemHash: read.item_hash! }],
1389
+ () => pm.close(id, reason),
1390
+ );
1391
+ } catch (error) {
1392
+ if (!isItemRevisionConflict(error)) throw error;
1393
+ // error.context.current_revision and error.context.details.changes_since
1394
+ // describe what changed; re-read, decide again, retry with the new hash.
1395
+ }
1396
+ ```
1397
+
1398
+ The check runs inside the locked item store, after the item lock is held and
1399
+ before that write. It therefore covers every item write the wrapped
1400
+ operation makes: lifecycle commands, annotations, linked files/docs/tests,
1401
+ dependency edits, `restore`, `delete`, history maintenance (`history-redact`,
1402
+ `history-repair`, `history-compact`, checked under the stream lock), bulk
1403
+ transactions and extension commands that mutate through the SDK. A `delete`
1404
+ advances the expectation to the item's absent (tombstone) revision, and a
1405
+ `restore` of a deleted item is judged under its own id, so a peer deletion
1406
+ between the read and the restore refuses instead of being resurrected over. A mismatch refuses that write with `item_revision_conflict`
1407
+ (exit 4); writes the operation already committed stay durable. The refusal carries `item_id`, `expected_revision`,
1408
+ `current_revision` and `details` with `changes_since`, the newest
1409
+ 10 history entries after the declared revision
1410
+ (`op`, `author`, `ts`, top-level `fields`), plus `changes_since_omitted`.
1411
+ `expected_revision_recorded: false` means no history entry ever had that hash.
1412
+ After one of the operation's own writes commits, the expectation advances to
1413
+ that revision, so a multi-write composition such as `claim --start` does not
1414
+ refuse itself; an interleaved peer write still refuses the next step. The
1415
+ result lists `checked` ids and `unchecked` ids that the operation never wrote:
1416
+ an unchecked expectation proved nothing. Expectations bind to the item's embedded id, so every
1417
+ spelling that locates the item (a short id, or a case variant on a
1418
+ case-insensitive filesystem) is guarded. The scope is shared process-wide across the
1419
+ published SDK entrypoints (`/sdk`, `/sdk/core`, ...), and
1420
+ `isItemRevisionConflict` checks the refusal structurally, so mixing entrypoints
1421
+ neither drops a guard nor hides its conflict. Trackers are compared by physical
1422
+ path (`realpath`), so a symlinked spelling of the same tracker is guarded too. `PmClient.readItemRevision` and
1423
+ `PmClient.withExpectedItemRevisions` resolve targets inside the client's
1424
+ extension context, so extension item types bind exactly as `get` reads them,
1425
+ then run the operation outside it, so its own actions (extension commands
1426
+ included) activate and dispatch normally. The standalone `readItemRevision` /
1427
+ `withExpectedItemRevisions` functions use the registrations already active in
1428
+ the calling context. A deleted item with retained history binds too: its
1429
+ revision is the empty tombstone its `delete` recorded (the delete entry's
1430
+ `after_hash`), so a guarded `restore` or history maintenance of a deleted item
1431
+ can declare it. Bulk `update-many`
1432
+ and `close-many` keep their partial-success semantics: a stale row fails with
1433
+ `error_code: "item_revision_conflict"` plus its `expected_revision`,
1434
+ `current_revision` and the same `details` (`changes_since`,
1435
+ `changes_since_omitted`) a single-item refusal carries, while fresh rows
1436
+ proceed. When a peer writes while `restore` or history maintenance waits for
1437
+ its lock, a declared revision turns the generic drift refusal into this typed
1438
+ conflict. Linked-test result recording (`test --run`, `test-all` with
1439
+ `test_result_tracking` enabled) stays best-effort, except that a stale declared
1440
+ revision refuses with `item_revision_conflict` instead of a warning. A guard cannot reach a `--background` test worker,
1441
+ which runs in another process, so `test`/`test-all --background` with an
1442
+ expected revision refuses with `expect_hash_background_unsupported`. Only the CLI invocation scope reads `GlobalOptions.expectHash`;
1443
+ direct `run*` SDK calls ignore it, so guard SDK writes with the functions above
1444
+ or `runAction`/MCP `expectHash`.
1445
+
1446
+ Transports use the same grammar. The CLI's global `--expect-hash <item_hash>`
1447
+ binds to the command's `<id>`; `--expect-hash <id>=<item_hash>` (repeatable or
1448
+ comma-separated) names targets explicitly and is required for bulk commands.
1449
+ MCP tools and `PmClient.run` accept `expectHash` (string or string array) on
1450
+ every item-writing action, at the top level or inside `options`; either
1451
+ placement is enforced. A native read action refuses the flag with
1452
+ `expect_hash_requires_mutation`, and a malformed, supplied-but-empty or `null`
1453
+ value (for example an unset shell variable) refuses with `expect_hash_invalid`
1454
+ rather than silently dropping the guard. An expectation no write reached is reported as the
1455
+ warning `expect_hash_unchecked:<id>:no_locked_write_reached_this_item`. This
1456
+ precondition guards the gap between an agent's read and its write; lease
1457
+ fencing ([pm-gh1549](../.agents/pm/issues/pm-gh1549.toon)) still guards the
1458
+ write itself against lock takeover, and the two compose.
1459
+
1351
1460
  ### Schema evolution and workspace history
1352
1461
 
1353
1462
  Tracked by [pm-dijg](../.agents/pm/features/pm-dijg.toon) and
@@ -1 +1 @@
1
- {"version":5,"transcript_version":2,"transcript_digest":"sha256:b4b151f3d36860e73cd535d0520e9f03b4ecec4444ee81a8b868896b5b0bba47","estimator":"ceil(utf8_bytes / 4)","comparison_scope":"plan_recovery_tracker_root_canonicalized","measurement_scope":"output_before_token_accounting","published_with_release":true,"orientation":{"canonical_task_id":"orientation-context-intent","required_capabilities":["actionable_work","active_ownership","workspace_state"],"measured_winner_tokens":1051,"protocols":[{"task_id":"orientation-context-intent","command_count":1,"estimated_tokens":1051,"capabilities":["actionable_work","active_ownership","workspace_state"],"capability_evidence":{"actionable_work":[{"step_id":"context","field_path":"hierarchy"}],"active_ownership":[{"step_id":"context","field_path":"activity"}],"workspace_state":[{"step_id":"context","field_path":"summary.active_items"}]}},{"task_id":"orientation-operating-rules","command_count":4,"estimated_tokens":2067,"capabilities":["actionable_work","active_ownership","workspace_state"],"capability_evidence":{"actionable_work":[{"step_id":"open","field_path":"items"}],"active_ownership":[{"step_id":"in-progress","field_path":"items"}],"workspace_state":[{"step_id":"context","field_path":"summary.active_items"}]}},{"task_id":"orientation-contracts-next","command_count":2,"estimated_tokens":5213,"capabilities":["actionable_work","active_ownership","workspace_state"],"capability_evidence":{"actionable_work":[{"step_id":"next","field_path":"recommended.id"}],"active_ownership":[{"step_id":"next","field_path":"held_by_others"}],"workspace_state":[{"step_id":"next","field_path":"summary.in_progress"}]}}]},"tasks":[{"id":"orientation-context-intent","max_estimated_tokens":1051,"steps":[{"id":"context","max_estimated_tokens":1051,"accounting_mode":"self_reported"}]},{"id":"orientation-contracts-next","max_estimated_tokens":5213,"steps":[{"id":"contracts","max_estimated_tokens":4481,"accounting_mode":"self_reported"},{"id":"next","max_estimated_tokens":732,"accounting_mode":"self_reported"}]},{"id":"orientation-operating-rules","max_estimated_tokens":2067,"steps":[{"id":"context","max_estimated_tokens":1015,"accounting_mode":"self_reported"},{"id":"search","max_estimated_tokens":164,"accounting_mode":"self_reported"},{"id":"open","max_estimated_tokens":712,"accounting_mode":"self_reported"},{"id":"in-progress","max_estimated_tokens":176,"accounting_mode":"self_reported"}]},{"id":"context-bootstrap","max_estimated_tokens":2005,"steps":[{"id":"triage","max_estimated_tokens":619,"accounting_mode":"self_reported"},{"id":"orient","max_estimated_tokens":1015,"accounting_mode":"self_reported"},{"id":"inspect","max_estimated_tokens":371,"accounting_mode":"self_reported"}]},{"id":"closed-domain-recovery","max_estimated_tokens":630,"steps":[{"id":"refuse-intent","max_estimated_tokens":259,"accounting_mode":"self_reported"},{"id":"retry-intent","max_estimated_tokens":371,"accounting_mode":"self_reported"}]},{"id":"unknown-option-recovery","max_estimated_tokens":596,"steps":[{"id":"refuse-option","max_estimated_tokens":433,"accounting_mode":"self_reported"},{"id":"retry-option","max_estimated_tokens":163,"accounting_mode":"self_reported"}]},{"id":"lifecycle-mutation","max_estimated_tokens":873,"steps":[{"id":"create","max_estimated_tokens":20,"accounting_mode":"self_reported"},{"id":"inspect-open","max_estimated_tokens":363,"accounting_mode":"self_reported"},{"id":"close","max_estimated_tokens":92,"accounting_mode":"self_reported"},{"id":"inspect-closed","max_estimated_tokens":398,"accounting_mode":"self_reported"}]},{"id":"bulk-effect-outcomes","max_estimated_tokens":392,"steps":[{"id":"create-bulk-target","max_estimated_tokens":20,"accounting_mode":"self_reported"},{"id":"partial-effect","max_estimated_tokens":240,"accounting_mode":"self_reported"},{"id":"no-effect","max_estimated_tokens":132,"accounting_mode":"self_reported"}]},{"id":"plan-execution-mutations","max_estimated_tokens":806,"steps":[{"id":"create-plan","max_estimated_tokens":21,"accounting_mode":"self_reported"},{"id":"add-step","max_estimated_tokens":254,"accounting_mode":"self_reported"},{"id":"complete-step","max_estimated_tokens":253,"accounting_mode":"self_reported"},{"id":"inspect-plan","max_estimated_tokens":278,"accounting_mode":"self_reported"}]}],"composite_max_estimated_tokens":13633}
1
+ {"version":5,"transcript_version":2,"transcript_digest":"sha256:b4b151f3d36860e73cd535d0520e9f03b4ecec4444ee81a8b868896b5b0bba47","estimator":"ceil(utf8_bytes / 4)","comparison_scope":"plan_recovery_tracker_root_canonicalized","measurement_scope":"output_before_token_accounting","published_with_release":true,"orientation":{"canonical_task_id":"orientation-context-intent","required_capabilities":["actionable_work","active_ownership","workspace_state"],"measured_winner_tokens":1051,"protocols":[{"task_id":"orientation-context-intent","command_count":1,"estimated_tokens":1051,"capabilities":["actionable_work","active_ownership","workspace_state"],"capability_evidence":{"actionable_work":[{"step_id":"context","field_path":"hierarchy"}],"active_ownership":[{"step_id":"context","field_path":"activity"}],"workspace_state":[{"step_id":"context","field_path":"summary.active_items"}]}},{"task_id":"orientation-operating-rules","command_count":4,"estimated_tokens":2067,"capabilities":["actionable_work","active_ownership","workspace_state"],"capability_evidence":{"actionable_work":[{"step_id":"open","field_path":"items"}],"active_ownership":[{"step_id":"in-progress","field_path":"items"}],"workspace_state":[{"step_id":"context","field_path":"summary.active_items"}]}},{"task_id":"orientation-contracts-next","command_count":2,"estimated_tokens":5213,"capabilities":["actionable_work","active_ownership","workspace_state"],"capability_evidence":{"actionable_work":[{"step_id":"next","field_path":"recommended.id"}],"active_ownership":[{"step_id":"next","field_path":"held_by_others"}],"workspace_state":[{"step_id":"next","field_path":"summary.in_progress"}]}}]},"tasks":[{"id":"orientation-context-intent","max_estimated_tokens":1051,"steps":[{"id":"context","max_estimated_tokens":1051,"accounting_mode":"self_reported"}]},{"id":"orientation-contracts-next","max_estimated_tokens":5213,"steps":[{"id":"contracts","max_estimated_tokens":4481,"accounting_mode":"self_reported"},{"id":"next","max_estimated_tokens":732,"accounting_mode":"self_reported"}]},{"id":"orientation-operating-rules","max_estimated_tokens":2067,"steps":[{"id":"context","max_estimated_tokens":1015,"accounting_mode":"self_reported"},{"id":"search","max_estimated_tokens":164,"accounting_mode":"self_reported"},{"id":"open","max_estimated_tokens":712,"accounting_mode":"self_reported"},{"id":"in-progress","max_estimated_tokens":176,"accounting_mode":"self_reported"}]},{"id":"context-bootstrap","max_estimated_tokens":2025,"steps":[{"id":"triage","max_estimated_tokens":619,"accounting_mode":"self_reported"},{"id":"orient","max_estimated_tokens":1015,"accounting_mode":"self_reported"},{"id":"inspect","max_estimated_tokens":391,"accounting_mode":"self_reported"}]},{"id":"closed-domain-recovery","max_estimated_tokens":650,"steps":[{"id":"refuse-intent","max_estimated_tokens":259,"accounting_mode":"self_reported"},{"id":"retry-intent","max_estimated_tokens":391,"accounting_mode":"self_reported"}]},{"id":"unknown-option-recovery","max_estimated_tokens":596,"steps":[{"id":"refuse-option","max_estimated_tokens":433,"accounting_mode":"self_reported"},{"id":"retry-option","max_estimated_tokens":163,"accounting_mode":"self_reported"}]},{"id":"lifecycle-mutation","max_estimated_tokens":915,"steps":[{"id":"create","max_estimated_tokens":20,"accounting_mode":"self_reported"},{"id":"inspect-open","max_estimated_tokens":384,"accounting_mode":"self_reported"},{"id":"close","max_estimated_tokens":92,"accounting_mode":"self_reported"},{"id":"inspect-closed","max_estimated_tokens":419,"accounting_mode":"self_reported"}]},{"id":"bulk-effect-outcomes","max_estimated_tokens":392,"steps":[{"id":"create-bulk-target","max_estimated_tokens":20,"accounting_mode":"self_reported"},{"id":"partial-effect","max_estimated_tokens":240,"accounting_mode":"self_reported"},{"id":"no-effect","max_estimated_tokens":132,"accounting_mode":"self_reported"}]},{"id":"plan-execution-mutations","max_estimated_tokens":806,"steps":[{"id":"create-plan","max_estimated_tokens":21,"accounting_mode":"self_reported"},{"id":"add-step","max_estimated_tokens":254,"accounting_mode":"self_reported"},{"id":"complete-step","max_estimated_tokens":253,"accounting_mode":"self_reported"},{"id":"inspect-plan","max_estimated_tokens":278,"accounting_mode":"self_reported"}]}],"composite_max_estimated_tokens":13715}