@unbrained/pm-cli 2026.10.24 → 2026.10.26

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 (222) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +48 -0
  3. package/dist/cli/error-guidance.d.ts +5 -0
  4. package/dist/cli/error-guidance.js +2 -2
  5. package/dist/cli/extension-command-help.js +2 -2
  6. package/dist/cli/register-history-activity.js +1 -1
  7. package/dist/cli/register-mutation.js +2 -2
  8. package/dist/cli-bundle/bundle-manifest.json +1 -1
  9. package/dist/cli-bundle/chunks/{chunk-3S5FLCG7.js → chunk-25MTQAHX.js} +2 -2
  10. package/dist/cli-bundle/chunks/chunk-57YPOOED.js +2 -0
  11. package/dist/cli-bundle/chunks/{chunk-YEI4N4A6.js → chunk-7UDQAINZ.js} +22 -22
  12. package/dist/cli-bundle/chunks/{chunk-ZDVTXLA6.js → chunk-ER7RLJW5.js} +2 -2
  13. package/dist/cli-bundle/chunks/{chunk-CSE47ZAN.js → chunk-MWCG25PO.js} +2 -2
  14. package/dist/cli-bundle/chunks/chunk-N6FTEILW.js +20 -0
  15. package/dist/cli-bundle/chunks/{chunk-CDR5ES7Q.js → chunk-NGKTROAO.js} +90 -86
  16. package/dist/cli-bundle/chunks/chunk-OCDRHIVQ.js +6 -0
  17. package/dist/cli-bundle/chunks/{chunk-R7KWBFM2.js → chunk-ONC4BBQP.js} +2 -2
  18. package/dist/cli-bundle/chunks/{chunk-URZB3USE.js → chunk-OUDR2HWR.js} +42 -42
  19. package/dist/cli-bundle/chunks/{chunk-FASDGYQC.js → chunk-T7T4WLUY.js} +3 -3
  20. package/dist/cli-bundle/chunks/{chunk-Z62RXNHW.js → chunk-TGOG5BFM.js} +2 -2
  21. package/dist/cli-bundle/chunks/chunk-UF6EXZSP.js +2 -0
  22. package/dist/cli-bundle/chunks/chunk-VS2IIV3F.js +128 -0
  23. package/dist/cli-bundle/chunks/chunk-XAIR53QZ.js +2 -0
  24. package/dist/cli-bundle/chunks/{chunk-VSSHB6XI.js → chunk-Y42DYMZX.js} +2 -2
  25. package/dist/cli-bundle/chunks/{chunk-MA7SNKYK.js → chunk-YJBIPNDL.js} +2 -2
  26. package/dist/cli-bundle/chunks/{chunk-4PNAUT2E.js → chunk-Z35XAHUN.js} +2 -2
  27. package/dist/cli-bundle/chunks/chunk-ZLVFUZI4.js +30 -0
  28. package/dist/cli-bundle/chunks/{chunk-3VI4XDP2.js → chunk-ZT64RDMF.js} +2 -2
  29. package/dist/cli-bundle/chunks/{indexed-receipt-blobs-XANVELIV.js → indexed-receipt-blobs-NWQIDTH7.js} +2 -2
  30. package/dist/cli-bundle/chunks/{mutation-admission-NVTBZ7DH.js → mutation-admission-VTNV6JV2.js} +2 -2
  31. package/dist/cli-bundle/chunks/{register-list-query-2FE6K7FQ.js → register-list-query-LZDWUZS4.js} +3 -3
  32. package/dist/cli-bundle/chunks/register-mutation-M2A7PH42.js +20 -0
  33. package/dist/cli-bundle/chunks/register-operations-E7P2JTXM.js +2 -0
  34. package/dist/cli-bundle/chunks/{register-setup-P3VLCTDY.js → register-setup-DOV7KGSH.js} +2 -2
  35. package/dist/cli-bundle/chunks/{settlement-hook-YUVCO65H.js → settlement-hook-SVD6CO7U.js} +2 -2
  36. package/dist/cli-bundle/chunks/stage-LYLEOAQB.js +2 -0
  37. package/dist/cli-bundle/focused-chunks/chunk-226XKW3W.js +8 -0
  38. package/dist/cli-bundle/focused-chunks/chunk-ABMNYWV6.js +2 -0
  39. package/dist/cli-bundle/focused-chunks/{chunk-RSEGFZTY.js → chunk-ADSBGYLW.js} +54 -54
  40. package/dist/cli-bundle/focused-chunks/{chunk-JYXA4JIP.js → chunk-B266DDBJ.js} +2 -2
  41. package/dist/cli-bundle/focused-chunks/{chunk-R536272H.js → chunk-BH7RE2ZY.js} +2 -2
  42. package/dist/cli-bundle/focused-chunks/chunk-BL77VCRB.js +22 -0
  43. package/dist/cli-bundle/focused-chunks/chunk-CSCZYSNZ.js +2 -0
  44. package/dist/cli-bundle/focused-chunks/{chunk-6HJCSHBG.js → chunk-D5YQ4KAE.js} +2 -2
  45. package/dist/cli-bundle/focused-chunks/{chunk-TAHSXKAT.js → chunk-E5C24MCV.js} +2 -2
  46. package/dist/cli-bundle/focused-chunks/{chunk-SHUPX6G2.js → chunk-I7H3XF25.js} +2 -2
  47. package/dist/cli-bundle/focused-chunks/chunk-IF6C43IP.js +2 -0
  48. package/dist/cli-bundle/focused-chunks/chunk-KERJNFXY.js +2 -0
  49. package/dist/cli-bundle/focused-chunks/{chunk-BUZ2Z4P3.js → chunk-KTXU5WU6.js} +2 -2
  50. package/dist/cli-bundle/focused-chunks/chunk-NE27VCU7.js +91 -0
  51. package/dist/cli-bundle/focused-chunks/chunk-NFN2P4TW.js +2 -0
  52. package/dist/cli-bundle/focused-chunks/{chunk-7RQOFHMH.js → chunk-NLZUI234.js} +4 -4
  53. package/dist/cli-bundle/focused-chunks/chunk-PK5FJIVA.js +12 -0
  54. package/dist/cli-bundle/focused-chunks/{chunk-IUAYTD2S.js → chunk-UHWTPOM4.js} +24 -24
  55. package/dist/cli-bundle/focused-chunks/{chunk-ES2NGVHZ.js → chunk-UP765R2V.js} +2 -2
  56. package/dist/cli-bundle/focused-chunks/{chunk-POLMFGPQ.js → chunk-WDWHUJQW.js} +2 -2
  57. package/dist/cli-bundle/focused-chunks/{chunk-NX7TVNUM.js → chunk-YQKLKRMW.js} +2 -2
  58. package/dist/cli-bundle/focused-chunks/chunk-YTEC2EPM.js +3 -0
  59. package/dist/cli-bundle/focused-chunks/{chunk-RVUGTIIU.js → chunk-YW5GHQN2.js} +2 -2
  60. package/dist/cli-bundle/focused-chunks/{indexed-receipt-blobs-FVOA3XMI.js → indexed-receipt-blobs-YFDNGHCM.js} +2 -2
  61. package/dist/cli-bundle/focused-chunks/{mutation-admission-PB6YFY7S.js → mutation-admission-L42E3U5U.js} +2 -2
  62. package/dist/cli-bundle/focused-chunks/{settlement-hook-2VXEB6PP.js → settlement-hook-3CADO4XZ.js} +2 -2
  63. package/dist/cli-bundle/focused-chunks/stage-TUQHWU7N.js +2 -0
  64. package/dist/cli-bundle/main.js +19 -19
  65. package/dist/cli-bundle/sdk-authoring.js +1 -1
  66. package/dist/cli-bundle/sdk-contracts.js +2 -2
  67. package/dist/cli-bundle/sdk-core.js +23 -23
  68. package/dist/cli-bundle/sdk-governance.js +1 -1
  69. package/dist/cli-bundle/sdk-graph.js +1 -1
  70. package/dist/cli-bundle/sdk-merge.js +57 -58
  71. package/dist/cli-bundle/sdk-query.js +1 -1
  72. package/dist/cli-bundle/sdk-runtime.js +1 -1
  73. package/dist/cli-bundle/sdk-testing.js +1 -1
  74. package/dist/cli-bundle/sdk.js +9 -9
  75. package/dist/core/diagnostics/remediation.js +1 -1
  76. package/dist/core/extensions/extension-types.d.ts +3 -0
  77. package/dist/core/extensions/extension-types.js +1 -1
  78. package/dist/core/output/mutation-projection.js +1 -1
  79. package/dist/core/policy/workflow-policy-store.js +1 -1
  80. package/dist/core/shared/errors.d.ts +22 -0
  81. package/dist/core/shared/errors.js +1 -1
  82. package/dist/core/shared/secret-rules.d.ts +17 -0
  83. package/dist/core/shared/secret-rules.js +2 -1
  84. package/dist/core/store/item-mutation-precondition.d.ts +8 -0
  85. package/dist/core/store/item-mutation-precondition.js +1 -1
  86. package/dist/core/store/item-store.js +1 -1
  87. package/dist/core/store/settings.js +2 -2
  88. package/dist/sdk/agent/command-recovery.js +1 -1
  89. package/dist/sdk/cli-contracts/commander-mutation-options.js +1 -1
  90. package/dist/sdk/cli-contracts/commander-types.js +1 -1
  91. package/dist/sdk/cli-contracts/completeness.js +1 -1
  92. package/dist/sdk/cli-contracts/flag-contracts.d.ts +2 -0
  93. package/dist/sdk/cli-contracts/flag-contracts.js +1 -1
  94. package/dist/sdk/cli-contracts/flag-lexicon-contracts.js +1 -1
  95. package/dist/sdk/cli-contracts/grammar-contracts.js +1 -1
  96. package/dist/sdk/cli-contracts/registration-helpers.d.ts +3 -1
  97. package/dist/sdk/cli-contracts/registration-helpers.js +2 -2
  98. package/dist/sdk/cli-contracts/runtime-contracts.js +1 -1
  99. package/dist/sdk/cli-contracts/tool-option-contracts.d.ts +4 -0
  100. package/dist/sdk/cli-contracts/tool-option-contracts.js +1 -1
  101. package/dist/sdk/cli-contracts/tool-parameter-tables.js +1 -1
  102. package/dist/sdk/cli-contracts/tool-schema.d.ts +2 -2
  103. package/dist/sdk/cli-contracts/tool-schema.js +1 -1
  104. package/dist/sdk/completion/fish.js +3 -1
  105. package/dist/sdk/completion/zsh.js +3 -1
  106. package/dist/sdk/context-signal-store.js +2 -2
  107. package/dist/sdk/core.d.ts +1 -1
  108. package/dist/sdk/core.js +1 -1
  109. package/dist/sdk/extension-command-context.js +2 -2
  110. package/dist/sdk/generated/generated-error-code-catalog-part-1.js +1 -1
  111. package/dist/sdk/generated/generated-error-code-catalog-part-2.js +1 -1
  112. package/dist/sdk/governance/health-merge-evidence.d.ts +5 -0
  113. package/dist/sdk/governance/health-merge-evidence.js +1 -1
  114. package/dist/sdk/governance/health.js +1 -1
  115. package/dist/sdk/index.d.ts +3 -2
  116. package/dist/sdk/index.js +1 -1
  117. package/dist/sdk/item-transaction.js +1 -1
  118. package/dist/sdk/lifecycle/close-many.d.ts +3 -0
  119. package/dist/sdk/lifecycle/close-many.js +1 -1
  120. package/dist/sdk/lifecycle/close.d.ts +3 -1
  121. package/dist/sdk/lifecycle/close.js +1 -1
  122. package/dist/sdk/lifecycle/create.d.ts +2 -0
  123. package/dist/sdk/lifecycle/create.js +1 -1
  124. package/dist/sdk/lifecycle/plan.d.ts +2 -0
  125. package/dist/sdk/lifecycle/plan.js +1 -1
  126. package/dist/sdk/lifecycle/restore.js +1 -1
  127. package/dist/sdk/lifecycle-policy.d.ts +11 -0
  128. package/dist/sdk/lifecycle-policy.js +1 -1
  129. package/dist/sdk/merge/history-scalar-rebase.d.ts +9 -0
  130. package/dist/sdk/merge/history-scalar-rebase.js +2 -0
  131. package/dist/sdk/merge/ignored-artifacts.d.ts +13 -0
  132. package/dist/sdk/merge/ignored-artifacts.js +2 -0
  133. package/dist/sdk/merge/stage.js +1 -1
  134. package/dist/sdk/merge/three-way.js +1 -1
  135. package/dist/sdk/mutation-guard.d.ts +6 -1
  136. package/dist/sdk/mutation-guard.js +1 -1
  137. package/dist/sdk/output/fit-envelope.d.ts +98 -0
  138. package/dist/sdk/output/fit-envelope.js +2 -0
  139. package/dist/sdk/public-surface.json +1 -1
  140. package/dist/sdk/query/activity.d.ts +14 -2
  141. package/dist/sdk/query/activity.js +1 -1
  142. package/dist/sdk/query/calendar.d.ts +16 -0
  143. package/dist/sdk/query/calendar.js +3 -3
  144. package/dist/sdk/query/context.js +2 -2
  145. package/dist/sdk/query/get.js +1 -1
  146. package/dist/sdk/query/list.js +1 -1
  147. package/dist/sdk/query/search-contracts.js +1 -1
  148. package/dist/sdk/query/window-pagination.d.ts +20 -0
  149. package/dist/sdk/query/window-pagination.js +2 -0
  150. package/dist/sdk/query.d.ts +1 -0
  151. package/dist/sdk/query.js +1 -1
  152. package/dist/sdk/read-output-contracts.js +1 -1
  153. package/dist/sdk/read-output-session.d.ts +1 -1
  154. package/dist/sdk/read-output-session.js +1 -1
  155. package/dist/sdk/runtime-input.js +1 -1
  156. package/dist/sdk/runtime-primitives.d.ts +1 -1
  157. package/dist/sdk/runtime-primitives.js +1 -1
  158. package/dist/sdk/runtime.d.ts +3 -3
  159. package/dist/sdk/runtime.js +1 -1
  160. package/dist/sdk/scheduling-shortcuts.d.ts +2 -0
  161. package/dist/sdk/scheduling-shortcuts.js +1 -1
  162. package/dist/sdk/schema-migration.js +2 -2
  163. package/dist/sdk/templates.d.ts +7 -1
  164. package/dist/sdk/templates.js +2 -2
  165. package/dist/sdk/workspace-transaction.d.ts +3 -1
  166. package/dist/sdk/workspace-transaction.js +2 -2
  167. package/docs/COMMANDS.md +21 -2
  168. package/docs/EXTENSIONS.md +1 -1
  169. package/docs/MERGE_SAFETY.md +28 -0
  170. package/docs/MUTATION_INTEGRITY.md +33 -0
  171. package/docs/QUALITY_EVIDENCE.md +9 -0
  172. package/docs/READ_OUTPUT_CONTRACTS.md +55 -1
  173. package/docs/SDK.md +173 -12
  174. package/docs/WORKFLOW_POLICIES.md +14 -1
  175. package/docs/agent-task-token-baseline.json +1 -1
  176. package/docs/agent-task-transcripts.json +1 -1
  177. package/docs/generated/FLAG_LEXICON_BUDGETS.md +4 -4
  178. package/docs/generated/REFUSAL_CLOSURE_CENSUS.md +6 -3
  179. package/marketplace.json +1 -1
  180. package/package.json +1 -1
  181. package/packages/pm-beads/package.json +1 -1
  182. package/packages/pm-calendar/extensions/calendar/index.ts +6 -0
  183. package/packages/pm-calendar/extensions/calendar/shortcuts.ts +2 -2
  184. package/packages/pm-calendar/package.json +1 -1
  185. package/packages/pm-command-kit/package.json +1 -1
  186. package/packages/pm-digital-twin/package.json +1 -1
  187. package/packages/pm-governance-audit/package.json +1 -1
  188. package/packages/pm-guide-shell/package.json +1 -1
  189. package/packages/pm-kanban/package.json +1 -1
  190. package/packages/pm-lifecycle-hooks/package.json +1 -1
  191. package/packages/pm-linked-test-adapters/package.json +1 -1
  192. package/packages/pm-search-advanced/package.json +1 -1
  193. package/packages/pm-templates/extensions/templates/runtime.ts +3 -1
  194. package/packages/pm-templates/package.json +1 -1
  195. package/packages/pm-todos/package.json +1 -1
  196. package/packages/pm-vcs/package.json +1 -1
  197. package/plugins/pm-claude/.claude-plugin/plugin.json +1 -1
  198. package/plugins/pm-claude/package.json +1 -1
  199. package/plugins/pm-codex/.codex-plugin/plugin.json +1 -1
  200. package/plugins/pm-codex/package.json +1 -1
  201. package/plugins/pm-codex/plugin.json +1 -1
  202. package/runtime-dependencies.json +1 -1
  203. package/dist/cli-bundle/chunks/chunk-CNEUJHA4.js +0 -2
  204. package/dist/cli-bundle/chunks/chunk-HZNFNETE.js +0 -127
  205. package/dist/cli-bundle/chunks/chunk-KGZGXC7T.js +0 -30
  206. package/dist/cli-bundle/chunks/chunk-TZYQ4JDZ.js +0 -6
  207. package/dist/cli-bundle/chunks/chunk-UY5UHI4I.js +0 -2
  208. package/dist/cli-bundle/chunks/chunk-VPMQEVU6.js +0 -20
  209. package/dist/cli-bundle/chunks/register-mutation-KZXQUZUH.js +0 -20
  210. package/dist/cli-bundle/chunks/register-operations-BOCBWNKJ.js +0 -2
  211. package/dist/cli-bundle/chunks/stage-WRDTMCCF.js +0 -2
  212. package/dist/cli-bundle/focused-chunks/chunk-3QYTS4OY.js +0 -12
  213. package/dist/cli-bundle/focused-chunks/chunk-5MKNVJCM.js +0 -2
  214. package/dist/cli-bundle/focused-chunks/chunk-AGCEWJ27.js +0 -21
  215. package/dist/cli-bundle/focused-chunks/chunk-CEEOWDB7.js +0 -8
  216. package/dist/cli-bundle/focused-chunks/chunk-GXAXDPD4.js +0 -2
  217. package/dist/cli-bundle/focused-chunks/chunk-NFFPNMTH.js +0 -2
  218. package/dist/cli-bundle/focused-chunks/chunk-SBJ2IKTH.js +0 -3
  219. package/dist/cli-bundle/focused-chunks/chunk-X5YRJMAY.js +0 -2
  220. package/dist/cli-bundle/focused-chunks/chunk-XTYEASIY.js +0 -2
  221. package/dist/cli-bundle/focused-chunks/chunk-ZMWLV2CE.js +0 -91
  222. package/dist/cli-bundle/focused-chunks/stage-MQE4PFWT.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]="89f9137d-876a-5aa7-8272-f83df5edf9e9")}catch{}})();import crypto from"node:crypto";import{constants}from"node:fs";import{isFileMissingError,readRegularFile,transactionPreviewNonRegularFileError}from"../core/fs/fs-utils.js";import{cp,lstat,mkdir,mkdtemp,open,readdir,realpath,rm,stat,writeFile}from"node:fs/promises";import{tmpdir}from"node:os";import{getActiveExtensionRegistrations,setActiveExtensionRegistrations}from"../core/extensions/index.js";import path from"node:path";import{transferMutationStdinTokenPolicy}from"../core/item/parse.js";import{EXIT_CODE,PM_REQUIRED_SUBDIRS,SETTINGS_DEFAULTS,SETTINGS_FILENAME}from"../core/shared/constants.js";import{resolveItemTypeRegistry}from"../core/item/type-registry.js";import{listAllItemMetadataLight}from"../core/store/item-store.js";import{runWithItemMutationPrecondition}from"../core/store/item-mutation-precondition.js";import{isConfigurationOnlySettingsRead,readSettings}from"../core/store/settings.js";import{DEFAULT_RUNTIME_SCHEMA_FILE_PATHS,filePathForSchemaSection,loadRuntimeSchemaFromOptionalFiles,normalizeRuntimeSchemaSettings}from"../core/schema/runtime-schema.js";import{validateSettings}from"../core/store/settings-validator.js";import{resolveWorkspaceRoot}from"../core/store/paths.js";import{getSessionStatePath}from"../core/session/session-state.js";import{PmCliError}from"../core/shared/errors.js";import{stableUpsertValue}from"./structured-mutations.js";import{stableStringify}from"../core/shared/serialization.js";import{assertMutationOptionValueTypes}from"./lifecycle/mutation-option-validation.js";import{readHistoryEntries}from"./history-read.js";import{PM_CONTEXT_INTENTS_FILE}from"./context-intent-runtime.js";import{close,create,deleteItem,get,release,restore,update,runWithActiveExtensions}from"./runtime.js";import{commitWorkspaceTransaction,validateWorkspaceTransactionIdentity}from"./workspace-transaction.js";async function resolveTransactionSchema(pmRoot,settings){if(!isConfigurationOnlySettingsRead(pmRoot))return{schema:normalizeRuntimeSchemaSettings(settings.schema),typeDefinitionsFromFile:[]};const loaded=await loadGuardedRuntimeSchema(pmRoot,settings.schema);return{schema:loaded.schema,typeDefinitionsFromFile:loaded.type_definitions_from_file??[]}}async function listUpsertKeyCandidates(pmRoot,settings,schemaContext,key){const typeRegistry=resolveItemTypeRegistry({...settings,item_types:{...settings.item_types,definitions:[...settings.item_types?.definitions??[],...schemaContext.typeDefinitionsFromFile]}},getActiveExtensionRegistrations());return(await listAllItemMetadataLight(pmRoot,settings.item_format,typeRegistry.type_to_folder,void 0,schemaContext.schema)).flatMap(item=>{const value=item[key];return value==null?[]:[{id:item.id,value}]})}function createUniqueKeyIndex(pmRoot){const scans=new Map,scan=async key=>{const settings=await readSettings(pmRoot),schemaContext=await resolveTransactionSchema(pmRoot,settings),byValue=new Map;for(const candidate of await listUpsertKeyCandidates(pmRoot,settings,schemaContext,key)){const stable=stableUpsertValue(candidate.value);byValue.set(stable,[...byValue.get(stable)??[],candidate.id])}return byValue};return{lookup(key){const pending=scans.get(key)??scan(key);return scans.set(key,pending),pending},async recordCreate(item){for(const[key,pending]of scans){const value=item[key];if(value==null)continue;const byValue=await pending,stable=stableUpsertValue(value);byValue.set(stable,[...byValue.get(stable)??[],String(item.id)])}},invalidate(){scans.clear()}}}async function assertUniqueKeyFree(index,unique){const stable=stableUpsertValue(unique.value),existing=(await index.lookup(unique.key)).get(stable)??[];if(existing.length!==0)throw new PmCliError(`Upsert key "${unique.key}" value ${JSON.stringify(stable)} now matches existing item ${existing.join(", ")}; the plan was resolved before it existed.`,EXIT_CODE.CONFLICT,{code:"upsert_key_conflict",required:"Re-plan the document so the row updates the existing item instead of creating a duplicate.",why:"Keyed upsert is resolved before the writer lock, so every remaining create is re-checked under the lock; concurrent imports therefore cannot create two items for one key value.",nextSteps:["Re-run pm item mutate with the same document and --upsert-key; the re-plan turns the row into an update of the existing item.","Inspect the existing item first when its content should win over the document row."]})}function assertUpsertMatchCurrent(document,id,match){const current=document.metadata[match.key],expected=stableUpsertValue(match.value);if(stableUpsertValue(current)!==expected)throw new PmCliError(`Item ${id} no longer carries upsert key "${match.key}" value ${JSON.stringify(expected)}; the plan selected it before the key changed.`,EXIT_CODE.CONFLICT,{code:"upsert_match_changed",required:"Re-plan the document so the row is resolved against the current key values.",why:"Keyed upsert selects its target before the writer lock, so the update re-reads the key under the lock instead of updating a mismatched item blindly.",nextSteps:["Re-run pm item mutate with the same document and --upsert-key; the re-plan creates or re-targets the row from the current key values."]})}function isValidUniqueKey(value){if(value===null||typeof value!="object"||Array.isArray(value))return!1;const candidate=value;return typeof candidate.key=="string"&&candidate.key.trim().length>0&&candidate.value!==void 0&&candidate.value!==null}function mergeTransactionMutationOptions(source,additions){const merged={...source,...additions};return source===void 0?merged:transferMutationStdinTokenPolicy(source,merged)}async function readItemSnapshot(pmRoot,id){try{const item=(await get(id,{},{pmRoot})).item;return{id:item.id,closedAt:item.closed_at,updatedAt:item.updated_at,assignee:item.assignee,claimPrincipal:item.claim_principal}}catch(error){if(error instanceof PmCliError&&error.exitCode===EXIT_CODE.NOT_FOUND)return;throw error}}function deriveStepId(mutation,index){const sanitizedTarget=mutation.id.replaceAll(/[^a-zA-Z0-9._-]/gu,"_"),fingerprint=crypto.createHash("sha256").update(stableStringify(mutation)).digest("hex").slice(0,12);return`${index+1}-${mutation.op}-${sanitizedTarget}-${fingerprint}`}function deriveOutcomeKey(mutation,index){const sanitizedTarget=mutation.id.replaceAll(/[^a-zA-Z0-9._-]/gu,"_");return`${index+1}-${mutation.op}-${sanitizedTarget}`}function bulkHistoryMarker(transactionId,stepId,phase){return`bulk-item-transaction ${transactionId} ${stepId} ${phase}`}async function isUpdateMarkerApplied(pmRoot,canonicalId,transactionId,stepId){const entries=await readHistoryEntries(path.join(pmRoot,"history",`${canonicalId}.jsonl`),canonicalId),applyMarker=bulkHistoryMarker(transactionId,stepId,"apply"),compensateMarker=bulkHistoryMarker(transactionId,stepId,"compensate");for(let index=entries.length-1;index>=0;index-=1){const message=entries[index].message;if(message===applyMarker)return!0;if(message===compensateMarker)return!1}return!1}async function prepareRestoreCompensation(pmRoot,mutation){const snapshot=await readItemSnapshot(pmRoot,mutation.id);if(snapshot?.updatedAt===void 0)throw new PmCliError(`Bulk ${mutation.op} target ${mutation.id} does not exist or has no restorable version`,EXIT_CODE.NOT_FOUND);return{target_updated_at:snapshot.updatedAt}}async function compensateByRestore(config,itemId,stepId,data){const target=data!==null&&typeof data=="object"&&!Array.isArray(data)?data.target_updated_at:void 0;typeof target=="string"&&await readItemSnapshot(config.pmRoot,itemId)!==void 0&&await restore(itemId,target,{force:!0,author:config.author,message:bulkHistoryMarker(config.transactionId,stepId,"compensate")},{pmRoot:config.pmRoot})}function buildCreateStep(config,mutation,stepId){return{id:stepId,async inspect(){const snapshot=await readItemSnapshot(config.pmRoot,mutation.id);if(snapshot===void 0)return{state:"pending"};if(snapshot.closedAt!==void 0){const history=await readHistoryEntries(path.join(config.pmRoot,"history",`${snapshot.id}.jsonl`),snapshot.id),reason=`Compensated: interrupted bulk transaction ${config.transactionId}`;if(history.filter(entry=>entry.op==="close").at(-1)?.patch.some(change=>change.op==="add"||change.op==="replace"?change.path==="/metadata/close_reason"&&change.value===reason:!1)??!1)throw new PmCliError(`Created item ${snapshot.id} remains closed by transaction compensation; explicitly reopen or reconcile it before retrying.`,EXIT_CODE.CONFLICT,{code:"transaction_create_compensated",item_id:snapshot.id,required:"Review the compensated item and explicitly reopen it before retrying the same document.",why:"This item's latest closure belongs to transaction compensation. Automatically reopening it could overwrite later lifecycle decisions.",nextSteps:[`Run pm item reopen ${snapshot.id} "Retry compensated creation" after reviewing its history, then retry the transaction.`]})}return{state:"applied",result:{id:snapshot.id,op:"create"}}},async apply(){mutation.unique!==void 0&&await assertUniqueKeyFree(config.uniqueIndex,mutation.unique);const created=await create(mergeTransactionMutationOptions(mutation.options,{author:config.author,id:mutation.id}),{pmRoot:config.pmRoot}),createdItem=created.item;return config.onCreateWarnings?.(String(createdItem.id),created.warnings),await config.uniqueIndex.recordCreate(createdItem),{id:String(createdItem.id),op:"create"}},async compensate(){const snapshot=await readItemSnapshot(config.pmRoot,mutation.id);if(snapshot!==void 0){if(config.createCompensation==="delete"){await deleteItem(mutation.id,{force:!0,author:config.author,message:`Compensate interrupted bulk transaction ${config.transactionId}`},{pmRoot:config.pmRoot});return}snapshot.closedAt===void 0&&await close(mutation.id,`Compensated: interrupted bulk transaction ${config.transactionId}`,{force:!0,author:config.author},{pmRoot:config.pmRoot})}}}}function buildUpdateStep(config,mutation,stepId){return{id:stepId,async inspect(){const snapshot=await readItemSnapshot(config.pmRoot,mutation.id);return snapshot===void 0?{state:"pending"}:await isUpdateMarkerApplied(config.pmRoot,snapshot.id,config.transactionId,stepId)?{state:"applied",result:{id:snapshot.id,op:"update"}}:{state:"pending"}},async prepareCompensation(){return prepareRestoreCompensation(config.pmRoot,mutation)},async apply(){const applyUpdate=()=>update(mutation.id,mergeTransactionMutationOptions(mutation.options,{author:config.author,message:bulkHistoryMarker(config.transactionId,stepId,"apply")}),{pmRoot:config.pmRoot}),match=mutation.match,updatedItem=(match===void 0?await applyUpdate():await runWithItemMutationPrecondition({pmRoot:config.pmRoot,id:String((await get(mutation.id,{},{pmRoot:config.pmRoot})).item.id),validate:document=>assertUpsertMatchCurrent(document,mutation.id,match)},applyUpdate)).item;return config.uniqueIndex.invalidate(),{id:String(updatedItem.id),op:"update"}},async compensate(data){await compensateByRestore(config,mutation.id,stepId,data)}}}function buildCloseStep(config,mutation,stepId){return{id:stepId,async inspect(){const snapshot=await readItemSnapshot(config.pmRoot,mutation.id);return snapshot===void 0?{state:"pending"}:await isUpdateMarkerApplied(config.pmRoot,snapshot.id,config.transactionId,stepId)?{state:"applied",result:{id:snapshot.id,op:"close"}}:snapshot.closedAt===void 0?{state:"pending"}:{state:"applied",result:{id:snapshot.id,op:"close"}}},async prepareCompensation(){return prepareRestoreCompensation(config.pmRoot,mutation)},async apply(){const closedItem=(await close(mutation.id,mutation.reason,mergeTransactionMutationOptions(mutation.options,{author:config.author,message:bulkHistoryMarker(config.transactionId,stepId,"apply")}),{pmRoot:config.pmRoot})).item;return config.uniqueIndex.invalidate(),{id:String(closedItem.id),op:"close"}},async compensate(data){await compensateByRestore(config,mutation.id,stepId,data)}}}function buildReleaseStep(config,mutation,stepId){return{id:stepId,async inspect(){const snapshot=await readItemSnapshot(config.pmRoot,mutation.id);return snapshot===void 0?{state:"pending"}:await isUpdateMarkerApplied(config.pmRoot,snapshot.id,config.transactionId,stepId)?{state:"applied",result:{id:snapshot.id,op:"release"}}:snapshot.assignee!==void 0||snapshot.claimPrincipal!==void 0?{state:"pending"}:{state:"applied",result:{id:snapshot.id,op:"release"}}},async prepareCompensation(){return prepareRestoreCompensation(config.pmRoot,mutation)},async apply(){const releasedItem=(await release(mutation.id,mergeTransactionMutationOptions(mutation.options,{author:config.author,message:bulkHistoryMarker(config.transactionId,stepId,"apply")}),{pmRoot:config.pmRoot})).item;return config.uniqueIndex.invalidate(),{id:String(releasedItem.id),op:"release"}},async compensate(data){await compensateByRestore(config,mutation.id,stepId,data)}}}function buildStepForMutation(config,mutation,index){const stepId=deriveStepId(mutation,index);switch(mutation.op){case"create":return buildCreateStep(config,mutation,stepId);case"update":return buildUpdateStep(config,mutation,stepId);case"close":return buildCloseStep(config,mutation,stepId);case"release":return buildReleaseStep(config,mutation,stepId)}}function assertValidBulkMutation(mutation,index){if(mutation===null||typeof mutation!="object"||Array.isArray(mutation))throw new TypeError(`Bulk mutation ${index+1} must be an object`);if(!["create","update","close","release"].includes(mutation.op))throw new TypeError(`Bulk mutation ${index+1} op must be create, update, close, or release`);if(typeof mutation.id!="string"||mutation.id.trim().length===0)throw new TypeError(`Bulk mutation ${index+1} requires a non-empty id`);if(mutation.op==="close"&&(typeof mutation.reason!="string"||mutation.reason.trim().length===0))throw new TypeError(`Bulk close mutation ${index+1} requires a non-empty reason`);if(mutation.op==="create"&&mutation.unique!==void 0&&!isValidUniqueKey(mutation.unique))throw new TypeError(`Bulk create mutation ${index+1} unique must be an object with a non-empty key and a value`);if(mutation.op==="update"&&mutation.match!==void 0&&!isValidUniqueKey(mutation.match))throw new TypeError(`Bulk update mutation ${index+1} match must be an object with a non-empty key and a value`);assertMutationOptionValueTypes(mutation.options,mutation.op,index)}function assertValidBulkMutations(mutations){if(mutations.length===0)throw new TypeError("Bulk item transaction requires at least one mutation");const keys=new Map;for(const[index,mutation]of mutations.entries()){if(assertValidBulkMutation(mutation,index),mutation.op!=="create"||mutation.unique===void 0)continue;const{key,value}=mutation.unique,values=keys.get(key)??new Map,stable=stableUpsertValue(value),previous=values.get(stable);if(previous!==void 0)throw new PmCliError(`Bulk create mutations ${previous+1} and ${index+1} share the upsert key value ${JSON.stringify(stable)} for "${key}".`,EXIT_CODE.USAGE,{code:"upsert_key_duplicate",required:"Each create must carry a distinct unique key value within one batch.",why:"Direct SDK batches must preserve the same uniqueness invariant as planned document rows."});values.set(stable,index),keys.set(key,values)}}async function validateMutationOperation(mutation,index,run){try{return await run()}catch(error){throw error instanceof PmCliError?new PmCliError(error.message,error.exitCode,{...error.context,transaction_operation:{index,op:mutation.op,id:mutation.id}}):error}}function includePreviewPath(root,file,ownedPaths,retainedPaths){const relative=path.relative(root,file);return["locks","runtime","search"].includes(relative.split(path.sep)[0])?retainedPaths.some(retained=>relative===retained||relative.startsWith(`${retained}${path.sep}`)||retained.startsWith(`${relative}${path.sep}`)):relative===""||ownedPaths===void 0||ownedPaths.some(owned=>relative===owned||relative.startsWith(`${owned}${path.sep}`)||owned.startsWith(`${relative}${path.sep}`))}async function previewEntryStat(root,file){const entry=await lstat(file);if(entry.isSymbolicLink())await stat(file);else if(entry.isDirectory()||entry.isFile())return entry;throw transactionPreviewNonRegularFileError(path.relative(root,file))}async function previewFileDigest(root,file){const handle=await open(file,constants.O_RDONLY|constants.O_NOFOLLOW|constants.O_NONBLOCK);try{const entry=await handle.stat();if(!entry.isFile())throw transactionPreviewNonRegularFileError(path.relative(root,file));const contents=crypto.createHash("sha256");if(entry.size>0)for await(const chunk of handle.createReadStream({highWaterMark:64*1024,end:entry.size-1,autoClose:!1}))contents.update(chunk);return contents.digest()}finally{await handle.close()}}async function previewTrackerFingerprint(root,ownedPaths,retainedPaths){const fingerprint=crypto.createHash("sha256");async function visit(directory){for(const name of(await readdir(directory)).sort()){const file=path.join(directory,name);includePreviewPath(root,file,ownedPaths,retainedPaths)&&((await previewEntryStat(root,file)).isDirectory()?await visit(file):(fingerprint.update(JSON.stringify(path.relative(root,file))),fingerprint.update(await previewFileDigest(root,file))))}}return await visit(root),fingerprint.digest("hex")}function previewSnapshotChangedError(){return new PmCliError("Tracker changed while preparing the transaction preview; retry against stable state.",EXIT_CODE.CONFLICT,{code:"transaction_preview_snapshot_changed"})}async function isDisappearingPreviewPath(error){if(!isFileMissingError(error))return!1;const file=error.path;if(typeof file!="string")return!1;const original=path.resolve(file);let candidate=original;for(;;)try{const entry=await lstat(candidate);return candidate!==original&&entry.isDirectory()}catch(lookupError){if(!isFileMissingError(lookupError))return!1;const parent=path.dirname(candidate);if(parent===candidate)return!1;candidate=parent}}async function readPreviewConfiguration(root,relative){const file=path.join(root,relative);try{let current=root;for(const component of relative.split(path.sep).slice(0,-1))current=path.join(current,component),await previewEntryStat(root,current);if(!(await previewEntryStat(root,file)).isFile())throw transactionPreviewNonRegularFileError(relative);return await readRegularFile(file,transactionPreviewNonRegularFileError(relative))}catch(error){if(await isDisappearingPreviewPath(error))return;throw error}}async function stageGuardedSchemaSections(root,configurationRoot,schemaSettings,configuration){const schema=normalizeRuntimeSchemaSettings(schemaSettings);for(const[section,configuredPath]of Object.entries(schema.files)){const relative=path.relative(root,filePathForSchemaSection(root,configuredPath,DEFAULT_RUNTIME_SCHEMA_FILE_PATHS[section]));if(relative===""||!path.resolve(root,relative).startsWith(path.join(root,path.sep)))throw new PmCliError("Transaction preview requires schema files inside the selected tracker root; relocate the configured schema file before retrying.",EXIT_CODE.USAGE,{code:"transaction_preview_external_schema",field:`schema.files.${section}`});schema.files[section]=relative;const contents=await readPreviewConfiguration(root,relative);if(configuration?.set(relative,contents),contents!==void 0){const target=path.join(configurationRoot,relative);await mkdir(path.dirname(target),{recursive:!0}),await writeFile(target,contents)}}return loadRuntimeSchemaFromOptionalFiles(configurationRoot,schema)}async function loadGuardedRuntimeSchema(pmRoot,schemaSettings){await stat(pmRoot);const root=await mkdtemp(path.join(tmpdir(),"pm-schema-plan-"));try{const resolvedRoot=await realpath(pmRoot);return await stageGuardedSchemaSections(resolvedRoot,path.join(root,"configuration"),schemaSettings)}catch(error){throw await isDisappearingPreviewPath(error)?previewSnapshotChangedError():error}finally{await rm(root,{recursive:!0,force:!0})}}async function resolvePreviewStorage(root,configurationRoot,registrations){const configuration=new Map,rawSettings=await readPreviewConfiguration(root,SETTINGS_FILENAME);configuration.set(SETTINGS_FILENAME,rawSettings);let parsed;try{parsed=JSON.parse(rawSettings??"null")}catch{parsed=null}const validated=validateSettings(parsed),settings=validated.success?validated.data:void 0,loaded=await stageGuardedSchemaSections(root,configurationRoot,settings?.schema,configuration),schema=loaded.schema,sessionPath=path.relative(root,getSessionStatePath(root));configuration.set(sessionPath,await readPreviewConfiguration(root,sessionPath));const registry=resolveItemTypeRegistry({...SETTINGS_DEFAULTS,item_types:{definitions:[...settings?.item_types?.definitions??[],...loaded.type_definitions_from_file??[]]}},registrations),ownedPaths=resolveWorkspaceRoot(root)===root?[...PM_REQUIRED_SUBDIRS.filter(Boolean),SETTINGS_FILENAME,PM_CONTEXT_INTENTS_FILE,"transactions",...registry.folders,...configuration.keys()].map(entry=>path.normalize(entry)):void 0,stagedSettings=settings===void 0?void 0:JSON.stringify({...parsed,schema:{...settings.schema,files:schema.files}});return{configuration,ownedPaths,stagedSettings,registryFolders:registry.folders}}async function stagePreviewTracker(sourceRoot,stagedRoot,registrations){await stat(sourceRoot);try{const resolvedRoot=await realpath(sourceRoot),storage=await resolvePreviewStorage(resolvedRoot,path.join(path.dirname(stagedRoot),"configuration"),registrations),retainedPaths=[...storage.configuration.keys(),...storage.registryFolders].map(entry=>path.normalize(entry)),sourceFingerprint=await previewTrackerFingerprint(resolvedRoot,storage.ownedPaths,retainedPaths);await cp(resolvedRoot,stagedRoot,{recursive:!0,dereference:!1,filter:async source=>includePreviewPath(resolvedRoot,source,storage.ownedPaths,retainedPaths)?(await previewEntryStat(resolvedRoot,source),!0):!1});const[currentFingerprint,stagedFingerprint]=await Promise.all([previewTrackerFingerprint(resolvedRoot,storage.ownedPaths,retainedPaths),previewTrackerFingerprint(stagedRoot,storage.ownedPaths,retainedPaths)]);if(sourceFingerprint!==currentFingerprint||sourceFingerprint!==stagedFingerprint)throw previewSnapshotChangedError();for(const[relative,contents]of storage.configuration)if(await readPreviewConfiguration(stagedRoot,relative)!==contents)throw previewSnapshotChangedError();storage.stagedSettings!==void 0&&await writeFile(path.join(stagedRoot,SETTINGS_FILENAME),storage.stagedSettings)}catch(error){throw await isDisappearingPreviewPath(error)?previewSnapshotChangedError():error}}async function previewItemMutations(options){const identity=validateWorkspaceTransactionIdentity(options),mutations=[...options.mutations];assertValidBulkMutations(mutations);const registrations=getActiveExtensionRegistrations(),root=await mkdtemp(path.join(tmpdir(),"pm-transaction-preview-")),stagedRoot=path.join(root,"tracker");try{await stagePreviewTracker(identity.pmRoot,stagedRoot,registrations);let validated=!1;const warnings=[];return await runWithActiveExtensions({path:stagedRoot,noExtensions:!0},async()=>{setActiveExtensionRegistrations(registrations);const config={...identity,pmRoot:stagedRoot,createCompensation:"close",uniqueIndex:createUniqueKeyIndex(stagedRoot)},steps=mutations.map((mutation,index)=>{const step=buildStepForMutation({...config,onCreateWarnings:(id,messages)=>{messages.length>0&&warnings.push({index,id,op:"create",warnings:messages})}},mutation,index);return{...step,inspect:()=>validateMutationOperation(mutation,index,()=>step.inspect()),prepareCompensation:()=>validateMutationOperation(mutation,index,async()=>step.prepareCompensation?.()),apply:()=>validateMutationOperation(mutation,index,()=>step.apply())}});await commitWorkspaceTransaction({...identity,pmRoot:stagedRoot,steps,onTransition:({transition})=>{transition==="committed"&&(validated=!0)}})}),{validated,state:validated?"staged_snapshot":"replayed_committed_plan",warnings,unresolved_commit_constraints:["concurrent_tracker_changes","extension_mutation_guards_and_hooks"]}}finally{await rm(root,{recursive:!0,force:!0})}}async function commitItemMutations(options){const mutations=[...options.mutations];assertValidBulkMutations(mutations);const createCompensation=options.createCompensation??"close";if(!["close","delete"].includes(createCompensation))throw new TypeError("Bulk item transaction createCompensation must be close or delete");const stepConfig={pmRoot:options.pmRoot,author:options.author,transactionId:options.transactionId,createCompensation,uniqueIndex:createUniqueKeyIndex(options.pmRoot)},committed=await commitWorkspaceTransaction({pmRoot:options.pmRoot,transactionId:options.transactionId,author:options.author,steps:mutations.map((mutation,index)=>buildStepForMutation(stepConfig,mutation,index)),...options.lockTtlSeconds===void 0?{}:{lockTtlSeconds:options.lockTtlSeconds},...options.lockWaitMs===void 0?{}:{lockWaitMs:options.lockWaitMs},...options.onTransition===void 0?{}:{onTransition:options.onTransition}}),results={};for(const[index,mutation]of mutations.entries()){const stepId=deriveStepId(mutation,index),value=committed.results[stepId];results[deriveOutcomeKey(mutation,index)]=value}return{transactionId:committed.transactionId,status:"committed",recovered:committed.recovered,results}}function buildItemCompletionMutations(options){const mutations=[];return options.evidence!==void 0&&Object.keys(options.evidence).length>0&&mutations.push({op:"update",id:options.id,options:options.evidence}),mutations.push({op:"close",id:options.id,reason:options.reason,...options.closeOptions===void 0?{}:{options:options.closeOptions}},{op:"release",id:options.id,...options.releaseOptions===void 0?{}:{options:options.releaseOptions}}),mutations}function commitItemCompletion(options){return commitItemMutations({pmRoot:options.pmRoot,transactionId:options.transactionId,author:options.author,mutations:buildItemCompletionMutations(options),...options.lockTtlSeconds===void 0?{}:{lockTtlSeconds:options.lockTtlSeconds},...options.lockWaitMs===void 0?{}:{lockWaitMs:options.lockWaitMs},...options.onTransition===void 0?{}:{onTransition:options.onTransition}})}const _testOnlyItemTransaction={buildCreateStep,bulkHistoryMarker,compensateByRestore,deriveStepId,isUpdateMarkerApplied,readItemSnapshot};export{_testOnlyItemTransaction,buildItemCompletionMutations,commitItemCompletion,commitItemMutations,listUpsertKeyCandidates,loadGuardedRuntimeSchema,previewItemMutations,resolveTransactionSchema};
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]="4d215571-44e3-5ce8-9644-47e42dd4b23e")}catch{}})();import crypto from"node:crypto";import{constants}from"node:fs";import{isFileMissingError,readRegularFile,transactionPreviewNonRegularFileError}from"../core/fs/fs-utils.js";import{cp,lstat,mkdir,mkdtemp,open,readdir,realpath,rm,stat,writeFile}from"node:fs/promises";import{tmpdir}from"node:os";import{getActiveExtensionRegistrations,setActiveExtensionRegistrations}from"../core/extensions/index.js";import path from"node:path";import{transferMutationStdinTokenPolicy}from"../core/item/parse.js";import{EXIT_CODE,PM_REQUIRED_SUBDIRS,SETTINGS_DEFAULTS,SETTINGS_FILENAME}from"../core/shared/constants.js";import{resolveItemTypeRegistry}from"../core/item/type-registry.js";import{listAllItemMetadataLight}from"../core/store/item-store.js";import{runWithItemMutationPrecondition}from"../core/store/item-mutation-precondition.js";import{hashDocument,hashHistoryStream}from"../core/history/history.js";import{historyVersionOffset}from"../core/history/version-address.js";import{isConfigurationOnlySettingsRead,readSettings}from"../core/store/settings.js";import{DEFAULT_RUNTIME_SCHEMA_FILE_PATHS,filePathForSchemaSection,loadRuntimeSchemaFromOptionalFiles,normalizeRuntimeSchemaSettings}from"../core/schema/runtime-schema.js";import{validateSettings}from"../core/store/settings-validator.js";import{resolveWorkspaceRoot}from"../core/store/paths.js";import{getSessionStatePath}from"../core/session/session-state.js";import{PmCliError}from"../core/shared/errors.js";import{stableUpsertValue}from"./structured-mutations.js";import{stableStringify}from"../core/shared/serialization.js";import{assertMutationOptionValueTypes}from"./lifecycle/mutation-option-validation.js";import{readHistoryEntries}from"./history-read.js";import{PM_CONTEXT_INTENTS_FILE}from"./context-intent-runtime.js";import{close,create,deleteItem,get,release,restore,update,runWithActiveExtensions}from"./runtime.js";import{commitWorkspaceTransaction,validateWorkspaceTransactionIdentity}from"./workspace-transaction.js";async function resolveTransactionSchema(pmRoot,settings){if(!isConfigurationOnlySettingsRead(pmRoot))return{schema:normalizeRuntimeSchemaSettings(settings.schema),typeDefinitionsFromFile:[]};const loaded=await loadGuardedRuntimeSchema(pmRoot,settings.schema);return{schema:loaded.schema,typeDefinitionsFromFile:loaded.type_definitions_from_file??[]}}async function listUpsertKeyCandidates(pmRoot,settings,schemaContext,key){const typeRegistry=resolveItemTypeRegistry({...settings,item_types:{...settings.item_types,definitions:[...settings.item_types?.definitions??[],...schemaContext.typeDefinitionsFromFile]}},getActiveExtensionRegistrations());return(await listAllItemMetadataLight(pmRoot,settings.item_format,typeRegistry.type_to_folder,void 0,schemaContext.schema)).flatMap(item=>{const value=item[key];return value==null?[]:[{id:item.id,value}]})}function createUniqueKeyIndex(pmRoot){const scans=new Map,scan=async key=>{const settings=await readSettings(pmRoot),schemaContext=await resolveTransactionSchema(pmRoot,settings),byValue=new Map;for(const candidate of await listUpsertKeyCandidates(pmRoot,settings,schemaContext,key)){const stable=stableUpsertValue(candidate.value);byValue.set(stable,[...byValue.get(stable)??[],candidate.id])}return byValue};return{lookup(key){const pending=scans.get(key)??scan(key);return scans.set(key,pending),pending},async recordCreate(item){for(const[key,pending]of scans){const value=item[key];if(value==null)continue;const byValue=await pending,stable=stableUpsertValue(value);byValue.set(stable,[...byValue.get(stable)??[],String(item.id)])}},invalidate(){scans.clear()}}}async function assertUniqueKeyFree(index,unique){const stable=stableUpsertValue(unique.value),existing=(await index.lookup(unique.key)).get(stable)??[];if(existing.length!==0)throw new PmCliError(`Upsert key "${unique.key}" value ${JSON.stringify(stable)} now matches existing item ${existing.join(", ")}; the plan was resolved before it existed.`,EXIT_CODE.CONFLICT,{code:"upsert_key_conflict",required:"Re-plan the document so the row updates the existing item instead of creating a duplicate.",why:"Keyed upsert is resolved before the writer lock, so every remaining create is re-checked under the lock; concurrent imports therefore cannot create two items for one key value.",nextSteps:["Re-run pm item mutate with the same document and --upsert-key; the re-plan turns the row into an update of the existing item.","Inspect the existing item first when its content should win over the document row."]})}function assertUpsertMatchCurrent(document,id,match){const current=document.metadata[match.key],expected=stableUpsertValue(match.value);if(stableUpsertValue(current)!==expected)throw new PmCliError(`Item ${id} no longer carries upsert key "${match.key}" value ${JSON.stringify(expected)}; the plan selected it before the key changed.`,EXIT_CODE.CONFLICT,{code:"upsert_match_changed",required:"Re-plan the document so the row is resolved against the current key values.",why:"Keyed upsert selects its target before the writer lock, so the update re-reads the key under the lock instead of updating a mismatched item blindly.",nextSteps:["Re-run pm item mutate with the same document and --upsert-key; the re-plan creates or re-targets the row from the current key values."]})}function isValidUniqueKey(value){if(value===null||typeof value!="object"||Array.isArray(value))return!1;const candidate=value;return typeof candidate.key=="string"&&candidate.key.trim().length>0&&candidate.value!==void 0&&candidate.value!==null}function mergeTransactionMutationOptions(source,additions){const merged={...source,...additions};return source===void 0?merged:transferMutationStdinTokenPolicy(source,merged)}async function readItemSnapshot(pmRoot,id){try{const item=(await get(id,{},{pmRoot})).item;return{id:item.id,closedAt:item.closed_at,updatedAt:item.updated_at,assignee:item.assignee,claimPrincipal:item.claim_principal}}catch(error){if(error instanceof PmCliError&&error.exitCode===EXIT_CODE.NOT_FOUND)return;throw error}}function deriveStepId(mutation,index){const sanitizedTarget=mutation.id.replaceAll(/[^a-zA-Z0-9._-]/gu,"_"),fingerprint=crypto.createHash("sha256").update(stableStringify(mutation)).digest("hex").slice(0,12);return`${index+1}-${mutation.op}-${sanitizedTarget}-${fingerprint}`}function deriveOutcomeKey(mutation,index){const sanitizedTarget=mutation.id.replaceAll(/[^a-zA-Z0-9._-]/gu,"_");return`${index+1}-${mutation.op}-${sanitizedTarget}`}function bulkHistoryMarker(transactionId,stepId,phase){return`bulk-item-transaction ${transactionId} ${stepId} ${phase}`}async function isUpdateMarkerApplied(pmRoot,canonicalId,transactionId,stepId){const entries=await readHistoryEntries(path.join(pmRoot,"history",`${canonicalId}.jsonl`),canonicalId),applyMarker=bulkHistoryMarker(transactionId,stepId,"apply"),compensateMarker=bulkHistoryMarker(transactionId,stepId,"compensate");for(let index=entries.length-1;index>=0;index-=1){const message=entries[index].message;if(message===applyMarker)return!0;if(message===compensateMarker)return!1}return!1}async function prepareRestoreCompensation(pmRoot,mutation){const snapshot=await readItemSnapshot(pmRoot,mutation.id);if(snapshot?.updatedAt===void 0)throw new PmCliError(`Bulk ${mutation.op} target ${mutation.id} does not exist or has no restorable version`,EXIT_CODE.NOT_FOUND);const history=await readHistoryEntries(path.join(pmRoot,"history",`${snapshot.id}.jsonl`),snapshot.id);if(history.length===0)throw new PmCliError(`Bulk ${mutation.op} target ${mutation.id} has no restorable history`,EXIT_CODE.NOT_FOUND);return{target_updated_at:snapshot.updatedAt,target_version:String(historyVersionOffset(history)+history.length),target_history_hash:hashHistoryStream(history),target_history_length:history.length}}function partialMutationError(config,itemId,stepId){return new PmCliError(`Transaction ${config.transactionId} cannot safely compensate incomplete step ${stepId}; review item ${itemId} history before recovery.`,EXIT_CODE.CONFLICT,{code:"transaction_partial_mutation",item_id:itemId,required:"Reconcile the item's partial or intervening changes before resuming this transaction.",why:"The retained history does not prove exclusive ownership of the changes to undo. The journal remains compensating instead of claiming a rollback or overwriting another writer.",nextSteps:[`Run pm history ${itemId} and inspect this transaction's retained compensation data before explicitly restoring or reconciling the item.`]})}function isTransactionHistoryEntry(entry,transactionId){const prefix=`bulk-item-transaction ${transactionId} `;return[entry.message,entry.context?.item_transaction_step].some(value=>typeof value=="string"&&value.startsWith(prefix))}function hasOwnedCompensationEffects(history,proof,identity){const{expectedHistory,length,version}=proof;if(hashHistoryStream(history)===expectedHistory)return!1;if(typeof length!="number"||!Number.isSafeInteger(length)||length<1||hashHistoryStream(history.slice(0,length))!==expectedHistory||version!==String(historyVersionOffset(history)+length))throw partialMutationError(identity,identity.itemId,identity.stepId);const marker=bulkHistoryMarker(identity.transactionId,identity.stepId,"apply"),changes=history.slice(length),isOwned=entry=>entry.message===marker||entry.context?.item_transaction_step===marker,compensationMarker=bulkHistoryMarker(identity.transactionId,identity.stepId,"compensate");if(changes.filter(entry=>isOwned(entry)||entry.message===compensationMarker).at(-1)?.message===compensationMarker)return!1;const owned=changes.filter(isOwned);if(owned.length===0)return!1;const siblingsRestored=history.at(-1).after_hash===owned.at(-1).after_hash&&changes.every(entry=>isTransactionHistoryEntry(entry,identity.transactionId));if(owned.length!==changes.length&&!siblingsRestored)throw partialMutationError(identity,identity.itemId,identity.stepId);return!0}async function compensateByRestore(config,itemId,stepId,data){const receipt=data!==null&&typeof data=="object"&&!Array.isArray(data)?data:void 0;if(receipt===void 0||typeof receipt.target_updated_at!="string")return;const target=receipt.target_updated_at,current=await readItemSnapshot(config.pmRoot,itemId);if(current===void 0)return;const history=await readHistoryEntries(path.join(config.pmRoot,"history",`${current.id}.jsonl`),current.id),expectedHistory=receipt.target_history_hash;if(typeof expectedHistory=="string"){if(!hasOwnedCompensationEffects(history,{expectedHistory,length:receipt.target_history_length,version:receipt.target_version},{...config,itemId:current.id,stepId}))return}else{if(current.updatedAt===target)return;if(!await isUpdateMarkerApplied(config.pmRoot,current.id,config.transactionId,stepId))throw partialMutationError(config,current.id,stepId)}const applyRestore=()=>restore(itemId,typeof receipt.target_version=="string"?receipt.target_version:target,{force:!0,author:config.author,message:bulkHistoryMarker(config.transactionId,stepId,"compensate")},{pmRoot:config.pmRoot});await runWithItemMutationPrecondition({pmRoot:config.pmRoot,id:current.id,validate:document=>{if(hashDocument(document)!==history.at(-1).after_hash)throw partialMutationError(config,current.id,stepId)}},applyRestore)}function buildCreateStep(config,mutation,stepId){return{id:stepId,async inspect(){const snapshot=await readItemSnapshot(config.pmRoot,mutation.id);if(snapshot===void 0)return{state:"pending"};if(snapshot.closedAt!==void 0){const history=await readHistoryEntries(path.join(config.pmRoot,"history",`${snapshot.id}.jsonl`),snapshot.id),reason=`Compensated: interrupted bulk transaction ${config.transactionId}`;if(history.filter(entry=>entry.op==="close").at(-1)?.patch.some(change=>change.op==="add"||change.op==="replace"?change.path==="/metadata/close_reason"&&change.value===reason:!1)??!1)throw new PmCliError(`Created item ${snapshot.id} remains closed by transaction compensation; explicitly reopen or reconcile it before retrying.`,EXIT_CODE.CONFLICT,{code:"transaction_create_compensated",item_id:snapshot.id,required:"Review the compensated item and explicitly reopen it before retrying the same document.",why:"This item's latest closure belongs to transaction compensation. Automatically reopening it could overwrite later lifecycle decisions.",nextSteps:[`Run pm item reopen ${snapshot.id} "Retry compensated creation" after reviewing its history, then retry the transaction.`]})}return{state:"applied",result:{id:snapshot.id,op:"create"}}},async apply(){mutation.unique!==void 0&&await assertUniqueKeyFree(config.uniqueIndex,mutation.unique);const created=await create(mergeTransactionMutationOptions(mutation.options,{author:config.author,id:mutation.id}),{pmRoot:config.pmRoot}),createdItem=created.item;return config.onCreateWarnings?.(String(createdItem.id),created.warnings),await config.uniqueIndex.recordCreate(createdItem),{id:String(createdItem.id),op:"create"}},async compensate(){const snapshot=await readItemSnapshot(config.pmRoot,mutation.id);if(snapshot!==void 0){if(config.createCompensation==="delete"){await deleteItem(mutation.id,{force:!0,author:config.author,message:`Compensate interrupted bulk transaction ${config.transactionId}`},{pmRoot:config.pmRoot});return}snapshot.closedAt===void 0&&await close(mutation.id,`Compensated: interrupted bulk transaction ${config.transactionId}`,{force:!0,author:config.author},{pmRoot:config.pmRoot})}}}}function buildUpdateStep(config,mutation,stepId){return{id:stepId,compensatePending:!0,async inspect(){const snapshot=await readItemSnapshot(config.pmRoot,mutation.id);return snapshot===void 0?{state:"pending"}:await isUpdateMarkerApplied(config.pmRoot,snapshot.id,config.transactionId,stepId)?{state:"applied",result:{id:snapshot.id,op:"update"}}:{state:"pending"}},async prepareCompensation(){return prepareRestoreCompensation(config.pmRoot,mutation)},async apply(){const applyUpdate=()=>update(mutation.id,mergeTransactionMutationOptions(mutation.options,{author:config.author,message:bulkHistoryMarker(config.transactionId,stepId,"apply")}),{pmRoot:config.pmRoot}),match=mutation.match;let currentMatch=match;const updatedItem=(await runWithItemMutationPrecondition({pmRoot:config.pmRoot,id:String((await get(mutation.id,{},{pmRoot:config.pmRoot})).item.id),validate:document=>{currentMatch!==void 0&&assertUpsertMatchCurrent(document,mutation.id,currentMatch)},committed:document=>{match!==void 0&&(currentMatch={key:match.key,value:structuredClone(document.metadata[match.key])})},historyContext:{item_transaction_step:bulkHistoryMarker(config.transactionId,stepId,"apply")}},applyUpdate)).item;return config.uniqueIndex.invalidate(),{id:String(updatedItem.id),op:"update"}},async compensate(data){await compensateByRestore(config,mutation.id,stepId,data)}}}function buildCloseStep(config,mutation,stepId){return{id:stepId,async inspect(){const snapshot=await readItemSnapshot(config.pmRoot,mutation.id);return snapshot===void 0?{state:"pending"}:await isUpdateMarkerApplied(config.pmRoot,snapshot.id,config.transactionId,stepId)?{state:"applied",result:{id:snapshot.id,op:"close"}}:snapshot.closedAt===void 0?{state:"pending"}:{state:"applied",result:{id:snapshot.id,op:"close"}}},async prepareCompensation(){return prepareRestoreCompensation(config.pmRoot,mutation)},async apply(){const closedItem=(await close(mutation.id,mutation.reason,mergeTransactionMutationOptions(mutation.options,{author:config.author,message:bulkHistoryMarker(config.transactionId,stepId,"apply")}),{pmRoot:config.pmRoot})).item;return config.uniqueIndex.invalidate(),{id:String(closedItem.id),op:"close"}},async compensate(data){await compensateByRestore(config,mutation.id,stepId,data)}}}function buildReleaseStep(config,mutation,stepId){return{id:stepId,async inspect(){const snapshot=await readItemSnapshot(config.pmRoot,mutation.id);return snapshot===void 0?{state:"pending"}:await isUpdateMarkerApplied(config.pmRoot,snapshot.id,config.transactionId,stepId)?{state:"applied",result:{id:snapshot.id,op:"release"}}:snapshot.assignee!==void 0||snapshot.claimPrincipal!==void 0?{state:"pending"}:{state:"applied",result:{id:snapshot.id,op:"release"}}},async prepareCompensation(){return prepareRestoreCompensation(config.pmRoot,mutation)},async apply(){const releasedItem=(await release(mutation.id,mergeTransactionMutationOptions(mutation.options,{author:config.author,message:bulkHistoryMarker(config.transactionId,stepId,"apply")}),{pmRoot:config.pmRoot})).item;return config.uniqueIndex.invalidate(),{id:String(releasedItem.id),op:"release"}},async compensate(data){await compensateByRestore(config,mutation.id,stepId,data)}}}function buildStepForMutation(config,mutation,index){const stepId=deriveStepId(mutation,index);switch(mutation.op){case"create":return buildCreateStep(config,mutation,stepId);case"update":return buildUpdateStep(config,mutation,stepId);case"close":return buildCloseStep(config,mutation,stepId);case"release":return buildReleaseStep(config,mutation,stepId)}}function assertValidBulkMutation(mutation,index){if(mutation===null||typeof mutation!="object"||Array.isArray(mutation))throw new TypeError(`Bulk mutation ${index+1} must be an object`);if(!["create","update","close","release"].includes(mutation.op))throw new TypeError(`Bulk mutation ${index+1} op must be create, update, close, or release`);if(typeof mutation.id!="string"||mutation.id.trim().length===0)throw new TypeError(`Bulk mutation ${index+1} requires a non-empty id`);if(mutation.op==="close"&&(typeof mutation.reason!="string"||mutation.reason.trim().length===0))throw new TypeError(`Bulk close mutation ${index+1} requires a non-empty reason`);if(mutation.op==="create"&&mutation.unique!==void 0&&!isValidUniqueKey(mutation.unique))throw new TypeError(`Bulk create mutation ${index+1} unique must be an object with a non-empty key and a value`);if(mutation.op==="update"&&mutation.match!==void 0&&!isValidUniqueKey(mutation.match))throw new TypeError(`Bulk update mutation ${index+1} match must be an object with a non-empty key and a value`);assertMutationOptionValueTypes(mutation.options,mutation.op,index)}function assertValidBulkMutations(mutations){if(mutations.length===0)throw new TypeError("Bulk item transaction requires at least one mutation");const keys=new Map;for(const[index,mutation]of mutations.entries()){if(assertValidBulkMutation(mutation,index),mutation.op!=="create"||mutation.unique===void 0)continue;const{key,value}=mutation.unique,values=keys.get(key)??new Map,stable=stableUpsertValue(value),previous=values.get(stable);if(previous!==void 0)throw new PmCliError(`Bulk create mutations ${previous+1} and ${index+1} share the upsert key value ${JSON.stringify(stable)} for "${key}".`,EXIT_CODE.USAGE,{code:"upsert_key_duplicate",required:"Each create must carry a distinct unique key value within one batch.",why:"Direct SDK batches must preserve the same uniqueness invariant as planned document rows."});values.set(stable,index),keys.set(key,values)}}async function validateMutationOperation(mutation,index,run){try{return await run()}catch(error){throw error instanceof PmCliError?new PmCliError(error.message,error.exitCode,{...error.context,transaction_operation:{index,op:mutation.op,id:mutation.id}}):error}}function includePreviewPath(root,file,ownedPaths,retainedPaths){const relative=path.relative(root,file),segments=relative.split(path.sep);return segments[0]==="extensions"&&segments[2]==="node_modules"&&!retainedPaths.some(retained=>relative===retained||relative.startsWith(`${retained}${path.sep}`)||retained.startsWith(`${relative}${path.sep}`))?!1:["locks","runtime","search"].includes(relative.split(path.sep)[0])?retainedPaths.some(retained=>relative===retained||relative.startsWith(`${retained}${path.sep}`)||retained.startsWith(`${relative}${path.sep}`)):relative===""||ownedPaths===void 0||ownedPaths.some(owned=>relative===owned||relative.startsWith(`${owned}${path.sep}`)||owned.startsWith(`${relative}${path.sep}`))}async function previewEntryStat(root,file){const entry=await lstat(file);if(entry.isSymbolicLink())await stat(file);else if(entry.isDirectory()||entry.isFile())return entry;throw transactionPreviewNonRegularFileError(path.relative(root,file))}async function previewFileDigest(root,file){const handle=await open(file,constants.O_RDONLY|constants.O_NOFOLLOW|constants.O_NONBLOCK);try{const entry=await handle.stat();if(!entry.isFile())throw transactionPreviewNonRegularFileError(path.relative(root,file));const contents=crypto.createHash("sha256");if(entry.size>0)for await(const chunk of handle.createReadStream({highWaterMark:64*1024,end:entry.size-1,autoClose:!1}))contents.update(chunk);return contents.digest()}finally{await handle.close()}}async function previewTrackerFingerprint(root,ownedPaths,retainedPaths){const fingerprint=crypto.createHash("sha256");async function visit(directory){for(const name of(await readdir(directory)).sort()){const file=path.join(directory,name);includePreviewPath(root,file,ownedPaths,retainedPaths)&&((await previewEntryStat(root,file)).isDirectory()?await visit(file):(fingerprint.update(JSON.stringify(path.relative(root,file))),fingerprint.update(await previewFileDigest(root,file))))}}return await visit(root),fingerprint.digest("hex")}function previewSnapshotChangedError(){return new PmCliError("Tracker changed while preparing the transaction preview; retry against stable state.",EXIT_CODE.CONFLICT,{code:"transaction_preview_snapshot_changed"})}async function isDisappearingPreviewPath(error){if(!isFileMissingError(error))return!1;const file=error.path;if(typeof file!="string")return!1;const original=path.resolve(file);let candidate=original;for(;;)try{const entry=await lstat(candidate);return candidate!==original&&entry.isDirectory()}catch(lookupError){if(!isFileMissingError(lookupError))return!1;const parent=path.dirname(candidate);if(parent===candidate)return!1;candidate=parent}}async function readPreviewConfiguration(root,relative){const file=path.join(root,relative);try{let current=root;for(const component of relative.split(path.sep).slice(0,-1))current=path.join(current,component),await previewEntryStat(root,current);if(!(await previewEntryStat(root,file)).isFile())throw transactionPreviewNonRegularFileError(relative);return await readRegularFile(file,transactionPreviewNonRegularFileError(relative))}catch(error){if(await isDisappearingPreviewPath(error))return;throw error}}async function stageGuardedSchemaSections(root,configurationRoot,schemaSettings,configuration){const schema=normalizeRuntimeSchemaSettings(schemaSettings);for(const[section,configuredPath]of Object.entries(schema.files)){const relative=path.relative(root,filePathForSchemaSection(root,configuredPath,DEFAULT_RUNTIME_SCHEMA_FILE_PATHS[section]));if(relative===""||!path.resolve(root,relative).startsWith(path.join(root,path.sep)))throw new PmCliError("Transaction preview requires schema files inside the selected tracker root; relocate the configured schema file before retrying.",EXIT_CODE.USAGE,{code:"transaction_preview_external_schema",field:`schema.files.${section}`});schema.files[section]=relative;const contents=await readPreviewConfiguration(root,relative);if(configuration?.set(relative,contents),contents!==void 0){const target=path.join(configurationRoot,relative);await mkdir(path.dirname(target),{recursive:!0}),await writeFile(target,contents)}}return loadRuntimeSchemaFromOptionalFiles(configurationRoot,schema)}async function loadGuardedRuntimeSchema(pmRoot,schemaSettings){await stat(pmRoot);const root=await mkdtemp(path.join(tmpdir(),"pm-schema-plan-"));try{const resolvedRoot=await realpath(pmRoot);return await stageGuardedSchemaSections(resolvedRoot,path.join(root,"configuration"),schemaSettings)}catch(error){throw await isDisappearingPreviewPath(error)?previewSnapshotChangedError():error}finally{await rm(root,{recursive:!0,force:!0})}}async function resolvePreviewStorage(root,configurationRoot,registrations){const configuration=new Map,rawSettings=await readPreviewConfiguration(root,SETTINGS_FILENAME);configuration.set(SETTINGS_FILENAME,rawSettings);let parsed;try{parsed=JSON.parse(rawSettings??"null")}catch{parsed=null}const validated=validateSettings(parsed),settings=validated.success?validated.data:void 0,loaded=await stageGuardedSchemaSections(root,configurationRoot,settings?.schema,configuration),schema=loaded.schema,sessionPath=path.relative(root,getSessionStatePath(root));configuration.set(sessionPath,await readPreviewConfiguration(root,sessionPath));const registry=resolveItemTypeRegistry({...SETTINGS_DEFAULTS,item_types:{definitions:[...settings?.item_types?.definitions??[],...loaded.type_definitions_from_file??[]]}},registrations),ownedPaths=resolveWorkspaceRoot(root)===root?[...PM_REQUIRED_SUBDIRS.filter(Boolean),SETTINGS_FILENAME,PM_CONTEXT_INTENTS_FILE,"transactions",...registry.folders,...configuration.keys()].map(entry=>path.normalize(entry)):void 0,stagedSettings=settings===void 0?void 0:JSON.stringify({...parsed,schema:{...settings.schema,files:schema.files}});return{configuration,ownedPaths,stagedSettings,registryFolders:registry.folders}}async function stagePreviewTracker(sourceRoot,stagedRoot,registrations){await stat(sourceRoot);try{const resolvedRoot=await realpath(sourceRoot),storage=await resolvePreviewStorage(resolvedRoot,path.join(path.dirname(stagedRoot),"configuration"),registrations),retainedPaths=[...storage.configuration.keys(),...storage.registryFolders].map(entry=>path.normalize(entry)),sourceFingerprint=await previewTrackerFingerprint(resolvedRoot,storage.ownedPaths,retainedPaths);await cp(resolvedRoot,stagedRoot,{recursive:!0,dereference:!1,filter:async source=>includePreviewPath(resolvedRoot,source,storage.ownedPaths,retainedPaths)?(await previewEntryStat(resolvedRoot,source),!0):!1});const[currentFingerprint,stagedFingerprint]=await Promise.all([previewTrackerFingerprint(resolvedRoot,storage.ownedPaths,retainedPaths),previewTrackerFingerprint(stagedRoot,storage.ownedPaths,retainedPaths)]);if(sourceFingerprint!==currentFingerprint||sourceFingerprint!==stagedFingerprint)throw previewSnapshotChangedError();for(const[relative,contents]of storage.configuration)if(await readPreviewConfiguration(stagedRoot,relative)!==contents)throw previewSnapshotChangedError();storage.stagedSettings!==void 0&&await writeFile(path.join(stagedRoot,SETTINGS_FILENAME),storage.stagedSettings)}catch(error){throw await isDisappearingPreviewPath(error)?previewSnapshotChangedError():error}}async function previewItemMutations(options){const identity=validateWorkspaceTransactionIdentity(options),mutations=[...options.mutations];assertValidBulkMutations(mutations);const registrations=getActiveExtensionRegistrations(),root=await mkdtemp(path.join(tmpdir(),"pm-transaction-preview-")),stagedRoot=path.join(root,"tracker");try{await stagePreviewTracker(identity.pmRoot,stagedRoot,registrations);let validated=!1;const warnings=[];return await runWithActiveExtensions({path:stagedRoot,noExtensions:!0},async()=>{setActiveExtensionRegistrations(registrations);const config={...identity,pmRoot:stagedRoot,createCompensation:"close",uniqueIndex:createUniqueKeyIndex(stagedRoot)},steps=mutations.map((mutation,index)=>{const step=buildStepForMutation({...config,onCreateWarnings:(id,messages)=>{messages.length>0&&warnings.push({index,id,op:"create",warnings:messages})}},mutation,index);return{...step,inspect:()=>validateMutationOperation(mutation,index,()=>step.inspect()),prepareCompensation:()=>validateMutationOperation(mutation,index,async()=>step.prepareCompensation?.()),apply:()=>validateMutationOperation(mutation,index,()=>step.apply())}});await commitWorkspaceTransaction({...identity,pmRoot:stagedRoot,steps,onTransition:({transition})=>{transition==="committed"&&(validated=!0)}})}),{validated,state:validated?"staged_snapshot":"replayed_committed_plan",warnings,unresolved_commit_constraints:["concurrent_tracker_changes","extension_mutation_guards_and_hooks","derived_extension_dependencies_excluded"]}}finally{await rm(root,{recursive:!0,force:!0})}}async function commitItemMutations(options){const mutations=[...options.mutations];assertValidBulkMutations(mutations);const createCompensation=options.createCompensation??"close";if(!["close","delete"].includes(createCompensation))throw new TypeError("Bulk item transaction createCompensation must be close or delete");const stepConfig={pmRoot:options.pmRoot,author:options.author,transactionId:options.transactionId,createCompensation,uniqueIndex:createUniqueKeyIndex(options.pmRoot)},committed=await commitWorkspaceTransaction({pmRoot:options.pmRoot,transactionId:options.transactionId,author:options.author,steps:mutations.map((mutation,index)=>buildStepForMutation(stepConfig,mutation,index)),...options.lockTtlSeconds===void 0?{}:{lockTtlSeconds:options.lockTtlSeconds},...options.lockWaitMs===void 0?{}:{lockWaitMs:options.lockWaitMs},...options.onTransition===void 0?{}:{onTransition:options.onTransition}}),results={};for(const[index,mutation]of mutations.entries()){const stepId=deriveStepId(mutation,index),value=committed.results[stepId];results[deriveOutcomeKey(mutation,index)]=value}return{transactionId:committed.transactionId,status:"committed",recovered:committed.recovered,results}}function buildItemCompletionMutations(options){const mutations=[];return options.evidence!==void 0&&Object.keys(options.evidence).length>0&&mutations.push({op:"update",id:options.id,options:options.evidence}),mutations.push({op:"close",id:options.id,reason:options.reason,...options.closeOptions===void 0?{}:{options:options.closeOptions}},{op:"release",id:options.id,...options.releaseOptions===void 0?{}:{options:options.releaseOptions}}),mutations}function commitItemCompletion(options){return commitItemMutations({pmRoot:options.pmRoot,transactionId:options.transactionId,author:options.author,mutations:buildItemCompletionMutations(options),...options.lockTtlSeconds===void 0?{}:{lockTtlSeconds:options.lockTtlSeconds},...options.lockWaitMs===void 0?{}:{lockWaitMs:options.lockWaitMs},...options.onTransition===void 0?{}:{onTransition:options.onTransition}})}const _testOnlyItemTransaction={buildCreateStep,bulkHistoryMarker,compensateByRestore,deriveStepId,isUpdateMarkerApplied,readItemSnapshot};export{_testOnlyItemTransaction,buildItemCompletionMutations,commitItemCompletion,commitItemMutations,listUpsertKeyCandidates,loadGuardedRuntimeSchema,previewItemMutations,resolveTransactionSchema};
2
2
  //# sourceMappingURL=item-transaction.js.map
@@ -1,4 +1,5 @@
1
1
  import { type GlobalOptions,readSettings } from "../runtime-primitives.js";
2
+ import type { CloseCompletionReceipt } from "../lifecycle-policy.js";
2
3
  import { type ListOptions } from "../query/list.js";
3
4
  import { type PmCommandEffectReceipt } from "../cli-contracts/command-exit-contracts.js";
4
5
  /** Documents the close many command options payload exchanged by command, SDK, and package integrations. */
@@ -43,6 +44,8 @@ skip_reason?:string;
43
44
  active_child_ids?:string[];
44
45
  }
45
46
  interface CloseManyApplyRow {
47
+ /** Completion provenance for a successfully closed target. */
48
+ completion?:CloseCompletionReceipt;
46
49
  id:string;
47
50
  status:"closed" | "skipped" | "failed";
48
51
  skip_reason?:string;
@@ -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]="92e7dad4-05e3-524f-9261-aeb4de9e0fe9")}catch{}})();import{assertInitializedTracker}from"../environment/tracker-preflight.js";import{withCheckpointMutation}from"../../core/checkpoint/mutation-context.js";import{resolveCheckpointRollbackId}from"../../core/checkpoint/mutation-checkpoint.js";import{rollbackMutationCheckpoint}from"./checkpoint-rollback.js";import{createCheckpointId,loadMutationCheckpoint,writeMutationCheckpoint,getActiveExtensionRegistrations,isTerminalStatus,resolveItemTypeRegistry,resolveRuntimeStatusRegistry,EXIT_CODE,PmCliError,toErrorMessage,resolveAuthor,nowIso,listAllItemMetadataLight,resolvePmRoot,readSettings}from"../runtime-primitives.js";import{runClose}from"./close.js";import{hasListFilters}from"../query/list-filter-shared.js";import{runList}from"../query/list.js";import{derivePmBulkMutationEffect}from"../cli-contracts/command-exit-contracts.js";const CLOSE_MANY_CHECKPOINT_SCHEMA_VERSION=2,CLOSE_MANY_CHECKPOINT_SUBDIR="close-many",CLOSE_MANY_FILTER_GUIDANCE=["--filter-status","--filter-type","--filter-tag","--filter-priority","--filter-sprint","--filter-release","--filter-parent","--filter-assignee","--filter-updated-before","--ids"].join(", ");function hasCloseManyFilters(list,status){return hasListFilters(list,status,{includePagination:!1})}function hasCloseManyRollbackConflicts(list,status){return hasListFilters(list,status)}function rejectBlankIdsFilter(list){if(list?.ids!=null&&String(list.ids).trim().length===0)throw new PmCliError("--ids requires at least one non-empty item ID",EXIT_CODE.USAGE)}function activeListOptions(list){const active={};if(!list)return active;for(const[key,value]of Object.entries(list))value==null||typeof value=="string"&&!value.split(",").some(entry=>entry.trim().length>0)||(active[key]=value);return active}function resolveReason(reason,required){const trimmed=String(reason??"").trim();if(trimmed.length>0)return trimmed;if(required)throw new PmCliError("pm close-many requires a shared close reason via --reason because governance.require_close_reason is enabled.",EXIT_CODE.USAGE,{code:"close_reason_required",required:"Provide a shared closing summary via --reason for the whole batch.",why:"governance.require_close_reason is enabled, so every close must record why the items are done.",examples:['pm close-many --filter-sprint S-12 --reason "Sprint S-12 acceptance criteria met"','pm close-many --ids pm-a,pm-b --reason "Superseded by redesign"'],nextSteps:["Re-run with --reason describing the shared outcome.","To stop requiring reasons, run: pm config set governance-require-close-reason --policy disabled"]})}async function buildActiveChildrenByParent(pmRoot,settings){const statusRegistry=resolveRuntimeStatusRegistry(settings.schema),typeRegistry=resolveItemTypeRegistry(settings,getActiveExtensionRegistrations()),items=await listAllItemMetadataLight(pmRoot,settings.item_format,typeRegistry.type_to_folder,void 0,settings.schema),childrenByParent=new Map,parentByChild=new Map;for(const item of items){if(typeof item.parent!="string"||item.parent.length===0||(parentByChild.set(item.id,item.parent),isTerminalStatus(item.status,statusRegistry)))continue;const existing=childrenByParent.get(item.parent);existing?existing.push(item.id):childrenByParent.set(item.parent,[item.id])}for(const ids of childrenByParent.values())ids.sort((left,right)=>left.localeCompare(right));return{childrenByParent,parentByChild}}function hierarchyDepth(id,parentByChild,cache){const cached=cache?.get(id);if(cached!==void 0)return cached;let depth=0,current=parentByChild.get(id);const path=[],visited=new Set;let hasCycle=!1;for(;current!==void 0&&!visited.has(current);){const cachedCurrent=cache?.get(current);if(cachedCurrent!==void 0){depth+=cachedCurrent+1;break}visited.add(current),path.push(current),depth+=1,current=parentByChild.get(current)}if(current!==void 0&&visited.has(current)&&(hasCycle=!0),!hasCycle){cache?.set(id,depth);for(const[index,ancestorId]of path.entries())cache?.set(ancestorId,depth-index-1)}return depth}async function runCloseManyRollback(params){const checkpoint=await loadMutationCheckpoint(params.pmRoot,CLOSE_MANY_CHECKPOINT_SUBDIR,params.rollbackId,CLOSE_MANY_CHECKPOINT_SCHEMA_VERSION),restoreMessage=params.options.message??`Rollback close-many checkpoint ${checkpoint.id}`,rollback=await rollbackMutationCheckpoint(params.pmRoot,checkpoint,{author:params.options.author,message:restoreMessage,force:params.options.force}),skipped=rollback.rows.filter(row=>row.status==="skipped").length;return{...derivePmBulkMutationEffect({applied:rollback.restored_ids.length,skipped,failed:rollback.failed_count,unmatched:0}),mode:"rollback",matched_count:checkpoint.items.length,dry_run:!1,rollback_checkpoint_id:checkpoint.id,checkpoint:{id:checkpoint.id,created_at:checkpoint.created_at,path:checkpoint.path,rollback_command:`pm close-many --rollback ${checkpoint.id}`},restored_count:rollback.restored_ids.length,skipped_count:skipped,failed_count:rollback.failed_count,rows:rollback.rows,ids:rollback.restored_ids}}function planCloseManyRows(matched,childrenByParent,closePlannedIds,statusRegistry,force){return matched.map(item=>{const activeChildIds=(childrenByParent.get(item.id)??[]).filter(childId=>!closePlannedIds.has(childId)),willSkip=!canCloseManyItem(item,statusRegistry,force);return{id:item.id,title:typeof item.title=="string"?item.title:"",status:item.status,action:willSkip?"skip":"close",...willSkip?{skip_reason:"already_terminal"}:{},...activeChildIds.length>0?{active_child_ids:activeChildIds}:{}}})}async function writeCloseManyCheckpoint(params){const checkpointPath=await writeMutationCheckpoint(params.pmRoot,CLOSE_MANY_CHECKPOINT_SUBDIR,params.checkpointId,{schema_version:CLOSE_MANY_CHECKPOINT_SCHEMA_VERSION,rollback_strategy:"guarded_inverse",id:params.checkpointId,created_at:params.nowValue,author:resolveAuthor(params.options.author,"unknown"),...params.reason!==void 0?{reason:params.reason}:{},filters:params.filters,items:params.checkpointItems});return{id:params.checkpointId,created_at:params.nowValue,path:checkpointPath,rollback_command:`pm close-many --rollback ${params.checkpointId}`}}async function applyCloseManyRows(params){const rows=[],closedIds=[];for(const item of params.matched){if(!params.closableIds.has(item.id)){rows.push({id:item.id,status:"skipped",skip_reason:"already_terminal"});continue}try{const result=await withCheckpointMutation({pmRoot:params.pmRoot,itemId:item.id,checkpointId:params.checkpointId},()=>runClose(item.id,params.reason,params.closeOptions,params.global));rows.push({id:item.id,status:"closed",changed_fields:result.changed_fields,...result.warnings&&result.warnings.length>0?{warnings:result.warnings}:{}}),closedIds.push(item.id)}catch(error){rows.push({id:item.id,status:"failed",error:toErrorMessage(error)})}}return{rows,closedIds}}function resolveCloseManyRollbackId(options){return resolveCheckpointRollbackId(options.rollback)}function validateCloseManyRollbackOptions(options){if(options.dryRun===!0)throw new PmCliError("--dry-run cannot be combined with --rollback",EXIT_CODE.USAGE);if(hasCloseManyRollbackConflicts(options.list,options.status))throw new PmCliError("Rollback mode does not accept filter options",EXIT_CODE.USAGE)}function assertCloseManyHasFilters(options){if(!hasCloseManyFilters(options.list,options.status))throw new PmCliError(`close-many requires at least one filter to scope the close (for example: ${CLOSE_MANY_FILTER_GUIDANCE}). Refusing to match every item.`,EXIT_CODE.USAGE)}function sortCloseManyMatchedItems(matched,parentByChild){const depthCache=new Map;return[...matched].sort((left,right)=>{const depthDelta=hierarchyDepth(right.id,parentByChild,depthCache)-hierarchyDepth(left.id,parentByChild,depthCache);return depthDelta!==0?depthDelta:left.id.localeCompare(right.id)})}function canCloseManyItem(item,statusRegistry,force){return force||!isTerminalStatus(item.status,statusRegistry)}function resolveCloseManyPlannedIds(matched,statusRegistry,force){return new Set(matched.filter(item=>canCloseManyItem(item,statusRegistry,force)).map(item=>item.id))}async function buildCloseManyPlanContext(params){assertCloseManyHasFilters(params.options);const statusRegistry=resolveRuntimeStatusRegistry(params.settings.schema),reason=resolveReason(params.options.reason,params.settings.governance.require_close_reason),force=params.options.force===!0,listed=await runList(params.options.status,activeListOptions(params.options.list),params.global),{childrenByParent,parentByChild}=await buildActiveChildrenByParent(params.pmRoot,params.settings),matched=sortCloseManyMatchedItems(listed.items,parentByChild),requestedIds=[...new Set(String(params.options.list?.ids??"").split(",").map(entry=>entry.trim()).filter(entry=>entry.length>0))],existenceItems=requestedIds.length===0?[]:(await runList(void 0,{ids:requestedIds.join(","),noTruncate:!0,full:!0},params.global)).items,existingIds=new Set(existenceItems.map(item=>item.id)),closePlannedIds=resolveCloseManyPlannedIds(matched,statusRegistry,force);return{pmRoot:params.pmRoot,settings:params.settings,statusRegistry,reason,validateCloseMode:params.options.validateClose,force,listed,matched,unmatchedIds:requestedIds.length===0?void 0:requestedIds.filter(id=>!existingIds.has(id)),planRows:planCloseManyRows(matched,childrenByParent,closePlannedIds,statusRegistry,force)}}function buildCloseManyDryRunResult(context){return{outcome:"effect",exit_code:EXIT_CODE.SUCCESS,mode:"dry_run",matched_count:context.matched.length,dry_run:!0,...context.reason!==void 0?{reason:context.reason}:{},filters:context.listed.filters,...context.validateCloseMode?{validate_close:context.validateCloseMode}:{},item_plans:context.planRows,ids:[],...context.unmatchedIds!==void 0?{unmatched_ids:context.unmatchedIds,unmatched_count:context.unmatchedIds.length}:{}}}async function resolveCloseManyCheckpointInfo(params){if(params.options.checkpoint===!1||params.closableIds.size===0)return;const checkpointItems=params.context.matched.filter(item=>params.closableIds.has(item.id)).map(item=>({id:item.id,target_updated_at:item.updated_at}));return writeCloseManyCheckpoint({pmRoot:params.context.pmRoot,checkpointId:params.checkpointId,nowValue:params.nowValue,options:params.options,reason:params.context.reason,filters:params.context.listed.filters,checkpointItems})}async function runCloseMany(options,global2){const rollbackId=resolveCloseManyRollbackId(options),pmRoot=resolvePmRoot(process.cwd(),global2.path);await assertInitializedTracker(pmRoot);const settings=await readSettings(pmRoot),dryRun=options.dryRun===!0;if(rejectBlankIdsFilter(options.list),rollbackId!==void 0)return validateCloseManyRollbackOptions(options),runCloseManyRollback({pmRoot,rollbackId,options,global:global2});const context=await buildCloseManyPlanContext({pmRoot,settings,options,global:global2});if(dryRun)return buildCloseManyDryRunResult(context);const closableIds=new Set(context.planRows.filter(row=>row.action==="close").map(row=>row.id)),nowValue=nowIso(),checkpointId=createCheckpointId(CLOSE_MANY_CHECKPOINT_SUBDIR,nowValue),checkpointInfo=await resolveCloseManyCheckpointInfo({context,options,closableIds,checkpointId,nowValue}),closeOptions={author:options.author,message:options.message??`close-many apply ${checkpointId}`,completedAt:options.completedAt,validateClose:context.validateCloseMode,force:context.force,resolution:options.resolution,expectedResult:options.expectedResult,actualResult:options.actualResult},{rows,closedIds}=await applyCloseManyRows({pmRoot,checkpointId,matched:context.matched,closableIds,reason:context.reason,closeOptions,global:global2}),closedCount=rows.filter(row=>row.status==="closed").length,skippedCount=rows.filter(row=>row.status==="skipped").length,failedCount=rows.filter(row=>row.status==="failed").length;return{...derivePmBulkMutationEffect({applied:closedCount,skipped:skippedCount,failed:failedCount,unmatched:context.unmatchedIds?.length??0}),mode:"apply",matched_count:context.matched.length,dry_run:!1,...context.reason!==void 0?{reason:context.reason}:{},filters:context.listed.filters,...context.validateCloseMode?{validate_close:context.validateCloseMode}:{},...checkpointInfo?{checkpoint:checkpointInfo}:{},closed_count:closedCount,skipped_count:skippedCount,failed_count:failedCount,rows,ids:closedIds,...context.unmatchedIds!==void 0?{unmatched_ids:context.unmatchedIds,unmatched_count:context.unmatchedIds.length}:{}}}const _testOnly={activeListOptions,buildActiveChildrenByParent,hasCloseManyFilters,hasCloseManyRollbackConflicts,hierarchyDepth,rejectBlankIdsFilter,resolveReason};export{_testOnly,runCloseMany};
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]="da095c39-4e7a-5c52-9b33-aa85d6e12e2d")}catch{}})();import{assertInitializedTracker}from"../environment/tracker-preflight.js";import{withCheckpointMutation}from"../../core/checkpoint/mutation-context.js";import{resolveCheckpointRollbackId}from"../../core/checkpoint/mutation-checkpoint.js";import{rollbackMutationCheckpoint}from"./checkpoint-rollback.js";import{createCheckpointId,loadMutationCheckpoint,writeMutationCheckpoint,getActiveExtensionRegistrations,isTerminalStatus,resolveItemTypeRegistry,resolveRuntimeStatusRegistry,EXIT_CODE,PmCliError,toErrorMessage,resolveAuthor,nowIso,listAllItemMetadataLight,resolvePmRoot,readSettings}from"../runtime-primitives.js";import{runClose}from"./close.js";import{hasListFilters}from"../query/list-filter-shared.js";import{runList}from"../query/list.js";import{derivePmBulkMutationEffect}from"../cli-contracts/command-exit-contracts.js";const CLOSE_MANY_CHECKPOINT_SCHEMA_VERSION=2,CLOSE_MANY_CHECKPOINT_SUBDIR="close-many",CLOSE_MANY_FILTER_GUIDANCE=["--filter-status","--filter-type","--filter-tag","--filter-priority","--filter-sprint","--filter-release","--filter-parent","--filter-assignee","--filter-updated-before","--ids"].join(", ");function hasCloseManyFilters(list,status){return hasListFilters(list,status,{includePagination:!1})}function hasCloseManyRollbackConflicts(list,status){return hasListFilters(list,status)}function rejectBlankIdsFilter(list){if(list?.ids!=null&&String(list.ids).trim().length===0)throw new PmCliError("--ids requires at least one non-empty item ID",EXIT_CODE.USAGE)}function activeListOptions(list){const active={};if(!list)return active;for(const[key,value]of Object.entries(list))value==null||typeof value=="string"&&!value.split(",").some(entry=>entry.trim().length>0)||(active[key]=value);return active}function resolveReason(reason,required){const trimmed=String(reason??"").trim();if(trimmed.length>0)return trimmed;if(required)throw new PmCliError("pm close-many requires a shared close reason via --reason because governance.require_close_reason is enabled.",EXIT_CODE.USAGE,{code:"close_reason_required",required:"Provide a shared closing summary via --reason for the whole batch.",why:"governance.require_close_reason is enabled, so every close must record why the items are done.",examples:['pm close-many --filter-sprint S-12 --reason "Sprint S-12 acceptance criteria met"','pm close-many --ids pm-a,pm-b --reason "Superseded by redesign"'],nextSteps:["Re-run with --reason describing the shared outcome.","To stop requiring reasons, run: pm config set governance-require-close-reason --policy disabled"]})}async function buildActiveChildrenByParent(pmRoot,settings){const statusRegistry=resolveRuntimeStatusRegistry(settings.schema),typeRegistry=resolveItemTypeRegistry(settings,getActiveExtensionRegistrations()),items=await listAllItemMetadataLight(pmRoot,settings.item_format,typeRegistry.type_to_folder,void 0,settings.schema),childrenByParent=new Map,parentByChild=new Map;for(const item of items){if(typeof item.parent!="string"||item.parent.length===0||(parentByChild.set(item.id,item.parent),isTerminalStatus(item.status,statusRegistry)))continue;const existing=childrenByParent.get(item.parent);existing?existing.push(item.id):childrenByParent.set(item.parent,[item.id])}for(const ids of childrenByParent.values())ids.sort((left,right)=>left.localeCompare(right));return{childrenByParent,parentByChild}}function hierarchyDepth(id,parentByChild,cache){const cached=cache?.get(id);if(cached!==void 0)return cached;let depth=0,current=parentByChild.get(id);const path=[],visited=new Set;let hasCycle=!1;for(;current!==void 0&&!visited.has(current);){const cachedCurrent=cache?.get(current);if(cachedCurrent!==void 0){depth+=cachedCurrent+1;break}visited.add(current),path.push(current),depth+=1,current=parentByChild.get(current)}if(current!==void 0&&visited.has(current)&&(hasCycle=!0),!hasCycle){cache?.set(id,depth);for(const[index,ancestorId]of path.entries())cache?.set(ancestorId,depth-index-1)}return depth}async function runCloseManyRollback(params){const checkpoint=await loadMutationCheckpoint(params.pmRoot,CLOSE_MANY_CHECKPOINT_SUBDIR,params.rollbackId,CLOSE_MANY_CHECKPOINT_SCHEMA_VERSION),restoreMessage=params.options.message??`Rollback close-many checkpoint ${checkpoint.id}`,rollback=await rollbackMutationCheckpoint(params.pmRoot,checkpoint,{author:params.options.author,message:restoreMessage,force:params.options.force}),skipped=rollback.rows.filter(row=>row.status==="skipped").length;return{...derivePmBulkMutationEffect({applied:rollback.restored_ids.length,skipped,failed:rollback.failed_count,unmatched:0}),mode:"rollback",matched_count:checkpoint.items.length,dry_run:!1,rollback_checkpoint_id:checkpoint.id,checkpoint:{id:checkpoint.id,created_at:checkpoint.created_at,path:checkpoint.path,rollback_command:`pm close-many --rollback ${checkpoint.id}`},restored_count:rollback.restored_ids.length,skipped_count:skipped,failed_count:rollback.failed_count,rows:rollback.rows,ids:rollback.restored_ids}}function planCloseManyRows(matched,childrenByParent,closePlannedIds,statusRegistry,force){return matched.map(item=>{const activeChildIds=(childrenByParent.get(item.id)??[]).filter(childId=>!closePlannedIds.has(childId)),willSkip=!canCloseManyItem(item,statusRegistry,force);return{id:item.id,title:typeof item.title=="string"?item.title:"",status:item.status,action:willSkip?"skip":"close",...willSkip?{skip_reason:"already_terminal"}:{},...activeChildIds.length>0?{active_child_ids:activeChildIds}:{}}})}async function writeCloseManyCheckpoint(params){const checkpointPath=await writeMutationCheckpoint(params.pmRoot,CLOSE_MANY_CHECKPOINT_SUBDIR,params.checkpointId,{schema_version:CLOSE_MANY_CHECKPOINT_SCHEMA_VERSION,rollback_strategy:"guarded_inverse",id:params.checkpointId,created_at:params.nowValue,author:resolveAuthor(params.options.author,"unknown"),...params.reason!==void 0?{reason:params.reason}:{},filters:params.filters,items:params.checkpointItems});return{id:params.checkpointId,created_at:params.nowValue,path:checkpointPath,rollback_command:`pm close-many --rollback ${params.checkpointId}`}}async function applyCloseManyRows(params){const rows=[],closedIds=[];for(const item of params.matched){if(!params.closableIds.has(item.id)){rows.push({id:item.id,status:"skipped",skip_reason:"already_terminal"});continue}try{const result=await withCheckpointMutation({pmRoot:params.pmRoot,itemId:item.id,checkpointId:params.checkpointId},()=>runClose(item.id,params.reason,params.closeOptions,params.global));rows.push({id:item.id,status:"closed",completion:result.completion,changed_fields:result.changed_fields,...result.warnings&&result.warnings.length>0?{warnings:result.warnings}:{}}),closedIds.push(item.id)}catch(error){rows.push({id:item.id,status:"failed",error:toErrorMessage(error)})}}return{rows,closedIds}}function resolveCloseManyRollbackId(options){return resolveCheckpointRollbackId(options.rollback)}function validateCloseManyRollbackOptions(options){if(options.dryRun===!0)throw new PmCliError("--dry-run cannot be combined with --rollback",EXIT_CODE.USAGE);if(hasCloseManyRollbackConflicts(options.list,options.status))throw new PmCliError("Rollback mode does not accept filter options",EXIT_CODE.USAGE)}function assertCloseManyHasFilters(options){if(!hasCloseManyFilters(options.list,options.status))throw new PmCliError(`close-many requires at least one filter to scope the close (for example: ${CLOSE_MANY_FILTER_GUIDANCE}). Refusing to match every item.`,EXIT_CODE.USAGE)}function sortCloseManyMatchedItems(matched,parentByChild){const depthCache=new Map;return[...matched].sort((left,right)=>{const depthDelta=hierarchyDepth(right.id,parentByChild,depthCache)-hierarchyDepth(left.id,parentByChild,depthCache);return depthDelta!==0?depthDelta:left.id.localeCompare(right.id)})}function canCloseManyItem(item,statusRegistry,force){return force||!isTerminalStatus(item.status,statusRegistry)}function resolveCloseManyPlannedIds(matched,statusRegistry,force){return new Set(matched.filter(item=>canCloseManyItem(item,statusRegistry,force)).map(item=>item.id))}async function buildCloseManyPlanContext(params){assertCloseManyHasFilters(params.options);const statusRegistry=resolveRuntimeStatusRegistry(params.settings.schema),reason=resolveReason(params.options.reason,params.settings.governance.require_close_reason),force=params.options.force===!0,listed=await runList(params.options.status,activeListOptions(params.options.list),params.global),{childrenByParent,parentByChild}=await buildActiveChildrenByParent(params.pmRoot,params.settings),matched=sortCloseManyMatchedItems(listed.items,parentByChild),requestedIds=[...new Set(String(params.options.list?.ids??"").split(",").map(entry=>entry.trim()).filter(entry=>entry.length>0))],existenceItems=requestedIds.length===0?[]:(await runList(void 0,{ids:requestedIds.join(","),noTruncate:!0,full:!0},params.global)).items,existingIds=new Set(existenceItems.map(item=>item.id)),closePlannedIds=resolveCloseManyPlannedIds(matched,statusRegistry,force);return{pmRoot:params.pmRoot,settings:params.settings,statusRegistry,reason,validateCloseMode:params.options.validateClose,force,listed,matched,unmatchedIds:requestedIds.length===0?void 0:requestedIds.filter(id=>!existingIds.has(id)),planRows:planCloseManyRows(matched,childrenByParent,closePlannedIds,statusRegistry,force)}}function buildCloseManyDryRunResult(context){return{outcome:"effect",exit_code:EXIT_CODE.SUCCESS,mode:"dry_run",matched_count:context.matched.length,dry_run:!0,...context.reason!==void 0?{reason:context.reason}:{},filters:context.listed.filters,...context.validateCloseMode?{validate_close:context.validateCloseMode}:{},item_plans:context.planRows,ids:[],...context.unmatchedIds!==void 0?{unmatched_ids:context.unmatchedIds,unmatched_count:context.unmatchedIds.length}:{}}}async function resolveCloseManyCheckpointInfo(params){if(params.options.checkpoint===!1||params.closableIds.size===0)return;const checkpointItems=params.context.matched.filter(item=>params.closableIds.has(item.id)).map(item=>({id:item.id,target_updated_at:item.updated_at}));return writeCloseManyCheckpoint({pmRoot:params.context.pmRoot,checkpointId:params.checkpointId,nowValue:params.nowValue,options:params.options,reason:params.context.reason,filters:params.context.listed.filters,checkpointItems})}async function runCloseMany(options,global2){const rollbackId=resolveCloseManyRollbackId(options),pmRoot=resolvePmRoot(process.cwd(),global2.path);await assertInitializedTracker(pmRoot);const settings=await readSettings(pmRoot),dryRun=options.dryRun===!0;if(rejectBlankIdsFilter(options.list),rollbackId!==void 0)return validateCloseManyRollbackOptions(options),runCloseManyRollback({pmRoot,rollbackId,options,global:global2});const context=await buildCloseManyPlanContext({pmRoot,settings,options,global:global2});if(dryRun)return buildCloseManyDryRunResult(context);const closableIds=new Set(context.planRows.filter(row=>row.action==="close").map(row=>row.id)),nowValue=nowIso(),checkpointId=createCheckpointId(CLOSE_MANY_CHECKPOINT_SUBDIR,nowValue),checkpointInfo=await resolveCloseManyCheckpointInfo({context,options,closableIds,checkpointId,nowValue}),closeOptions={author:options.author,message:options.message??`close-many apply ${checkpointId}`,completedAt:options.completedAt,validateClose:context.validateCloseMode,force:context.force,resolution:options.resolution,expectedResult:options.expectedResult,actualResult:options.actualResult},{rows,closedIds}=await applyCloseManyRows({pmRoot,checkpointId,matched:context.matched,closableIds,reason:context.reason,closeOptions,global:global2}),closedCount=rows.filter(row=>row.status==="closed").length,skippedCount=rows.filter(row=>row.status==="skipped").length,failedCount=rows.filter(row=>row.status==="failed").length;return{...derivePmBulkMutationEffect({applied:closedCount,skipped:skippedCount,failed:failedCount,unmatched:context.unmatchedIds?.length??0}),mode:"apply",matched_count:context.matched.length,dry_run:!1,...context.reason!==void 0?{reason:context.reason}:{},filters:context.listed.filters,...context.validateCloseMode?{validate_close:context.validateCloseMode}:{},...checkpointInfo?{checkpoint:checkpointInfo}:{},closed_count:closedCount,skipped_count:skippedCount,failed_count:failedCount,rows,ids:closedIds,...context.unmatchedIds!==void 0?{unmatched_ids:context.unmatchedIds,unmatched_count:context.unmatchedIds.length}:{}}}const _testOnly={activeListOptions,buildActiveChildrenByParent,hasCloseManyFilters,hasCloseManyRollbackConflicts,hierarchyDepth,rejectBlankIdsFilter,resolveReason};export{_testOnly,runCloseMany};
2
2
  //# sourceMappingURL=close-many.js.map
@@ -1,6 +1,6 @@
1
1
  import { type GlobalOptions } from "../runtime-primitives.js";
2
2
  import { collectBlockedByIds } from "../actionability.js";
3
- import { type CloseOperationOptions,type CloseOperationResult,type CloseEvidenceFollowUp } from "../lifecycle-policy.js";
3
+ import { type CloseOperationOptions,type CloseOperationResult,type CloseEvidenceFollowUp,type CloseCompletionReceipt } from "../lifecycle-policy.js";
4
4
  /** Compatibility options for command-shaped close consumers. */
5
5
  export interface CloseCommandOptions {
6
6
  /** Mutation author. */
@@ -24,6 +24,8 @@ completedAt?:string;
24
24
  }
25
25
  /** Compatibility result for command-shaped close consumers. */
26
26
  export interface CloseResult {
27
+ /** Completion timestamp provenance sealed into this close event. */
28
+ completion?:CloseCompletionReceipt;
27
29
  /** Closed item record. */
28
30
  item:Record<string,unknown>;
29
31
  /** Changed metadata fields. */
@@ -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]="6b7f5ee8-94ca-574f-b660-963eb574d571")}catch{}})();import{assertInitializedTracker}from"../environment/tracker-preflight.js";import{toItemRecord,isTerminalStatus,resolveItemTypeRegistry,resolveRuntimeStatusRegistry,EXIT_CODE,PmCliError,buildItemNotFoundError,listAllItemMetadataLight,locateItem,mutateItem,readLocatedItem,resolvePmRoot,readSettings,resolveAuthor,resolveIsoOrRelative,shouldCompletePlanOnClose,getActiveExtensionRegistrations}from"../runtime-primitives.js";import{collectBlockedByIds,indexBlockedByIds}from"../actionability.js";import{applyTerminalOrderingPolicy,requireTerminalReason}from"../lifecycle-policy.js";import{nowIso}from"../../core/shared/time.js";import{renderPmCommand}from"../command-line.js";const CLOSE_VALIDATION_FIELDS=[{key:"resolution",label:"resolution (--resolution)"},{key:"expected_result",label:"expected_result (--expected-result)"},{key:"actual_result",label:"actual_result (--actual-result)"}];function parseValidateCloseMode(raw){if(raw===void 0)return;const normalized=raw.trim().toLowerCase();if(normalized.length===0||normalized==="warn")return"warn";if(normalized==="off"||normalized==="none"||normalized==="disabled")return"off";if(normalized==="strict")return"strict";throw new PmCliError(`Invalid --validate-close mode "${raw}" (expected "off", "warn", or "strict")`,EXIT_CODE.USAGE)}function findMissingCloseValidationFields(itemMetadata){const missing=[];for(const field of CLOSE_VALIDATION_FIELDS){const rawValue=itemMetadata[field.key];(typeof rawValue!="string"||rawValue.trim().length===0)&&missing.push(field)}return missing}function closeEvidenceRecovery(metadata,pmRoot,options={}){const missing=findMissingCloseValidationFields(metadata);if(missing.length===0)return;const values={resolution:options.resolution,expected_result:options.expectedResult,actual_result:options.actualResult},missingFlags=missing.map(field=>`--${field.key.replaceAll("_","-")}`),args=["--pm-path",pmRoot,"update",metadata.id,...missing.flatMap((field,index)=>[missingFlags[index],values[field.key]?.trim()||"<value>"])];return{missing:missingFlags,suggested_retry:renderPmCommand(args),suggested_retry_args:args}}async function duplicateChainReferencesClosingItem(loadItemById,initialDuplicateOf,closingId){const visited=new Set;let current=initialDuplicateOf;for(;typeof current=="string"&&current.trim().length>0;){const currentId=current.trim();if(currentId===closingId)return!0;if(visited.has(currentId))return!1;visited.add(currentId),current=(await loadItemById(currentId))?.duplicate_of}return!1}function shouldApplyDuplicateFallback(value){return typeof value!="string"||value.trim().length===0}async function assertDuplicateTargetExists(pmRoot,settings,duplicateOf,closingId){const rawTarget=duplicateOf?.trim();if(!rawTarget)return;if(rawTarget===closingId)throw new PmCliError("An item cannot be closed as a duplicate of itself.",EXIT_CODE.USAGE,{code:"duplicate_target_self",why:"--duplicate-of must identify the canonical item that should remain open or already represent the work."});const typeRegistry=resolveItemTypeRegistry(settings,getActiveExtensionRegistrations()),itemCache=new Map,loadItemById=async id=>{if(itemCache.has(id))return itemCache.get(id)??null;const located=await locateItem(pmRoot,id,settings.id_prefix,settings.item_format,typeRegistry.type_to_folder);if(!located)return itemCache.set(id,null),null;const{document}=await readLocatedItem(located,{schema:settings.schema});return itemCache.set(id,document.metadata),document.metadata},target=await loadItemById(rawTarget);if(!target)throw new PmCliError(`Duplicate target "${rawTarget}" was not found. Create or locate the canonical item first.`,EXIT_CODE.USAGE,{code:"duplicate_target_missing",why:"Duplicate closure should point at a real canonical pm item so future dedupe and changelog tooling can trace the relationship.",examples:[`pm close ${closingId} "Duplicate of ${rawTarget}" --duplicate-of ${rawTarget}`],nextSteps:["Run pm search/list to find the canonical item, then retry with --duplicate-of <id>."]});if(await duplicateChainReferencesClosingItem(loadItemById,target.duplicate_of,closingId))throw new PmCliError(`Circular duplicate reference detected. Target "${rawTarget}" points back to "${closingId}".`,EXIT_CODE.USAGE,{code:"duplicate_target_circular",why:"Circular duplicate relationships create loops for dedupe and status propagation tooling.",nextSteps:["Choose the existing canonical item, or clear the target duplicate_of metadata before closing this item as a duplicate."]});if(typeof target.duplicate_of=="string"&&target.duplicate_of.trim().length>0)throw new PmCliError(`Duplicate target "${rawTarget}" is already marked as a duplicate of "${target.duplicate_of.trim()}".`,EXIT_CODE.USAGE,{code:"duplicate_target_is_duplicate",why:"Duplicate closure should point directly at the canonical item, not another duplicate.",examples:[`pm close ${closingId} "Duplicate of ${target.duplicate_of.trim()}" --duplicate-of ${target.duplicate_of.trim()}`],nextSteps:["Use the canonical item referenced by duplicate_of as the --duplicate-of target."]});return target.id}function findActiveChildIds(items,parentId,statusRegistry){return items.filter(item=>item.parent===parentId&&!isTerminalStatus(item.status,statusRegistry)).map(item=>item.id).sort((left,right)=>left.localeCompare(right))}function findAutoUnblockCandidates(items,closedId,statusRegistry){const itemsById=new Map(items.map(item=>[item.id.trim().toLowerCase(),item])),blockerIdsByItem=indexBlockedByIds(items),normalizedClosedId=closedId.trim().toLowerCase(),blockedStatuses=statusRegistry.blocked_statuses;return items.filter(item=>blockedStatuses.has(item.status)).map(item=>({item,blockerIds:blockerIdsByItem.get(item.id.trim().toLowerCase())})).filter(({blockerIds})=>blockerIds.includes(normalizedClosedId)).filter(({blockerIds})=>blockerIds.every(blockerId=>{if(blockerId===normalizedClosedId)return!0;const blocker=itemsById.get(blockerId);return blocker!==void 0&&isTerminalStatus(blocker.status,statusRegistry)})).map(({item,blockerIds})=>({id:item.id,blocker_ids:blockerIds})).sort((left,right)=>left.id.localeCompare(right.id))}async function autoUnblockResolvedDependents(pmRoot,settings,items,closedId,author,statusRegistry){const candidates=findAutoUnblockCandidates(items,closedId,statusRegistry),warnings=[];for(const candidate of candidates)try{const unblocked=await mutateItem({pmRoot,settings,id:candidate.id,op:"update",author,message:`Auto-unblocked after blocker ${closedId} closed`,mutate(document){const dependencies=document.metadata.dependencies??[],remainingDependencies=dependencies.filter(dependency=>{const dependencyId=dependency.id.trim().toLowerCase();return dependency.kind!=="blocked_by"||!candidate.blocker_ids.includes(dependencyId)});document.metadata.status=statusRegistry.open_status,delete document.metadata.blocked_by,delete document.metadata.blocked_reason,document.metadata.unblock_note=`Auto-unblocked after blocker ${closedId} closed; all blockers resolved (${candidate.blocker_ids.join(", ")}).`,remainingDependencies.length>0?document.metadata.dependencies=remainingDependencies:delete document.metadata.dependencies;const changedFields=["status","blocked_by","blocked_reason","unblock_note"];return remainingDependencies.length!==dependencies.length&&changedFields.push("dependencies"),{changedFields}}});warnings.push(`auto_unblocked:${unblocked.item.id}:resolved_blockers=${candidate.blocker_ids.join(",")}`)}catch(error){const reason=error instanceof Error?error.message.replace(/\s+/g," ").trim():"unknown";warnings.push(`auto_unblock_failed:${candidate.id}:${reason}`)}return warnings}function applyInlineCloseFields(metadata,options){const changedFields=[],inlineCloseFields=[{option:options.resolution,key:"resolution"},{option:options.expectedResult,key:"expected_result"},{option:options.actualResult,key:"actual_result"}];for(const{option,key}of inlineCloseFields){if(typeof option!="string")continue;const trimmed=option.trim();trimmed.length!==0&&(metadata[key]=trimmed,changedFields.push(key))}return changedFields}function applyDuplicateCloseMetadata(metadata,duplicateOf){if(duplicateOf===void 0)return[];metadata.duplicate_of=duplicateOf;const duplicateFallbackFields=[],fallbackValues=[{key:"resolution",value:`Duplicate of ${duplicateOf}`},{key:"expected_result",value:`Canonical item ${duplicateOf} tracks the work.`},{key:"actual_result",value:`Closed as duplicate of ${duplicateOf}.`}];for(const{key,value}of fallbackValues)shouldApplyDuplicateFallback(metadata[key])&&(metadata[key]=value,duplicateFallbackFields.push(key));return duplicateFallbackFields}function collectCloseValidationWarnings(metadata,validateCloseMode,activeChildIds,pmRoot){const warnings=[];if(validateCloseMode!=="off"){const missingFields=findMissingCloseValidationFields(metadata);if(missingFields.length>0){const missingLabels=missingFields.map(field=>field.label);if(validateCloseMode==="strict"){const missingFlags=missingFields.map(field=>`--${field.key.replaceAll("_","-")}`),updateTokens=["--pm-path",pmRoot,"update",metadata.id,...missingFlags.flatMap(flag=>[flag,"<value>"])];throw new PmCliError(`Cannot close item ${metadata.id}: missing ${missingLabels.join(", ")}. Populate fields or use --validate-close warn.`,EXIT_CODE.USAGE,{code:"close_validation_missing_fields",recovery:{missing:missingFlags,suggested_retry:renderPmCommand(updateTokens)},nextSteps:["Run the suggested pm update, then retry the original pm close invocation.","Use --validate-close warn only when governance permits an evidenced exception."]})}warnings.push(`close_validation_missing_fields:${metadata.id}:${missingLabels.join(",")}`)}if(activeChildIds.length>0){if(validateCloseMode==="strict")throw new PmCliError(`Cannot close item ${metadata.id}: active child items remain open (${activeChildIds.join(", ")}). Close, cancel, or re-parent them first, or use --validate-close warn.`,EXIT_CODE.USAGE);warnings.push(`close_validation_active_children:${metadata.id}:${activeChildIds.join(",")}`)}return warnings}return activeChildIds.length>0&&warnings.push(`closed_with_active_children:${metadata.id}:${activeChildIds.join(",")}`),warnings}function applyCloseReason(metadata,closeReason){return closeReason!==void 0?(metadata.close_reason=closeReason,["close_reason"]):[]}function clearCloseAssignee(metadata){return metadata.assignee===void 0?[]:(delete metadata.assignee,["assignee"])}function mutateCloseMetadata(metadata,context){if(isTerminalStatus(metadata.status,context.statusRegistry)&&!context.force)throw new PmCliError(`Item ${metadata.id} is already terminal; use pm update to append acceptance evidence, or --force only for an intentional repeat closure.`,EXIT_CODE.CONFLICT,{recovery:closeEvidenceRecovery(metadata,context.pmRoot,context.options)});const inlineChangedFields=applyInlineCloseFields(metadata,context.options),duplicateFallbackFields=applyDuplicateCloseMetadata(metadata,context.duplicateOf),mutationWarnings=collectCloseValidationWarnings(metadata,context.validateCloseMode,context.activeChildIds,context.pmRoot);metadata.status=context.statusRegistry.close_status,metadata.closed_at=context.closedAt;const shouldWriteCompletedAt=context.options.completedAt!==void 0||metadata.completed_at===void 0;shouldWriteCompletedAt&&(metadata.completed_at=context.completedAt);const changedFields=["status","closed_at",...shouldWriteCompletedAt?["completed_at"]:[],...applyCloseReason(metadata,context.closeReason),...inlineChangedFields];if(shouldCompletePlanOnClose(metadata)&&(metadata.plan_mode="completed",changedFields.push("plan_mode")),context.duplicateOf!==void 0){changedFields.push("duplicate_of");for(const key of duplicateFallbackFields)changedFields.includes(key)||changedFields.push(key)}changedFields.push(...clearCloseAssignee(metadata));const blockerCleanup=applyTerminalOrderingPolicy(metadata,context.lifecyclePolicy);return changedFields.push(...blockerCleanup.changedFields),mutationWarnings.push(...blockerCleanup.warnings),{changedFields,...mutationWarnings.length>0?{warnings:mutationWarnings}:{}}}const _testOnlyCloseCommand={blockedByIds:collectBlockedByIds};async function closeItem(id,closeReasonText,options,global2){const pmRoot=resolvePmRoot(process.cwd(),global2.path);await assertInitializedTracker(pmRoot);const settings=await readSettings(pmRoot),statusRegistry=resolveRuntimeStatusRegistry(settings.schema),author=resolveAuthor(options.author,settings.author_default),typeToFolder=resolveItemTypeRegistry(settings,getActiveExtensionRegistrations()).type_to_folder,located=await locateItem(pmRoot,id,settings.id_prefix,settings.item_format,typeToFolder);if(!located)throw await buildItemNotFoundError(pmRoot,id,settings.id_prefix,typeToFolder);const duplicateOf=await assertDuplicateTargetExists(pmRoot,settings,options.duplicateOf,located.id),lifecyclePolicy={requireCloseReason:options.lifecyclePolicy?.requireCloseReason??settings.governance.require_close_reason,orderingEdges:options.lifecyclePolicy?.orderingEdges??"preserve"},closeReason=requireTerminalReason({explicit:closeReasonText,duplicateOf,resolution:options.resolution,message:options.message},lifecyclePolicy.requireCloseReason).closeReason,validateCloseMode=parseValidateCloseMode(options.validateClose)??settings.governance.close_validation_default,trackerItems=await listAllItemMetadataLight(pmRoot,settings.item_format,typeToFolder,void 0,settings.schema),activeChildIds=findActiveChildIds(trackerItems,located.id,statusRegistry),closedAt=nowIso(),completedAt=options.completedAt===void 0?closedAt:resolveIsoOrRelative(options.completedAt,new Date(closedAt),"completed-at"),result=await mutateItem({pmRoot,settings,id:located.id,op:"close",author,message:options.message,force:options.force,mutate(document){return mutateCloseMetadata(document.metadata,{pmRoot,statusRegistry,force:options.force,options,duplicateOf,validateCloseMode,activeChildIds,closeReason,closedAt,completedAt,lifecyclePolicy})}}),evidence=validateCloseMode==="warn"?closeEvidenceRecovery(result.item,pmRoot):void 0;return{item:toItemRecord(result.item),changed_fields:result.changedFields,...evidence?{follow_up:{kind:"complete_close_evidence",missing:evidence.missing,suggested_command:evidence.suggested_retry,suggested_command_args:evidence.suggested_retry_args}}:{},warnings:[...result.warnings,...await autoUnblockResolvedDependents(pmRoot,settings,trackerItems,located.id,author,statusRegistry)]}}function runClose(id,closeReasonText,options,global2){return closeItem(id,closeReasonText,options,global2)}export{_testOnlyCloseCommand,closeItem,runClose};
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]="6237fa26-bb0a-5239-b52d-a66b7cfe4f7f")}catch{}})();import{assertInitializedTracker}from"../environment/tracker-preflight.js";import{toItemRecord,isTerminalStatus,resolveItemTypeRegistry,resolveRuntimeStatusRegistry,EXIT_CODE,PmCliError,buildItemNotFoundError,listAllItemMetadataLight,locateItem,readLocatedItem,mutateItem,resolvePmRoot,readSettings,resolveAuthor,resolveIsoOrRelative,shouldCompletePlanOnClose,getActiveExtensionRegistrations}from"../runtime-primitives.js";import{collectBlockedByIds,indexBlockedByIds}from"../actionability.js";import{applyTerminalOrderingPolicy,requireTerminalReason}from"../lifecycle-policy.js";import{nowIso}from"../../core/shared/time.js";import{renderPmCommand}from"../command-line.js";import{mutateItemWithHistoryContextResolver}from"../../core/store/item-store.js";const CLOSE_VALIDATION_FIELDS=[{key:"resolution",label:"resolution (--resolution)"},{key:"expected_result",label:"expected_result (--expected-result)"},{key:"actual_result",label:"actual_result (--actual-result)"}];function parseValidateCloseMode(raw){if(raw===void 0)return;const normalized=raw.trim().toLowerCase();if(normalized.length===0||normalized==="warn")return"warn";if(normalized==="off"||normalized==="none"||normalized==="disabled")return"off";if(normalized==="strict")return"strict";throw new PmCliError(`Invalid --validate-close mode "${raw}" (expected "off", "warn", or "strict")`,EXIT_CODE.USAGE)}function findMissingCloseValidationFields(itemMetadata){const missing=[];for(const field of CLOSE_VALIDATION_FIELDS){const rawValue=itemMetadata[field.key];(typeof rawValue!="string"||rawValue.trim().length===0)&&missing.push(field)}return missing}function closeEvidenceRecovery(metadata,pmRoot,options={}){const missing=findMissingCloseValidationFields(metadata);if(missing.length===0)return;const values={resolution:options.resolution,expected_result:options.expectedResult,actual_result:options.actualResult},missingFlags=missing.map(field=>`--${field.key.replaceAll("_","-")}`),args=["--pm-path",pmRoot,"update",metadata.id,...missing.flatMap((field,index)=>[missingFlags[index],values[field.key]?.trim()||"<value>"])];return{missing:missingFlags,suggested_retry:renderPmCommand(args),suggested_retry_args:args}}async function duplicateChainReferencesClosingItem(loadItemById,initialDuplicateOf,closingId){const visited=new Set;let current=initialDuplicateOf;for(;typeof current=="string"&&current.trim().length>0;){const currentId=current.trim();if(currentId===closingId)return!0;if(visited.has(currentId))return!1;visited.add(currentId),current=(await loadItemById(currentId))?.duplicate_of}return!1}function shouldApplyDuplicateFallback(value){return typeof value!="string"||value.trim().length===0}async function assertDuplicateTargetExists(pmRoot,settings,duplicateOf,closingId){const rawTarget=duplicateOf?.trim();if(!rawTarget)return;if(rawTarget===closingId)throw new PmCliError("An item cannot be closed as a duplicate of itself.",EXIT_CODE.USAGE,{code:"duplicate_target_self",why:"--duplicate-of must identify the canonical item that should remain open or already represent the work."});const typeRegistry=resolveItemTypeRegistry(settings,getActiveExtensionRegistrations()),itemCache=new Map,loadItemById=async id=>{if(itemCache.has(id))return itemCache.get(id)??null;const located=await locateItem(pmRoot,id,settings.id_prefix,settings.item_format,typeRegistry.type_to_folder);if(!located)return itemCache.set(id,null),null;const{document}=await readLocatedItem(located,{schema:settings.schema});return itemCache.set(id,document.metadata),document.metadata},target=await loadItemById(rawTarget);if(!target)throw new PmCliError(`Duplicate target "${rawTarget}" was not found. Create or locate the canonical item first.`,EXIT_CODE.USAGE,{code:"duplicate_target_missing",why:"Duplicate closure should point at a real canonical pm item so future dedupe and changelog tooling can trace the relationship.",examples:[`pm close ${closingId} "Duplicate of ${rawTarget}" --duplicate-of ${rawTarget}`],nextSteps:["Run pm search/list to find the canonical item, then retry with --duplicate-of <id>."]});if(await duplicateChainReferencesClosingItem(loadItemById,target.duplicate_of,closingId))throw new PmCliError(`Circular duplicate reference detected. Target "${rawTarget}" points back to "${closingId}".`,EXIT_CODE.USAGE,{code:"duplicate_target_circular",why:"Circular duplicate relationships create loops for dedupe and status propagation tooling.",nextSteps:["Choose the existing canonical item, or clear the target duplicate_of metadata before closing this item as a duplicate."]});if(typeof target.duplicate_of=="string"&&target.duplicate_of.trim().length>0)throw new PmCliError(`Duplicate target "${rawTarget}" is already marked as a duplicate of "${target.duplicate_of.trim()}".`,EXIT_CODE.USAGE,{code:"duplicate_target_is_duplicate",why:"Duplicate closure should point directly at the canonical item, not another duplicate.",examples:[`pm close ${closingId} "Duplicate of ${target.duplicate_of.trim()}" --duplicate-of ${target.duplicate_of.trim()}`],nextSteps:["Use the canonical item referenced by duplicate_of as the --duplicate-of target."]});return target.id}function findActiveChildIds(items,parentId,statusRegistry){return items.filter(item=>item.parent===parentId&&!isTerminalStatus(item.status,statusRegistry)).map(item=>item.id).sort((left,right)=>left.localeCompare(right))}function findAutoUnblockCandidates(items,closedId,statusRegistry){const itemsById=new Map(items.map(item=>[item.id.trim().toLowerCase(),item])),blockerIdsByItem=indexBlockedByIds(items),normalizedClosedId=closedId.trim().toLowerCase(),blockedStatuses=statusRegistry.blocked_statuses;return items.filter(item=>blockedStatuses.has(item.status)).map(item=>({item,blockerIds:blockerIdsByItem.get(item.id.trim().toLowerCase())})).filter(({blockerIds})=>blockerIds.includes(normalizedClosedId)).filter(({blockerIds})=>blockerIds.every(blockerId=>{if(blockerId===normalizedClosedId)return!0;const blocker=itemsById.get(blockerId);return blocker!==void 0&&isTerminalStatus(blocker.status,statusRegistry)})).map(({item,blockerIds})=>({id:item.id,blocker_ids:blockerIds})).sort((left,right)=>left.id.localeCompare(right.id))}async function autoUnblockResolvedDependents(pmRoot,settings,items,closedId,author,statusRegistry){const candidates=findAutoUnblockCandidates(items,closedId,statusRegistry),warnings=[];for(const candidate of candidates)try{const unblocked=await mutateItem({pmRoot,settings,id:candidate.id,op:"update",author,message:`Auto-unblocked after blocker ${closedId} closed`,mutate(document){const dependencies=document.metadata.dependencies??[],remainingDependencies=dependencies.filter(dependency=>{const dependencyId=dependency.id.trim().toLowerCase();return dependency.kind!=="blocked_by"||!candidate.blocker_ids.includes(dependencyId)});document.metadata.status=statusRegistry.open_status,delete document.metadata.blocked_by,delete document.metadata.blocked_reason,document.metadata.unblock_note=`Auto-unblocked after blocker ${closedId} closed; all blockers resolved (${candidate.blocker_ids.join(", ")}).`,remainingDependencies.length>0?document.metadata.dependencies=remainingDependencies:delete document.metadata.dependencies;const changedFields=["status","blocked_by","blocked_reason","unblock_note"];return remainingDependencies.length!==dependencies.length&&changedFields.push("dependencies"),{changedFields}}});warnings.push(`auto_unblocked:${unblocked.item.id}:resolved_blockers=${candidate.blocker_ids.join(",")}`)}catch(error){const reason=error instanceof Error?error.message.replace(/\s+/g," ").trim():"unknown";warnings.push(`auto_unblock_failed:${candidate.id}:${reason}`)}return warnings}function applyInlineCloseFields(metadata,options){const changedFields=[],inlineCloseFields=[{option:options.resolution,key:"resolution"},{option:options.expectedResult,key:"expected_result"},{option:options.actualResult,key:"actual_result"}];for(const{option,key}of inlineCloseFields){if(typeof option!="string")continue;const trimmed=option.trim();trimmed.length!==0&&(metadata[key]=trimmed,changedFields.push(key))}return changedFields}function applyDuplicateCloseMetadata(metadata,duplicateOf){if(duplicateOf===void 0)return[];metadata.duplicate_of=duplicateOf;const duplicateFallbackFields=[],fallbackValues=[{key:"resolution",value:`Duplicate of ${duplicateOf}`},{key:"expected_result",value:`Canonical item ${duplicateOf} tracks the work.`},{key:"actual_result",value:`Closed as duplicate of ${duplicateOf}.`}];for(const{key,value}of fallbackValues)shouldApplyDuplicateFallback(metadata[key])&&(metadata[key]=value,duplicateFallbackFields.push(key));return duplicateFallbackFields}function collectCloseValidationWarnings(metadata,validateCloseMode,activeChildIds,pmRoot){const warnings=[];if(validateCloseMode!=="off"){const missingFields=findMissingCloseValidationFields(metadata);if(missingFields.length>0){const missingLabels=missingFields.map(field=>field.label);if(validateCloseMode==="strict"){const missingFlags=missingFields.map(field=>`--${field.key.replaceAll("_","-")}`),updateTokens=["--pm-path",pmRoot,"update",metadata.id,...missingFlags.flatMap(flag=>[flag,"<value>"])];throw new PmCliError(`Cannot close item ${metadata.id}: missing ${missingLabels.join(", ")}. Populate fields or use --validate-close warn.`,EXIT_CODE.USAGE,{code:"close_validation_missing_fields",recovery:{missing:missingFlags,suggested_retry:renderPmCommand(updateTokens)},nextSteps:["Run the suggested pm update, then retry the original pm close invocation.","Use --validate-close warn only when governance permits an evidenced exception."]})}warnings.push(`close_validation_missing_fields:${metadata.id}:${missingLabels.join(",")}`)}if(activeChildIds.length>0){if(validateCloseMode==="strict")throw new PmCliError(`Cannot close item ${metadata.id}: active child items remain open (${activeChildIds.join(", ")}). Close, cancel, or re-parent them first, or use --validate-close warn.`,EXIT_CODE.USAGE);warnings.push(`close_validation_active_children:${metadata.id}:${activeChildIds.join(",")}`)}return warnings}return activeChildIds.length>0&&warnings.push(`closed_with_active_children:${metadata.id}:${activeChildIds.join(",")}`),warnings}function applyCloseReason(metadata,closeReason){return closeReason!==void 0?(metadata.close_reason=closeReason,["close_reason"]):[]}function clearCloseAssignee(metadata){return metadata.assignee===void 0?[]:(delete metadata.assignee,["assignee"])}function mutateCloseMetadata(metadata,context){if(isTerminalStatus(metadata.status,context.statusRegistry)&&!context.force)throw new PmCliError(`Item ${metadata.id} is already terminal; use pm update to append acceptance evidence, or --force only for an intentional repeat closure.`,EXIT_CODE.CONFLICT,{recovery:closeEvidenceRecovery(metadata,context.pmRoot,context.options)});const inlineChangedFields=applyInlineCloseFields(metadata,context.options),duplicateFallbackFields=applyDuplicateCloseMetadata(metadata,context.duplicateOf),mutationWarnings=collectCloseValidationWarnings(metadata,context.validateCloseMode,context.activeChildIds,context.pmRoot);metadata.status=context.statusRegistry.close_status,metadata.closed_at=context.closedAt;const shouldWriteCompletedAt=context.options.completedAt!==void 0||metadata.completed_at===void 0;shouldWriteCompletedAt&&(metadata.completed_at=context.completedAt);const changedFields=["status","closed_at",...shouldWriteCompletedAt?["completed_at"]:[],...applyCloseReason(metadata,context.closeReason),...inlineChangedFields];if(shouldCompletePlanOnClose(metadata)&&(metadata.plan_mode="completed",changedFields.push("plan_mode")),context.duplicateOf!==void 0){changedFields.push("duplicate_of");for(const key of duplicateFallbackFields)changedFields.includes(key)||changedFields.push(key)}changedFields.push(...clearCloseAssignee(metadata));const blockerCleanup=applyTerminalOrderingPolicy(metadata,context.lifecyclePolicy);return changedFields.push(...blockerCleanup.changedFields),mutationWarnings.push(...blockerCleanup.warnings),{changedFields,...mutationWarnings.length>0?{warnings:mutationWarnings}:{}}}const _testOnlyCloseCommand={blockedByIds:collectBlockedByIds};async function closeItem(id,closeReasonText,options,global2){const pmRoot=resolvePmRoot(process.cwd(),global2.path);await assertInitializedTracker(pmRoot);const settings=await readSettings(pmRoot),statusRegistry=resolveRuntimeStatusRegistry(settings.schema),author=resolveAuthor(options.author,settings.author_default),typeToFolder=resolveItemTypeRegistry(settings,getActiveExtensionRegistrations()).type_to_folder,located=await locateItem(pmRoot,id,settings.id_prefix,settings.item_format,typeToFolder);if(!located)throw await buildItemNotFoundError(pmRoot,id,settings.id_prefix,typeToFolder);const duplicateOf=await assertDuplicateTargetExists(pmRoot,settings,options.duplicateOf,located.id),lifecyclePolicy={requireCloseReason:options.lifecyclePolicy?.requireCloseReason??settings.governance.require_close_reason,orderingEdges:options.lifecyclePolicy?.orderingEdges??"preserve"},closeReason=requireTerminalReason({explicit:closeReasonText,duplicateOf,resolution:options.resolution,message:options.message},lifecyclePolicy.requireCloseReason).closeReason,validateCloseMode=parseValidateCloseMode(options.validateClose)??settings.governance.close_validation_default,trackerItems=await listAllItemMetadataLight(pmRoot,settings.item_format,typeToFolder,void 0,settings.schema),activeChildIds=findActiveChildIds(trackerItems,located.id,statusRegistry),closedAt=nowIso(),completedAt=options.completedAt===void 0?closedAt:resolveIsoOrRelative(options.completedAt,new Date(closedAt),"completed-at");let completion={timestamp:completedAt,source:options.completedAt===void 0?"close_time":"asserted",...options.completedAt===void 0?{hint:`completed_at defaults to the close time. If work finished earlier, correct it with ${renderPmCommand(["update",located.id,"--completed-at","<ISO>","--pm-path",pmRoot])}.`}:{}};const result=await mutateItemWithHistoryContextResolver({pmRoot,settings,id:located.id,op:"close",author,message:options.message,force:options.force,resolveHistoryContext:()=>({completion:{timestamp:completion.timestamp,source:completion.source}}),mutate(document){return options.completedAt===void 0&&document.metadata.completed_at!==void 0&&(completion={timestamp:document.metadata.completed_at,source:"existing"}),mutateCloseMetadata(document.metadata,{pmRoot,statusRegistry,force:options.force,options,duplicateOf,validateCloseMode,activeChildIds,closeReason,closedAt,completedAt,lifecyclePolicy})}}),evidence=validateCloseMode==="warn"?closeEvidenceRecovery(result.item,pmRoot):void 0;return{item:toItemRecord(result.item),changed_fields:result.changedFields,...evidence?{follow_up:{kind:"complete_close_evidence",missing:evidence.missing,suggested_command:evidence.suggested_retry,suggested_command_args:evidence.suggested_retry_args}}:{},warnings:[...result.warnings,...await autoUnblockResolvedDependents(pmRoot,settings,trackerItems,located.id,author,statusRegistry)],completion}}function runClose(id,closeReasonText,options,global2){return closeItem(id,closeReasonText,options,global2)}export{_testOnlyCloseCommand,closeItem,runClose};
2
2
  //# sourceMappingURL=close.js.map
@@ -39,6 +39,8 @@ createMode?:string;
39
39
  schedulePreset?:string;
40
40
  /** Explicitly permit a likely duplicate when strict governance is enabled. */
41
41
  allowDuplicate?:boolean;
42
+ /** Opt into saved session focus as parent when neither parent nor unset-parent is supplied. Defaults to false in SDK/MCP; the CLI enables it by default. */
43
+ inheritFocus?:boolean;
42
44
  [key:string]:unknown;
43
45
  }
44
46
  /** Presentation-neutral input accepted by the SDK-owned create operation. */