@unbrained/pm-cli 2026.10.20 → 2026.10.22

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 (196) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +142 -77
  3. package/dist/cli/error-guidance.d.ts +8 -0
  4. package/dist/cli/error-guidance.js +2 -2
  5. package/dist/cli/main.js +1 -1
  6. package/dist/cli/register-list-query.js +2 -2
  7. package/dist/cli/register-operations.js +1 -1
  8. package/dist/cli-bundle/bundle-manifest.json +1 -1
  9. package/dist/cli-bundle/chunks/{chunk-QMZ73FCW.js → chunk-2B5HAZFK.js} +2 -2
  10. package/dist/cli-bundle/chunks/chunk-35ISFIPC.js +113 -0
  11. package/dist/cli-bundle/chunks/chunk-4D25HF3V.js +2 -0
  12. package/dist/cli-bundle/chunks/chunk-5LSN7K4N.js +2 -0
  13. package/dist/cli-bundle/chunks/chunk-7OTYULIJ.js +30 -0
  14. package/dist/cli-bundle/chunks/{chunk-GHJOMRSF.js → chunk-AOAQDCXX.js} +2 -2
  15. package/dist/cli-bundle/chunks/{chunk-ZA3XXHEE.js → chunk-BAIVTLJM.js} +2 -2
  16. package/dist/cli-bundle/chunks/{chunk-LS55VKRK.js → chunk-C37RDYZI.js} +2 -2
  17. package/dist/cli-bundle/chunks/{chunk-YT2Z3JPC.js → chunk-E3JC3WQL.js} +47 -47
  18. package/dist/cli-bundle/chunks/{chunk-YECCKB4E.js → chunk-GWIGZDRS.js} +2 -2
  19. package/dist/cli-bundle/chunks/{chunk-YTHI5DTH.js → chunk-HC2DCGFA.js} +2 -2
  20. package/dist/cli-bundle/chunks/chunk-IB7UB5TE.js +2 -0
  21. package/dist/cli-bundle/chunks/chunk-J444ZX23.js +126 -0
  22. package/dist/cli-bundle/chunks/{chunk-VLJLWZBO.js → chunk-LIC5EUQS.js} +2 -2
  23. package/dist/cli-bundle/chunks/{chunk-KU4Q3MSI.js → chunk-MV3KYY2A.js} +6 -6
  24. package/dist/cli-bundle/chunks/chunk-O67N62DT.js +2 -0
  25. package/dist/cli-bundle/chunks/{chunk-TZU6U5ZE.js → chunk-PCPOTFJP.js} +2 -2
  26. package/dist/cli-bundle/chunks/chunk-TNU3XVPC.js +20 -0
  27. package/dist/cli-bundle/chunks/{chunk-7ZOIZ2AZ.js → chunk-URV4VHQ4.js} +2 -2
  28. package/dist/cli-bundle/chunks/{chunk-YW6SIKYP.js → chunk-UYYTV2TX.js} +2 -2
  29. package/dist/cli-bundle/chunks/chunk-VO3KDIU7.js +9 -0
  30. package/dist/cli-bundle/chunks/{indexed-receipt-blobs-U7VGKIYC.js → indexed-receipt-blobs-L3BOTMZN.js} +2 -2
  31. package/dist/cli-bundle/chunks/mutation-admission-TATOFEXF.js +2 -0
  32. package/dist/cli-bundle/chunks/{register-list-query-NNDV6F6I.js → register-list-query-GULR2IYO.js} +5 -5
  33. package/dist/cli-bundle/chunks/{register-mutation-3GCBSNQS.js → register-mutation-HLSQ4WVW.js} +4 -4
  34. package/dist/cli-bundle/chunks/register-operations-FE3GGA7O.js +2 -0
  35. package/dist/cli-bundle/chunks/register-setup-XUIKAIZA.js +2 -0
  36. package/dist/cli-bundle/chunks/{settlement-hook-CLRGA6O7.js → settlement-hook-UVFYSBMU.js} +2 -2
  37. package/dist/cli-bundle/chunks/stage-QCPZBZRC.js +2 -0
  38. package/dist/cli-bundle/focused-chunks/chunk-25L3L2PF.js +2 -0
  39. package/dist/cli-bundle/focused-chunks/chunk-25NQRZ7J.js +8 -0
  40. package/dist/cli-bundle/focused-chunks/chunk-2GFTVXKZ.js +8 -0
  41. package/dist/cli-bundle/focused-chunks/{chunk-HUZAXSIJ.js → chunk-3LWH2RJC.js} +2 -2
  42. package/dist/cli-bundle/focused-chunks/chunk-46HBJC6J.js +2 -0
  43. package/dist/cli-bundle/focused-chunks/chunk-A52W6GLA.js +2 -0
  44. package/dist/cli-bundle/focused-chunks/chunk-CB7TSZ7P.js +3 -0
  45. package/dist/cli-bundle/focused-chunks/{chunk-VLSER3XW.js → chunk-CX5FNEFC.js} +2 -2
  46. package/dist/cli-bundle/focused-chunks/chunk-DVNUHTVK.js +9 -0
  47. package/dist/cli-bundle/focused-chunks/{chunk-LJPUJ2KK.js → chunk-EHPRSWJE.js} +2 -2
  48. package/dist/cli-bundle/focused-chunks/chunk-GC4VMLXL.js +20 -0
  49. package/dist/cli-bundle/focused-chunks/chunk-GHQVM6K6.js +2 -0
  50. package/dist/cli-bundle/focused-chunks/{chunk-KKRFSI2N.js → chunk-GVKYTDOH.js} +2 -2
  51. package/dist/cli-bundle/focused-chunks/{chunk-NI6NS44Q.js → chunk-KM77WU5U.js} +2 -2
  52. package/dist/cli-bundle/focused-chunks/chunk-QGDKWULS.js +2 -0
  53. package/dist/cli-bundle/focused-chunks/chunk-QWKCQ5PW.js +91 -0
  54. package/dist/cli-bundle/focused-chunks/chunk-SOVXHW6Z.js +2 -0
  55. package/dist/cli-bundle/focused-chunks/chunk-SVOOGGAK.js +2 -0
  56. package/dist/cli-bundle/focused-chunks/chunk-SXVJRLLP.js +2 -0
  57. package/dist/cli-bundle/focused-chunks/{chunk-E4II2D2B.js → chunk-TMN3RAXZ.js} +2 -2
  58. package/dist/cli-bundle/focused-chunks/{chunk-F7FD25TG.js → chunk-UQ5JPBY2.js} +11 -11
  59. package/dist/cli-bundle/focused-chunks/chunk-UUPJ6OZL.js +26 -0
  60. package/dist/cli-bundle/focused-chunks/chunk-VYAZND5Q.js +78 -0
  61. package/dist/cli-bundle/focused-chunks/chunk-W2CND3BD.js +65 -0
  62. package/dist/cli-bundle/focused-chunks/{chunk-ZGEK57KD.js → chunk-XX5JQCLF.js} +2 -2
  63. package/dist/cli-bundle/focused-chunks/{indexed-receipt-blobs-H2G6CCGO.js → indexed-receipt-blobs-AAQOKO5B.js} +2 -2
  64. package/dist/cli-bundle/focused-chunks/mutation-admission-TBKR4KRM.js +2 -0
  65. package/dist/cli-bundle/focused-chunks/{settlement-hook-5SNBBT7C.js → settlement-hook-MNCHFCAX.js} +2 -2
  66. package/dist/cli-bundle/focused-chunks/stage-RM6MJIMT.js +2 -0
  67. package/dist/cli-bundle/main.js +21 -21
  68. package/dist/cli-bundle/sdk-authoring.js +1 -1
  69. package/dist/cli-bundle/sdk-contracts.js +2 -2
  70. package/dist/cli-bundle/sdk-core.js +23 -23
  71. package/dist/cli-bundle/sdk-governance.js +1 -1
  72. package/dist/cli-bundle/sdk-graph.js +1 -1
  73. package/dist/cli-bundle/sdk-merge.js +58 -57
  74. package/dist/cli-bundle/sdk-query.js +1 -1
  75. package/dist/cli-bundle/sdk-runtime.js +1 -1
  76. package/dist/cli-bundle/sdk-testing.js +1 -1
  77. package/dist/cli-bundle/sdk.js +7 -7
  78. package/dist/core/item/actionability.js +1 -1
  79. package/dist/core/lock/lock-gc.d.ts +12 -5
  80. package/dist/core/lock/lock-gc.js +1 -1
  81. package/dist/core/lock/lock-owner.d.ts +33 -0
  82. package/dist/core/lock/lock-owner.js +2 -0
  83. package/dist/core/lock/lock.d.ts +11 -22
  84. package/dist/core/lock/lock.js +4 -3
  85. package/dist/core/output/output.js +3 -3
  86. package/dist/core/shared/errors.d.ts +2 -0
  87. package/dist/core/shared/errors.js +1 -1
  88. package/dist/mcp/server.d.ts +2 -2
  89. package/dist/mcp/server.js +1 -1
  90. package/dist/sdk/agent/command-recovery.d.ts +2 -0
  91. package/dist/sdk/agent/command-recovery.js +1 -1
  92. package/dist/sdk/cli-contracts/runtime-contracts.js +1 -1
  93. package/dist/sdk/cli-contracts/tool-option-contracts.d.ts +6 -0
  94. package/dist/sdk/cli-contracts/tool-option-contracts.js +1 -1
  95. package/dist/sdk/cli-contracts/tool-schema.js +1 -1
  96. package/dist/sdk/context-intent-contracts.d.ts +13 -0
  97. package/dist/sdk/context-intent-contracts.js +1 -1
  98. package/dist/sdk/contracts.d.ts +1 -1
  99. package/dist/sdk/contracts.js +1 -1
  100. package/dist/sdk/core.d.ts +2 -2
  101. package/dist/sdk/core.js +1 -1
  102. package/dist/sdk/define.d.ts +15 -16
  103. package/dist/sdk/define.js +1 -1
  104. package/dist/sdk/dependencies.js +1 -1
  105. package/dist/sdk/extension-command-context.js +2 -2
  106. package/dist/sdk/generated/generated-error-code-catalog-part-1.js +1 -1
  107. package/dist/sdk/governance/assurance-action.js +1 -1
  108. package/dist/sdk/governance/assurance.js +1 -1
  109. package/dist/sdk/governance/health.js +2 -2
  110. package/dist/sdk/merge/install.js +2 -2
  111. package/dist/sdk/output/list-envelope.js +1 -1
  112. package/dist/sdk/pagination.d.ts +3 -3
  113. package/dist/sdk/pagination.js +1 -1
  114. package/dist/sdk/public-surface.json +1 -1
  115. package/dist/sdk/query/context.d.ts +5 -0
  116. package/dist/sdk/query/context.js +2 -2
  117. package/dist/sdk/query/list.d.ts +2 -0
  118. package/dist/sdk/query/list.js +1 -1
  119. package/dist/sdk/query/projection-contracts.d.ts +1 -1
  120. package/dist/sdk/query/projection-contracts.js +1 -1
  121. package/dist/sdk/read-output/session-result.d.ts +39 -0
  122. package/dist/sdk/read-output/session-result.js +2 -0
  123. package/dist/sdk/read-output-contracts.d.ts +18 -5
  124. package/dist/sdk/read-output-contracts.js +1 -1
  125. package/dist/sdk/read-output-session.d.ts +2 -0
  126. package/dist/sdk/read-output-session.js +1 -1
  127. package/dist/sdk/relationship-context.js +1 -1
  128. package/dist/sdk/runtime/mutation-admission.d.ts +2 -0
  129. package/dist/sdk/runtime/mutation-admission.js +2 -0
  130. package/dist/sdk/runtime-input.d.ts +2 -0
  131. package/dist/sdk/runtime-input.js +1 -1
  132. package/dist/sdk/runtime-primitives.d.ts +6 -0
  133. package/dist/sdk/runtime-primitives.js +1 -1
  134. package/dist/sdk/runtime.d.ts +10 -8
  135. package/dist/sdk/runtime.js +1 -1
  136. package/dist/sdk/test/execution.d.ts +1 -0
  137. package/dist/sdk/test/execution.js +2 -2
  138. package/dist/sdk/workspace.d.ts +3 -1
  139. package/dist/sdk/workspace.js +8 -5
  140. package/docs/AGENT_RUNTIME_PRIMITIVES.md +9 -0
  141. package/docs/ASSURANCE.md +17 -2
  142. package/docs/COMMANDS.md +17 -3
  143. package/docs/CONFIGURATION.md +38 -0
  144. package/docs/CONTEXT_RELEVANCE.md +15 -0
  145. package/docs/DEVELOPMENT_DEPENDENCY_SECURITY.md +56 -3
  146. package/docs/MCP_OPTION_CONFORMANCE.md +27 -1
  147. package/docs/MERGE_SAFETY.md +8 -0
  148. package/docs/READ_OUTPUT_CONTRACTS.md +79 -0
  149. package/docs/RELATIONSHIP_GRAPH.md +15 -0
  150. package/docs/RELEASING.md +8 -0
  151. package/docs/SDK.md +104 -7
  152. package/docs/TESTING.md +23 -0
  153. package/docs/agent-task-token-baseline.json +1 -1
  154. package/marketplace.json +1 -1
  155. package/package.json +1 -1
  156. package/packages/pm-beads/package.json +1 -1
  157. package/packages/pm-calendar/package.json +1 -1
  158. package/packages/pm-command-kit/package.json +1 -1
  159. package/packages/pm-digital-twin/package.json +1 -1
  160. package/packages/pm-governance-audit/package.json +1 -1
  161. package/packages/pm-guide-shell/package.json +1 -1
  162. package/packages/pm-kanban/package.json +1 -1
  163. package/packages/pm-lifecycle-hooks/package.json +1 -1
  164. package/packages/pm-linked-test-adapters/package.json +1 -1
  165. package/packages/pm-search-advanced/package.json +1 -1
  166. package/packages/pm-templates/package.json +1 -1
  167. package/packages/pm-todos/package.json +1 -1
  168. package/packages/pm-vcs/package.json +1 -1
  169. package/plugins/pm-claude/.claude-plugin/plugin.json +1 -1
  170. package/plugins/pm-claude/package.json +1 -1
  171. package/plugins/pm-codex/.codex-plugin/plugin.json +1 -1
  172. package/plugins/pm-codex/package.json +1 -1
  173. package/plugins/pm-codex/plugin.json +1 -1
  174. package/runtime-dependencies.json +1 -1
  175. package/dist/cli-bundle/chunks/chunk-2GGGOD7P.js +0 -30
  176. package/dist/cli-bundle/chunks/chunk-7NHA5K77.js +0 -126
  177. package/dist/cli-bundle/chunks/chunk-PNPE2OVJ.js +0 -112
  178. package/dist/cli-bundle/chunks/chunk-PTL3TBT6.js +0 -20
  179. package/dist/cli-bundle/chunks/chunk-QETMESEB.js +0 -6
  180. package/dist/cli-bundle/chunks/chunk-QQYCB2LO.js +0 -2
  181. package/dist/cli-bundle/chunks/register-operations-YKDGNKUL.js +0 -2
  182. package/dist/cli-bundle/chunks/register-setup-AOUZQQWB.js +0 -2
  183. package/dist/cli-bundle/chunks/stage-6QXYRMSU.js +0 -2
  184. package/dist/cli-bundle/focused-chunks/chunk-37SU7BZI.js +0 -3
  185. package/dist/cli-bundle/focused-chunks/chunk-6IU7BESB.js +0 -65
  186. package/dist/cli-bundle/focused-chunks/chunk-AS3MOBO3.js +0 -25
  187. package/dist/cli-bundle/focused-chunks/chunk-AW35RDKA.js +0 -78
  188. package/dist/cli-bundle/focused-chunks/chunk-GYHZ7W6O.js +0 -2
  189. package/dist/cli-bundle/focused-chunks/chunk-H5Q2HUU6.js +0 -8
  190. package/dist/cli-bundle/focused-chunks/chunk-J6EYUGAR.js +0 -6
  191. package/dist/cli-bundle/focused-chunks/chunk-JEXZEIAX.js +0 -91
  192. package/dist/cli-bundle/focused-chunks/chunk-KDGUIIRU.js +0 -20
  193. package/dist/cli-bundle/focused-chunks/chunk-MQ42BFGX.js +0 -2
  194. package/dist/cli-bundle/focused-chunks/chunk-VAYS754X.js +0 -2
  195. package/dist/cli-bundle/focused-chunks/chunk-VS6VTWTZ.js +0 -8
  196. package/dist/cli-bundle/focused-chunks/stage-EMTISXR7.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]="1745a409-0f91-5f5a-8d2a-e5e61ff667a0")}catch{}})();import{isTerminalStatus,normalizeStatusForRegistry}from"./status.js";import{BUILTIN_ITEM_EXECUTION_ROLES,resolveTypeDefinition}from"./type-registry.js";import{isExternalDependencyReference,isExternalDependencySourceKind}from"./dependency-reference.js";function resolveItemExecutionRole(type,registry){return(registry===void 0?void 0:resolveTypeDefinition(type,registry))?.execution_role??BUILTIN_ITEM_EXECUTION_ROLES[type.trim().toLowerCase()]??"agent"}const BLOCKED_BY_DEPENDENCY_KIND="blocked_by",BLOCKS_DEPENDENCY_KIND="blocks",ACTIONABILITY_ORDERING_KINDS=Object.freeze([BLOCKED_BY_DEPENDENCY_KIND,BLOCKS_DEPENDENCY_KIND]),NO_ACTIVE_BLOCKER_SENTINEL="no-active-blocker";function normalizeItemId(id){return id.trim().toLowerCase()}function blockerReferenceKey(id,external){return external?`external:${id}`:`local:${normalizeItemId(id)}`}function collectBlockedByIds(item){const ids=new Map,scalar=typeof item.blocked_by=="string"?item.blocked_by.trim():"";if(scalar.length>0&&normalizeItemId(scalar)!==NO_ACTIVE_BLOCKER_SENTINEL){const external=isExternalDependencyReference(scalar);ids.set(blockerReferenceKey(scalar,external),external?scalar:normalizeItemId(scalar))}for(const dependency of item.dependencies??[]){const dependencyId=typeof dependency.id=="string"?dependency.id.trim():"";if(dependency.kind===BLOCKED_BY_DEPENDENCY_KIND&&dependencyId.length>0&&normalizeItemId(dependencyId)!==NO_ACTIVE_BLOCKER_SENTINEL){const external=isExternalDependencyReference(dependencyId)||isExternalDependencySourceKind(dependency.source_kind);ids.set(blockerReferenceKey(dependencyId,external),external?dependencyId:normalizeItemId(dependencyId))}}return[...ids.values()].sort((left,right)=>left.localeCompare(right))}function indexBlockedByIds(corpus){const idsByItem=new Map;for(const item of corpus){const itemId=normalizeItemId(item.id);itemId.length!==0&&idsByItem.set(itemId,new Set(collectBlockedByIds(item)))}for(const candidate of corpus){const candidateId=normalizeItemId(candidate.id);if(candidateId.length!==0)for(const dependency of candidate.dependencies??[]){const targetId=dependency.kind===BLOCKS_DEPENDENCY_KIND&&typeof dependency.id=="string"?normalizeItemId(dependency.id):"";if(targetId.length===0||targetId===NO_ACTIVE_BLOCKER_SENTINEL)continue;const blockerIds=idsByItem.get(targetId)??new Set;blockerIds.add(candidateId),idsByItem.set(targetId,blockerIds)}}return new Map([...idsByItem].map(([itemId,blockerIds])=>[itemId,[...blockerIds].sort((left,right)=>left.localeCompare(right))]))}function collectBlockedByIdsFromCorpus(item,corpus){const itemId=normalizeItemId(item.id);return itemId.length>0?indexBlockedByIds(corpus).get(itemId)??collectBlockedByIds(item):collectBlockedByIds(item)}function resolveItemBlockersWithIndex(item,itemsById,statusRegistry,blockerIdsByItem){const itemWithId=item,blockerIds=typeof itemWithId.id=="string"?blockerIdsByItem?.get(normalizeItemId(itemWithId.id))??collectBlockedByIdsFromCorpus(itemWithId,[...itemsById.values()]):collectBlockedByIds(item),structuredExternalBlockerIds=new Set((item.dependencies??[]).filter(dependency=>dependency.kind===BLOCKED_BY_DEPENDENCY_KIND&&isExternalDependencySourceKind(dependency.source_kind)&&typeof dependency.id=="string"&&dependency.id.trim().length>0).map(dependency=>blockerReferenceKey(dependency.id.trim(),!0)));return blockerIds.map(id=>{if(isExternalDependencyReference(id)||structuredExternalBlockerIds.has(blockerReferenceKey(id,!0)))return{id,title:null,status:null,resolved:!1,external:!0,...typeof item.updated_at=="string"?{blocked_since:item.updated_at}:{},resolver:null};const blocker=itemsById.get(normalizeItemId(id));return blocker?{id,title:blocker.title,status:blocker.status,resolved:isTerminalStatus(blocker.status,statusRegistry)}:{id,title:null,status:null,resolved:!1}})}function resolveItemBlockers(item,itemsById,statusRegistry){return resolveItemBlockersWithIndex(item,itemsById,statusRegistry)}function collectDependencyBlockedIds(corpus,statusRegistry){const itemsById=new Map(corpus.map(item=>[normalizeItemId(item.id),item])),blockerIdsByItem=indexBlockedByIds(corpus),candidateStatuses=new Set([...resolveActiveStatusSet(statusRegistry),...statusRegistry.blocked_statuses]),blocked=new Set;for(const item of corpus){const normalizedStatus=normalizeStatusForRegistry(item.status,statusRegistry);candidateStatuses.has(normalizedStatus)&&(statusRegistry.blocked_statuses.has(normalizedStatus)||resolveItemBlockersWithIndex(item,itemsById,statusRegistry,blockerIdsByItem).some(blocker=>!blocker.resolved))&&blocked.add(normalizeItemId(item.id))}return blocked}function selectActionableEntries(report,statusRegistry,options={},typeRegistry){const candidates=[...report.ready,...report.decisions,...report.gates];options.includeContainers===!0&&candidates.push(...report.containers.filter(entry=>entry.open_blockers.length===0&&!statusRegistry.blocked_statuses.has(normalizeStatusForRegistry(entry.item.status,statusRegistry))));const selected={ready:[],decisions:[],gates:[]};for(const entry of candidates){const role=resolveItemExecutionRole(entry.item.type,typeRegistry);role==="human"&&options.includeDecisions!==!0?selected.decisions.push(entry):role==="gate"&&options.includeGates!==!0?selected.gates.push(entry):selected.ready.push(entry)}return selected}function resolveActiveStatusSet(statusRegistry){return statusRegistry.active_statuses.size>0?statusRegistry.active_statuses:new Set([statusRegistry.open_status])}function hasOpenDescendant(rootId,childrenByParent,statusRegistry){const stack=[normalizeItemId(rootId)],visited=new Set;for(;stack.length>0;){const current=stack.pop();if(!visited.has(current)){visited.add(current);for(const child of childrenByParent.get(current)??[]){if(!isTerminalStatus(child.status,statusRegistry))return!0;stack.push(normalizeItemId(child.id))}}}return!1}function indexCorpus(corpus){const itemsById=new Map,childrenByParent=new Map,blockedByReverse=new Map,blockerIdsByItem=indexBlockedByIds(corpus);for(const item of corpus){itemsById.set(normalizeItemId(item.id),item);const parent=typeof item.parent=="string"?item.parent.trim():"";if(parent.length>0){const parentKey=normalizeItemId(parent),siblings=childrenByParent.get(parentKey)??[];siblings.push(item),childrenByParent.set(parentKey,siblings)}for(const blockerId of blockerIdsByItem.get(normalizeItemId(item.id))){const blockerKey=normalizeItemId(blockerId),dependents=blockedByReverse.get(blockerKey)??[];dependents.push(item.id),blockedByReverse.set(blockerKey,dependents)}}return{itemsById,childrenByParent,blockedByReverse,blockerIdsByItem}}function computeActionabilityReport(candidates,corpus,statusRegistry,typeRegistry){const{itemsById,childrenByParent,blockedByReverse,blockerIdsByItem}=indexCorpus(corpus),activeStatuses=new Set([...resolveActiveStatusSet(statusRegistry),...statusRegistry.blocked_statuses]),nonTerminalIds=new Set(corpus.filter(entry=>!isTerminalStatus(entry.status,statusRegistry)).map(entry=>normalizeItemId(entry.id))),ready=[],blocked=[],containers=[],decisions=[],gates=[];let activeCount=0;for(const item of candidates){if(!activeStatuses.has(normalizeStatusForRegistry(item.status,statusRegistry)))continue;activeCount+=1;const openBlockers=resolveItemBlockersWithIndex(item,itemsById,statusRegistry,blockerIdsByItem).filter(blocker=>!blocker.resolved),unblocks=(blockedByReverse.get(normalizeItemId(item.id))??[]).filter(dependentId=>nonTerminalIds.has(normalizeItemId(dependentId))).sort((left,right)=>left.localeCompare(right)),entry={item,open_blockers:openBlockers,unblocks};if(hasOpenDescendant(item.id,childrenByParent,statusRegistry)){containers.push(entry);continue}const lifecycleBlocked=statusRegistry.blocked_statuses.has(normalizeStatusForRegistry(item.status,statusRegistry));if(openBlockers.length===0&&!lifecycleBlocked){const role=resolveItemExecutionRole(item.type,typeRegistry);role==="human"?decisions.push(entry):role==="gate"?gates.push(entry):ready.push(entry)}else blocked.push(entry)}return{ready,blocked,containers,decisions,gates,active_count:activeCount,container_count:containers.length}}export{ACTIONABILITY_ORDERING_KINDS,collectBlockedByIds,collectBlockedByIdsFromCorpus,collectDependencyBlockedIds,computeActionabilityReport,indexBlockedByIds,resolveItemBlockers,resolveItemExecutionRole,selectActionableEntries};
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]="a319e7d9-e248-573b-bf1f-35217ce49fd0")}catch{}})();import{isTerminalStatus,normalizeStatusForRegistry}from"./status.js";import{BUILTIN_ITEM_EXECUTION_ROLES,resolveTypeDefinition}from"./type-registry.js";import{resolveCanonicalRelationshipKind}from"../../sdk/relationships.js";import{isExternalDependencyReference,isExternalDependencySourceKind}from"./dependency-reference.js";function resolveItemExecutionRole(type,registry){return(registry===void 0?void 0:resolveTypeDefinition(type,registry))?.execution_role??BUILTIN_ITEM_EXECUTION_ROLES[type.trim().toLowerCase()]??"agent"}const BLOCKED_BY_DEPENDENCY_KIND="blocked_by",BLOCKS_DEPENDENCY_KIND="blocks",ACTIONABILITY_ORDERING_KINDS=Object.freeze([BLOCKED_BY_DEPENDENCY_KIND,BLOCKS_DEPENDENCY_KIND]),NO_ACTIVE_BLOCKER_SENTINEL="no-active-blocker";function normalizeItemId(id){return id.trim().toLowerCase()}function blockerReferenceKey(id,external){return external?`external:${id}`:`local:${normalizeItemId(id)}`}function collectBlockedByIds(item){const ids=new Map,scalar=typeof item.blocked_by=="string"?item.blocked_by.trim():"";if(scalar.length>0&&normalizeItemId(scalar)!==NO_ACTIVE_BLOCKER_SENTINEL){const external=isExternalDependencyReference(scalar);ids.set(blockerReferenceKey(scalar,external),external?scalar:normalizeItemId(scalar))}for(const dependency of item.dependencies??[]){const dependencyId=typeof dependency.id=="string"?dependency.id.trim():"";if(resolveCanonicalRelationshipKind(dependency.kind)===BLOCKED_BY_DEPENDENCY_KIND&&dependencyId.length>0&&normalizeItemId(dependencyId)!==NO_ACTIVE_BLOCKER_SENTINEL){const external=isExternalDependencyReference(dependencyId)||isExternalDependencySourceKind(dependency.source_kind);ids.set(blockerReferenceKey(dependencyId,external),external?dependencyId:normalizeItemId(dependencyId))}}return[...ids.values()].sort((left,right)=>left.localeCompare(right))}function indexBlockedByIds(corpus){const idsByItem=new Map;for(const item of corpus){const itemId=normalizeItemId(item.id);itemId.length!==0&&idsByItem.set(itemId,new Set(collectBlockedByIds(item)))}for(const candidate of corpus){const candidateId=normalizeItemId(candidate.id);if(candidateId.length!==0)for(const dependency of candidate.dependencies??[]){const targetId=resolveCanonicalRelationshipKind(dependency.kind)===BLOCKS_DEPENDENCY_KIND&&typeof dependency.id=="string"?normalizeItemId(dependency.id):"";if(targetId.length===0||targetId===NO_ACTIVE_BLOCKER_SENTINEL)continue;const blockerIds=idsByItem.get(targetId)??new Set;blockerIds.add(candidateId),idsByItem.set(targetId,blockerIds)}}return new Map([...idsByItem].map(([itemId,blockerIds])=>[itemId,[...blockerIds].sort((left,right)=>left.localeCompare(right))]))}function collectBlockedByIdsFromCorpus(item,corpus){const itemId=normalizeItemId(item.id);return itemId.length>0?indexBlockedByIds(corpus).get(itemId)??collectBlockedByIds(item):collectBlockedByIds(item)}function resolveItemBlockersWithIndex(item,itemsById,statusRegistry,blockerIdsByItem){const itemWithId=item,blockerIds=typeof itemWithId.id=="string"?blockerIdsByItem?.get(normalizeItemId(itemWithId.id))??collectBlockedByIdsFromCorpus(itemWithId,[...itemsById.values()]):collectBlockedByIds(item),structuredExternalBlockerIds=new Set((item.dependencies??[]).filter(dependency=>resolveCanonicalRelationshipKind(dependency.kind)===BLOCKED_BY_DEPENDENCY_KIND&&isExternalDependencySourceKind(dependency.source_kind)&&typeof dependency.id=="string"&&dependency.id.trim().length>0).map(dependency=>blockerReferenceKey(dependency.id.trim(),!0)));return blockerIds.map(id=>{if(isExternalDependencyReference(id)||structuredExternalBlockerIds.has(blockerReferenceKey(id,!0)))return{id,title:null,status:null,resolved:!1,external:!0,...typeof item.updated_at=="string"?{blocked_since:item.updated_at}:{},resolver:null};const blocker=itemsById.get(normalizeItemId(id));return blocker?{id,title:blocker.title,status:blocker.status,resolved:isTerminalStatus(blocker.status,statusRegistry)}:{id,title:null,status:null,resolved:!1}})}function resolveItemBlockers(item,itemsById,statusRegistry){return resolveItemBlockersWithIndex(item,itemsById,statusRegistry)}function collectDependencyBlockedIds(corpus,statusRegistry){const itemsById=new Map(corpus.map(item=>[normalizeItemId(item.id),item])),blockerIdsByItem=indexBlockedByIds(corpus),candidateStatuses=new Set([...resolveActiveStatusSet(statusRegistry),...statusRegistry.blocked_statuses]),blocked=new Set;for(const item of corpus){const normalizedStatus=normalizeStatusForRegistry(item.status,statusRegistry);candidateStatuses.has(normalizedStatus)&&(statusRegistry.blocked_statuses.has(normalizedStatus)||resolveItemBlockersWithIndex(item,itemsById,statusRegistry,blockerIdsByItem).some(blocker=>!blocker.resolved))&&blocked.add(normalizeItemId(item.id))}return blocked}function selectActionableEntries(report,statusRegistry,options={},typeRegistry){const candidates=[...report.ready,...report.decisions,...report.gates];options.includeContainers===!0&&candidates.push(...report.containers.filter(entry=>entry.open_blockers.length===0&&!statusRegistry.blocked_statuses.has(normalizeStatusForRegistry(entry.item.status,statusRegistry))));const selected={ready:[],decisions:[],gates:[]};for(const entry of candidates){const role=resolveItemExecutionRole(entry.item.type,typeRegistry);role==="human"&&options.includeDecisions!==!0?selected.decisions.push(entry):role==="gate"&&options.includeGates!==!0?selected.gates.push(entry):selected.ready.push(entry)}return selected}function resolveActiveStatusSet(statusRegistry){return statusRegistry.active_statuses.size>0?statusRegistry.active_statuses:new Set([statusRegistry.open_status])}function hasOpenDescendant(rootId,childrenByParent,statusRegistry){const stack=[normalizeItemId(rootId)],visited=new Set;for(;stack.length>0;){const current=stack.pop();if(!visited.has(current)){visited.add(current);for(const child of childrenByParent.get(current)??[]){if(!isTerminalStatus(child.status,statusRegistry))return!0;stack.push(normalizeItemId(child.id))}}}return!1}function indexCorpus(corpus){const itemsById=new Map,childrenByParent=new Map,blockedByReverse=new Map,blockerIdsByItem=indexBlockedByIds(corpus);for(const item of corpus){itemsById.set(normalizeItemId(item.id),item);const parent=typeof item.parent=="string"?item.parent.trim():"";if(parent.length>0){const parentKey=normalizeItemId(parent),siblings=childrenByParent.get(parentKey)??[];siblings.push(item),childrenByParent.set(parentKey,siblings)}for(const blockerId of blockerIdsByItem.get(normalizeItemId(item.id))){const blockerKey=normalizeItemId(blockerId),dependents=blockedByReverse.get(blockerKey)??[];dependents.push(item.id),blockedByReverse.set(blockerKey,dependents)}}return{itemsById,childrenByParent,blockedByReverse,blockerIdsByItem}}function computeActionabilityReport(candidates,corpus,statusRegistry,typeRegistry){const{itemsById,childrenByParent,blockedByReverse,blockerIdsByItem}=indexCorpus(corpus),activeStatuses=new Set([...resolveActiveStatusSet(statusRegistry),...statusRegistry.blocked_statuses]),nonTerminalIds=new Set(corpus.filter(entry=>!isTerminalStatus(entry.status,statusRegistry)).map(entry=>normalizeItemId(entry.id))),ready=[],blocked=[],containers=[],decisions=[],gates=[];let activeCount=0;for(const item of candidates){if(!activeStatuses.has(normalizeStatusForRegistry(item.status,statusRegistry)))continue;activeCount+=1;const openBlockers=resolveItemBlockersWithIndex(item,itemsById,statusRegistry,blockerIdsByItem).filter(blocker=>!blocker.resolved),unblocks=(blockedByReverse.get(normalizeItemId(item.id))??[]).filter(dependentId=>nonTerminalIds.has(normalizeItemId(dependentId))).sort((left,right)=>left.localeCompare(right)),entry={item,open_blockers:openBlockers,unblocks};if(hasOpenDescendant(item.id,childrenByParent,statusRegistry)){containers.push(entry);continue}const lifecycleBlocked=statusRegistry.blocked_statuses.has(normalizeStatusForRegistry(item.status,statusRegistry));if(openBlockers.length===0&&!lifecycleBlocked){const role=resolveItemExecutionRole(item.type,typeRegistry);role==="human"?decisions.push(entry):role==="gate"?gates.push(entry):ready.push(entry)}else blocked.push(entry)}return{ready,blocked,containers,decisions,gates,active_count:activeCount,container_count:containers.length}}export{ACTIONABILITY_ORDERING_KINDS,collectBlockedByIds,collectBlockedByIdsFromCorpus,collectDependencyBlockedIds,computeActionabilityReport,indexBlockedByIds,resolveItemBlockers,resolveItemExecutionRole,selectActionableEntries};
2
2
  //# sourceMappingURL=actionability.js.map
@@ -3,7 +3,8 @@
3
3
  *
4
4
  * Safety-first policy: if a lock file cannot be read or parsed, it is RETAINED
5
5
  * rather than deleted. We only remove locks whose staleness can be conclusively
6
- * determined from the lock's own embedded `ttl_seconds` field.
6
+ * determined from the embedded lease or positive same-host owner-death evidence.
7
+ * Deletion is revalidated under the writer cleanup gate.
7
8
  *
8
9
  * Staleness formula: (now_ms - Date.parse(created_at)) > ttl_seconds * 1000
9
10
  * Zero ttl means any lock older than 0 ms is stale. Negative ttl means always stale.
@@ -22,10 +23,14 @@ created_at:string | null;
22
23
  ttl_seconds:number | null;
23
24
  /** floor((now - created_at)/1000), null if unparseable */
24
25
  age_seconds:number | null;
25
- /** true only when classified expired */
26
+ /** True only when lease expiry or positive original-owner death is established. */
26
27
  stale:boolean;
27
28
  /** Value that configures or reports reason for this contract. */
28
- reason:"expired" | "active" | "unparseable";
29
+ reason:"expired" | "owner_dead" | "active" | "unparseable";
30
+ /** Verified original-process liveness, when the receipt supports same-host observation. */
31
+ owner_state?:"alive" | "dead" | "unknown";
32
+ /** Valid recorded PID, available for diagnosis without revealing host coordinates. */
33
+ pid?:number;
29
34
  }
30
35
  /** Documents the lock gc hooks payload exchanged by command, SDK, and package integrations. */
31
36
  export interface LockGcHooks {
@@ -57,7 +62,7 @@ warnings:string[];
57
62
  entries:StaleLockScanEntry[];
58
63
  }
59
64
  /** Fine-grained classification outcome for one readable lock file's content. `unparseable_json` and `invalid_timestamp` both surface as the coarse `reason: "unparseable"` on the scan entry (gc retains both), but stay distinct here so callers can emit precise warnings. */
60
- export type LockClassificationDetail = "active" | "expired" | "unparseable_json" | "invalid_timestamp";
65
+ export type LockClassificationDetail = "active" | "expired" | "owner_dead" | "unparseable_json" | "invalid_timestamp";
61
66
  /** Documents the lock content classification payload exchanged by command, SDK, and package integrations. */
62
67
  export interface LockContentClassification {
63
68
  /** Value that configures or reports detail for this contract. */
@@ -65,7 +70,7 @@ detail:LockClassificationDetail;
65
70
  /** Value that configures or reports entry for this contract. */
66
71
  entry:StaleLockScanEntry;
67
72
  }
68
- /** Pure classification of one lock file's raw content against `nowMs`. This is the single staleness/parse policy shared by `pm gc --scope locks` (which acts on it) and the `pm health` locks check (which only reports it). */
73
+ /** Pure TTL classification retained for callers that cannot observe the owner process. Health and GC additionally share conservative same-host liveness observation. */
69
74
  export declare function classifyLockContent(file:string,raw:string,nowMs:number):LockContentClassification;
70
75
  /** Documents the lock health scan payload exchanged by command, SDK, and package integrations. */
71
76
  export interface LockHealthScan {
@@ -79,6 +84,8 @@ stale_lock_count:number;
79
84
  unreadable_lock_count:number;
80
85
  /** invalid JSON, wrong shape, or an unparseable created_at timestamp */
81
86
  unparseable_lock_count:number;
87
+ /** At most twenty verified dead-owner receipts for actionable full health diagnosis. */
88
+ owner_dead_lock_samples?:StaleLockScanEntry[];
82
89
  }
83
90
  /** Read-only locks scan for `pm health`: classifies every lock file with the same policy `pm gc --scope locks` uses but never removes anything. */
84
91
  export declare function scanLockHealth(pmRoot:string,now?:number):Promise<LockHealthScan>;
@@ -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]="b5974276-3b45-5ad6-a7aa-e05a33807ee7")}catch{}})();import{isFileMissingError}from"../fs/fs-utils.js";import fs from"node:fs/promises";import path from"node:path";function parseLock(raw){let parsed;try{parsed=JSON.parse(raw)}catch{return null}if(typeof parsed!="object"||parsed===null||Array.isArray(parsed))return null;const candidate=parsed,{id,owner,created_at,ttl_seconds}=candidate;return typeof id!="string"||typeof owner!="string"||typeof created_at!="string"||typeof ttl_seconds!="number"||!Number.isFinite(ttl_seconds)?null:{id,owner,created_at,ttl_seconds}}function classifyLockContent(file,raw,nowMs){const lock=parseLock(raw);if(lock===null)return{detail:"unparseable_json",entry:{file,id:null,owner:null,created_at:null,ttl_seconds:null,age_seconds:null,stale:!1,reason:"unparseable"}};const createdAtMs=Date.parse(lock.created_at);if(!Number.isFinite(createdAtMs))return{detail:"invalid_timestamp",entry:{file,id:lock.id,owner:lock.owner,created_at:lock.created_at,ttl_seconds:lock.ttl_seconds,age_seconds:null,stale:!1,reason:"unparseable"}};const ageMs=nowMs-createdAtMs,age_seconds=Math.floor(ageMs/1e3),stale=ageMs>lock.ttl_seconds*1e3;return{detail:stale?"expired":"active",entry:{file,id:lock.id,owner:lock.owner,created_at:lock.created_at,ttl_seconds:lock.ttl_seconds,age_seconds,stale,reason:stale?"expired":"active"}}}async function scanLockHealth(pmRoot,now){const nowMs=now??Date.now(),locksDir=path.join(pmRoot,"locks"),scan={scanned:0,active_lock_count:0,stale_lock_count:0,unreadable_lock_count:0,unparseable_lock_count:0};let dirEntries;try{dirEntries=(await fs.readdir(locksDir)).filter(f=>f.endsWith(".lock")).sort()}catch(error){if(isFileMissingError(error))return scan;throw error}for(const file of dirEntries){let raw;try{raw=await fs.readFile(path.join(locksDir,file),"utf8")}catch(readError){if(isFileMissingError(readError))continue;scan.scanned+=1,scan.unreadable_lock_count+=1;continue}scan.scanned+=1;const{detail}=classifyLockContent(file,raw,nowMs);detail==="active"?scan.active_lock_count+=1:detail==="expired"?scan.stale_lock_count+=1:scan.unparseable_lock_count+=1}return scan}async function runLockGc(pmRoot,options){const{dryRun,hooks}=options,nowMs=options.now??Date.now(),locksDir=path.join(pmRoot,"locks"),result={scanned:0,removed:[],retained:[],warnings:[],entries:[]};let dirEntries;try{dirEntries=(await fs.readdir(locksDir)).filter(f=>f.endsWith(".lock")).sort()}catch(error){if(isFileMissingError(error))return result;throw error}for(const file of dirEntries){result.scanned+=1;const absPath=path.join(locksDir,file),relPath=`locks/${file}`;if(hooks?.onRead){const hookWarnings=await hooks.onRead(absPath);result.warnings.push(...hookWarnings)}let raw;try{raw=await fs.readFile(absPath,"utf8")}catch(readError){if(isFileMissingError(readError)){result.scanned-=1;continue}result.warnings.push(`lock_unreadable:${file}`),result.retained.push(relPath),result.entries.push({file,id:null,owner:null,created_at:null,ttl_seconds:null,age_seconds:null,stale:!1,reason:"unparseable"});continue}const classified=classifyLockContent(file,raw,nowMs);if(classified.detail==="unparseable_json"){result.warnings.push(`lock_unparseable:${file}`),result.retained.push(relPath),result.entries.push(classified.entry);continue}if(classified.detail==="invalid_timestamp"){result.warnings.push(`lock_invalid_timestamp:${file}`),result.retained.push(relPath),result.entries.push(classified.entry);continue}if(classified.detail==="active"){result.retained.push(relPath),result.entries.push(classified.entry);continue}if(!dryRun){try{await fs.unlink(absPath)}catch(unlinkError){if(!isFileMissingError(unlinkError)){result.warnings.push(`lock_remove_failed:${file}`),result.retained.push(relPath),result.entries.push(classified.entry);continue}}if(hooks?.onWrite){const hookWarnings=await hooks.onWrite(absPath);result.warnings.push(...hookWarnings)}}result.removed.push(relPath),result.entries.push(classified.entry)}return result}export{classifyLockContent,runLockGc,scanLockHealth};
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]="6b4ef85f-d844-53e3-ba32-08b95377584a")}catch{}})();import{inspectLockOwner}from"./lock-owner.js";import{acquireStaleCleanupGate,recordDeadLockRecovery}from"./lock.js";import{isFileMissingError}from"../fs/fs-utils.js";import fs from"node:fs/promises";import path from"node:path";function parseLock(raw){let parsed;try{parsed=JSON.parse(raw)}catch{return null}if(typeof parsed!="object"||parsed===null||Array.isArray(parsed))return null;const candidate=parsed,{id,owner,created_at,ttl_seconds}=candidate;return typeof id!="string"||typeof owner!="string"||typeof created_at!="string"||typeof ttl_seconds!="number"||!Number.isFinite(ttl_seconds)?null:{id,owner,created_at,ttl_seconds,pid:candidate.pid,host_identity:candidate.host_identity,process_start_identity:candidate.process_start_identity}}function classifyLockContent(file,raw,nowMs){const lock=parseLock(raw);if(lock===null)return{detail:"unparseable_json",entry:{file,id:null,owner:null,created_at:null,ttl_seconds:null,age_seconds:null,stale:!1,reason:"unparseable"}};const createdAtMs=Date.parse(lock.created_at);if(!Number.isFinite(createdAtMs))return{detail:"invalid_timestamp",entry:{file,id:lock.id,owner:lock.owner,created_at:lock.created_at,ttl_seconds:lock.ttl_seconds,age_seconds:null,stale:!1,reason:"unparseable"}};const ageMs=nowMs-createdAtMs,age_seconds=Math.floor(ageMs/1e3),stale=ageMs>lock.ttl_seconds*1e3;return{detail:stale?"expired":"active",entry:{file,id:lock.id,owner:lock.owner,created_at:lock.created_at,ttl_seconds:lock.ttl_seconds,age_seconds,stale,reason:stale?"expired":"active"}}}async function classifyObservedLock(file,raw,nowMs){const classified=classifyLockContent(file,raw,nowMs);if(classified.detail!=="active")return classified;const lock=parseLock(raw),state=await inspectLockOwner(lock);return state==="unknown"?classified:{detail:state==="dead"?"owner_dead":"active",entry:{...classified.entry,owner_state:state,pid:lock.pid,...state==="dead"?{stale:!0,reason:"owner_dead"}:{}}}}async function removeObservedLock(lockPath,raw,ownerDead){const release=await acquireStaleCleanupGate(lockPath,"gc");if(release===null)return!1;try{let current;try{current=await fs.readFile(lockPath,"utf8")}catch(error){if(isFileMissingError(error))return!0;throw error}if(current!==raw)return!1;ownerDead&&await recordDeadLockRecovery(lockPath,raw);try{await fs.unlink(lockPath)}catch(error){if(!isFileMissingError(error))throw error}return!0}finally{await release()}}function addHealthObservation(scan,detail,entry){detail==="owner_dead"&&(scan.owner_dead_lock_samples??=[],scan.owner_dead_lock_samples.length<20&&scan.owner_dead_lock_samples.push(entry)),detail==="active"?scan.active_lock_count+=1:detail==="expired"||detail==="owner_dead"?scan.stale_lock_count+=1:scan.unparseable_lock_count+=1}async function scanLockHealth(pmRoot,now){const nowMs=now??Date.now(),locksDir=path.join(pmRoot,"locks"),scan={scanned:0,active_lock_count:0,stale_lock_count:0,unreadable_lock_count:0,unparseable_lock_count:0};let dirEntries;try{dirEntries=(await fs.readdir(locksDir)).filter(f=>f.endsWith(".lock")).sort()}catch(error){if(isFileMissingError(error))return scan;throw error}for(const file of dirEntries){let raw;try{raw=await fs.readFile(path.join(locksDir,file),"utf8")}catch(readError){if(isFileMissingError(readError))continue;scan.scanned+=1,scan.unreadable_lock_count+=1;continue}scan.scanned+=1;const{detail,entry}=await classifyObservedLock(file,raw,nowMs);addHealthObservation(scan,detail,entry)}return scan}async function removeOrPreviewLock(result,options,absPath,relPath,file,raw,entry){if(!options.dryRun){try{if(!await removeObservedLock(absPath,raw,entry.reason==="owner_dead")){result.warnings.push(`lock_changed_or_busy:${file}`),result.retained.push(relPath),result.entries.push(entry);return}}catch{result.warnings.push(`lock_remove_failed:${file}`),result.retained.push(relPath),result.entries.push(entry);return}if(options.hooks?.onWrite){const hookWarnings=await options.hooks.onWrite(absPath);result.warnings.push(...hookWarnings)}}result.removed.push(relPath),result.entries.push(entry)}async function runLockGc(pmRoot,options){const{hooks}=options,nowMs=options.now??Date.now(),locksDir=path.join(pmRoot,"locks"),result={scanned:0,removed:[],retained:[],warnings:[],entries:[]};let dirEntries;try{dirEntries=(await fs.readdir(locksDir)).filter(f=>f.endsWith(".lock")).sort()}catch(error){if(isFileMissingError(error))return result;throw error}for(const file of dirEntries){result.scanned+=1;const absPath=path.join(locksDir,file),relPath=`locks/${file}`;if(hooks?.onRead){const hookWarnings=await hooks.onRead(absPath);result.warnings.push(...hookWarnings)}let raw;try{raw=await fs.readFile(absPath,"utf8")}catch(readError){if(isFileMissingError(readError)){result.scanned-=1;continue}result.warnings.push(`lock_unreadable:${file}`),result.retained.push(relPath),result.entries.push({file,id:null,owner:null,created_at:null,ttl_seconds:null,age_seconds:null,stale:!1,reason:"unparseable"});continue}const classified=await classifyObservedLock(file,raw,nowMs);if(classified.detail==="unparseable_json"){result.warnings.push(`lock_unparseable:${file}`),result.retained.push(relPath),result.entries.push(classified.entry);continue}if(classified.detail==="invalid_timestamp"){result.warnings.push(`lock_invalid_timestamp:${file}`),result.retained.push(relPath),result.entries.push(classified.entry);continue}if(classified.detail==="active"){result.retained.push(relPath),result.entries.push(classified.entry);continue}await removeOrPreviewLock(result,options,absPath,relPath,file,raw,classified.entry)}return result}export{classifyLockContent,runLockGc,scanLockHealth};
2
2
  //# sourceMappingURL=lock-gc.js.map
@@ -0,0 +1,33 @@
1
+ /** Stable operating-system identity for a process when the host exposes one. */
2
+ export interface ProcessIdentity {
3
+ /** Operating-system process identifier captured with the owner record. */
4
+ pid:number;
5
+ /** Host process-start coordinate that distinguishes reuse of the same PID. */
6
+ process_start_identity?:string;
7
+ }
8
+ /** Untrusted optional receipt coordinates; validate before observing host processes. */
9
+ export interface LockOwnerIdentity {
10
+ /** PID recorded by the writer; legacy GC receipts may omit it. */
11
+ pid?:unknown;
12
+ /** Opaque boot and PID-namespace identity, absent on unsupported hosts. */
13
+ host_identity?:unknown;
14
+ /** Original process start tick, absent on legacy receipts. */
15
+ process_start_identity?:unknown;
16
+ }
17
+ /** Parse the kernel start tick without treating spaces in a process name as fields. */
18
+ export declare function parseLinuxProcessStartIdentity(raw:string):string | null;
19
+ /** Read the host process-start coordinate used to distinguish PID reuse. */
20
+ export declare function readProcessStartIdentity(pid:number):Promise<string | null>;
21
+ /** Capture the current PID plus a host process-start coordinate when available. */
22
+ export declare function captureProcessIdentity(pid?:number):Promise<ProcessIdentity>;
23
+ /**
24
+ * Verify that a captured process owner still exists, with a bounded grace
25
+ * period when the host cannot expose a reusable-PID-safe start coordinate.
26
+ */
27
+ export declare function isProcessIdentityAlive(identity:ProcessIdentity,unverifiedAgeMs:number,maxUnverifiedAgeMs:number):Promise<boolean>;
28
+ /** Opaque same-boot, same-PID-namespace coordinate; unavailable means no early reclaim. */
29
+ export declare function readLockHostIdentity():Promise<string | null>;
30
+ /** Capture only the coordinates this host can actually verify later. */
31
+ export declare function captureLockOwnerIdentity():Promise<LockOwnerIdentity>;
32
+ /** Positive proof of original-owner death; foreign, legacy, inaccessible and malformed evidence stays unknown. */
33
+ export declare function inspectLockOwner(identity:LockOwnerIdentity | null):Promise<"alive" | "dead" | "unknown">;
@@ -0,0 +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]="d1f829a5-70d6-5096-86f7-717440dd67d5")}catch{}})();import fs from"node:fs/promises";import{createHash}from"node:crypto";function isErrno(error,code){return typeof error=="object"&&error!==null&&"code"in error&&error.code===code}function isProcessAlive(pid){if(!Number.isSafeInteger(pid)||pid<=0)return!1;try{return process.kill(pid,0),!0}catch(error){return!isErrno(error,"ESRCH")}}function parseLinuxProcessStartIdentity(raw){const closeParenIndex=raw.lastIndexOf(")");if(closeParenIndex<0)return null;const startTime=raw.slice(closeParenIndex+2).trim().split(/\s+/u)[19];return startTime!==void 0&&/^\d+$/u.test(startTime)?startTime:null}async function readProcessStartIdentity(pid){try{return parseLinuxProcessStartIdentity(await fs.readFile(`/proc/${pid}/stat`,"utf8"))}catch{return null}}async function captureProcessIdentity(pid=process.pid){const processStartIdentity=await readProcessStartIdentity(pid);return{pid,...processStartIdentity===null?{}:{process_start_identity:processStartIdentity}}}async function isProcessIdentityAlive(identity,unverifiedAgeMs,maxUnverifiedAgeMs){if(!isProcessAlive(identity.pid))return!1;const currentStartIdentity=await readProcessStartIdentity(identity.pid);return identity.process_start_identity!==void 0&&currentStartIdentity!==null?currentStartIdentity===identity.process_start_identity:unverifiedAgeMs<=maxUnverifiedAgeMs}async function readLockHostIdentity(){try{const[boot,namespace]=await Promise.all([fs.readFile("/proc/sys/kernel/random/boot_id","utf8"),fs.readlink("/proc/self/ns/pid")]);return!/^[a-f0-9-]{36}\s*$/u.test(boot)||!/^pid:\[\d+\]$/u.test(namespace)?null:createHash("sha256").update(`${boot.trim()}:${namespace}`).digest("hex")}catch{return null}}async function captureLockOwnerIdentity(){const[host,start]=await Promise.all([readLockHostIdentity(),readProcessStartIdentity(process.pid)]);return host!==null&&start!==null?{host_identity:host,process_start_identity:start}:{}}async function inspectLockOwner(identity){if(!identity||typeof identity.pid!="number"||!Number.isSafeInteger(identity.pid)||identity.pid<=0||typeof identity.host_identity!="string"||typeof identity.process_start_identity!="string"||!/^\d+$/u.test(identity.process_start_identity))return"unknown";const host=await readLockHostIdentity();if(host===null||host!==identity.host_identity)return"unknown";try{process.kill(identity.pid,0)}catch(error){return isErrno(error,"ESRCH")?"dead":"unknown"}const current=await readProcessStartIdentity(identity.pid);return current===null?"unknown":current===identity.process_start_identity?"alive":"dead"}export{captureLockOwnerIdentity,captureProcessIdentity,inspectLockOwner,isProcessIdentityAlive,parseLinuxProcessStartIdentity,readLockHostIdentity,readProcessStartIdentity};
2
+ //# sourceMappingURL=lock-owner.js.map
@@ -1,4 +1,6 @@
1
- interface LockInfo {
1
+ import { parseLinuxProcessStartIdentity,readProcessStartIdentity,type LockOwnerIdentity } from "./lock-owner.js";
2
+ export { captureProcessIdentity,isProcessIdentityAlive,readProcessStartIdentity,} from "./lock-owner.js";
3
+ interface LockInfo extends LockOwnerIdentity {
2
4
  id:string;
3
5
  pid:number;
4
6
  owner:string;
@@ -15,13 +17,6 @@ lockInfo:LockReadResult;
15
17
  shouldRetryCreate:boolean;
16
18
  staleRemovalCounted:boolean;
17
19
  }
18
- /** Stable operating-system identity for a process when the host exposes one. */
19
- export interface ProcessIdentity {
20
- /** Operating-system process identifier captured with the owner record. */
21
- pid:number;
22
- /** Host process-start coordinate that distinguishes reuse of the same PID. */
23
- process_start_identity?:string;
24
- }
25
20
  declare function parseLockInfo(raw:string):LockReadResult;
26
21
  declare function readLockInfo(lockPath:string,emitReadHook?:boolean):Promise<LockReadResult>;
27
22
  declare function isErrno(error:unknown,code:string):boolean;
@@ -34,24 +29,17 @@ declare function isErrno(error:unknown,code:string):boolean;
34
29
  * when its parent remains writable, preserving genuine permission failures.
35
30
  */
36
31
  declare function isLockCreateContention(error:unknown,lockPath:string,platform?:NodeJS.Platform):Promise<boolean>;
37
- declare function buildLockPayload(id:string,owner:string,ttlSeconds:number,token?:string):LockInfo;
32
+ declare function buildLockPayload(id:string,owner:string,ttlSeconds:number,token?:string):Promise<LockInfo>;
38
33
  declare function isStaleLock(info:LockInfo | null,ttlSeconds:number):boolean;
39
34
  /** Observe a built-in lock's owner lease without acquiring it or changing storage. */
40
35
  export declare function hasUnexpiredLockOwner(pmRoot:string,id:string,ownerPrefix:string):Promise<boolean>;
41
36
  declare function lockOwnerSuffix(info:LockInfo | null):string;
42
37
  /** Resolves the effective bounded wait budget for a contended lock: the PM_LOCK_WAIT_MS environment override wins when it parses as a non-negative integer, then the caller-provided budget (settings `locks.wait_ms`), then 0 (fail-fast, the pre-wait behavior). */
43
38
  declare function resolveLockWaitMs(waitMs:number | undefined):number;
44
- declare function parseLinuxProcessStartIdentity(raw:string):string | null;
45
- /** Read the host process-start coordinate used to distinguish PID reuse. */
46
- export declare function readProcessStartIdentity(pid:number):Promise<string | null>;
47
- /** Capture the current PID plus a host process-start coordinate when available. */
48
- export declare function captureProcessIdentity(pid?:number):Promise<ProcessIdentity>;
49
- /**
50
- * Verify that a captured process owner still exists, with a bounded grace
51
- * period when the host cannot expose a reusable-PID-safe start coordinate.
52
- */
53
- export declare function isProcessIdentityAlive(identity:ProcessIdentity,unverifiedAgeMs:number,maxUnverifiedAgeMs:number):Promise<boolean>;
54
- declare function acquireStaleCleanupGate(lockPath:string,_id:string):Promise<(() => Promise<void>) | null>;
39
+ /** Fence lock publication, release and garbage collection under one cleanup owner. */
40
+ export declare function acquireStaleCleanupGate(lockPath:string,_id:string):Promise<(() => Promise<void>) | null>;
41
+ /** Retain an immutable local audit of early-recovery intent before removing the observed owner. */
42
+ export declare function recordDeadLockRecovery(lockPath:string,raw:string):Promise<void>;
55
43
  declare function removeConfirmedStaleLock(params:{
56
44
  lockPath:string;
57
45
  id:string;
@@ -80,7 +68,9 @@ removeConfirmedStaleLock:typeof removeConfirmedStaleLock;
80
68
  };
81
69
  /**
82
70
  * Acquire a token-owned tracker lock with bounded contention waiting and
83
- * explicit stale-owner policy. Background consumers may disable creation of
71
+ * explicit stale-owner policy. Proven same-host original-process death permits
72
+ * automatic early recovery; unverifiable owners retain TTL policy.
73
+ * Background consumers may disable creation of
84
74
  * parent directories so acquiring or retrying a lock cannot revive removed
85
75
  * storage. An explicit maxWaitMs caps contention retries even when the process
86
76
  * environment requests a longer wait. Extension overrides receive that ceiling
@@ -92,4 +82,3 @@ createParentDirectories?:boolean;
92
82
  /** Ceiling on the effective retry budget, including PM_LOCK_WAIT_MS. */
93
83
  maxWaitMs?:number;
94
84
  }):Promise<() => Promise<void>>;
95
- export {};
@@ -1,4 +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]="4a6fa947-cc6f-5e48-8722-3db9a1af36f1")}catch{}})();import{isFileMissingError}from"../fs/fs-utils.js";import{constants as fsConstants}from"node:fs";import fs from"node:fs/promises";import path from"node:path";import{randomUUID}from"node:crypto";import{runActiveOnReadHooks,runActiveOnWriteHooks,runActiveServiceOverride}from"../extensions/index.js";import{EXIT_CODE}from"../shared/constants.js";import{PmCliError}from"../shared/errors.js";import{toErrorMessage}from"../shared/primitives.js";import{getLockPath}from"../store/paths.js";import{nowIso}from"../shared/time.js";function parseLockInfo(raw){let parsed;try{parsed=JSON.parse(raw)}catch{return{info:null,warnings:["lock_info_invalid_json"]}}if(typeof parsed!="object"||parsed===null||Array.isArray(parsed))return{info:null,warnings:["lock_info_invalid_shape"]};const candidate=parsed,id=candidate.id,pid=candidate.pid,owner=candidate.owner,token=candidate.token,createdAt=candidate.created_at,ttlSeconds=candidate.ttl_seconds;return typeof id!="string"||typeof pid!="number"||!Number.isFinite(pid)||typeof owner!="string"||token!==void 0&&(typeof token!="string"||token.trim().length===0)||typeof createdAt!="string"||typeof ttlSeconds!="number"||!Number.isFinite(ttlSeconds)?{info:null,warnings:["lock_info_invalid_shape"]}:{info:{id,pid,owner,...typeof token=="string"?{token}:{},created_at:createdAt,ttl_seconds:ttlSeconds},warnings:[]}}async function readLockInfo(lockPath,emitReadHook=!0){try{const raw=await fs.readFile(lockPath,"utf8");return emitReadHook&&await runActiveOnReadHooks({path:lockPath,scope:"project"}),parseLockInfo(raw)}catch(error){return isFileMissingError(error)?{info:null,warnings:[]}:{info:null,warnings:["lock_info_read_failed"]}}}function isErrno(error,code){return typeof error=="object"&&error!==null&&"code"in error&&error.code===code}async function isLockCreateContention(error,lockPath,platform=process.platform){if(isErrno(error,"EEXIST"))return!0;if(!isErrno(error,"EPERM")&&!isErrno(error,"EACCES")&&!isErrno(error,"EBUSY"))return!1;try{return await fs.lstat(lockPath),!0}catch(probeError){if(platform!=="win32"||!isFileMissingError(probeError)&&!isErrno(probeError,"EPERM")&&!isErrno(probeError,"EACCES")&&!isErrno(probeError,"EBUSY"))return!1}try{return await fs.access(path.dirname(lockPath),fsConstants.W_OK),!0}catch{return!1}}async function emitLockWriteHook(lockPath,op){await runActiveOnWriteHooks({path:lockPath,scope:"project",op})}function buildLockPayload(id,owner,ttlSeconds,token=randomUUID()){return{id,pid:process.pid,owner,token,created_at:nowIso(),ttl_seconds:ttlSeconds}}async function createLockFile(lockPath,id,owner,ttlSeconds,token,createParentDirectories){createParentDirectories&&await fs.mkdir(path.dirname(lockPath),{recursive:!0});const releaseCleanupGate=await acquireStaleCleanupGate(lockPath,id);if(releaseCleanupGate===null)throw await fs.access(path.dirname(lockPath)),Object.assign(new Error("Lock initialization is contended"),{code:"EEXIST"});try{const handle=await fs.open(lockPath,"wx");try{try{await handle.writeFile(`${JSON.stringify(buildLockPayload(id,owner,ttlSeconds,token),null,2)}
2
- `,"utf8")}finally{await handle.close()}}catch(error){throw await unlinkLockWithHook(lockPath,"lock:release"),error}}finally{await releaseCleanupGate()}await emitLockWriteHook(lockPath,"lock:create")}async function unlinkLockWithHook(lockPath,op){try{return await fs.unlink(lockPath),await emitLockWriteHook(lockPath,op),!0}catch{return!1}}async function releaseOwnedLock(lockPath,id,owner,token){const startedAtMs=Date.now();let backoffMs=LOCK_WAIT_INITIAL_DELAY_MS;for(;;){const releaseCleanupGate=await acquireStaleCleanupGate(lockPath,id);if(releaseCleanupGate!==null){try{const current2=await readLockInfo(lockPath,!1);isOwnedLock(current2.info,id,owner,token)&&await unlinkLockWithHook(lockPath,"lock:release")}finally{await releaseCleanupGate()}return}const current=await readLockInfo(lockPath,!1);if(!isOwnedLock(current.info,id,owner,token))return;const elapsedMs=Date.now()-startedAtMs;if(elapsedMs>=LOCK_RELEASE_GATE_WAIT_MS)return;await sleepWithJitter(Math.min(backoffMs,LOCK_RELEASE_GATE_WAIT_MS-elapsedMs)),backoffMs=Math.min(backoffMs*2,LOCK_WAIT_MAX_DELAY_MS)}}function isOwnedLock(info,id,owner,token){return info?.id===id&&info.pid===process.pid&&info.owner===owner&&info.token===token}function isStaleLock(info,ttlSeconds){const createdAtMs=info?.created_at?Date.parse(info.created_at):Number.NaN;return(Number.isFinite(createdAtMs)?Date.now()-createdAtMs:Number.POSITIVE_INFINITY)>ttlSeconds*1e3}async function hasUnexpiredLockOwner(pmRoot,id,ownerPrefix){const{info}=await readLockInfo(getLockPath(pmRoot,id),!1);return info!==null&&info.owner.startsWith(ownerPrefix)&&!isStaleLock(info,info.ttl_seconds)}function lockOwnerSuffix(info){return info?.owner?` (owner ${info.owner})`:""}const LOCK_WAIT_INITIAL_DELAY_MS=25,LOCK_WAIT_MAX_DELAY_MS=200,LOCK_CONFLICT_RETRY_HINT_MS=250,MAX_STALE_LOCK_REMOVALS=3,STALE_CLEANUP_GATE_SUFFIX=".stale-cleanup",STALE_CLEANUP_GATE_OWNER_FILE="owner.json",STALE_CLEANUP_GATE_STALE_MS=1e4,LOCK_RELEASE_GATE_WAIT_MS=STALE_CLEANUP_GATE_STALE_MS+LOCK_WAIT_MAX_DELAY_MS,STALE_CLEANUP_GATE_MAX_UNVERIFIED_MS=60*6e4;function parseNonNegativeIntegerWaitMs(value){if(typeof value=="string"){const trimmed=value.trim();if(!/^\d+$/.test(trimmed))return;const parsed=Number(trimmed);return Number.isSafeInteger(parsed)?parsed:void 0}if(typeof value=="number"&&Number.isFinite(value)&&value>=0)return Math.trunc(value)}function resolveLockWaitMs(waitMs){const envRaw=process.env.PM_LOCK_WAIT_MS,envParsed=parseNonNegativeIntegerWaitMs(envRaw);if(envParsed!==void 0)return envParsed;const callerParsed=parseNonNegativeIntegerWaitMs(waitMs);return callerParsed!==void 0?callerParsed:0}function cappedLockWaitMs(waitMs,maxWaitMs){return Math.min(resolveLockWaitMs(waitMs),maxWaitMs??1/0)}function sleepWithJitter(baseDelayMs){const jitteredMs=Math.max(1,Math.round(baseDelayMs*(.5+Math.random())));return new Promise(resolve=>setTimeout(resolve,jitteredMs))}function buildLockConflictError(id,info,waitedMs){const waitedSuffix=waitedMs>0?` after waiting ${waitedMs}ms`:"";return new PmCliError(`Item ${id} is locked${lockOwnerSuffix(info)}${waitedSuffix}`,EXIT_CODE.CONFLICT,{code:"lock_conflict",recovery:{retry_after_ms:LOCK_CONFLICT_RETRY_HINT_MS}})}function resolveLockOverrideRelease(result){if(typeof result=="function")return result;if(typeof result=="object"&&result!==null&&"release"in result){const release=result.release;return typeof release=="function"?release:null}return null}function throwIfStaleLockNeedsForce(id,lockInfo,force,forceRequired){if(!force&&forceRequired){const warningSuffix=lockInfo.warnings.length>0?` (${lockInfo.warnings.join(",")})`:"";throw new PmCliError(`Item ${id} lock is stale${warningSuffix}; rerun with --force when supported for this command`,EXIT_CODE.CONFLICT)}}function parseStaleCleanupGateOwner(raw){let parsed;try{parsed=JSON.parse(raw)}catch{return null}if(typeof parsed!="object"||parsed===null||Array.isArray(parsed))return null;const candidate=parsed,pid=candidate.pid,token=candidate.token,processStartIdentity=candidate.process_start_identity;return typeof pid!="number"||!Number.isInteger(pid)||pid<=0||typeof token!="string"||token.length===0||processStartIdentity!==void 0&&(typeof processStartIdentity!="string"||!/^\d+$/u.test(processStartIdentity))?null:{pid,token,...typeof processStartIdentity=="string"?{process_start_identity:processStartIdentity}:{}}}async function readStaleCleanupGateOwner(gatePath){try{return parseStaleCleanupGateOwner(await fs.readFile(path.join(gatePath,STALE_CLEANUP_GATE_OWNER_FILE),"utf8"))}catch{return null}}function isProcessAlive(pid){if(!Number.isSafeInteger(pid)||pid<=0)return!1;try{return process.kill(pid,0),!0}catch(error){return!isErrno(error,"ESRCH")}}function parseLinuxProcessStartIdentity(raw){const closeParenIndex=raw.lastIndexOf(")");if(closeParenIndex<0)return null;const startTime=raw.slice(closeParenIndex+2).trim().split(/\s+/u)[19];return startTime!==void 0&&/^\d+$/u.test(startTime)?startTime:null}async function readProcessStartIdentity(pid){try{return parseLinuxProcessStartIdentity(await fs.readFile(`/proc/${pid}/stat`,"utf8"))}catch{return null}}async function captureProcessIdentity(pid=process.pid){const processStartIdentity=await readProcessStartIdentity(pid);return{pid,...processStartIdentity===null?{}:{process_start_identity:processStartIdentity}}}async function isProcessIdentityAlive(identity,unverifiedAgeMs,maxUnverifiedAgeMs){if(!isProcessAlive(identity.pid))return!1;const currentStartIdentity=await readProcessStartIdentity(identity.pid);return identity.process_start_identity!==void 0&&currentStartIdentity!==null?currentStartIdentity===identity.process_start_identity:unverifiedAgeMs<=maxUnverifiedAgeMs}async function removeStaleCleanupGateDirectory(gatePath){try{return await fs.rm(gatePath,{recursive:!0,force:!0}),!0}catch{return!1}}async function releaseStaleCleanupGate(gatePath,token){const owner=await readStaleCleanupGateOwner(gatePath);owner===null||owner.token!==token||await removeStaleCleanupGateDirectory(gatePath)}async function tryCreateStaleCleanupGate(gatePath){try{await fs.mkdir(gatePath)}catch(error){return isErrno(error,"EEXIST"),null}const owner={...await captureProcessIdentity(),token:randomUUID()};try{await fs.writeFile(path.join(gatePath,STALE_CLEANUP_GATE_OWNER_FILE),`${JSON.stringify(owner)}
3
- `,"utf8")}catch{return await removeStaleCleanupGateDirectory(gatePath),null}return owner}async function removeExpiredStaleCleanupGate(gatePath){let gateAgeMs;try{const stats=await fs.stat(gatePath);if(gateAgeMs=Date.now()-stats.mtimeMs,gateAgeMs<=STALE_CLEANUP_GATE_STALE_MS)return!1}catch(error){return isFileMissingError(error)}const owner=await readStaleCleanupGateOwner(gatePath);return owner!==null&&await isProcessIdentityAlive(owner,gateAgeMs,STALE_CLEANUP_GATE_MAX_UNVERIFIED_MS)?!1:removeStaleCleanupGateDirectory(gatePath)}async function acquireStaleCleanupGate(lockPath,_id){const gatePath=`${lockPath}${STALE_CLEANUP_GATE_SUFFIX}`,owner=await tryCreateStaleCleanupGate(gatePath);if(owner!==null)return async()=>releaseStaleCleanupGate(gatePath,owner.token);if(!await removeExpiredStaleCleanupGate(gatePath))return null;const recoveredOwner=await tryCreateStaleCleanupGate(gatePath);return recoveredOwner===null?null:async()=>releaseStaleCleanupGate(gatePath,recoveredOwner.token)}async function removeConfirmedStaleLock(params){const releaseCleanupGate=await acquireStaleCleanupGate(params.lockPath,params.id);if(releaseCleanupGate===null)return{lockInfo:params.fallbackLockInfo,shouldRetryCreate:!1,staleRemovalCounted:!1};try{const currentLockInfo=await readLockInfo(params.lockPath);if(currentLockInfo.info===null&&currentLockInfo.warnings.length===0)return{lockInfo:currentLockInfo,shouldRetryCreate:!0,staleRemovalCounted:!1};if(!isStaleLock(currentLockInfo.info,params.ttlSeconds))return{lockInfo:currentLockInfo,shouldRetryCreate:!1,staleRemovalCounted:!1};if(throwIfStaleLockNeedsForce(params.id,currentLockInfo,params.force,params.forceRequiredForStaleLock),params.staleRemovals>=MAX_STALE_LOCK_REMOVALS)throw buildLockConflictError(params.id,currentLockInfo.info,Date.now()-params.startedAtMs);return{lockInfo:currentLockInfo,shouldRetryCreate:await unlinkLockWithHook(params.lockPath,"lock:stale_remove"),staleRemovalCounted:!0}}finally{await releaseCleanupGate()}}const _testOnly={parseLockInfo,readLockInfo,isErrno,isLockCreateContention,buildLockPayload,isStaleLock,lockOwnerSuffix,resolveLockWaitMs,parseLinuxProcessStartIdentity,readProcessStartIdentity,acquireStaleCleanupGate,removeConfirmedStaleLock};async function acquireLock(pmRoot,id,ttlSeconds,owner,force=!1,forceRequiredForStaleLock=!0,waitMs,options={}){const maxWaitMs=parseNonNegativeIntegerWaitMs(options.maxWaitMs),lockOverride=await runActiveServiceOverride("lock_acquire",{pm_root:pmRoot,id,ttl_seconds:ttlSeconds,owner,force,force_required_for_stale_lock:forceRequiredForStaleLock,...maxWaitMs===void 0?{}:{max_wait_ms:maxWaitMs}});if(lockOverride.handled){const release=resolveLockOverrideRelease(lockOverride.result);if(release)return async()=>{await Promise.resolve(release()),await runActiveServiceOverride("lock_release",{pm_root:pmRoot,id,owner})}}const lockPath=getLockPath(pmRoot,id),waitBudgetMs=cappedLockWaitMs(waitMs,maxWaitMs),startedAtMs=Date.now();let staleRemovals=0,backoffMs=LOCK_WAIT_INITIAL_DELAY_MS;const token=randomUUID();for(;;)try{return await createLockFile(lockPath,id,owner,ttlSeconds,token,options.createParentDirectories!==!1),async()=>{await releaseOwnedLock(lockPath,id,owner,token),await runActiveServiceOverride("lock_release",{pm_root:pmRoot,id,owner})}}catch(error){if(!await isLockCreateContention(error,lockPath))throw new PmCliError(`Failed to acquire lock for ${id}: ${toErrorMessage(error)}`,EXIT_CODE.GENERIC_FAILURE);let lockInfo=await readLockInfo(lockPath);if(isStaleLock(lockInfo.info,ttlSeconds)){const staleRemoval=await removeConfirmedStaleLock({lockPath,id,ttlSeconds,force,forceRequiredForStaleLock,staleRemovals,waitBudgetMs,startedAtMs,fallbackLockInfo:lockInfo});if(lockInfo=staleRemoval.lockInfo,staleRemoval.staleRemovalCounted&&(staleRemovals+=1),staleRemoval.shouldRetryCreate)continue}const elapsedMs=Date.now()-startedAtMs;if(waitBudgetMs===0||elapsedMs>=waitBudgetMs)throw buildLockConflictError(id,lockInfo.info,waitBudgetMs===0?0:elapsedMs);await sleepWithJitter(Math.min(backoffMs,waitBudgetMs-elapsedMs)),backoffMs=Math.min(backoffMs*2,LOCK_WAIT_MAX_DELAY_MS)}}export{_testOnly,acquireLock,captureProcessIdentity,hasUnexpiredLockOwner,isProcessIdentityAlive,readProcessStartIdentity};
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]="8aac3d30-44d1-5876-ab56-6dbc2285d28f")}catch{}})();import{isFileMissingError}from"../fs/fs-utils.js";import{constants as fsConstants}from"node:fs";import fs from"node:fs/promises";import path from"node:path";import{randomUUID}from"node:crypto";import{runActiveOnReadHooks,runActiveOnWriteHooks,runActiveServiceOverride}from"../extensions/index.js";import{EXIT_CODE}from"../shared/constants.js";import{PmCliError}from"../shared/errors.js";import{toErrorMessage}from"../shared/primitives.js";import{getLockPath}from"../store/paths.js";import{nowIso}from"../shared/time.js";import{captureProcessIdentity,captureLockOwnerIdentity,inspectLockOwner,isProcessIdentityAlive,parseLinuxProcessStartIdentity,readProcessStartIdentity}from"./lock-owner.js";import{captureProcessIdentity as captureProcessIdentity2,isProcessIdentityAlive as isProcessIdentityAlive2,readProcessStartIdentity as readProcessStartIdentity2}from"./lock-owner.js";function parseLockInfo(raw){let parsed;try{parsed=JSON.parse(raw)}catch{return{info:null,warnings:["lock_info_invalid_json"]}}if(typeof parsed!="object"||parsed===null||Array.isArray(parsed))return{info:null,warnings:["lock_info_invalid_shape"]};const candidate=parsed,id=candidate.id,pid=candidate.pid,owner=candidate.owner,token=candidate.token,createdAt=candidate.created_at,ttlSeconds=candidate.ttl_seconds;return typeof id!="string"||typeof pid!="number"||!Number.isFinite(pid)||typeof owner!="string"||token!==void 0&&(typeof token!="string"||token.trim().length===0)||typeof createdAt!="string"||typeof ttlSeconds!="number"||!Number.isFinite(ttlSeconds)?{info:null,warnings:["lock_info_invalid_shape"]}:{info:{id,pid,owner,...typeof token=="string"?{token}:{},created_at:createdAt,ttl_seconds:ttlSeconds,host_identity:candidate.host_identity,process_start_identity:candidate.process_start_identity},warnings:[]}}async function readLockInfo(lockPath,emitReadHook=!0){try{const raw=await fs.readFile(lockPath,"utf8");return emitReadHook&&await runActiveOnReadHooks({path:lockPath,scope:"project"}),parseLockInfo(raw)}catch(error){return isFileMissingError(error)?{info:null,warnings:[]}:{info:null,warnings:["lock_info_read_failed"]}}}function isErrno(error,code){return typeof error=="object"&&error!==null&&"code"in error&&error.code===code}async function isLockCreateContention(error,lockPath,platform=process.platform){if(isErrno(error,"EEXIST"))return!0;if(!isErrno(error,"EPERM")&&!isErrno(error,"EACCES")&&!isErrno(error,"EBUSY"))return!1;try{return await fs.lstat(lockPath),!0}catch(probeError){if(platform!=="win32"||!isFileMissingError(probeError)&&!isErrno(probeError,"EPERM")&&!isErrno(probeError,"EACCES")&&!isErrno(probeError,"EBUSY"))return!1}try{return await fs.access(path.dirname(lockPath),fsConstants.W_OK),!0}catch{return!1}}async function emitLockWriteHook(lockPath,op){await runActiveOnWriteHooks({path:lockPath,scope:"project",op})}async function buildLockPayload(id,owner,ttlSeconds,token=randomUUID()){return{...await captureLockOwnerIdentity(),id,pid:process.pid,owner,token,created_at:nowIso(),ttl_seconds:ttlSeconds}}async function createLockFile(lockPath,id,owner,ttlSeconds,token,createParentDirectories){createParentDirectories&&await fs.mkdir(path.dirname(lockPath),{recursive:!0});const releaseCleanupGate=await acquireStaleCleanupGate(lockPath,id);if(releaseCleanupGate===null)throw await fs.access(path.dirname(lockPath)),Object.assign(new Error("Lock initialization is contended"),{code:"EEXIST"});try{const handle=await fs.open(lockPath,"wx");try{try{await handle.writeFile(`${JSON.stringify(await buildLockPayload(id,owner,ttlSeconds,token),null,2)}
2
+ `,"utf8")}finally{await handle.close()}}catch(error){throw await unlinkLockWithHook(lockPath,"lock:release"),error}}finally{await releaseCleanupGate()}await emitLockWriteHook(lockPath,"lock:create")}async function unlinkLockWithHook(lockPath,op){try{return await fs.unlink(lockPath),await emitLockWriteHook(lockPath,op),!0}catch{return!1}}async function releaseOwnedLock(lockPath,id,owner,token){const startedAtMs=Date.now();let backoffMs=LOCK_WAIT_INITIAL_DELAY_MS;for(;;){const releaseCleanupGate=await acquireStaleCleanupGate(lockPath,id);if(releaseCleanupGate!==null){try{const current2=await readLockInfo(lockPath,!1);isOwnedLock(current2.info,id,owner,token)&&await unlinkLockWithHook(lockPath,"lock:release")}finally{await releaseCleanupGate()}return}const current=await readLockInfo(lockPath,!1);if(!isOwnedLock(current.info,id,owner,token))return;const elapsedMs=Date.now()-startedAtMs;if(elapsedMs>=LOCK_RELEASE_GATE_WAIT_MS)return;await sleepWithJitter(Math.min(backoffMs,LOCK_RELEASE_GATE_WAIT_MS-elapsedMs)),backoffMs=Math.min(backoffMs*2,LOCK_WAIT_MAX_DELAY_MS)}}function isOwnedLock(info,id,owner,token){return info?.id===id&&info.pid===process.pid&&info.owner===owner&&info.token===token}function isStaleLock(info,ttlSeconds){const createdAtMs=info?.created_at?Date.parse(info.created_at):Number.NaN;return(Number.isFinite(createdAtMs)?Date.now()-createdAtMs:Number.POSITIVE_INFINITY)>ttlSeconds*1e3}async function isConfirmedDeadLock(info){return info!==null&&Number.isFinite(Date.parse(info.created_at))&&await inspectLockOwner(info)==="dead"}async function isReclaimableLock(info,ttlSeconds){return isStaleLock(info,ttlSeconds)||await isConfirmedDeadLock(info)}async function hasUnexpiredLockOwner(pmRoot,id,ownerPrefix){const{info}=await readLockInfo(getLockPath(pmRoot,id),!1);return info!==null&&info.owner.startsWith(ownerPrefix)&&!await isReclaimableLock(info,info.ttl_seconds)}function lockOwnerSuffix(info){return info?.owner?` (owner ${info.owner})`:""}const LOCK_WAIT_INITIAL_DELAY_MS=25,LOCK_WAIT_MAX_DELAY_MS=200,LOCK_CONFLICT_RETRY_HINT_MS=250,MAX_STALE_LOCK_REMOVALS=3,STALE_CLEANUP_GATE_SUFFIX=".stale-cleanup",STALE_CLEANUP_GATE_OWNER_FILE="owner.json",STALE_CLEANUP_GATE_STALE_MS=1e4,LOCK_RELEASE_GATE_WAIT_MS=STALE_CLEANUP_GATE_STALE_MS+LOCK_WAIT_MAX_DELAY_MS,STALE_CLEANUP_GATE_MAX_UNVERIFIED_MS=60*6e4;function parseNonNegativeIntegerWaitMs(value){if(typeof value=="string"){const trimmed=value.trim();if(!/^\d+$/.test(trimmed))return;const parsed=Number(trimmed);return Number.isSafeInteger(parsed)?parsed:void 0}if(typeof value=="number"&&Number.isFinite(value)&&value>=0)return Math.trunc(value)}function resolveLockWaitMs(waitMs){const envRaw=process.env.PM_LOCK_WAIT_MS,envParsed=parseNonNegativeIntegerWaitMs(envRaw);if(envParsed!==void 0)return envParsed;const callerParsed=parseNonNegativeIntegerWaitMs(waitMs);return callerParsed!==void 0?callerParsed:0}function cappedLockWaitMs(waitMs,maxWaitMs){return Math.min(resolveLockWaitMs(waitMs),maxWaitMs??1/0)}function sleepWithJitter(baseDelayMs){const jitteredMs=Math.max(1,Math.round(baseDelayMs*(.5+Math.random())));return new Promise(resolve=>setTimeout(resolve,jitteredMs))}async function buildLockConflictError(id,info,waitedMs){const ownerState=await inspectLockOwner(info),waitedSuffix=waitedMs>0?` after waiting ${waitedMs}ms`:"";return new PmCliError(`Item ${id} is locked${lockOwnerSuffix(info)} (owner process ${ownerState})${waitedSuffix}`,EXIT_CODE.CONFLICT,{code:"lock_conflict",item_id:id,reason:`lock_owner_${ownerState}`,required:"Wait for the owner to release the lock, then retry the same operation. Set PM_LOCK_WAIT_MS to a larger bounded millisecond budget when contention is expected. Inspect stale receipts with pm gc --scope locks --dry-run.",recovery:{retry_after_ms:LOCK_CONFLICT_RETRY_HINT_MS}})}function resolveLockOverrideRelease(result){if(typeof result=="function")return result;if(typeof result=="object"&&result!==null&&"release"in result){const release=result.release;return typeof release=="function"?release:null}return null}function throwIfStaleLockNeedsForce(id,lockInfo,force,forceRequired){if(!force&&forceRequired){const warningSuffix=lockInfo.warnings.length>0?` (${lockInfo.warnings.join(",")})`:"";throw new PmCliError(`Item ${id} lock is stale${warningSuffix}; rerun with --force when supported for this command`,EXIT_CODE.CONFLICT)}}function parseStaleCleanupGateOwner(raw){let parsed;try{parsed=JSON.parse(raw)}catch{return null}if(typeof parsed!="object"||parsed===null||Array.isArray(parsed))return null;const candidate=parsed,pid=candidate.pid,token=candidate.token,processStartIdentity=candidate.process_start_identity;return typeof pid!="number"||!Number.isInteger(pid)||pid<=0||typeof token!="string"||token.length===0||processStartIdentity!==void 0&&(typeof processStartIdentity!="string"||!/^\d+$/u.test(processStartIdentity))?null:{pid,token,...typeof processStartIdentity=="string"?{process_start_identity:processStartIdentity}:{}}}async function readStaleCleanupGateOwner(gatePath){try{return parseStaleCleanupGateOwner(await fs.readFile(path.join(gatePath,STALE_CLEANUP_GATE_OWNER_FILE),"utf8"))}catch{return null}}async function removeStaleCleanupGateDirectory(gatePath){try{return await fs.rm(gatePath,{recursive:!0,force:!0}),!0}catch{return!1}}async function releaseStaleCleanupGate(gatePath,token){const owner=await readStaleCleanupGateOwner(gatePath);owner===null||owner.token!==token||await removeStaleCleanupGateDirectory(gatePath)}async function tryCreateStaleCleanupGate(gatePath){try{await fs.mkdir(gatePath)}catch(error){return isErrno(error,"EEXIST"),null}const owner={...await captureProcessIdentity(),token:randomUUID()};try{await fs.writeFile(path.join(gatePath,STALE_CLEANUP_GATE_OWNER_FILE),`${JSON.stringify(owner)}
3
+ `,"utf8")}catch{return await removeStaleCleanupGateDirectory(gatePath),null}return owner}async function removeExpiredStaleCleanupGate(gatePath){let gateAgeMs;try{const stats=await fs.stat(gatePath);if(gateAgeMs=Date.now()-stats.mtimeMs,gateAgeMs<=STALE_CLEANUP_GATE_STALE_MS)return!1}catch(error){return isFileMissingError(error)}const owner=await readStaleCleanupGateOwner(gatePath);return owner!==null&&await isProcessIdentityAlive(owner,gateAgeMs,STALE_CLEANUP_GATE_MAX_UNVERIFIED_MS)?!1:removeStaleCleanupGateDirectory(gatePath)}async function acquireStaleCleanupGate(lockPath,_id){const gatePath=`${lockPath}${STALE_CLEANUP_GATE_SUFFIX}`,owner=await tryCreateStaleCleanupGate(gatePath);if(owner!==null)return async()=>releaseStaleCleanupGate(gatePath,owner.token);if(!await removeExpiredStaleCleanupGate(gatePath))return null;const recoveredOwner=await tryCreateStaleCleanupGate(gatePath);return recoveredOwner===null?null:async()=>releaseStaleCleanupGate(gatePath,recoveredOwner.token)}async function recordDeadLockRecovery(lockPath,raw){const directory=path.join(path.dirname(lockPath),"recoveries");await fs.mkdir(directory,{recursive:!0}),await fs.writeFile(path.join(directory,`${randomUUID()}.json`),`${JSON.stringify({event:"owner_dead_recovery_attempt",recorded_at:nowIso(),lock:JSON.parse(raw)})}
4
+ `,{encoding:"utf8",flag:"wx",mode:384})}async function removeConfirmedStaleLock(params){const releaseCleanupGate=await acquireStaleCleanupGate(params.lockPath,params.id);if(releaseCleanupGate===null)return{lockInfo:params.fallbackLockInfo,shouldRetryCreate:!1,staleRemovalCounted:!1};try{const currentLockInfo=await readLockInfo(params.lockPath);if(currentLockInfo.info===null&&currentLockInfo.warnings.length===0)return{lockInfo:currentLockInfo,shouldRetryCreate:!0,staleRemovalCounted:!1};const ownerDead=await isConfirmedDeadLock(currentLockInfo.info);if(!isStaleLock(currentLockInfo.info,params.ttlSeconds)&&!ownerDead)return{lockInfo:currentLockInfo,shouldRetryCreate:!1,staleRemovalCounted:!1};if(throwIfStaleLockNeedsForce(params.id,currentLockInfo,params.force||ownerDead,params.forceRequiredForStaleLock),params.staleRemovals>=MAX_STALE_LOCK_REMOVALS)throw await buildLockConflictError(params.id,currentLockInfo.info,Date.now()-params.startedAtMs);return ownerDead&&await recordDeadLockRecovery(params.lockPath,JSON.stringify(currentLockInfo.info)),{lockInfo:currentLockInfo,shouldRetryCreate:await unlinkLockWithHook(params.lockPath,"lock:stale_remove"),staleRemovalCounted:!0}}finally{await releaseCleanupGate()}}const _testOnly={parseLockInfo,readLockInfo,isErrno,isLockCreateContention,buildLockPayload,isStaleLock,lockOwnerSuffix,resolveLockWaitMs,parseLinuxProcessStartIdentity,readProcessStartIdentity,acquireStaleCleanupGate,removeConfirmedStaleLock};async function acquireLock(pmRoot,id,ttlSeconds,owner,force=!1,forceRequiredForStaleLock=!0,waitMs,options={}){const maxWaitMs=parseNonNegativeIntegerWaitMs(options.maxWaitMs),lockOverride=await runActiveServiceOverride("lock_acquire",{pm_root:pmRoot,id,ttl_seconds:ttlSeconds,owner,force,force_required_for_stale_lock:forceRequiredForStaleLock,...maxWaitMs===void 0?{}:{max_wait_ms:maxWaitMs}});if(lockOverride.handled){const release=resolveLockOverrideRelease(lockOverride.result);if(release)return async()=>{await Promise.resolve(release()),await runActiveServiceOverride("lock_release",{pm_root:pmRoot,id,owner})}}const lockPath=getLockPath(pmRoot,id),waitBudgetMs=cappedLockWaitMs(waitMs,maxWaitMs),startedAtMs=Date.now();let staleRemovals=0,backoffMs=LOCK_WAIT_INITIAL_DELAY_MS;const token=randomUUID();for(;;)try{return await createLockFile(lockPath,id,owner,ttlSeconds,token,options.createParentDirectories!==!1),async()=>{await releaseOwnedLock(lockPath,id,owner,token),await runActiveServiceOverride("lock_release",{pm_root:pmRoot,id,owner})}}catch(error){if(!await isLockCreateContention(error,lockPath))throw new PmCliError(`Failed to acquire lock for ${id}: ${toErrorMessage(error)}`,EXIT_CODE.GENERIC_FAILURE);let lockInfo=await readLockInfo(lockPath);if(await isReclaimableLock(lockInfo.info,ttlSeconds)){const staleRemoval=await removeConfirmedStaleLock({lockPath,id,ttlSeconds,force,forceRequiredForStaleLock,staleRemovals,waitBudgetMs,startedAtMs,fallbackLockInfo:lockInfo});if(lockInfo=staleRemoval.lockInfo,staleRemoval.staleRemovalCounted&&(staleRemovals+=1),staleRemoval.shouldRetryCreate)continue}const elapsedMs=Date.now()-startedAtMs;if(waitBudgetMs===0||elapsedMs>=waitBudgetMs)throw await buildLockConflictError(id,lockInfo.info,waitBudgetMs===0?0:elapsedMs);await sleepWithJitter(Math.min(backoffMs,waitBudgetMs-elapsedMs)),backoffMs=Math.min(backoffMs*2,LOCK_WAIT_MAX_DELAY_MS)}}export{_testOnly,acquireLock,acquireStaleCleanupGate,captureProcessIdentity2 as captureProcessIdentity,hasUnexpiredLockOwner,isProcessIdentityAlive2 as isProcessIdentityAlive,readProcessStartIdentity2 as readProcessStartIdentity,recordDeadLockRecovery};
4
5
  //# sourceMappingURL=lock.js.map
@@ -1,4 +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]="9716962d-d651-5e7a-84d8-496d35e8775f")}catch{}})();import{getActiveCommandContext,runActiveCommandOverride,runActiveRendererOverride,runActiveServiceOverrideSync,setActiveCommandResult}from"../extensions/index.js";import{EXIT_CODE}from"../shared/constants.js";import{isHostOutputSuppressed}from"./output-control.js";import{projectMutationResult}from"./mutation-projection.js";import{attachReadOutputContracts}from"../../sdk/context-intent-contracts.js";import{attachOutputTokenAccounting}from"../../sdk/output-token-accounting.js";import{propagateContextUsageServingReceipt}from"../../sdk/context-usage.js";import{isReadOutputBudgetExceeded,resolveReadOutputEncoding}from"../../sdk/read-output-contracts.js";import{encode as encodeToon}from"@toon-format/toon";import{encodePmTableRows}from"../../sdk/output/table-rows.js";import{projectPmBriefListEnvelope}from"../../sdk/output/list-envelope.js";const DECLARED_PROCESS_EXIT_CODES=new Set(Object.values(EXIT_CODE)),COLLECTION_COUNT_ALIASES=new Map([["notes_count","notes"],["tests_count","tests"]]),NATIVE_OUTPUT_MARKER="__pm_native_output";let streamErrorHandlersInstalled=!1;function isPlainObject(value){return typeof value=="object"&&value!==null&&!Array.isArray(value)}function setActiveCommandResultOmitted(source){const omitted={read_output:{result_omitted:!0}};propagateContextUsageServingReceipt(source,omitted),setActiveCommandResult(omitted)}function setActiveCommandDeliveryResult(result,receiptSource,structuredDeliveryPreserved){if(structuredDeliveryPreserved){setActiveCommandResult(result);return}setActiveCommandResultOmitted(receiptSource)}function stripNativeOutputMarker(result){if(!isPlainObject(result)||result[NATIVE_OUTPUT_MARKER]!==!0)return result;const{[NATIVE_OUTPUT_MARKER]:_marker,...rest}=result;return rest}function isBrokenPipeError(error){return typeof error=="object"&&error!==null&&error.code==="EPIPE"}function markStdoutBrokenPipeExitCode(){(process.exitCode===void 0||process.exitCode===EXIT_CODE.SUCCESS)&&(process.exitCode=EXIT_CODE.SUCCESS)}function markStderrBrokenPipeExitCode(){(process.exitCode===void 0||process.exitCode===EXIT_CODE.SUCCESS)&&(process.exitCode=EXIT_CODE.GENERIC_FAILURE)}function markBrokenPipeExitCode(target){if(target==="stdout"){markStdoutBrokenPipeExitCode();return}markStderrBrokenPipeExitCode()}function handleUnhandledStreamError(error){const unhandled=error instanceof Error?error:new Error(String(error));setImmediate(()=>{throw unhandled})}function installStreamErrorHandlers(){streamErrorHandlersInstalled||(streamErrorHandlersInstalled=!0,process.stdout.on("error",error=>{if(isBrokenPipeError(error)){markBrokenPipeExitCode("stdout");return}handleUnhandledStreamError(error)}),process.stderr.on("error",error=>{if(isBrokenPipeError(error)){markBrokenPipeExitCode("stderr");return}handleUnhandledStreamError(error)}))}function writeToStream(target,text){installStreamErrorHandlers();try{return target==="stdout"?process.stdout.write(text):process.stderr.write(text),!0}catch(error){if(isBrokenPipeError(error))return markBrokenPipeExitCode(target),!1;throw error}}function writeStdout(text){return writeToStream("stdout",text)}function writeStderr(text){return writeToStream("stderr",text)}function renderScalar(value){return typeof value=="string"?JSON.stringify(value):typeof value=="number"||typeof value=="boolean"?String(value):value===null?"null":JSON.stringify(value)}function tabularObjectArray(value){const keys=Object.keys(value[0]);return keys.length>0&&value.every(entry=>Object.keys(entry).length===keys.length&&keys.every(key=>{if(!Object.hasOwn(entry,key))return!1;const cell=entry[key];return cell===null||typeof cell=="string"||typeof cell=="number"||typeof cell=="boolean"}))}function indentToon(encoded,depth){const indent=" ".repeat(depth);return encoded.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]="dd3b19cb-eda3-5ee9-a838-f0abaa5cc8ea")}catch{}})();import{getActiveCommandContext,runActiveCommandOverride,runActiveRendererOverride,runActiveServiceOverrideSync,setActiveCommandResult}from"../extensions/index.js";import{EXIT_CODE}from"../shared/constants.js";import{isHostOutputSuppressed}from"./output-control.js";import{projectMutationResult}from"./mutation-projection.js";import{attachReadOutputContracts}from"../../sdk/context-intent-contracts.js";import{attachOutputTokenAccounting}from"../../sdk/output-token-accounting.js";import{propagateContextUsageServingReceipt}from"../../sdk/context-usage.js";import{isReadOutputBudgetExceeded,resolveReadOutputEncoding}from"../../sdk/read-output-contracts.js";import{encode as encodeToon}from"@toon-format/toon";import{encodePmTableRows}from"../../sdk/output/table-rows.js";import{projectPmBriefListEnvelope}from"../../sdk/output/list-envelope.js";const DECLARED_PROCESS_EXIT_CODES=new Set(Object.values(EXIT_CODE)),COLLECTION_COUNT_ALIASES=new Map([["notes_count","notes"],["tests_count","tests"]]),NATIVE_OUTPUT_MARKER="__pm_native_output";let streamErrorHandlersInstalled=!1;function isPlainObject(value){return typeof value=="object"&&value!==null&&!Array.isArray(value)}function setActiveCommandResultOmitted(source){const omitted={read_output:{result_omitted:!0}};propagateContextUsageServingReceipt(source,omitted),setActiveCommandResult(omitted)}function setActiveCommandDeliveryResult(result,receiptSource,structuredDeliveryPreserved){if(structuredDeliveryPreserved){setActiveCommandResult(result);return}setActiveCommandResultOmitted(receiptSource)}function stripNativeOutputMarker(result){if(!isPlainObject(result)||result[NATIVE_OUTPUT_MARKER]!==!0)return result;const{[NATIVE_OUTPUT_MARKER]:_marker,...rest}=result;return rest}function isBrokenPipeError(error){return typeof error=="object"&&error!==null&&error.code==="EPIPE"}function markStdoutBrokenPipeExitCode(){(process.exitCode===void 0||process.exitCode===EXIT_CODE.SUCCESS)&&(process.exitCode=EXIT_CODE.SUCCESS)}function markStderrBrokenPipeExitCode(){(process.exitCode===void 0||process.exitCode===EXIT_CODE.SUCCESS)&&(process.exitCode=EXIT_CODE.GENERIC_FAILURE)}function markBrokenPipeExitCode(target){if(target==="stdout"){markStdoutBrokenPipeExitCode();return}markStderrBrokenPipeExitCode()}function handleUnhandledStreamError(error){const unhandled=error instanceof Error?error:new Error(String(error));setImmediate(()=>{throw unhandled})}function installStreamErrorHandlers(){streamErrorHandlersInstalled||(streamErrorHandlersInstalled=!0,process.stdout.on("error",error=>{if(isBrokenPipeError(error)){markBrokenPipeExitCode("stdout");return}handleUnhandledStreamError(error)}),process.stderr.on("error",error=>{if(isBrokenPipeError(error)){markBrokenPipeExitCode("stderr");return}handleUnhandledStreamError(error)}))}function writeToStream(target,text){installStreamErrorHandlers();try{return target==="stdout"?process.stdout.write(text):process.stderr.write(text),!0}catch(error){if(isBrokenPipeError(error))return markBrokenPipeExitCode(target),!1;throw error}}function writeStdout(text){return writeToStream("stdout",text)}function writeStderr(text){return writeToStream("stderr",text)}function renderScalar(value){return typeof value=="string"?JSON.stringify(value):typeof value=="number"||typeof value=="boolean"?String(value):value===null?"null":JSON.stringify(value)}function tabularObjectArray(value){const keys=Object.keys(value[0]);return keys.length>0&&value.every(entry=>Object.keys(entry).length===keys.length&&keys.every(key=>{if(!Object.hasOwn(entry,key))return!1;const cell=entry[key];return cell===null||typeof cell=="string"||typeof cell=="number"||typeof cell=="boolean"}))}function indentToon(encoded,depth){const indent=" ".repeat(depth);return encoded.split(`
2
2
  `).map(line=>`${indent}${line}`).join(`
3
3
  `)}function isNestedTableCandidateBounded(rows){const columns=new Set;let presentCells=0;for(const row of rows){const keys=Object.keys(row);presentCells+=keys.length;for(const key of keys)columns.add(key)}return rows.length*columns.size<=presentCells*2}function renderToonObjectEntry(key,entry,depth,allowNestedTable){const indent=" ".repeat(depth);if(!isPlainObject(entry)&&!Array.isArray(entry))return`${indent}${key}: ${renderScalar(entry)}`;if(Array.isArray(entry)){if(entry.length===0)return`${indent}${key}: []`;const expanded=indentToon(encodeToon({[key]:entry}),depth);if(allowNestedTable&&entry.length>=2&&entry.every(isPlainObject)&&!tabularObjectArray(entry)&&isNestedTableCandidateBounded(entry)){const table=encodePmTableRows(entry),tabular=indentToon(encodeToon({[key]:table.rows,[`${key}_encoding`]:table.encoding}),depth);if(Buffer.byteLength(tabular)<Buffer.byteLength(expanded))return tabular}return expanded}return isPlainObject(entry)&&Object.keys(entry).length===0?`${indent}${key}: {}`:`${indent}${key}:
4
4
  ${renderToonValue(entry,depth+1)}`}function compactToonArray(values,policy,preserveEmpty){const compactedEntries=values.map(entry=>compactToonValue(entry,policy,preserveEmpty||policy.preserveRowFacts&&isPlainObject(entry))).filter(entry=>entry!==void 0);return compactedEntries.length>0||preserveEmpty?compactedEntries:void 0}function compactToonValue(value,policy={omitDuplicateCountAliases:!0,preserveRowFacts:!0},preserveEmpty=!1){if(value===null)return preserveEmpty?null:void 0;if(Array.isArray(value))return compactToonArray(value,policy,preserveEmpty);if(isPlainObject(value)){const compacted=[],entries=Object.entries(value).filter(([key,entry])=>{const canonicalCount=COLLECTION_COUNT_ALIASES.get(key);return!policy.omitDuplicateCountAliases||canonicalCount===void 0||!isPlainObject(value.collection_counts)||entry!==value.collection_counts[canonicalCount]});for(const[key,entry]of entries){if(key==="omitted_field_groups"&&Array.isArray(entry)&&entry.length===0){compacted.push([key,[]]);continue}const compactedEntry=compactToonValue(entry,policy,preserveEmpty);compactedEntry!==void 0&&compacted.push([key,compactedEntry])}return compacted.length>0||preserveEmpty?Object.fromEntries(compacted):void 0}return value}function renderToonValue(value,depth){const indent=" ".repeat(depth);if(Array.isArray(value))return value.length===0?"[]":indentToon(encodeToon(value),depth);if(isPlainObject(value)){const entries=Object.entries(value);return entries.length===0?"{}":entries.map(([key,entry])=>renderToonObjectEntry(key,entry,depth,!Object.hasOwn(value,`${key}_encoding`)&&!(key==="checks"&&isPlainObject(value.verdict)&&value.verdict.authority==="ok"))).join(`
@@ -6,8 +6,8 @@ ${renderToonValue(entry,depth+1)}`}function compactToonArray(values,policy,prese
6
6
  `)}
7
7
  `;for(const event of value.events){if(!isPlainObject(event))continue;const kind=typeof event.kind=="string"?event.kind:"event",title=typeof event.item_title=="string"?event.item_title:"",itemId=typeof event.item_id=="string"?event.item_id:"",reminderText=typeof event.reminder_text=="string"&&event.reminder_text.length>0?` ${event.reminder_text}`:"";lines.push(`- [${kind}] ${itemId} ${title}${reminderText}`.trim())}return`${lines.join(`
8
8
  `)}
9
- `}const LEAN_READ_ENVELOPE_ECHO_KEYS=new Set(["filters","now","projection","sorting"]);function projectLeanJsonValue(value){const compacted=compactToonValue(value,{omitDuplicateCountAliases:!1,preserveRowFacts:!1});if(!isPlainObject(compacted)||!Array.isArray(compacted.items))return compacted??null;const projected=Object.fromEntries(Object.entries(compacted).filter(([key])=>!LEAN_READ_ENVELOPE_ECHO_KEYS.has(key)));return projected.has_more!==!0&&delete projected.next_cursor,projected}function projectLinkedTestEvidence(value,lean){if(!isPlainObject(value)||!Array.isArray(value.run_results)||!value.run_results.every(isPlainObject))return value;const rows=value.run_results,contexts=rows.map(row=>row.execution_context).filter(context=>context!==void 0),sharedContext=contexts.length===rows.length&&contexts.length>0&&contexts.every(context=>JSON.stringify(context)===JSON.stringify(contexts[0]))?contexts[0]:void 0,runResults=rows.map(row=>{const{execution_context:_executionContext,stdout,stderr,...rest}=row;return sharedContext===void 0?row:!lean||row.status!=="passed"?{...rest,stdout,stderr}:rest});return{...value,...sharedContext===void 0?{}:{execution_context:sharedContext},run_results:runResults}}function projectEmptyCheckDetails(value){return!isPlainObject(value)||value.ok!==!0||!isPlainObject(value.verdict)||value.verdict.authority!=="ok"||!Array.isArray(value.checks)?value:{...value,checks:value.checks.map(omitEmptySuccessfulCheckDetails)}}function omitEmptySuccessfulCheckDetails(check){if(!isPlainObject(check)||check.ok!==!0||!isPlainObject(check.details)||Object.keys(check.details).length!==0)return check;const{details:_details,...rest}=check;return rest}function formatBuiltInOutput(result,format,lean=!1){if(format==="json")return`${JSON.stringify(lean?projectLeanJsonValue(result):result,null,2)}
10
- `;const compactedToon=compactToonValue(projectEmptyCheckDetails(projectPmBriefListEnvelope(result)),{omitDuplicateCountAliases:!0,preserveRowFacts:!0});return compactedToon===void 0?`{}
9
+ `}const LEAN_READ_ENVELOPE_ECHO_KEYS=new Set(["filters","now","projection","sorting"]);function projectLeanJsonValue(value){const compacted=compactToonValue(value,{omitDuplicateCountAliases:!1,preserveRowFacts:!1});if(!isPlainObject(compacted)||!Array.isArray(compacted.items))return compacted??null;const projected=Object.fromEntries(Object.entries(compacted).filter(([key])=>!LEAN_READ_ENVELOPE_ECHO_KEYS.has(key)));return projected.has_more!==!0&&delete projected.next_cursor,projected}function projectLinkedTestEvidence(value,lean){if(!isPlainObject(value)||!Array.isArray(value.run_results)||!value.run_results.every(isPlainObject))return value;const rows=value.run_results,contexts=rows.map(row=>row.execution_context).filter(context=>context!==void 0),sharedContext=contexts.length===rows.length&&contexts.length>0&&contexts.every(context=>JSON.stringify(context)===JSON.stringify(contexts[0]))?contexts[0]:void 0,runResults=rows.map(row=>{const{execution_context:_executionContext,stdout,stderr,...rest}=row;return sharedContext===void 0?row:!lean||row.status!=="passed"?{...rest,stdout,stderr}:rest});return{...value,...sharedContext===void 0?{}:{execution_context:sharedContext},run_results:runResults}}function projectEmptyCheckDetails(value){return!isPlainObject(value)||value.ok!==!0||!isPlainObject(value.verdict)||value.verdict.authority!=="ok"||!Array.isArray(value.checks)?value:{...value,checks:value.checks.map(omitEmptySuccessfulCheckDetails)}}function omitEmptySuccessfulCheckDetails(check){if(!isPlainObject(check)||check.ok!==!0||!isPlainObject(check.details)||Object.keys(check.details).length!==0)return check;const{details:_details,...rest}=check;return rest}function formatBuiltInOutput(result,format,lean=!1){if(format==="json"){const value=lean?projectLeanJsonValue(result):result,briefList=isPlainObject(result)&&isPlainObject(result.projection)&&result.projection.mode==="brief"&&Array.isArray(result.items);return`${JSON.stringify(value,null,briefList?void 0:2)}
10
+ `}const compactedToon=compactToonValue(projectEmptyCheckDetails(projectPmBriefListEnvelope(result)),{omitDuplicateCountAliases:!0,preserveRowFacts:!0});return compactedToon===void 0?`{}
11
11
  `:`${renderToonValue(compactedToon,0)}
12
12
  `}function resolveOutputService(effectiveResult,nativeOutput,options,format){const serviceOverride=nativeOutput?{handled:!1,result:effectiveResult,warnings:[]}:runActiveServiceOverrideSync("output_format",{command:options.command,args:options.commandArgs,command_options:options.commandOptions,global:{...options},pm_root:options.pmRoot,format,options:{...options},result:effectiveResult}),legacyPayloadEchoWarning=serviceOverride.warnings.find(warning=>warning.startsWith("extension_output_format_payload_echo_deprecated:"));return legacyPayloadEchoWarning!==void 0&&writeStderr(`Warning: ${legacyPayloadEchoWarning}. Returning context.payload now declines safely for compatibility; migrate the override to declineServiceOverride() or { handled: false }.
13
13
  `),serviceOverride.handled&&typeof serviceOverride.result=="string"?{result:serviceOverride.result,rendered:serviceOverride.result.endsWith(`
@@ -89,6 +89,8 @@ next_best_command?:string;
89
89
  }
90
90
  /** Documents the pm cli error context payload exchanged by command, SDK, and package integrations. */
91
91
  export interface PmCliErrorContext {
92
+ /** Admission completed before any mutation; omitted when writes may have occurred. */
93
+ changed?:false;
92
94
  /** Value that configures or reports code for this contract. */
93
95
  code?:string;
94
96
  /** Canonical existing item identity returned by a refused create operation. */
@@ -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]="1e7d4217-da18-5bfc-a00b-67bba61f57c2")}catch{}})();class PmCliError extends Error{exitCode;code;context;constructor(message,exitCode,context={}){super(message),this.name="PmCliError",this.exitCode=exitCode,this.context=context,this.code=context.code}}export{PmCliError};
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]="3399082a-aeaf-5235-9524-d0e86253ee95")}catch{}})();class PmCliError extends Error{exitCode;code;context;constructor(message,exitCode,context={}){super(message),this.name="PmCliError",this.exitCode=exitCode,this.context=context,this.code=context.code}}export{PmCliError};
2
2
  //# sourceMappingURL=errors.js.map
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { type AgentClientInfo } from "../sdk/runtime-primitives.js";
2
+ import { readSettings,type AgentClientInfo } from "../sdk/runtime-primitives.js";
3
3
  import { readRequiredString,type PmActionInput } from "../sdk/runtime.js";
4
4
  import { PmMcpProtocolError,resolveMcpRequestContext,type PmMcpRequestContext } from "../sdk/mcp/protocol.js";
5
5
  import { PmMcpInputRequiredError } from "../sdk/mcp/interactions.js";
@@ -24,7 +24,7 @@ declare function detectUnexpectedTopLevelKeys(toolName:string,args:Record<string
24
24
  * routing paths. Return redacted advisories; propagate blocking guard failures.
25
25
  */
26
26
  declare function collectMutationGuardWarnings(toolName:string,action:string | undefined,args:Record<string,unknown>):Promise<string[]>;
27
- declare function detectUnexpectedOptionKeys(toolName:string,action:string | undefined,args:Record<string,unknown>):string[];
27
+ declare function detectUnexpectedOptionKeys(toolName:string,action:string | undefined,args:Record<string,unknown>,schema?:Awaited<ReturnType<typeof readSettings>>["schema"]):string[];
28
28
  declare function resultContent(result:unknown,warnings?:string[],tokenAccounting?:boolean,canonicalStructuredResult?:boolean):Record<string,unknown>;
29
29
  declare function errorContent(error:unknown,canonicalStructuredResult?:boolean):Record<string,unknown>;
30
30
  /** Retain at most 32 own, valid provenance values without cloning unbounded input. */