@unbrained/pm-cli 2026.11.3 → 2026.11.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +10 -0
- package/dist/cli/error-guidance.d.ts +14 -35
- package/dist/cli/error-guidance.js +2 -2
- package/dist/cli/expected-revisions.d.ts +55 -0
- package/dist/cli/expected-revisions.js +2 -0
- package/dist/cli/main.js +2 -2
- package/dist/cli/runtime/invocation-options.js +1 -1
- package/dist/cli-bundle/bundle-manifest.json +1 -1
- package/dist/cli-bundle/chunks/{chunk-UYNMKUGA.js → chunk-2GW662DP.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-32BCU6HN.js → chunk-3OXQGGRD.js} +46 -46
- package/dist/cli-bundle/chunks/chunk-5BVZ32G7.js +2 -0
- package/dist/cli-bundle/chunks/chunk-5S6RT5LX.js +9 -0
- package/dist/cli-bundle/chunks/chunk-5Z6LWFDL.js +31 -0
- package/dist/cli-bundle/chunks/{chunk-MQJXZX3Q.js → chunk-6T2V3UIK.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-5JZMF4M5.js → chunk-7PGNAHNW.js} +2 -2
- package/dist/cli-bundle/chunks/chunk-7RCI7AAA.js +20 -0
- package/dist/cli-bundle/chunks/chunk-AQ5UHHQS.js +2 -0
- package/dist/cli-bundle/chunks/chunk-BK2XAQVC.js +2 -0
- package/dist/cli-bundle/chunks/{chunk-TZ3D3DPQ.js → chunk-BRIPGUAO.js} +2 -2
- package/dist/cli-bundle/chunks/chunk-BWAXA6S4.js +6 -0
- package/dist/cli-bundle/chunks/{chunk-5IPXDZKU.js → chunk-CERNIZLB.js} +66 -66
- package/dist/cli-bundle/chunks/chunk-DSCFJTHB.js +122 -0
- package/dist/cli-bundle/chunks/{chunk-JPKIUWLG.js → chunk-L4BKTPJ6.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-Y7H55PCY.js → chunk-NPEDFOVQ.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-2CBHQXYJ.js → chunk-ODMJ3HIB.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-DEJHTFEF.js → chunk-QN7VCOUU.js} +2 -2
- package/dist/cli-bundle/chunks/chunk-QQHMNUZT.js +2 -0
- package/dist/cli-bundle/chunks/chunk-SRDO2NJU.js +2 -0
- package/dist/cli-bundle/chunks/chunk-T56MB6SF.js +2 -0
- package/dist/cli-bundle/chunks/chunk-TX2W6G37.js +3 -0
- package/dist/cli-bundle/chunks/item-revision-UAZOBP2Q.js +2 -0
- package/dist/cli-bundle/chunks/item-transaction-N5F4LXYV.js +2 -0
- package/dist/cli-bundle/chunks/mutation-admission-QZQI2NN5.js +2 -0
- package/dist/cli-bundle/chunks/{register-list-query-I6AZA3IX.js → register-list-query-P3NOKQUA.js} +2 -2
- package/dist/cli-bundle/chunks/{register-mutation-3DAXCLXR.js → register-mutation-A6I5K5N6.js} +2 -2
- package/dist/cli-bundle/chunks/register-operations-SPKB3TNM.js +2 -0
- package/dist/cli-bundle/chunks/register-setup-ZZRHJ4IS.js +2 -0
- package/dist/cli-bundle/chunks/{stage-7H36OTDJ.js → stage-L3T725DS.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-5NGNO54M.js +44 -0
- package/dist/cli-bundle/focused-chunks/{chunk-ZQRCVRGS.js → chunk-A2246ZM7.js} +54 -54
- package/dist/cli-bundle/focused-chunks/chunk-B6U44WPW.js +2 -0
- package/dist/cli-bundle/focused-chunks/chunk-BAW3DGQO.js +91 -0
- package/dist/cli-bundle/focused-chunks/chunk-BHNY3KPJ.js +2 -0
- package/dist/cli-bundle/focused-chunks/chunk-C3O6JJPO.js +2 -0
- package/dist/cli-bundle/focused-chunks/{chunk-LKN2WGGB.js → chunk-C7UX2ZI3.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-YVLAP3BL.js → chunk-CBG67HUP.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-TYHDZUL4.js → chunk-CO2DNPH7.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-CXTDEQ3N.js +8 -0
- package/dist/cli-bundle/focused-chunks/chunk-FEPE2CE4.js +2 -0
- package/dist/cli-bundle/focused-chunks/{chunk-RY4QEMAE.js → chunk-FFO7RBLN.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-I5FODKYB.js → chunk-FXFDVM5O.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-I5LGPUWH.js +2 -0
- package/dist/cli-bundle/focused-chunks/chunk-J3YOATXR.js +80 -0
- package/dist/cli-bundle/focused-chunks/{chunk-HOXIIZEO.js → chunk-J6FDCAMH.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-J7M52SCZ.js +2 -0
- package/dist/cli-bundle/focused-chunks/chunk-JQS7TYQ6.js +12 -0
- package/dist/cli-bundle/focused-chunks/chunk-MEJKELWC.js +26 -0
- package/dist/cli-bundle/focused-chunks/chunk-PMKR4MJI.js +2 -0
- package/dist/cli-bundle/focused-chunks/chunk-PNPYQDHU.js +2 -0
- package/dist/cli-bundle/focused-chunks/chunk-Q4OIRSKL.js +2 -0
- package/dist/cli-bundle/focused-chunks/{chunk-F3X6UWMI.js → chunk-QEVJ3Y67.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-HJ27QLZV.js → chunk-SDLIUZEL.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-TFYTTDAK.js +2 -0
- package/dist/cli-bundle/focused-chunks/chunk-W3CSX6GI.js +3 -0
- package/dist/cli-bundle/focused-chunks/{chunk-D4XQOMG4.js → chunk-Y5GOUUYJ.js} +2 -2
- package/dist/cli-bundle/focused-chunks/item-revision-L6GVT42D.js +2 -0
- package/dist/cli-bundle/focused-chunks/item-transaction-GYGP264N.js +2 -0
- package/dist/cli-bundle/focused-chunks/mutation-admission-UWTH56OD.js +2 -0
- package/dist/cli-bundle/focused-chunks/{stage-4SQHVI3O.js → stage-WRSNJ7VI.js} +2 -2
- package/dist/cli-bundle/main.js +25 -25
- package/dist/cli-bundle/sdk-authoring.js +1 -1
- package/dist/cli-bundle/sdk-contracts.js +5 -5
- package/dist/cli-bundle/sdk-core.js +1 -1
- package/dist/cli-bundle/sdk-governance.js +1 -1
- package/dist/cli-bundle/sdk-graph.js +1 -1
- package/dist/cli-bundle/sdk-merge.js +57 -57
- package/dist/cli-bundle/sdk-query.js +1 -1
- package/dist/cli-bundle/sdk-runtime.js +1 -1
- package/dist/cli-bundle/sdk-testing.js +1 -1
- package/dist/cli-bundle/sdk.js +11 -11
- package/dist/core/config/positional-value.d.ts +1 -1
- package/dist/core/config/positional-value.js +1 -1
- package/dist/core/extensions/reserved-host-flags.js +1 -1
- package/dist/core/history/history-rewrite.js +1 -1
- package/dist/core/shared/command-types.d.ts +7 -0
- package/dist/core/store/item-mutation-precondition.d.ts +70 -6
- package/dist/core/store/item-mutation-precondition.js +1 -1
- package/dist/core/store/item-store.js +1 -1
- package/dist/core/store/settings-validator.js +1 -1
- package/dist/core/store/settings.js +1 -1
- package/dist/sdk/cli-contracts/bootstrap-command-scanner.js +1 -1
- package/dist/sdk/cli-contracts/completeness.js +1 -1
- package/dist/sdk/cli-contracts/enum-contracts.d.ts +11 -0
- package/dist/sdk/cli-contracts/enum-contracts.js +1 -1
- package/dist/sdk/cli-contracts/flag-lexicon-contracts.js +1 -1
- package/dist/sdk/cli-contracts/registration-helpers.js +1 -1
- package/dist/sdk/cli-contracts/tool-parameter-tables.js +1 -1
- package/dist/sdk/cli-contracts/tool-schema.js +1 -1
- package/dist/sdk/cli-program.js +1 -1
- package/dist/sdk/config.d.ts +1 -1
- package/dist/sdk/config.js +1 -1
- package/dist/sdk/environment/project-runtime-compatibility.js +1 -1
- package/dist/sdk/generated/generated-error-code-catalog-part-1.js +1 -1
- package/dist/sdk/generated/generated-error-code-catalog-part-2.js +1 -1
- package/dist/sdk/governance/validate.js +1 -1
- package/dist/sdk/history/salvage.js +3 -3
- package/dist/sdk/history-compact.d.ts +7 -1
- package/dist/sdk/history-compact.js +1 -1
- package/dist/sdk/history-repair.d.ts +2 -1
- package/dist/sdk/history-repair.js +1 -1
- package/dist/sdk/index.d.ts +3 -0
- package/dist/sdk/index.js +1 -1
- package/dist/sdk/lifecycle/checkpoint-rollback.d.ts +10 -2
- package/dist/sdk/lifecycle/checkpoint-rollback.js +1 -1
- package/dist/sdk/lifecycle/claim.js +1 -1
- package/dist/sdk/lifecycle/close-many.d.ts +11 -2
- package/dist/sdk/lifecycle/close-many.js +1 -1
- package/dist/sdk/lifecycle/close.d.ts +15 -0
- package/dist/sdk/lifecycle/close.js +1 -1
- package/dist/sdk/lifecycle/item-revision.d.ts +162 -0
- package/dist/sdk/lifecycle/item-revision.js +2 -0
- package/dist/sdk/lifecycle/restore.js +1 -1
- package/dist/sdk/lifecycle/start-blockers.d.ts +52 -0
- package/dist/sdk/lifecycle/start-blockers.js +2 -0
- package/dist/sdk/lifecycle/task-composition.d.ts +11 -0
- package/dist/sdk/lifecycle/task-composition.js +1 -1
- package/dist/sdk/lifecycle/update-many.d.ts +11 -2
- package/dist/sdk/lifecycle/update-many.js +1 -1
- package/dist/sdk/lifecycle/update.js +2 -2
- package/dist/sdk/public-surface.json +1 -1
- package/dist/sdk/query/declared-blockers.d.ts +16 -0
- package/dist/sdk/query/declared-blockers.js +2 -0
- package/dist/sdk/query/get.d.ts +7 -0
- package/dist/sdk/query/get.js +1 -1
- package/dist/sdk/query/projection-contracts.js +1 -1
- package/dist/sdk/runtime-input.d.ts +12 -0
- package/dist/sdk/runtime-input.js +1 -1
- package/dist/sdk/runtime.d.ts +10 -0
- package/dist/sdk/runtime.js +1 -1
- package/dist/sdk/structured-mutations.js +1 -1
- package/dist/sdk/test/batch.js +2 -2
- package/dist/sdk/test/execution.js +2 -2
- package/dist/sdk/test/linked-command-detection.js +1 -1
- package/dist/sdk/workspace-transaction.d.ts +17 -6
- package/dist/sdk/workspace-transaction.js +1 -1
- package/dist/types.d.ts +6 -0
- package/dist/types.js +1 -1
- package/docs/COMMANDS.md +91 -0
- package/docs/CONFIGURATION.md +1 -0
- package/docs/SDK.md +110 -1
- package/docs/agent-task-token-baseline.json +1 -1
- package/docs/generated/FLAG_LEXICON_BUDGETS.md +111 -111
- package/docs/generated/REFUSAL_CLOSURE_CENSUS.md +12 -3
- package/marketplace.json +1 -1
- package/package.json +1 -1
- package/packages/pm-beads/package.json +1 -1
- package/packages/pm-calendar/package.json +1 -1
- package/packages/pm-command-kit/package.json +1 -1
- package/packages/pm-digital-twin/package.json +1 -1
- package/packages/pm-governance-audit/package.json +1 -1
- package/packages/pm-guide-shell/package.json +1 -1
- package/packages/pm-kanban/package.json +1 -1
- package/packages/pm-lifecycle-hooks/package.json +1 -1
- package/packages/pm-linked-test-adapters/package.json +1 -1
- package/packages/pm-search-advanced/package.json +1 -1
- package/packages/pm-templates/package.json +1 -1
- package/packages/pm-todos/package.json +1 -1
- package/packages/pm-vcs/package.json +1 -1
- package/plugins/pm-claude/.claude-plugin/plugin.json +1 -1
- package/plugins/pm-claude/package.json +1 -1
- package/plugins/pm-codex/.codex-plugin/plugin.json +1 -1
- package/plugins/pm-codex/package.json +1 -1
- package/plugins/pm-codex/plugin.json +1 -1
- package/runtime-dependencies.json +1 -1
- package/dist/cli-bundle/chunks/chunk-7ZEFHFAU.js +0 -2
- package/dist/cli-bundle/chunks/chunk-B6UTLYLM.js +0 -3
- package/dist/cli-bundle/chunks/chunk-HBKD5HZB.js +0 -2
- package/dist/cli-bundle/chunks/chunk-L6LCLC37.js +0 -2
- package/dist/cli-bundle/chunks/chunk-NR4KABHC.js +0 -31
- package/dist/cli-bundle/chunks/chunk-PWD5RY2D.js +0 -2
- package/dist/cli-bundle/chunks/chunk-XRMYA4R7.js +0 -20
- package/dist/cli-bundle/chunks/chunk-YWS4XV4I.js +0 -6
- package/dist/cli-bundle/chunks/chunk-ZOAJNPBL.js +0 -129
- package/dist/cli-bundle/chunks/item-transaction-ZHLJTBD7.js +0 -2
- package/dist/cli-bundle/chunks/mutation-admission-ZQMRMGIB.js +0 -2
- package/dist/cli-bundle/chunks/register-operations-G3QGGQ5Z.js +0 -2
- package/dist/cli-bundle/chunks/register-setup-LDXHMNOT.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-3XGKBVJ3.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-4HY6EGPB.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-AK2CI2GI.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-HUXZCPWT.js +0 -26
- package/dist/cli-bundle/focused-chunks/chunk-IKXJ57XA.js +0 -8
- package/dist/cli-bundle/focused-chunks/chunk-MOY6KZGJ.js +0 -44
- package/dist/cli-bundle/focused-chunks/chunk-PWUTFKSI.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-PZRB7IXH.js +0 -91
- package/dist/cli-bundle/focused-chunks/chunk-WSYPVBKS.js +0 -80
- package/dist/cli-bundle/focused-chunks/chunk-WXLG7HZ5.js +0 -3
- package/dist/cli-bundle/focused-chunks/chunk-WZDFCVLX.js +0 -12
- package/dist/cli-bundle/focused-chunks/chunk-YGO3FWA2.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-ZYXQACPB.js +0 -2
- package/dist/cli-bundle/focused-chunks/item-transaction-PZ2FQ6B2.js +0 -2
- package/dist/cli-bundle/focused-chunks/mutation-admission-XOOK6XFH.js +0 -2
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
(function(){try{var e=typeof window<"u"?window:typeof global<"u"?global:typeof globalThis<"u"?globalThis:typeof self<"u"?self:{},n=new e.Error().stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="3445f237-469f-5713-bcd1-826fda836efd")}catch{}})();import{assertInitializedTracker}from"../environment/tracker-preflight.js";import{toItemRecord,isTerminalStatus,resolveItemTypeRegistry,resolveRuntimeStatusRegistry,EXIT_CODE,PmCliError,buildItemNotFoundError,listAllItemMetadataLight,locateItem,readLocatedItem,mutateItem,resolvePmRoot,readSettings,resolveAuthor,resolveIsoOrRelative,shouldCompletePlanOnClose,getActiveExtensionRegistrations}from"../runtime-primitives.js";import{collectBlockedByIds,indexBlockedByIds}from"../actionability.js";import{applyTerminalOrderingPolicy,requireTerminalReason}from"../lifecycle-policy.js";import{nowIso}from"../../core/shared/time.js";import{renderPmCommand,resolvePmCommandScopeArgs}from"../command-line.js";import{mutateItemWithHistoryContextResolver}from"../../core/store/item-store.js";const CLOSE_VALIDATION_FIELDS=[{key:"resolution",label:"resolution (--resolution)"},{key:"expected_result",label:"expected_result (--expected-result)"},{key:"actual_result",label:"actual_result (--actual-result)"}];function parseValidateCloseMode(raw){if(raw===void 0)return;const normalized=raw.trim().toLowerCase();if(normalized.length===0||normalized==="warn")return"warn";if(normalized==="off"||normalized==="none"||normalized==="disabled")return"off";if(normalized==="strict")return"strict";throw new PmCliError(`Invalid --validate-close mode "${raw}" (expected "off", "warn", or "strict")`,EXIT_CODE.USAGE)}function findMissingCloseValidationFields(itemMetadata){const missing=[];for(const field of CLOSE_VALIDATION_FIELDS){const rawValue=itemMetadata[field.key];(typeof rawValue!="string"||rawValue.trim().length===0)&&missing.push(field)}return missing}function closeEvidenceRecovery(metadata,commandScope,options={}){const missing=findMissingCloseValidationFields(metadata);if(missing.length===0)return;const values={resolution:options.resolution,expected_result:options.expectedResult,actual_result:options.actualResult},missingFlags=missing.map(field=>`--${field.key.replaceAll("_","-")}`),args=[...commandScope,"update",metadata.id,...missing.flatMap((field,index)=>[missingFlags[index],values[field.key]?.trim()||"<value>"])];return{missing:missingFlags,suggested_retry:renderPmCommand(args),suggested_retry_args:args}}async function duplicateChainReferencesClosingItem(loadItemById,initialDuplicateOf,closingId){const visited=new Set;let current=initialDuplicateOf;for(;typeof current=="string"&¤t.trim().length>0;){const currentId=current.trim();if(currentId===closingId)return!0;if(visited.has(currentId))return!1;visited.add(currentId),current=(await loadItemById(currentId))?.duplicate_of}return!1}function shouldApplyDuplicateFallback(value){return typeof value!="string"||value.trim().length===0}async function assertDuplicateTargetExists(pmRoot,settings,duplicateOf,closingId){const rawTarget=duplicateOf?.trim();if(!rawTarget)return;if(rawTarget===closingId)throw new PmCliError("An item cannot be closed as a duplicate of itself.",EXIT_CODE.USAGE,{code:"duplicate_target_self",why:"--duplicate-of must identify the canonical item that should remain open or already represent the work."});const typeRegistry=resolveItemTypeRegistry(settings,getActiveExtensionRegistrations()),itemCache=new Map,loadItemById=async id=>{if(itemCache.has(id))return itemCache.get(id)??null;const located=await locateItem(pmRoot,id,settings.id_prefix,settings.item_format,typeRegistry.type_to_folder);if(!located)return itemCache.set(id,null),null;const{document}=await readLocatedItem(located,{schema:settings.schema});return itemCache.set(id,document.metadata),document.metadata},target=await loadItemById(rawTarget);if(!target)throw new PmCliError(`Duplicate target "${rawTarget}" was not found. Create or locate the canonical item first.`,EXIT_CODE.USAGE,{code:"duplicate_target_missing",why:"Duplicate closure should point at a real canonical pm item so future dedupe and changelog tooling can trace the relationship.",examples:[`pm close ${closingId} "Duplicate of ${rawTarget}" --duplicate-of ${rawTarget}`],nextSteps:["Run pm search/list to find the canonical item, then retry with --duplicate-of <id>."]});if(await duplicateChainReferencesClosingItem(loadItemById,target.duplicate_of,closingId))throw new PmCliError(`Circular duplicate reference detected. Target "${rawTarget}" points back to "${closingId}".`,EXIT_CODE.USAGE,{code:"duplicate_target_circular",why:"Circular duplicate relationships create loops for dedupe and status propagation tooling.",nextSteps:["Choose the existing canonical item, or clear the target duplicate_of metadata before closing this item as a duplicate."]});if(typeof target.duplicate_of=="string"&&target.duplicate_of.trim().length>0)throw new PmCliError(`Duplicate target "${rawTarget}" is already marked as a duplicate of "${target.duplicate_of.trim()}".`,EXIT_CODE.USAGE,{code:"duplicate_target_is_duplicate",why:"Duplicate closure should point directly at the canonical item, not another duplicate.",examples:[`pm close ${closingId} "Duplicate of ${target.duplicate_of.trim()}" --duplicate-of ${target.duplicate_of.trim()}`],nextSteps:["Use the canonical item referenced by duplicate_of as the --duplicate-of target."]});return target.id}function findActiveChildIds(items,parentId,statusRegistry){return items.filter(item=>item.parent===parentId&&!isTerminalStatus(item.status,statusRegistry)).map(item=>item.id).sort((left,right)=>left.localeCompare(right))}function findAutoUnblockCandidates(items,closedId,statusRegistry){const itemsById=new Map(items.map(item=>[item.id.trim().toLowerCase(),item])),blockerIdsByItem=indexBlockedByIds(items),normalizedClosedId=closedId.trim().toLowerCase(),blockedStatuses=statusRegistry.blocked_statuses;return items.filter(item=>blockedStatuses.has(item.status)).map(item=>({item,blockerIds:blockerIdsByItem.get(item.id.trim().toLowerCase())})).filter(({blockerIds})=>blockerIds.includes(normalizedClosedId)).filter(({blockerIds})=>blockerIds.every(blockerId=>{if(blockerId===normalizedClosedId)return!0;const blocker=itemsById.get(blockerId);return blocker!==void 0&&isTerminalStatus(blocker.status,statusRegistry)})).map(({item,blockerIds})=>({id:item.id,blocker_ids:blockerIds})).sort((left,right)=>left.id.localeCompare(right.id))}async function autoUnblockResolvedDependents(pmRoot,settings,items,closedId,author,statusRegistry){const candidates=findAutoUnblockCandidates(items,closedId,statusRegistry),warnings=[];for(const candidate of candidates)try{const unblocked=await mutateItem({pmRoot,settings,id:candidate.id,op:"update",author,message:`Auto-unblocked after blocker ${closedId} closed`,mutate(document){const dependencies=document.metadata.dependencies??[],remainingDependencies=dependencies.filter(dependency=>{const dependencyId=dependency.id.trim().toLowerCase();return dependency.kind!=="blocked_by"||!candidate.blocker_ids.includes(dependencyId)});document.metadata.status=statusRegistry.open_status,delete document.metadata.blocked_by,delete document.metadata.blocked_reason,document.metadata.unblock_note=`Auto-unblocked after blocker ${closedId} closed; all blockers resolved (${candidate.blocker_ids.join(", ")}).`,remainingDependencies.length>0?document.metadata.dependencies=remainingDependencies:delete document.metadata.dependencies;const changedFields=["status","blocked_by","blocked_reason","unblock_note"];return remainingDependencies.length!==dependencies.length&&changedFields.push("dependencies"),{changedFields}}});warnings.push(`auto_unblocked:${unblocked.item.id}:resolved_blockers=${candidate.blocker_ids.join(",")}`)}catch(error){const reason=error instanceof Error?error.message.replace(/\s+/g," ").trim():"unknown";warnings.push(`auto_unblock_failed:${candidate.id}:${reason}`)}return warnings}function applyInlineCloseFields(metadata,options){const changedFields=[],inlineCloseFields=[{option:options.resolution,key:"resolution"},{option:options.expectedResult,key:"expected_result"},{option:options.actualResult,key:"actual_result"}];for(const{option,key}of inlineCloseFields){if(typeof option!="string")continue;const trimmed=option.trim();trimmed.length!==0&&(metadata[key]=trimmed,changedFields.push(key))}return changedFields}function applyDuplicateCloseMetadata(metadata,duplicateOf){if(duplicateOf===void 0)return[];metadata.duplicate_of=duplicateOf;const duplicateFallbackFields=[],fallbackValues=[{key:"resolution",value:`Duplicate of ${duplicateOf}`},{key:"expected_result",value:`Canonical item ${duplicateOf} tracks the work.`},{key:"actual_result",value:`Closed as duplicate of ${duplicateOf}.`}];for(const{key,value}of fallbackValues)shouldApplyDuplicateFallback(metadata[key])&&(metadata[key]=value,duplicateFallbackFields.push(key));return duplicateFallbackFields}function collectCloseValidationWarnings(metadata,validateCloseMode,activeChildIds,commandScope){const warnings=[];if(validateCloseMode!=="off"){const missingFields=findMissingCloseValidationFields(metadata);if(missingFields.length>0){const missingLabels=missingFields.map(field=>field.label);if(validateCloseMode==="strict"){const missingFlags=missingFields.map(field=>`--${field.key.replaceAll("_","-")}`),updateTokens=[...commandScope,"update",metadata.id,...missingFlags.flatMap(flag=>[flag,"<value>"])];throw new PmCliError(`Cannot close item ${metadata.id}: missing ${missingLabels.join(", ")}. Populate fields or use --validate-close warn.`,EXIT_CODE.USAGE,{code:"close_validation_missing_fields",recovery:{missing:missingFlags,suggested_retry:renderPmCommand(updateTokens),suggested_retry_args:updateTokens},nextSteps:["Run the suggested pm update, then retry the original pm close invocation.","Use --validate-close warn only when governance permits an evidenced exception."]})}warnings.push(`close_validation_missing_fields:${metadata.id}:${missingLabels.join(",")}`)}if(activeChildIds.length>0){if(validateCloseMode==="strict")throw new PmCliError(`Cannot close item ${metadata.id}: active child items remain open (${activeChildIds.join(", ")}). Close, cancel, or re-parent them first, or use --validate-close warn.`,EXIT_CODE.USAGE);warnings.push(`close_validation_active_children:${metadata.id}:${activeChildIds.join(",")}`)}return warnings}return activeChildIds.length>0&&warnings.push(`closed_with_active_children:${metadata.id}:${activeChildIds.join(",")}`),warnings}function applyCloseReason(metadata,closeReason){return closeReason!==void 0?(metadata.close_reason=closeReason,["close_reason"]):[]}function clearCloseAssignee(metadata){const cleared=["assignee","claim_principal"].filter(key=>metadata[key]!==void 0);for(const key of cleared)delete metadata[key];return[...cleared]}function mutateCloseMetadata(metadata,context){if(isTerminalStatus(metadata.status,context.statusRegistry)&&!context.force)throw new PmCliError(`Item ${metadata.id} is already terminal; use pm update to append acceptance evidence, or --force only for an intentional repeat closure.`,EXIT_CODE.CONFLICT,{recovery:closeEvidenceRecovery(metadata,context.commandScope,context.options)});const inlineChangedFields=applyInlineCloseFields(metadata,context.options),duplicateFallbackFields=applyDuplicateCloseMetadata(metadata,context.duplicateOf),mutationWarnings=collectCloseValidationWarnings(metadata,context.validateCloseMode,context.activeChildIds,context.commandScope);metadata.status=context.statusRegistry.close_status,metadata.closed_at=context.closedAt;const shouldWriteCompletedAt=context.options.completedAt!==void 0||metadata.completed_at===void 0;shouldWriteCompletedAt&&(metadata.completed_at=context.completedAt);const changedFields=["status","closed_at",...shouldWriteCompletedAt?["completed_at"]:[],...applyCloseReason(metadata,context.closeReason),...inlineChangedFields];if(shouldCompletePlanOnClose(metadata)&&(metadata.plan_mode="completed",changedFields.push("plan_mode")),context.duplicateOf!==void 0){changedFields.push("duplicate_of");for(const key of duplicateFallbackFields)changedFields.includes(key)||changedFields.push(key)}changedFields.push(...clearCloseAssignee(metadata));const blockerCleanup=applyTerminalOrderingPolicy(metadata,context.lifecyclePolicy);return changedFields.push(...blockerCleanup.changedFields),mutationWarnings.push(...blockerCleanup.warnings),{changedFields,...mutationWarnings.length>0?{warnings:mutationWarnings}:{}}}const _testOnlyCloseCommand={blockedByIds:collectBlockedByIds};async function closeItem(id,closeReasonText,options,global2){const pmRoot=resolvePmRoot(process.cwd(),global2.path),commandScope=resolvePmCommandScopeArgs(global2);await assertInitializedTracker(pmRoot);const settings=await readSettings(pmRoot),statusRegistry=resolveRuntimeStatusRegistry(settings.schema),author=resolveAuthor(options.author,settings.author_default),typeToFolder=resolveItemTypeRegistry(settings,getActiveExtensionRegistrations()).type_to_folder,located=await locateItem(pmRoot,id,settings.id_prefix,settings.item_format,typeToFolder);if(!located)throw await buildItemNotFoundError(pmRoot,id,settings.id_prefix,typeToFolder);const duplicateOf=await assertDuplicateTargetExists(pmRoot,settings,options.duplicateOf,located.id),lifecyclePolicy={requireCloseReason:options.lifecyclePolicy?.requireCloseReason??settings.governance.require_close_reason,orderingEdges:options.lifecyclePolicy?.orderingEdges??"preserve"},closeReason=requireTerminalReason({explicit:closeReasonText,duplicateOf,resolution:options.resolution,message:options.message},lifecyclePolicy.requireCloseReason).closeReason,validateCloseMode=parseValidateCloseMode(options.validateClose)??settings.governance.close_validation_default,trackerItems=await listAllItemMetadataLight(pmRoot,settings.item_format,typeToFolder,void 0,settings.schema),activeChildIds=findActiveChildIds(trackerItems,located.id,statusRegistry),closedAt=nowIso(),completedAt=options.completedAt===void 0?closedAt:resolveIsoOrRelative(options.completedAt,new Date(closedAt),"completed-at");let completion={timestamp:completedAt,source:options.completedAt===void 0?"close_time":"asserted",...options.completedAt===void 0?{hint:`completed_at defaults to the close time. If work finished earlier, correct it with ${renderPmCommand([...commandScope,"update",located.id,"--completed-at","<ISO>"])}.`}:{}};const result=await mutateItemWithHistoryContextResolver({pmRoot,settings,id:located.id,op:"close",author,message:options.message,force:options.force,resolveHistoryContext:()=>({completion:{timestamp:completion.timestamp,source:completion.source}}),mutate(document){return options.completedAt===void 0&&document.metadata.completed_at!==void 0&&(completion={timestamp:document.metadata.completed_at,source:"existing"}),mutateCloseMetadata(document.metadata,{pmRoot,commandScope,statusRegistry,force:options.force,options,duplicateOf,validateCloseMode,activeChildIds,closeReason,closedAt,completedAt,lifecyclePolicy})}}),evidence=validateCloseMode==="warn"?closeEvidenceRecovery(result.item,commandScope):void 0;return{item:toItemRecord(result.item),changed_fields:result.changedFields,...evidence?{follow_up:{kind:"complete_close_evidence",missing:evidence.missing,suggested_command:evidence.suggested_retry,suggested_command_args:evidence.suggested_retry_args}}:{},warnings:[...result.warnings,...await autoUnblockResolvedDependents(pmRoot,settings,trackerItems,located.id,author,statusRegistry)],completion}}function runClose(id,closeReasonText,options,global2){return closeItem(id,closeReasonText,options,global2)}export{_testOnlyCloseCommand,closeItem,runClose};
|
|
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]="0912c0d0-66d6-5e13-b663-91844378a79a")}catch{}})();import{assertInitializedTracker}from"../environment/tracker-preflight.js";import{isItemRevisionConflict,itemRevisionConflictRowFields}from"./item-revision.js";import{toErrorMessage}from"../runtime-primitives.js";import{toItemRecord,isTerminalStatus,resolveItemTypeRegistry,resolveRuntimeStatusRegistry,EXIT_CODE,PmCliError,buildItemNotFoundError,listAllItemMetadataLight,locateItem,readLocatedItem,mutateItem,resolvePmRoot,readSettings,resolveAuthor,resolveIsoOrRelative,shouldCompletePlanOnClose,getActiveExtensionRegistrations}from"../runtime-primitives.js";import{collectBlockedByIds,indexBlockedByIds}from"../actionability.js";import{applyTerminalOrderingPolicy,requireTerminalReason}from"../lifecycle-policy.js";import{nowIso}from"../../core/shared/time.js";import{renderPmCommand,resolvePmCommandScopeArgs}from"../command-line.js";import{mutateItemWithHistoryContextResolver}from"../../core/store/item-store.js";const CLOSE_VALIDATION_FIELDS=[{key:"resolution",label:"resolution (--resolution)"},{key:"expected_result",label:"expected_result (--expected-result)"},{key:"actual_result",label:"actual_result (--actual-result)"}];function parseValidateCloseMode(raw){if(raw===void 0)return;const normalized=raw.trim().toLowerCase();if(normalized.length===0||normalized==="warn")return"warn";if(normalized==="off"||normalized==="none"||normalized==="disabled")return"off";if(normalized==="strict")return"strict";throw new PmCliError(`Invalid --validate-close mode "${raw}" (expected "off", "warn", or "strict")`,EXIT_CODE.USAGE)}function findMissingCloseValidationFields(itemMetadata){const missing=[];for(const field of CLOSE_VALIDATION_FIELDS){const rawValue=itemMetadata[field.key];(typeof rawValue!="string"||rawValue.trim().length===0)&&missing.push(field)}return missing}function closeEvidenceRecovery(metadata,commandScope,options={}){const missing=findMissingCloseValidationFields(metadata);if(missing.length===0)return;const values={resolution:options.resolution,expected_result:options.expectedResult,actual_result:options.actualResult},missingFlags=missing.map(field=>`--${field.key.replaceAll("_","-")}`),args=[...commandScope,"update",metadata.id,...missing.flatMap((field,index)=>[missingFlags[index],values[field.key]?.trim()||"<value>"])];return{missing:missingFlags,suggested_retry:renderPmCommand(args),suggested_retry_args:args}}async function duplicateChainReferencesClosingItem(loadItemById,initialDuplicateOf,closingId){const visited=new Set;let current=initialDuplicateOf;for(;typeof current=="string"&¤t.trim().length>0;){const currentId=current.trim();if(currentId===closingId)return!0;if(visited.has(currentId))return!1;visited.add(currentId),current=(await loadItemById(currentId))?.duplicate_of}return!1}function shouldApplyDuplicateFallback(value){return typeof value!="string"||value.trim().length===0}async function assertDuplicateTargetExists(pmRoot,settings,duplicateOf,closingId){const rawTarget=duplicateOf?.trim();if(!rawTarget)return;if(rawTarget===closingId)throw new PmCliError("An item cannot be closed as a duplicate of itself.",EXIT_CODE.USAGE,{code:"duplicate_target_self",why:"--duplicate-of must identify the canonical item that should remain open or already represent the work."});const typeRegistry=resolveItemTypeRegistry(settings,getActiveExtensionRegistrations()),itemCache=new Map,loadItemById=async id=>{if(itemCache.has(id))return itemCache.get(id)??null;const located=await locateItem(pmRoot,id,settings.id_prefix,settings.item_format,typeRegistry.type_to_folder);if(!located)return itemCache.set(id,null),null;const{document}=await readLocatedItem(located,{schema:settings.schema});return itemCache.set(id,document.metadata),document.metadata},target=await loadItemById(rawTarget);if(!target)throw new PmCliError(`Duplicate target "${rawTarget}" was not found. Create or locate the canonical item first.`,EXIT_CODE.USAGE,{code:"duplicate_target_missing",why:"Duplicate closure should point at a real canonical pm item so future dedupe and changelog tooling can trace the relationship.",examples:[`pm close ${closingId} "Duplicate of ${rawTarget}" --duplicate-of ${rawTarget}`],nextSteps:["Run pm search/list to find the canonical item, then retry with --duplicate-of <id>."]});if(await duplicateChainReferencesClosingItem(loadItemById,target.duplicate_of,closingId))throw new PmCliError(`Circular duplicate reference detected. Target "${rawTarget}" points back to "${closingId}".`,EXIT_CODE.USAGE,{code:"duplicate_target_circular",why:"Circular duplicate relationships create loops for dedupe and status propagation tooling.",nextSteps:["Choose the existing canonical item, or clear the target duplicate_of metadata before closing this item as a duplicate."]});if(typeof target.duplicate_of=="string"&&target.duplicate_of.trim().length>0)throw new PmCliError(`Duplicate target "${rawTarget}" is already marked as a duplicate of "${target.duplicate_of.trim()}".`,EXIT_CODE.USAGE,{code:"duplicate_target_is_duplicate",why:"Duplicate closure should point directly at the canonical item, not another duplicate.",examples:[`pm close ${closingId} "Duplicate of ${target.duplicate_of.trim()}" --duplicate-of ${target.duplicate_of.trim()}`],nextSteps:["Use the canonical item referenced by duplicate_of as the --duplicate-of target."]});return target.id}function findActiveChildIds(items,parentId,statusRegistry){return items.filter(item=>item.parent===parentId&&!isTerminalStatus(item.status,statusRegistry)).map(item=>item.id).sort((left,right)=>left.localeCompare(right))}function findAutoUnblockCandidates(items,closedId,statusRegistry){const itemsById=new Map(items.map(item=>[item.id.trim().toLowerCase(),item])),blockerIdsByItem=indexBlockedByIds(items),normalizedClosedId=closedId.trim().toLowerCase(),blockedStatuses=statusRegistry.blocked_statuses;return items.filter(item=>blockedStatuses.has(item.status)).map(item=>({item,blockerIds:blockerIdsByItem.get(item.id.trim().toLowerCase())})).filter(({blockerIds})=>blockerIds.includes(normalizedClosedId)).filter(({blockerIds})=>blockerIds.every(blockerId=>{if(blockerId===normalizedClosedId)return!0;const blocker=itemsById.get(blockerId);return blocker!==void 0&&isTerminalStatus(blocker.status,statusRegistry)})).map(({item,blockerIds})=>({id:item.id,blocker_ids:blockerIds})).sort((left,right)=>left.id.localeCompare(right.id))}async function autoUnblockResolvedDependents(pmRoot,settings,items,closedId,author,statusRegistry){const candidates=findAutoUnblockCandidates(items,closedId,statusRegistry),warnings=[],unblockedIds=[];let conflict;for(const candidate of candidates)try{const unblocked=await mutateItem({pmRoot,settings,id:candidate.id,op:"update",author,message:`Auto-unblocked after blocker ${closedId} closed`,mutate(document){const dependencies=document.metadata.dependencies??[],remainingDependencies=dependencies.filter(dependency=>{const dependencyId=dependency.id.trim().toLowerCase();return dependency.kind!=="blocked_by"||!candidate.blocker_ids.includes(dependencyId)});document.metadata.status=statusRegistry.open_status,delete document.metadata.blocked_by,delete document.metadata.blocked_reason,document.metadata.unblock_note=`Auto-unblocked after blocker ${closedId} closed; all blockers resolved (${candidate.blocker_ids.join(", ")}).`,remainingDependencies.length>0?document.metadata.dependencies=remainingDependencies:delete document.metadata.dependencies;const changedFields=["status","blocked_by","blocked_reason","unblock_note"];return remainingDependencies.length!==dependencies.length&&changedFields.push("dependencies"),{changedFields}}});warnings.push(`auto_unblocked:${unblocked.item.id}:resolved_blockers=${candidate.blocker_ids.join(",")}`),unblockedIds.push(unblocked.item.id)}catch(error){conflict===void 0&&isItemRevisionConflict(error)&&(conflict=error);const reason=error instanceof Error?error.message.replace(/\s+/g," ").trim():"unknown";warnings.push(`auto_unblock_failed:${candidate.id}:${reason}`)}if(conflict!==void 0)throw new PmCliError(`${closedId} was closed, but auto-unblocking ${String(conflict.context.item_id)} was refused. ${conflict.message}`,conflict.exitCode,{...conflict.context,side_effects:[{path:`history/${closedId}.jsonl`,op:"close"},...unblockedIds.map(id=>({path:`history/${id}.jsonl`,op:"update"}))]});return warnings}async function committedCloseDependentConflict(error,id,pmRoot){if(!isItemRevisionConflict(error)||!error.context.side_effects?.some(effect=>`${effect.op}:${effect.path}`==`close:history/${id}.jsonl`))return;const dependent=String(error.context.item_id);return{warnings:[...error.context.side_effects.filter(effect=>effect.op==="update").map(effect=>effect.path.slice(8,-6)).map(unblockedId=>`auto_unblocked:${unblockedId}`),`auto_unblock_refused:${dependent}:item_revision_conflict`],dependent_revision_conflict:{item_id:dependent,error:toErrorMessage(error),...await itemRevisionConflictRowFields(error,pmRoot)}}}function applyInlineCloseFields(metadata,options){const changedFields=[],inlineCloseFields=[{option:options.resolution,key:"resolution"},{option:options.expectedResult,key:"expected_result"},{option:options.actualResult,key:"actual_result"}];for(const{option,key}of inlineCloseFields){if(typeof option!="string")continue;const trimmed=option.trim();trimmed.length!==0&&(metadata[key]=trimmed,changedFields.push(key))}return changedFields}function applyDuplicateCloseMetadata(metadata,duplicateOf){if(duplicateOf===void 0)return[];metadata.duplicate_of=duplicateOf;const duplicateFallbackFields=[],fallbackValues=[{key:"resolution",value:`Duplicate of ${duplicateOf}`},{key:"expected_result",value:`Canonical item ${duplicateOf} tracks the work.`},{key:"actual_result",value:`Closed as duplicate of ${duplicateOf}.`}];for(const{key,value}of fallbackValues)shouldApplyDuplicateFallback(metadata[key])&&(metadata[key]=value,duplicateFallbackFields.push(key));return duplicateFallbackFields}function collectCloseValidationWarnings(metadata,validateCloseMode,activeChildIds,commandScope){const warnings=[];if(validateCloseMode!=="off"){const missingFields=findMissingCloseValidationFields(metadata);if(missingFields.length>0){const missingLabels=missingFields.map(field=>field.label);if(validateCloseMode==="strict"){const missingFlags=missingFields.map(field=>`--${field.key.replaceAll("_","-")}`),updateTokens=[...commandScope,"update",metadata.id,...missingFlags.flatMap(flag=>[flag,"<value>"])];throw new PmCliError(`Cannot close item ${metadata.id}: missing ${missingLabels.join(", ")}. Populate fields or use --validate-close warn.`,EXIT_CODE.USAGE,{code:"close_validation_missing_fields",recovery:{missing:missingFlags,suggested_retry:renderPmCommand(updateTokens),suggested_retry_args:updateTokens},nextSteps:["Run the suggested pm update, then retry the original pm close invocation.","Use --validate-close warn only when governance permits an evidenced exception."]})}warnings.push(`close_validation_missing_fields:${metadata.id}:${missingLabels.join(",")}`)}if(activeChildIds.length>0){if(validateCloseMode==="strict")throw new PmCliError(`Cannot close item ${metadata.id}: active child items remain open (${activeChildIds.join(", ")}). Close, cancel, or re-parent them first, or use --validate-close warn.`,EXIT_CODE.USAGE);warnings.push(`close_validation_active_children:${metadata.id}:${activeChildIds.join(",")}`)}return warnings}return activeChildIds.length>0&&warnings.push(`closed_with_active_children:${metadata.id}:${activeChildIds.join(",")}`),warnings}function applyCloseReason(metadata,closeReason){return closeReason!==void 0?(metadata.close_reason=closeReason,["close_reason"]):[]}function clearCloseAssignee(metadata){const cleared=["assignee","claim_principal"].filter(key=>metadata[key]!==void 0);for(const key of cleared)delete metadata[key];return[...cleared]}function mutateCloseMetadata(metadata,context){if(isTerminalStatus(metadata.status,context.statusRegistry)&&!context.force)throw new PmCliError(`Item ${metadata.id} is already terminal; use pm update to append acceptance evidence, or --force only for an intentional repeat closure.`,EXIT_CODE.CONFLICT,{recovery:closeEvidenceRecovery(metadata,context.commandScope,context.options)});const inlineChangedFields=applyInlineCloseFields(metadata,context.options),duplicateFallbackFields=applyDuplicateCloseMetadata(metadata,context.duplicateOf),mutationWarnings=collectCloseValidationWarnings(metadata,context.validateCloseMode,context.activeChildIds,context.commandScope);metadata.status=context.statusRegistry.close_status,metadata.closed_at=context.closedAt;const shouldWriteCompletedAt=context.options.completedAt!==void 0||metadata.completed_at===void 0;shouldWriteCompletedAt&&(metadata.completed_at=context.completedAt);const changedFields=["status","closed_at",...shouldWriteCompletedAt?["completed_at"]:[],...applyCloseReason(metadata,context.closeReason),...inlineChangedFields];if(shouldCompletePlanOnClose(metadata)&&(metadata.plan_mode="completed",changedFields.push("plan_mode")),context.duplicateOf!==void 0){changedFields.push("duplicate_of");for(const key of duplicateFallbackFields)changedFields.includes(key)||changedFields.push(key)}changedFields.push(...clearCloseAssignee(metadata));const blockerCleanup=applyTerminalOrderingPolicy(metadata,context.lifecyclePolicy);return changedFields.push(...blockerCleanup.changedFields),mutationWarnings.push(...blockerCleanup.warnings),{changedFields,...mutationWarnings.length>0?{warnings:mutationWarnings}:{}}}const _testOnlyCloseCommand={blockedByIds:collectBlockedByIds};async function closeItem(id,closeReasonText,options,global2){const pmRoot=resolvePmRoot(process.cwd(),global2.path),commandScope=resolvePmCommandScopeArgs(global2);await assertInitializedTracker(pmRoot);const settings=await readSettings(pmRoot),statusRegistry=resolveRuntimeStatusRegistry(settings.schema),author=resolveAuthor(options.author,settings.author_default),typeToFolder=resolveItemTypeRegistry(settings,getActiveExtensionRegistrations()).type_to_folder,located=await locateItem(pmRoot,id,settings.id_prefix,settings.item_format,typeToFolder);if(!located)throw await buildItemNotFoundError(pmRoot,id,settings.id_prefix,typeToFolder);const duplicateOf=await assertDuplicateTargetExists(pmRoot,settings,options.duplicateOf,located.id),lifecyclePolicy={requireCloseReason:options.lifecyclePolicy?.requireCloseReason??settings.governance.require_close_reason,orderingEdges:options.lifecyclePolicy?.orderingEdges??"preserve"},closeReason=requireTerminalReason({explicit:closeReasonText,duplicateOf,resolution:options.resolution,message:options.message},lifecyclePolicy.requireCloseReason).closeReason,validateCloseMode=parseValidateCloseMode(options.validateClose)??settings.governance.close_validation_default,trackerItems=await listAllItemMetadataLight(pmRoot,settings.item_format,typeToFolder,void 0,settings.schema),activeChildIds=findActiveChildIds(trackerItems,located.id,statusRegistry),closedAt=nowIso(),completedAt=options.completedAt===void 0?closedAt:resolveIsoOrRelative(options.completedAt,new Date(closedAt),"completed-at");let completion={timestamp:completedAt,source:options.completedAt===void 0?"close_time":"asserted",...options.completedAt===void 0?{hint:`completed_at defaults to the close time. If work finished earlier, correct it with ${renderPmCommand([...commandScope,"update",located.id,"--completed-at","<ISO>"])}.`}:{}};const result=await mutateItemWithHistoryContextResolver({pmRoot,settings,id:located.id,op:"close",author,message:options.message,force:options.force,resolveHistoryContext:()=>({completion:{timestamp:completion.timestamp,source:completion.source}}),mutate(document){return options.completedAt===void 0&&document.metadata.completed_at!==void 0&&(completion={timestamp:document.metadata.completed_at,source:"existing"}),mutateCloseMetadata(document.metadata,{pmRoot,commandScope,statusRegistry,force:options.force,options,duplicateOf,validateCloseMode,activeChildIds,closeReason,closedAt,completedAt,lifecyclePolicy})}}),evidence=validateCloseMode==="warn"?closeEvidenceRecovery(result.item,commandScope):void 0;return{item:toItemRecord(result.item),changed_fields:result.changedFields,...evidence?{follow_up:{kind:"complete_close_evidence",missing:evidence.missing,suggested_command:evidence.suggested_retry,suggested_command_args:evidence.suggested_retry_args}}:{},warnings:[...result.warnings,...await autoUnblockResolvedDependents(pmRoot,settings,trackerItems,located.id,author,statusRegistry)],completion}}function runClose(id,closeReasonText,options,global2){return closeItem(id,closeReasonText,options,global2)}export{_testOnlyCloseCommand,closeItem,committedCloseDependentConflict,runClose};
|
|
2
2
|
//# sourceMappingURL=close.js.map
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { bindItemRevisionScope,createItemRevisionScope,itemRevisionScopeReceipt,runWithItemRevisionScope,type ItemRevisionScope } from "../../core/store/item-mutation-precondition.js";
|
|
2
|
+
import { PmCliError,type PmCliErrorDetails } from "../../core/shared/errors.js";
|
|
3
|
+
export type { ItemRevisionScope } from "../../core/store/item-mutation-precondition.js";
|
|
4
|
+
/** Refusal code for a locked write that found a revision other than the declared one. */
|
|
5
|
+
export declare const ITEM_REVISION_CONFLICT_CODE = "item_revision_conflict";
|
|
6
|
+
/** Most recent history entries a conflict refusal describes. */
|
|
7
|
+
export declare const ITEM_REVISION_CONFLICT_CHANGE_LIMIT = 10;
|
|
8
|
+
/** One item revision a caller decided on. */
|
|
9
|
+
export interface ExpectedItemRevision {
|
|
10
|
+
/** Item id; unprefixed ids are normalized with the workspace id prefix. */
|
|
11
|
+
id:string;
|
|
12
|
+
/** The `item_hash` returned by `pm get` or {@link readItemRevision}. */
|
|
13
|
+
itemHash:string;
|
|
14
|
+
}
|
|
15
|
+
/** Current revision of one item. */
|
|
16
|
+
export interface ItemRevision {
|
|
17
|
+
/** Canonical item id. */
|
|
18
|
+
id:string;
|
|
19
|
+
/** SHA-256 of the canonical item document, equal to its latest history `after_hash`. */
|
|
20
|
+
item_hash:string;
|
|
21
|
+
}
|
|
22
|
+
/** Tracker selection for revision reads and guarded operations. */
|
|
23
|
+
export interface ItemRevisionContext {
|
|
24
|
+
/** Tracker root (the SDK equivalent of `--pm-path`). */
|
|
25
|
+
pmRoot?:string;
|
|
26
|
+
/** Working directory used when `pmRoot` is omitted. */
|
|
27
|
+
cwd?:string;
|
|
28
|
+
}
|
|
29
|
+
/** Result of an operation run under expected revisions. */
|
|
30
|
+
export interface ExpectedItemRevisionsOutcome<T> {
|
|
31
|
+
/** Value returned by the guarded operation. */
|
|
32
|
+
result:T;
|
|
33
|
+
/** Canonical ids whose expectation a locked write evaluated. */
|
|
34
|
+
checked:string[];
|
|
35
|
+
/** Canonical ids the operation never wrote, so their expectation was never evaluated. */
|
|
36
|
+
unchecked:string[];
|
|
37
|
+
}
|
|
38
|
+
/** One parsed `--expect-hash` / `expect_hash` token before id resolution. */
|
|
39
|
+
export interface ExpectHashSpec {
|
|
40
|
+
/** Explicit target id, or undefined when the token binds to the command's item. */
|
|
41
|
+
id?:string;
|
|
42
|
+
/** Normalized lowercase revision. */
|
|
43
|
+
itemHash:string;
|
|
44
|
+
}
|
|
45
|
+
/** One history entry summarized for a revision conflict. */
|
|
46
|
+
export interface ItemRevisionChange {
|
|
47
|
+
/** Entry timestamp. */
|
|
48
|
+
ts:string;
|
|
49
|
+
/** History operation. */
|
|
50
|
+
op:string;
|
|
51
|
+
/** Recorded author. */
|
|
52
|
+
author:string;
|
|
53
|
+
/** Top-level metadata fields (or `body`) the entry changed. */
|
|
54
|
+
fields:string[];
|
|
55
|
+
}
|
|
56
|
+
/** Normalize one declared revision, refusing anything that cannot be an item_hash. */
|
|
57
|
+
export declare function normalizeExpectedItemHash(value:unknown):string;
|
|
58
|
+
/**
|
|
59
|
+
* Parse expected-revision tokens. A bare hash binds to the command's item; the
|
|
60
|
+
* `<id>=<hash>` form names its target explicitly, which bulk commands require.
|
|
61
|
+
*/
|
|
62
|
+
export declare function parseExpectHashSpecs(values:string | readonly string[] | undefined):ExpectHashSpec[];
|
|
63
|
+
/** Read the current revision of one item (the tombstone revision for a deleted item) without writing anything. */
|
|
64
|
+
export declare function readItemRevision(id:string,context?:ItemRevisionContext):Promise<ItemRevision>;
|
|
65
|
+
/**
|
|
66
|
+
* Resolve parsed tokens to canonical ids. A bare token binds to `defaultId`;
|
|
67
|
+
* without one the caller must name the target explicitly.
|
|
68
|
+
*/
|
|
69
|
+
export declare function resolveExpectedItemRevisions(pmRoot:string,specs:readonly ExpectHashSpec[],defaultId?:string):Promise<ExpectedItemRevision[]>;
|
|
70
|
+
/**
|
|
71
|
+
* Describe what changed after the declared revision, from the item's immutable
|
|
72
|
+
* history. Returns undefined when the history cannot be read.
|
|
73
|
+
*/
|
|
74
|
+
export declare function describeItemRevisionChanges(pmRoot:string,id:string,expectedHash:string):Promise<{
|
|
75
|
+
expected_revision_recorded:boolean;
|
|
76
|
+
changes_since:ItemRevisionChange[];
|
|
77
|
+
changes_since_omitted:number;
|
|
78
|
+
} | undefined>;
|
|
79
|
+
/** Structured revision fields a bulk row keeps when its item refused an expected revision. */
|
|
80
|
+
export interface ItemRevisionConflictRowFields {
|
|
81
|
+
/** Always `item_revision_conflict`. */
|
|
82
|
+
error_code?:typeof ITEM_REVISION_CONFLICT_CODE;
|
|
83
|
+
/** Revision the row's locked write expected. */
|
|
84
|
+
expected_revision?:string;
|
|
85
|
+
/** Revision the row's locked write found. */
|
|
86
|
+
current_revision?:string;
|
|
87
|
+
/** Bounded history evidence of what changed since the expected revision, as single-item refusals report it. */
|
|
88
|
+
details?:PmCliErrorDetails;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Keep the typed refusal on a bulk row: partial-success commands report a
|
|
92
|
+
* stale row's code, both revisions and the same bounded change evidence a
|
|
93
|
+
* single-item refusal carries, not only its message.
|
|
94
|
+
*/
|
|
95
|
+
export declare function itemRevisionConflictRowFields(error:unknown,pmRoot:string):Promise<ItemRevisionConflictRowFields>;
|
|
96
|
+
/**
|
|
97
|
+
* Whether an error is the locked-write expected-revision refusal. Structural, not
|
|
98
|
+
* `instanceof`: a refusal raised through another published SDK entrypoint's bundle
|
|
99
|
+
* is a different PmCliError class with the same shape.
|
|
100
|
+
*/
|
|
101
|
+
export declare function isItemRevisionConflict(error:unknown):error is PmCliError;
|
|
102
|
+
/**
|
|
103
|
+
* Raise the first expected-revision refusal among errors collected by a
|
|
104
|
+
* best-effort batch (validate auto-fix), once every member was attempted.
|
|
105
|
+
*/
|
|
106
|
+
export declare function throwFirstItemRevisionConflict(errors:readonly unknown[]):void;
|
|
107
|
+
/** Refusal for a guard on an invocation an extension parser or preflight override rewrote. */
|
|
108
|
+
export declare function expectHashInvocationRewrittenError():PmCliError;
|
|
109
|
+
/**
|
|
110
|
+
* Attach bounded change evidence to a revision conflict. The evidence is read
|
|
111
|
+
* after the lock is released, so it describes the record as it was then; the
|
|
112
|
+
* refusal's `current_revision` remains the revision the locked write found.
|
|
113
|
+
*/
|
|
114
|
+
export declare function enrichItemRevisionConflict(error:unknown,pmRoot:string):Promise<unknown>;
|
|
115
|
+
/**
|
|
116
|
+
* Run an operation under an already-bound scope and return its receipt,
|
|
117
|
+
* enriching a revision refusal with bounded change evidence.
|
|
118
|
+
*/
|
|
119
|
+
export declare function runWithBoundItemRevisionScope<T>(scope:ItemRevisionScope,operation:() => Promise<T>):Promise<ExpectedItemRevisionsOutcome<T>>;
|
|
120
|
+
/**
|
|
121
|
+
* Run `operation` so every locked write to an expected item first proves the
|
|
122
|
+
* item is still at the declared revision. A mismatch refuses that write with
|
|
123
|
+
* `item_revision_conflict` (exit 4) and leaves the item unchanged. After the
|
|
124
|
+
* operation's own committed write, the expectation advances to that revision,
|
|
125
|
+
* so a multi-write operation does not refuse itself. The receipt lists items
|
|
126
|
+
* the operation never wrote, whose expectation therefore proved nothing.
|
|
127
|
+
*/
|
|
128
|
+
export declare function withExpectedItemRevisions<T>(expectations:readonly ExpectedItemRevision[],operation:() => Promise<T>,context?:ItemRevisionContext):Promise<ExpectedItemRevisionsOutcome<T>>;
|
|
129
|
+
/**
|
|
130
|
+
* Resolve expectations to canonical ids and bind them to a fresh scope, without
|
|
131
|
+
* running anything. Hosts that must resolve targets inside one extension
|
|
132
|
+
* context but run the guarded operation outside it bind first, then call
|
|
133
|
+
* {@link runWithBoundItemRevisionScope}.
|
|
134
|
+
*/
|
|
135
|
+
export declare function bindExpectedItemRevisions(expectations:readonly ExpectedItemRevision[],context?:ItemRevisionContext):Promise<ItemRevisionScope>;
|
|
136
|
+
export { bindItemRevisionScope,createItemRevisionScope,itemRevisionScopeReceipt,runWithItemRevisionScope };
|
|
137
|
+
/** Warning recorded when an expectation's item was never written by the action. */
|
|
138
|
+
export declare function itemRevisionUncheckedWarning(id:string):string;
|
|
139
|
+
/**
|
|
140
|
+
* Refuse an expected revision on a native action that never writes items.
|
|
141
|
+
* Extension and package actions stay admitted because only their handlers
|
|
142
|
+
* know whether they write; the receipt reports any expectation they never reach.
|
|
143
|
+
*/
|
|
144
|
+
export declare function assertExpectHashAdmissible(path:string,options?:Record<string,unknown>):void;
|
|
145
|
+
/** Refusal for a revision guard on a command or action that writes no items. */
|
|
146
|
+
export declare function expectHashRequiresMutationError(action:string):PmCliError;
|
|
147
|
+
/**
|
|
148
|
+
* Generic-dispatch entry for the `expectHash` action parameter used by MCP and
|
|
149
|
+
* `PmClient.run`. Tokens follow the CLI grammar: a bare hash binds to the
|
|
150
|
+
* action's `id`, and `<id>=<hash>` names the target explicitly.
|
|
151
|
+
*/
|
|
152
|
+
export declare function dispatchWithExpectedItemRevisions<T>(params:{
|
|
153
|
+
action:string;
|
|
154
|
+
expectHash:unknown;
|
|
155
|
+
defaultId?:string;
|
|
156
|
+
pmRoot:string;
|
|
157
|
+
run:() => Promise<T>;
|
|
158
|
+
/** Nested action options, consulted for modes the guard cannot cover (background test workers). */
|
|
159
|
+
options?:Record<string,unknown>;
|
|
160
|
+
/** Top-level background flag, consulted like `options.background`. */
|
|
161
|
+
background?:unknown;
|
|
162
|
+
}):Promise<T>;
|
|
@@ -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]="aca1dfc0-e0c9-5554-96e3-bb9b2cdeb7f9")}catch{}})();import{getActiveExtensionRegistrations}from"../../core/extensions/index.js";import{hashDocument}from"../../core/history/history.js";import{readHistoryEntries}from"../../core/history/read.js";import{resolveItemTypeRegistry}from"../../core/item/type-registry.js";import{bindItemRevisionScope,createItemRevisionScope,itemRevisionScopeReceipt,runWithItemRevisionScope}from"../../core/store/item-mutation-precondition.js";import{buildItemNotFoundError,locateItem,readLocatedItem}from"../../core/store/item-store.js";import{getHistoryPath,resolvePmRoot}from"../../core/store/paths.js";import{readSettings}from"../../core/store/settings.js";import{EXIT_CODE}from"../../core/shared/constants.js";import{PmCliError}from"../../core/shared/errors.js";import{PM_EXPECT_HASH_ACTIONS,PM_TOOL_ACTIONS}from"../cli-contracts/enum-contracts.js";import{resolveHistorySubject}from"../history/subject.js";import{resolvePhysicalItemId}from"../query/declared-blockers.js";import{EMPTY_CANONICAL_DOCUMENT}from"../../core/shared/constants.js";import{isMutationAction}from"../mutation-guard.js";import{isPmCliExpectedError}from"../errors.js";const ITEM_REVISION_CONFLICT_CODE="item_revision_conflict",ITEM_REVISION_CONFLICT_CHANGE_LIMIT=10,ITEM_HASH_PATTERN=/^[0-9a-f]{64}$/u;function invalidExpectHash(value,reason){return new PmCliError(`Invalid expected revision ${JSON.stringify(value)}: ${reason}.`,EXIT_CODE.USAGE,{code:"expect_hash_invalid",value,required:"Pass the 64-character item_hash from pm get <id> --json, optionally as <id>=<item_hash>.",examples:["--expect-hash <item_hash>","--expect-hash pm-a1b2=<item_hash>"]})}function normalizeExpectedItemHash(value){const normalized=typeof value=="string"?value.trim().toLowerCase():"";if(!ITEM_HASH_PATTERN.test(normalized))throw invalidExpectHash(String(value),"the revision is not a 64-character hexadecimal item_hash");return normalized}function parseExpectHashSpecs(values){return(values===void 0?[]:typeof values=="string"?[values]:values).flatMap(raw=>{if(typeof raw!="string")throw invalidExpectHash(String(raw),"expected a string");const parts=raw.split(",").map(token=>token.trim()).filter(token=>token.length>0);if(parts.length===0)throw invalidExpectHash(raw,"the value is empty");return parts.map(token=>{const separator=token.lastIndexOf("="),id=separator===-1?void 0:token.slice(0,separator).trim();if(id!==void 0&&id.length===0)throw invalidExpectHash(token,"the item id before = is empty");const itemHash=normalizeExpectedItemHash(separator===-1?token:token.slice(separator+1));return id===void 0?{itemHash}:{id,itemHash}})})}async function locateRevisionTarget(pmRoot,id){const settings=await readSettings(pmRoot),typeToFolder=resolveItemTypeRegistry(settings,getActiveExtensionRegistrations()).type_to_folder,located=await locateItem(pmRoot,id,settings.id_prefix,settings.item_format,typeToFolder);if(located){const{document}=await readLocatedItem(located,{schema:settings.schema});if(document.metadata.id!==await resolvePhysicalItemId(located.itemPath,located.id))throw new PmCliError(`Item file ${located.id} embeds the id ${document.metadata.id}; no revision can be bound to an item whose identity is in conflict.`,EXIT_CODE.CONFLICT,{code:"item_identity_conflict",item_id:located.id,required:`Repair ${located.id}'s identity first (pm validate reports it; pm restore ${located.id} <version> recovers a recorded state), then read its item_hash again.`});return{id:document.metadata.id,document}}try{return{id:(await resolveHistorySubject(pmRoot,id,settings,typeToFolder)).id,document:EMPTY_CANONICAL_DOCUMENT}}catch{throw await buildItemNotFoundError(pmRoot,id,settings.id_prefix,typeToFolder)}}function resolveRevisionRoot(context){return resolvePmRoot(context.cwd??process.cwd(),context.pmRoot)}async function readItemRevision(id,context={}){const target=await locateRevisionTarget(resolveRevisionRoot(context),id);return{id:target.id,item_hash:hashDocument(target.document)}}async function resolveExpectedItemRevisions(pmRoot,specs,defaultId){const resolved=[];for(const spec of specs){const target=spec.id??defaultId;if(target===void 0||target.trim().length===0)throw new PmCliError("A bare expected revision needs an item to bind to.",EXIT_CODE.USAGE,{code:"expect_hash_target_required",required:"This command has no single item argument; name each target as --expect-hash <id>=<item_hash>.",examples:["--expect-hash pm-a1b2=<item_hash>"]});resolved.push({id:(await locateRevisionTarget(pmRoot,target)).id,itemHash:spec.itemHash})}return resolved}function changedTopLevelFields(entry){return[...new Set(entry.patch.map(change=>{const segments=change.path.split("/");return String(segments[1]==="metadata"?segments[2]:segments[1])}))].sort()}async function describeItemRevisionChanges(pmRoot,id,expectedHash){let history;try{history=await readHistoryEntries(getHistoryPath(pmRoot,id),id)}catch{return}let start=-1;for(let index=history.length-1;index>=0;index-=1){const entry=history[index];if(entry.after_hash===expectedHash){start=index+1;break}if(entry.before_hash===expectedHash){start=index;break}}if(start===-1)return{expected_revision_recorded:!1,changes_since:[],changes_since_omitted:0};const later=history.slice(start),shown=later.slice(-ITEM_REVISION_CONFLICT_CHANGE_LIMIT);return{expected_revision_recorded:!0,changes_since:shown.map(entry=>({ts:entry.ts,op:entry.op,author:entry.author,fields:changedTopLevelFields(entry)})),changes_since_omitted:later.length-shown.length}}async function itemRevisionConflictRowFields(error,pmRoot){if(!isItemRevisionConflict(error))return{};const enriched=await enrichItemRevisionConflict(error,pmRoot);return{error_code:ITEM_REVISION_CONFLICT_CODE,expected_revision:String(enriched.context.expected_revision),current_revision:String(enriched.context.current_revision),details:enriched.context.details}}function isItemRevisionConflict(error){return isPmCliExpectedError(error)&&error.context.code===ITEM_REVISION_CONFLICT_CODE}function throwFirstItemRevisionConflict(errors){const conflict=errors.find(error=>isItemRevisionConflict(error));if(conflict!==void 0)throw conflict}function expectHashInvocationRewrittenError(){return new PmCliError("--expect-hash cannot guard an invocation an extension override rewrote: which item it writes is not provable.",EXIT_CODE.USAGE,{code:"expect_hash_invocation_rewritten",required:"Run the command without the rewriting extension (--no-extensions), or drop --expect-hash.",why:"An extension parser or preflight override changed this invocation's tracker or arguments, and final dispatch, the extension SDK client and the guard do not share one rewritten view."})}async function enrichItemRevisionConflict(error,pmRoot){if(!isItemRevisionConflict(error)||error.context.details!==void 0)return error;const id=error.context.item_id,expected=error.context.expected_revision;if(typeof id!="string"||typeof expected!="string")return error;let changes;try{changes=await describeItemRevisionChanges(pmRoot,id,expected)}catch{return error}if(changes===void 0)return error;const details={expected_revision_recorded:changes.expected_revision_recorded,changes_since:changes.changes_since.map(change=>({ts:change.ts,op:change.op,author:change.author,fields:[...change.fields]})),changes_since_omitted:changes.changes_since_omitted},enriched=new PmCliError(error.message,error.exitCode,{...error.context,details});return enriched.stack=error.stack,enriched}async function runWithBoundItemRevisionScope(scope,operation){try{return{result:await runWithItemRevisionScope(scope,operation),...itemRevisionScopeReceipt(scope)}}catch(error){throw scope.pmRoot===void 0?error:await enrichItemRevisionConflict(error,scope.pmRoot)}}async function withExpectedItemRevisions(expectations,operation,context={}){return runWithBoundItemRevisionScope(await bindExpectedItemRevisions(expectations,context),operation)}async function bindExpectedItemRevisions(expectations,context={}){const pmRoot=resolveRevisionRoot(context),specs=expectations.map(({id,itemHash})=>({id,itemHash:normalizeExpectedItemHash(itemHash)})),scope=createItemRevisionScope();return bindItemRevisionScope(scope,pmRoot,await resolveExpectedItemRevisions(pmRoot,specs)),scope}function itemRevisionUncheckedWarning(id){return`expect_hash_unchecked:${id}:no_locked_write_reached_this_item`}const NATIVE_ACTIONS=new Set(PM_TOOL_ACTIONS);function assertExpectHashAdmissible(path,options={}){const hyphenated=path.trim().replace(/\s+/gu,"-"),action=NATIVE_ACTIONS.has(hyphenated)?hyphenated:path;if(NATIVE_ACTIONS.has(action)&&(!isMutationAction(action)||action==="deps")&&!PM_EXPECT_HASH_ACTIONS.has(action))throw expectHashRequiresMutationError(action);if((action==="test"||action==="test-all")&&options.background===!0)throw new PmCliError(`--expect-hash cannot guard ${action} --background: the worker runs in another process.`,EXIT_CODE.USAGE,{code:"expect_hash_background_unsupported",required:`Run ${action} in the foreground to guard its result recording, or drop --expect-hash for a background run.`})}function expectHashRequiresMutationError(action){return new PmCliError(`--expect-hash guards item writes, and ${action} is not a mutation.`,EXIT_CODE.USAGE,{code:"expect_hash_requires_mutation",required:"Use --expect-hash (MCP/SDK expectHash) only on a command or action that writes items."})}function withUncheckedRevisionWarnings(result,unchecked){if(unchecked.length===0||typeof result!="object"||result===null||Array.isArray(result))return result;const record=result,warnings=Array.isArray(record.warnings)?record.warnings:[];return{...record,warnings:[...warnings,...unchecked.map(itemRevisionUncheckedWarning)]}}async function dispatchWithExpectedItemRevisions(params){const raw=params.expectHash;if(raw!==void 0&&typeof raw!="string"&&!(Array.isArray(raw)&&raw.every(entry=>typeof entry=="string")))throw invalidExpectHash(JSON.stringify(raw)??String(raw),"expected a string or an array of strings");const specs=parseExpectHashSpecs(raw);if(specs.length===0)return params.run();assertExpectHashAdmissible(params.action,{background:params.background===!0||params.options?.background===!0});const scope=createItemRevisionScope();bindItemRevisionScope(scope,params.pmRoot,await resolveExpectedItemRevisions(params.pmRoot,specs,params.defaultId));const outcome=await runWithBoundItemRevisionScope(scope,params.run);return withUncheckedRevisionWarnings(outcome.result,outcome.unchecked)}export{ITEM_REVISION_CONFLICT_CHANGE_LIMIT,ITEM_REVISION_CONFLICT_CODE,assertExpectHashAdmissible,bindExpectedItemRevisions,bindItemRevisionScope,createItemRevisionScope,describeItemRevisionChanges,dispatchWithExpectedItemRevisions,enrichItemRevisionConflict,expectHashInvocationRewrittenError,expectHashRequiresMutationError,isItemRevisionConflict,itemRevisionConflictRowFields,itemRevisionScopeReceipt,itemRevisionUncheckedWarning,normalizeExpectedItemHash,parseExpectHashSpecs,readItemRevision,resolveExpectedItemRevisions,runWithBoundItemRevisionScope,runWithItemRevisionScope,throwFirstItemRevisionConflict,withExpectedItemRevisions};
|
|
2
|
+
//# sourceMappingURL=item-revision.js.map
|
|
@@ -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]="f3145f17-e911-571b-9da3-f014e6fdde1c")}catch{}})();import{assertInitializedTracker}from"../environment/tracker-preflight.js";import{normalizeItemFormatVersion,requiredItemFormatVersion}from"../../core/item/item-format-version.js";import{assertProjectRuntimeFormatGuard}from"../environment/project-runtime-compatibility.js";import{enforceWorkflowMutation}from"../../core/policy/workflow-policy-store.js";import jsonPatch from"fast-json-patch";import fs from"node:fs/promises";import{readLocatedItemSnapshot}from"../../core/store/item-store.js";import{assertItemMutationPreconditions}from"../../core/store/item-mutation-precondition.js";import{readHistorySnapshot}from"../../core/history/read.js";import{verifyHistoryRewriteNoDrift}from"../../core/history/history-rewrite.js";import{resolveHistorySubject}from"../history/subject.js";import{historyVersionOffset}from"../../core/history/version-address.js";import{writeFileAtomic,appendHistoryEntry,createHistoryEntry,replayToCanonicalItemDocument,replayToItemDocument,toReplayDocument,enforceHistoryStreamPolicyForItem,canonicalDocument,serializeItemDocument,resolveItemTypeRegistry,acquireLockLease,EXIT_CODE,PmCliError,nowIso,getActiveExtensionRegistrations,projectAfterCommandItemSnapshot,recordAfterCommandAffectedItem,runActiveOnWriteHooks,runActiveBeforeMutationHooks,createMutationGuardSdk,getItemPath,resolvePmRoot,resolveWorkspaceRoot,readSettings,resolveAuthor}from"../runtime-primitives.js";import{applyHistoryPatch,ensureMaterializedHistoryTarget as ensureMaterializedRestoreTarget,extractPatchFailureContext,replayHistoryToTarget as replayToTarget,resolveHistoryTarget}from"../history-read.js";import{acquireItemMetadataDerivedIndexLock,refreshItemMetadataDerivedIndex}from"../item-metadata-index.js";async function readRestoreItem(located,settings,history){const snapshot=await readLocatedItemSnapshot(located,{schema:settings.schema});return snapshot.error===null?{raw:snapshot.raw,document:snapshot.document}:{raw:snapshot.raw,document:replayCurrentDocument(history),recovered:!0}}function ensureReplayTarget(target,history){return resolveHistoryTarget(target,history,{errorSubject:"restore",allowFutureTimestamp:!0})}function replayCurrentDocument(history){const currentReplay=replayToTarget(history,history.length-1);return Object.keys(currentReplay.metadata).length===0?{metadata:{},body:currentReplay.body}:canonicalDocument(replayToItemDocument(currentReplay))}async function resolveRestoreSubject(pmRoot,id,settings,typeToFolder){const subject=await resolveHistorySubject(pmRoot,id,settings,typeToFolder),historyPolicy=subject.located?await enforceHistoryStreamPolicyForItem({pmRoot,settings,itemId:subject.id,commandLabel:"restore"}):{warnings:[]};return{...subject,historyPolicyWarnings:historyPolicy.warnings}}function changedFields(beforeDocument,afterDocument){const beforeReplay=toReplayDocument(beforeDocument),afterReplay=toReplayDocument(afterDocument),patch=jsonPatch.compare(beforeReplay,afterReplay),fields=new Set;for(const op of patch){if(op.path==="/body"||op.path.startsWith("/body/")){fields.add("body");continue}const segment=op.path.replace(/^\/(?:metadata|front_matter)\/?/,"").split("/")[0];fields.add(segment.replaceAll("~1","/").replaceAll("~0","~"))}return Array.from(fields).sort((a,b)=>a.localeCompare(b))}async function loadRestoreStateUnderLock(params){const verified=await verifyHistoryRewriteNoDrift({pmRoot:params.pmRoot,subject:params.subject,settings:params.settings,typeRegistry:params.typeRegistry,historyRawBeforeLock:params.historyRawBeforeLock,currentItemRawBeforeLock:params.currentItemRawBeforeLock,operation:"restore",readItem:located=>readRestoreItem(located,params.settings,params.history)});return{loadedItemUnderLock:verified.loadedItemUnderLock,existingItemPath:verified.locatedUnderLock?.itemPath??null}}function resolveRestoreCurrentState(loadedItemUnderLock,history){return loadedItemUnderLock?{document:loadedItemUnderLock.document,originalRaw:loadedItemUnderLock.raw}:{document:replayCurrentDocument(history),originalRaw:null}}function collectRestoreOwnershipWarnings(params){const assigned=params.document.metadata.assignee?.trim();if(!(assigned&&assigned!==params.author&&!params.force))return[];if(params.enforcement==="strict")throw new PmCliError(`Item ${params.resolvedId} is assigned to ${assigned}. Use --force to override.`,EXIT_CODE.CONFLICT);return params.enforcement==="warn"?[`ownership_warning:assignee_conflict:${params.resolvedId}:${assigned}`]:[]}async function restorePreviousItemAfterHistoryFailure(params){if(params.existingItemPath&¶ms.resolvedOriginalRaw!==null&¶ms.restoredItemPath!==params.existingItemPath){await writeFileAtomic(params.existingItemPath,params.resolvedOriginalRaw),await fs.rm(params.restoredItemPath,{force:!0});return}if(params.existingItemPath&¶ms.resolvedOriginalRaw!==null){await writeFileAtomic(params.existingItemPath,params.resolvedOriginalRaw);return}await fs.rm(params.restoredItemPath,{force:!0})}async function runRestoreWriteHooks(params){return[...await runActiveOnWriteHooks({path:params.restoredItemPath,scope:"project",op:"restore",item_id:params.after.metadata.id,item_type:params.after.metadata.type,before:params.before,after:params.after,changed_fields:["restored"]}),...await runActiveOnWriteHooks({path:params.historyPath,scope:"project",op:"restore:history",item_id:params.after.metadata.id,item_type:params.after.metadata.type,before:params.before,after:params.after,changed_fields:["restored"]})]}async function runRestore(id,target,options,global2){const pmRoot=resolvePmRoot(process.cwd(),global2.path);await assertInitializedTracker(pmRoot);const settings=await readSettings(pmRoot),typeRegistry=resolveItemTypeRegistry(settings,getActiveExtensionRegistrations()),subject=await resolveRestoreSubject(pmRoot,id,settings,typeRegistry.type_to_folder),resolvedId=subject.id,{raw:historyRawBeforeLock,entries:history}=await readHistorySnapshot(subject.historyPath,resolvedId);if(history.length===0)throw new PmCliError(`No history exists for ${resolvedId}; restore is unavailable.`,EXIT_CODE.NOT_FOUND);const resolvedTarget=ensureReplayTarget(target,history),replayDocument=ensureMaterializedRestoreTarget(replayToTarget(history,resolvedTarget.historyIndex),resolvedTarget),restoredDocument=replayToCanonicalItemDocument(replayDocument,{schema:settings.schema});if(restoredDocument.metadata.id!==resolvedId)throw new PmCliError(`Restore target resolved to item ${restoredDocument.metadata.id}, expected ${resolvedId}.`,EXIT_CODE.GENERIC_FAILURE);const currentItemRawBeforeLock=(subject.located?await readRestoreItem(subject.located,settings,history):null)?.raw??null,author=resolveAuthor(options.author,settings.author_default),lease=await acquireLockLease(pmRoot,resolvedId,settings.locks.ttl_seconds,author,!!options.force,settings.governance.force_required_for_stale_lock,settings.locks.wait_ms,{waitPolicy:"progress"}),releaseLock=lease.release;try{const{loadedItemUnderLock,existingItemPath}=await loadRestoreStateUnderLock({pmRoot,subject,settings,typeRegistry,historyRawBeforeLock,currentItemRawBeforeLock,history}),itemFormat="toon",currentState=resolveRestoreCurrentState(loadedItemUnderLock,history);assertItemMutationPreconditions(pmRoot,currentState.document),restoredDocument.metadata.pm_format_version=normalizeItemFormatVersion(Math.max(requiredItemFormatVersion(restoredDocument.metadata),requiredItemFormatVersion(currentState.document.metadata))),assertProjectRuntimeFormatGuard(resolveWorkspaceRoot(pmRoot),requiredItemFormatVersion(restoredDocument.metadata));const ownershipWarnings=collectRestoreOwnershipWarnings({document:currentState.document,author,force:options.force,enforcement:settings.governance.ownership_enforcement,resolvedId}),restoreChangedFields=changedFields(currentState.document,restoredDocument),workflowPolicy=await enforceWorkflowMutation({pmRoot,settings,operation:"restore",author,before:currentState.document,after:restoredDocument});await runActiveBeforeMutationHooks({pm_root:pmRoot,operation:"restore",before:currentState.document,after:restoredDocument,changed_fields:restoreChangedFields,sdk:createMutationGuardSdk({pmRoot,settings,typeToFolder:typeRegistry.type_to_folder})});const serializedRestore=serializeItemDocument(restoredDocument,{format:itemFormat,schema:settings.schema}),restoredItemPath=getItemPath(pmRoot,restoredDocument.metadata.type,resolvedId,itemFormat,typeRegistry.type_to_folder),derivedIndexWarnings=await lease.fence(async()=>{const releaseDerivedIndexLock=await acquireItemMetadataDerivedIndexLock(pmRoot,author);let derivedIndexWarnings2;try{await writeFileAtomic(restoredItemPath,serializedRestore),existingItemPath&&restoredItemPath!==existingItemPath&&await fs.rm(existingItemPath);const historyEntry=createHistoryEntry({nowIso:nowIso(),author,op:"restore",before:currentState.document,after:restoredDocument,message:options.message,context:workflowPolicy.decisions.length===0?void 0:{workflow_policies:workflowPolicy.decisions}});try{await appendHistoryEntry(subject.historyPath,historyEntry)}catch(error){throw await restorePreviousItemAfterHistoryFailure({existingItemPath,resolvedOriginalRaw:currentState.originalRaw,restoredItemPath}),error}derivedIndexWarnings2=await refreshItemMetadataDerivedIndex({pmRoot,preferredFormat:settings.item_format,typeToFolder:typeRegistry.type_to_folder,schema:settings.schema,itemPath:restoredItemPath,previousItemPath:existingItemPath??void 0,document:restoredDocument})}finally{await releaseDerivedIndexLock()}return derivedIndexWarnings2}),hookWarnings=await runRestoreWriteHooks({restoredItemPath,historyPath:subject.historyPath,before:currentState.document,after:restoredDocument});recordAfterCommandAffectedItem({id:restoredDocument.metadata.id,op:"restore",item_type:restoredDocument.metadata.type,previous_status:currentState.document.metadata.status,status:restoredDocument.metadata.status,previous:projectAfterCommandItemSnapshot(currentState.document.metadata,restoreChangedFields),current:projectAfterCommandItemSnapshot(restoredDocument.metadata,restoreChangedFields),changed_fields:restoreChangedFields});const targetEntry=history[resolvedTarget.historyIndex],offset=historyVersionOffset(history,!0);return{item:restoredDocument.metadata,restored_from:{kind:resolvedTarget.kind,target:resolvedTarget.raw,history_index:resolvedTarget.historyIndex+1,version:offset===null?null:offset+resolvedTarget.historyIndex+1,entry_ts:targetEntry.ts,entry_op:targetEntry.op},changed_fields:restoreChangedFields,warnings:[...workflowPolicy.warnings,...loadedItemUnderLock?.recovered?["restore_unreadable_item_recovered"]:[],...subject.historyPolicyWarnings,...ownershipWarnings,...derivedIndexWarnings,...hookWarnings]}}finally{await releaseLock()}}const _testOnlyRestoreCommand={applyHistoryPatch,changedFields,ensureMaterializedRestoreTarget,ensureReplayTarget,extractPatchFailureContext,replayCurrentDocument,replayToTarget,resolveRestoreSubject};export{_testOnlyRestoreCommand,runRestore};
|
|
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]="89278323-311b-567b-b7ba-d51d3bec7a69")}catch{}})();import{assertInitializedTracker}from"../environment/tracker-preflight.js";import{normalizeItemFormatVersion,requiredItemFormatVersion}from"../../core/item/item-format-version.js";import{assertProjectRuntimeFormatGuard}from"../environment/project-runtime-compatibility.js";import{enforceWorkflowMutation}from"../../core/policy/workflow-policy-store.js";import jsonPatch from"fast-json-patch";import fs from"node:fs/promises";import{readLocatedItemSnapshot}from"../../core/store/item-store.js";import{advanceItemMutationPreconditions,assertItemMutationPreconditions,assertItemRevisionExpectationsOnUnreadable}from"../../core/store/item-mutation-precondition.js";import{EMPTY_CANONICAL_DOCUMENT}from"../../core/shared/constants.js";import{readHistorySnapshot}from"../../core/history/read.js";import{verifyHistoryRewriteNoDrift}from"../../core/history/history-rewrite.js";import{resolveHistorySubject}from"../history/subject.js";import{historyVersionOffset}from"../../core/history/version-address.js";import{writeFileAtomic,appendHistoryEntry,createHistoryEntry,replayToCanonicalItemDocument,replayToItemDocument,toReplayDocument,enforceHistoryStreamPolicyForItem,canonicalDocument,serializeItemDocument,resolveItemTypeRegistry,acquireLockLease,EXIT_CODE,PmCliError,nowIso,getActiveExtensionRegistrations,projectAfterCommandItemSnapshot,recordAfterCommandAffectedItem,runActiveOnWriteHooks,runActiveBeforeMutationHooks,createMutationGuardSdk,getItemPath,resolvePmRoot,resolveWorkspaceRoot,readSettings,resolveAuthor}from"../runtime-primitives.js";import{applyHistoryPatch,ensureMaterializedHistoryTarget as ensureMaterializedRestoreTarget,extractPatchFailureContext,replayHistoryToTarget as replayToTarget,resolveHistoryTarget}from"../history-read.js";import{acquireItemMetadataDerivedIndexLock,refreshItemMetadataDerivedIndex}from"../item-metadata-index.js";async function readRestoreItem(located,settings,history){const snapshot=await readLocatedItemSnapshot(located,{schema:settings.schema});return snapshot.error===null?{raw:snapshot.raw,document:snapshot.document}:{raw:snapshot.raw,document:replayCurrentDocument(history),recovered:!0}}function ensureReplayTarget(target,history){return resolveHistoryTarget(target,history,{errorSubject:"restore",allowFutureTimestamp:!0})}function replayCurrentDocument(history){const currentReplay=replayToTarget(history,history.length-1);return Object.keys(currentReplay.metadata).length===0?{metadata:{},body:currentReplay.body}:canonicalDocument(replayToItemDocument(currentReplay))}async function resolveRestoreSubject(pmRoot,id,settings,typeToFolder){const subject=await resolveHistorySubject(pmRoot,id,settings,typeToFolder),historyPolicy=subject.located?await enforceHistoryStreamPolicyForItem({pmRoot,settings,itemId:subject.id,commandLabel:"restore"}):{warnings:[]};return{...subject,historyPolicyWarnings:historyPolicy.warnings}}function changedFields(beforeDocument,afterDocument){const beforeReplay=toReplayDocument(beforeDocument),afterReplay=toReplayDocument(afterDocument),patch=jsonPatch.compare(beforeReplay,afterReplay),fields=new Set;for(const op of patch){if(op.path==="/body"||op.path.startsWith("/body/")){fields.add("body");continue}const segment=op.path.replace(/^\/(?:metadata|front_matter)\/?/,"").split("/")[0];fields.add(segment.replaceAll("~1","/").replaceAll("~0","~"))}return Array.from(fields).sort((a,b)=>a.localeCompare(b))}async function loadRestoreStateUnderLock(params){const verified=await verifyHistoryRewriteNoDrift({pmRoot:params.pmRoot,subject:params.subject,settings:params.settings,typeRegistry:params.typeRegistry,historyRawBeforeLock:params.historyRawBeforeLock,currentItemRawBeforeLock:params.currentItemRawBeforeLock,operation:"restore",readItem:located=>readRestoreItem(located,params.settings,params.history)});return{loadedItemUnderLock:verified.loadedItemUnderLock,existingItemPath:verified.locatedUnderLock?.itemPath??null}}function resolveRestoreCurrentState(loadedItemUnderLock,history){return loadedItemUnderLock?{document:loadedItemUnderLock.document,originalRaw:loadedItemUnderLock.raw}:{document:replayCurrentDocument(history),originalRaw:null}}function collectRestoreOwnershipWarnings(params){const assigned=params.document.metadata.assignee?.trim();if(!(assigned&&assigned!==params.author&&!params.force))return[];if(params.enforcement==="strict")throw new PmCliError(`Item ${params.resolvedId} is assigned to ${assigned}. Use --force to override.`,EXIT_CODE.CONFLICT);return params.enforcement==="warn"?[`ownership_warning:assignee_conflict:${params.resolvedId}:${assigned}`]:[]}async function restorePreviousItemAfterHistoryFailure(params){if(params.existingItemPath&¶ms.resolvedOriginalRaw!==null&¶ms.restoredItemPath!==params.existingItemPath){await writeFileAtomic(params.existingItemPath,params.resolvedOriginalRaw),await fs.rm(params.restoredItemPath,{force:!0});return}if(params.existingItemPath&¶ms.resolvedOriginalRaw!==null){await writeFileAtomic(params.existingItemPath,params.resolvedOriginalRaw);return}await fs.rm(params.restoredItemPath,{force:!0})}async function runRestoreWriteHooks(params){return[...await runActiveOnWriteHooks({path:params.restoredItemPath,scope:"project",op:"restore",item_id:params.after.metadata.id,item_type:params.after.metadata.type,before:params.before,after:params.after,changed_fields:["restored"]}),...await runActiveOnWriteHooks({path:params.historyPath,scope:"project",op:"restore:history",item_id:params.after.metadata.id,item_type:params.after.metadata.type,before:params.before,after:params.after,changed_fields:["restored"]})]}async function runRestore(id,target,options,global2){const pmRoot=resolvePmRoot(process.cwd(),global2.path);await assertInitializedTracker(pmRoot);const settings=await readSettings(pmRoot),typeRegistry=resolveItemTypeRegistry(settings,getActiveExtensionRegistrations()),subject=await resolveRestoreSubject(pmRoot,id,settings,typeRegistry.type_to_folder),resolvedId=subject.id,{raw:historyRawBeforeLock,entries:history}=await readHistorySnapshot(subject.historyPath,resolvedId);if(history.length===0)throw new PmCliError(`No history exists for ${resolvedId}; restore is unavailable.`,EXIT_CODE.NOT_FOUND);const resolvedTarget=ensureReplayTarget(target,history),replayDocument=ensureMaterializedRestoreTarget(replayToTarget(history,resolvedTarget.historyIndex),resolvedTarget),restoredDocument=replayToCanonicalItemDocument(replayDocument,{schema:settings.schema});if(restoredDocument.metadata.id!==resolvedId)throw new PmCliError(`Restore target resolved to item ${restoredDocument.metadata.id}, expected ${resolvedId}.`,EXIT_CODE.GENERIC_FAILURE);const currentItemRawBeforeLock=(subject.located?await readRestoreItem(subject.located,settings,history):null)?.raw??null,author=resolveAuthor(options.author,settings.author_default),lease=await acquireLockLease(pmRoot,resolvedId,settings.locks.ttl_seconds,author,!!options.force,settings.governance.force_required_for_stale_lock,settings.locks.wait_ms,{waitPolicy:"progress"}),releaseLock=lease.release;try{const{loadedItemUnderLock,existingItemPath}=await loadRestoreStateUnderLock({pmRoot,subject,settings,typeRegistry,historyRawBeforeLock,currentItemRawBeforeLock,history}),itemFormat="toon",currentState=resolveRestoreCurrentState(loadedItemUnderLock,history);loadedItemUnderLock?.recovered===!0&&assertItemRevisionExpectationsOnUnreadable(pmRoot,resolvedId,loadedItemUnderLock.raw),assertItemMutationPreconditions(pmRoot,loadedItemUnderLock?.document??EMPTY_CANONICAL_DOCUMENT,resolvedId),restoredDocument.metadata.pm_format_version=normalizeItemFormatVersion(Math.max(requiredItemFormatVersion(restoredDocument.metadata),requiredItemFormatVersion(currentState.document.metadata))),assertProjectRuntimeFormatGuard(resolveWorkspaceRoot(pmRoot),requiredItemFormatVersion(restoredDocument.metadata));const ownershipWarnings=collectRestoreOwnershipWarnings({document:currentState.document,author,force:options.force,enforcement:settings.governance.ownership_enforcement,resolvedId}),restoreChangedFields=changedFields(currentState.document,restoredDocument),workflowPolicy=await enforceWorkflowMutation({pmRoot,settings,operation:"restore",author,before:currentState.document,after:restoredDocument});await runActiveBeforeMutationHooks({pm_root:pmRoot,operation:"restore",before:currentState.document,after:restoredDocument,changed_fields:restoreChangedFields,sdk:createMutationGuardSdk({pmRoot,settings,typeToFolder:typeRegistry.type_to_folder})});const serializedRestore=serializeItemDocument(restoredDocument,{format:itemFormat,schema:settings.schema}),restoredItemPath=getItemPath(pmRoot,restoredDocument.metadata.type,resolvedId,itemFormat,typeRegistry.type_to_folder),derivedIndexWarnings=await lease.fence(async()=>{const releaseDerivedIndexLock=await acquireItemMetadataDerivedIndexLock(pmRoot,author);let derivedIndexWarnings2;try{await writeFileAtomic(restoredItemPath,serializedRestore),existingItemPath&&restoredItemPath!==existingItemPath&&await fs.rm(existingItemPath);const historyEntry=createHistoryEntry({nowIso:nowIso(),author,op:"restore",before:currentState.document,after:restoredDocument,message:options.message,context:workflowPolicy.decisions.length===0?void 0:{workflow_policies:workflowPolicy.decisions}});try{await appendHistoryEntry(subject.historyPath,historyEntry)}catch(error){throw await restorePreviousItemAfterHistoryFailure({existingItemPath,resolvedOriginalRaw:currentState.originalRaw,restoredItemPath}),error}derivedIndexWarnings2=await refreshItemMetadataDerivedIndex({pmRoot,preferredFormat:settings.item_format,typeToFolder:typeRegistry.type_to_folder,schema:settings.schema,itemPath:restoredItemPath,previousItemPath:existingItemPath??void 0,document:restoredDocument})}finally{await releaseDerivedIndexLock()}return derivedIndexWarnings2});advanceItemMutationPreconditions(pmRoot,restoredDocument,resolvedId);const hookWarnings=await runRestoreWriteHooks({restoredItemPath,historyPath:subject.historyPath,before:currentState.document,after:restoredDocument});recordAfterCommandAffectedItem({id:restoredDocument.metadata.id,op:"restore",item_type:restoredDocument.metadata.type,previous_status:currentState.document.metadata.status,status:restoredDocument.metadata.status,previous:projectAfterCommandItemSnapshot(currentState.document.metadata,restoreChangedFields),current:projectAfterCommandItemSnapshot(restoredDocument.metadata,restoreChangedFields),changed_fields:restoreChangedFields});const targetEntry=history[resolvedTarget.historyIndex],offset=historyVersionOffset(history,!0);return{item:restoredDocument.metadata,restored_from:{kind:resolvedTarget.kind,target:resolvedTarget.raw,history_index:resolvedTarget.historyIndex+1,version:offset===null?null:offset+resolvedTarget.historyIndex+1,entry_ts:targetEntry.ts,entry_op:targetEntry.op},changed_fields:restoreChangedFields,warnings:[...workflowPolicy.warnings,...loadedItemUnderLock?.recovered?["restore_unreadable_item_recovered"]:[],...subject.historyPolicyWarnings,...ownershipWarnings,...derivedIndexWarnings,...hookWarnings]}}finally{await releaseLock()}}const _testOnlyRestoreCommand={applyHistoryPatch,changedFields,ensureMaterializedRestoreTarget,ensureReplayTarget,extractPatchFailureContext,replayCurrentDocument,replayToTarget,resolveRestoreSubject};export{_testOnlyRestoreCommand,runRestore};
|
|
2
2
|
//# sourceMappingURL=restore.js.map
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { RuntimeStatusRegistry } from "../../core/schema/runtime-schema.js";
|
|
2
|
+
import type { GovernanceStartBlockerEnforcement,ItemMetadata,PmSettings } from "../../types/index.js";
|
|
3
|
+
/** Enforcement applied when `governance.start_blocker_enforcement` is unset. */
|
|
4
|
+
export declare const START_BLOCKER_ENFORCEMENT_DEFAULT:GovernanceStartBlockerEnforcement;
|
|
5
|
+
/** Typed refusal for starting an item whose declared blocker is still open. */
|
|
6
|
+
export declare const START_BLOCKED_REFUSAL_CODE = "start_blocked_by_open_dependency";
|
|
7
|
+
/**
|
|
8
|
+
* Whether a status change moves an item into the effective start status from
|
|
9
|
+
* another status. The effective status is the one start-task writes, including
|
|
10
|
+
* its registered fallback when the workflow names `open` or a removed status.
|
|
11
|
+
*/
|
|
12
|
+
export declare function isStartTransition(fromStatus:string | undefined,toStatus:string | undefined,registry:RuntimeStatusRegistry):boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Admit or refuse starting `metadata` against its currently open declared
|
|
15
|
+
* blockers. Returns warnings for admitted starts; throws the typed refusal
|
|
16
|
+
* under `strict` enforcement without `force`.
|
|
17
|
+
*/
|
|
18
|
+
export declare function admitStartWithOpenBlockers(params:{
|
|
19
|
+
pmRoot:string;
|
|
20
|
+
settings:PmSettings;
|
|
21
|
+
metadata:ItemMetadata;
|
|
22
|
+
force?:boolean;
|
|
23
|
+
}):Promise<string[]>;
|
|
24
|
+
/**
|
|
25
|
+
* Judge a status change already applied to `metadata`: a move into progress
|
|
26
|
+
* from `fromStatus` goes through {@link admitStartWithOpenBlockers}; any other
|
|
27
|
+
* change, or a caller that did not request a start, is admitted without a read.
|
|
28
|
+
*/
|
|
29
|
+
export declare function admitStartTransition(params:{
|
|
30
|
+
pmRoot:string;
|
|
31
|
+
settings:PmSettings;
|
|
32
|
+
metadata:ItemMetadata;
|
|
33
|
+
fromStatus:string | undefined;
|
|
34
|
+
registry:RuntimeStatusRegistry;
|
|
35
|
+
force?:boolean;
|
|
36
|
+
requested?:boolean;
|
|
37
|
+
}):Promise<string[]>;
|
|
38
|
+
/**
|
|
39
|
+
* Run an explicit claim-then-update start composition. Its claim judges the
|
|
40
|
+
* start under the claim's own item lock, before writing ownership; the status
|
|
41
|
+
* update judges again under its own lock, so a blocker opened in between still
|
|
42
|
+
* refuses (the composition then releases the claim it took).
|
|
43
|
+
*/
|
|
44
|
+
export declare function runAsExplicitStart<T>(operation:() => Promise<T>):Promise<T>;
|
|
45
|
+
/** Judge the start an enclosing explicit start composition will make, inside the claim's locked mutation. */
|
|
46
|
+
export declare function admitExplicitStartClaim(params:{
|
|
47
|
+
pmRoot:string;
|
|
48
|
+
settings:PmSettings;
|
|
49
|
+
metadata:ItemMetadata;
|
|
50
|
+
registry:RuntimeStatusRegistry;
|
|
51
|
+
force?:boolean;
|
|
52
|
+
}):Promise<string[]>;
|
|
@@ -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]="a49a1fae-4705-55b4-b4d7-8aa2b4fbfe89")}catch{}})();import{AsyncLocalStorage}from"node:async_hooks";import{getActiveExtensionRegistrations}from"../../core/extensions/index.js";import{normalizeItemId}from"../../core/item/id.js";import{renderPmCommand}from"../../core/shared/command-line.js";import{normalizeStatusForRegistry}from"../../core/item/status.js";import{resolveItemTypeRegistry}from"../../core/item/type-registry.js";import{EXIT_CODE}from"../../core/shared/constants.js";import{PmCliError}from"../../core/shared/errors.js";import{isPmCliExpectedError}from"../errors.js";import{resolveDeclaredBlockers}from"../query/declared-blockers.js";import{resolveStartTaskInProgressStatus}from"../start-task-status.js";import{normalizeItemReference}from"../dependency-provenance.js";import{resolveCanonicalRelationshipKind}from"../relationships.js";const START_BLOCKER_ENFORCEMENT_DEFAULT="strict",START_BLOCKED_REFUSAL_CODE="start_blocked_by_open_dependency";function isStartTransition(fromStatus,toStatus,registry){if(toStatus===void 0)return!1;const inProgress=normalizeStatusForRegistry(resolveStartTaskInProgressStatus(registry),registry);return normalizeStatusForRegistry(toStatus,registry)===inProgress&&normalizeStatusForRegistry(fromStatus??"",registry)!==inProgress}function sameBlocker(reference,blockerId,idPrefix){const canonical=value=>normalizeItemId(value.trim(),idPrefix).toLowerCase();return canonical(reference)===canonical(blockerId)}function unblockCommand(metadata,blockerId,idPrefix){const dependencies=metadata.dependencies??[],edges=dependencies.filter(dependency=>dependency.kind==="blocked_by");if(dependencies.some(dependency=>dependency.kind!=="blocked_by"&&resolveCanonicalRelationshipKind(dependency.kind)==="blocked_by"&&sameBlocker(dependency.id,blockerId,idPrefix)))return renderPmCommand(["get",metadata.id,"--fields","blocked_by,dependencies"]);if(!(typeof metadata.blocked_by=="string"&&sameBlocker(metadata.blocked_by,blockerId,idPrefix))){const matching=edges.filter(dependency=>sameBlocker(dependency.id,blockerId,idPrefix));return matching.length===1&&matching.every(dependency=>dependency.id===normalizeItemReference(dependency.id,idPrefix,void 0))?renderPmCommand(["update",metadata.id,"--dep-remove",`id=${matching[0].id},kind=blocked_by`]):renderPmCommand(["get",metadata.id,"--fields","blocked_by,dependencies"])}const literal=value=>value.trim().toLowerCase();return edges.every(dependency=>literal(dependency.id)===literal(String(metadata.blocked_by)))?renderPmCommand(["update",metadata.id,"--unset","blocked_by"]):renderPmCommand(["get",metadata.id,"--fields","blocked_by,dependencies"])}const UNVERIFIABLE_BLOCKER_CODES=new Set(["item_document_invalid","item_document_encoding_invalid","item_format_version_unsupported","item_identity_ambiguous","item_identity_conflict"]);async function readOpenBlockers(params,admitUnverifiable){try{return(await resolveDeclaredBlockers(params)).filter(blocker=>!blocker.resolved)}catch(error){const code=isPmCliExpectedError(error)?error.context.code:void 0;if(!admitUnverifiable||typeof code!="string"||!UNVERIFIABLE_BLOCKER_CODES.has(code))throw error;return code}}async function admitStartWithOpenBlockers(params){const enforcement=params.settings.governance.start_blocker_enforcement??START_BLOCKER_ENFORCEMENT_DEFAULT;if(enforcement==="off")return[];const typeToFolder=resolveItemTypeRegistry(params.settings,getActiveExtensionRegistrations()).type_to_folder,id=params.metadata.id,open=await readOpenBlockers({pmRoot:params.pmRoot,settings:params.settings,typeToFolder,metadata:params.metadata},enforcement==="warn"||params.force===!0);if(typeof open=="string")return[`start_blocker_unverifiable:${id}:${open}`];if(open.length===0)return[];const ids=open.map(blocker=>blocker.id).join(",");if(enforcement==="warn")return[`start_with_open_blockers:${id}:${ids}`];if(params.force===!0)return[`start_blocker_override:${id}:${ids}`];throw new PmCliError(`Cannot start ${id}: ${open.length===1?"its blocker":`${open.length} of its blockers`} ${ids} ${open.length===1?"is":"are"} still open.`,EXIT_CODE.CONFLICT,{code:"start_blocked_by_open_dependency",item_id:id,required:`Finish or close the open blocker${open.length===1?"":"s"} first, remove the blocked_by edge if it no longer applies, or start anyway with --force.`,why:"Starting work that a declared blocker still gates would leave an in-progress item that validate reports as non_blocked_status_has_blocked_by. governance.start_blocker_enforcement (strict by default) governs this check.",nextSteps:[...open.filter(blocker=>blocker.external!==!0).slice(0,3).map(blocker=>renderPmCommand(["get",blocker.id])),unblockCommand(params.metadata,open[0].id,params.settings.id_prefix),"pm next"],details:{enforcement,open_blockers:open.map(blocker=>({id:blocker.id,title:blocker.title,status:blocker.status,external:blocker.external===!0}))}})}async function admitStartTransition(params){if(params.requested===!1||!isStartTransition(params.fromStatus,params.metadata.status,params.registry))return[];const warnings=await admitStartWithOpenBlockers(params),recorded=explicitStarts.getStore()?.warnings??new Set;return warnings.filter(warning=>!recorded.has(warning))}const explicitStarts=new AsyncLocalStorage;function runAsExplicitStart(operation){return explicitStarts.run({warnings:new Set},operation)}async function admitExplicitStartClaim(params){const pending=explicitStarts.getStore();if(pending===void 0)return[];const warnings=isStartTransition(params.metadata.status,resolveStartTaskInProgressStatus(params.registry),params.registry)?await admitStartWithOpenBlockers(params):[];for(const warning of warnings)pending.warnings.add(warning);return warnings}export{START_BLOCKED_REFUSAL_CODE,START_BLOCKER_ENFORCEMENT_DEFAULT,admitExplicitStartClaim,admitStartTransition,admitStartWithOpenBlockers,isStartTransition,runAsExplicitStart};
|
|
2
|
+
//# sourceMappingURL=start-blockers.js.map
|
|
@@ -13,6 +13,17 @@ next?:boolean;
|
|
|
13
13
|
export declare function finishComposition<T>(id:string,completed:"claim" | "update" | "close",operation:() => Promise<T>):Promise<T>;
|
|
14
14
|
/** Claim explicit work and advance it through the workspace's configured workflow. */
|
|
15
15
|
export declare function runStartTask(id:string,options:TaskCompositionOptions,global:GlobalOptions):Promise<StartTaskResult>;
|
|
16
|
+
/**
|
|
17
|
+
* Compensate the one refusal a start's own claim cannot have judged: a blocker
|
|
18
|
+
* opened after the claim committed. When this start took the item from no
|
|
19
|
+
* owner, release that claim before refusing, so the start leaves no ownership;
|
|
20
|
+
* every other failure keeps the partial-state error unchanged.
|
|
21
|
+
*/
|
|
22
|
+
export declare function releaseRefusedStartClaim(error:unknown,claim:{
|
|
23
|
+
item:Record<string,unknown>;
|
|
24
|
+
previous_assignee:string | null;
|
|
25
|
+
claimed_by:string;
|
|
26
|
+
},global:GlobalOptions,author:string | undefined):Promise<unknown>;
|
|
16
27
|
/** Return work to its configured open status, then release ownership. */
|
|
17
28
|
export declare function runPauseTask(id:string,options:TaskCompositionOptions,global:GlobalOptions):Promise<PauseTaskResult>;
|
|
18
29
|
/** Record all close evidence before releasing assignment metadata. */
|
|
@@ -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]="
|
|
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]="35d9d28d-68f5-5a6d-b3e8-02424b81a4d4")}catch{}})();import{PmCliError}from"../../core/shared/errors.js";import{EXIT_CODE}from"../../core/shared/constants.js";import{resolveRuntimeStatusRegistry}from"../../core/schema/runtime-schema.js";import{resolvePmRoot}from"../../core/store/paths.js";import{readSettings}from"../../core/store/settings.js";import{runClaim,runRelease}from"./claim.js";import{resolveReleaseHandoff}from"./release-handoff.js";import{runClose}from"./close.js";import{runUpdate}from"./update.js";import{resolveStartTaskInProgressStatus}from"../start-task-status.js";import{runAsExplicitStart,START_BLOCKED_REFUSAL_CODE}from"./start-blockers.js";import{runWithItemMutationPrecondition}from"../../core/store/item-mutation-precondition.js";import{mutationOptionsWithOverrides}from"../runtime-input.js";async function finishComposition(id,completed,operation){try{return await operation()}catch(error){const context=error instanceof PmCliError?error.context:{},failure=new PmCliError(error instanceof Error?error.message:String(error),error instanceof PmCliError?error.exitCode:EXIT_CODE.GENERIC_FAILURE,{...context,item_id:id,why:`${completed} completed for ${id} before the following step failed. No rollback was performed.${context.why?` ${context.why}`:""}`,nextSteps:[`Inspect the persisted state with pm get ${id} and pm history ${id} before retrying the failed step.`,...context.nextSteps??[]]});throw failure.cause=error,failure}}async function runStartTask(id,options,global2){if(options.next===!0||options.ifAvailable===!0)throw new PmCliError("--start cannot be combined with --next or --if-available for an explicit item",EXIT_CODE.USAGE,{code:"invalid_argument_value",required:"Remove --next and --if-available when starting an explicit item.",nextSteps:["Remove --next and --if-available, keep the explicit item ID and other supplied arguments, then retry."]});const settings=await readSettings(resolvePmRoot(process.cwd(),global2.path)),status=resolveStartTaskInProgressStatus(resolveRuntimeStatusRegistry(settings.schema));return runAsExplicitStart(async()=>{const claim=await runClaim(id,options.force===!0,global2,mutationOptionsWithOverrides(options,{},["start"])),canonicalId=claim.item.id,update=await finishComposition(canonicalId,"claim",()=>runUpdate(canonicalId,mutationOptionsWithOverrides(options,{status},["assignee","start","next","ifAvailable","maxAttempts"]),global2)).catch(async error=>{throw await releaseRefusedStartClaim(error,claim,global2,options.author)});return{id:canonicalId,action:"start_task",claim,update}})}async function releaseRefusedStartClaim(error,claim,global2,author){if(!(error instanceof PmCliError)||error.code!==START_BLOCKED_REFUSAL_CODE||claim.previous_assignee!==null)return error;const id=String(claim.item.id);try{await runWithItemMutationPrecondition({pmRoot:resolvePmRoot(process.cwd(),global2.path),id,validate:document=>{if(document.metadata.assignee!==claim.claimed_by||document.metadata.claim_principal!==claim.item.claim_principal)throw new PmCliError("The claim this start took is no longer its own.",EXIT_CODE.CONFLICT)}},()=>runRelease(id,!1,global2,{author,message:"Release the claim: a blocker opened before its start could be recorded"}))}catch{return error}const refusal=error.cause instanceof PmCliError?error.cause:error;return new PmCliError(refusal.message,refusal.exitCode,{...refusal.context,why:`${refusal.context.why??""} A blocker opened after this start's claim, so the claim was released and no ownership remains.`.trim()})}async function runPauseTask(id,options,global2){const settings=await readSettings(resolvePmRoot(process.cwd(),global2.path)),registry=resolveRuntimeStatusRegistry(settings.schema);resolveReleaseHandoff({...options,pause:!0},registry);const status=registry.open_status,update=await runUpdate(id,mutationOptionsWithOverrides(options,{status},["assignee","pause"]),global2),canonicalId=update.item.id,release=await finishComposition(canonicalId,"update",()=>runRelease(canonicalId,options.force===!0,global2,options));return{id:canonicalId,action:"pause_task",update,release}}async function runCloseTask(id,reason,options,global2){const close=await runClose(id,reason,options,global2),canonicalId=close.item.id,release=await finishComposition(canonicalId,"close",()=>runRelease(canonicalId,options.force===!0,global2,options));return{id:canonicalId,action:"close_task",close,release}}export{finishComposition,releaseRefusedStartClaim,runCloseTask,runPauseTask,runStartTask};
|
|
2
2
|
//# sourceMappingURL=task-composition.js.map
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module cli/commands/update-many
|
|
3
|
+
*
|
|
4
|
+
* Implements the pm update many command surface and its agent-facing runtime behavior.
|
|
5
|
+
*/
|
|
6
|
+
import { type ItemRevisionConflictRowFields } from "./item-revision.js";
|
|
7
|
+
import { type DependentRevisionConflict } from "./close.js";
|
|
1
8
|
import { type RuntimeFieldRegistry,type RuntimeStatusRegistry,type GlobalOptions } from "../runtime-primitives.js";
|
|
2
9
|
import type { ItemStatus,PmSettings } from "../../types/index.js";
|
|
3
10
|
import { hasListFilters } from "../query/list-filter-shared.js";
|
|
@@ -31,14 +38,16 @@ id:string;
|
|
|
31
38
|
changes:PlannedChange[];
|
|
32
39
|
warnings?:string[];
|
|
33
40
|
}
|
|
34
|
-
interface UpdateManyApplyResultRow {
|
|
41
|
+
interface UpdateManyApplyResultRow extends ItemRevisionConflictRowFields {
|
|
35
42
|
id:string;
|
|
36
43
|
status:"updated" | "failed" | "skipped";
|
|
37
44
|
changed_fields?:string[];
|
|
38
45
|
warnings?:string[];
|
|
39
46
|
error?:string;
|
|
47
|
+
/** A guarded dependent whose auto-unblock refused after this row's close committed. */
|
|
48
|
+
dependent_revision_conflict?:DependentRevisionConflict;
|
|
40
49
|
}
|
|
41
|
-
interface UpdateManyRollbackResultRow {
|
|
50
|
+
interface UpdateManyRollbackResultRow extends ItemRevisionConflictRowFields {
|
|
42
51
|
id:string;
|
|
43
52
|
status:"restored" | "failed" | "skipped";
|
|
44
53
|
changed_fields?:string[];
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
(function(){try{var e=typeof window<"u"?window:typeof global<"u"?global:typeof globalThis<"u"?globalThis:typeof self<"u"?self:{},n=new e.Error().stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="86acbe76-ed18-52be-b561-4819c648e88e")}catch{}})();import{assertInitializedTracker}from"../environment/tracker-preflight.js";import{withCheckpointMutation}from"../../core/checkpoint/mutation-context.js";import{resolveCheckpointRollbackId}from"../../core/checkpoint/mutation-checkpoint.js";import{rollbackMutationCheckpoint}from"./checkpoint-rollback.js";import{createCheckpointId,loadMutationCheckpoint,writeMutationCheckpoint,toItemRecord,applyAcceptanceCriteriaMutations,applyTagRemovals,mergeAdditiveTags,parseOptionalNonNegativeInteger,parseTags,splitAcceptanceCriteria,resolvePriority,normalizeStatusInput,resolveItemTypeRegistry,resolveTypeName,collectRuntimeUpdateFieldValues,buildInvalidTypeError,resolveItemTypesFilePath,resolveRuntimeFieldRegistry,resolveRuntimeStatusRegistry,EXIT_CODE,PmCliError,toErrorMessage,stableValueEquals,nowIso,preserveMutationStdinTokenLiterals,transferMutationStdinTokenPolicy,resolveIsoOrRelative,getActiveExtensionRegistrations,collectRegisteredItemFieldNames,resolvePmRoot,readSettings,resolveAuthor}from"../runtime-primitives.js";import{assertDependencyTargetsResolvable}from"../dependency-flag-validation.js";import{parseStatusFilterCsv}from"../../core/item/status-filter.js";import{hasListFilters}from"../query/list-filter-shared.js";import{runList}from"../query/list.js";import{assertUpdateMutationOptionCombinations,assertUpdateUnsetFieldsSupported,parseDependencyAdditions,resolveUpdateTransportOptions,runUpdate}from"./update.js";import{derivePmBulkMutationEffect}from"../cli-contracts/command-exit-contracts.js";const UPDATE_MANY_CHECKPOINT_SCHEMA_VERSION=2,UPDATE_MANY_CHECKPOINT_SUBDIR="update-many",NON_MUTATION_UPDATE_OPTION_KEYS=new Set(["author","message","force","ownershipMetadataBypass","ownershipDependencyBypass","allowUnresolvedDeps"]),UPDATE_MANY_MUTATION_FLAG_GUIDANCE=["--status","--priority","--type","--tags","--description","--body","--deadline","--estimate","--assignee","--dep","--dep-remove","--comment","--note","--learning","--file","--test","--doc","--replace-deps","--replace-tests","--replace-files","--replace-docs","--unset","--clear-*"].join(", "),UPDATE_OPTION_TO_ITEM_KEY={title:"title",description:"description",body:"body",status:"status",closeReason:"close_reason",priority:"priority",type:"type",deadline:"deadline",estimatedMinutes:"estimated_minutes",acceptanceCriteria:"acceptance_criteria",definitionOfReady:"definition_of_ready",order:"order",goal:"goal",objective:"objective",value:"value",impact:"impact",outcome:"outcome",whyNow:"why_now",assignee:"assignee",parent:"parent",reviewer:"reviewer",risk:"risk",confidence:"confidence",sprint:"sprint",release:"release",blockedBy:"blocked_by",blockedReason:"blocked_reason",unblockNote:"unblock_note",reporter:"reporter",severity:"severity",environment:"environment",reproSteps:"repro_steps",resolution:"resolution",expectedResult:"expected_result",actualResult:"actual_result",affectedVersion:"affected_version",fixedVersion:"fixed_version",component:"component",regression:"regression",customerImpact:"customer_impact"},COLLECTION_MUTATION_PLAN_DEFINITIONS=[{field:"dependencies",addKey:"dep",removeKey:"depRemove",clearKey:"clearDeps",replaceKey:"replaceDeps"},{field:"comments",addKey:"comment",clearKey:"clearComments"},{field:"notes",addKey:"note",clearKey:"clearNotes"},{field:"learnings",addKey:"learning",clearKey:"clearLearnings"},{field:"files",addKey:"file",clearKey:"clearFiles",replaceKey:"replaceFiles"},{field:"tests",addKey:"test",clearKey:"clearTests",replaceKey:"replaceTests"},{field:"docs",addKey:"doc",clearKey:"clearDocs",replaceKey:"replaceDocs"},{field:"reminders",addKey:"reminder",clearKey:"clearReminders"},{field:"events",addKey:"event",clearKey:"clearEvents"},{field:"type_options",addKey:"typeOption",clearKey:"clearTypeOptions"}],sanitizeUpdateOptionsForSummary=options=>{const summary={};for(const[key,value]of Object.entries(options))NON_MUTATION_UPDATE_OPTION_KEYS.has(key)||value!==void 0&&(summary[key]=value);return summary},hasAnyUpdateMutationInput=options=>Object.keys(sanitizeUpdateOptionsForSummary(options)).length>0,trimPreviewValue=value=>typeof value=="string"?value.trim():value,normalizeIntegerPreviewValue=value=>{const trimmed=String(value).trim(),parsed=Number(trimmed);return Number.isInteger(parsed)?parsed:trimmed},normalizeNumericPreviewValue=value=>{const trimmed=String(value).trim(),parsed=Number(trimmed);return Number.isFinite(parsed)?parsed:trimmed},normalizeRegressionPreviewValue=value=>{const normalized=String(value).trim().toLowerCase();return new Map([["true",!0],["1",!0],["false",!1],["0",!1]]).get(normalized)??trimPreviewValue(value)},PREVIEW_VALUE_NORMALIZERS={priority:normalizeIntegerPreviewValue,estimatedMinutes:normalizeNumericPreviewValue,order:normalizeNumericPreviewValue,rank:normalizeNumericPreviewValue,regression:normalizeRegressionPreviewValue},toComparablePreviewValue=(optionKey,value)=>{if(value!==void 0)return(PREVIEW_VALUE_NORMALIZERS[optionKey]??trimPreviewValue)(value)},areValuesEqual=(left,right)=>stableValueEquals(left,right),normalizeCollectionBeforeValue=(field,value)=>value!==void 0?value:field==="type_options"?{}:[],collectionValueCount=(field,value)=>Array.isArray(value)?value.length:field==="type_options"&&value&&typeof value=="object"?Object.keys(value).length:0,resolveCollectionMutationOperation=params=>params.replace?"replace":params.clear?"clear_or_reset":params.removeCount>0?"merge_remove":"append",readOptionalUpdateValue=(update,key)=>key===void 0?void 0:update[key],updateArrayValueCount=value=>Array.isArray(value)?value.length:0,buildCollectionMutationPlan=(definition,row,update)=>{const addValues=update[definition.addKey],removeValues=readOptionalUpdateValue(update,definition.removeKey),addCount=updateArrayValueCount(addValues),removeCount=updateArrayValueCount(removeValues),clear=update[definition.clearKey]===!0,replace=readOptionalUpdateValue(update,definition.replaceKey)===!0;if(![clear,replace,addCount>0,removeCount>0].includes(!0))return;const before=normalizeCollectionBeforeValue(definition.field,row[definition.field]);return{field:definition.field,before,after:{operation:resolveCollectionMutationOperation({replace,clear,removeCount}),clear,replace,add_count:addCount,remove_count:removeCount,before_count:collectionValueCount(definition.field,before)}}},buildCollectionMutationPlans=(row,update)=>{const changes=[];for(const definition of COLLECTION_MUTATION_PLAN_DEFINITIONS){const plan=buildCollectionMutationPlan(definition,row,update);plan!==void 0&&changes.push(plan)}return changes},normalizeExistingTags=value=>Array.isArray(value)?value.filter(tag=>typeof tag=="string"):[],buildTagMutationPlan=(row,update)=>{if(![update.tags!==void 0,updateArrayValueCount(update.addTags)>0,updateArrayValueCount(update.removeTags)>0].includes(!0))return;const existing=normalizeExistingTags(row.tags),baseTags=update.tags===void 0?existing:parseTags(update.tags),withAdditions=mergeAdditiveTags(baseTags,update.addTags),after=applyTagRemovals(withAdditions,update.removeTags).slice().sort((a,b)=>a.localeCompare(b)),before=existing.slice().sort((a,b)=>a.localeCompare(b));if(!areValuesEqual(before,after))return{field:"tags",before:existing,after}},buildAcceptanceCriteriaMutationPlan=(row,update)=>{const before=row.acceptance_criteria,mutation=applyAcceptanceCriteriaMutations(splitAcceptanceCriteria(before),update.addAc,update.removeAc);if(mutation.unmatchedRemovals.length>0)throw new PmCliError(`Acceptance criteria removal did not match: ${mutation.unmatchedRemovals.join(", ")}`,EXIT_CODE.NOT_FOUND,{code:"acceptance_criteria_remove_unmatched",reason:"exact_match_not_found",field:"acceptance_criteria",unmatched:mutation.unmatchedRemovals});const after=mutation.criteria.join("; ");if(!areValuesEqual(typeof before=="string"?before:"",after))return{field:"acceptance_criteria",before,after}},appendScalarMutationPlans=(changes,row,update)=>{for(const[optionKey,itemKey]of Object.entries(UPDATE_OPTION_TO_ITEM_KEY)){const candidate=update[optionKey];if(candidate===void 0)continue;const before=row[itemKey],after=toComparablePreviewValue(optionKey,candidate);areValuesEqual(before,after)||changes.push({field:itemKey,before,after})}},appendRuntimeFieldMutationPlans=(changes,row,update,runtimeFieldRegistry)=>{const runtimeFieldUpdates=collectRuntimeUpdateFieldValues(update,runtimeFieldRegistry,["update_many"]);for(const[field,after]of Object.entries(runtimeFieldUpdates)){const before=row[field];areValuesEqual(before,after)||changes.push({field,before,after})}},appendUnsetMutationPlans=(changes,row,unset,runtimeFieldRegistry)=>{if(unset===void 0||unset.length===0)return;const fields=assertUpdateUnsetFieldsSupported(unset,runtimeFieldRegistry,collectRegisteredItemFieldNames(getActiveExtensionRegistrations()),["update_many"]);for(const field of fields){const before=row[field];before!==void 0&&changes.push({field,before,after:null})}},buildPlannedItemDiff=(item,runtimeFieldRegistry,update={})=>{const row=toItemRecord(item),changes=[],tagPlan=buildTagMutationPlan(row,update);if(tagPlan&&changes.push(tagPlan),appendScalarMutationPlans(changes,row,update),updateArrayValueCount(update.addAc)>0||updateArrayValueCount(update.removeAc)>0){const acceptanceCriteriaChange=buildAcceptanceCriteriaMutationPlan(row,update);acceptanceCriteriaChange!==void 0&&changes.push(acceptanceCriteriaChange)}return changes.push(...buildCollectionMutationPlans(row,update)),appendRuntimeFieldMutationPlans(changes,row,update,runtimeFieldRegistry),appendUnsetMutationPlans(changes,row,update.unset,runtimeFieldRegistry),{id:item.id,changes}},normalizeStatusFilter=(value,statusRegistry)=>parseStatusFilterCsv(value,statusRegistry,{strict:!0,flagLabel:"--filter-status"}),rejectBlankIdsFilter=list=>{if(list?.ids!=null&&String(list.ids).trim().length===0)throw new PmCliError("--ids requires at least one non-empty item ID",EXIT_CODE.USAGE)},PLANNED_UPDATE_VALIDATORS=[({update})=>{update.priority!==void 0&&resolvePriority(update.priority)},({update,settings})=>{update.unset!==void 0&&update.unset.length>0&&assertUpdateUnsetFieldsSupported(update.unset,resolveRuntimeFieldRegistry(settings.schema),collectRegisteredItemFieldNames(getActiveExtensionRegistrations()),["update_many"])},({update,settings,pmRoot})=>{if(update.type===void 0)return;const typeRegistry=resolveItemTypeRegistry(settings,getActiveExtensionRegistrations());if(!resolveTypeName(update.type,typeRegistry))throw new PmCliError(buildInvalidTypeError(update.type,typeRegistry.types,resolveItemTypesFilePath(pmRoot,settings.schema)),EXIT_CODE.USAGE)},({update,statusRegistry})=>{if(update.status!==void 0&&!normalizeStatusInput(update.status,statusRegistry)){const allowedStatuses=statusRegistry.definitions.map(definition=>definition.id);throw new PmCliError(`Invalid status value "${update.status}". Allowed: ${allowedStatuses.join(", ")}`,EXIT_CODE.USAGE,{code:"invalid_argument_value",field:"status",value:update.status,allowed:allowedStatuses})}},({update})=>{update.deadline!==void 0&&resolveIsoOrRelative(update.deadline,new Date,"deadline")},({update})=>{update.estimatedMinutes!==void 0&&parseOptionalNonNegativeInteger(update.estimatedMinutes,"estimated-minutes")}],assertPlannedUpdateValuesValid=(update,settings,statusRegistry,pmRoot)=>{const context={update,settings,statusRegistry,pmRoot};for(const validate of PLANNED_UPDATE_VALIDATORS)validate(context)},runUpdateManyRollback=async params=>{const checkpoint=await loadMutationCheckpoint(params.pmRoot,UPDATE_MANY_CHECKPOINT_SUBDIR,params.rollbackId,UPDATE_MANY_CHECKPOINT_SCHEMA_VERSION),restoreMessage=params.options.update?.message??`Rollback update-many checkpoint ${checkpoint.id}`,rollback=await rollbackMutationCheckpoint(params.pmRoot,checkpoint,{author:params.options.update?.author,message:restoreMessage,force:params.options.update?.force}),skipped=rollback.rows.filter(row=>row.status==="skipped").length;return{...derivePmBulkMutationEffect({applied:rollback.restored_ids.length,skipped,failed:rollback.failed_count,unmatched:0}),mode:"rollback",matched_count:checkpoint.items.length,dry_run:!1,rollback_checkpoint_id:checkpoint.id,checkpoint:{id:checkpoint.id,created_at:checkpoint.created_at,path:checkpoint.path,rollback_command:`pm update-many --rollback ${checkpoint.id}`},restored_count:rollback.restored_ids.length,skipped_count:skipped,failed_count:rollback.failed_count,rows:rollback.rows,ids:rollback.restored_ids}},writeUpdateManyCheckpoint=async params=>{const checkpointPayload={schema_version:UPDATE_MANY_CHECKPOINT_SCHEMA_VERSION,rollback_strategy:"guarded_inverse",id:params.checkpointId,created_at:params.nowValue,author:resolveAuthor(params.options.update?.author,"unknown"),status_filter:params.statusFilter??null,list_filters:params.filters,update_options:params.updateSummary,items:params.checkpointItems},checkpointPath=await writeMutationCheckpoint(params.pmRoot,UPDATE_MANY_CHECKPOINT_SUBDIR,params.checkpointId,checkpointPayload);return{id:params.checkpointId,created_at:params.nowValue,path:checkpointPath,rollback_command:`pm update-many --rollback ${params.checkpointId}`}},collectUnmatchedRequestedIds=(rawIds,existingItems)=>{if(rawIds==null)return;const requested=[...new Set(String(rawIds).split(",").map(entry=>entry.trim()).filter(entry=>entry.length>0))],existing=new Set(existingItems.map(item=>item.id));return requested.filter(id=>!existing.has(id))},buildUnmatchedIdFields=plan=>plan.unmatchedIds===void 0?{}:{unmatched_ids:plan.unmatchedIds,unmatched_count:plan.unmatchedIds.length},loadUpdateManyRuntimeContext=async global2=>{const pmRoot=resolvePmRoot(process.cwd(),global2.path);await assertInitializedTracker(pmRoot);const settings=await readSettings(pmRoot);return{pmRoot,settings,statusRegistry:resolveRuntimeStatusRegistry(settings.schema),runtimeFieldRegistry:resolveRuntimeFieldRegistry(settings.schema)}},runRequestedUpdateManyRollback=params=>{if(params.options.dryRun===!0)throw new PmCliError("--dry-run cannot be combined with --rollback",EXIT_CODE.USAGE);if(hasListFilters(params.options.list,params.options.status))throw new PmCliError("Rollback mode does not accept filter options",EXIT_CODE.USAGE);if(Object.keys(params.updateSummary).length>0)throw new PmCliError("Rollback mode does not accept update mutation flags",EXIT_CODE.USAGE);return runUpdateManyRollback({pmRoot:params.runtime.pmRoot,rollbackId:params.rollbackId,options:params.options,global:params.global})},buildUpdateManyPlan=async params=>{normalizeStatusFilter(params.options.status,params.runtime.statusRegistry);const listed=await runList(void 0,{...params.options.list,status:params.options.status,compact:void 0,brief:void 0,fields:void 0,includeBody:!0,full:!0},params.global),planned=listed.items.map(item=>{const diff=buildPlannedItemDiff(item,params.runtime.runtimeFieldRegistry,params.options.update);return params.dependencyWarnings.length===0?diff:{...diff,warnings:[...params.dependencyWarnings]}}),existenceItems=params.options.list?.ids==null?[]:(await runList(void 0,{ids:params.options.list.ids,noTruncate:!0,full:!0},params.global)).items;return{listed,planned,actionable:planned.filter(row=>row.changes.length>0),updateSummary:params.updateSummary,unmatchedIds:collectUnmatchedRequestedIds(params.options.list?.ids,existenceItems)}},buildUpdateManyDryRunResult=plan=>({outcome:"effect",exit_code:EXIT_CODE.SUCCESS,mode:"dry_run",matched_count:plan.listed.items.length,dry_run:!0,filters:plan.listed.filters,planned_update_options:plan.updateSummary,item_plans:plan.planned,ids:[],...buildUnmatchedIdFields(plan)}),buildUpdateManyNoopResult=plan=>({...derivePmBulkMutationEffect({applied:0,skipped:plan.listed.items.length,failed:0,unmatched:plan.unmatchedIds?.length??0}),mode:"apply",matched_count:plan.listed.items.length,dry_run:!1,filters:plan.listed.filters,planned_update_options:plan.updateSummary,updated_count:0,skipped_count:plan.listed.items.length,failed_count:0,rows:plan.planned.map(row=>({id:row.id,status:"skipped"})),ids:[],...buildUnmatchedIdFields(plan)}),applyUpdateManyRows=async params=>{const rows=[],updatedIds=[],updateMessage=params.options.update.message??`update-many apply ${params.checkpointId}`,actionableById=new Set(params.plans.filter(row=>row.changes.length>0).map(row=>row.id));for(const item of params.items){if(!actionableById.has(item.id)){rows.push({id:item.id,status:"skipped"});continue}try{const rowUpdateOptions=preserveMutationStdinTokenLiterals(transferMutationStdinTokenPolicy(params.options.update,{...params.options.update,message:updateMessage,runtimeFieldCommands:["update_many"]})),result=await withCheckpointMutation({pmRoot:params.pmRoot,itemId:item.id,checkpointId:params.checkpointId},()=>runUpdate(item.id,rowUpdateOptions,params.global));rows.push({id:item.id,status:"updated",changed_fields:result.changed_fields,warnings:result.warnings}),updatedIds.push(item.id)}catch(error){rows.push({id:item.id,status:"failed",error:toErrorMessage(error)})}}return{rows,updatedIds}},applyUpdateManyPlan=async params=>{if(params.plan.actionable.length===0)return buildUpdateManyNoopResult(params.plan);const nowValue=nowIso(),checkpointId=createCheckpointId(UPDATE_MANY_CHECKPOINT_SUBDIR,nowValue),actionableById=new Set(params.plan.actionable.map(row=>row.id)),checkpointItems=params.plan.listed.items.filter(item=>actionableById.has(item.id)).map(item=>({id:item.id,target_updated_at:item.updated_at})),checkpointInfo=params.options.checkpoint===!1?void 0:await writeUpdateManyCheckpoint({pmRoot:params.runtime.pmRoot,checkpointId,nowValue,options:params.options,statusFilter:params.options.status,filters:params.plan.listed.filters,updateSummary:params.plan.updateSummary,checkpointItems}),applied=await applyUpdateManyRows({pmRoot:params.runtime.pmRoot,items:params.plan.listed.items,plans:params.plan.planned,options:params.options,global:params.global,checkpointId}),countStatus=status=>applied.rows.filter(row=>row.status===status).length,updatedCount=countStatus("updated"),skippedCount=countStatus("skipped"),failedCount=countStatus("failed");return{...derivePmBulkMutationEffect({applied:updatedCount,skipped:skippedCount,failed:failedCount,unmatched:params.plan.unmatchedIds?.length??0}),mode:"apply",matched_count:params.plan.listed.items.length,dry_run:!1,filters:params.plan.listed.filters,planned_update_options:params.plan.updateSummary,...checkpointInfo?{checkpoint:checkpointInfo}:{},updated_count:updatedCount,skipped_count:skippedCount,failed_count:failedCount,rows:applied.rows,ids:applied.updatedIds,...buildUnmatchedIdFields(params.plan)}},runUpdateMany=async(options,global2)=>{const rollbackId=resolveCheckpointRollbackId(options.rollback),runtime=await loadUpdateManyRuntimeContext(global2);rollbackId===void 0&&(options={...options,update:preserveMutationStdinTokenLiterals(await resolveUpdateTransportOptions(options.update))});const updateSummary=sanitizeUpdateOptionsForSummary(options.update);if(rejectBlankIdsFilter(options.list),rollbackId!==void 0)return runRequestedUpdateManyRollback({runtime,rollbackId,options,global:global2,updateSummary});if(!hasAnyUpdateMutationInput(options.update)&&options.dryRun!==!0)throw new PmCliError(`No update-many mutation flags provided. Add at least one mutation flag (for example: ${UPDATE_MANY_MUTATION_FLAG_GUIDANCE}), or pass --dry-run for a filter-only preview.`,EXIT_CODE.USAGE);assertPlannedUpdateValuesValid(options.update,runtime.settings,runtime.statusRegistry,runtime.pmRoot),assertUpdateMutationOptionCombinations(options.update);const dependencyWarnings=await assertDependencyTargetsResolvable({pmRoot:runtime.pmRoot,dependencies:parseDependencyAdditions(options.update.dep,runtime.settings.id_prefix,nowIso(),resolveAuthor(options.update.author,runtime.settings.author_default)).additions,idPrefix:runtime.settings.id_prefix,itemFormat:runtime.settings.item_format,typeToFolder:resolveItemTypeRegistry(runtime.settings,getActiveExtensionRegistrations()).type_to_folder,allowUnresolved:options.update.allowUnresolvedDeps}),plan=await buildUpdateManyPlan({options,global:global2,runtime,updateSummary,dependencyWarnings});return await assertDependencyTargetsResolvable({pmRoot:runtime.pmRoot,dependencies:parseDependencyAdditions(options.update.dep,runtime.settings.id_prefix,nowIso(),resolveAuthor(options.update.author,runtime.settings.author_default)).additions,idPrefix:runtime.settings.id_prefix,itemFormat:runtime.settings.item_format,typeToFolder:resolveItemTypeRegistry(runtime.settings,getActiveExtensionRegistrations()).type_to_folder,allowUnresolved:options.update.allowUnresolvedDeps,holders:plan.listed.items.map(item=>({id:item.id,createdAt:item.created_at})),schema:runtime.settings.schema}),options.dryRun===!0?buildUpdateManyDryRunResult(plan):applyUpdateManyPlan({runtime,options,global:global2,plan})},_testOnlyUpdateManyCommand={assertPlannedUpdateValuesValid,buildCollectionMutationPlans,buildPlannedItemDiff,buildTagMutationPlan,collectionValueCount,hasAnyUpdateMutationInput,hasListFilters,normalizeStatusFilter,normalizeCollectionBeforeValue,normalizeExistingTags,rejectBlankIdsFilter,sanitizeUpdateOptionsForSummary,toComparablePreviewValue};export{_testOnlyUpdateManyCommand,runUpdateMany};
|
|
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]="06b0780c-92aa-5b21-a4bb-0abb54b74b2f")}catch{}})();import{itemRevisionConflictRowFields}from"./item-revision.js";import{assertInitializedTracker}from"../environment/tracker-preflight.js";import{withCheckpointMutation}from"../../core/checkpoint/mutation-context.js";import{resolveCheckpointRollbackId}from"../../core/checkpoint/mutation-checkpoint.js";import{rollbackMutationCheckpoint}from"./checkpoint-rollback.js";import{committedCloseDependentConflict}from"./close.js";import{createCheckpointId,loadMutationCheckpoint,writeMutationCheckpoint,toItemRecord,applyAcceptanceCriteriaMutations,applyTagRemovals,mergeAdditiveTags,parseOptionalNonNegativeInteger,parseTags,splitAcceptanceCriteria,resolvePriority,normalizeStatusInput,resolveItemTypeRegistry,resolveTypeName,collectRuntimeUpdateFieldValues,buildInvalidTypeError,resolveItemTypesFilePath,resolveRuntimeFieldRegistry,resolveRuntimeStatusRegistry,EXIT_CODE,PmCliError,toErrorMessage,stableValueEquals,nowIso,preserveMutationStdinTokenLiterals,transferMutationStdinTokenPolicy,resolveIsoOrRelative,getActiveExtensionRegistrations,collectRegisteredItemFieldNames,resolvePmRoot,readSettings,resolveAuthor}from"../runtime-primitives.js";import{assertDependencyTargetsResolvable}from"../dependency-flag-validation.js";import{parseStatusFilterCsv}from"../../core/item/status-filter.js";import{hasListFilters}from"../query/list-filter-shared.js";import{runList}from"../query/list.js";import{assertUpdateMutationOptionCombinations,assertUpdateUnsetFieldsSupported,parseDependencyAdditions,resolveUpdateTransportOptions,runUpdate}from"./update.js";import{derivePmBulkMutationEffect}from"../cli-contracts/command-exit-contracts.js";const UPDATE_MANY_CHECKPOINT_SCHEMA_VERSION=2,UPDATE_MANY_CHECKPOINT_SUBDIR="update-many",NON_MUTATION_UPDATE_OPTION_KEYS=new Set(["author","message","force","ownershipMetadataBypass","ownershipDependencyBypass","allowUnresolvedDeps"]),UPDATE_MANY_MUTATION_FLAG_GUIDANCE=["--status","--priority","--type","--tags","--description","--body","--deadline","--estimate","--assignee","--dep","--dep-remove","--comment","--note","--learning","--file","--test","--doc","--replace-deps","--replace-tests","--replace-files","--replace-docs","--unset","--clear-*"].join(", "),UPDATE_OPTION_TO_ITEM_KEY={title:"title",description:"description",body:"body",status:"status",closeReason:"close_reason",priority:"priority",type:"type",deadline:"deadline",estimatedMinutes:"estimated_minutes",acceptanceCriteria:"acceptance_criteria",definitionOfReady:"definition_of_ready",order:"order",goal:"goal",objective:"objective",value:"value",impact:"impact",outcome:"outcome",whyNow:"why_now",assignee:"assignee",parent:"parent",reviewer:"reviewer",risk:"risk",confidence:"confidence",sprint:"sprint",release:"release",blockedBy:"blocked_by",blockedReason:"blocked_reason",unblockNote:"unblock_note",reporter:"reporter",severity:"severity",environment:"environment",reproSteps:"repro_steps",resolution:"resolution",expectedResult:"expected_result",actualResult:"actual_result",affectedVersion:"affected_version",fixedVersion:"fixed_version",component:"component",regression:"regression",customerImpact:"customer_impact"},COLLECTION_MUTATION_PLAN_DEFINITIONS=[{field:"dependencies",addKey:"dep",removeKey:"depRemove",clearKey:"clearDeps",replaceKey:"replaceDeps"},{field:"comments",addKey:"comment",clearKey:"clearComments"},{field:"notes",addKey:"note",clearKey:"clearNotes"},{field:"learnings",addKey:"learning",clearKey:"clearLearnings"},{field:"files",addKey:"file",clearKey:"clearFiles",replaceKey:"replaceFiles"},{field:"tests",addKey:"test",clearKey:"clearTests",replaceKey:"replaceTests"},{field:"docs",addKey:"doc",clearKey:"clearDocs",replaceKey:"replaceDocs"},{field:"reminders",addKey:"reminder",clearKey:"clearReminders"},{field:"events",addKey:"event",clearKey:"clearEvents"},{field:"type_options",addKey:"typeOption",clearKey:"clearTypeOptions"}],sanitizeUpdateOptionsForSummary=options=>{const summary={};for(const[key,value]of Object.entries(options))NON_MUTATION_UPDATE_OPTION_KEYS.has(key)||value!==void 0&&(summary[key]=value);return summary},hasAnyUpdateMutationInput=options=>Object.keys(sanitizeUpdateOptionsForSummary(options)).length>0,trimPreviewValue=value=>typeof value=="string"?value.trim():value,normalizeIntegerPreviewValue=value=>{const trimmed=String(value).trim(),parsed=Number(trimmed);return Number.isInteger(parsed)?parsed:trimmed},normalizeNumericPreviewValue=value=>{const trimmed=String(value).trim(),parsed=Number(trimmed);return Number.isFinite(parsed)?parsed:trimmed},normalizeRegressionPreviewValue=value=>{const normalized=String(value).trim().toLowerCase();return new Map([["true",!0],["1",!0],["false",!1],["0",!1]]).get(normalized)??trimPreviewValue(value)},PREVIEW_VALUE_NORMALIZERS={priority:normalizeIntegerPreviewValue,estimatedMinutes:normalizeNumericPreviewValue,order:normalizeNumericPreviewValue,rank:normalizeNumericPreviewValue,regression:normalizeRegressionPreviewValue},toComparablePreviewValue=(optionKey,value)=>{if(value!==void 0)return(PREVIEW_VALUE_NORMALIZERS[optionKey]??trimPreviewValue)(value)},areValuesEqual=(left,right)=>stableValueEquals(left,right),normalizeCollectionBeforeValue=(field,value)=>value!==void 0?value:field==="type_options"?{}:[],collectionValueCount=(field,value)=>Array.isArray(value)?value.length:field==="type_options"&&value&&typeof value=="object"?Object.keys(value).length:0,resolveCollectionMutationOperation=params=>params.replace?"replace":params.clear?"clear_or_reset":params.removeCount>0?"merge_remove":"append",readOptionalUpdateValue=(update,key)=>key===void 0?void 0:update[key],updateArrayValueCount=value=>Array.isArray(value)?value.length:0,buildCollectionMutationPlan=(definition,row,update)=>{const addValues=update[definition.addKey],removeValues=readOptionalUpdateValue(update,definition.removeKey),addCount=updateArrayValueCount(addValues),removeCount=updateArrayValueCount(removeValues),clear=update[definition.clearKey]===!0,replace=readOptionalUpdateValue(update,definition.replaceKey)===!0;if(![clear,replace,addCount>0,removeCount>0].includes(!0))return;const before=normalizeCollectionBeforeValue(definition.field,row[definition.field]);return{field:definition.field,before,after:{operation:resolveCollectionMutationOperation({replace,clear,removeCount}),clear,replace,add_count:addCount,remove_count:removeCount,before_count:collectionValueCount(definition.field,before)}}},buildCollectionMutationPlans=(row,update)=>{const changes=[];for(const definition of COLLECTION_MUTATION_PLAN_DEFINITIONS){const plan=buildCollectionMutationPlan(definition,row,update);plan!==void 0&&changes.push(plan)}return changes},normalizeExistingTags=value=>Array.isArray(value)?value.filter(tag=>typeof tag=="string"):[],buildTagMutationPlan=(row,update)=>{if(![update.tags!==void 0,updateArrayValueCount(update.addTags)>0,updateArrayValueCount(update.removeTags)>0].includes(!0))return;const existing=normalizeExistingTags(row.tags),baseTags=update.tags===void 0?existing:parseTags(update.tags),withAdditions=mergeAdditiveTags(baseTags,update.addTags),after=applyTagRemovals(withAdditions,update.removeTags).slice().sort((a,b)=>a.localeCompare(b)),before=existing.slice().sort((a,b)=>a.localeCompare(b));if(!areValuesEqual(before,after))return{field:"tags",before:existing,after}},buildAcceptanceCriteriaMutationPlan=(row,update)=>{const before=row.acceptance_criteria,mutation=applyAcceptanceCriteriaMutations(splitAcceptanceCriteria(before),update.addAc,update.removeAc);if(mutation.unmatchedRemovals.length>0)throw new PmCliError(`Acceptance criteria removal did not match: ${mutation.unmatchedRemovals.join(", ")}`,EXIT_CODE.NOT_FOUND,{code:"acceptance_criteria_remove_unmatched",reason:"exact_match_not_found",field:"acceptance_criteria",unmatched:mutation.unmatchedRemovals});const after=mutation.criteria.join("; ");if(!areValuesEqual(typeof before=="string"?before:"",after))return{field:"acceptance_criteria",before,after}},appendScalarMutationPlans=(changes,row,update)=>{for(const[optionKey,itemKey]of Object.entries(UPDATE_OPTION_TO_ITEM_KEY)){const candidate=update[optionKey];if(candidate===void 0)continue;const before=row[itemKey],after=toComparablePreviewValue(optionKey,candidate);areValuesEqual(before,after)||changes.push({field:itemKey,before,after})}},appendRuntimeFieldMutationPlans=(changes,row,update,runtimeFieldRegistry)=>{const runtimeFieldUpdates=collectRuntimeUpdateFieldValues(update,runtimeFieldRegistry,["update_many"]);for(const[field,after]of Object.entries(runtimeFieldUpdates)){const before=row[field];areValuesEqual(before,after)||changes.push({field,before,after})}},appendUnsetMutationPlans=(changes,row,unset,runtimeFieldRegistry)=>{if(unset===void 0||unset.length===0)return;const fields=assertUpdateUnsetFieldsSupported(unset,runtimeFieldRegistry,collectRegisteredItemFieldNames(getActiveExtensionRegistrations()),["update_many"]);for(const field of fields){const before=row[field];before!==void 0&&changes.push({field,before,after:null})}},buildPlannedItemDiff=(item,runtimeFieldRegistry,update={})=>{const row=toItemRecord(item),changes=[],tagPlan=buildTagMutationPlan(row,update);if(tagPlan&&changes.push(tagPlan),appendScalarMutationPlans(changes,row,update),updateArrayValueCount(update.addAc)>0||updateArrayValueCount(update.removeAc)>0){const acceptanceCriteriaChange=buildAcceptanceCriteriaMutationPlan(row,update);acceptanceCriteriaChange!==void 0&&changes.push(acceptanceCriteriaChange)}return changes.push(...buildCollectionMutationPlans(row,update)),appendRuntimeFieldMutationPlans(changes,row,update,runtimeFieldRegistry),appendUnsetMutationPlans(changes,row,update.unset,runtimeFieldRegistry),{id:item.id,changes}},normalizeStatusFilter=(value,statusRegistry)=>parseStatusFilterCsv(value,statusRegistry,{strict:!0,flagLabel:"--filter-status"}),rejectBlankIdsFilter=list=>{if(list?.ids!=null&&String(list.ids).trim().length===0)throw new PmCliError("--ids requires at least one non-empty item ID",EXIT_CODE.USAGE)},PLANNED_UPDATE_VALIDATORS=[({update})=>{update.priority!==void 0&&resolvePriority(update.priority)},({update,settings})=>{update.unset!==void 0&&update.unset.length>0&&assertUpdateUnsetFieldsSupported(update.unset,resolveRuntimeFieldRegistry(settings.schema),collectRegisteredItemFieldNames(getActiveExtensionRegistrations()),["update_many"])},({update,settings,pmRoot})=>{if(update.type===void 0)return;const typeRegistry=resolveItemTypeRegistry(settings,getActiveExtensionRegistrations());if(!resolveTypeName(update.type,typeRegistry))throw new PmCliError(buildInvalidTypeError(update.type,typeRegistry.types,resolveItemTypesFilePath(pmRoot,settings.schema)),EXIT_CODE.USAGE)},({update,statusRegistry})=>{if(update.status!==void 0&&!normalizeStatusInput(update.status,statusRegistry)){const allowedStatuses=statusRegistry.definitions.map(definition=>definition.id);throw new PmCliError(`Invalid status value "${update.status}". Allowed: ${allowedStatuses.join(", ")}`,EXIT_CODE.USAGE,{code:"invalid_argument_value",field:"status",value:update.status,allowed:allowedStatuses})}},({update})=>{update.deadline!==void 0&&resolveIsoOrRelative(update.deadline,new Date,"deadline")},({update})=>{update.estimatedMinutes!==void 0&&parseOptionalNonNegativeInteger(update.estimatedMinutes,"estimated-minutes")}],assertPlannedUpdateValuesValid=(update,settings,statusRegistry,pmRoot)=>{const context={update,settings,statusRegistry,pmRoot};for(const validate of PLANNED_UPDATE_VALIDATORS)validate(context)},runUpdateManyRollback=async params=>{const checkpoint=await loadMutationCheckpoint(params.pmRoot,UPDATE_MANY_CHECKPOINT_SUBDIR,params.rollbackId,UPDATE_MANY_CHECKPOINT_SCHEMA_VERSION),restoreMessage=params.options.update?.message??`Rollback update-many checkpoint ${checkpoint.id}`,rollback=await rollbackMutationCheckpoint(params.pmRoot,checkpoint,{author:params.options.update?.author,message:restoreMessage,force:params.options.update?.force}),skipped=rollback.rows.filter(row=>row.status==="skipped").length;return{...derivePmBulkMutationEffect({applied:rollback.restored_ids.length,skipped,failed:rollback.failed_count,unmatched:0}),mode:"rollback",matched_count:checkpoint.items.length,dry_run:!1,rollback_checkpoint_id:checkpoint.id,checkpoint:{id:checkpoint.id,created_at:checkpoint.created_at,path:checkpoint.path,rollback_command:`pm update-many --rollback ${checkpoint.id}`},restored_count:rollback.restored_ids.length,skipped_count:skipped,failed_count:rollback.failed_count,rows:rollback.rows,ids:rollback.restored_ids}},writeUpdateManyCheckpoint=async params=>{const checkpointPayload={schema_version:UPDATE_MANY_CHECKPOINT_SCHEMA_VERSION,rollback_strategy:"guarded_inverse",id:params.checkpointId,created_at:params.nowValue,author:resolveAuthor(params.options.update?.author,"unknown"),status_filter:params.statusFilter??null,list_filters:params.filters,update_options:params.updateSummary,items:params.checkpointItems},checkpointPath=await writeMutationCheckpoint(params.pmRoot,UPDATE_MANY_CHECKPOINT_SUBDIR,params.checkpointId,checkpointPayload);return{id:params.checkpointId,created_at:params.nowValue,path:checkpointPath,rollback_command:`pm update-many --rollback ${params.checkpointId}`}},collectUnmatchedRequestedIds=(rawIds,existingItems)=>{if(rawIds==null)return;const requested=[...new Set(String(rawIds).split(",").map(entry=>entry.trim()).filter(entry=>entry.length>0))],existing=new Set(existingItems.map(item=>item.id));return requested.filter(id=>!existing.has(id))},buildUnmatchedIdFields=plan=>plan.unmatchedIds===void 0?{}:{unmatched_ids:plan.unmatchedIds,unmatched_count:plan.unmatchedIds.length},loadUpdateManyRuntimeContext=async global2=>{const pmRoot=resolvePmRoot(process.cwd(),global2.path);await assertInitializedTracker(pmRoot);const settings=await readSettings(pmRoot);return{pmRoot,settings,statusRegistry:resolveRuntimeStatusRegistry(settings.schema),runtimeFieldRegistry:resolveRuntimeFieldRegistry(settings.schema)}},runRequestedUpdateManyRollback=params=>{if(params.options.dryRun===!0)throw new PmCliError("--dry-run cannot be combined with --rollback",EXIT_CODE.USAGE);if(hasListFilters(params.options.list,params.options.status))throw new PmCliError("Rollback mode does not accept filter options",EXIT_CODE.USAGE);if(Object.keys(params.updateSummary).length>0)throw new PmCliError("Rollback mode does not accept update mutation flags",EXIT_CODE.USAGE);return runUpdateManyRollback({pmRoot:params.runtime.pmRoot,rollbackId:params.rollbackId,options:params.options,global:params.global})},buildUpdateManyPlan=async params=>{normalizeStatusFilter(params.options.status,params.runtime.statusRegistry);const listed=await runList(void 0,{...params.options.list,status:params.options.status,compact:void 0,brief:void 0,fields:void 0,includeBody:!0,full:!0},params.global),planned=listed.items.map(item=>{const diff=buildPlannedItemDiff(item,params.runtime.runtimeFieldRegistry,params.options.update);return params.dependencyWarnings.length===0?diff:{...diff,warnings:[...params.dependencyWarnings]}}),existenceItems=params.options.list?.ids==null?[]:(await runList(void 0,{ids:params.options.list.ids,noTruncate:!0,full:!0},params.global)).items;return{listed,planned,actionable:planned.filter(row=>row.changes.length>0),updateSummary:params.updateSummary,unmatchedIds:collectUnmatchedRequestedIds(params.options.list?.ids,existenceItems)}},buildUpdateManyDryRunResult=plan=>({outcome:"effect",exit_code:EXIT_CODE.SUCCESS,mode:"dry_run",matched_count:plan.listed.items.length,dry_run:!0,filters:plan.listed.filters,planned_update_options:plan.updateSummary,item_plans:plan.planned,ids:[],...buildUnmatchedIdFields(plan)}),buildUpdateManyNoopResult=plan=>({...derivePmBulkMutationEffect({applied:0,skipped:plan.listed.items.length,failed:0,unmatched:plan.unmatchedIds?.length??0}),mode:"apply",matched_count:plan.listed.items.length,dry_run:!1,filters:plan.listed.filters,planned_update_options:plan.updateSummary,updated_count:0,skipped_count:plan.listed.items.length,failed_count:0,rows:plan.planned.map(row=>({id:row.id,status:"skipped"})),ids:[],...buildUnmatchedIdFields(plan)}),applyUpdateManyRows=async params=>{const rows=[],updatedIds=[],updateMessage=params.options.update.message??`update-many apply ${params.checkpointId}`,actionableById=new Set(params.plans.filter(row=>row.changes.length>0).map(row=>row.id));for(const item of params.items){if(!actionableById.has(item.id)){rows.push({id:item.id,status:"skipped"});continue}try{const rowUpdateOptions=preserveMutationStdinTokenLiterals(transferMutationStdinTokenPolicy(params.options.update,{...params.options.update,message:updateMessage,runtimeFieldCommands:["update_many"]})),result=await withCheckpointMutation({pmRoot:params.pmRoot,itemId:item.id,checkpointId:params.checkpointId},()=>runUpdate(item.id,rowUpdateOptions,params.global));rows.push({id:item.id,status:"updated",changed_fields:result.changed_fields,warnings:result.warnings}),updatedIds.push(item.id)}catch(error){const committed=await committedCloseDependentConflict(error,item.id,params.pmRoot);if(committed!==void 0){rows.push({id:item.id,status:"updated",...committed}),updatedIds.push(item.id);continue}rows.push({id:item.id,status:"failed",error:toErrorMessage(error),...await itemRevisionConflictRowFields(error,params.pmRoot)})}}return{rows,updatedIds}},applyUpdateManyPlan=async params=>{if(params.plan.actionable.length===0)return buildUpdateManyNoopResult(params.plan);const nowValue=nowIso(),checkpointId=createCheckpointId(UPDATE_MANY_CHECKPOINT_SUBDIR,nowValue),actionableById=new Set(params.plan.actionable.map(row=>row.id)),checkpointItems=params.plan.listed.items.filter(item=>actionableById.has(item.id)).map(item=>({id:item.id,target_updated_at:item.updated_at})),checkpointInfo=params.options.checkpoint===!1?void 0:await writeUpdateManyCheckpoint({pmRoot:params.runtime.pmRoot,checkpointId,nowValue,options:params.options,statusFilter:params.options.status,filters:params.plan.listed.filters,updateSummary:params.plan.updateSummary,checkpointItems}),applied=await applyUpdateManyRows({pmRoot:params.runtime.pmRoot,items:params.plan.listed.items,plans:params.plan.planned,options:params.options,global:params.global,checkpointId}),countStatus=status=>applied.rows.filter(row=>row.status===status).length,updatedCount=countStatus("updated"),skippedCount=countStatus("skipped"),failedCount=countStatus("failed");return{...derivePmBulkMutationEffect({applied:updatedCount,skipped:skippedCount,failed:failedCount,unmatched:params.plan.unmatchedIds?.length??0}),mode:"apply",matched_count:params.plan.listed.items.length,dry_run:!1,filters:params.plan.listed.filters,planned_update_options:params.plan.updateSummary,...checkpointInfo?{checkpoint:checkpointInfo}:{},updated_count:updatedCount,skipped_count:skippedCount,failed_count:failedCount,rows:applied.rows,ids:applied.updatedIds,...buildUnmatchedIdFields(params.plan)}},runUpdateMany=async(options,global2)=>{const rollbackId=resolveCheckpointRollbackId(options.rollback),runtime=await loadUpdateManyRuntimeContext(global2);rollbackId===void 0&&(options={...options,update:preserveMutationStdinTokenLiterals(await resolveUpdateTransportOptions(options.update))});const updateSummary=sanitizeUpdateOptionsForSummary(options.update);if(rejectBlankIdsFilter(options.list),rollbackId!==void 0)return runRequestedUpdateManyRollback({runtime,rollbackId,options,global:global2,updateSummary});if(!hasAnyUpdateMutationInput(options.update)&&options.dryRun!==!0)throw new PmCliError(`No update-many mutation flags provided. Add at least one mutation flag (for example: ${UPDATE_MANY_MUTATION_FLAG_GUIDANCE}), or pass --dry-run for a filter-only preview.`,EXIT_CODE.USAGE);assertPlannedUpdateValuesValid(options.update,runtime.settings,runtime.statusRegistry,runtime.pmRoot),assertUpdateMutationOptionCombinations(options.update);const dependencyWarnings=await assertDependencyTargetsResolvable({pmRoot:runtime.pmRoot,dependencies:parseDependencyAdditions(options.update.dep,runtime.settings.id_prefix,nowIso(),resolveAuthor(options.update.author,runtime.settings.author_default)).additions,idPrefix:runtime.settings.id_prefix,itemFormat:runtime.settings.item_format,typeToFolder:resolveItemTypeRegistry(runtime.settings,getActiveExtensionRegistrations()).type_to_folder,allowUnresolved:options.update.allowUnresolvedDeps}),plan=await buildUpdateManyPlan({options,global:global2,runtime,updateSummary,dependencyWarnings});return await assertDependencyTargetsResolvable({pmRoot:runtime.pmRoot,dependencies:parseDependencyAdditions(options.update.dep,runtime.settings.id_prefix,nowIso(),resolveAuthor(options.update.author,runtime.settings.author_default)).additions,idPrefix:runtime.settings.id_prefix,itemFormat:runtime.settings.item_format,typeToFolder:resolveItemTypeRegistry(runtime.settings,getActiveExtensionRegistrations()).type_to_folder,allowUnresolved:options.update.allowUnresolvedDeps,holders:plan.listed.items.map(item=>({id:item.id,createdAt:item.created_at})),schema:runtime.settings.schema}),options.dryRun===!0?buildUpdateManyDryRunResult(plan):applyUpdateManyPlan({runtime,options,global:global2,plan})},_testOnlyUpdateManyCommand={assertPlannedUpdateValuesValid,buildCollectionMutationPlans,buildPlannedItemDiff,buildTagMutationPlan,collectionValueCount,hasAnyUpdateMutationInput,hasListFilters,normalizeStatusFilter,normalizeCollectionBeforeValue,normalizeExistingTags,rejectBlankIdsFilter,sanitizeUpdateOptionsForSummary,toComparablePreviewValue};export{_testOnlyUpdateManyCommand,runUpdateMany};
|
|
2
2
|
//# sourceMappingURL=update-many.js.map
|