@unbrained/pm-cli 2026.10.18 → 2026.10.20

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 (186) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +43 -0
  3. package/dist/cli/error-guidance.d.ts +7 -0
  4. package/dist/cli/error-guidance.js +2 -2
  5. package/dist/cli/help-content.js +2 -2
  6. package/dist/cli/register-mutation.js +2 -2
  7. package/dist/cli/register-setup.js +1 -1
  8. package/dist/cli/register-structured-mutation.js +1 -1
  9. package/dist/cli-bundle/bundle-manifest.json +1 -1
  10. package/dist/cli-bundle/chunks/{chunk-PYRREEUB.js → chunk-2GGGOD7P.js} +28 -28
  11. package/dist/cli-bundle/chunks/chunk-7NHA5K77.js +126 -0
  12. package/dist/cli-bundle/chunks/chunk-7ZOIZ2AZ.js +8 -0
  13. package/dist/cli-bundle/chunks/{chunk-4GA4MUTE.js → chunk-GHJOMRSF.js} +2 -2
  14. package/dist/cli-bundle/chunks/chunk-KU4Q3MSI.js +8 -0
  15. package/dist/cli-bundle/chunks/chunk-LS55VKRK.js +3 -0
  16. package/dist/cli-bundle/chunks/chunk-PNPE2OVJ.js +112 -0
  17. package/dist/cli-bundle/chunks/chunk-PTL3TBT6.js +20 -0
  18. package/dist/cli-bundle/chunks/chunk-QETMESEB.js +6 -0
  19. package/dist/cli-bundle/chunks/{chunk-7WIEAGDS.js → chunk-QMZ73FCW.js} +2 -2
  20. package/dist/cli-bundle/chunks/chunk-QQYCB2LO.js +2 -0
  21. package/dist/cli-bundle/chunks/chunk-RUKSEZE7.js +2 -0
  22. package/dist/cli-bundle/chunks/chunk-TZU6U5ZE.js +3 -0
  23. package/dist/cli-bundle/chunks/chunk-VLJLWZBO.js +70 -0
  24. package/dist/cli-bundle/chunks/chunk-VS565KBX.js +2 -0
  25. package/dist/cli-bundle/chunks/chunk-XAWE4EAK.js +2 -0
  26. package/dist/cli-bundle/chunks/chunk-YECCKB4E.js +3 -0
  27. package/dist/cli-bundle/chunks/{chunk-FIJOSX2C.js → chunk-YT2Z3JPC.js} +72 -66
  28. package/dist/cli-bundle/chunks/chunk-YTHI5DTH.js +2 -0
  29. package/dist/cli-bundle/chunks/{chunk-6VZKYJW7.js → chunk-YW6SIKYP.js} +2 -2
  30. package/dist/cli-bundle/chunks/{chunk-ZZED447K.js → chunk-ZA3XXHEE.js} +6 -6
  31. package/dist/cli-bundle/chunks/context-budget-recovery-L2WP5S26.js +2 -0
  32. package/dist/cli-bundle/chunks/indexed-receipt-blobs-U7VGKIYC.js +4 -0
  33. package/dist/cli-bundle/chunks/{register-list-query-PUCVYYBD.js → register-list-query-NNDV6F6I.js} +3 -3
  34. package/dist/cli-bundle/chunks/register-mutation-3GCBSNQS.js +20 -0
  35. package/dist/cli-bundle/chunks/register-operations-YKDGNKUL.js +2 -0
  36. package/dist/cli-bundle/chunks/register-setup-AOUZQQWB.js +2 -0
  37. package/dist/cli-bundle/chunks/settlement-hook-CLRGA6O7.js +7 -0
  38. package/dist/cli-bundle/chunks/stage-6QXYRMSU.js +2 -0
  39. package/dist/cli-bundle/focused-chunks/chunk-37SU7BZI.js +3 -0
  40. package/dist/cli-bundle/focused-chunks/chunk-6IU7BESB.js +65 -0
  41. package/dist/cli-bundle/focused-chunks/chunk-AK2CI2GI.js +2 -0
  42. package/dist/cli-bundle/focused-chunks/chunk-AS3MOBO3.js +25 -0
  43. package/dist/cli-bundle/focused-chunks/chunk-AW35RDKA.js +78 -0
  44. package/dist/cli-bundle/focused-chunks/chunk-E4II2D2B.js +3 -0
  45. package/dist/cli-bundle/focused-chunks/{chunk-A4ZHHTEX.js → chunk-F7FD25TG.js} +12 -12
  46. package/dist/cli-bundle/focused-chunks/chunk-FOA7XKWP.js +2 -0
  47. package/dist/cli-bundle/focused-chunks/{chunk-TB7F4FCR.js → chunk-GYHZ7W6O.js} +2 -2
  48. package/dist/cli-bundle/focused-chunks/chunk-H5Q2HUU6.js +8 -0
  49. package/dist/cli-bundle/focused-chunks/chunk-HUZAXSIJ.js +3 -0
  50. package/dist/cli-bundle/focused-chunks/chunk-J6EYUGAR.js +6 -0
  51. package/dist/cli-bundle/focused-chunks/chunk-JEXZEIAX.js +91 -0
  52. package/dist/cli-bundle/focused-chunks/{chunk-BFEMT5U3.js → chunk-KDGUIIRU.js} +3 -3
  53. package/dist/cli-bundle/focused-chunks/{chunk-ZDBS2DCT.js → chunk-KKRFSI2N.js} +2 -2
  54. package/dist/cli-bundle/focused-chunks/chunk-LJPUJ2KK.js +2 -0
  55. package/dist/cli-bundle/focused-chunks/chunk-MQ42BFGX.js +2 -0
  56. package/dist/cli-bundle/focused-chunks/{chunk-IRVL4UXR.js → chunk-NI6NS44Q.js} +2 -2
  57. package/dist/cli-bundle/focused-chunks/chunk-VAYS754X.js +2 -0
  58. package/dist/cli-bundle/focused-chunks/{chunk-Z7YIFCNE.js → chunk-VLSER3XW.js} +2 -2
  59. package/dist/cli-bundle/focused-chunks/chunk-VS6VTWTZ.js +8 -0
  60. package/dist/cli-bundle/focused-chunks/chunk-VVRXF4JO.js +2 -0
  61. package/dist/cli-bundle/focused-chunks/chunk-ZGEK57KD.js +2 -0
  62. package/dist/cli-bundle/focused-chunks/context-budget-recovery-5MKOCGY5.js +2 -0
  63. package/dist/cli-bundle/focused-chunks/indexed-receipt-blobs-H2G6CCGO.js +4 -0
  64. package/dist/cli-bundle/focused-chunks/settlement-hook-5SNBBT7C.js +7 -0
  65. package/dist/cli-bundle/focused-chunks/stage-EMTISXR7.js +2 -0
  66. package/dist/cli-bundle/main.js +21 -21
  67. package/dist/cli-bundle/sdk-authoring.js +1 -1
  68. package/dist/cli-bundle/sdk-contracts.js +2 -2
  69. package/dist/cli-bundle/sdk-core.js +28 -22
  70. package/dist/cli-bundle/sdk-governance.js +1 -1
  71. package/dist/cli-bundle/sdk-graph.js +1 -1
  72. package/dist/cli-bundle/sdk-merge.js +66 -53
  73. package/dist/cli-bundle/sdk-query.js +1 -1
  74. package/dist/cli-bundle/sdk-runtime.js +1 -1
  75. package/dist/cli-bundle/sdk-testing.js +1 -1
  76. package/dist/cli-bundle/sdk.js +8 -8
  77. package/dist/core/fs/fs-utils.d.ts +2 -2
  78. package/dist/core/fs/fs-utils.js +1 -1
  79. package/dist/core/history/event-index.js +2 -2
  80. package/dist/core/output/output.d.ts +7 -2
  81. package/dist/core/output/output.js +5 -5
  82. package/dist/core/shared/errors.d.ts +48 -0
  83. package/dist/core/shared/errors.js +1 -1
  84. package/dist/core/store/settings.js +2 -2
  85. package/dist/mcp/server.js +1 -1
  86. package/dist/sdk/cli-contracts/bootstrap-command-scanner.js +1 -1
  87. package/dist/sdk/cli-contracts/flag-contracts.js +1 -1
  88. package/dist/sdk/cli-contracts/flag-lexicon-contracts.js +1 -1
  89. package/dist/sdk/cli-contracts/runtime-contracts.d.ts +1 -0
  90. package/dist/sdk/cli-contracts/runtime-contracts.js +1 -1
  91. package/dist/sdk/cli-contracts/tool-parameter-tables.js +1 -1
  92. package/dist/sdk/cli-contracts/tool-schema.js +1 -1
  93. package/dist/sdk/core.d.ts +1 -1
  94. package/dist/sdk/core.js +1 -1
  95. package/dist/sdk/dependencies/context-budget-recovery.d.ts +21 -0
  96. package/dist/sdk/dependencies/context-budget-recovery.js +2 -0
  97. package/dist/sdk/dependencies.js +1 -1
  98. package/dist/sdk/errors.d.ts +4 -1
  99. package/dist/sdk/errors.js +1 -1
  100. package/dist/sdk/extension/install-sources.js +3 -3
  101. package/dist/sdk/extension/shared.js +1 -1
  102. package/dist/sdk/generated/generated-error-code-catalog-part-1.js +1 -1
  103. package/dist/sdk/generated/generated-error-code-catalog-part-2.js +1 -1
  104. package/dist/sdk/governance/health.js +1 -1
  105. package/dist/sdk/history-repair.js +1 -1
  106. package/dist/sdk/index.d.ts +1 -1
  107. package/dist/sdk/index.js +1 -1
  108. package/dist/sdk/item-transaction.d.ts +13 -0
  109. package/dist/sdk/item-transaction.js +1 -1
  110. package/dist/sdk/lifecycle/structured-mutation-plan.d.ts +14 -1
  111. package/dist/sdk/lifecycle/structured-mutation-plan.js +1 -1
  112. package/dist/sdk/merge/driver.js +7 -1
  113. package/dist/sdk/merge/indexed-receipt-blobs.d.ts +2 -0
  114. package/dist/sdk/merge/indexed-receipt-blobs.js +4 -0
  115. package/dist/sdk/merge/install.d.ts +8 -0
  116. package/dist/sdk/merge/install.js +3 -3
  117. package/dist/sdk/merge/receipts.d.ts +6 -0
  118. package/dist/sdk/merge/receipts.js +2 -2
  119. package/dist/sdk/merge/reconcile.d.ts +6 -0
  120. package/dist/sdk/merge/reconcile.js +1 -1
  121. package/dist/sdk/merge/settlement-hook.d.ts +12 -0
  122. package/dist/sdk/merge/settlement-hook.js +7 -0
  123. package/dist/sdk/merge/stage.d.ts +13 -0
  124. package/dist/sdk/merge/stage.js +2 -0
  125. package/dist/sdk/output/list-envelope.d.ts +7 -0
  126. package/dist/sdk/output/list-envelope.js +2 -0
  127. package/dist/sdk/output.d.ts +1 -0
  128. package/dist/sdk/output.js +2 -2
  129. package/dist/sdk/public-surface.json +1 -1
  130. package/dist/sdk/query/list.js +1 -1
  131. package/dist/sdk/relationship-context.d.ts +2 -0
  132. package/dist/sdk/relationship-context.js +1 -1
  133. package/dist/sdk/runtime-extended-actions.js +1 -1
  134. package/dist/sdk/structured-mutations.d.ts +2 -0
  135. package/dist/sdk/structured-mutations.js +1 -1
  136. package/docs/COMMANDS.md +27 -0
  137. package/docs/CONFIGURATION.md +14 -0
  138. package/docs/MERGE_SAFETY.md +88 -5
  139. package/docs/PR_REVIEW_LOOP.md +32 -12
  140. package/docs/SCRIPTING.md +10 -0
  141. package/docs/SDK.md +103 -0
  142. package/docs/TESTING.md +32 -1
  143. package/docs/generated/FLAG_LEXICON_BUDGETS.md +2 -2
  144. package/docs/generated/REFUSAL_CLOSURE_CENSUS.md +6 -3
  145. package/marketplace.json +1 -1
  146. package/package.json +1 -1
  147. package/packages/pm-beads/package.json +1 -1
  148. package/packages/pm-calendar/package.json +1 -1
  149. package/packages/pm-command-kit/package.json +1 -1
  150. package/packages/pm-digital-twin/package.json +1 -1
  151. package/packages/pm-governance-audit/package.json +1 -1
  152. package/packages/pm-guide-shell/package.json +1 -1
  153. package/packages/pm-kanban/package.json +1 -1
  154. package/packages/pm-lifecycle-hooks/package.json +1 -1
  155. package/packages/pm-linked-test-adapters/package.json +1 -1
  156. package/packages/pm-search-advanced/package.json +1 -1
  157. package/packages/pm-templates/package.json +1 -1
  158. package/packages/pm-todos/package.json +1 -1
  159. package/packages/pm-vcs/package.json +1 -1
  160. package/plugins/pm-claude/.claude-plugin/plugin.json +1 -1
  161. package/plugins/pm-claude/package.json +1 -1
  162. package/plugins/pm-codex/.codex-plugin/plugin.json +1 -1
  163. package/plugins/pm-codex/package.json +1 -1
  164. package/plugins/pm-codex/plugin.json +1 -1
  165. package/runtime-dependencies.json +1 -1
  166. package/dist/cli-bundle/chunks/chunk-4V6ONLI6.js +0 -3
  167. package/dist/cli-bundle/chunks/chunk-7PATQWTH.js +0 -20
  168. package/dist/cli-bundle/chunks/chunk-AETP4JEM.js +0 -3
  169. package/dist/cli-bundle/chunks/chunk-LUKVNL72.js +0 -2
  170. package/dist/cli-bundle/chunks/chunk-OJ77ZQN3.js +0 -8
  171. package/dist/cli-bundle/chunks/chunk-RHQJ6VWC.js +0 -237
  172. package/dist/cli-bundle/chunks/chunk-RNABUEFB.js +0 -80
  173. package/dist/cli-bundle/chunks/chunk-TO6CIV4C.js +0 -2
  174. package/dist/cli-bundle/chunks/register-mutation-GGGVBXD5.js +0 -20
  175. package/dist/cli-bundle/chunks/register-operations-NRZ6Q4QE.js +0 -2
  176. package/dist/cli-bundle/chunks/register-setup-MV3P3YSF.js +0 -2
  177. package/dist/cli-bundle/focused-chunks/chunk-2MPQUMTF.js +0 -2
  178. package/dist/cli-bundle/focused-chunks/chunk-5UKCSZWT.js +0 -2
  179. package/dist/cli-bundle/focused-chunks/chunk-B4YLP5ZB.js +0 -3
  180. package/dist/cli-bundle/focused-chunks/chunk-FW7CSSKL.js +0 -75
  181. package/dist/cli-bundle/focused-chunks/chunk-HYPRPSYD.js +0 -3
  182. package/dist/cli-bundle/focused-chunks/chunk-L55KQ2KT.js +0 -2
  183. package/dist/cli-bundle/focused-chunks/chunk-MASIOFPG.js +0 -8
  184. package/dist/cli-bundle/focused-chunks/chunk-RJ3EIHDU.js +0 -26
  185. package/dist/cli-bundle/focused-chunks/chunk-SEL4Z2OO.js +0 -167
  186. package/dist/cli-bundle/focused-chunks/chunk-TZUIBG3P.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]="03112259-10b6-53a9-8e6b-e541ac8c7d5d")}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 createdItem=(await create(mergeTransactionMutationOptions(mutation.options,{author:config.author,id:mutation.id}),{pmRoot:config.pmRoot})).item;return 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(file){const entry=await lstat(file);if(entry.isSymbolicLink())await stat(file);else if(entry.isDirectory()||entry.isFile())return entry;throw transactionPreviewNonRegularFileError()}async function previewFileDigest(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();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(file)).isDirectory()?await visit(file):(fingerprint.update(JSON.stringify(path.relative(root,file))),fingerprint.update(await previewFileDigest(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(current);if(!(await previewEntryStat(file)).isFile())throw transactionPreviewNonRegularFileError();return await readRegularFile(file,transactionPreviewNonRegularFileError())}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(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;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,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",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]="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};
2
2
  //# sourceMappingURL=item-transaction.js.map
@@ -1,5 +1,6 @@
1
+ import { type ItemMutationPreviewValidation } from "../item-transaction.js";
1
2
  import type { PmSettings } from "../../types/index.js";
2
- import { type ResolvedItemMutationDocument } from "../structured-mutations.js";
3
+ import { type ResolvedItemMutationDocument,type ResolvedItemMutationRow } from "../structured-mutations.js";
3
4
  /** Inputs for planning one mutation document against a workspace. */
4
5
  export interface PlanItemMutationDocumentOptions {
5
6
  /** Tracker root whose schema and items govern the plan. */
@@ -13,6 +14,18 @@ upsertKey?:string;
13
14
  /** Already-read settings, to avoid a second settings read on hot paths. */
14
15
  settings?:PmSettings;
15
16
  }
17
+ /** Preview a resolved document and join lifecycle warnings to its source row indexes and aliases. */
18
+ export declare function previewItemMutationDocument(options:{
19
+ /** Tracker whose state and policy the disposable preview validates. */
20
+ pmRoot:string;
21
+ /** Actor used only within the disposable staged tracker. */
22
+ author:string;
23
+ /** SDK-resolved document with its transaction identity and ordered rows. */
24
+ plan:PlannedItemMutationDocument;
25
+ }):Promise<{
26
+ validation:ItemMutationPreviewValidation;
27
+ rows:ResolvedItemMutationRow[];
28
+ }>;
16
29
  /** Resolved plan plus the transaction identity the caller must reuse to resume. */
17
30
  export interface PlannedItemMutationDocument extends ResolvedItemMutationDocument {
18
31
  /** Transaction id to pass to preview/commit; stable across replays of the same document. */
@@ -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]="3c45eeb2-58f2-58f9-ad24-e439967b99ef")}catch{}})();import{resolveRuntimeFieldRegistry,runtimeFieldOptionTarget}from"../../core/schema/runtime-schema.js";import{listUpsertKeyCandidates,resolveTransactionSchema}from"../item-transaction.js";import{readSettings}from"../../core/store/settings.js";import{resolveCoreUpsertMetadataKey}from"./core-item-fields.js";import{EXIT_CODE}from"../../core/shared/constants.js";import{PmCliError}from"../../core/shared/errors.js";import{deriveMutationTransactionId,resolveItemMutationDocument}from"../structured-mutations.js";async function planItemMutationDocument(options){const suppliedKey=options.upsertKey?.trim();if(suppliedKey!==void 0&&suppliedKey.length===0)throw new PmCliError("Upsert key must be a non-empty field name.",EXIT_CODE.USAGE);const settings=options.settings??await readSettings(options.pmRoot),explicit=options.transactionId?.trim()??"",transactionId=explicit.length>0?explicit:deriveMutationTransactionId(options.input),planSchema=await resolveTransactionSchema(options.pmRoot,settings),runtimeFields=resolveRuntimeFieldRegistry(planSchema.schema).definitions,upsertKey=(runtimeFields.find(field=>field.metadata_key===suppliedKey)??runtimeFields.find(field=>field.key===suppliedKey||runtimeFieldOptionTarget(field)===suppliedKey))?.metadata_key??resolveCoreUpsertMetadataKey(suppliedKey),upsert=upsertKey===void 0?void 0:{key:upsertKey,existing:await listUpsertKeyCandidates(options.pmRoot,settings,planSchema,upsertKey)};return{...resolveItemMutationDocument(options.input,{transactionId,idPrefix:settings.id_prefix,runtimeFields,...upsert===void 0?{}:{upsert}}),transaction_id:transactionId,transaction_id_source:explicit.length>0?"explicit":"derived",...upsert===void 0?{}:{upsert_key:upsert.key}}}export{planItemMutationDocument};
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]="3041c268-69c8-58e5-8a68-eaaa3c0dbd3b")}catch{}})();import{resolveRuntimeFieldRegistry,runtimeFieldOptionTarget}from"../../core/schema/runtime-schema.js";import{listUpsertKeyCandidates,previewItemMutations,resolveTransactionSchema}from"../item-transaction.js";import{readSettings}from"../../core/store/settings.js";import{resolveCoreUpsertMetadataKey}from"./core-item-fields.js";import{EXIT_CODE}from"../../core/shared/constants.js";import{PmCliError}from"../../core/shared/errors.js";import{deriveMutationTransactionId,resolveItemMutationDocument}from"../structured-mutations.js";async function previewItemMutationDocument(options){const{plan}=options,validation=await previewItemMutations({pmRoot:options.pmRoot,author:options.author,transactionId:plan.transaction_id,mutations:plan.mutations}),warnings=new Map(validation.warnings.map(entry=>[entry.index,entry.warnings])),rows=plan.rows.map(row=>{const projected={...row};delete projected.warnings;const messages=warnings.get(row.index);return messages!==void 0&&(projected.warnings=messages),projected});return{validation,rows}}async function planItemMutationDocument(options){const suppliedKey=options.upsertKey?.trim();if(suppliedKey!==void 0&&suppliedKey.length===0)throw new PmCliError("Upsert key must be a non-empty field name.",EXIT_CODE.USAGE);const settings=options.settings??await readSettings(options.pmRoot),explicit=options.transactionId?.trim()??"",transactionId=explicit.length>0?explicit:deriveMutationTransactionId(options.input),planSchema=await resolveTransactionSchema(options.pmRoot,settings),runtimeFields=resolveRuntimeFieldRegistry(planSchema.schema).definitions,upsertKey=(runtimeFields.find(field=>field.metadata_key===suppliedKey)??runtimeFields.find(field=>field.key===suppliedKey||runtimeFieldOptionTarget(field)===suppliedKey))?.metadata_key??resolveCoreUpsertMetadataKey(suppliedKey),upsert=upsertKey===void 0?void 0:{key:upsertKey,existing:await listUpsertKeyCandidates(options.pmRoot,settings,planSchema,upsertKey)};return{...resolveItemMutationDocument(options.input,{transactionId,idPrefix:settings.id_prefix,runtimeFields,...upsert===void 0?{}:{upsert}}),transaction_id:transactionId,transaction_id_source:explicit.length>0?"explicit":"derived",...upsert===void 0?{}:{upsert_key:upsert.key}}}export{planItemMutationDocument,previewItemMutationDocument};
2
2
  //# sourceMappingURL=structured-mutation-plan.js.map
@@ -1,2 +1,8 @@
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]="932450d7-963b-566b-a5ad-1b577a895e8e")}catch{}})();import{readFile,writeFile}from"node:fs/promises";import path from"node:path";import{getActiveExtensionRegistrations}from"../../core/extensions/index.js";import{collectRegisteredItemFieldNames}from"../../core/extensions/item-fields.js";import{pathExists}from"../../core/fs/fs-utils.js";import{parseItemDocument}from"../../core/item/item-format.js";import{EXIT_CODE}from"../../core/shared/constants.js";import{PmCliError}from"../../core/shared/errors.js";import{nowIso}from"../../core/shared/time.js";import{getSettingsPath,resolvePmRoot}from"../../core/store/paths.js";import{readSettings}from"../../core/store/settings.js";import{hashItemScalarDecisionValue,mergeHistoryStreams,mergeItemDocuments,mergeJsonDocuments,mergeRelationshipEventStreams}from"./three-way.js";import{captureMergeReceiptOperation}from"./receipt-operation.js";import{summarizeMergeReceipt,writeMergeReceipt}from"./receipts.js";const MERGE_DRIVER_ARTIFACT_VALUES=["item","history","relationship","json"];function resolveArtifact(value){const normalized=value.trim().toLowerCase();if(MERGE_DRIVER_ARTIFACT_VALUES.includes(normalized))return normalized;throw new PmCliError(`Unknown merge artifact "${value}". Supported values: ${MERGE_DRIVER_ARTIFACT_VALUES.join(", ")}.`,EXIT_CODE.USAGE)}function resolvePreferredSide(value){if(value===void 0)return"ours";const normalized=value.trim().toLowerCase();if(normalized==="ours"||normalized==="theirs")return normalized;throw new PmCliError(`Unknown --prefer value "${value}". Supported values: ours, theirs.`,EXIT_CODE.USAGE)}async function readSideFile(filePath,label){try{return await readFile(filePath,"utf8")}catch{throw new PmCliError(`Cannot read merge ${label} file at ${filePath}.`,EXIT_CODE.NOT_FOUND)}}async function loadOptionalSettings(global2){const pmRoot=resolvePmRoot(process.cwd(),global2.path);return await pathExists(getSettingsPath(pmRoot))?readSettings(pmRoot):null}function itemFormatForPath(filePath,settings){return filePath.endsWith(".md")?"json_markdown":filePath.endsWith(".toon")?"toon":settings?.item_format??"toon"}async function runMergeDriver(options,global2){const artifact=resolveArtifact(options.artifact),preferred=resolvePreferredSide(options.prefer),outputPath=path.resolve(options.outputPath??options.oursPath),[baseRaw,oursRaw,theirsRaw]=await Promise.all([readSideFile(options.basePath,"base"),readSideFile(options.oursPath,"ours"),readSideFile(options.theirsPath,"theirs")]);let merged,receiptInput;const conflicts=[],result={};if(artifact==="history"){const historyMerge=mergeHistoryStreams(baseRaw,oursRaw,theirsRaw);merged=historyMerge.merged,result.history={strategy:historyMerge.strategy,common_entries:historyMerge.common_entries,entries_from_ours:historyMerge.entries_from_ours,entries_from_theirs:historyMerge.entries_from_theirs,entries_total:historyMerge.entries_total,reanchored:historyMerge.reanchored}}else if(artifact==="relationship"){const relationshipMerge=mergeRelationshipEventStreams(baseRaw,oursRaw,theirsRaw);merged=relationshipMerge.merged,result.relationship={strategy:relationshipMerge.strategy,common_entries:relationshipMerge.common_entries,entries_from_ours:relationshipMerge.entries_from_ours,entries_from_theirs:relationshipMerge.entries_from_theirs,entries_total:relationshipMerge.entries_total,reanchored:relationshipMerge.reanchored}}else if(artifact==="item"){const settings=await loadOptionalSettings(global2),itemFormat=itemFormatForPath(options.itemPath??outputPath,settings),extensionFieldNames=collectRegisteredItemFieldNames(getActiveExtensionRegistrations()),itemMerge=mergeItemDocuments(baseRaw,oursRaw,theirsRaw,{format:itemFormat,schema:settings?.schema,extensionFieldNames,preferred,conflictResolution:"latest_document_update"});if(merged=itemMerge.merged,conflicts.push(...itemMerge.conflict_fields),result.item={fields_from_theirs:itemMerge.fields_from_theirs,union_fields:itemMerge.union_fields,conflict_resolution:"latest_document_update",requested_preference_applied:!1},options.itemPath!==void 0){const mergedDocument=parseItemDocument(merged,{format:itemFormat,schema:settings?.schema,extensionFieldNames}),mergedMetadata=mergedDocument.metadata,receiptFields=[...new Set([...itemMerge.fields_from_theirs,...itemMerge.union_fields,...itemMerge.conflict_decisions.map(decision=>decision.field)])];receiptInput={cwd:process.cwd(),itemPath:options.itemPath,preferred,conflictResolution:"latest_document_update",fieldsFromTheirs:itemMerge.fields_from_theirs,unionFields:itemMerge.union_fields,mergedFieldHashes:Object.fromEntries(receiptFields.map(field=>[field,hashItemScalarDecisionValue(field==="body"?mergedDocument.body:mergedMetadata[field])])),decisions:itemMerge.conflict_decisions,operation:await captureMergeReceiptOperation(process.cwd(),options.itemPath,oursRaw)}}}else{const jsonMerge=mergeJsonDocuments(baseRaw,oursRaw,theirsRaw,{preferred});merged=jsonMerge.merged,conflicts.push(...jsonMerge.conflict_paths),result.json={paths_from_theirs:jsonMerge.paths_from_theirs}}if(await writeFile(outputPath,merged,"utf8"),receiptInput!==void 0){const receipt=await writeMergeReceipt(receiptInput);receipt!==null&&(result.receipt=summarizeMergeReceipt(receipt))}const guidance=[];if(conflicts.length>0){const receiptGuidance=result.receipt===void 0?'Run "pm merge report" to inspect clone-local merge evidence.':`Run "pm merge report" and correlate receipt ${result.receipt.receipt_id} with item ${result.receipt.item_id}; clone-local discarded values are never copied into this result.`;guidance.push(`Both branches changed ${conflicts.join(", ")}; the item driver kept the value from the later document update, with stable value order only as an equal-timestamp tie-break. --prefer does not apply to convergent item scalar conflicts. ${receiptGuidance} Review the merged file, re-apply the discarded change if needed, then "git add" it.`)}return artifact==="history"&&result.history?.reanchored&&guidance.push('The merged history chain was re-anchored. Run "pm merge reconcile --dry-run" to inspect the complete post-merge repair, then "pm merge reconcile" to apply it; use --force only after explicitly accepting reported discards.'),{ok:conflicts.length===0,artifact,output_path:outputPath,requested_preference:preferred,conflicts,...result,guidance,generated_at:nowIso()}}export{MERGE_DRIVER_ARTIFACT_VALUES,runMergeDriver};
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]="74f8a3ee-f0dc-568d-86d1-8c0b9649e2e2")}catch{}})();import{readFile,writeFile}from"node:fs/promises";import path from"node:path";import{getActiveExtensionRegistrations}from"../../core/extensions/index.js";import{collectRegisteredItemFieldNames}from"../../core/extensions/item-fields.js";import{pathExists}from"../../core/fs/fs-utils.js";import{parseItemDocument}from"../../core/item/item-format.js";import{EXIT_CODE}from"../../core/shared/constants.js";import{PmCliError}from"../../core/shared/errors.js";import{nowIso}from"../../core/shared/time.js";import{getSettingsPath,resolvePmRoot}from"../../core/store/paths.js";import{readSettings}from"../../core/store/settings.js";import{hashItemScalarDecisionValue,mergeHistoryStreams,mergeItemDocuments,mergeJsonDocuments,mergeRelationshipEventStreams}from"./three-way.js";import{captureMergeReceiptOperation}from"./receipt-operation.js";import{summarizeMergeReceipt,writeMergeReceipt}from"./receipts.js";const MERGE_DRIVER_ARTIFACT_VALUES=["item","history","relationship","json"];function resolveArtifact(value){const normalized=value.trim().toLowerCase();if(MERGE_DRIVER_ARTIFACT_VALUES.includes(normalized))return normalized;throw new PmCliError(`Unknown merge artifact "${value}". Supported values: ${MERGE_DRIVER_ARTIFACT_VALUES.join(", ")}.`,EXIT_CODE.USAGE)}function resolvePreferredSide(value){if(value===void 0)return"ours";const normalized=value.trim().toLowerCase();if(normalized==="ours"||normalized==="theirs")return normalized;throw new PmCliError(`Unknown --prefer value "${value}". Supported values: ours, theirs.`,EXIT_CODE.USAGE)}async function readSideFile(filePath,label){try{return await readFile(filePath,"utf8")}catch{throw new PmCliError(`Cannot read merge ${label} file at ${filePath}.`,EXIT_CODE.NOT_FOUND)}}async function loadOptionalSettings(global2){const pmRoot=resolvePmRoot(process.cwd(),global2.path);return await pathExists(getSettingsPath(pmRoot))?readSettings(pmRoot):null}function itemFormatForPath(filePath,settings){return filePath.endsWith(".md")?"json_markdown":filePath.endsWith(".toon")?"toon":settings?.item_format??"toon"}async function preserveIdentityRefusal(merge,sides){try{return merge()}catch(error){if(error instanceof PmCliError&&error.context?.code==="item_identity_conflict"){const line=raw=>raw.endsWith(`
2
+ `)?raw:`${raw}
3
+ `;throw await writeFile(sides.outputPath,`<<<<<<< pm ours
4
+ ${line(sides.oursRaw)}||||||| pm base
5
+ ${line(sides.baseRaw)}=======
6
+ ${line(sides.theirsRaw)}>>>>>>> pm theirs
7
+ `,"utf8"),new PmCliError(`Unresolved identity for ${sides.itemPath}: both snapshots remain in conflict markers. ${error.message}`,error.exitCode,{...error.context,recovery:{suggested_retry:"git status --short"},nextSteps:["Preserve both source snapshots and establish item identity before resolving. Do not stage conflict markers as a completed merge; pm health and storage validation reject them."]})}throw error}}async function runMergeDriver(options,global2){const artifact=resolveArtifact(options.artifact),preferred=resolvePreferredSide(options.prefer),outputPath=path.resolve(options.outputPath??options.oursPath),[baseRaw,oursRaw,theirsRaw]=await Promise.all([readSideFile(options.basePath,"base"),readSideFile(options.oursPath,"ours"),readSideFile(options.theirsPath,"theirs")]);let merged,receiptInput;const conflicts=[],result={},refusalSides={baseRaw,oursRaw,theirsRaw,outputPath,itemPath:options.itemPath??outputPath};if(artifact==="history"){const historyMerge=await preserveIdentityRefusal(()=>mergeHistoryStreams(baseRaw,oursRaw,theirsRaw),refusalSides);merged=historyMerge.merged,result.history={strategy:historyMerge.strategy,common_entries:historyMerge.common_entries,entries_from_ours:historyMerge.entries_from_ours,entries_from_theirs:historyMerge.entries_from_theirs,entries_total:historyMerge.entries_total,reanchored:historyMerge.reanchored}}else if(artifact==="relationship"){const relationshipMerge=mergeRelationshipEventStreams(baseRaw,oursRaw,theirsRaw);merged=relationshipMerge.merged,result.relationship={strategy:relationshipMerge.strategy,common_entries:relationshipMerge.common_entries,entries_from_ours:relationshipMerge.entries_from_ours,entries_from_theirs:relationshipMerge.entries_from_theirs,entries_total:relationshipMerge.entries_total,reanchored:relationshipMerge.reanchored}}else if(artifact==="item"){const settings=await loadOptionalSettings(global2),itemFormat=itemFormatForPath(options.itemPath??outputPath,settings),extensionFieldNames=collectRegisteredItemFieldNames(getActiveExtensionRegistrations()),itemMerge=await preserveIdentityRefusal(()=>mergeItemDocuments(baseRaw,oursRaw,theirsRaw,{format:itemFormat,schema:settings?.schema,extensionFieldNames,preferred,conflictResolution:"latest_document_update"}),refusalSides);if(merged=itemMerge.merged,conflicts.push(...itemMerge.conflict_fields),result.item={fields_from_theirs:itemMerge.fields_from_theirs,union_fields:itemMerge.union_fields,conflict_resolution:"latest_document_update",requested_preference_applied:!1},options.itemPath!==void 0){const mergedDocument=parseItemDocument(merged,{format:itemFormat,schema:settings?.schema,extensionFieldNames}),mergedMetadata=mergedDocument.metadata,receiptFields=[...new Set([...itemMerge.fields_from_theirs,...itemMerge.union_fields,...itemMerge.conflict_decisions.map(decision=>decision.field)])];receiptInput={cwd:process.cwd(),itemPath:options.itemPath,preferred,conflictResolution:"latest_document_update",fieldsFromTheirs:itemMerge.fields_from_theirs,unionFields:itemMerge.union_fields,mergedFieldHashes:Object.fromEntries(receiptFields.map(field=>[field,hashItemScalarDecisionValue(field==="body"?mergedDocument.body:mergedMetadata[field])])),decisions:itemMerge.conflict_decisions,operation:await captureMergeReceiptOperation(process.cwd(),options.itemPath,oursRaw)}}}else{const jsonMerge=mergeJsonDocuments(baseRaw,oursRaw,theirsRaw,{preferred});merged=jsonMerge.merged,conflicts.push(...jsonMerge.conflict_paths),result.json={paths_from_theirs:jsonMerge.paths_from_theirs}}if(await writeFile(outputPath,merged,"utf8"),receiptInput!==void 0){const receipt=await writeMergeReceipt(receiptInput);receipt!==null&&(result.receipt=summarizeMergeReceipt(receipt))}const guidance=[];if(conflicts.length>0){const receiptGuidance=result.receipt===void 0?'Run "pm merge report" to inspect clone-local merge evidence.':`Run "pm merge report" and correlate receipt ${result.receipt.receipt_id} with item ${result.receipt.item_id}; clone-local discarded values are never copied into this result.`;guidance.push(`Both branches changed ${conflicts.join(", ")}; the item driver kept the value from the later document update, with stable value order only as an equal-timestamp tie-break. --prefer does not apply to convergent item scalar conflicts. ${receiptGuidance} Review the merged file, re-apply the discarded change if needed, then "git add" it.`)}return artifact==="history"&&result.history?.reanchored&&guidance.push('The merged history chain was re-anchored. Run "pm merge reconcile --dry-run" to inspect the complete post-merge repair, then "pm merge reconcile" to apply it; use --force only after explicitly accepting reported discards.'),{ok:conflicts.length===0,artifact,output_path:outputPath,requested_preference:preferred,conflicts,...result,guidance,generated_at:nowIso()}}export{MERGE_DRIVER_ARTIFACT_VALUES,runMergeDriver};
2
8
  //# sourceMappingURL=driver.js.map
@@ -0,0 +1,2 @@
1
+ /** Resolve physical roots, list the index once, and batch only requested bounded blobs. */
2
+ export declare function readIndexedReceiptBlobs(cwd:string,directory:string,fileNames:string[],maximumBytes:number):Promise<Map<string,string>>;
@@ -0,0 +1,4 @@
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]="a29ee03b-9955-56c6-a72e-1e93182e5de8")}catch{}})();import{execFile}from"node:child_process";import{realpath}from"node:fs/promises";import path from"node:path";import{promisify}from"node:util";import{isPathOutsideRoot}from"../workspace.js";import{gitWorkspaceEnvironment}from"./worktree-config.js";const execFileAsync=promisify(execFile),OBJECT_ID="[a-f0-9]{40}(?:[a-f0-9]{24})?",INDEX_ENTRY=new RegExp(`^100(?:644|755) (${OBJECT_ID}) 0$`,"u"),BLOB_METADATA=new RegExp(`^(${OBJECT_ID}) blob (\\d+)$`,"u");async function gitBatch(args,ids,options){const pending=execFileAsync("git",args,{...options,encoding:"buffer"}),input=pending.child.stdin;return input!==null&&(input.on("error",()=>{pending.child.kill()}),input.end(`${ids.join(`
2
+ `)}
3
+ `)),(await pending).stdout}function indexedFiles(listing,requested){const files=new Map;for(const record of listing.split("\0")){const separator=record.indexOf(" "),file=record.slice(separator+1),metadata=INDEX_ENTRY.exec(record.slice(0,separator));metadata!==null&&requested.has(file)&&files.set(file,metadata[1])}return files}function blobBatches(metadata,requested,maximumBytes){const batches=[];let batch=[],bytes=0;for(const line of metadata.trimEnd().split(/\r?\n/u)){const match=BLOB_METADATA.exec(line);if(match===null||!requested.has(match[1]))continue;const size=Number(match[2]);!Number.isSafeInteger(size)||size>maximumBytes||(batch.length>0&&bytes+size+128>maximumBytes&&(batches.push(batch),batch=[],bytes=0),batch.push({id:match[1],size}),bytes+=size+128)}return batch.length>0&&batches.push(batch),batches}function decodeBlobs(output,entries,blobs){let offset=0;for(const{id,size}of entries){const start=output.indexOf(10,offset)+1;if(start===0||output.subarray(offset,start-1).toString("ascii")!==`${id} blob ${size}`||output[start+size]!==10)throw new Error("Invalid indexed receipt batch framing");blobs.set(id,output.subarray(start,start+size).toString("utf8")),offset=start+size+1}if(offset!==output.length)throw new Error("Unexpected indexed receipt batch data")}async function readIndexedReceiptBlobs(cwd,directory,fileNames,maximumBytes){const env=gitWorkspaceEnvironment();env.GIT_NO_REPLACE_OBJECTS="1",process.env.GIT_INDEX_FILE!==void 0&&(env.GIT_INDEX_FILE=path.resolve(process.env.GIT_INDEX_FILE));const options={cwd,env,windowsHide:!0,timeout:1e4,maxBuffer:maximumBytes+256};try{const root=(await execFileAsync("git",["rev-parse","--show-toplevel"],{...options,encoding:"utf8"})).stdout.trim(),[workspace,physicalDirectory]=await Promise.all([realpath(root),realpath(directory)]),relative=path.relative(workspace,physicalDirectory).replaceAll("\\","/");if(isPathOutsideRoot(relative))return new Map;options.cwd=workspace;const names=new Map(fileNames.map(file=>[`${relative}/${file}`,file])),listing=(await execFileAsync("git",["ls-files","--stage","-z","--",`:(literal)${relative}/`],{...options,encoding:"utf8"})).stdout,files=indexedFiles(listing,new Set(names.keys()));if(files.size===0)return new Map;const ids=new Set(files.values()),metadata=(await gitBatch(["cat-file","--batch-check"],[...ids],options)).toString("utf8"),blobs=new Map;for(const batch of blobBatches(metadata,ids,maximumBytes))decodeBlobs(await gitBatch(["cat-file","--batch"],batch.map(entry=>entry.id),options),batch,blobs);return new Map([...files].filter(([,id])=>blobs.has(id)).map(([file,id])=>[names.get(file),blobs.get(id)]))}catch{return new Map}}export{readIndexedReceiptBlobs};
4
+ //# sourceMappingURL=indexed-receipt-blobs.js.map
@@ -13,6 +13,8 @@ export declare const PM_GITATTRIBUTES_V2_END = "# pm-cli:merge-drivers:v2:end";
13
13
  export interface MergeInstallOptions {
14
14
  /** Preview the `.gitattributes` and `git config` changes without writing anything. */
15
15
  dryRun?:boolean;
16
+ /** Explicitly install an owned merge-aware pre-commit hook for authenticated lossless settlement. */
17
+ preCommitHook?:boolean;
16
18
  }
17
19
  /** Installation or preview evidence identifying the selected workspace, fence coverage, and exact Git driver commands. */
18
20
  export interface MergeInstallResult {
@@ -37,6 +39,11 @@ git_config:Array<{
37
39
  key:string;
38
40
  value:string;
39
41
  }>;
42
+ /** Explicitly requested clone-local hook; omitted by the default hook-free installation. */
43
+ settlement_hook?:{
44
+ path:string;
45
+ changed:boolean;
46
+ };
40
47
  /** Guidance for completing the multi-branch workflow setup. */
41
48
  guidance:string[];
42
49
  /** ISO 8601 timestamp recording when generated occurred. */
@@ -144,4 +151,5 @@ export declare function installMergeFence(options:{
144
151
  pmRoot:string;
145
152
  workspaceRoot:string;
146
153
  dryRun?:boolean;
154
+ preCommitHook?:boolean;
147
155
  }):Promise<MergeInstallResult>;
@@ -1,8 +1,8 @@
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]="98a35fdb-bff9-5556-9c1d-1c345cb39171")}catch{}})();import{assertInitializedTracker}from"../environment/tracker-preflight.js";import{isFileMissingError,readFileIfExists,writeFileAtomic}from"../../core/fs/fs-utils.js";import{access,readFile,realpath}from"node:fs/promises";import{constants}from"node:fs";import{execFile}from"node:child_process";import path from"node:path";import{promisify}from"node:util";import{pathExists}from"../../core/fs/fs-utils.js";import{resolveItemTypeRegistry}from"../../core/item/type-registry.js";import{acquireLock}from"../../core/lock/lock.js";import{resolvePmPackageRootFromModule}from"../../core/packages/root.js";import{EXIT_CODE}from"../../core/shared/constants.js";import{PmCliError}from"../../core/shared/errors.js";import{resolveAuthor}from"../../core/shared/author.js";import{nowIso}from"../../core/shared/time.js";import{resolvePmRoot}from"../../core/store/paths.js";import{readSettings,resolveGovernanceKnobs}from"../../core/store/settings.js";import{isPathOutsideRoot}from"../workspace.js";import{resolveSourceContextWritePolicy}from"../environment/source-context.js";import{gitWorkspaceEnvironment,resolveMergeDriverConfigScope,writeMergeDriverGitConfig}from"./worktree-config.js";import{discoverProjectRuntimeVersionPins}from"../environment/project-runtime-compatibility.js";const execFileAsync=promisify(execFile),PM_GITATTRIBUTES_START="# pm-cli:merge-drivers:start",PM_GITATTRIBUTES_END="# pm-cli:merge-drivers:end",PM_GITATTRIBUTES_V2_START="# pm-cli:merge-drivers:v2:start",PM_GITATTRIBUTES_V2_END="# pm-cli:merge-drivers:v2:end",MERGE_FENCE_LOCK_ID="merge-fence",MERGE_ATTRIBUTES_LOCK_ID="merge-attributes";function quoteMergeDriverArgument(value){return`'${value.replaceAll("'",`'"'"'`)}'`}async function resolveMatchingBunLauncher(candidate,version){const resolvedCandidate=await realpath(candidate);if(/(?:^|[\\/])bun-node-[^\\/]+[\\/]bun(?:\.exe)?$/iu.test(resolvedCandidate))return;const{stdout}=await execFileAsync(candidate,["--version"],{encoding:"utf8",windowsHide:!0,timeout:1e4});return stdout.trim()===version?resolvedCandidate:void 0}async function resolveMergeDriverCliCommand(){const packageRoot=resolvePmPackageRootFromModule(import.meta.url,["../../.."]);let executable=process.execPath,bunLauncherFound=!1;const runtimeIdentity=await realpath(process.execPath),bunVersion=process.versions.bun,pathCandidates=(process.env.PATH??"").split(path.delimiter).filter(directory=>path.isAbsolute(directory)).map(directory=>path.join(directory,bunVersion===void 0?path.basename(process.execPath):process.platform==="win32"?"bun.exe":"bun")),candidates=bunVersion===void 0?pathCandidates:[process.env.npm_execpath??"",...pathCandidates,runtimeIdentity].filter(candidate=>path.isAbsolute(candidate)&&/^(?:bun|bun\.exe)$/iu.test(path.basename(candidate)));for(const candidate of candidates)try{if(await access(candidate,constants.X_OK),bunVersion!==void 0){const launcher=await resolveMatchingBunLauncher(candidate,bunVersion);if(launcher!==void 0){executable=launcher,bunLauncherFound=!0;break}continue}if(candidate!==runtimeIdentity&&await realpath(candidate)===runtimeIdentity){executable=candidate;break}}catch{}if(bunVersion!==void 0&&!bunLauncherFound)throw new PmCliError("Cannot install Bun merge drivers without a durable executable matching the current Bun version.",EXIT_CODE.DEPENDENCY_FAILED,{code:"merge_bun_launcher_unavailable",why:"The current Bun process may be a temporary bunx launcher that Git cannot reuse after this command exits.",nextSteps:["Run with a durable version-matched Bun executable, expose one on an absolute PATH entry, or invoke pm through a Bun runner that publishes an absolute npm_execpath.","Retry pm merge install, then check pm health --strict-exit before merging branches."]});return`${quoteMergeDriverArgument(executable)} ${quoteMergeDriverArgument(path.join(packageRoot,"dist","cli.js"))}`}const MERGE_DRIVER_DEFINITIONS=[{key:"pm-item-toon",name:"pm field-aware TOON item document merge",artifact:"item",itemPath:"%P"},{key:"pm-item-markdown",name:"pm field-aware JSON-markdown item document merge",artifact:"item",itemPath:"%P"},{key:"pm-history",name:"pm append-only history stream merge",artifact:"history",itemPath:void 0},{key:"pm-relationship",name:"pm append-only relationship event store merge",artifact:"relationship",itemPath:void 0},{key:"pm-json",name:"pm key-level JSON config merge",artifact:"json",itemPath:void 0}];function resolveMergeInstallContext(options){const explicitPath=options.explicitPath?.trim(),sourceWorkspaceRoot=options.environment.PM_SOURCE_WORKSPACE_ROOT?.trim(),sourcePmRoot=options.environment.PM_SOURCE_PM_PATH?.trim(),sourcePolicy=resolveSourceContextWritePolicy(options.environment),sourceCoordinatesAvailable=!!(sourceWorkspaceRoot&&sourcePmRoot),sourceContextSelected=explicitPath===void 0&&sourceCoordinatesAvailable&&(options.dryRun||sourcePolicy.source_writes_allowed);if(explicitPath!==void 0){const pmRoot=resolvePmRoot(options.cwd,explicitPath);return{pm_root:pmRoot,workspace_start:pmRoot,source_coordinates_available:sourceCoordinatesAvailable,source_context_selected:!1,source_policy:sourcePolicy}}return sourceContextSelected?{pm_root:path.resolve(sourcePmRoot),workspace_start:path.resolve(sourceWorkspaceRoot),source_coordinates_available:!0,source_context_selected:!0,source_policy:sourcePolicy}:{pm_root:sourceCoordinatesAvailable?resolvePmRoot(options.cwd,".agents/pm"):resolvePmRoot(options.cwd),workspace_start:options.cwd,source_coordinates_available:sourceCoordinatesAvailable,source_context_selected:!1,source_policy:sourcePolicy}}async function findGitWorkspaceRoot(cwd){try{const{stdout}=await execFileAsync("git",["rev-parse","--show-toplevel"],{cwd,env:gitWorkspaceEnvironment(),encoding:"utf8",windowsHide:!0,timeout:1e4});return await realpath(stdout.trim())}catch{return null}}async function resolveGitWorkspaceRoot(cwd){const workspaceRoot=await findGitWorkspaceRoot(cwd);if(workspaceRoot===null)throw new PmCliError("pm merge install requires a git repository (merge drivers are git configuration).",EXIT_CODE.USAGE);return workspaceRoot}function toPosixRelative(fromPath,toPath){return path.relative(fromPath,toPath).replaceAll("\\","/")}function quoteGitAttributePattern(value){return`"${value.replaceAll("\\","\\\\").replaceAll('"','\\"')}"`}function readSingleQuotedArgument(command,offset){if(command[offset]!=="'")return null;let value="";for(let index=offset+1;index<command.length;index+=1){if(command.startsWith(`'"'"'`,index)){value+="'",index+=4;continue}if(command[index]==="'")return{value,nextOffset:index+1};value+=command[index]}return null}async function isMergeDriverWorkspaceCompatible(packageRoot,workspaceRoot,version){if(discoverProjectRuntimeVersionPins(workspaceRoot).filter(pin=>pin.constraint==="exact").some(pin=>pin.version!==version))return!1;const driverWorkspace=await findGitWorkspaceRoot(packageRoot);if(driverWorkspace!==null&&driverWorkspace!==await realpath(workspaceRoot)){const commonDirectories=await Promise.all([driverWorkspace,workspaceRoot].map(async cwd=>{const result=await execFileAsync("git",["rev-parse","--path-format=absolute","--git-common-dir"],{cwd,env:gitWorkspaceEnvironment(),timeout:1e4});return realpath(result.stdout.trim())}));if(commonDirectories[0]===commonDirectories[1])return!1}return!0}async function isPortableMergeDriverCommand(configured,expectedSuffix,workspaceRoot){const nodeArgument=readSingleQuotedArgument(configured,0);if(nodeArgument===null||configured[nodeArgument.nextOffset]!==" ")return!1;const cliArgument=readSingleQuotedArgument(configured,nodeArgument.nextOffset+1);if(cliArgument===null||configured.slice(cliArgument.nextOffset)!==expectedSuffix||!/^(?:node|bun)(?:\.exe)?$/iu.test(path.basename(nodeArgument.value))||path.basename(cliArgument.value)!=="cli.js"||path.basename(path.dirname(cliArgument.value))!=="dist"||!await pathExists(nodeArgument.value)||!await pathExists(cliArgument.value))return!1;try{const packageRoot=path.dirname(path.dirname(cliArgument.value)),manifest=JSON.parse(await readFile(path.join(packageRoot,"package.json"),"utf8")),pmBin=typeof manifest.bin=="string"?manifest.bin:manifest.bin?.pm;return await isMergeDriverWorkspaceCompatible(packageRoot,workspaceRoot,manifest.version)?manifest.name==="@unbrained/pm-cli"&&pmBin==="dist/cli.js":!1}catch{return!1}}function buildMergeAttributePatterns(trackerRelativeRoot,typeFolders){const prefix=trackerRelativeRoot.length>0?`${trackerRelativeRoot}/`:"",patterns=[`${quoteGitAttributePattern(`${prefix}**/*.toon`)} merge=pm-item-toon`,`${quoteGitAttributePattern(`${prefix}**/*.md`)} merge=pm-item-markdown`];for(const folder of typeFolders)patterns.push(`${quoteGitAttributePattern(`${prefix}${folder}/*.toon`)} merge=pm-item-toon`),patterns.push(`${quoteGitAttributePattern(`${prefix}${folder}/*.md`)} merge=pm-item-markdown`);return patterns.push(`${quoteGitAttributePattern(`${prefix}**/*.jsonl`)} merge=pm-relationship`),patterns.push(`${quoteGitAttributePattern(`${prefix}history/*.jsonl`)} merge=pm-history`),patterns.push(`${quoteGitAttributePattern(`${prefix}settings.json`)} merge=pm-json`),patterns.push(`${quoteGitAttributePattern(`${prefix}**/*.json`)} merge=pm-json`),patterns.push(`${quoteGitAttributePattern(`${prefix}extensions/**`)} merge=text`),patterns.push(`${quoteGitAttributePattern(`${prefix}extensions/.managed-extensions.json`)} merge=pm-json`),patterns}function managedMergeBlocks(content){const blocks=[];let cursor=0;for(;cursor<content.length;){const legacyStart=content.indexOf(PM_GITATTRIBUTES_START,cursor),versionedStart=content.indexOf(PM_GITATTRIBUTES_V2_START,cursor),start=legacyStart===-1?versionedStart:versionedStart===-1?legacyStart:Math.min(legacyStart,versionedStart);if(start===-1)break;const versioned=start===versionedStart,startMarker=versioned?PM_GITATTRIBUTES_V2_START:PM_GITATTRIBUTES_START,endMarker=versioned?PM_GITATTRIBUTES_V2_END:PM_GITATTRIBUTES_END,closing=content.indexOf(endMarker,start+startMarker.length),end=closing===-1?content.length:closing+endMarker.length;blocks.push({start,end,closed:closing!==-1,lines:content.slice(start+startMarker.length,closing===-1?end:closing).split(`
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]="da37bda1-1520-5db0-9dad-bb1acc556de8")}catch{}})();import{assertInitializedTracker}from"../environment/tracker-preflight.js";import{isFileMissingError,readFileIfExists,writeFileAtomic}from"../../core/fs/fs-utils.js";import{access,readFile,realpath}from"node:fs/promises";import{constants}from"node:fs";import{execFile}from"node:child_process";import path from"node:path";import{promisify}from"node:util";import{pathExists}from"../../core/fs/fs-utils.js";import{resolveItemTypeRegistry}from"../../core/item/type-registry.js";import{acquireLock}from"../../core/lock/lock.js";import{resolvePmPackageRootFromModule}from"../../core/packages/root.js";import{EXIT_CODE}from"../../core/shared/constants.js";import{PmCliError}from"../../core/shared/errors.js";import{resolveAuthor}from"../../core/shared/author.js";import{nowIso}from"../../core/shared/time.js";import{resolvePmRoot}from"../../core/store/paths.js";import{readSettings,resolveGovernanceKnobs}from"../../core/store/settings.js";import{isPathOutsideRoot}from"../workspace.js";import{resolveSourceContextWritePolicy}from"../environment/source-context.js";import{gitWorkspaceEnvironment,resolveMergeDriverConfigScope,writeMergeDriverGitConfig}from"./worktree-config.js";import{discoverProjectRuntimeVersionPins}from"../environment/project-runtime-compatibility.js";const execFileAsync=promisify(execFile),PM_GITATTRIBUTES_START="# pm-cli:merge-drivers:start",PM_GITATTRIBUTES_END="# pm-cli:merge-drivers:end",PM_GITATTRIBUTES_V2_START="# pm-cli:merge-drivers:v2:start",PM_GITATTRIBUTES_V2_END="# pm-cli:merge-drivers:v2:end",MERGE_FENCE_LOCK_ID="merge-fence",MERGE_ATTRIBUTES_LOCK_ID="merge-attributes";function quoteMergeDriverArgument(value){return`'${value.replaceAll("'",`'"'"'`)}'`}async function resolveMatchingBunLauncher(candidate,version){const resolvedCandidate=await realpath(candidate);if(/(?:^|[\\/])bun-node-[^\\/]+[\\/]bun(?:\.exe)?$/iu.test(resolvedCandidate))return;const{stdout}=await execFileAsync(candidate,["--version"],{encoding:"utf8",windowsHide:!0,timeout:1e4});return stdout.trim()===version?resolvedCandidate:void 0}async function resolveMergeDriverCliCommand(){const packageRoot=resolvePmPackageRootFromModule(import.meta.url,["../../.."]);let executable=process.execPath,bunLauncherFound=!1;const runtimeIdentity=await realpath(process.execPath),bunVersion=process.versions.bun,pathCandidates=(process.env.PATH??"").split(path.delimiter).filter(directory=>path.isAbsolute(directory)).map(directory=>path.join(directory,bunVersion===void 0?path.basename(process.execPath):process.platform==="win32"?"bun.exe":"bun")),candidates=bunVersion===void 0?pathCandidates:[process.env.npm_execpath??"",...pathCandidates,runtimeIdentity].filter(candidate=>path.isAbsolute(candidate)&&/^(?:bun|bun\.exe)$/iu.test(path.basename(candidate)));for(const candidate of candidates)try{if(await access(candidate,constants.X_OK),bunVersion!==void 0){const launcher=await resolveMatchingBunLauncher(candidate,bunVersion);if(launcher!==void 0){executable=launcher,bunLauncherFound=!0;break}continue}if(candidate!==runtimeIdentity&&await realpath(candidate)===runtimeIdentity){executable=candidate;break}}catch{}if(bunVersion!==void 0&&!bunLauncherFound)throw new PmCliError("Cannot install Bun merge drivers without a durable executable matching the current Bun version.",EXIT_CODE.DEPENDENCY_FAILED,{code:"merge_bun_launcher_unavailable",why:"The current Bun process may be a temporary bunx launcher that Git cannot reuse after this command exits.",nextSteps:["Run with a durable version-matched Bun executable, expose one on an absolute PATH entry, or invoke pm through a Bun runner that publishes an absolute npm_execpath.","Retry pm merge install, then check pm health --strict-exit before merging branches."]});return`${quoteMergeDriverArgument(executable)} ${quoteMergeDriverArgument(path.join(packageRoot,"dist","cli.js"))}`}const MERGE_DRIVER_DEFINITIONS=[{key:"pm-item-toon",name:"pm field-aware TOON item document merge",artifact:"item",itemPath:"%P"},{key:"pm-item-markdown",name:"pm field-aware JSON-markdown item document merge",artifact:"item",itemPath:"%P"},{key:"pm-history",name:"pm append-only history stream merge",artifact:"history",itemPath:void 0},{key:"pm-relationship",name:"pm append-only relationship event store merge",artifact:"relationship",itemPath:void 0},{key:"pm-json",name:"pm key-level JSON config merge",artifact:"json",itemPath:void 0}];function resolveMergeInstallContext(options){const explicitPath=options.explicitPath?.trim(),sourceWorkspaceRoot=options.environment.PM_SOURCE_WORKSPACE_ROOT?.trim(),sourcePmRoot=options.environment.PM_SOURCE_PM_PATH?.trim(),sourcePolicy=resolveSourceContextWritePolicy(options.environment),sourceCoordinatesAvailable=!!(sourceWorkspaceRoot&&sourcePmRoot),sourceContextSelected=explicitPath===void 0&&sourceCoordinatesAvailable&&(options.dryRun||sourcePolicy.source_writes_allowed);if(explicitPath!==void 0){const pmRoot=resolvePmRoot(options.cwd,explicitPath);return{pm_root:pmRoot,workspace_start:pmRoot,source_coordinates_available:sourceCoordinatesAvailable,source_context_selected:!1,source_policy:sourcePolicy}}return sourceContextSelected?{pm_root:path.resolve(sourcePmRoot),workspace_start:path.resolve(sourceWorkspaceRoot),source_coordinates_available:!0,source_context_selected:!0,source_policy:sourcePolicy}:{pm_root:sourceCoordinatesAvailable?resolvePmRoot(options.cwd,".agents/pm"):resolvePmRoot(options.cwd),workspace_start:options.cwd,source_coordinates_available:sourceCoordinatesAvailable,source_context_selected:!1,source_policy:sourcePolicy}}async function findGitWorkspaceRoot(cwd){try{const{stdout}=await execFileAsync("git",["rev-parse","--show-toplevel"],{cwd,env:gitWorkspaceEnvironment(),encoding:"utf8",windowsHide:!0,timeout:1e4});return await realpath(stdout.trim())}catch{return null}}async function resolveGitWorkspaceRoot(cwd){const workspaceRoot=await findGitWorkspaceRoot(cwd);if(workspaceRoot===null)throw new PmCliError("pm merge install requires a git repository (merge drivers are git configuration).",EXIT_CODE.USAGE);return workspaceRoot}function toPosixRelative(fromPath,toPath){return path.relative(fromPath,toPath).replaceAll("\\","/")}function quoteGitAttributePattern(value){return`"${value.replaceAll("\\","\\\\").replaceAll('"','\\"')}"`}function readSingleQuotedArgument(command,offset){if(command[offset]!=="'")return null;let value="";for(let index=offset+1;index<command.length;index+=1){if(command.startsWith(`'"'"'`,index)){value+="'",index+=4;continue}if(command[index]==="'")return{value,nextOffset:index+1};value+=command[index]}return null}async function isMergeDriverWorkspaceCompatible(packageRoot,workspaceRoot,version){if(discoverProjectRuntimeVersionPins(workspaceRoot).filter(pin=>pin.constraint==="exact").some(pin=>pin.version!==version))return!1;const driverWorkspace=await findGitWorkspaceRoot(packageRoot);if(driverWorkspace!==null&&driverWorkspace!==await realpath(workspaceRoot)){const commonDirectories=await Promise.all([driverWorkspace,workspaceRoot].map(async cwd=>{const result=await execFileAsync("git",["rev-parse","--path-format=absolute","--git-common-dir"],{cwd,env:gitWorkspaceEnvironment(),timeout:1e4});return realpath(result.stdout.trim())}));if(commonDirectories[0]===commonDirectories[1])return!1}return!0}async function isPortableMergeDriverCommand(configured,expectedSuffix,workspaceRoot){const nodeArgument=readSingleQuotedArgument(configured,0);if(nodeArgument===null||configured[nodeArgument.nextOffset]!==" ")return!1;const cliArgument=readSingleQuotedArgument(configured,nodeArgument.nextOffset+1);if(cliArgument===null||configured.slice(cliArgument.nextOffset)!==expectedSuffix||!/^(?:node|bun)(?:\.exe)?$/iu.test(path.basename(nodeArgument.value))||path.basename(cliArgument.value)!=="cli.js"||path.basename(path.dirname(cliArgument.value))!=="dist"||!await pathExists(nodeArgument.value)||!await pathExists(cliArgument.value))return!1;try{const packageRoot=path.dirname(path.dirname(cliArgument.value)),manifest=JSON.parse(await readFile(path.join(packageRoot,"package.json"),"utf8")),pmBin=typeof manifest.bin=="string"?manifest.bin:manifest.bin?.pm;return await isMergeDriverWorkspaceCompatible(packageRoot,workspaceRoot,manifest.version)?manifest.name==="@unbrained/pm-cli"&&pmBin==="dist/cli.js":!1}catch{return!1}}function buildMergeAttributePatterns(trackerRelativeRoot,typeFolders){const prefix=trackerRelativeRoot.length>0?`${trackerRelativeRoot}/`:"",patterns=[`${quoteGitAttributePattern(`${prefix}**/*.toon`)} merge=pm-item-toon`,`${quoteGitAttributePattern(`${prefix}**/*.md`)} merge=pm-item-markdown`];for(const folder of typeFolders)patterns.push(`${quoteGitAttributePattern(`${prefix}${folder}/*.toon`)} merge=pm-item-toon`),patterns.push(`${quoteGitAttributePattern(`${prefix}${folder}/*.md`)} merge=pm-item-markdown`);return patterns.push(`${quoteGitAttributePattern(`${prefix}**/*.jsonl`)} merge=pm-relationship`),patterns.push(`${quoteGitAttributePattern(`${prefix}history/*.jsonl`)} merge=pm-history`),patterns.push(`${quoteGitAttributePattern(`${prefix}settings.json`)} merge=pm-json`),patterns.push(`${quoteGitAttributePattern(`${prefix}**/*.json`)} merge=pm-json`),patterns.push(`${quoteGitAttributePattern(`${prefix}extensions/**`)} merge=text`),patterns.push(`${quoteGitAttributePattern(`${prefix}extensions/.managed-extensions.json`)} merge=pm-json`),patterns}function managedMergeBlocks(content){const blocks=[];let cursor=0;for(;cursor<content.length;){const legacyStart=content.indexOf(PM_GITATTRIBUTES_START,cursor),versionedStart=content.indexOf(PM_GITATTRIBUTES_V2_START,cursor),start=legacyStart===-1?versionedStart:versionedStart===-1?legacyStart:Math.min(legacyStart,versionedStart);if(start===-1)break;const versioned=start===versionedStart,startMarker=versioned?PM_GITATTRIBUTES_V2_START:PM_GITATTRIBUTES_START,endMarker=versioned?PM_GITATTRIBUTES_V2_END:PM_GITATTRIBUTES_END,closing=content.indexOf(endMarker,start+startMarker.length),end=closing===-1?content.length:closing+endMarker.length;blocks.push({start,end,closed:closing!==-1,lines:content.slice(start+startMarker.length,closing===-1?end:closing).split(`
2
2
  `).map(line=>line.trim()).filter(Boolean)}),cursor=end}return blocks}function blockBelongsToTracker(lines,patterns,trackerRelativeRoot){const ownedPatterns=new Set(patterns),prefix=quoteGitAttributePattern(trackerRelativeRoot.length>0?`${trackerRelativeRoot}/`:"").slice(0,-1);return lines.some(line=>ownedPatterns.has(line)||line.startsWith(prefix)&&/^[^/]+\/\*\.(?:toon|md)" merge=pm-item-(?:toon|markdown)$/u.test(line.slice(prefix.length)))}async function reconcileGitattributesBlock(workspaceRoot,trackerRelativeRoot,patterns,dryRun){const gitattributesPath=path.join(workspaceRoot,".gitattributes"),gitDirectory=dryRun?null:(await execFileAsync("git",["rev-parse","--absolute-git-dir"],{cwd:workspaceRoot,env:gitWorkspaceEnvironment(),encoding:"utf8",windowsHide:!0,timeout:1e4})).stdout.trim(),releaseLock=gitDirectory===null?null:await acquireLock(gitDirectory,MERGE_ATTRIBUTES_LOCK_ID,120,"merge-fence",!1,!0,5e3);try{const current=await readFileIfExists(gitattributesPath)??"",block=[PM_GITATTRIBUTES_V2_START,...patterns,PM_GITATTRIBUTES_V2_END].join(`
3
3
  `),blocks=managedMergeBlocks(current),owned=blocks.filter(candidate=>blockBelongsToTracker(candidate.lines,patterns,trackerRelativeRoot));owned.length===0&&trackerRelativeRoot===".agents/pm"&&blocks.length===1&&blocks[0].lines.every(line=>!line.includes(".agents/pm/"))&&owned.push(blocks[0]);let next;if(owned.length>0){const first=owned[0];next=current;for(const duplicate of owned.slice(1).reverse())next=`${next.slice(0,duplicate.start)}${next.slice(duplicate.end)}`;next=`${next.slice(0,first.start)}${block}${next.slice(first.end)}`}else{const prefix=current.trimEnd();next=`${prefix.length>0?`${prefix}
4
4
 
5
5
  `:""}${block}
6
- `}return next===current?{path:gitattributesPath,changed:!1}:(dryRun||await writeFileAtomic(gitattributesPath,next),{path:gitattributesPath,changed:!0})}finally{await releaseLock?.()}}function resolveProjectMergeTypeFolders(settings){const registry=resolveItemTypeRegistry(settings,null);return[...new Set(Object.values(registry.type_to_folder))].sort((left,right)=>left.localeCompare(right))}async function auditMergeAttributeFence(pmRoot,typeFolders){const resolvedRoot=path.resolve(pmRoot);let fenceDirectory=null,fenceContent="",directory=resolvedRoot;for(let depth=0;depth<8;depth+=1){const candidate=path.join(directory,".gitattributes");try{const raw=await readFile(candidate,"utf8");if(raw.includes(PM_GITATTRIBUTES_V2_START)||raw.includes(PM_GITATTRIBUTES_START)){fenceDirectory=directory,fenceContent=raw;break}}catch{}const parent=path.dirname(directory);if(parent===directory)break;directory=parent}if(fenceDirectory===null)return{status:"not_installed",missing_patterns:[],stale_patterns:[],schema_scope:"project"};const fencePath=path.join(fenceDirectory,".gitattributes"),relativeRoot=toPosixRelative(fenceDirectory,resolvedRoot),blocks=managedMergeBlocks(fenceContent),expected=buildMergeAttributePatterns(relativeRoot,typeFolders),owned=blocks.filter(block=>blockBelongsToTracker(block.lines,expected,relativeRoot)),committed=owned.flatMap(block=>block.closed?block.lines:[]),committedSet=new Set(committed),expectedSet=new Set(expected),missing=expected.filter(line=>!committedSet.has(line)),stale=committed.filter(line=>!expectedSet.has(line));return{status:owned.length!==1||!owned[0].closed||missing.length>0||stale.length>0?"drift":"ok",path:fencePath,missing_patterns:missing,stale_patterns:stale,schema_scope:"project"}}async function auditMergeDriverConfiguration(workspaceRoot){const missingKeys=[],driftedKeys=[];for(const definition of MERGE_DRIVER_DEFINITIONS){const key=`merge.${definition.key}.driver`,expectedSuffix=` merge driver ${definition.artifact} "%O" "%A" "%B"${definition.itemPath===void 0?"":` --item-path ${definition.itemPath}`}`;try{const{stdout}=await execFileAsync("git",["config","--get",key],{cwd:workspaceRoot,env:gitWorkspaceEnvironment(),encoding:"utf8",windowsHide:!0,timeout:1e4}),configured=stdout.trim();await isPortableMergeDriverCommand(configured,expectedSuffix,workspaceRoot)||driftedKeys.push(key)}catch{missingKeys.push(key)}}return{status:missingKeys.length>0?"missing":driftedKeys.length>0?"drift":"ok",missing_keys:missingKeys,drifted_keys:driftedKeys}}async function refreshMergeAttributeFenceIfInstalled(pmRoot){let workspaceRoot;try{workspaceRoot=await resolveGitWorkspaceRoot(path.resolve(pmRoot))}catch{return{status:"no_git"}}const trackerRelativeRoot=toPosixRelative(workspaceRoot,path.resolve(pmRoot)),lockSettings=await readSettings(pmRoot),owner=resolveAuthor(void 0,lockSettings.author_default),releaseLock=await acquireLock(pmRoot,MERGE_FENCE_LOCK_ID,lockSettings.locks.ttl_seconds,owner,!1,resolveGovernanceKnobs(lockSettings).force_required_for_stale_lock,lockSettings.locks.wait_ms);try{const gitattributesPath=path.join(workspaceRoot,".gitattributes");let current;try{current=await readFile(gitattributesPath,"utf8")}catch(error){if(error instanceof Error&&"code"in error&&isFileMissingError(error))return{status:"not_installed",path:gitattributesPath};throw error}if(!current.includes(PM_GITATTRIBUTES_V2_START)&&!current.includes(PM_GITATTRIBUTES_START))return{status:"not_installed",path:gitattributesPath};const settings=await readSettings(pmRoot),typeFolders=resolveProjectMergeTypeFolders(settings),outcome=await reconcileGitattributesBlock(workspaceRoot,trackerRelativeRoot,buildMergeAttributePatterns(trackerRelativeRoot,typeFolders),!1);return{status:outcome.changed?"refreshed":"unchanged",path:outcome.path}}finally{await releaseLock()}}async function runMergeInstall(options,global2){const context=resolveMergeInstallContext({cwd:process.cwd(),explicitPath:global2.path,dryRun:options.dryRun===!0,environment:process.env}),pmRoot=context.pm_root;await assertInitializedTracker(pmRoot);const workspaceRoot=await resolveGitWorkspaceRoot(context.workspace_start);return installMergeFence({pmRoot,workspaceRoot,dryRun:options.dryRun===!0})}async function installMergeDriverConfiguration(workspaceRoot,entries){const commonDirectory=(await execFileAsync("git",["rev-parse","--path-format=absolute","--git-common-dir"],{cwd:workspaceRoot,env:gitWorkspaceEnvironment(),timeout:1e4})).stdout.trim(),release=await acquireLock(commonDirectory,"merge-driver-config",120,"merge-install",!1,!0,5e3);try{const scope=await resolveMergeDriverConfigScope(workspaceRoot);let stdout;try{stdout=(await execFileAsync("git",["config",scope,"--null","--get-regexp","."],{cwd:workspaceRoot,env:gitWorkspaceEnvironment(),timeout:1e4})).stdout}catch(error){if(typeof error!="object"||error===null||!("code"in error)||error.code!==1)throw error;stdout=""}const current=new Map(stdout.split("\0").filter(Boolean).map(entry=>{const separator=entry.indexOf(`
7
- `);return[entry.slice(0,separator),entry.slice(separator+1)]}));for(const entry of entries)current.get(entry.key)!==entry.value&&await writeMergeDriverGitConfig(workspaceRoot,[scope,entry.key,entry.value])}finally{await release()}}async function installMergeFence(options){const pmRoot=path.resolve(options.pmRoot),workspaceRoot=path.resolve(options.workspaceRoot),dryRun=options.dryRun===!0;let canonicalPmRoot,canonicalWorkspaceRoot;try{[canonicalPmRoot,canonicalWorkspaceRoot]=await Promise.all([realpath(pmRoot),realpath(workspaceRoot)])}catch(error){throw new PmCliError(`Cannot resolve tracker root ${pmRoot} or workspace root ${workspaceRoot}: ${String(error)}`,EXIT_CODE.NOT_FOUND,{code:"merge_root_not_found",nextSteps:["Initialize the tracker and ensure both paths are accessible.",'Retry "pm merge install" from the repository workspace.']})}const trackerRelativeRoot=toPosixRelative(canonicalWorkspaceRoot,canonicalPmRoot);if(isPathOutsideRoot(trackerRelativeRoot))throw new PmCliError(`Tracker root ${pmRoot} is outside the git repository ${workspaceRoot}; there is nothing to configure for this repository.`,EXIT_CODE.USAGE);const settings=await readSettings(canonicalPmRoot),typeFolders=resolveProjectMergeTypeFolders(settings),patterns=buildMergeAttributePatterns(trackerRelativeRoot,typeFolders),gitConfigEntries=[],cliCommand=await resolveMergeDriverCliCommand();for(const definition of MERGE_DRIVER_DEFINITIONS)gitConfigEntries.push({key:`merge.${definition.key}.name`,value:definition.name}),gitConfigEntries.push({key:`merge.${definition.key}.driver`,value:`${cliCommand} merge driver ${definition.artifact} "%O" "%A" "%B"${definition.itemPath===void 0?"":` --item-path ${definition.itemPath}`}`});if(!dryRun)try{await installMergeDriverConfiguration(canonicalWorkspaceRoot,gitConfigEntries)}catch{throw new PmCliError(`Cannot install repository-local merge drivers in ${canonicalWorkspaceRoot}. Ensure the repository Git config is writable and no other Git process holds its lock, then retry.`,EXIT_CODE.DEPENDENCY_FAILED,{code:"merge_git_config_unwritable",why:"pm merge install must persist clone-local driver commands before publishing their shared .gitattributes mappings.",nextSteps:["Make the repository Git config writable or wait for the process holding its lock to finish.",'Retry "pm merge install"; the operation is idempotent.','Use "pm merge install --dry-run --json" when the workspace is intentionally read-only.']})}const gitattributes=await reconcileGitattributesBlock(canonicalWorkspaceRoot,trackerRelativeRoot,patterns,dryRun);return{ok:!0,dry_run:dryRun,workspace_root:canonicalWorkspaceRoot,pm_root:canonicalPmRoot,gitattributes:{path:gitattributes.path,changed:gitattributes.changed,patterns,schema_scope:"project"},git_config:gitConfigEntries,guidance:["Commit .gitattributes so every branch and collaborator shares the merge mapping.",'git config is per-clone: each collaborator (and each fresh worktree/clone) runs "pm merge install" once.','After merging branches, run "pm merge reconcile --dry-run" to inspect the complete receipt and history repair, then apply "pm merge reconcile"; use --force only after explicitly accepting any discarded values.',"Custom item types registered later refresh this fence automatically (pm schema add-type/remove-type); rerun pm merge install only after out-of-band type-folder edits."],generated_at:nowIso()}}export{PM_GITATTRIBUTES_END,PM_GITATTRIBUTES_START,PM_GITATTRIBUTES_V2_END,PM_GITATTRIBUTES_V2_START,auditMergeAttributeFence,auditMergeDriverConfiguration,buildMergeAttributePatterns,findGitWorkspaceRoot,installMergeFence,refreshMergeAttributeFenceIfInstalled,resolveMergeInstallContext,resolveProjectMergeTypeFolders,runMergeInstall};
6
+ `}return next===current?{path:gitattributesPath,changed:!1}:(dryRun||await writeFileAtomic(gitattributesPath,next),{path:gitattributesPath,changed:!0})}finally{await releaseLock?.()}}function resolveProjectMergeTypeFolders(settings){const registry=resolveItemTypeRegistry(settings,null);return[...new Set(Object.values(registry.type_to_folder))].sort((left,right)=>left.localeCompare(right))}async function auditMergeAttributeFence(pmRoot,typeFolders){const resolvedRoot=path.resolve(pmRoot);let fenceDirectory=null,fenceContent="",directory=resolvedRoot;for(let depth=0;depth<8;depth+=1){const candidate=path.join(directory,".gitattributes");try{const raw=await readFile(candidate,"utf8");if(raw.includes(PM_GITATTRIBUTES_V2_START)||raw.includes(PM_GITATTRIBUTES_START)){fenceDirectory=directory,fenceContent=raw;break}}catch{}const parent=path.dirname(directory);if(parent===directory)break;directory=parent}if(fenceDirectory===null)return{status:"not_installed",missing_patterns:[],stale_patterns:[],schema_scope:"project"};const fencePath=path.join(fenceDirectory,".gitattributes"),relativeRoot=toPosixRelative(fenceDirectory,resolvedRoot),blocks=managedMergeBlocks(fenceContent),expected=buildMergeAttributePatterns(relativeRoot,typeFolders),owned=blocks.filter(block=>blockBelongsToTracker(block.lines,expected,relativeRoot)),committed=owned.flatMap(block=>block.closed?block.lines:[]),committedSet=new Set(committed),expectedSet=new Set(expected),missing=expected.filter(line=>!committedSet.has(line)),stale=committed.filter(line=>!expectedSet.has(line));return{status:owned.length!==1||!owned[0].closed||missing.length>0||stale.length>0?"drift":"ok",path:fencePath,missing_patterns:missing,stale_patterns:stale,schema_scope:"project"}}async function auditMergeDriverConfiguration(workspaceRoot){const missingKeys=[],driftedKeys=[];for(const definition of MERGE_DRIVER_DEFINITIONS){const key=`merge.${definition.key}.driver`,expectedSuffix=` merge driver ${definition.artifact} "%O" "%A" "%B"${definition.itemPath===void 0?"":` --item-path ${definition.itemPath}`}`;try{const{stdout}=await execFileAsync("git",["config","--get",key],{cwd:workspaceRoot,env:gitWorkspaceEnvironment(),encoding:"utf8",windowsHide:!0,timeout:1e4}),configured=stdout.trim();await isPortableMergeDriverCommand(configured,expectedSuffix,workspaceRoot)||driftedKeys.push(key)}catch{missingKeys.push(key)}}return{status:missingKeys.length>0?"missing":driftedKeys.length>0?"drift":"ok",missing_keys:missingKeys,drifted_keys:driftedKeys}}async function refreshMergeAttributeFenceIfInstalled(pmRoot){let workspaceRoot;try{workspaceRoot=await resolveGitWorkspaceRoot(path.resolve(pmRoot))}catch{return{status:"no_git"}}const trackerRelativeRoot=toPosixRelative(workspaceRoot,path.resolve(pmRoot)),lockSettings=await readSettings(pmRoot),owner=resolveAuthor(void 0,lockSettings.author_default),releaseLock=await acquireLock(pmRoot,MERGE_FENCE_LOCK_ID,lockSettings.locks.ttl_seconds,owner,!1,resolveGovernanceKnobs(lockSettings).force_required_for_stale_lock,lockSettings.locks.wait_ms);try{const gitattributesPath=path.join(workspaceRoot,".gitattributes");let current;try{current=await readFile(gitattributesPath,"utf8")}catch(error){if(error instanceof Error&&"code"in error&&isFileMissingError(error))return{status:"not_installed",path:gitattributesPath};throw error}if(!current.includes(PM_GITATTRIBUTES_V2_START)&&!current.includes(PM_GITATTRIBUTES_START))return{status:"not_installed",path:gitattributesPath};const settings=await readSettings(pmRoot),typeFolders=resolveProjectMergeTypeFolders(settings),outcome=await reconcileGitattributesBlock(workspaceRoot,trackerRelativeRoot,buildMergeAttributePatterns(trackerRelativeRoot,typeFolders),!1);return{status:outcome.changed?"refreshed":"unchanged",path:outcome.path}}finally{await releaseLock()}}async function runMergeInstall(options,global2){const context=resolveMergeInstallContext({cwd:process.cwd(),explicitPath:global2.path,dryRun:options.dryRun===!0,environment:process.env}),pmRoot=context.pm_root;await assertInitializedTracker(pmRoot);const workspaceRoot=await resolveGitWorkspaceRoot(context.workspace_start);return installMergeFence({pmRoot,workspaceRoot,dryRun:options.dryRun===!0,preCommitHook:options.preCommitHook===!0})}async function installMergeDriverConfiguration(workspaceRoot,entries){const commonDirectory=(await execFileAsync("git",["rev-parse","--path-format=absolute","--git-common-dir"],{cwd:workspaceRoot,env:gitWorkspaceEnvironment(),timeout:1e4})).stdout.trim(),release=await acquireLock(commonDirectory,"merge-driver-config",120,"merge-install",!1,!0,5e3);try{const scope=await resolveMergeDriverConfigScope(workspaceRoot);let stdout;try{stdout=(await execFileAsync("git",["config",scope,"--null","--get-regexp","."],{cwd:workspaceRoot,env:gitWorkspaceEnvironment(),timeout:1e4})).stdout}catch(error){if(typeof error!="object"||error===null||!("code"in error)||error.code!==1)throw error;stdout=""}const current=new Map(stdout.split("\0").filter(Boolean).map(entry=>{const separator=entry.indexOf(`
7
+ `);return[entry.slice(0,separator),entry.slice(separator+1)]}));for(const entry of entries)current.get(entry.key)!==entry.value&&await writeMergeDriverGitConfig(workspaceRoot,[scope,entry.key,entry.value])}finally{await release()}}async function installMergeFence(options){const pmRoot=path.resolve(options.pmRoot),workspaceRoot=path.resolve(options.workspaceRoot),dryRun=options.dryRun===!0;let canonicalPmRoot,canonicalWorkspaceRoot;try{[canonicalPmRoot,canonicalWorkspaceRoot]=await Promise.all([realpath(pmRoot),realpath(workspaceRoot)])}catch(error){throw new PmCliError(`Cannot resolve tracker root ${pmRoot} or workspace root ${workspaceRoot}: ${String(error)}`,EXIT_CODE.NOT_FOUND,{code:"merge_root_not_found",nextSteps:["Initialize the tracker and ensure both paths are accessible.",'Retry "pm merge install" from the repository workspace.']})}const trackerRelativeRoot=toPosixRelative(canonicalWorkspaceRoot,canonicalPmRoot);if(isPathOutsideRoot(trackerRelativeRoot))throw new PmCliError(`Tracker root ${pmRoot} is outside the git repository ${workspaceRoot}; there is nothing to configure for this repository.`,EXIT_CODE.USAGE);const settings=await readSettings(canonicalPmRoot),typeFolders=resolveProjectMergeTypeFolders(settings),patterns=buildMergeAttributePatterns(trackerRelativeRoot,typeFolders),gitConfigEntries=[],cliCommand=await resolveMergeDriverCliCommand(),settlementHook=options.preCommitHook===!0?await import("./settlement-hook.js").then(async hooks=>({plan:await hooks.prepareMergeSettlementHook(canonicalWorkspaceRoot,`${cliCommand} --pm-path ${quoteMergeDriverArgument(trackerRelativeRoot||".")}`),write:hooks.writeMergeSettlementHook})):void 0;for(const definition of MERGE_DRIVER_DEFINITIONS)gitConfigEntries.push({key:`merge.${definition.key}.name`,value:definition.name}),gitConfigEntries.push({key:`merge.${definition.key}.driver`,value:`${cliCommand} merge driver ${definition.artifact} "%O" "%A" "%B"${definition.itemPath===void 0?"":` --item-path ${definition.itemPath}`}`});if(!dryRun)try{await installMergeDriverConfiguration(canonicalWorkspaceRoot,gitConfigEntries)}catch{throw new PmCliError(`Cannot install repository-local merge drivers in ${canonicalWorkspaceRoot}. Ensure the repository Git config is writable and no other Git process holds its lock, then retry.`,EXIT_CODE.DEPENDENCY_FAILED,{code:"merge_git_config_unwritable",why:"pm merge install must persist clone-local driver commands before publishing their shared .gitattributes mappings.",nextSteps:["Make the repository Git config writable or wait for the process holding its lock to finish.",'Retry "pm merge install"; the operation is idempotent.','Use "pm merge install --dry-run --json" when the workspace is intentionally read-only.']})}const gitattributes=await reconcileGitattributesBlock(canonicalWorkspaceRoot,trackerRelativeRoot,patterns,dryRun);return settlementHook!==void 0&&!dryRun&&await settlementHook.write(settlementHook.plan),{ok:!0,dry_run:dryRun,workspace_root:canonicalWorkspaceRoot,pm_root:canonicalPmRoot,gitattributes:{path:gitattributes.path,changed:gitattributes.changed,patterns,schema_scope:"project"},git_config:gitConfigEntries,...settlementHook===void 0?{}:{settlement_hook:{path:settlementHook.plan.path,changed:settlementHook.plan.changed}},guidance:["Commit .gitattributes so every branch and collaborator shares the merge mapping.",'git config is per-clone: each collaborator (and each fresh worktree/clone) runs "pm merge install" once.','After merging branches, run "pm merge reconcile --dry-run" to inspect the complete receipt and history repair, then apply "pm merge reconcile"; use --force only after explicitly accepting any discarded values.',"Custom item types registered later refresh this fence automatically (pm schema add-type/remove-type); rerun pm merge install only after out-of-band type-folder edits."],generated_at:nowIso()}}export{PM_GITATTRIBUTES_END,PM_GITATTRIBUTES_START,PM_GITATTRIBUTES_V2_END,PM_GITATTRIBUTES_V2_START,auditMergeAttributeFence,auditMergeDriverConfiguration,buildMergeAttributePatterns,findGitWorkspaceRoot,installMergeFence,refreshMergeAttributeFenceIfInstalled,resolveMergeInstallContext,resolveProjectMergeTypeFolders,runMergeInstall};
8
8
  //# sourceMappingURL=install.js.map
@@ -109,6 +109,10 @@ generated_at:string;
109
109
  export interface MergeReceiptEvidenceScan {
110
110
  /** Valid receipts that passed bounded-file, schema, and identity validation. */
111
111
  receipts:MergeDecisionReceipt[];
112
+ /** Valid counterparts with matching immutable provenance in the active Git index; absent evidence never authorizes a tracked history reference. */
113
+ durable_receipt_ids?:string[];
114
+ /** Valid filesystem sidecars not represented by matching indexed evidence; usable only for a read-only preview before guarded staging. */
115
+ unstaged_durable_receipt_ids?:string[];
112
116
  /** Number of JSON receipt candidates that could not be validated safely. */
113
117
  invalid_evidence_count:number;
114
118
  /** Bounded privacy-safe identities and reason codes for rejected evidence. */
@@ -178,6 +182,8 @@ includeReconciled?:boolean;
178
182
  includeLossless?:boolean;
179
183
  pmRoot?:string;
180
184
  }):Promise<MergeDecisionReceipt[]>;
185
+ /** Refuse audit references unless every requested identity has validated portable evidence. */
186
+ export declare function assertPortableMergeReceiptReferences(receiptIds:Iterable<string>,evidence:Pick<MergeReceiptEvidenceScan,"durable_receipt_ids" | "unstaged_durable_receipt_ids"> | undefined,preview:boolean):void;
181
187
  /** Split merge provenance into receipts with discarded values and receipts whose composition was lossless. */
182
188
  export declare function partitionMergeReceipts(receipts:MergeDecisionReceipt[]):{
183
189
  /** Receipts that require an explicit decision because at least one competing scalar value was discarded. */
@@ -1,5 +1,5 @@
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]="6e5e4691-4858-5a54-82b0-65b97d929b57")}catch{}})();import{execFile}from"node:child_process";import{randomUUID}from"node:crypto";import{readdir,stat}from"node:fs/promises";import path from"node:path";import{promisify}from"node:util";import{readHistoryEntries}from"../../core/history/read.js";import{verifyHistoryChainWithVersion}from"../../core/history/replay.js";import{ensureDir,isFileAbsentError,isFileMissingError,pathExists,writeFileAtomic}from"../../core/fs/fs-utils.js";import{sha256Hex,stableStringify}from"../../core/shared/serialization.js";import{nowIso}from"../../core/shared/time.js";import{encodeItemScalarDecisionValue,hashItemScalarDecisionValue,isItemScalarMissingValue}from"./three-way.js";import{readBoundedRegularFile}from"./receipt-file-boundary.js";import{DURABLE_VALUE_POLICY,durableValueEvidence,isSafeReceiptId,isMergeDecisionReceipt,isPrehashedValue,normalizeLegacyReceipt,receiptCollectionValidationPath,receiptValidationError}from"./receipt-schema.js";import{isSafeReceiptId as isSafeReceiptId2}from"./receipt-schema.js";const execFileAsync=promisify(execFile),RECEIPT_FILE_MAX_BYTES=16*1024*1024,RECEIPT_INVALID_EVIDENCE_DETAIL_LIMIT=100;async function resolveReceiptDirectory(cwd){try{const{stdout}=await execFileAsync("git",["rev-parse","--path-format=absolute","--git-path","pm-merge-receipts"],{cwd,encoding:"utf8",windowsHide:!0,timeout:1e4});return stdout.trim()}catch{return null}}async function resolveTrackerRootFromItemPath(cwd,itemPath){let directory=path.dirname(path.resolve(cwd,itemPath));for(let depth=0;depth<12;depth+=1){if(await pathExists(path.join(directory,"settings.json")))return directory;const parent=path.dirname(directory);if(parent===directory)break;directory=parent}return null}function durableReceiptDirectory(pmRoot){return path.join(pmRoot,"merge-receipts")}function receiptFileName(id){return`${id}.json`}async function prepareReceiptSettlement(params){let raw;try{raw=params.readReceipt?await params.readReceipt(params.receiptPath):await readBoundedRegularFile(params.receiptPath,RECEIPT_FILE_MAX_BYTES)}catch(error){if(isFileAbsentError(error))return null;throw error}if(raw===null)throw new Error(`Receipt ${params.receiptId} settlement source is not a bounded regular file.`);let parsed;try{parsed=normalizeLegacyReceipt(JSON.parse(raw))}catch{throw new Error(`Receipt ${params.receiptId} settlement source is not valid JSON.`)}if(!isMergeDecisionReceipt(parsed,params.evidenceSource)||parsed.id!==params.receiptId||params.settlement!==void 0&&parsed.operation===void 0||path.basename(params.receiptPath)!==receiptFileName(parsed.id))throw new Error(`Receipt ${params.receiptId} settlement source failed schema or identity validation.`);const{evidence_source:_evidenceSource,...persistedReceipt}=parsed;return{path:params.receiptPath,fingerprints:receiptProvenanceFingerprints(parsed),modern:parsed.value_policy!==void 0,content:`${JSON.stringify({...persistedReceipt,state:"reconciled",reconciled_at:params.reconciledAt,...params.settlement?{settlement:params.settlement}:{}},null,2)}
1
+ (function(){try{var e=typeof window<"u"?window:typeof global<"u"?global:typeof globalThis<"u"?globalThis:typeof self<"u"?self:{},n=new e.Error().stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="21c15a9e-da38-5fec-9a7d-57556f77ce0a")}catch{}})();import{execFile}from"node:child_process";import{randomUUID}from"node:crypto";import{readdir,stat}from"node:fs/promises";import path from"node:path";import{promisify}from"node:util";import{readHistoryEntries}from"../../core/history/read.js";import{verifyHistoryChainWithVersion}from"../../core/history/replay.js";import{ensureDir,isFileAbsentError,isFileMissingError,pathExists,writeFileAtomic}from"../../core/fs/fs-utils.js";import{sha256Hex,stableStringify}from"../../core/shared/serialization.js";import{nowIso}from"../../core/shared/time.js";import{EXIT_CODE}from"../../core/shared/constants.js";import{PmCliError}from"../../core/shared/errors.js";import{encodeItemScalarDecisionValue,hashItemScalarDecisionValue,isItemScalarMissingValue}from"./three-way.js";import{readBoundedRegularFile}from"./receipt-file-boundary.js";import{DURABLE_VALUE_POLICY,durableValueEvidence,isSafeReceiptId,isMergeDecisionReceipt,isPrehashedValue,normalizeLegacyReceipt,receiptCollectionValidationPath,receiptValidationError}from"./receipt-schema.js";import{isSafeReceiptId as isSafeReceiptId2}from"./receipt-schema.js";const execFileAsync=promisify(execFile),RECEIPT_FILE_MAX_BYTES=16*1024*1024,RECEIPT_INVALID_EVIDENCE_DETAIL_LIMIT=100;async function resolveReceiptDirectory(cwd){try{const{stdout}=await execFileAsync("git",["rev-parse","--path-format=absolute","--git-path","pm-merge-receipts"],{cwd,encoding:"utf8",windowsHide:!0,timeout:1e4});return stdout.trim()}catch{return null}}async function resolveTrackerRootFromItemPath(cwd,itemPath){let directory=path.dirname(path.resolve(cwd,itemPath));for(let depth=0;depth<12;depth+=1){if(await pathExists(path.join(directory,"settings.json")))return directory;const parent=path.dirname(directory);if(parent===directory)break;directory=parent}return null}function durableReceiptDirectory(pmRoot){return path.join(pmRoot,"merge-receipts")}function receiptFileName(id){return`${id}.json`}async function prepareReceiptSettlement(params){let raw;try{raw=params.readReceipt?await params.readReceipt(params.receiptPath):await readBoundedRegularFile(params.receiptPath,RECEIPT_FILE_MAX_BYTES)}catch(error){if(isFileAbsentError(error))return null;throw error}if(raw===null)throw new Error(`Receipt ${params.receiptId} settlement source is not a bounded regular file.`);let parsed;try{parsed=normalizeLegacyReceipt(JSON.parse(raw))}catch{throw new Error(`Receipt ${params.receiptId} settlement source is not valid JSON.`)}if(!isMergeDecisionReceipt(parsed,params.evidenceSource)||parsed.id!==params.receiptId||params.settlement!==void 0&&parsed.operation===void 0||path.basename(params.receiptPath)!==receiptFileName(parsed.id))throw new Error(`Receipt ${params.receiptId} settlement source failed schema or identity validation.`);const{evidence_source:_evidenceSource,...persistedReceipt}=parsed;return{path:params.receiptPath,fingerprints:receiptProvenanceFingerprints(parsed),modern:parsed.value_policy!==void 0,content:`${JSON.stringify({...persistedReceipt,state:"reconciled",reconciled_at:params.reconciledAt,...params.settlement?{settlement:params.settlement}:{}},null,2)}
2
2
  `}}function receiptProvenanceFingerprint(receipt,legacy=!1){return sha256Hex(stableStringify({id:receipt.id,item_id:receipt.item_id,item_path:receipt.item_path,created_at:receipt.created_at,requested_preference:receipt.requested_preference??receipt.preferred??"ours",requested_preference_applied:receipt.requested_preference_applied??(receipt.conflict_resolution??"preferred_side")==="preferred_side",conflict_resolution:receipt.conflict_resolution??"preferred_side",fields_from_theirs:receipt.fields_from_theirs,union_fields:receipt.union_fields,merged_field_hashes:receipt.merged_field_hashes??null,operation:receipt.operation??null,decisions:legacy?receipt.decisions.map(decision=>({field:decision.field,retained_hash:sha256Hex(stableStringify(encodeItemScalarDecisionValue(decision.retained))),discarded_hash:sha256Hex(stableStringify(encodeItemScalarDecisionValue(decision.discarded)))})):summarizeMergeReceipt(receipt).decisions}))}function receiptProvenanceFingerprints(receipt){const fingerprints=[receiptProvenanceFingerprint(receipt)];return receipt.value_policy===void 0&&(receipt.value_availability===void 0||receipt.value_availability==="clone_local")&&fingerprints.push(receiptProvenanceFingerprint(receipt,!0)),fingerprints}function mergeReceiptCopyLifecycle(local,durable){const{reconciled_at:_reconciledAt,settlement:_settlement,...pendingLocal}=local;return durable.state==="reconciled"?{...pendingLocal,state:"reconciled",reconciled_at:durable.reconciled_at,...durable.settlement===void 0?{}:{settlement:durable.settlement}}:{...pendingLocal,state:"pending"}}async function retainAuditedDurableSettlements(receipts,durableReceipts,pmRoot){const histories=new Map;for(const durable of durableReceipts){const receipt=receipts.get(durable.id);if(durable.state!=="reconciled"||receipt===void 0)continue;let entries=histories.get(receipt.item_id);if(entries===void 0){try{entries=await readHistoryEntries(path.join(pmRoot,"history",`${receipt.item_id}.jsonl`),receipt.item_id),verifyHistoryChainWithVersion(entries).ok||(entries=[])}catch{entries=[]}histories.set(receipt.item_id,entries)}const abandoned=receipt.settlement==="original_git_state_restored",expected=stableStringify({...summarizeMergeReceipt(receipt),...abandoned?{reason:receipt.settlement,operation:receipt.operation}:{}});if(!entries.some(entry=>{if(entry.op!=="merge_reconcile"||entry.record_hash_version===void 0)return!1;const merge=entry.context?.merge;if(typeof merge!="object"||merge===null||Array.isArray(merge))return!1;const summaries=merge[abandoned?"abandoned_receipts":"receipts"];return Array.isArray(summaries)&&summaries.some(summary=>stableStringify(summary)===expected)})){const{reconciled_at:_reconciledAt,settlement:_settlement,...pending}=receipt;receipts.set(receipt.id,{...pending,state:"pending"})}}}function summarizeMergeReceipt(receipt){return{receipt_id:receipt.id,item_id:receipt.item_id,item_path:receipt.item_path,conflict_fields:receipt.decisions.map(decision=>decision.field),fields_from_theirs:receipt.fields_from_theirs,union_fields:receipt.union_fields,requested_preference:receipt.requested_preference??receipt.preferred??"ours",requested_preference_applied:receipt.requested_preference_applied??(receipt.conflict_resolution??"preferred_side")==="preferred_side",conflict_resolution:receipt.conflict_resolution,decisions:receipt.decisions.map(decision=>({field:decision.field,retained_hash:isPrehashedValue(decision.retained)??hashItemScalarDecisionValue(isItemScalarMissingValue(decision.retained)?void 0:decision.retained),discarded_hash:isPrehashedValue(decision.discarded)??hashItemScalarDecisionValue(isItemScalarMissingValue(decision.discarded)?void 0:decision.discarded)}))}}async function writeMergeReceipt(params){const directory=await resolveReceiptDirectory(params.cwd);if(directory===null)return null;const trimmedItemPath=params.itemPath.trim(),itemPath=/^(['"])(.*)\1$/su.exec(trimmedItemPath)?.[2]??trimmedItemPath,itemId=path.basename(itemPath,path.extname(itemPath)),receipt={version:1,id:randomUUID(),item_path:itemPath.replaceAll("\\","/"),item_id:itemId,requested_preference:params.preferred,requested_preference_applied:(params.conflictResolution??"preferred_side")==="preferred_side",conflict_resolution:params.conflictResolution??"preferred_side",fields_from_theirs:[...params.fieldsFromTheirs],union_fields:[...params.unionFields],...params.mergedFieldHashes?{merged_field_hashes:{...params.mergedFieldHashes}}:{},decisions:structuredClone(params.decisions).map(decision=>({...decision,base:encodeItemScalarDecisionValue(decision.base),ours:encodeItemScalarDecisionValue(decision.ours),theirs:encodeItemScalarDecisionValue(decision.theirs),retained:encodeItemScalarDecisionValue(decision.retained),discarded:encodeItemScalarDecisionValue(decision.discarded)})),...params.operation?{operation:params.operation}:{},state:"pending",created_at:nowIso(),value_availability:"clone_local"};await ensureDir(directory),await writeFileAtomic(path.join(directory,receiptFileName(receipt.id)),`${JSON.stringify(receipt,null,2)}
3
3
  `);const trackerRoot=await resolveTrackerRootFromItemPath(params.cwd,receipt.item_path);if(trackerRoot!==null){const durableDirectory=durableReceiptDirectory(trackerRoot),durableDecisions=receipt.decisions.map(decision=>({field:decision.field,base:null,ours:null,theirs:null,retained:durableValueEvidence(decision.field,decision.retained),discarded:durableValueEvidence(decision.field,decision.discarded),retained_side:decision.retained_side,resolution_basis:decision.resolution_basis})),inlineValueCount=durableDecisions.reduce((count,decision)=>count+(Object.hasOwn(decision.retained,"pm_value")?1:0)+(Object.hasOwn(decision.discarded,"pm_value")?1:0),0),durableReceipt={...receipt,decisions:durableDecisions,value_availability:inlineValueCount===0?"hash_only":inlineValueCount===durableDecisions.length*2?"bounded_inline":"mixed",value_policy:DURABLE_VALUE_POLICY};await ensureDir(durableDirectory),await writeFileAtomic(path.join(durableDirectory,receiptFileName(receipt.id)),`${JSON.stringify(durableReceipt,null,2)}
4
- `)}return receipt}async function readReceiptsFromDirectory(directory,evidenceSource){let names;try{names=await readdir(directory)}catch(error){const absent=await receiptDirectoryFailureMeansAbsent(directory,error);return{receipts:[],invalid_evidence_count:absent?0:1,invalid_evidence:absent?[]:[{evidence_source:evidenceSource,reason:"directory_unreadable"}],invalid_evidence_truncated:!1}}const receipts=[];let invalidEvidenceCount=0;const invalidEvidence=[],recordInvalidEvidence=(name,candidate)=>{if(invalidEvidenceCount+=1,invalidEvidence.length>=RECEIPT_INVALID_EVIDENCE_DETAIL_LIMIT)return;const receiptId=name.slice(0,-5);invalidEvidence.push({evidence_source:evidenceSource,reason:candidate.reason,...candidate.validation_path?{validation_path:candidate.validation_path}:{},...candidate.validation_error===void 0?{}:{validation_error:candidate.validation_error},...isSafeReceiptId(receiptId)?{receipt_id:receiptId}:{candidate_name_hash:sha256Hex(name)}})};for(const name of names.sort((left,right)=>left.localeCompare(right))){if(!name.endsWith(".json"))continue;const candidate=await inspectReceiptCandidate(directory,name,evidenceSource);candidate.receipt!==void 0?receipts.push(candidate.receipt):recordInvalidEvidence(name,candidate)}return{receipts,invalid_evidence_count:invalidEvidenceCount,invalid_evidence:invalidEvidence,invalid_evidence_truncated:invalidEvidenceCount>invalidEvidence.length}}async function receiptDirectoryFailureMeansAbsent(directory,error,inspectAncestor=stat){if(!isFileMissingError(error))return!1;let ancestor=directory;for(;;)try{return(await inspectAncestor(ancestor)).isDirectory()}catch(ancestorError){if(!isFileMissingError(ancestorError))return!1;const parent=path.dirname(ancestor);if(parent===ancestor)return!0;ancestor=parent}}async function inspectReceiptCandidate(directory,name,evidenceSource){let raw;try{raw=await readBoundedRegularFile(path.join(directory,name),RECEIPT_FILE_MAX_BYTES)}catch{return{reason:"candidate_unreadable"}}if(raw===null)return{reason:"candidate_not_bounded_regular_file"};let parsed;try{parsed=normalizeLegacyReceipt(JSON.parse(raw))}catch{return{reason:"candidate_invalid_json"}}const validationError=receiptValidationError(parsed,evidenceSource);if(validationError!==null)return{reason:new Set(["receipt_id","item_id","item_path"]).has(validationError)?"identity_invalid":"schema_invalid",validation_error:validationError,...validationError==="collections"?{validation_path:receiptCollectionValidationPath(parsed)}:{}};const receipt=parsed;if(name!==receiptFileName(receipt.id))return{reason:"identity_invalid",validation_error:"filename"};const{preferred:legacyPreference,evidence_source:_serializedEvidenceSource,...receiptWithoutRuntimeKeys}=receipt;return{receipt:{...receiptWithoutRuntimeKeys,requested_preference:receipt.requested_preference??legacyPreference??"ours",requested_preference_applied:receipt.requested_preference_applied??(receipt.conflict_resolution??"preferred_side")==="preferred_side",conflict_resolution:receipt.conflict_resolution??"preferred_side",evidence_source:evidenceSource}}}async function inspectMergeReceiptEvidence(cwd,options={}){const directory=await resolveReceiptDirectory(cwd),local=directory===null?{receipts:[],invalid_evidence_count:0,invalid_evidence:[],invalid_evidence_truncated:!1}:await readReceiptsFromDirectory(directory,"clone_local"),trackerRoot=options.pmRoot??path.join(cwd,".agents","pm"),durable=await readReceiptsFromDirectory(durableReceiptDirectory(trackerRoot),"durable"),receipts=new Map(durable.receipts.map(receipt=>[receipt.id,receipt]));let divergentCopyCount=0;const divergentCopyEvidence=[];for(const receipt of local.receipts){const durableCopy=receipts.get(receipt.id);if(durableCopy!==void 0&&!(durableCopy.value_policy===void 0?receiptProvenanceFingerprints(receipt):[receiptProvenanceFingerprint(receipt)]).includes(receiptProvenanceFingerprint(durableCopy))){receipts.delete(receipt.id),divergentCopyCount+=1,local.invalid_evidence.length+durable.invalid_evidence.length+divergentCopyEvidence.length<RECEIPT_INVALID_EVIDENCE_DETAIL_LIMIT&&divergentCopyEvidence.push({evidence_source:"clone_local_and_durable",reason:"copy_provenance_mismatch",receipt_id:receipt.id});continue}receipts.set(receipt.id,durableCopy===void 0?receipt:mergeReceiptCopyLifecycle(receipt,durableCopy))}return await retainAuditedDurableSettlements(receipts,durable.receipts,trackerRoot),{receipts:[...receipts.values()].filter(receipt=>(options.includeReconciled||receipt.state==="pending")&&(options.includeLossless!==!1||receipt.decisions.length>0)).sort((left,right)=>left.created_at.localeCompare(right.created_at)),invalid_evidence_count:local.invalid_evidence_count+durable.invalid_evidence_count+divergentCopyCount,invalid_evidence:[...local.invalid_evidence,...durable.invalid_evidence,...divergentCopyEvidence].slice(0,RECEIPT_INVALID_EVIDENCE_DETAIL_LIMIT),invalid_evidence_truncated:local.invalid_evidence_truncated||durable.invalid_evidence_truncated||local.invalid_evidence_count+durable.invalid_evidence_count+divergentCopyCount>RECEIPT_INVALID_EVIDENCE_DETAIL_LIMIT,clone_local_evidence_resolved:directory!==null}}async function listMergeReceipts(cwd,options={}){return(await inspectMergeReceiptEvidence(cwd,options)).receipts}function partitionMergeReceipts(receipts){const pendingDecisions=[],lossless=[];for(const receipt of receipts)(Array.isArray(receipt.decisions)&&receipt.decisions.length>0?pendingDecisions:lossless).push(receipt);return{pendingDecisions,lossless}}async function markMergeReceiptReconciled(cwd,receipt,options={}){const directory=await resolveReceiptDirectory(cwd);if(directory===null){if(options.requireExisting===!0)throw new Error(`Receipt ${receipt.id} disappeared before settlement.`);return}const receiptEvidenceSource=receipt.evidence_source??"clone_local";if(!isMergeDecisionReceipt(receipt,receiptEvidenceSource))throw new Error("Merge receipt trusted settlement input failed schema validation.");const reconciledAt=nowIso(),localPath=path.join(directory,receiptFileName(receipt.id)),trackerRoot=await resolveTrackerRootFromItemPath(cwd,receipt.item_path),durablePath=trackerRoot===null?null:path.join(durableReceiptDirectory(trackerRoot),receiptFileName(receipt.id)),writes=(await Promise.all([prepareReceiptSettlement({receiptPath:localPath,receiptId:receipt.id,evidenceSource:"clone_local",reconciledAt,settlement:options.settlement}),...durablePath===null?[]:[prepareReceiptSettlement({receiptPath:durablePath,receiptId:receipt.id,evidenceSource:"durable",reconciledAt,settlement:options.settlement})]])).filter(entry=>entry!==null);if(writes.length===0){if(options.requireExisting===!0)throw new Error(`Receipt ${receipt.id} disappeared before settlement.`);return}if(!(writes.some(write=>write.modern)?[receiptProvenanceFingerprint(receipt)]:receiptProvenanceFingerprints(receipt)).some(fingerprint=>writes.every(write=>write.fingerprints.includes(fingerprint))))throw new Error(`Receipt ${receipt.id} settlement copies disagree on immutable merge provenance.`);for(const write of writes)await writeFileAtomic(write.path,write.content)}async function runMergeReceiptReport(options){const receipts=await listMergeReceipts(options.cwd??process.cwd(),{...options,includeLossless:!0});return{ok:!0,count:receipts.length,receipts,generated_at:nowIso()}}async function runMergeReceiptEvidenceReport(options){const evidence=await inspectMergeReceiptEvidence(options.cwd??process.cwd(),{...options,includeLossless:!0}),complete=evidence.clone_local_evidence_resolved===!0&&evidence.invalid_evidence_count===0;return{ok:complete,complete,count:evidence.receipts.length,invalid_evidence_count:evidence.invalid_evidence_count,invalid_evidence:evidence.invalid_evidence,invalid_evidence_truncated:evidence.invalid_evidence_truncated,clone_local_evidence_resolved:evidence.clone_local_evidence_resolved,receipts:evidence.receipts,generated_at:nowIso()}}const _testOnlyMergeReceipts={prepareReceiptSettlement,receiptDirectoryFailureMeansAbsent};export{_testOnlyMergeReceipts,inspectMergeReceiptEvidence,isSafeReceiptId2 as isSafeReceiptId,listMergeReceipts,markMergeReceiptReconciled,partitionMergeReceipts,runMergeReceiptEvidenceReport,runMergeReceiptReport,summarizeMergeReceipt,writeMergeReceipt};
4
+ `)}return receipt}async function readReceiptsFromDirectory(directory,evidenceSource){let names;try{names=await readdir(directory)}catch(error){const absent=await receiptDirectoryFailureMeansAbsent(directory,error);return{receipts:[],invalid_evidence_count:absent?0:1,invalid_evidence:absent?[]:[{evidence_source:evidenceSource,reason:"directory_unreadable"}],invalid_evidence_truncated:!1}}const receipts=[];let invalidEvidenceCount=0;const invalidEvidence=[],recordInvalidEvidence=(name,candidate)=>{if(invalidEvidenceCount+=1,invalidEvidence.length>=RECEIPT_INVALID_EVIDENCE_DETAIL_LIMIT)return;const receiptId=name.slice(0,-5);invalidEvidence.push({evidence_source:evidenceSource,reason:candidate.reason,...candidate.validation_path?{validation_path:candidate.validation_path}:{},...candidate.validation_error===void 0?{}:{validation_error:candidate.validation_error},...isSafeReceiptId(receiptId)?{receipt_id:receiptId}:{candidate_name_hash:sha256Hex(name)}})};for(const name of names.sort((left,right)=>left.localeCompare(right))){if(!name.endsWith(".json"))continue;const candidate=await inspectReceiptCandidate(directory,name,evidenceSource);candidate.receipt!==void 0?receipts.push(candidate.receipt):recordInvalidEvidence(name,candidate)}return{receipts,invalid_evidence_count:invalidEvidenceCount,invalid_evidence:invalidEvidence,invalid_evidence_truncated:invalidEvidenceCount>invalidEvidence.length}}async function receiptDirectoryFailureMeansAbsent(directory,error,inspectAncestor=stat){if(!isFileMissingError(error))return!1;let ancestor=directory;for(;;)try{return(await inspectAncestor(ancestor)).isDirectory()}catch(ancestorError){if(!isFileMissingError(ancestorError))return!1;const parent=path.dirname(ancestor);if(parent===ancestor)return!0;ancestor=parent}}async function inspectReceiptCandidate(directory,name,evidenceSource){let raw;try{raw=await readBoundedRegularFile(path.join(directory,name),RECEIPT_FILE_MAX_BYTES)}catch{return{reason:"candidate_unreadable"}}if(raw===null)return{reason:"candidate_not_bounded_regular_file"};let parsed;try{parsed=normalizeLegacyReceipt(JSON.parse(raw))}catch{return{reason:"candidate_invalid_json"}}const validationError=receiptValidationError(parsed,evidenceSource);if(validationError!==null)return{reason:new Set(["receipt_id","item_id","item_path"]).has(validationError)?"identity_invalid":"schema_invalid",validation_error:validationError,...validationError==="collections"?{validation_path:receiptCollectionValidationPath(parsed)}:{}};const receipt=parsed;if(name!==receiptFileName(receipt.id))return{reason:"identity_invalid",validation_error:"filename"};const{preferred:legacyPreference,evidence_source:_serializedEvidenceSource,...receiptWithoutRuntimeKeys}=receipt;return{receipt:{...receiptWithoutRuntimeKeys,requested_preference:receipt.requested_preference??legacyPreference??"ours",requested_preference_applied:receipt.requested_preference_applied??(receipt.conflict_resolution??"preferred_side")==="preferred_side",conflict_resolution:receipt.conflict_resolution??"preferred_side",evidence_source:evidenceSource}}}async function indexedDurableReceiptIds(cwd,trackerRoot,receipts){if(receipts.length===0)return[];const{readIndexedReceiptBlobs}=await import("./indexed-receipt-blobs.js"),blobs=await readIndexedReceiptBlobs(cwd,durableReceiptDirectory(trackerRoot),receipts.map(receipt=>receiptFileName(receipt.id)),RECEIPT_FILE_MAX_BYTES);return receipts.flatMap(receipt=>{try{const indexed=normalizeLegacyReceipt(JSON.parse(blobs.get(receiptFileName(receipt.id))??""));if(receiptValidationError(indexed,"durable")!==null)return[];const stored=indexed,fingerprints=receiptProvenanceFingerprints(receipt);return stored.id===receipt.id&&receiptProvenanceFingerprints(stored).some(fingerprint=>fingerprints.includes(fingerprint))?[receipt.id]:[]}catch{return[]}})}async function inspectMergeReceiptEvidence(cwd,options={}){const directory=await resolveReceiptDirectory(cwd),local=directory===null?{receipts:[],invalid_evidence_count:0,invalid_evidence:[],invalid_evidence_truncated:!1}:await readReceiptsFromDirectory(directory,"clone_local"),trackerRoot=options.pmRoot??path.join(cwd,".agents","pm"),durable=await readReceiptsFromDirectory(durableReceiptDirectory(trackerRoot),"durable"),receipts=new Map(durable.receipts.map(receipt=>[receipt.id,receipt]));let divergentCopyCount=0;const divergentCopyEvidence=[];for(const receipt of local.receipts){const durableCopy=receipts.get(receipt.id);if(durableCopy!==void 0&&!(durableCopy.value_policy===void 0?receiptProvenanceFingerprints(receipt):[receiptProvenanceFingerprint(receipt)]).includes(receiptProvenanceFingerprint(durableCopy))){receipts.delete(receipt.id),divergentCopyCount+=1,local.invalid_evidence.length+durable.invalid_evidence.length+divergentCopyEvidence.length<RECEIPT_INVALID_EVIDENCE_DETAIL_LIMIT&&divergentCopyEvidence.push({evidence_source:"clone_local_and_durable",reason:"copy_provenance_mismatch",receipt_id:receipt.id});continue}receipts.set(receipt.id,durableCopy===void 0?receipt:mergeReceiptCopyLifecycle(receipt,durableCopy))}await retainAuditedDurableSettlements(receipts,durable.receipts,trackerRoot);const validDurable=durable.receipts.filter(receipt=>receipts.has(receipt.id)),indexedIds=new Set(await indexedDurableReceiptIds(cwd,trackerRoot,validDurable));return{durable_receipt_ids:[...indexedIds],unstaged_durable_receipt_ids:validDurable.filter(receipt=>!indexedIds.has(receipt.id)).map(receipt=>receipt.id),receipts:[...receipts.values()].filter(receipt=>(options.includeReconciled||receipt.state==="pending")&&(options.includeLossless!==!1||receipt.decisions.length>0)).sort((left,right)=>left.created_at.localeCompare(right.created_at)),invalid_evidence_count:local.invalid_evidence_count+durable.invalid_evidence_count+divergentCopyCount,invalid_evidence:[...local.invalid_evidence,...durable.invalid_evidence,...divergentCopyEvidence].slice(0,RECEIPT_INVALID_EVIDENCE_DETAIL_LIMIT),invalid_evidence_truncated:local.invalid_evidence_truncated||durable.invalid_evidence_truncated||local.invalid_evidence_count+durable.invalid_evidence_count+divergentCopyCount>RECEIPT_INVALID_EVIDENCE_DETAIL_LIMIT,clone_local_evidence_resolved:directory!==null}}async function listMergeReceipts(cwd,options={}){return(await inspectMergeReceiptEvidence(cwd,options)).receipts}function assertPortableMergeReceiptReferences(receiptIds,evidence,preview){const portable=new Set([...evidence?.durable_receipt_ids??[],...preview?evidence?.unstaged_durable_receipt_ids??[]:[]]),nonportable=[...receiptIds].filter(id=>!portable.has(id));if(nonportable.length!==0)throw new PmCliError(`Merge reconciliation refused ${nonportable.length} receipt(s) without validated tracked evidence; no audit history was written.`,EXIT_CODE.CONFLICT,{code:"merge_receipt_not_portable",required:"Every receipt cited by tracked history must have matching validated evidence in the Git index before any audit write.",recovery:{suggested_retry:"pm merge report --include-reconciled"},nextSteps:["Stage valid project sidecars after reviewing git diff, or use pm merge reconcile --stage for guarded lossless settlement. Restore missing original counterparts or preserve unrelated stale receipts outside the active store. Never copy private clone-local values into tracked evidence. Then rerun pm merge reconcile --dry-run."]})}function partitionMergeReceipts(receipts){const pendingDecisions=[],lossless=[];for(const receipt of receipts)(Array.isArray(receipt.decisions)&&receipt.decisions.length>0?pendingDecisions:lossless).push(receipt);return{pendingDecisions,lossless}}async function markMergeReceiptReconciled(cwd,receipt,options={}){const directory=await resolveReceiptDirectory(cwd);if(directory===null){if(options.requireExisting===!0)throw new Error(`Receipt ${receipt.id} disappeared before settlement.`);return}const receiptEvidenceSource=receipt.evidence_source??"clone_local";if(!isMergeDecisionReceipt(receipt,receiptEvidenceSource))throw new Error("Merge receipt trusted settlement input failed schema validation.");const reconciledAt=nowIso(),localPath=path.join(directory,receiptFileName(receipt.id)),trackerRoot=await resolveTrackerRootFromItemPath(cwd,receipt.item_path),durablePath=trackerRoot===null?null:path.join(durableReceiptDirectory(trackerRoot),receiptFileName(receipt.id)),writes=(await Promise.all([prepareReceiptSettlement({receiptPath:localPath,receiptId:receipt.id,evidenceSource:"clone_local",reconciledAt,settlement:options.settlement}),...durablePath===null?[]:[prepareReceiptSettlement({receiptPath:durablePath,receiptId:receipt.id,evidenceSource:"durable",reconciledAt,settlement:options.settlement})]])).filter(entry=>entry!==null);if(writes.length===0){if(options.requireExisting===!0)throw new Error(`Receipt ${receipt.id} disappeared before settlement.`);return}if(!(writes.some(write=>write.modern)?[receiptProvenanceFingerprint(receipt)]:receiptProvenanceFingerprints(receipt)).some(fingerprint=>writes.every(write=>write.fingerprints.includes(fingerprint))))throw new Error(`Receipt ${receipt.id} settlement copies disagree on immutable merge provenance.`);for(const write of writes)await writeFileAtomic(write.path,write.content)}async function runMergeReceiptReport(options){const receipts=await listMergeReceipts(options.cwd??process.cwd(),{...options,includeLossless:!0});return{ok:!0,count:receipts.length,receipts,generated_at:nowIso()}}async function runMergeReceiptEvidenceReport(options){const evidence=await inspectMergeReceiptEvidence(options.cwd??process.cwd(),{...options,includeLossless:!0}),complete=evidence.clone_local_evidence_resolved===!0&&evidence.invalid_evidence_count===0;return{ok:complete,complete,count:evidence.receipts.length,invalid_evidence_count:evidence.invalid_evidence_count,invalid_evidence:evidence.invalid_evidence,invalid_evidence_truncated:evidence.invalid_evidence_truncated,clone_local_evidence_resolved:evidence.clone_local_evidence_resolved,receipts:evidence.receipts,generated_at:nowIso()}}const _testOnlyMergeReceipts={prepareReceiptSettlement,receiptDirectoryFailureMeansAbsent};export{_testOnlyMergeReceipts,assertPortableMergeReceiptReferences,inspectMergeReceiptEvidence,isSafeReceiptId2 as isSafeReceiptId,listMergeReceipts,markMergeReceiptReconciled,partitionMergeReceipts,runMergeReceiptEvidenceReport,runMergeReceiptReport,summarizeMergeReceipt,writeMergeReceipt};
5
5
  //# sourceMappingURL=receipts.js.map
@@ -12,6 +12,8 @@ author?:string;
12
12
  message?:string;
13
13
  /** Permit ownership overrides already supported by history repair. */
14
14
  force?:boolean;
15
+ /** Stage only authenticated lossless audit artifacts, refusing force, conflicts and unrelated drift. */
16
+ stage?:boolean;
15
17
  }
16
18
  /** Structured post-merge reconciliation and verification result. */
17
19
  export interface MergeReconcileResult {
@@ -32,6 +34,8 @@ reconciled:number;
32
34
  /** Receipts proven to restore their original Git state; these do not represent applied merges. */
33
35
  abandoned:number;
34
36
  summaries:MergeDecisionReceiptSummary[];
37
+ /** Valid sidecars awaiting matching Git index evidence; a preview never authorizes audit writes. */
38
+ unstaged_durable_receipt_ids?:string[];
35
39
  /** Missing durable references discovered before this reconciliation. */
36
40
  missing_history_references_before:number;
37
41
  /** Pre-durable references eligible for explicit audited disposition. */
@@ -47,6 +51,8 @@ validation:ValidateResult;
47
51
  guidance:string[];
48
52
  /** ISO timestamp copied from the validation pass. */
49
53
  generated_at:string;
54
+ /** Repository-relative artifacts staged by an explicit stage request; dry runs return an empty list. */
55
+ staged_paths?:string[];
50
56
  }
51
57
  /**
52
58
  * Preview or apply post-merge history reconciliation and immediately validate
@@ -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]="7b1ffef4-9e2b-5ff9-8b8c-030ea89ea27e")}catch{}})();import{settleAbandonedMergeReceipts}from"./abandoned-receipts.js";import{readFile}from"node:fs/promises";import path from"node:path";import{EXIT_CODE}from"../../core/shared/constants.js";import{PmCliError}from"../../core/shared/errors.js";import{compareTimestampStrings}from"../../core/shared/time.js";import{resolvePmRoot}from"../../core/store/paths.js";import{mapWithFixedConcurrency}from"../extension/concurrency.js";import{runHealth}from"../governance/health.js";import{DURABLE_MERGE_RECEIPT_INTRODUCED_AT,extractHistoryMergeReceiptReferences,isPreDurableDispositionEligibleReference}from"../governance/merge-receipt-history.js";import{runValidate}from"../governance/validate.js";import{runHistoryRepair,runHistoryRepairAll}from"../history-repair.js";import{findGitWorkspaceRoot}from"./install.js";import{inspectMergeReceiptEvidence,markMergeReceiptReconciled,summarizeMergeReceipt}from"./receipts.js";const RECEIPT_ONLY_REPAIR_CONCURRENCY=4;function missingReceiptCoordinates(health){const integrity=health.checks.find(check=>check.name==="integrity"),counts=integrity?.details.counts,count=typeof counts=="object"&&counts!==null&&!Array.isArray(counts)&&typeof counts.missing_merge_receipt_history_references=="number"?counts.missing_merge_receipt_history_references:0,details=integrity?.details.missing_merge_receipt_history_reference_details,truncated=integrity?.details.missing_merge_receipt_history_reference_details_truncated===!0,coordinates=Array.isArray(details)?details.flatMap(detail=>{if(typeof detail!="object"||detail===null||Array.isArray(detail))return[];const record=detail;return typeof record.item_id=="string"&&typeof record.history_line=="number"&&Number.isSafeInteger(record.history_line)&&typeof record.receipt_id=="string"?[{item_id:record.item_id,history_line:record.history_line,receipt_id:record.receipt_id}]:[]}):[];return{count,truncated,coordinates}}async function identifyLegacyDispositionCandidates(pmRoot,coordinates){const candidates=[];for(const coordinate of coordinates){let raw;try{raw=await readFile(path.join(pmRoot,"history",`${coordinate.item_id}.jsonl`),"utf8")}catch{continue}const line=raw.split(/\r?\n/)[coordinate.history_line-1];if(line===void 0||line.trim().length===0)continue;let parsed;try{parsed=JSON.parse(line)}catch{continue}const reference=extractHistoryMergeReceiptReferences(parsed,coordinate.item_id).find(entry=>entry.receiptId===coordinate.receipt_id);reference===void 0||!isPreDurableDispositionEligibleReference(reference)||reference.eventTimestamp===void 0||compareTimestampStrings(reference.eventTimestamp,DURABLE_MERGE_RECEIPT_INTRODUCED_AT)>=0||candidates.push({...coordinate,original_event_ts:reference.eventTimestamp})}return candidates}async function recordLegacyReceiptDispositions(params){if(params.options.dryRun===!0||params.options.force!==!0||params.candidates.length===0)return 0;const byItem=new Map;for(const candidate of params.candidates){const itemCandidates=byItem.get(candidate.item_id)??[];itemCandidates.push(candidate),byItem.set(candidate.item_id,itemCandidates)}for(const[itemId,itemCandidates]of byItem)await runHistoryRepair(itemId,{author:params.options.author??params.global.author,message:params.options.message??"accept unrecoverable pre-durable merge receipt references",force:!0,auditOperation:"merge_reconcile",forceAuditEntry:!0,auditContext:{merge:{missing_receipt_dispositions:itemCandidates.map(candidate=>({receipt_id:candidate.receipt_id,original_history_line:candidate.history_line,original_event_ts:candidate.original_event_ts,reason:"legacy_clone_local_only"}))}}},params.global);return params.candidates.length}async function runReceiptOnlyRepair(params){try{return{status:"fulfilled",value:await runHistoryRepair(params.id,{dryRun:params.dryRun,author:params.options.author??params.global.author,message:params.options.message??"record field-aware branch merge provenance",force:params.options.force,auditOperation:"merge_reconcile",auditContext:params.auditContext,forceAuditEntry:!0,mergeReceiptProof:params.mergeReceiptProof},params.global)}}catch(reason){return{status:"rejected",reason}}}function appendReceiptOnlyResults(params){params.results.forEach((settled,index)=>{const id=params.receiptOnlyIds[index];if(settled.status==="fulfilled"){const outcome=settled.value.changed?"repaired":"skipped_clean";params.repair.streams.push({id,outcome,entries_rehashed:settled.value.history.entries_rehashed,entries_patch_repaired:settled.value.history.entries_patch_repaired,reconciled_with_item:settled.value.history.reconciled_with_item,...settled.value.merge_receipt_proof?{merge_receipt_proof:settled.value.merge_receipt_proof}:{},warnings:settled.value.warnings}),params.repair.totals[outcome]+=1;return}params.repair.streams.push({id,outcome:"failed",error:settled.reason instanceof Error?settled.reason.message:String(settled.reason)}),params.repair.totals.failed+=1})}async function settleProvenReceipts(params){if(params.dryRun||params.repair.totals.failed>0)return 0;const trustedReceiptIds=new Set(params.repair.streams.flatMap(stream=>stream.merge_receipt_proof?.trusted?stream.merge_receipt_proof.receipt_ids:[])),receiptsToSettle=params.pendingReceipts.filter(receipt=>trustedReceiptIds.has(receipt.id));return await Promise.all(receiptsToSettle.map(receipt=>markMergeReceiptReconciled(params.receiptWorkspaceRoot,receipt,{requireExisting:!0}))),receiptsToSettle.length}function receiptReviewGuidance(pendingCount,complete){if(!(pendingCount===0||complete))return'If receipt proof is unavailable or competing values were discarded, first review receipts.summaries and repair.streams and re-apply intended values. Only with explicit authority to accept that review, run pm merge reconcile --force --message "<reviewed receipt disposition and rationale>". This appends an audit event; it does not restore missing evidence or discarded values.'}async function runMergeReconcile(options,global2){const dryRun=options.dryRun===!0,pmRoot=resolvePmRoot(process.cwd(),global2.path),preflightHealth=await runHealth(global2,{checkOnly:!0,full:!0,skipVectors:!0,skipDrift:!0}),missingBefore=missingReceiptCoordinates(preflightHealth),legacyDispositionCandidates=await identifyLegacyDispositionCandidates(pmRoot,missingBefore.coordinates),gitWorkspaceRoot=await findGitWorkspaceRoot(pmRoot),receiptWorkspaceRoot=gitWorkspaceRoot??pmRoot,receiptEvidence=await inspectMergeReceiptEvidence(receiptWorkspaceRoot,{includeLossless:!0,pmRoot});if(receiptEvidence.invalid_evidence_count>0)throw new PmCliError(`Merge reconciliation refused ${receiptEvidence.invalid_evidence_count} invalid receipt evidence file(s).`,EXIT_CODE.CONFLICT,{code:"merge_receipt_evidence_invalid",required:"Every clone-local and durable receipt candidate must pass bounded-file, schema, and identity validation.",recovery:{suggested_retry:"pm health --check-only --full"}});const recordedLegacyDispositionCount=await recordLegacyReceiptDispositions({candidates:legacyDispositionCandidates,options,global:global2}),pendingReceipts=receiptEvidence.receipts,abandonedReceiptIds=await settleAbandonedMergeReceipts({cwd:gitWorkspaceRoot,receipts:pendingReceipts,dryRun,author:options.author,message:options.message,global:global2}),abandonedIds=new Set(abandonedReceiptIds),receiptsByItem=new Map;for(const receipt of pendingReceipts.filter(entry=>!abandonedIds.has(entry.id))){const receipts=receiptsByItem.get(receipt.item_id)??[];receipts.push(receipt),receiptsByItem.set(receipt.item_id,receipts)}const auditContextById=Object.fromEntries([...receiptsByItem].map(([id,receipts])=>[id,{merge:{receipts:receipts.map(summarizeMergeReceipt)}}])),mergeReceiptProofById=Object.fromEntries([...receiptsByItem].map(([id,receipts])=>[id,{gitWorkspaceRoot,receipts}])),repair=await runHistoryRepairAll({dryRun,author:options.author??global2.author,message:options.message??"post-merge reconciliation of field-aware tracker history",force:options.force,auditOperation:"merge_reconcile",auditContextById,...pendingReceipts.length>0?{mergeReceiptProofById}:{}},global2),representedIds=new Set(repair.streams.map(stream=>stream.id)),receiptOnlyIds=[...receiptsByItem.keys()].filter(id=>!representedIds.has(id)),receiptOnlyResults=await mapWithFixedConcurrency(receiptOnlyIds,RECEIPT_ONLY_REPAIR_CONCURRENCY,id=>runReceiptOnlyRepair({id,dryRun,options,global:global2,auditContext:auditContextById[id],mergeReceiptProof:mergeReceiptProofById[id]}));appendReceiptOnlyResults({receiptOnlyIds,results:receiptOnlyResults,repair});const reconciledReceiptCount=await settleProvenReceipts({dryRun,pendingReceipts,repair,receiptWorkspaceRoot}),validation=await runValidate({checkHistoryDrift:!0,checkStorageIntegrity:!0},global2),postflightHealth=recordedLegacyDispositionCount===0?preflightHealth:await runHealth(global2,{checkOnly:!0,full:!0,skipVectors:!0,skipDrift:!0}),missingAfter=missingReceiptCoordinates(postflightHealth),mergeChecksGreen=validation.checks.every(check=>check.status==="ok"),receiptSettlementComplete=dryRun?pendingReceipts.length===0:reconciledReceiptCount+abandonedReceiptIds.length===pendingReceipts.length,ok=[repair.totals.failed===0,mergeChecksGreen,receiptSettlementComplete,missingAfter.count===0,!missingAfter.truncated].every(Boolean),boundedBatchGuidance=missingBefore.truncated||missingAfter.truncated?"Missing receipt coordinates exceed the bounded detail response; each reviewed --force pass can disposition only the reported eligible batch. Repeat dry-run and --force passes until receipts.missing_history_references_after=0 and pm merge reconcile returns ok=true.":void 0,explicitReviewGuidance=receiptReviewGuidance(pendingReceipts.length,ok),guidance=(dryRun?["Review repair.streams, then rerun pm merge reconcile without --dry-run to apply audited repairs.",boundedBatchGuidance,explicitReviewGuidance,"No Git hook is installed automatically; invoke this command from an explicit post-merge hook only when your repository policy opts in."]:ok?["Reconciliation is complete; commit changed history streams with the merge result.","Rerun pm merge reconcile --dry-run after future tracker-data merges as a non-mutating integrity check."]:["Reconciliation remains incomplete; inspect repair failures and non-green validation checks before retrying.",boundedBatchGuidance,explicitReviewGuidance,...legacyDispositionCandidates.length>0&&recordedLegacyDispositionCount===0?["Pre-durable clone-local-only receipt references are eligible for an audited disposition; rerun with --force after reviewing receipts.legacy_disposition_eligible."]:[],"Do not commit repaired history streams as reconciled until pm merge reconcile returns ok=true."]).filter(message=>message!==void 0);return{ok,dry_run:dryRun,repair,receipts:{pending_before:pendingReceipts.length,reconciled:reconciledReceiptCount,abandoned:abandonedReceiptIds.length,summaries:pendingReceipts.map(summarizeMergeReceipt),missing_history_references_before:missingBefore.count,legacy_disposition_eligible:legacyDispositionCandidates.length,legacy_disposition_recorded:recordedLegacyDispositionCount,missing_history_references_after:missingAfter.count},validation,guidance,generated_at:validation.generated_at}}export{runMergeReconcile};
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]="4d506b3c-f1c1-5642-a258-45d93ba64d81")}catch{}})();import{settleAbandonedMergeReceipts}from"./abandoned-receipts.js";import{readFile}from"node:fs/promises";import path from"node:path";import{EXIT_CODE}from"../../core/shared/constants.js";import{PmCliError}from"../../core/shared/errors.js";import{compareTimestampStrings}from"../../core/shared/time.js";import{resolvePmRoot}from"../../core/store/paths.js";import{mapWithFixedConcurrency}from"../extension/concurrency.js";import{runHealth}from"../governance/health.js";import{DURABLE_MERGE_RECEIPT_INTRODUCED_AT,extractHistoryMergeReceiptReferences,isPreDurableDispositionEligibleReference}from"../governance/merge-receipt-history.js";import{runValidate}from"../governance/validate.js";import{runHistoryRepair,runHistoryRepairAll}from"../history-repair.js";import{findGitWorkspaceRoot}from"./install.js";import{assertPortableMergeReceiptReferences,inspectMergeReceiptEvidence,markMergeReceiptReconciled,summarizeMergeReceipt}from"./receipts.js";const RECEIPT_ONLY_REPAIR_CONCURRENCY=4;function missingReceiptCoordinates(health){const integrity=health.checks.find(check=>check.name==="integrity"),counts=integrity?.details.counts,count=typeof counts=="object"&&counts!==null&&!Array.isArray(counts)&&typeof counts.missing_merge_receipt_history_references=="number"?counts.missing_merge_receipt_history_references:0,details=integrity?.details.missing_merge_receipt_history_reference_details,truncated=integrity?.details.missing_merge_receipt_history_reference_details_truncated===!0,coordinates=Array.isArray(details)?details.flatMap(detail=>{if(typeof detail!="object"||detail===null||Array.isArray(detail))return[];const record=detail;return typeof record.item_id=="string"&&typeof record.history_line=="number"&&Number.isSafeInteger(record.history_line)&&typeof record.receipt_id=="string"?[{item_id:record.item_id,history_line:record.history_line,receipt_id:record.receipt_id}]:[]}):[];return{count,truncated,coordinates}}async function identifyLegacyDispositionCandidates(pmRoot,coordinates){const candidates=[];for(const coordinate of coordinates){let raw;try{raw=await readFile(path.join(pmRoot,"history",`${coordinate.item_id}.jsonl`),"utf8")}catch{continue}const line=raw.split(/\r?\n/)[coordinate.history_line-1];if(line===void 0||line.trim().length===0)continue;let parsed;try{parsed=JSON.parse(line)}catch{continue}const reference=extractHistoryMergeReceiptReferences(parsed,coordinate.item_id).find(entry=>entry.receiptId===coordinate.receipt_id);reference===void 0||!isPreDurableDispositionEligibleReference(reference)||reference.eventTimestamp===void 0||compareTimestampStrings(reference.eventTimestamp,DURABLE_MERGE_RECEIPT_INTRODUCED_AT)>=0||candidates.push({...coordinate,original_event_ts:reference.eventTimestamp})}return candidates}async function recordLegacyReceiptDispositions(params){if(params.options.dryRun===!0||params.options.force!==!0||params.candidates.length===0)return 0;const byItem=new Map;for(const candidate of params.candidates){const itemCandidates=byItem.get(candidate.item_id)??[];itemCandidates.push(candidate),byItem.set(candidate.item_id,itemCandidates)}for(const[itemId,itemCandidates]of byItem)await runHistoryRepair(itemId,{author:params.options.author??params.global.author,message:params.options.message??"accept unrecoverable pre-durable merge receipt references",force:!0,auditOperation:"merge_reconcile",forceAuditEntry:!0,auditContext:{merge:{missing_receipt_dispositions:itemCandidates.map(candidate=>({receipt_id:candidate.receipt_id,original_history_line:candidate.history_line,original_event_ts:candidate.original_event_ts,reason:"legacy_clone_local_only"}))}}},params.global);return params.candidates.length}async function runReceiptOnlyRepair(params){try{return{status:"fulfilled",value:await runHistoryRepair(params.id,{dryRun:params.dryRun,author:params.options.author??params.global.author,message:params.options.message??"record field-aware branch merge provenance",force:params.options.force,auditOperation:"merge_reconcile",auditContext:params.auditContext,forceAuditEntry:!0,mergeReceiptProof:params.mergeReceiptProof},params.global)}}catch(reason){return{status:"rejected",reason}}}function appendReceiptOnlyResults(params){params.results.forEach((settled,index)=>{const id=params.receiptOnlyIds[index];if(settled.status==="fulfilled"){const outcome=settled.value.changed?"repaired":"skipped_clean";params.repair.streams.push({id,outcome,entries_rehashed:settled.value.history.entries_rehashed,entries_patch_repaired:settled.value.history.entries_patch_repaired,reconciled_with_item:settled.value.history.reconciled_with_item,...settled.value.merge_receipt_proof?{merge_receipt_proof:settled.value.merge_receipt_proof}:{},warnings:settled.value.warnings}),params.repair.totals[outcome]+=1;return}params.repair.streams.push({id,outcome:"failed",error:settled.reason instanceof Error?settled.reason.message:String(settled.reason)}),params.repair.totals.failed+=1})}async function settleProvenReceipts(params){if(params.dryRun||params.repair.totals.failed>0)return 0;const trustedReceiptIds=new Set(params.repair.streams.flatMap(stream=>stream.merge_receipt_proof?.trusted?stream.merge_receipt_proof.receipt_ids:[])),receiptsToSettle=params.pendingReceipts.filter(receipt=>trustedReceiptIds.has(receipt.id));return await Promise.all(receiptsToSettle.map(receipt=>markMergeReceiptReconciled(params.receiptWorkspaceRoot,receipt,{requireExisting:!0}))),receiptsToSettle.length}function receiptReviewGuidance(pendingCount,complete){if(!(pendingCount===0||complete))return'If receipt proof is unavailable or competing values were discarded, first review receipts.summaries and repair.streams and re-apply intended values. Only with explicit authority to accept that review, run pm merge reconcile --force --message "<reviewed receipt disposition and rationale>". This appends an audit event; it does not restore missing evidence or discarded values.'}async function runStagedMergeReconcile(options,global2){if(options.force===!0)throw new PmCliError("Automatic staging cannot accept forced reconciliation.",EXIT_CODE.CONFLICT,{code:"merge_settlement_review_required",recovery:{suggested_retry:"pm merge reconcile --dry-run"}});const unstagedOptions={...options,stage:!1},{stageLosslessMergeReconciliation,resolveMergeSettlementGlobal}=await import("./stage.js"),settledGlobal=await resolveMergeSettlementGlobal(global2),preview=await runMergeReconcile({...unstagedOptions,dryRun:!0},settledGlobal);return stageLosslessMergeReconciliation({preview,dryRun:options.dryRun===!0,global:settledGlobal,apply:()=>runMergeReconcile({...unstagedOptions,dryRun:!1},settledGlobal)})}async function runMergeReconcile(options,global2){return options.stage===!0?runStagedMergeReconcile(options,global2):reconcileMergeHistory(options,global2)}async function reconcileMergeHistory(options,global2){const dryRun=options.dryRun===!0,pmRoot=resolvePmRoot(process.cwd(),global2.path),preflightHealth=await runHealth(global2,{checkOnly:!0,full:!0,skipVectors:!0,skipDrift:!0}),missingBefore=missingReceiptCoordinates(preflightHealth),legacyDispositionCandidates=await identifyLegacyDispositionCandidates(pmRoot,missingBefore.coordinates),gitWorkspaceRoot=await findGitWorkspaceRoot(pmRoot),receiptWorkspaceRoot=gitWorkspaceRoot??pmRoot,receiptEvidence=await inspectMergeReceiptEvidence(receiptWorkspaceRoot,{includeLossless:!0,pmRoot});if(receiptEvidence.invalid_evidence_count>0)throw new PmCliError(`Merge reconciliation refused ${receiptEvidence.invalid_evidence_count} invalid receipt evidence file(s).`,EXIT_CODE.CONFLICT,{code:"merge_receipt_evidence_invalid",required:"Every clone-local and durable receipt candidate must pass bounded-file, schema, and identity validation.",recovery:{suggested_retry:"pm health --check-only --full"}});assertPortableMergeReceiptReferences(receiptEvidence.receipts.map(receipt=>receipt.id),receiptEvidence,dryRun);const recordedLegacyDispositionCount=await recordLegacyReceiptDispositions({candidates:legacyDispositionCandidates,options,global:global2}),pendingReceipts=receiptEvidence.receipts,abandonedReceiptIds=await settleAbandonedMergeReceipts({cwd:gitWorkspaceRoot,receipts:pendingReceipts,dryRun,author:options.author,message:options.message,global:global2}),abandonedIds=new Set(abandonedReceiptIds),receiptsByItem=new Map;for(const receipt of pendingReceipts.filter(entry=>!abandonedIds.has(entry.id))){const receipts=receiptsByItem.get(receipt.item_id)??[];receipts.push(receipt),receiptsByItem.set(receipt.item_id,receipts)}const auditContextById=Object.fromEntries([...receiptsByItem].map(([id,receipts])=>[id,{merge:{receipts:receipts.map(summarizeMergeReceipt)}}])),mergeReceiptProofById=Object.fromEntries([...receiptsByItem].map(([id,receipts])=>[id,{gitWorkspaceRoot,receipts}])),repair=await runHistoryRepairAll({dryRun,author:options.author??global2.author,message:options.message??"post-merge reconciliation of field-aware tracker history",force:options.force,auditOperation:"merge_reconcile",auditContextById,...pendingReceipts.length>0?{mergeReceiptProofById}:{}},global2),representedIds=new Set(repair.streams.map(stream=>stream.id)),receiptOnlyIds=[...receiptsByItem.keys()].filter(id=>!representedIds.has(id)),receiptOnlyResults=await mapWithFixedConcurrency(receiptOnlyIds,RECEIPT_ONLY_REPAIR_CONCURRENCY,id=>runReceiptOnlyRepair({id,dryRun,options,global:global2,auditContext:auditContextById[id],mergeReceiptProof:mergeReceiptProofById[id]}));appendReceiptOnlyResults({receiptOnlyIds,results:receiptOnlyResults,repair});const reconciledReceiptCount=await settleProvenReceipts({dryRun,pendingReceipts,repair,receiptWorkspaceRoot}),validation=await runValidate({checkHistoryDrift:!0,checkStorageIntegrity:!0},global2),postflightHealth=recordedLegacyDispositionCount===0?preflightHealth:await runHealth(global2,{checkOnly:!0,full:!0,skipVectors:!0,skipDrift:!0}),missingAfter=missingReceiptCoordinates(postflightHealth),mergeChecksGreen=validation.checks.every(check=>check.status==="ok"),receiptSettlementComplete=dryRun?pendingReceipts.length===0:reconciledReceiptCount+abandonedReceiptIds.length===pendingReceipts.length,ok=[repair.totals.failed===0,mergeChecksGreen,receiptSettlementComplete,missingAfter.count===0,!missingAfter.truncated].every(Boolean),boundedBatchGuidance=missingBefore.truncated||missingAfter.truncated?"Missing receipt coordinates exceed the bounded detail response; each reviewed --force pass can disposition only the reported eligible batch. Repeat dry-run and --force passes until receipts.missing_history_references_after=0 and pm merge reconcile returns ok=true.":void 0,explicitReviewGuidance=receiptReviewGuidance(pendingReceipts.length,ok),guidance=(dryRun?["Review repair.streams, then rerun pm merge reconcile without --dry-run to apply audited repairs.",boundedBatchGuidance,explicitReviewGuidance,"No Git hook is installed automatically; invoke this command from an explicit post-merge hook only when your repository policy opts in."]:ok?["Reconciliation is complete; commit changed history streams with the merge result.","Rerun pm merge reconcile --dry-run after future tracker-data merges as a non-mutating integrity check."]:["Reconciliation remains incomplete; inspect repair failures and non-green validation checks before retrying.",boundedBatchGuidance,explicitReviewGuidance,...legacyDispositionCandidates.length>0&&recordedLegacyDispositionCount===0?["Pre-durable clone-local-only receipt references are eligible for an audited disposition; rerun with --force after reviewing receipts.legacy_disposition_eligible."]:[],"Do not commit repaired history streams as reconciled until pm merge reconcile returns ok=true."]).filter(message=>message!==void 0);return{ok,dry_run:dryRun,repair,receipts:{pending_before:pendingReceipts.length,reconciled:reconciledReceiptCount,abandoned:abandonedReceiptIds.length,summaries:pendingReceipts.map(summarizeMergeReceipt),unstaged_durable_receipt_ids:receiptEvidence.unstaged_durable_receipt_ids,missing_history_references_before:missingBefore.count,legacy_disposition_eligible:legacyDispositionCandidates.length,legacy_disposition_recorded:recordedLegacyDispositionCount,missing_history_references_after:missingAfter.count},validation,guidance,generated_at:validation.generated_at}}export{runMergeReconcile};
2
2
  //# sourceMappingURL=reconcile.js.map
@@ -0,0 +1,12 @@
1
+ /** Preview an owned hook without replacing existing user policy or following file links. */
2
+ export declare function prepareMergeSettlementHook(workspaceRoot:string,command:string):Promise<{
3
+ path:string;
4
+ changed:boolean;
5
+ content:string;
6
+ }>;
7
+ /** Publish an explicitly requested hook after its command and merge fence are installed. */
8
+ export declare function writeMergeSettlementHook(hook:{
9
+ path:string;
10
+ changed:boolean;
11
+ content:string;
12
+ }):Promise<void>;