@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]="9261d364-cb6c-5442-9ad0-41d0aefa8184")}catch{}})();import crypto from"node:crypto";import{ITEM_MUTATION_FACET_OPTION_MAPPINGS}from"./lifecycle/mutation-facet-contracts.js";import{normalizeItemId,normalizePrefix}from"../core/item/id.js";import{preserveMutationStdinTokenFields,preserveMutationStdinTokenLiterals}from"../core/item/parse.js";import{preserveLiteralMutationInputFields}from"../core/item/literal-mutation-input.js";import{EXIT_CODE}from"../core/shared/constants.js";import{PmCliError}from"../core/shared/errors.js";import{levenshteinDistanceWithinLimit}from"../core/shared/levenshtein.js";import{describeUnknownError}from"./error-runtime.js";import{CLOSE_FLAG_CONTRACTS,RELEASE_FLAG_CONTRACTS}from"./cli-contracts/flag-contracts.js";import{CREATE_COMMANDER_OPTION_REGISTRATION_CONTRACTS,UPDATE_COMMANDER_OPTION_REGISTRATION_CONTRACTS}from"./cli-contracts/commander-mutation-options.js";import{runtimeFieldOptionTarget}from"../core/schema/runtime-schema.js";import{CORE_ITEM_FIELD_KEYS as ITEM_FIELD_KEYS,resolveCoreUpsertMetadataKey}from"./lifecycle/core-item-fields.js";import{readRuntimeFieldOptionValue}from"../core/schema/runtime-field-values.js";import{assertMutationOptionValueTypes}from"./lifecycle/mutation-option-validation.js";const MUTATION_ROW_KEYS=["op","id","reason","options","unique","match"],REFERENCED_MUTATION_ROW_KEYS=[...MUTATION_ROW_KEYS,"ref"],DOCUMENT_MUTATION_ROW_KEYS=[...REFERENCED_MUTATION_ROW_KEYS,"item"],DERIVED_TRANSACTION_ID_PREFIX="mutate-",SYNTHESIZED_REFERENCE_PREFIX="row-",MUTATION_DOCUMENT_KEYS=["schema_version","mutations"],MUTATION_REFERENCE_SOURCE="[a-z][a-z0-9._-]{0,63}",MUTATION_REFERENCE_PATTERN=new RegExp(`^${MUTATION_REFERENCE_SOURCE}$`,"u"),MUTATION_DEPENDENCY_REFERENCE_PATTERN=new RegExp(`(^|,)id=@(${MUTATION_REFERENCE_SOURCE})(?=,|$)`,"gu"),flagToOptionKey=flag=>flag.slice(2).replaceAll(/-([a-z])/gu,(_match,letter)=>letter.toUpperCase()),ITEM_ENVELOPE_KEYS=["item","linked","schedule","claim_state","children","omission_receipt","row_contract","context_intent"],READ_ONLY_ITEM_KEYS=new Set(["created_at","updated_at","closed_at","version","format_version","path","author","collection_counts","notes_count","tests_count"]),UPDATE_READ_ONLY_ITEM_KEYS=new Set([...READ_ONLY_ITEM_KEYS,"completed_at","id"]),MUTATION_OPTION_KEYS={create:CREATE_COMMANDER_OPTION_REGISTRATION_CONTRACTS.map(contract=>contract.target),update:UPDATE_COMMANDER_OPTION_REGISTRATION_CONTRACTS.map(contract=>contract.target),close:CLOSE_FLAG_CONTRACTS.map(contract=>flagToOptionKey(contract.flag)).filter(key=>key!=="reason"),release:RELEASE_FLAG_CONTRACTS.map(contract=>flagToOptionKey(contract.flag))};function deriveMutationTransactionId(input){const digest=crypto.createHash("sha256").update(input).digest("hex");return`${DERIVED_TRANSACTION_ID_PREFIX}${digest.slice(0,24)}`}function parseAtomicMutationControls(value){const controls={},compensation=value.createCompensation;if(compensation!==void 0){if(compensation!=="close"&&compensation!=="delete")throw new PmCliError("createCompensation must be close or delete.",EXIT_CODE.USAGE);controls.createCompensation=compensation}for(const key of["lockTtlSeconds","lockWaitMs"]){const raw=value[key];if(raw===void 0)continue;const parsed=typeof raw=="number"||typeof raw=="string"&&raw.trim().length>0?Number(raw):Number.NaN;if(!Number.isSafeInteger(parsed)||parsed<=0)throw new PmCliError(`${key} must be a positive safe integer.`,EXIT_CODE.USAGE);controls[key]=parsed}return controls}function isPlainObject(value){if(typeof value!="object"||value===null||Array.isArray(value))return!1;const prototype=Object.getPrototypeOf(value);return prototype===Object.prototype||prototype===null}function nearestKey(key,candidates){const limit=Math.max(1,Math.min(3,Math.floor(key.length/4)+1));let best;for(const candidate of candidates){const distance=levenshteinDistanceWithinLimit(key,candidate,limit);distance!==null&&(best===void 0||distance<best.distance)&&(best={key:candidate,distance})}return best?.key}function unknownKeyError(scope,key,candidates){const suggestion=nearestKey(key,candidates);return new PmCliError(suggestion===void 0?`${scope} does not recognize key "${key}". Allowed keys: ${candidates.join(", ")}.`:`${scope} does not recognize key "${key}". Did you mean "${suggestion}"?`,EXIT_CODE.USAGE,{code:"stdin_json_unknown_key",required:`Use documented ${scope} keys only.`,why:"Strict structured input prevents silent typos from creating incomplete or unintended mutations.",nextSteps:[suggestion===void 0?`Remove or correct "${key}".`:`Replace "${key}" with "${suggestion}".`]})}function parseJsonValue(input,label){try{return JSON.parse(input)}catch(error){const detail=error instanceof Error?error.message:String(error);throw new PmCliError(`${label} must be valid JSON: ${detail}`,EXIT_CODE.USAGE,{code:"stdin_json_invalid",required:`Pipe one valid JSON ${label.toLowerCase()} into stdin.`,why:"Structured mutation input must parse deterministically before any tracker write begins."})}}function validateMutationObject(value,index){if(!isPlainObject(value))throw new PmCliError(`Mutation ${index+1} must be an object.`,EXIT_CODE.USAGE);for(const key of Object.keys(value))if(!MUTATION_ROW_KEYS.includes(key))throw unknownKeyError(`mutation ${index+1}`,key,MUTATION_ROW_KEYS);return value}function validateMutationOptions(options,op,index,runtimeFields){if(options===void 0)return;if(!isPlainObject(options))throw new PmCliError(`Mutation ${index+1} options must be an object.`,EXIT_CODE.USAGE);const allowed=[...MUTATION_OPTION_KEYS[op],...runtimeFieldOptionKeys(op,runtimeFields)];for(const key of Object.keys(options))if(!allowed.includes(key))throw unknownKeyError(`mutation ${index+1} ${op} options`,key,allowed);return assertMutationOptionValueTypes(options,op,index),options}function validateMutationKeyGuard(value,op,index,field){if(value===void 0)return;const allowedOp=field==="unique"?"create":"update";if(op!==allowedOp)throw new PmCliError(`Mutation ${index+1} ${field} is only valid for ${allowedOp} operations.`,EXIT_CODE.USAGE);const key=isPlainObject(value)&&typeof value.key=="string"?value.key.trim():"";if(!isPlainObject(value)||key.length===0||value.value===void 0||value.value===null)throw new PmCliError(`Mutation ${index+1} ${field} must be an object with a non-empty key and a value.`,EXIT_CODE.USAGE);return{key,value:value.value}}function validateMutationRow(value,index,runtimeFields){const mutation=validateMutationObject(value,index),{op,id,reason}=mutation;if(op!=="create"&&op!=="update"&&op!=="close"&&op!=="release")throw new PmCliError(`Mutation ${index+1} op must be create, update, close, or release.`,EXIT_CODE.USAGE);const options=validateMutationOptions(mutation.options,op,index,runtimeFields),unique=validateMutationKeyGuard(mutation.unique,op,index,"unique"),match=validateMutationKeyGuard(mutation.match,op,index,"match");if(typeof id!="string"||id.trim().length===0)throw new PmCliError(`Mutation ${index+1} requires a non-empty id.`,EXIT_CODE.USAGE);const normalizedId=id.trim();if(op==="close"){if(typeof reason!="string"||reason.trim().length===0)throw new PmCliError(`Mutation ${index+1} close requires a non-empty reason.`,EXIT_CODE.USAGE);return{op,id:normalizedId,reason:reason.trim(),...options===void 0?{}:{options}}}if(op==="release")return{op,id:normalizedId,...options===void 0?{}:{options}};if(!isPlainObject(options))throw new PmCliError(`Mutation ${index+1} ${op} requires an options object.`,EXIT_CODE.USAGE);return{op,id:normalizedId,options,...unique===void 0?{}:{unique},...match===void 0?{}:{match}}}function parseItemMutationBatch(input,parseOptions={}){const parsed=parseJsonValue(input,"Mutation batch"),rows=Array.isArray(parsed)?parsed:isPlainObject(parsed)&&Array.isArray(parsed.mutations)?parsed.mutations:void 0;if(rows===void 0||rows.length===0)throw new PmCliError("Mutation batch must be a non-empty JSON array or an object with a non-empty mutations array.",EXIT_CODE.USAGE);const runtimeFields=parseOptions.runtimeFields??[];return rows.map((row,index)=>{const mutation=validateMutationRow(row,index,runtimeFields);return mutation.options!==void 0&&preserveMutationStdinTokenLiterals(mutation.options),mutation})}function deriveReferencedItemId(transactionId,reference,idPrefix){const digest=crypto.createHash("sha256").update(transactionId).update("\0").update(reference).digest("hex"),token=BigInt(`0x${digest.slice(0,16)}`).toString(36).padStart(13,"0").slice(-12);return`${normalizePrefix(idPrefix)}${token}`}function replaceExactMutationReference(value,references){if(typeof value!="string"||!value.startsWith("@"))return value;const reference=value.slice(1),resolved=references[reference];if(resolved===void 0)throw new PmCliError(`Unknown mutation reference "${value}".`,EXIT_CODE.USAGE);return resolved}function replaceDependencyReference(value,references){if(typeof value!="string")return value;const resolvedValue=value.replace(MUTATION_DEPENDENCY_REFERENCE_PATTERN,(_match,prefix,reference)=>{const resolved=references[reference];if(resolved===void 0)throw new PmCliError(`Unknown mutation reference "@${reference}".`,EXIT_CODE.USAGE);return`${prefix}id=${resolved}`});if(/(^|,)id=@/u.test(resolvedValue))throw new PmCliError(`Malformed mutation reference in dependency "${value}".`,EXIT_CODE.USAGE);return resolvedValue}function referencedAliases(value){return typeof value!="string"?[]:value.startsWith("@")?[value.slice(1)]:[...value.matchAll(MUTATION_DEPENDENCY_REFERENCE_PATTERN)].map(match=>match[1]).filter(reference=>reference!==void 0)}function assertAcyclicCreateReferences(rows,inputReferences){const graph=new Map;for(const[index,row]of rows.entries()){const reference=inputReferences[index];if(reference===void 0)continue;const mutationOptions=isPlainObject(row.options)?row.options:{},dependencies=Array.isArray(mutationOptions.dep)?mutationOptions.dep:mutationOptions.dep===void 0?[]:[mutationOptions.dep];graph.set(reference,[mutationOptions.parent,mutationOptions.blockedBy,...dependencies].flatMap(referencedAliases))}const visiting=new Set,visited=new Set,visit=reference=>{if(visiting.has(reference))throw new PmCliError(`Mutation reference cycle includes "@${reference}".`,EXIT_CODE.USAGE);if(!visited.has(reference)){visiting.add(reference);for(const dependency of graph.get(reference))graph.has(dependency)&&visit(dependency);visiting.delete(reference),visited.add(reference)}};for(const reference of graph.keys())visit(reference)}function parseJsonLinesRows(input){const candidates=input.split(/\r?\n/u).map((line,index)=>({line:line.trim(),number:index+1})).filter(entry=>entry.line.length>0);if(candidates.length<2)return null;const parsed=candidates.map(entry=>{try{return{...entry,ok:!0,value:JSON.parse(entry.line)}}catch(error){return{...entry,ok:!1,error}}}),first=parsed[0];if(!first.ok&&(first.line.startsWith("[")||first.line==="{"||parsed.at(-1).line==="}")||!parsed.some(entry=>entry.ok&&isPlainObject(entry.value)))return null;const rows=[];for(const entry of parsed){if(!entry.ok)throw new PmCliError(`Mutation document line ${entry.number} must be one valid JSON object: ${describeUnknownError(entry.error)}`,EXIT_CODE.USAGE,{code:"stdin_json_invalid",required:"Provide one JSON object per line (JSON Lines), a JSON array, or a {schema_version, mutations} document.",why:"Structured mutation input must parse deterministically before any tracker write begins."});if(!isPlainObject(entry.value))throw new PmCliError(`Mutation document line ${entry.number} must be a JSON object.`,EXIT_CODE.USAGE);rows.push(entry.value)}return rows}function isMutationEnvelope(value){return Object.hasOwn(value,"mutations")||Object.hasOwn(value,"schema_version")}function parseMutationDocumentEnvelope(input){let parsed;try{parsed=parseJsonValue(input,"Mutation document")}catch(error){const rows=parseJsonLinesRows(input);if(rows===null)throw error;return{schema_version:1,mutations:rows}}return Array.isArray(parsed)?{schema_version:1,mutations:parsed}:isPlainObject(parsed)&&!isMutationEnvelope(parsed)?{schema_version:1,mutations:[parsed]}:parsed}function normalizeDocumentRow(row,index){const normalized=Object.hasOwn(row,"op")?validateOperationRow(row,index):normalizeCreateDocumentRow(row,index);if(normalized.op==="create"&&normalized.id===void 0&&isPlainObject(normalized.item)&&normalized.item.id!==void 0){if(typeof normalized.item.id!="string")throw new PmCliError(`Mutation ${index+1} item id must be a string.`,EXIT_CODE.USAGE);normalized.id=normalized.item.id}return normalized}function validateOperationRow(row,index){for(const key of Object.keys(row))if(!DOCUMENT_MUTATION_ROW_KEYS.includes(key))throw unknownKeyError(`mutation ${index+1}`,key,DOCUMENT_MUTATION_ROW_KEYS);if(row.item!==void 0&&row.op!=="create")throw new PmCliError(`Mutation ${index+1} item documents are only valid for create operations.`,EXIT_CODE.USAGE);if(row.item!==void 0&&row.options!==void 0)throw new PmCliError(`Mutation ${index+1} must declare either item or options, not both.`,EXIT_CODE.USAGE);return{...row}}function normalizeCreateDocumentRow(row,index){const{ref,id,item,...fields}=row,document=item!==void 0?item:fields;if(item!==void 0&&Object.keys(fields).length>0)throw new PmCliError(`Mutation ${index+1} must not mix item document keys with top-level fields.`,EXIT_CODE.USAGE);if(!isPlainObject(document))throw new PmCliError(`Mutation ${index+1} item document must be an object.`,EXIT_CODE.USAGE);return{op:"create",...ref===void 0?{}:{ref},...id===void 0?{}:{id},item:document}}function parseMutationDocumentRows(input){const envelope=parseMutationDocumentEnvelope(input);if(!isPlainObject(envelope))throw new PmCliError("Mutation document must be a JSON array or object.",EXIT_CODE.USAGE);for(const key of Object.keys(envelope))if(!MUTATION_DOCUMENT_KEYS.includes(key))throw unknownKeyError("mutation document",key,MUTATION_DOCUMENT_KEYS);if(envelope.schema_version!==void 0&&envelope.schema_version!==1)throw new PmCliError("Mutation document schema_version must be 1.",EXIT_CODE.USAGE);if(!Array.isArray(envelope.mutations)||envelope.mutations.length===0)throw new PmCliError("Mutation document requires a non-empty mutations array.",EXIT_CODE.USAGE);return envelope.mutations.map((row,index)=>{if(!isPlainObject(row))throw new PmCliError(`Mutation ${index+1} must be an object.`,EXIT_CODE.USAGE);return normalizeDocumentRow(row,index)})}function excludeOwnCreateIds(upsert,rows,options){const ownIds=new Set(Object.values(collectMutationReferences(rows,options,Object.create(null))));for(const row of rows)row.op==="create"&&typeof row.id=="string"&&row.id.trim().length>0&&ownIds.add(normalizeItemId(row.id,options.idPrefix));return{...upsert,existing:upsert.existing.filter(candidate=>!ownIds.has(candidate.id))}}function synthesizeMissingReferences(rows){const declared=new Set(rows.map(row=>row.ref).filter(ref=>typeof ref=="string"));for(const[index,row]of rows.entries()){if(row.op!=="create"||row.ref!==void 0||typeof row.id=="string"&&row.id.trim().length>0)continue;const synthesized=`${SYNTHESIZED_REFERENCE_PREFIX}${index+1}`;if(declared.has(synthesized))throw new PmCliError(`Mutation ${index+1} receives the synthesized ref "${synthesized}", but another row declares that ref. Rename it or give mutation ${index+1} its own ref; ${SYNTHESIZED_REFERENCE_PREFIX}<n> aliases are reserved for rows without one.`,EXIT_CODE.USAGE);row.ref=synthesized}}function itemDocumentToBatchOptions(item,mode,runtimeFields){const converted=itemDocumentToMutationOptions(JSON.stringify(item),mode,{},runtimeFields);return delete converted.stdinJson,delete converted.documentAnnotationSnapshot,converted}function runtimeFieldOptionKeys(op,runtimeFields){return op!=="create"&&op!=="update"?[]:runtimeFields.filter(definition=>definition.commands.includes(op)).map(definition=>runtimeFieldOptionTarget(definition))}function convertItemDocumentRows(rows,runtimeFields){for(const row of rows)isPlainObject(row.item)&&(row.options=itemDocumentToBatchOptions(row.item,"create",runtimeFields),delete row.item)}function itemDocumentMetadataKey(key,runtimeFields){return runtimeFields.some(field=>field.metadata_key===key)?key:resolveCoreUpsertMetadataKey(key)}function readUpsertKeyValue(row,key,runtimeFields){const camel=key.replaceAll(/_([a-z])/gu,(_match,letter)=>letter.toUpperCase());if(isPlainObject(row.item)){if(row.item[key]!==void 0)return row.item[key];if(itemDocumentMetadataKey(camel,runtimeFields)===key&&row.item[camel]!==void 0)return row.item[camel]}const options=isPlainObject(row.options)?row.options:{},declared=runtimeFields.find(field=>field.metadata_key===key);return declared!==void 0&&options[runtimeFieldOptionTarget(declared)]!==void 0?options[runtimeFieldOptionTarget(declared)]:options[key]!==void 0?options[key]:options[camel]!==void 0?options[camel]:(Array.isArray(options.field)?options.field:[]).map(String).find(entry=>entry.startsWith(`${key}=`))?.slice(key.length+1)}function compareCodeUnits(left,right){return left<right?-1:+(left!==right)}function canonicalizeUpsertValue(value){return Array.isArray(value)?value.map(canonicalizeUpsertValue):isPlainObject(value)?Object.fromEntries(Object.entries(value).sort(([left],[right])=>compareCodeUnits(left,right)).map(([key,nested])=>[key,canonicalizeUpsertValue(nested)])):value}function stableUpsertValue(value){return typeof value=="string"?value.trim():JSON.stringify(canonicalizeUpsertValue(value))}function applyKeyedUpsert(rows,upsert,idPrefix,runtimeFields,references){const key=upsert.key.trim();if(key.length===0)throw new PmCliError("Upsert key must be a non-empty field name.",EXIT_CODE.USAGE);const existingByValue=indexUpsertCandidates(upsert.existing),seen=new Map,upserted=new Set;for(const[index,row]of rows.entries()){if(row.op!=="create")continue;const stable=readStableUpsertKey(row,key,index,seen,runtimeFields),match=resolveUpsertMatch(existingByValue.get(stable)??[],key,stable);if(match===void 0){row.unique={key,value:readUpsertKeyValue(row,key,runtimeFields)};continue}const id=normalizeItemId(match,idPrefix);typeof row.ref=="string"&&(references[row.ref]=id),convertRowToUpsertUpdate(row,id,runtimeFields,{key,value:readUpsertKeyValue(row,key,runtimeFields)}),upserted.add(index)}return upserted}function indexUpsertCandidates(existing){const existingByValue=new Map;for(const candidate of existing){if(candidate.value===void 0||candidate.value===null)continue;const stable=stableUpsertValue(candidate.value);existingByValue.set(stable,[...existingByValue.get(stable)??[],candidate.id])}return existingByValue}function readStableUpsertKey(row,key,index,seen,runtimeFields){const value=readUpsertKeyValue(row,key,runtimeFields);if(value==null||value==="")throw new PmCliError(`Mutation ${index+1} is missing the upsert key "${key}".`,EXIT_CODE.USAGE,{code:"upsert_key_missing",required:`Every create record must carry a non-empty "${key}" value when --upsert-key ${key} is set.`,why:"Keyed upsert identifies existing items by that field; a record without it cannot be matched or safely created."});const stable=stableUpsertValue(value),previous=seen.get(stable);if(previous!==void 0)throw new PmCliError(`Mutations ${previous+1} and ${index+1} share the upsert key value ${JSON.stringify(stable)} for "${key}".`,EXIT_CODE.USAGE,{code:"upsert_key_duplicate",required:"Each record must carry a distinct upsert key value within one batch.",why:"Two records with the same key would race for the same existing item."});return seen.set(stable,index),stable}function resolveUpsertMatch(matches,key,stable){if(matches.length>1)throw new PmCliError(`Upsert key "${key}" value ${JSON.stringify(stable)} matches ${matches.length} existing items: ${matches.join(", ")}.`,EXIT_CODE.CONFLICT,{code:"upsert_key_ambiguous",required:"Resolve the duplicate existing items or choose a key that is unique per item.",why:"An ambiguous key cannot select one item to update without guessing."});return matches[0]}function convertRowToUpsertUpdate(row,id,runtimeFields,match){const updateKeys=new Set([...MUTATION_OPTION_KEYS.update,...runtimeFieldOptionKeys("update",runtimeFields)]),options=isPlainObject(row.item)?itemDocumentToBatchOptions(row.item,"update",runtimeFields):Object.fromEntries(Object.entries(row.options).filter(([optionKey])=>updateKeys.has(optionKey)));delete row.item,delete row.ref,row.op="update",row.id=id,row.options=options,row.match=match}function collectMutationReferences(rows,options,references){for(const[index,row]of rows.entries())if(row.ref!==void 0){if(row.op!=="create")throw new PmCliError(`Mutation ${index+1} ref is only valid for create operations.`,EXIT_CODE.USAGE);if(typeof row.ref!="string"||!MUTATION_REFERENCE_PATTERN.test(row.ref))throw new PmCliError(`Mutation ${index+1} ref must match ${MUTATION_REFERENCE_PATTERN.source}.`,EXIT_CODE.USAGE);if(references[row.ref]!==void 0)throw new PmCliError(`Duplicate mutation ref "${row.ref}".`,EXIT_CODE.USAGE);references[row.ref]=typeof row.id=="string"&&row.id.trim().length>0?normalizeItemId(row.id,options.idPrefix):deriveReferencedItemId(options.transactionId,row.ref,options.idPrefix)}return references}function resolveItemMutationDocument(input,options){const rows=parseMutationDocumentRows(input);synthesizeMissingReferences(rows);const inputReferences=rows.map(row=>typeof row.ref=="string"?row.ref:void 0),references=Object.create(null),upserted=options.upsert===void 0?new Set:applyKeyedUpsert(rows,excludeOwnCreateIds(options.upsert,rows,options),options.idPrefix,options.runtimeFields??[],references);convertItemDocumentRows(rows,options.runtimeFields??[]),collectMutationReferences(rows,options,references),assertAcyclicCreateReferences(rows,inputReferences);const resolvedRows=rows.map(row=>{const resolved={...row},reference=typeof resolved.ref=="string"?resolved.ref:void 0;if(delete resolved.ref,reference!==void 0?resolved.id=references[reference]:resolved.id=replaceExactMutationReference(resolved.id,references),isPlainObject(resolved.options)){const mutationOptions={...resolved.options};for(const key of["parent","blockedBy"])mutationOptions[key]!==void 0&&(mutationOptions[key]=replaceExactMutationReference(mutationOptions[key],references));Array.isArray(mutationOptions.dep)?mutationOptions.dep=mutationOptions.dep.map(entry=>replaceDependencyReference(entry,references)):mutationOptions.dep!==void 0&&(mutationOptions.dep=replaceDependencyReference(mutationOptions.dep,references)),resolved.options=mutationOptions}return resolved}),mutations=parseItemMutationBatch(JSON.stringify(resolvedRows),{runtimeFields:options.runtimeFields??[]}),createIds=mutations.filter(mutation=>mutation.op==="create").map(mutation=>mutation.id);if(new Set(createIds).size!==createIds.length)throw new PmCliError("Duplicate resolved create id in mutation document.",EXIT_CODE.USAGE);const receiptRows=mutations.map((mutation,index)=>{const reference=inputReferences[index];return{index,...typeof reference=="string"?{ref:reference}:{},id:mutation.id,op:mutation.op,...upserted.has(index)?{upserted:!0}:{}}});return{schema_version:1,mutations,references,rows:receiptRows}}function serializePairs(value,keys){return keys.filter(key=>value[key]!==void 0&&value[key]!==null).map(key=>{const rendered=String(value[key]);return`${key}=${/[",\n\r\\]/u.test(rendered)?JSON.stringify(rendered):rendered}`}).join(",")}function serializeObjectArray(value,keys,label){if(value!==void 0){if(!Array.isArray(value)||!value.every(isPlainObject))throw new PmCliError(`${label} must be an array of objects.`,EXIT_CODE.USAGE);return value.map(entry=>serializePairs(entry,keys))}}function scalarItemOption(key,value){return key==="tags"&&Array.isArray(value)&&value.every(entry=>typeof entry=="string")?JSON.stringify(value):typeof value=="number"||typeof value=="boolean"?String(value):value}function appendFacetOptions(target,source,skipPersistedAnnotations=!1){for(const[sourceKey,targetKey,keys]of ITEM_MUTATION_FACET_OPTION_MAPPINGS){const sourceValue=source[sourceKey],serialized=serializeObjectArray(skipPersistedAnnotations&&["comments","notes","learnings"].includes(sourceKey)&&Array.isArray(sourceValue)?sourceValue.filter(entry=>!isPlainObject(entry)||entry.created_at===void 0):sourceValue,keys,sourceKey);serialized!==void 0&&serialized.length>0&&(target[targetKey]=serialized)}}function validateItemDocumentKeys(parsed,envelope){for(const key of Object.keys(parsed)){if(envelope){if(!ITEM_ENVELOPE_KEYS.includes(key))throw unknownKeyError("item envelope",key,ITEM_ENVELOPE_KEYS);continue}if(!ITEM_FIELD_KEYS.has(key)&&nearestKey(key,[...ITEM_FIELD_KEYS])===void 0&&nearestKey(key,ITEM_ENVELOPE_KEYS)==="item")throw unknownKeyError("item envelope",key,ITEM_ENVELOPE_KEYS)}}function resolveItemDocument(parsed){const envelope=Object.hasOwn(parsed,"item");validateItemDocumentKeys(parsed,envelope);const item=envelope?parsed.item:parsed;if(!isPlainObject(item))throw new PmCliError("Item document item must be an object.",EXIT_CODE.USAGE);return{item,...envelope&&isPlainObject(parsed.linked)?{linked:parsed.linked}:{}}}function appendItemDocumentEntry(documentOptions,key,value,mode,runtimeFields,explicitOptions){if((mode==="update"?UPDATE_READ_ONLY_ITEM_KEYS:READ_ONLY_ITEM_KEYS).has(key))return;const definition=runtimeFields.find(field=>field.metadata_key===key&&field.commands.includes(mode));if(definition!==void 0){const explicitValue=readRuntimeFieldOptionValue(explicitOptions,definition);documentOptions[runtimeFieldOptionTarget(definition)]=explicitValue!==void 0?explicitValue:definition.type==="array"||definition.type==="object"?JSON.stringify(value):value;return}if(!ITEM_FIELD_KEYS.has(key)){if(nearestKey(key,[...ITEM_FIELD_KEYS])!==void 0)throw unknownKeyError("item document",key,[...ITEM_FIELD_KEYS]);const fields=Array.isArray(documentOptions.field)?documentOptions.field:[];fields.push(`${key}=${typeof value=="string"?value:JSON.stringify(value)}`),documentOptions.field=fields;return}["dependencies","comments","notes","learnings","files","tests","docs","reminders","events","type_options"].includes(key)||(documentOptions[key.replaceAll(/_([a-z])/gu,(_match,letter)=>letter.toUpperCase())]=scalarItemOption(key,value))}function explicitDocumentOptionKeys(explicitOptions,mode,runtimeFields){const explicitKeys=new Set(Object.entries(explicitOptions).filter(([,value])=>value!==void 0).map(([key])=>key)),contracts=mode==="create"?CREATE_COMMANDER_OPTION_REGISTRATION_CONTRACTS:UPDATE_COMMANDER_OPTION_REGISTRATION_CONTRACTS;for(const{target,keys}of contracts)keys.some(key=>explicitKeys.has(key))&&explicitKeys.add(target);for(const definition of runtimeFields)readRuntimeFieldOptionValue(explicitOptions,definition)!==void 0&&explicitKeys.add(runtimeFieldOptionTarget(definition));return explicitKeys}function removeOverriddenDocumentOptions(documentOptions,mode,explicitKeys){const contracts=mode==="create"?CREATE_COMMANDER_OPTION_REGISTRATION_CONTRACTS:UPDATE_COMMANDER_OPTION_REGISTRATION_CONTRACTS;for(const{target}of contracts)target!=="field"&&explicitKeys.has(target)&&delete documentOptions[target]}function itemDocumentToMutationOptions(input,mode,explicitOptions={},runtimeFields=[]){const parsed=parseJsonValue(input,"Item document");if(!isPlainObject(parsed))throw new PmCliError("Item document must be a JSON object.",EXIT_CODE.USAGE);const{item,linked}=resolveItemDocument(parsed),documentOptions={};for(const[key,value]of Object.entries(item)){const metadataKey=itemDocumentMetadataKey(key,runtimeFields);metadataKey!==key&&Object.hasOwn(item,metadataKey)||appendItemDocumentEntry(documentOptions,metadataKey,value,mode,runtimeFields,explicitOptions)}appendFacetOptions(documentOptions,item,mode==="update"),linked!==void 0&&appendFacetOptions(documentOptions,linked);const explicitKeys=explicitDocumentOptionKeys(explicitOptions,mode,runtimeFields);if(removeOverriddenDocumentOptions(documentOptions,mode,explicitKeys),mode==="update"){const annotations=Object.fromEntries(["comments","notes","learnings"].filter(key=>Object.hasOwn(item,key)&&!explicitKeys.has(key.slice(0,-1))).map(key=>[key,item[key].filter(entry=>isPlainObject(entry)&&entry.created_at!==void 0)]));Object.keys(annotations).length>0&&(documentOptions.documentAnnotationSnapshot=annotations)}const merged={...documentOptions,...Object.fromEntries(Object.entries(explicitOptions).filter(([,value])=>value!==void 0)),...Array.isArray(documentOptions.field)&&Array.isArray(explicitOptions.field)?{field:[...documentOptions.field,...explicitOptions.field]}:{},stdinJson:void 0},literalKeys=Object.keys(documentOptions).filter(key=>!explicitKeys.has(key));return preserveMutationStdinTokenFields(preserveLiteralMutationInputFields(merged,literalKeys),literalKeys)}function validateItemMutationRows(value){return parseItemMutationBatch(JSON.stringify(value))}export{deriveMutationTransactionId,itemDocumentToMutationOptions,parseAtomicMutationControls,parseItemMutationBatch,resolveItemMutationDocument,stableUpsertValue,validateItemMutationRows};
|
|
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]="a17df3f5-aee8-5734-b1ce-b643482be3a7")}catch{}})();import crypto from"node:crypto";import{ITEM_MUTATION_FACET_OPTION_MAPPINGS}from"./lifecycle/mutation-facet-contracts.js";import{normalizeItemId,normalizePrefix}from"../core/item/id.js";import{preserveMutationStdinTokenFields,preserveMutationStdinTokenLiterals}from"../core/item/parse.js";import{preserveLiteralMutationInputFields}from"../core/item/literal-mutation-input.js";import{EXIT_CODE}from"../core/shared/constants.js";import{PmCliError}from"../core/shared/errors.js";import{levenshteinDistanceWithinLimit}from"../core/shared/levenshtein.js";import{describeUnknownError}from"./error-runtime.js";import{CLOSE_FLAG_CONTRACTS,RELEASE_FLAG_CONTRACTS}from"./cli-contracts/flag-contracts.js";import{CREATE_COMMANDER_OPTION_REGISTRATION_CONTRACTS,UPDATE_COMMANDER_OPTION_REGISTRATION_CONTRACTS}from"./cli-contracts/commander-mutation-options.js";import{runtimeFieldOptionTarget}from"../core/schema/runtime-schema.js";import{CORE_ITEM_FIELD_KEYS as ITEM_FIELD_KEYS,resolveCoreUpsertMetadataKey}from"./lifecycle/core-item-fields.js";import{readRuntimeFieldOptionValue}from"../core/schema/runtime-field-values.js";import{assertMutationOptionValueTypes}from"./lifecycle/mutation-option-validation.js";const MUTATION_ROW_KEYS=["op","id","reason","options","unique","match"],REFERENCED_MUTATION_ROW_KEYS=[...MUTATION_ROW_KEYS,"ref"],DOCUMENT_MUTATION_ROW_KEYS=[...REFERENCED_MUTATION_ROW_KEYS,"item"],DERIVED_TRANSACTION_ID_PREFIX="mutate-",SYNTHESIZED_REFERENCE_PREFIX="row-",MUTATION_DOCUMENT_KEYS=["schema_version","mutations"],MUTATION_REFERENCE_SOURCE="[a-z][a-z0-9._-]{0,63}",MUTATION_REFERENCE_PATTERN=new RegExp(`^${MUTATION_REFERENCE_SOURCE}$`,"u"),MUTATION_DEPENDENCY_REFERENCE_PATTERN=new RegExp(`(^|,)id=@(${MUTATION_REFERENCE_SOURCE})(?=,|$)`,"gu"),flagToOptionKey=flag=>flag.slice(2).replaceAll(/-([a-z])/gu,(_match,letter)=>letter.toUpperCase()),ITEM_ENVELOPE_KEYS=["item","item_hash","linked","schedule","claim_state","children","omission_receipt","row_contract","context_intent"],READ_ONLY_ITEM_KEYS=new Set(["created_at","updated_at","closed_at","version","format_version","path","author","collection_counts","notes_count","tests_count"]),UPDATE_READ_ONLY_ITEM_KEYS=new Set([...READ_ONLY_ITEM_KEYS,"completed_at","id"]),MUTATION_OPTION_KEYS={create:CREATE_COMMANDER_OPTION_REGISTRATION_CONTRACTS.map(contract=>contract.target),update:UPDATE_COMMANDER_OPTION_REGISTRATION_CONTRACTS.map(contract=>contract.target),close:CLOSE_FLAG_CONTRACTS.map(contract=>flagToOptionKey(contract.flag)).filter(key=>key!=="reason"),release:RELEASE_FLAG_CONTRACTS.map(contract=>flagToOptionKey(contract.flag))};function deriveMutationTransactionId(input){const digest=crypto.createHash("sha256").update(input).digest("hex");return`${DERIVED_TRANSACTION_ID_PREFIX}${digest.slice(0,24)}`}function parseAtomicMutationControls(value){const controls={},compensation=value.createCompensation;if(compensation!==void 0){if(compensation!=="close"&&compensation!=="delete")throw new PmCliError("createCompensation must be close or delete.",EXIT_CODE.USAGE);controls.createCompensation=compensation}for(const key of["lockTtlSeconds","lockWaitMs"]){const raw=value[key];if(raw===void 0)continue;const parsed=typeof raw=="number"||typeof raw=="string"&&raw.trim().length>0?Number(raw):Number.NaN;if(!Number.isSafeInteger(parsed)||parsed<=0)throw new PmCliError(`${key} must be a positive safe integer.`,EXIT_CODE.USAGE);controls[key]=parsed}return controls}function isPlainObject(value){if(typeof value!="object"||value===null||Array.isArray(value))return!1;const prototype=Object.getPrototypeOf(value);return prototype===Object.prototype||prototype===null}function nearestKey(key,candidates){const limit=Math.max(1,Math.min(3,Math.floor(key.length/4)+1));let best;for(const candidate of candidates){const distance=levenshteinDistanceWithinLimit(key,candidate,limit);distance!==null&&(best===void 0||distance<best.distance)&&(best={key:candidate,distance})}return best?.key}function unknownKeyError(scope,key,candidates){const suggestion=nearestKey(key,candidates);return new PmCliError(suggestion===void 0?`${scope} does not recognize key "${key}". Allowed keys: ${candidates.join(", ")}.`:`${scope} does not recognize key "${key}". Did you mean "${suggestion}"?`,EXIT_CODE.USAGE,{code:"stdin_json_unknown_key",required:`Use documented ${scope} keys only.`,why:"Strict structured input prevents silent typos from creating incomplete or unintended mutations.",nextSteps:[suggestion===void 0?`Remove or correct "${key}".`:`Replace "${key}" with "${suggestion}".`]})}function parseJsonValue(input,label){try{return JSON.parse(input)}catch(error){const detail=error instanceof Error?error.message:String(error);throw new PmCliError(`${label} must be valid JSON: ${detail}`,EXIT_CODE.USAGE,{code:"stdin_json_invalid",required:`Pipe one valid JSON ${label.toLowerCase()} into stdin.`,why:"Structured mutation input must parse deterministically before any tracker write begins."})}}function validateMutationObject(value,index){if(!isPlainObject(value))throw new PmCliError(`Mutation ${index+1} must be an object.`,EXIT_CODE.USAGE);for(const key of Object.keys(value))if(!MUTATION_ROW_KEYS.includes(key))throw unknownKeyError(`mutation ${index+1}`,key,MUTATION_ROW_KEYS);return value}function validateMutationOptions(options,op,index,runtimeFields){if(options===void 0)return;if(!isPlainObject(options))throw new PmCliError(`Mutation ${index+1} options must be an object.`,EXIT_CODE.USAGE);const allowed=[...MUTATION_OPTION_KEYS[op],...runtimeFieldOptionKeys(op,runtimeFields)];for(const key of Object.keys(options))if(!allowed.includes(key))throw unknownKeyError(`mutation ${index+1} ${op} options`,key,allowed);return assertMutationOptionValueTypes(options,op,index),options}function validateMutationKeyGuard(value,op,index,field){if(value===void 0)return;const allowedOp=field==="unique"?"create":"update";if(op!==allowedOp)throw new PmCliError(`Mutation ${index+1} ${field} is only valid for ${allowedOp} operations.`,EXIT_CODE.USAGE);const key=isPlainObject(value)&&typeof value.key=="string"?value.key.trim():"";if(!isPlainObject(value)||key.length===0||value.value===void 0||value.value===null)throw new PmCliError(`Mutation ${index+1} ${field} must be an object with a non-empty key and a value.`,EXIT_CODE.USAGE);return{key,value:value.value}}function validateMutationRow(value,index,runtimeFields){const mutation=validateMutationObject(value,index),{op,id,reason}=mutation;if(op!=="create"&&op!=="update"&&op!=="close"&&op!=="release")throw new PmCliError(`Mutation ${index+1} op must be create, update, close, or release.`,EXIT_CODE.USAGE);const options=validateMutationOptions(mutation.options,op,index,runtimeFields),unique=validateMutationKeyGuard(mutation.unique,op,index,"unique"),match=validateMutationKeyGuard(mutation.match,op,index,"match");if(typeof id!="string"||id.trim().length===0)throw new PmCliError(`Mutation ${index+1} requires a non-empty id.`,EXIT_CODE.USAGE);const normalizedId=id.trim();if(op==="close"){if(typeof reason!="string"||reason.trim().length===0)throw new PmCliError(`Mutation ${index+1} close requires a non-empty reason.`,EXIT_CODE.USAGE);return{op,id:normalizedId,reason:reason.trim(),...options===void 0?{}:{options}}}if(op==="release")return{op,id:normalizedId,...options===void 0?{}:{options}};if(!isPlainObject(options))throw new PmCliError(`Mutation ${index+1} ${op} requires an options object.`,EXIT_CODE.USAGE);return{op,id:normalizedId,options,...unique===void 0?{}:{unique},...match===void 0?{}:{match}}}function parseItemMutationBatch(input,parseOptions={}){const parsed=parseJsonValue(input,"Mutation batch"),rows=Array.isArray(parsed)?parsed:isPlainObject(parsed)&&Array.isArray(parsed.mutations)?parsed.mutations:void 0;if(rows===void 0||rows.length===0)throw new PmCliError("Mutation batch must be a non-empty JSON array or an object with a non-empty mutations array.",EXIT_CODE.USAGE);const runtimeFields=parseOptions.runtimeFields??[];return rows.map((row,index)=>{const mutation=validateMutationRow(row,index,runtimeFields);return mutation.options!==void 0&&preserveMutationStdinTokenLiterals(mutation.options),mutation})}function deriveReferencedItemId(transactionId,reference,idPrefix){const digest=crypto.createHash("sha256").update(transactionId).update("\0").update(reference).digest("hex"),token=BigInt(`0x${digest.slice(0,16)}`).toString(36).padStart(13,"0").slice(-12);return`${normalizePrefix(idPrefix)}${token}`}function replaceExactMutationReference(value,references){if(typeof value!="string"||!value.startsWith("@"))return value;const reference=value.slice(1),resolved=references[reference];if(resolved===void 0)throw new PmCliError(`Unknown mutation reference "${value}".`,EXIT_CODE.USAGE);return resolved}function replaceDependencyReference(value,references){if(typeof value!="string")return value;const resolvedValue=value.replace(MUTATION_DEPENDENCY_REFERENCE_PATTERN,(_match,prefix,reference)=>{const resolved=references[reference];if(resolved===void 0)throw new PmCliError(`Unknown mutation reference "@${reference}".`,EXIT_CODE.USAGE);return`${prefix}id=${resolved}`});if(/(^|,)id=@/u.test(resolvedValue))throw new PmCliError(`Malformed mutation reference in dependency "${value}".`,EXIT_CODE.USAGE);return resolvedValue}function referencedAliases(value){return typeof value!="string"?[]:value.startsWith("@")?[value.slice(1)]:[...value.matchAll(MUTATION_DEPENDENCY_REFERENCE_PATTERN)].map(match=>match[1]).filter(reference=>reference!==void 0)}function assertAcyclicCreateReferences(rows,inputReferences){const graph=new Map;for(const[index,row]of rows.entries()){const reference=inputReferences[index];if(reference===void 0)continue;const mutationOptions=isPlainObject(row.options)?row.options:{},dependencies=Array.isArray(mutationOptions.dep)?mutationOptions.dep:mutationOptions.dep===void 0?[]:[mutationOptions.dep];graph.set(reference,[mutationOptions.parent,mutationOptions.blockedBy,...dependencies].flatMap(referencedAliases))}const visiting=new Set,visited=new Set,visit=reference=>{if(visiting.has(reference))throw new PmCliError(`Mutation reference cycle includes "@${reference}".`,EXIT_CODE.USAGE);if(!visited.has(reference)){visiting.add(reference);for(const dependency of graph.get(reference))graph.has(dependency)&&visit(dependency);visiting.delete(reference),visited.add(reference)}};for(const reference of graph.keys())visit(reference)}function parseJsonLinesRows(input){const candidates=input.split(/\r?\n/u).map((line,index)=>({line:line.trim(),number:index+1})).filter(entry=>entry.line.length>0);if(candidates.length<2)return null;const parsed=candidates.map(entry=>{try{return{...entry,ok:!0,value:JSON.parse(entry.line)}}catch(error){return{...entry,ok:!1,error}}}),first=parsed[0];if(!first.ok&&(first.line.startsWith("[")||first.line==="{"||parsed.at(-1).line==="}")||!parsed.some(entry=>entry.ok&&isPlainObject(entry.value)))return null;const rows=[];for(const entry of parsed){if(!entry.ok)throw new PmCliError(`Mutation document line ${entry.number} must be one valid JSON object: ${describeUnknownError(entry.error)}`,EXIT_CODE.USAGE,{code:"stdin_json_invalid",required:"Provide one JSON object per line (JSON Lines), a JSON array, or a {schema_version, mutations} document.",why:"Structured mutation input must parse deterministically before any tracker write begins."});if(!isPlainObject(entry.value))throw new PmCliError(`Mutation document line ${entry.number} must be a JSON object.`,EXIT_CODE.USAGE);rows.push(entry.value)}return rows}function isMutationEnvelope(value){return Object.hasOwn(value,"mutations")||Object.hasOwn(value,"schema_version")}function parseMutationDocumentEnvelope(input){let parsed;try{parsed=parseJsonValue(input,"Mutation document")}catch(error){const rows=parseJsonLinesRows(input);if(rows===null)throw error;return{schema_version:1,mutations:rows}}return Array.isArray(parsed)?{schema_version:1,mutations:parsed}:isPlainObject(parsed)&&!isMutationEnvelope(parsed)?{schema_version:1,mutations:[parsed]}:parsed}function normalizeDocumentRow(row,index){const normalized=Object.hasOwn(row,"op")?validateOperationRow(row,index):normalizeCreateDocumentRow(row,index);if(normalized.op==="create"&&normalized.id===void 0&&isPlainObject(normalized.item)&&normalized.item.id!==void 0){if(typeof normalized.item.id!="string")throw new PmCliError(`Mutation ${index+1} item id must be a string.`,EXIT_CODE.USAGE);normalized.id=normalized.item.id}return normalized}function validateOperationRow(row,index){for(const key of Object.keys(row))if(!DOCUMENT_MUTATION_ROW_KEYS.includes(key))throw unknownKeyError(`mutation ${index+1}`,key,DOCUMENT_MUTATION_ROW_KEYS);if(row.item!==void 0&&row.op!=="create")throw new PmCliError(`Mutation ${index+1} item documents are only valid for create operations.`,EXIT_CODE.USAGE);if(row.item!==void 0&&row.options!==void 0)throw new PmCliError(`Mutation ${index+1} must declare either item or options, not both.`,EXIT_CODE.USAGE);return{...row}}function normalizeCreateDocumentRow(row,index){const{ref,id,item,...fields}=row,document=item!==void 0?item:fields;if(item!==void 0&&Object.keys(fields).length>0)throw new PmCliError(`Mutation ${index+1} must not mix item document keys with top-level fields.`,EXIT_CODE.USAGE);if(!isPlainObject(document))throw new PmCliError(`Mutation ${index+1} item document must be an object.`,EXIT_CODE.USAGE);return{op:"create",...ref===void 0?{}:{ref},...id===void 0?{}:{id},item:document}}function parseMutationDocumentRows(input){const envelope=parseMutationDocumentEnvelope(input);if(!isPlainObject(envelope))throw new PmCliError("Mutation document must be a JSON array or object.",EXIT_CODE.USAGE);for(const key of Object.keys(envelope))if(!MUTATION_DOCUMENT_KEYS.includes(key))throw unknownKeyError("mutation document",key,MUTATION_DOCUMENT_KEYS);if(envelope.schema_version!==void 0&&envelope.schema_version!==1)throw new PmCliError("Mutation document schema_version must be 1.",EXIT_CODE.USAGE);if(!Array.isArray(envelope.mutations)||envelope.mutations.length===0)throw new PmCliError("Mutation document requires a non-empty mutations array.",EXIT_CODE.USAGE);return envelope.mutations.map((row,index)=>{if(!isPlainObject(row))throw new PmCliError(`Mutation ${index+1} must be an object.`,EXIT_CODE.USAGE);return normalizeDocumentRow(row,index)})}function excludeOwnCreateIds(upsert,rows,options){const ownIds=new Set(Object.values(collectMutationReferences(rows,options,Object.create(null))));for(const row of rows)row.op==="create"&&typeof row.id=="string"&&row.id.trim().length>0&&ownIds.add(normalizeItemId(row.id,options.idPrefix));return{...upsert,existing:upsert.existing.filter(candidate=>!ownIds.has(candidate.id))}}function synthesizeMissingReferences(rows){const declared=new Set(rows.map(row=>row.ref).filter(ref=>typeof ref=="string"));for(const[index,row]of rows.entries()){if(row.op!=="create"||row.ref!==void 0||typeof row.id=="string"&&row.id.trim().length>0)continue;const synthesized=`${SYNTHESIZED_REFERENCE_PREFIX}${index+1}`;if(declared.has(synthesized))throw new PmCliError(`Mutation ${index+1} receives the synthesized ref "${synthesized}", but another row declares that ref. Rename it or give mutation ${index+1} its own ref; ${SYNTHESIZED_REFERENCE_PREFIX}<n> aliases are reserved for rows without one.`,EXIT_CODE.USAGE);row.ref=synthesized}}function itemDocumentToBatchOptions(item,mode,runtimeFields){const converted=itemDocumentToMutationOptions(JSON.stringify(item),mode,{},runtimeFields);return delete converted.stdinJson,delete converted.documentAnnotationSnapshot,converted}function runtimeFieldOptionKeys(op,runtimeFields){return op!=="create"&&op!=="update"?[]:runtimeFields.filter(definition=>definition.commands.includes(op)).map(definition=>runtimeFieldOptionTarget(definition))}function convertItemDocumentRows(rows,runtimeFields){for(const row of rows)isPlainObject(row.item)&&(row.options=itemDocumentToBatchOptions(row.item,"create",runtimeFields),delete row.item)}function itemDocumentMetadataKey(key,runtimeFields){return runtimeFields.some(field=>field.metadata_key===key)?key:resolveCoreUpsertMetadataKey(key)}function readUpsertKeyValue(row,key,runtimeFields){const camel=key.replaceAll(/_([a-z])/gu,(_match,letter)=>letter.toUpperCase());if(isPlainObject(row.item)){if(row.item[key]!==void 0)return row.item[key];if(itemDocumentMetadataKey(camel,runtimeFields)===key&&row.item[camel]!==void 0)return row.item[camel]}const options=isPlainObject(row.options)?row.options:{},declared=runtimeFields.find(field=>field.metadata_key===key);return declared!==void 0&&options[runtimeFieldOptionTarget(declared)]!==void 0?options[runtimeFieldOptionTarget(declared)]:options[key]!==void 0?options[key]:options[camel]!==void 0?options[camel]:(Array.isArray(options.field)?options.field:[]).map(String).find(entry=>entry.startsWith(`${key}=`))?.slice(key.length+1)}function compareCodeUnits(left,right){return left<right?-1:+(left!==right)}function canonicalizeUpsertValue(value){return Array.isArray(value)?value.map(canonicalizeUpsertValue):isPlainObject(value)?Object.fromEntries(Object.entries(value).sort(([left],[right])=>compareCodeUnits(left,right)).map(([key,nested])=>[key,canonicalizeUpsertValue(nested)])):value}function stableUpsertValue(value){return typeof value=="string"?value.trim():JSON.stringify(canonicalizeUpsertValue(value))}function applyKeyedUpsert(rows,upsert,idPrefix,runtimeFields,references){const key=upsert.key.trim();if(key.length===0)throw new PmCliError("Upsert key must be a non-empty field name.",EXIT_CODE.USAGE);const existingByValue=indexUpsertCandidates(upsert.existing),seen=new Map,upserted=new Set;for(const[index,row]of rows.entries()){if(row.op!=="create")continue;const stable=readStableUpsertKey(row,key,index,seen,runtimeFields),match=resolveUpsertMatch(existingByValue.get(stable)??[],key,stable);if(match===void 0){row.unique={key,value:readUpsertKeyValue(row,key,runtimeFields)};continue}const id=normalizeItemId(match,idPrefix);typeof row.ref=="string"&&(references[row.ref]=id),convertRowToUpsertUpdate(row,id,runtimeFields,{key,value:readUpsertKeyValue(row,key,runtimeFields)}),upserted.add(index)}return upserted}function indexUpsertCandidates(existing){const existingByValue=new Map;for(const candidate of existing){if(candidate.value===void 0||candidate.value===null)continue;const stable=stableUpsertValue(candidate.value);existingByValue.set(stable,[...existingByValue.get(stable)??[],candidate.id])}return existingByValue}function readStableUpsertKey(row,key,index,seen,runtimeFields){const value=readUpsertKeyValue(row,key,runtimeFields);if(value==null||value==="")throw new PmCliError(`Mutation ${index+1} is missing the upsert key "${key}".`,EXIT_CODE.USAGE,{code:"upsert_key_missing",required:`Every create record must carry a non-empty "${key}" value when --upsert-key ${key} is set.`,why:"Keyed upsert identifies existing items by that field; a record without it cannot be matched or safely created."});const stable=stableUpsertValue(value),previous=seen.get(stable);if(previous!==void 0)throw new PmCliError(`Mutations ${previous+1} and ${index+1} share the upsert key value ${JSON.stringify(stable)} for "${key}".`,EXIT_CODE.USAGE,{code:"upsert_key_duplicate",required:"Each record must carry a distinct upsert key value within one batch.",why:"Two records with the same key would race for the same existing item."});return seen.set(stable,index),stable}function resolveUpsertMatch(matches,key,stable){if(matches.length>1)throw new PmCliError(`Upsert key "${key}" value ${JSON.stringify(stable)} matches ${matches.length} existing items: ${matches.join(", ")}.`,EXIT_CODE.CONFLICT,{code:"upsert_key_ambiguous",required:"Resolve the duplicate existing items or choose a key that is unique per item.",why:"An ambiguous key cannot select one item to update without guessing."});return matches[0]}function convertRowToUpsertUpdate(row,id,runtimeFields,match){const updateKeys=new Set([...MUTATION_OPTION_KEYS.update,...runtimeFieldOptionKeys("update",runtimeFields)]),options=isPlainObject(row.item)?itemDocumentToBatchOptions(row.item,"update",runtimeFields):Object.fromEntries(Object.entries(row.options).filter(([optionKey])=>updateKeys.has(optionKey)));delete row.item,delete row.ref,row.op="update",row.id=id,row.options=options,row.match=match}function collectMutationReferences(rows,options,references){for(const[index,row]of rows.entries())if(row.ref!==void 0){if(row.op!=="create")throw new PmCliError(`Mutation ${index+1} ref is only valid for create operations.`,EXIT_CODE.USAGE);if(typeof row.ref!="string"||!MUTATION_REFERENCE_PATTERN.test(row.ref))throw new PmCliError(`Mutation ${index+1} ref must match ${MUTATION_REFERENCE_PATTERN.source}.`,EXIT_CODE.USAGE);if(references[row.ref]!==void 0)throw new PmCliError(`Duplicate mutation ref "${row.ref}".`,EXIT_CODE.USAGE);references[row.ref]=typeof row.id=="string"&&row.id.trim().length>0?normalizeItemId(row.id,options.idPrefix):deriveReferencedItemId(options.transactionId,row.ref,options.idPrefix)}return references}function resolveItemMutationDocument(input,options){const rows=parseMutationDocumentRows(input);synthesizeMissingReferences(rows);const inputReferences=rows.map(row=>typeof row.ref=="string"?row.ref:void 0),references=Object.create(null),upserted=options.upsert===void 0?new Set:applyKeyedUpsert(rows,excludeOwnCreateIds(options.upsert,rows,options),options.idPrefix,options.runtimeFields??[],references);convertItemDocumentRows(rows,options.runtimeFields??[]),collectMutationReferences(rows,options,references),assertAcyclicCreateReferences(rows,inputReferences);const resolvedRows=rows.map(row=>{const resolved={...row},reference=typeof resolved.ref=="string"?resolved.ref:void 0;if(delete resolved.ref,reference!==void 0?resolved.id=references[reference]:resolved.id=replaceExactMutationReference(resolved.id,references),isPlainObject(resolved.options)){const mutationOptions={...resolved.options};for(const key of["parent","blockedBy"])mutationOptions[key]!==void 0&&(mutationOptions[key]=replaceExactMutationReference(mutationOptions[key],references));Array.isArray(mutationOptions.dep)?mutationOptions.dep=mutationOptions.dep.map(entry=>replaceDependencyReference(entry,references)):mutationOptions.dep!==void 0&&(mutationOptions.dep=replaceDependencyReference(mutationOptions.dep,references)),resolved.options=mutationOptions}return resolved}),mutations=parseItemMutationBatch(JSON.stringify(resolvedRows),{runtimeFields:options.runtimeFields??[]}),createIds=mutations.filter(mutation=>mutation.op==="create").map(mutation=>mutation.id);if(new Set(createIds).size!==createIds.length)throw new PmCliError("Duplicate resolved create id in mutation document.",EXIT_CODE.USAGE);const receiptRows=mutations.map((mutation,index)=>{const reference=inputReferences[index];return{index,...typeof reference=="string"?{ref:reference}:{},id:mutation.id,op:mutation.op,...upserted.has(index)?{upserted:!0}:{}}});return{schema_version:1,mutations,references,rows:receiptRows}}function serializePairs(value,keys){return keys.filter(key=>value[key]!==void 0&&value[key]!==null).map(key=>{const rendered=String(value[key]);return`${key}=${/[",\n\r\\]/u.test(rendered)?JSON.stringify(rendered):rendered}`}).join(",")}function serializeObjectArray(value,keys,label){if(value!==void 0){if(!Array.isArray(value)||!value.every(isPlainObject))throw new PmCliError(`${label} must be an array of objects.`,EXIT_CODE.USAGE);return value.map(entry=>serializePairs(entry,keys))}}function scalarItemOption(key,value){return key==="tags"&&Array.isArray(value)&&value.every(entry=>typeof entry=="string")?JSON.stringify(value):typeof value=="number"||typeof value=="boolean"?String(value):value}function appendFacetOptions(target,source,skipPersistedAnnotations=!1){for(const[sourceKey,targetKey,keys]of ITEM_MUTATION_FACET_OPTION_MAPPINGS){const sourceValue=source[sourceKey],serialized=serializeObjectArray(skipPersistedAnnotations&&["comments","notes","learnings"].includes(sourceKey)&&Array.isArray(sourceValue)?sourceValue.filter(entry=>!isPlainObject(entry)||entry.created_at===void 0):sourceValue,keys,sourceKey);serialized!==void 0&&serialized.length>0&&(target[targetKey]=serialized)}}function validateItemDocumentKeys(parsed,envelope){for(const key of Object.keys(parsed)){if(envelope){if(!ITEM_ENVELOPE_KEYS.includes(key))throw unknownKeyError("item envelope",key,ITEM_ENVELOPE_KEYS);continue}if(!ITEM_FIELD_KEYS.has(key)&&nearestKey(key,[...ITEM_FIELD_KEYS])===void 0&&nearestKey(key,ITEM_ENVELOPE_KEYS)==="item")throw unknownKeyError("item envelope",key,ITEM_ENVELOPE_KEYS)}}function resolveItemDocument(parsed){const envelope=Object.hasOwn(parsed,"item");validateItemDocumentKeys(parsed,envelope);const item=envelope?parsed.item:parsed;if(!isPlainObject(item))throw new PmCliError("Item document item must be an object.",EXIT_CODE.USAGE);return{item,...envelope&&isPlainObject(parsed.linked)?{linked:parsed.linked}:{}}}function appendItemDocumentEntry(documentOptions,key,value,mode,runtimeFields,explicitOptions){if((mode==="update"?UPDATE_READ_ONLY_ITEM_KEYS:READ_ONLY_ITEM_KEYS).has(key))return;const definition=runtimeFields.find(field=>field.metadata_key===key&&field.commands.includes(mode));if(definition!==void 0){const explicitValue=readRuntimeFieldOptionValue(explicitOptions,definition);documentOptions[runtimeFieldOptionTarget(definition)]=explicitValue!==void 0?explicitValue:definition.type==="array"||definition.type==="object"?JSON.stringify(value):value;return}if(!ITEM_FIELD_KEYS.has(key)){if(nearestKey(key,[...ITEM_FIELD_KEYS])!==void 0)throw unknownKeyError("item document",key,[...ITEM_FIELD_KEYS]);const fields=Array.isArray(documentOptions.field)?documentOptions.field:[];fields.push(`${key}=${typeof value=="string"?value:JSON.stringify(value)}`),documentOptions.field=fields;return}["dependencies","comments","notes","learnings","files","tests","docs","reminders","events","type_options"].includes(key)||(documentOptions[key.replaceAll(/_([a-z])/gu,(_match,letter)=>letter.toUpperCase())]=scalarItemOption(key,value))}function explicitDocumentOptionKeys(explicitOptions,mode,runtimeFields){const explicitKeys=new Set(Object.entries(explicitOptions).filter(([,value])=>value!==void 0).map(([key])=>key)),contracts=mode==="create"?CREATE_COMMANDER_OPTION_REGISTRATION_CONTRACTS:UPDATE_COMMANDER_OPTION_REGISTRATION_CONTRACTS;for(const{target,keys}of contracts)keys.some(key=>explicitKeys.has(key))&&explicitKeys.add(target);for(const definition of runtimeFields)readRuntimeFieldOptionValue(explicitOptions,definition)!==void 0&&explicitKeys.add(runtimeFieldOptionTarget(definition));return explicitKeys}function removeOverriddenDocumentOptions(documentOptions,mode,explicitKeys){const contracts=mode==="create"?CREATE_COMMANDER_OPTION_REGISTRATION_CONTRACTS:UPDATE_COMMANDER_OPTION_REGISTRATION_CONTRACTS;for(const{target}of contracts)target!=="field"&&explicitKeys.has(target)&&delete documentOptions[target]}function itemDocumentToMutationOptions(input,mode,explicitOptions={},runtimeFields=[]){const parsed=parseJsonValue(input,"Item document");if(!isPlainObject(parsed))throw new PmCliError("Item document must be a JSON object.",EXIT_CODE.USAGE);const{item,linked}=resolveItemDocument(parsed),documentOptions={};for(const[key,value]of Object.entries(item)){const metadataKey=itemDocumentMetadataKey(key,runtimeFields);metadataKey!==key&&Object.hasOwn(item,metadataKey)||appendItemDocumentEntry(documentOptions,metadataKey,value,mode,runtimeFields,explicitOptions)}appendFacetOptions(documentOptions,item,mode==="update"),linked!==void 0&&appendFacetOptions(documentOptions,linked);const explicitKeys=explicitDocumentOptionKeys(explicitOptions,mode,runtimeFields);if(removeOverriddenDocumentOptions(documentOptions,mode,explicitKeys),mode==="update"){const annotations=Object.fromEntries(["comments","notes","learnings"].filter(key=>Object.hasOwn(item,key)&&!explicitKeys.has(key.slice(0,-1))).map(key=>[key,item[key].filter(entry=>isPlainObject(entry)&&entry.created_at!==void 0)]));Object.keys(annotations).length>0&&(documentOptions.documentAnnotationSnapshot=annotations)}const merged={...documentOptions,...Object.fromEntries(Object.entries(explicitOptions).filter(([,value])=>value!==void 0)),...Array.isArray(documentOptions.field)&&Array.isArray(explicitOptions.field)?{field:[...documentOptions.field,...explicitOptions.field]}:{},stdinJson:void 0},literalKeys=Object.keys(documentOptions).filter(key=>!explicitKeys.has(key));return preserveMutationStdinTokenFields(preserveLiteralMutationInputFields(merged,literalKeys),literalKeys)}function validateItemMutationRows(value){return parseItemMutationBatch(JSON.stringify(value))}export{deriveMutationTransactionId,itemDocumentToMutationOptions,parseAtomicMutationControls,parseItemMutationBatch,resolveItemMutationDocument,stableUpsertValue,validateItemMutationRows};
|
|
2
2
|
//# sourceMappingURL=structured-mutations.js.map
|
package/dist/sdk/test/batch.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
(function(){try{var e=typeof window<"u"?window:typeof global<"u"?global:typeof globalThis<"u"?globalThis:typeof self<"u"?self:{},n=new e.Error().stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="
|
|
2
|
-
`)}catch{}},collectTestAllItemTests=async(filteredItems,global2,pmRoot)=>{const itemTests=[];let linkedTests=0;for(const item of filteredItems){const readResult=await runTest(item.id,{run:!1},{...global2,path:pmRoot});linkedTests+=readResult.tests.length,itemTests.push({item,tests:readResult.tests})}return{itemTests,linkedTests}},selectTestAllItems=async params=>{const statusRegistry=resolveRuntimeStatusRegistry(params.settings.schema),typeRegistry=resolveItemTypeRegistry(params.settings,getActiveExtensionRegistrations()),statusFilter=parseStatus(params.options.status,statusRegistry),limitFilter=parseNonNegativeInteger(params.options.limit,"--limit"),offsetFilter=parseNonNegativeInteger(params.options.offset,"--offset")??0,statusFilteredItems=(await listAllItemMetadataLight(params.pmRoot,params.settings.item_format,typeRegistry.type_to_folder,void 0,params.settings.schema)).filter(item=>statusFilter?item.status===statusFilter:!0).sort((a,b)=>a.id.localeCompare(b.id));return{filteredItems:limitFilter===void 0?statusFilteredItems.slice(offsetFilter):statusFilteredItems.slice(offsetFilter,offsetFilter+limitFilter),statusFilter,limitFilter,offsetFilter}},buildEffectiveTimeoutByKey=itemTests=>{const effectiveTimeoutByKey=new Map;for(const{tests}of itemTests)for(const test of tests){const key=buildLinkedTestKey(test);if(!effectiveTimeoutByKey.has(key)){effectiveTimeoutByKey.set(key,test.timeout_seconds);continue}effectiveTimeoutByKey.set(key,maxTimeoutSeconds(effectiveTimeoutByKey.get(key),test.timeout_seconds))}return effectiveTimeoutByKey};function resolveTestAllCommandRefusal(test,itemId){try{assertLinkedTestCommand(test.command);return}catch(error){if(!(error instanceof PmCliError)||error.code!=="linked_test_command_not_executable")throw error;return{command:test.command,path:test.path,status:"refused",exit_code:null,failure_category:"config_refusal",error:`Linked test refused for ${itemId}: ${error.message}`}}}const runTestAllItem=async(entry,context)=>{const testsToRun=[],keyedTests=entry.tests.map(test=>{const key=buildLinkedTestKey(test),refusal=resolveTestAllCommandRefusal(test,entry.item.id);if(refusal)return{test,key,duplicate:!1,refusal};const duplicate=context.seenTestKeys.has(key);if(!duplicate){context.seenTestKeys.add(key);const effectiveTimeoutSeconds=context.effectiveTimeoutByKey.get(key);testsToRun.push(effectiveTimeoutSeconds===void 0?test:{...test,timeout_seconds:effectiveTimeoutSeconds})}return{test,key,duplicate,refusal}}),executedResults=testsToRun.length>0?await runLinkedTests(testsToRun,context.defaultTimeoutSeconds,{progress:context.options.progress,sourceRoots:context.sourceRoots,envSet:context.options.envSet,envClear:context.options.envClear,sharedHostSafe:context.options.sharedHostSafe,pmContext:context.options.pmContext,overrideLinkedPmContext:context.options.overrideLinkedPmContext,failOnContextMismatch:context.options.failOnContextMismatch,failOnEmptyTestRun:context.options.failOnEmptyTestRun,requireAssertionsForPm:context.options.requireAssertionsForPm,checkContext:context.options.checkContext,autoPmContext:context.options.autoPmContext,workspaceContext:context.options.workspaceContext,overrideLinkedWorkspaceContext:context.options.overrideLinkedWorkspaceContext,pmRoot:context.pmRoot,allowUntrustedLinkedTests:context.options.allowUntrustedLinkedTests,untrustedLinkedTestsPolicyEnabled:context.settings.testing.allow_untrusted_linked_tests}):[];let executedIndex=0;const runResults=keyedTests.map(({test,key,duplicate,refusal})=>{if(refusal)return refusal;if(!duplicate){const executed=executedResults[executedIndex];return executedIndex+=1,executed}return{command:test.command,path:test.path,status:"skipped",error:`Duplicate linked test skipped (key=${key}).`}}),summary=countStatuses(runResults),failureCategories=countFailureCategories(runResults),trackingWarnings=await appendTestAllItemTracking(entry,summary,runResults,context);return{passed:summary.passed,failed:summary.failed,skipped:summary.skipped,trackingWarnings,failureCategories,result:{ok:summary.failed===0&&!(context.options.failOnSkipped===!0&&summary.skipped>0),id:entry.item.id,status:entry.item.status,test_count:entry.tests.length,passed:summary.passed,failed:summary.failed,skipped:summary.skipped,run_results:runResults,failure_categories:failureCategories}}},trackedRunFailed=(summary,options)=>[summary.failed>0,options.failOnSkipped===!0&&summary.skipped>0].includes(!0),optionalTrue=value=>value?!0:void 0,appendTestAllItemTracking=async(entry,summary,runResults,context)=>{if(!context.trackingEnabled)return[];try{const failed=trackedRunFailed(summary,context.options);await appendTrackedTestRunSummary({pmRoot:context.pmRoot,settings:context.settings,itemId:entry.item.id,author:context.trackingAuthor,message:`Track test-all run summary (${context.trackingRunId})`,entry:{run_id:context.trackingRunId,kind:"test-all",status:failed?"failed":"passed",started_at:context.runStartedAt,finished_at:nowIso(),recorded_at:nowIso(),attempt:context.trackingAttempt,resumed_from:context.trackingResumedFrom,passed:summary.passed,failed:summary.failed,skipped:summary.skipped,items:1,linked_tests:entry.tests.length,fail_on_skipped_triggered:optionalTrue(context.options.failOnSkipped===!0&&summary.skipped>0),executions:runResults.filter(result=>result.command?.trim()).slice(0,32).map(result=>({command:result.command.trim(),requested_pm_context_mode:result.execution_context?.requested_pm_context_mode,pm_context_mode:result.execution_context?.pm_context_mode,workspace_context_mode:result.execution_context?.workspace_context_mode,trust_reason:result.execution_context?.trust.reason}))}})}catch(error){return[`test_result_tracking_failed:${entry.item.id}:${formatTrackingError(error)}`]}return[]},resolveTrackingAttempt=()=>{const raw=process.env.PM_BACKGROUND_TEST_RUN_ATTEMPT?.trim(),parsed=raw?Number.parseInt(raw,10):Number.NaN;return Number.isFinite(parsed)&&parsed>=1?parsed:void 0},resolveOptionalEnvironmentValue=value=>{const trimmed=value?.trim();return trimmed&&trimmed.length>0?trimmed:void 0},buildTestAllItemRunContext=params=>({pmRoot:params.pmRoot,settings:params.settings,options:params.options,defaultTimeoutSeconds:params.defaultTimeoutSeconds,sourceRoots:{projectPmRoot:params.pmRoot,globalPmRoot:resolveGlobalPmRoot(process.cwd())},seenTestKeys:new Set,effectiveTimeoutByKey:buildEffectiveTimeoutByKey(params.itemTests),trackingEnabled:params.settings.testing.record_results_to_items===!0,trackingAuthor:resolveAuthor(params.options.author,params.settings.author_default),trackingRunId:resolveTrackedRunId(),trackingAttempt:resolveTrackingAttempt(),trackingResumedFrom:resolveOptionalEnvironmentValue(process.env.PM_BACKGROUND_TEST_RUN_RESUMED_FROM),runStartedAt:params.runStartedAt}),initializeTestAllAccumulation=(options,linkedTests,filteredItems)=>{const accumulation={results:[],passed:0,failed:0,skipped:0,failureCategories:countFailureCategories([]),trackingWarnings:[]},failOnEmptyTestRunTriggered=options.failOnEmptyTestRun===!0&&linkedTests===0;return failOnEmptyTestRunTriggered&&(accumulation.failed+=1,accumulation.failureCategories.empty_run+=1,accumulation.trackingWarnings.push(`empty_linked_test_selection:items=${filteredItems.length};linked_tests=0;fail_on_empty_test_run=true`)),{accumulation,failOnEmptyTestRunTriggered}},runTestAllItems=async(itemTests,context,accumulation)=>{for(const[itemIndex,entry]of itemTests.entries()){emitTestAllProgress(context.options,`item ${itemIndex+1}/${itemTests.length} start id=${entry.item.id} linked_tests=${entry.tests.length}`);const execution=await runTestAllItem(entry,context);mergeFailureCategoryCounts(accumulation.failureCategories,execution.failureCategories),accumulation.passed+=execution.passed,accumulation.failed+=execution.failed,accumulation.skipped+=execution.skipped,accumulation.trackingWarnings.push(...execution.trackingWarnings),accumulation.results.push(execution.result),emitTestAllProgress(context.options,`item ${itemIndex+1}/${itemTests.length} end id=${entry.item.id} status=${execution.failed===0?"passed":"failed"} passed=${execution.passed} failed=${execution.failed} skipped=${execution.skipped}`)}},appendContextPreflightWarning=(options,accumulation)=>{if(options.checkContext!==!0)return;const allRunResults=accumulation.results.flatMap(entry=>entry.run_results),preflight=summarizeContextPreflight(allRunResults);accumulation.trackingWarnings.push(`context_preflight:checked_pm_commands=${preflight.checked_pm_commands};tracker_read_commands=${preflight.tracker_read_commands};mismatches=${preflight.mismatches};auto_remediated=${preflight.auto_remediated}`)},buildTestAllResult=params=>({ok:params.ok,totals:{items:params.filteredItems.length,linked_tests:params.linkedTests,passed:params.accumulation.passed,failed:params.accumulation.failed,skipped:params.accumulation.skipped,failure_categories:params.accumulation.failureCategories},failed:params.accumulation.failed,passed:params.accumulation.passed,skipped:params.accumulation.skipped,fail_on_skipped_triggered:params.failOnSkippedTriggered?!0:void 0,fail_on_empty_test_run_triggered:params.failOnEmptyTestRunTriggered?!0:void 0,warnings:params.accumulation.trackingWarnings.length>0?params.accumulation.trackingWarnings:void 0,results:params.accumulation.results}),buildTestAllSelectionProgress=params=>[`selection items=${params.filteredItems.length}`,`linked_tests=${params.linkedTests}`,params.statusFilter===void 0?"":`status=${params.statusFilter}`,params.limitFilter===void 0?"":`limit=${params.limitFilter}`,params.offsetFilter>0?`offset=${params.offsetFilter}`:""].filter(entry=>entry.length>0).join(" "),prepareTestAllRun=async(options,global2)=>{const pmRoot=resolvePmRoot(process.cwd(),global2.path);await assertInitializedTracker(pmRoot);const settings=await readSettings(pmRoot),selection=await selectTestAllItems({pmRoot,settings,options}),defaultTimeoutSeconds=parseTimeout(options.timeout),runStartedAt=nowIso(),collected=await collectTestAllItemTests(selection.filteredItems,global2,pmRoot);return emitTestAllProgress(options,buildTestAllSelectionProgress({...selection,linkedTests:collected.linkedTests})),{pmRoot,settings,filteredItems:selection.filteredItems,itemTests:collected.itemTests,linkedTests:collected.linkedTests,defaultTimeoutSeconds,runStartedAt}},resolveTestAllOutcome=params=>{const failOnSkippedTriggered=params.options.failOnSkipped===!0&¶ms.accumulation.skipped>0,ok=![params.accumulation.failed>0,failOnSkippedTriggered,params.failOnEmptyTestRunTriggered].includes(!0);return{failOnSkippedTriggered,ok}},runTestAll=async(options,global2)=>{const prepared=await prepareTestAllRun(options,global2),{accumulation,failOnEmptyTestRunTriggered}=initializeTestAllAccumulation(options,prepared.linkedTests,prepared.filteredItems),itemRunContext=buildTestAllItemRunContext({pmRoot:prepared.pmRoot,settings:prepared.settings,options,itemTests:prepared.itemTests,defaultTimeoutSeconds:prepared.defaultTimeoutSeconds,runStartedAt:prepared.runStartedAt});await runTestAllItems(prepared.itemTests,itemRunContext,accumulation),appendContextPreflightWarning(options,accumulation);const outcome=resolveTestAllOutcome({options,accumulation,failOnEmptyTestRunTriggered});return emitTestAllProgress(options,`end status=${outcome.ok?"passed":"failed"} items=${prepared.filteredItems.length} linked_tests=${prepared.linkedTests} passed=${accumulation.passed} failed=${accumulation.failed} skipped=${accumulation.skipped}`),buildTestAllResult({ok:outcome.ok,filteredItems:prepared.filteredItems,linkedTests:prepared.linkedTests,accumulation,failOnSkippedTriggered:outcome.failOnSkippedTriggered,failOnEmptyTestRunTriggered})},_testOnlyTestAll={buildLinkedTestKey,formatTrackingError};export{_testOnlyTestAll,runTestAll};
|
|
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]="22944f9f-02a6-5fb4-b13e-d034ec4cfa7d")}catch{}})();import{assertInitializedTracker}from"../environment/tracker-preflight.js";import{assertLinkedTestCommand}from"./entry.js";import{isItemRevisionConflict}from"../lifecycle/item-revision.js";import{getActiveExtensionRegistrations}from"../../core/extensions/index.js";import{resolveItemTypeRegistry}from"../../core/item/type-registry.js";import{parseOptionalNumber}from"../../core/item/parse.js";import{normalizeStatusInput}from"../../core/item/status.js";import{resolveRuntimeStatusRegistry}from"../../core/schema/runtime-schema.js";import{EXIT_CODE}from"../../core/shared/constants.js";import{PmCliError}from"../../core/shared/errors.js";import{nowIso}from"../../core/shared/time.js";import{listAllItemMetadataLight}from"../../core/store/item-store.js";import{resolveGlobalPmRoot,resolvePmRoot}from"../../core/store/paths.js";import{readSettings}from"../../core/store/settings.js";import{appendTrackedTestRunSummary}from"../../core/test/item-test-run-tracking.js";import{resolveAuthor}from"../../core/shared/author.js";import{countFailureCategories,runLinkedTests,runTest,summarizeContextPreflight}from"./execution.js";const parseStatus=(raw,statusRegistry)=>{if(raw===void 0)return;const normalized=normalizeStatusInput(raw,statusRegistry);if(!normalized){const allowedStatuses=statusRegistry.definitions.map(definition=>definition.id);throw new PmCliError(`Invalid --status value "${raw}". Allowed: ${allowedStatuses.join(", ")}`,EXIT_CODE.USAGE)}return normalized},parseTimeout=raw=>{if(raw===void 0)return;const parsed=parseOptionalNumber(raw,"timeout");if(parsed<=0)throw new PmCliError("timeout must be a positive number",EXIT_CODE.USAGE);return parsed},parseNonNegativeInteger=(raw,flag)=>{if(raw===void 0)return;const parsed=Number(raw);if(!Number.isInteger(parsed)||parsed<0)throw new PmCliError(`${flag} must be a non-negative integer`,EXIT_CODE.USAGE);return parsed},formatTrackingError=error=>error instanceof Error?error.message:String(error),resolveTrackedRunId=()=>{const fromEnv=process.env.PM_BACKGROUND_TEST_RUN_ID?.trim();return fromEnv&&fromEnv.length>0?fromEnv:`test-all-local-${Date.now().toString(36)}-${Math.random().toString(36).slice(2,8)}`},normalizeCommand=command=>command.trim().replaceAll(/\s+/g," "),normalizeEnvSetSignature=value=>!value||Object.keys(value).length===0?"{}":JSON.stringify(Object.fromEntries(Object.entries(value).sort(([left],[right])=>left.localeCompare(right)))),normalizeEnvClearSignature=value=>!value||value.length===0?"[]":JSON.stringify([...value].sort((left,right)=>left.localeCompare(right))),normalizePmContextModeSignature=value=>{const normalized=value?.trim().toLowerCase();return normalized&&normalized.length>0?normalized:"none"},normalizeWorkspaceContextModeSignature=value=>{const normalized=value?.trim().toLowerCase();return normalized&&normalized.length>0?normalized:"source"},normalizeAssertionStrings=values=>[...new Set(values??[])].sort((left,right)=>left.localeCompare(right)),normalizeAssertionMap=value=>Object.fromEntries(Object.entries(value??{}).sort(([left],[right])=>left.localeCompare(right))),normalizeAssertionLineCount=value=>typeof value=="number"?value:void 0,normalizeAssertionSignature=test=>{const normalized={assert_stdout_contains:normalizeAssertionStrings(test.assert_stdout_contains),assert_stdout_regex:normalizeAssertionStrings(test.assert_stdout_regex),assert_stderr_contains:normalizeAssertionStrings(test.assert_stderr_contains),assert_stderr_regex:normalizeAssertionStrings(test.assert_stderr_regex),assert_stdout_min_lines:normalizeAssertionLineCount(test.assert_stdout_min_lines),assert_json_field_equals:normalizeAssertionMap(test.assert_json_field_equals),assert_json_field_gte:normalizeAssertionMap(test.assert_json_field_gte)};return JSON.stringify(normalized)},resolveLinkedTestTargetSignature=test=>{const command=resolveOptionalEnvironmentValue(test.command);return command!==void 0?`command:${test.scope}:${normalizeCommand(command)}`:`path:${test.scope}:${resolveOptionalEnvironmentValue(test.path)??""}`},buildLinkedTestKey=test=>{const envSet=normalizeEnvSetSignature(test.env_set),envClear=normalizeEnvClearSignature(test.env_clear),pmContextMode=normalizePmContextModeSignature(test.pm_context_mode),workspaceContextMode=normalizeWorkspaceContextModeSignature(test.workspace_context_mode),sharedHostSafe=test.shared_host_safe===!0?"true":"false",assertions=normalizeAssertionSignature(test);return`${resolveLinkedTestTargetSignature(test)}:${envSet}:${envClear}:${pmContextMode}:${workspaceContextMode}:${sharedHostSafe}:${assertions}`},maxTimeoutSeconds=(current,candidate)=>candidate===void 0?current:current===void 0||candidate>current?candidate:current,countStatuses=runResults=>{let passed=0,failed=0,skipped=0;for(const result of runResults){if(result.status==="passed"){passed+=1;continue}if(result.status==="failed"||result.status==="refused"){failed+=1;continue}skipped+=1}return{passed,failed,skipped}},mergeFailureCategoryCounts=(target,source)=>{for(const[key,value]of Object.entries(source))target[key]+=value},emitTestAllProgress=(options,message)=>{if(options.progress===!0)try{process.stderr.write(`[pm test-all] ${message}
|
|
2
|
+
`)}catch{}},collectTestAllItemTests=async(filteredItems,global2,pmRoot)=>{const itemTests=[];let linkedTests=0;for(const item of filteredItems){const readResult=await runTest(item.id,{run:!1},{...global2,path:pmRoot});linkedTests+=readResult.tests.length,itemTests.push({item,tests:readResult.tests})}return{itemTests,linkedTests}},selectTestAllItems=async params=>{const statusRegistry=resolveRuntimeStatusRegistry(params.settings.schema),typeRegistry=resolveItemTypeRegistry(params.settings,getActiveExtensionRegistrations()),statusFilter=parseStatus(params.options.status,statusRegistry),limitFilter=parseNonNegativeInteger(params.options.limit,"--limit"),offsetFilter=parseNonNegativeInteger(params.options.offset,"--offset")??0,statusFilteredItems=(await listAllItemMetadataLight(params.pmRoot,params.settings.item_format,typeRegistry.type_to_folder,void 0,params.settings.schema)).filter(item=>statusFilter?item.status===statusFilter:!0).sort((a,b)=>a.id.localeCompare(b.id));return{filteredItems:limitFilter===void 0?statusFilteredItems.slice(offsetFilter):statusFilteredItems.slice(offsetFilter,offsetFilter+limitFilter),statusFilter,limitFilter,offsetFilter}},buildEffectiveTimeoutByKey=itemTests=>{const effectiveTimeoutByKey=new Map;for(const{tests}of itemTests)for(const test of tests){const key=buildLinkedTestKey(test);if(!effectiveTimeoutByKey.has(key)){effectiveTimeoutByKey.set(key,test.timeout_seconds);continue}effectiveTimeoutByKey.set(key,maxTimeoutSeconds(effectiveTimeoutByKey.get(key),test.timeout_seconds))}return effectiveTimeoutByKey};function resolveTestAllCommandRefusal(test,itemId){try{assertLinkedTestCommand(test.command);return}catch(error){if(!(error instanceof PmCliError)||error.code!=="linked_test_command_not_executable")throw error;return{command:test.command,path:test.path,status:"refused",exit_code:null,failure_category:"config_refusal",error:`Linked test refused for ${itemId}: ${error.message}`}}}const runTestAllItem=async(entry,context)=>{const testsToRun=[],keyedTests=entry.tests.map(test=>{const key=buildLinkedTestKey(test),refusal=resolveTestAllCommandRefusal(test,entry.item.id);if(refusal)return{test,key,duplicate:!1,refusal};const duplicate=context.seenTestKeys.has(key);if(!duplicate){context.seenTestKeys.add(key);const effectiveTimeoutSeconds=context.effectiveTimeoutByKey.get(key);testsToRun.push(effectiveTimeoutSeconds===void 0?test:{...test,timeout_seconds:effectiveTimeoutSeconds})}return{test,key,duplicate,refusal}}),executedResults=testsToRun.length>0?await runLinkedTests(testsToRun,context.defaultTimeoutSeconds,{progress:context.options.progress,sourceRoots:context.sourceRoots,envSet:context.options.envSet,envClear:context.options.envClear,sharedHostSafe:context.options.sharedHostSafe,pmContext:context.options.pmContext,overrideLinkedPmContext:context.options.overrideLinkedPmContext,failOnContextMismatch:context.options.failOnContextMismatch,failOnEmptyTestRun:context.options.failOnEmptyTestRun,requireAssertionsForPm:context.options.requireAssertionsForPm,checkContext:context.options.checkContext,autoPmContext:context.options.autoPmContext,workspaceContext:context.options.workspaceContext,overrideLinkedWorkspaceContext:context.options.overrideLinkedWorkspaceContext,pmRoot:context.pmRoot,allowUntrustedLinkedTests:context.options.allowUntrustedLinkedTests,untrustedLinkedTestsPolicyEnabled:context.settings.testing.allow_untrusted_linked_tests}):[];let executedIndex=0;const runResults=keyedTests.map(({test,key,duplicate,refusal})=>{if(refusal)return refusal;if(!duplicate){const executed=executedResults[executedIndex];return executedIndex+=1,executed}return{command:test.command,path:test.path,status:"skipped",error:`Duplicate linked test skipped (key=${key}).`}}),summary=countStatuses(runResults),failureCategories=countFailureCategories(runResults),trackingWarnings=await appendTestAllItemTracking(entry,summary,runResults,context);return{passed:summary.passed,failed:summary.failed,skipped:summary.skipped,trackingWarnings,failureCategories,result:{ok:summary.failed===0&&!(context.options.failOnSkipped===!0&&summary.skipped>0),id:entry.item.id,status:entry.item.status,test_count:entry.tests.length,passed:summary.passed,failed:summary.failed,skipped:summary.skipped,run_results:runResults,failure_categories:failureCategories}}},trackedRunFailed=(summary,options)=>[summary.failed>0,options.failOnSkipped===!0&&summary.skipped>0].includes(!0),optionalTrue=value=>value?!0:void 0,appendTestAllItemTracking=async(entry,summary,runResults,context)=>{if(!context.trackingEnabled)return[];try{const failed=trackedRunFailed(summary,context.options);await appendTrackedTestRunSummary({pmRoot:context.pmRoot,settings:context.settings,itemId:entry.item.id,author:context.trackingAuthor,message:`Track test-all run summary (${context.trackingRunId})`,entry:{run_id:context.trackingRunId,kind:"test-all",status:failed?"failed":"passed",started_at:context.runStartedAt,finished_at:nowIso(),recorded_at:nowIso(),attempt:context.trackingAttempt,resumed_from:context.trackingResumedFrom,passed:summary.passed,failed:summary.failed,skipped:summary.skipped,items:1,linked_tests:entry.tests.length,fail_on_skipped_triggered:optionalTrue(context.options.failOnSkipped===!0&&summary.skipped>0),executions:runResults.filter(result=>result.command?.trim()).slice(0,32).map(result=>({command:result.command.trim(),requested_pm_context_mode:result.execution_context?.requested_pm_context_mode,pm_context_mode:result.execution_context?.pm_context_mode,workspace_context_mode:result.execution_context?.workspace_context_mode,trust_reason:result.execution_context?.trust.reason}))}})}catch(error){return isItemRevisionConflict(error)?(context.revisionConflicts.push(error),[]):[`test_result_tracking_failed:${entry.item.id}:${formatTrackingError(error)}`]}return[]},resolveTrackingAttempt=()=>{const raw=process.env.PM_BACKGROUND_TEST_RUN_ATTEMPT?.trim(),parsed=raw?Number.parseInt(raw,10):Number.NaN;return Number.isFinite(parsed)&&parsed>=1?parsed:void 0},resolveOptionalEnvironmentValue=value=>{const trimmed=value?.trim();return trimmed&&trimmed.length>0?trimmed:void 0},buildTestAllItemRunContext=params=>({pmRoot:params.pmRoot,settings:params.settings,options:params.options,defaultTimeoutSeconds:params.defaultTimeoutSeconds,sourceRoots:{projectPmRoot:params.pmRoot,globalPmRoot:resolveGlobalPmRoot(process.cwd())},seenTestKeys:new Set,effectiveTimeoutByKey:buildEffectiveTimeoutByKey(params.itemTests),trackingEnabled:params.settings.testing.record_results_to_items===!0,trackingAuthor:resolveAuthor(params.options.author,params.settings.author_default),trackingRunId:resolveTrackedRunId(),trackingAttempt:resolveTrackingAttempt(),trackingResumedFrom:resolveOptionalEnvironmentValue(process.env.PM_BACKGROUND_TEST_RUN_RESUMED_FROM),runStartedAt:params.runStartedAt,revisionConflicts:[]}),initializeTestAllAccumulation=(options,linkedTests,filteredItems)=>{const accumulation={results:[],passed:0,failed:0,skipped:0,failureCategories:countFailureCategories([]),trackingWarnings:[]},failOnEmptyTestRunTriggered=options.failOnEmptyTestRun===!0&&linkedTests===0;return failOnEmptyTestRunTriggered&&(accumulation.failed+=1,accumulation.failureCategories.empty_run+=1,accumulation.trackingWarnings.push(`empty_linked_test_selection:items=${filteredItems.length};linked_tests=0;fail_on_empty_test_run=true`)),{accumulation,failOnEmptyTestRunTriggered}},runTestAllItems=async(itemTests,context,accumulation)=>{for(const[itemIndex,entry]of itemTests.entries()){emitTestAllProgress(context.options,`item ${itemIndex+1}/${itemTests.length} start id=${entry.item.id} linked_tests=${entry.tests.length}`);const execution=await runTestAllItem(entry,context);mergeFailureCategoryCounts(accumulation.failureCategories,execution.failureCategories),accumulation.passed+=execution.passed,accumulation.failed+=execution.failed,accumulation.skipped+=execution.skipped,accumulation.trackingWarnings.push(...execution.trackingWarnings),accumulation.results.push(execution.result),emitTestAllProgress(context.options,`item ${itemIndex+1}/${itemTests.length} end id=${entry.item.id} status=${execution.failed===0?"passed":"failed"} passed=${execution.passed} failed=${execution.failed} skipped=${execution.skipped}`)}const[conflict]=context.revisionConflicts;if(conflict!==void 0)throw conflict},appendContextPreflightWarning=(options,accumulation)=>{if(options.checkContext!==!0)return;const allRunResults=accumulation.results.flatMap(entry=>entry.run_results),preflight=summarizeContextPreflight(allRunResults);accumulation.trackingWarnings.push(`context_preflight:checked_pm_commands=${preflight.checked_pm_commands};tracker_read_commands=${preflight.tracker_read_commands};mismatches=${preflight.mismatches};auto_remediated=${preflight.auto_remediated}`)},buildTestAllResult=params=>({ok:params.ok,totals:{items:params.filteredItems.length,linked_tests:params.linkedTests,passed:params.accumulation.passed,failed:params.accumulation.failed,skipped:params.accumulation.skipped,failure_categories:params.accumulation.failureCategories},failed:params.accumulation.failed,passed:params.accumulation.passed,skipped:params.accumulation.skipped,fail_on_skipped_triggered:params.failOnSkippedTriggered?!0:void 0,fail_on_empty_test_run_triggered:params.failOnEmptyTestRunTriggered?!0:void 0,warnings:params.accumulation.trackingWarnings.length>0?params.accumulation.trackingWarnings:void 0,results:params.accumulation.results}),buildTestAllSelectionProgress=params=>[`selection items=${params.filteredItems.length}`,`linked_tests=${params.linkedTests}`,params.statusFilter===void 0?"":`status=${params.statusFilter}`,params.limitFilter===void 0?"":`limit=${params.limitFilter}`,params.offsetFilter>0?`offset=${params.offsetFilter}`:""].filter(entry=>entry.length>0).join(" "),prepareTestAllRun=async(options,global2)=>{const pmRoot=resolvePmRoot(process.cwd(),global2.path);await assertInitializedTracker(pmRoot);const settings=await readSettings(pmRoot),selection=await selectTestAllItems({pmRoot,settings,options}),defaultTimeoutSeconds=parseTimeout(options.timeout),runStartedAt=nowIso(),collected=await collectTestAllItemTests(selection.filteredItems,global2,pmRoot);return emitTestAllProgress(options,buildTestAllSelectionProgress({...selection,linkedTests:collected.linkedTests})),{pmRoot,settings,filteredItems:selection.filteredItems,itemTests:collected.itemTests,linkedTests:collected.linkedTests,defaultTimeoutSeconds,runStartedAt}},resolveTestAllOutcome=params=>{const failOnSkippedTriggered=params.options.failOnSkipped===!0&¶ms.accumulation.skipped>0,ok=![params.accumulation.failed>0,failOnSkippedTriggered,params.failOnEmptyTestRunTriggered].includes(!0);return{failOnSkippedTriggered,ok}},runTestAll=async(options,global2)=>{const prepared=await prepareTestAllRun(options,global2),{accumulation,failOnEmptyTestRunTriggered}=initializeTestAllAccumulation(options,prepared.linkedTests,prepared.filteredItems),itemRunContext=buildTestAllItemRunContext({pmRoot:prepared.pmRoot,settings:prepared.settings,options,itemTests:prepared.itemTests,defaultTimeoutSeconds:prepared.defaultTimeoutSeconds,runStartedAt:prepared.runStartedAt});await runTestAllItems(prepared.itemTests,itemRunContext,accumulation),appendContextPreflightWarning(options,accumulation);const outcome=resolveTestAllOutcome({options,accumulation,failOnEmptyTestRunTriggered});return emitTestAllProgress(options,`end status=${outcome.ok?"passed":"failed"} items=${prepared.filteredItems.length} linked_tests=${prepared.linkedTests} passed=${accumulation.passed} failed=${accumulation.failed} skipped=${accumulation.skipped}`),buildTestAllResult({ok:outcome.ok,filteredItems:prepared.filteredItems,linkedTests:prepared.linkedTests,accumulation,failOnSkippedTriggered:outcome.failOnSkippedTriggered,failOnEmptyTestRunTriggered})},_testOnlyTestAll={buildLinkedTestKey,formatTrackingError};export{_testOnlyTestAll,runTestAll};
|
|
3
3
|
//# sourceMappingURL=batch.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
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]="c7e43b2c-eac0-57a2-84bb-33b474dc545c")}catch{}})();import{readJsonPathValue,splitJsonPathSegments}from"./json-path.js";import{detectEmptyLinkedTestRun,findLinkedTestExecutionReceipt,renderLinkedTestRestoreCommand,resolveLinkedTestRestoreScope}from"./execution-receipts.js";import{seedLinkedTestWorkspaceSnapshot}from"./workspace-snapshot.js";import{detectLinkedTestCommandNotFound}from"./command-resolution.js";import{updateLinkedTest,parseLinkedTestUpdateOptions,assertLinkedTestEditUsage}from"./update.js";import{admitLinkedTestRequest,assertLinkedTestCommand,linkedTestsHaveSameIdentity}from"./entry.js";import{assertInitializedTracker}from"../environment/tracker-preflight.js";import{spawn}from"node:child_process";import{cp,mkdir,mkdtemp,open,readdir,rm,symlink}from"node:fs/promises";import{tmpdir}from"node:os";import path from"node:path";import{getActiveExtensionRegistrations}from"../../core/extensions/index.js";import{isFileMissingError,pathExists,readFileIfExists}from"../../core/fs/fs-utils.js";import{getWorkspaceHistoryPath,writeWorkspaceJsonWithHistory}from"../../core/history/workspace-history.js";import{resolveItemTypeRegistry}from"../../core/item/type-registry.js";import{createStdinTokenResolver,parseCsvKv,parseOptionalNumber}from"../../core/item/parse.js";import{EXIT_CODE}from"../../core/shared/constants.js";import{resolveAuthor}from"../../core/shared/author.js";import{PmCliError}from"../../core/shared/errors.js";import{stableValueEquals}from"../../core/shared/serialization.js";import{nowIso}from"../../core/shared/time.js";import{locateItem,mutateItem,readLocatedItem}from"../../core/store/item-store.js";import{getSettingsPath,ITEM_FILE_EXTENSIONS,resolveGlobalPmRoot,resolvePmRoot}from"../../core/store/paths.js";import{readSettings}from"../../core/store/settings.js";import{appendTrackedTestRunSummary,buildTrackedTestRunHistory}from"../../core/test/item-test-run-tracking.js";import{runInit}from"../init.js";import{looksLikeStructuredLinkedTestEntry,normalizeStructuredLinkedTestEntry}from"./entry.js";import{LINKED_TEST_PM_CONTEXT_MODE_VALUES as PM_CONTEXT_MODE_VALUES,LINKED_TEST_PROTECTED_ENV_KEYS,parseLinkedTestAssertionEqualsMap,parseLinkedTestAssertionGteMap,parseLinkedTestBoolean as parseLinkedTestBooleanValue,parseLinkedTestContextMode as parseLinkedTestContextModeValue,parseLinkedTestEnvClear as parseLinkedTestEnvClearValue,parseLinkedTestEnvSet as parseLinkedTestEnvSetValue,parseLinkedTestJsonEntries,parseLinkedTestMinLines,parseLinkedTestRegexList,parseLinkedTestStringList,parseLinkedTestWorkspaceContextMode}from"./parsers.js";import{parseOnlyIndexValue,resolveLinkedTestRunSelection}from"../../core/test/run-selectors.js";import{SCOPE_VALUES}from"../../types/index.js";import{diffTestRunMeasurements,parseTestRunMeasurements,queryTestRunMeasurementsBelow}from"./measurements.js";import{withHostEnvironmentBoundary}from"../environment/host-environment-errors.js";import{SOURCE_CONTEXT_ACCESS_ENV}from"../environment/source-context.js";import{assertTestMeasurementExecution,assertTestMeasurementRun}from"./prerequisites.js";import{acknowledgeLinkedTests,attachLinkedTestMutationProvenance,resolveLinkedTestSourceRef,resolveLinkedTestSourceWorkspaceRoot,resolveLinkedTestTrust}from"./trust.js";const TEST_OUTPUT_MAX_BUFFER_BYTES=20*1024*1024,DEFAULT_LINKED_TEST_TIMEOUT_FORCE_KILL_DELAY_MS=3e3,DEFAULT_LINKED_TEST_HEARTBEAT_INTERVAL_MS=1e4,DEFAULT_LINKED_TEST_PIPE_CLOSE_GRACE_MS=5e3,MAX_LINKED_TEST_COMMAND_LABEL_LENGTH=120,LINKED_TEST_TRACKER_DIRS_TO_SKIP=new Set(["locks","extensions","runtime"]),LINKED_TEST_ITEM_COUNT_DIRS_TO_SKIP=new Set(["history","index","search","extensions","locks","runtime"]),LINKED_TEST_INFRA_COLLISION_PATTERNS=[/eaddrinuse/i,/address already in use/i,/port\s+\d+\s+is already in use/i,/web server[^.\n]*already running/i,/failed to listen on/i],LINKED_TEST_LOCK_ACQUISITION_PATTERN=/\b(?:could not|failed to|unable to)\s+acquire\b[^\n.]*\block\b/i,LINKED_TEST_LOCK_CONTENTION_CONTEXT_PATTERN=/\b(?:already running|timed?\s*out|timeout|held by another|lock\s+is\s+held)\b/i,PM_TRACKER_READ_SUBCOMMANDS=new Set(["activity","calendar","context","ctx","deps","get","health","history","list","list-all","list-blocked","list-canceled","list-closed","list-draft","list-in-progress","list-open","search","stats","test-all","validate"]);function readPositiveIntegerEnv(name,fallback){const raw=process.env[name];if(typeof raw!="string"||raw.trim().length===0)return fallback;const parsed=Number.parseInt(raw,10);return!Number.isFinite(parsed)||parsed<=0?fallback:parsed}function linkedTestTimeoutForceKillDelayMs(){return readPositiveIntegerEnv("PM_LINKED_TEST_TIMEOUT_FORCE_KILL_DELAY_MS",DEFAULT_LINKED_TEST_TIMEOUT_FORCE_KILL_DELAY_MS)}function linkedTestHeartbeatIntervalMs(){return readPositiveIntegerEnv("PM_LINKED_TEST_HEARTBEAT_INTERVAL_MS",DEFAULT_LINKED_TEST_HEARTBEAT_INTERVAL_MS)}function linkedTestPipeCloseGraceMs(){return readPositiveIntegerEnv("PM_LINKED_TEST_PIPE_CLOSE_GRACE_MS",DEFAULT_LINKED_TEST_PIPE_CLOSE_GRACE_MS)}function resolveTrackedRunId(kind){const fromEnv=process.env.PM_BACKGROUND_TEST_RUN_ID?.trim();return fromEnv&&fromEnv.length>0?fromEnv:`${kind}-local-${Date.now().toString(36)}-${Math.random().toString(36).slice(2,8)}`}function summarizeRunResultStatuses(results){let passed=0,failed=0,skipped=0;for(const entry of results){if(entry.status==="passed"){passed+=1;continue}if(entry.status==="failed"||entry.status==="refused"){failed+=1;continue}skipped+=1}return{passed,failed,skipped}}function summarizeContextPreflight(runResults){let checkedPmCommands=0,trackerReadCommands=0,mismatches=0,autoRemediated=0;for(const result of runResults){const context=result.execution_context;!context||context.is_pm_command!==!0||(checkedPmCommands+=1,context.is_pm_tracker_read_command===!0&&(trackerReadCommands+=1),context.mismatch_detected===!0&&(mismatches+=1),context.auto_pm_context_applied===!0&&(autoRemediated+=1))}return{checked_pm_commands:checkedPmCommands,tracker_read_commands:trackerReadCommands,mismatches,auto_remediated:autoRemediated}}function ensureScope(raw){const value=raw??"project";if(!SCOPE_VALUES.includes(value))throw new PmCliError(`Invalid scope "${raw}"`,EXIT_CODE.USAGE);return value}function parsePmContextMode(raw){if(!raw)return"schema";const normalized=raw.trim().toLowerCase();if(PM_CONTEXT_MODE_VALUES.includes(normalized))return normalized;throw new PmCliError(`Invalid --pm-context value "${raw}". Expected one of: ${PM_CONTEXT_MODE_VALUES.join(", ")}`,EXIT_CODE.USAGE)}function parseWorkspaceContextMode(raw){return parseLinkedTestWorkspaceContextMode(raw??"source","--workspace-context")}function resolveLinkedTestWorkspaceContextMode(linkedTest,runLevelMode,overrideLinkedMode){return overrideLinkedMode?runLevelMode:parseLinkedTestWorkspaceContextMode(linkedTest.workspace_context_mode,"linked test")??runLevelMode}function resolveLinkedTestRequestedContextMode(linkedTest,runLevelMode,overrideLinkedPmContext){return overrideLinkedPmContext||typeof linkedTest.pm_context_mode!="string"||linkedTest.pm_context_mode.trim().length===0?runLevelMode:parsePmContextMode(linkedTest.pm_context_mode)}function resolveLinkedTestEffectiveContextMode(requestedMode,isPmTrackerReadCommand){return requestedMode==="auto"?isPmTrackerReadCommand?"tracker":"schema":requestedMode}function hasLinkedTestAssertions(linkedTest){return Object.entries(linkedTest).filter(([key])=>key.startsWith("assert_")).some(([,value])=>value==null?!1:Array.isArray(value)?value.length>0:typeof value=="object"?Object.keys(value).length>0:typeof value=="string"?value.trim().length>0:!0)}function buildPmContextMismatchHint(params){const{executionContext,runLevelPmContextMode,linkedOverridePmContextMode}=params;return!executionContext.is_pm_tracker_read_command||!executionContext.mismatch_detected?"":runLevelPmContextMode==="tracker"&&linkedOverridePmContextMode==="schema"?" Linked test metadata pm_context_mode=schema overrides run-level --pm-context tracker. Set pm_context_mode=tracker (or auto) on the linked test, or remove the override, to run against seeded tracker data.":executionContext.pm_context_mode==="schema"?" Use --auto-pm-context to route PM tracker-read linked commands through seeded tracker data automatically. Alternatively, use --pm-context tracker for the whole run.":""}function mergeEnvSetDirectives(entries,optionName){const merged={};if(!entries)return merged;for(const entry of entries){const parsed=parseLinkedTestEnvSetValue(entry,optionName);if(parsed)for(const[key,value]of Object.entries(parsed))merged[key]=value}return merged}function mergeEnvClearDirectives(entries,optionName){if(!entries)return[];const values=[];for(const entry of entries){const parsed=parseLinkedTestEnvClearValue(entry,optionName);parsed&&values.push(...parsed)}return[...new Set(values)]}import{BUN_GLOBAL_FLAGS_WITH_VALUE,NPM_GLOBAL_FLAGS_WITH_VALUE,PNPM_GLOBAL_FLAGS_WITH_VALUE,SCRIPT_RUN_FLAGS_WITH_VALUE,SCRIPT_RUN_SUBCOMMANDS,YARN_GLOBAL_FLAGS_WITH_VALUE,extractPmInvocationArgsFromSegment,parseLauncherSubcommand,parseNpmExecCommand,parseNpxCommand,parsePnpmDlxCommand,resolvePmSubcommandContext,splitNormalizedCommandSegments,stripLeadingEnvAssignments,SHELL_COMMAND_FLAG_PATTERNS,assertNoRecursiveTestAllCommand,invokesRecursiveTestAllCommand,normalizeCommandForValidation,segmentInvokesRecursiveTestAll}from"./linked-command-detection.js";import{extractReferencedPmItemIdsFromCommand}from"./linked-command-detection.js";function commandInvokesPmCli(command){const normalizedCommand=normalizeCommandForValidation(command);return splitNormalizedCommandSegments(normalizedCommand).some(segment=>extractPmInvocationArgsFromSegment(segment)!==null)}function commandInvokesPmTrackerReadCommand(command){const normalizedCommand=normalizeCommandForValidation(command);return splitNormalizedCommandSegments(normalizedCommand).some(segment=>{const invocationArgs=extractPmInvocationArgsFromSegment(segment);if(!invocationArgs)return!1;const context=resolvePmSubcommandContext(invocationArgs);return context?PM_TRACKER_READ_SUBCOMMANDS.has(context.subcommand):!1})}function resolveDirectRunnerSubcommand(parsed){if(parsed)return SCRIPT_RUN_SUBCOMMANDS.has(parsed.subcommand)?parseLauncherSubcommand(parsed.args,SCRIPT_RUN_FLAGS_WITH_VALUE)?.subcommand:parsed.subcommand}function firstDirectTestRunnerSubcommand(executable,args){if(executable==="npx"||executable==="bunx")return parseNpxCommand(args)?.command;if(executable==="pnpm")return resolveDirectRunnerSubcommand(parseLauncherSubcommand(args,PNPM_GLOBAL_FLAGS_WITH_VALUE));if(executable==="npm")return resolveDirectRunnerSubcommand(parseLauncherSubcommand(args,NPM_GLOBAL_FLAGS_WITH_VALUE));if(executable==="yarn")return resolveDirectRunnerSubcommand(parseLauncherSubcommand(args,YARN_GLOBAL_FLAGS_WITH_VALUE));if(executable==="bun")return resolveDirectRunnerSubcommand(parseLauncherSubcommand(args,BUN_GLOBAL_FLAGS_WITH_VALUE))}function isDirectTestRunnerSubcommand(token){return token?token==="vitest"||token==="test"||token.startsWith("test:"):!1}function commandUsesSandboxRunner(normalizedCommand){return normalizedCommand.includes("node scripts/run-tests.mjs ")||normalizedCommand.endsWith("node scripts/run-tests.mjs")||normalizedCommand.includes("node ./scripts/run-tests.mjs ")||normalizedCommand.endsWith("node ./scripts/run-tests.mjs")}function segmentInvokesDirectTestRunner(normalizedSegment){const rawTokens=normalizedSegment.split(" ").filter(token=>token.length>0),tokens=stripLeadingEnvAssignments(rawTokens);if(tokens.length===0)return!1;const[executable,...args]=tokens,shellCommandFlagPattern=SHELL_COMMAND_FLAG_PATTERNS.get(executable);if(shellCommandFlagPattern){const commandFlagIndex=args.findIndex(argument=>shellCommandFlagPattern.test(argument));return commandFlagIndex>=0&&segmentInvokesDirectTestRunner(args.slice(commandFlagIndex+1).join(" "))}return executable==="vitest"||executable.endsWith("/vitest")||executable.endsWith("/vitest.mjs")?!0:executable==="node"?nodeArgsInvokeDirectTestRunner(args):packageManagerInvokesDirectTestRunner(executable,args)}function nodeArgsInvokeDirectTestRunner(args){return args.includes("--test")||args.some(arg=>arg==="vitest"||arg==="vitest.mjs"||arg.endsWith("/vitest")||arg.endsWith("/vitest.mjs"))}function packageManagerInvokesDirectTestRunner(executable,args){return executable==="npx"||executable==="bunx"?isDirectTestRunnerSubcommand(parseNpxCommand(args)?.command):executable==="pnpm"||executable==="npm"?isDirectTestRunnerSubcommand((executable==="pnpm"?parsePnpmDlxCommand(args):parseNpmExecCommand(args))?.command)||isDirectTestRunnerSubcommand(firstDirectTestRunnerSubcommand(executable,args)):(executable==="yarn"||executable==="bun")&&isDirectTestRunnerSubcommand(firstDirectTestRunnerSubcommand(executable,args))}function classifyLinkedTestCommandSafety(command){const normalized=normalizeCommandForValidation(command),segments=splitNormalizedCommandSegments(normalized),directRunner=segments.some(segment=>!commandUsesSandboxRunner(segment)&&segmentInvokesDirectTestRunner(segment)),sandboxRunner=segments.some(commandUsesSandboxRunner);return{accepted:!0,command_kind:directRunner?"direct_runner":sandboxRunner?"sandbox_runner":"other",tracker_isolation:"runtime_injected",trust_boundary:"provenance",advisory:directRunner?"Accepted with runtime-injected tracker isolation. Prefer the repository sandbox runner when available for reproducible build and coverage setup.":"Accepted with runtime-injected tracker isolation and provenance checks before execution."}}function getRuntimeSafetySkipReason(command){if(invokesRecursiveTestAllCommand(command))return'Linked test command skipped: Linked test commands must not invoke "pm test-all"; this creates recursive orchestration.'}function parseAddEntries(raw){return raw?raw.map(parseAddEntry):[]}function parseAddEntry(entry){const trimmed=entry.trim(),kv=looksLikeStructuredLinkedTestEntry(trimmed)?normalizeStructuredLinkedTestEntry(parseCsvKv(entry,"--add"),"--add"):{command:trimmed},command=trimLinkedTestEntryField(kv.command);if(!command)throw new PmCliError("--add requires command=<value> or a bare command (path=<value> is optional metadata)",EXIT_CODE.USAGE);return assertNoRecursiveTestAllCommand(command),{command,path:trimLinkedTestEntryField(kv.path),scope:ensureScope(kv.scope),timeout_seconds:parseLinkedTestTimeoutSeconds(trimLinkedTestEntryField(kv.timeout_seconds),trimLinkedTestEntryField(kv.timeout)),pm_context_mode:parseLinkedTestContextModeValue(trimLinkedTestEntryField(kv.pm_context_mode),"--add"),workspace_context_mode:parseLinkedTestWorkspaceContextMode(trimLinkedTestEntryField(kv.workspace_context_mode),"--add"),env_set:parseLinkedTestEnvSetValue(trimLinkedTestEntryField(kv.env_set),"--add"),env_clear:parseLinkedTestEnvClearValue(trimLinkedTestEntryField(kv.env_clear),"--add"),shared_host_safe:parseLinkedTestBooleanValue(trimLinkedTestEntryField(kv.shared_host_safe),"--add","shared_host_safe"),assert_stdout_contains:parseLinkedTestStringList(trimLinkedTestEntryField(kv.assert_stdout_contains)),assert_stdout_regex:parseLinkedTestRegexList(trimLinkedTestEntryField(kv.assert_stdout_regex),"--add","assert_stdout_regex"),assert_stderr_contains:parseLinkedTestStringList(trimLinkedTestEntryField(kv.assert_stderr_contains)),assert_stderr_regex:parseLinkedTestRegexList(trimLinkedTestEntryField(kv.assert_stderr_regex),"--add","assert_stderr_regex"),assert_stdout_min_lines:parseLinkedTestMinLines(trimLinkedTestEntryField(kv.assert_stdout_min_lines),"--add"),assert_json_field_equals:parseLinkedTestAssertionEqualsMap(trimLinkedTestEntryField(kv.assert_json_field_equals),"--add"),assert_json_field_gte:parseLinkedTestAssertionGteMap(trimLinkedTestEntryField(kv.assert_json_field_gte),"--add"),note:trimLinkedTestEntryField(kv.note)}}function trimLinkedTestEntryField(value){const trimmed=value?.trim();return trimmed&&trimmed.length>0?trimmed:void 0}function parseLinkedTestTimeoutSeconds(timeoutSecondsRaw,timeoutAliasRaw){if(timeoutSecondsRaw&&timeoutAliasRaw&&timeoutSecondsRaw!==timeoutAliasRaw)throw new PmCliError("--add timeout and timeout_seconds must match when both are provided",EXIT_CODE.USAGE);const timeoutRaw=timeoutSecondsRaw??timeoutAliasRaw;if(timeoutRaw===void 0)return;const timeout=Math.floor(parseOptionalNumber(timeoutRaw,"timeout_seconds"));if(timeout<=0)throw new PmCliError("--add timeout_seconds must be a positive integer",EXIT_CODE.USAGE);return timeout}function parseAddJsonEntries(raw){return raw?raw.flatMap(entry=>{const parsed=parseLinkedTestJsonEntries(entry,"--add-json");for(const linkedTest of parsed){const command=linkedTest.command;if(!command)throw new PmCliError("--add-json requires a non-empty command string",EXIT_CODE.USAGE);assertNoRecursiveTestAllCommand(command)}return parsed}):[]}function parseRemoveEntries(raw){return raw?raw.map(entry=>{const trimmed=entry.trim();if(!trimmed)throw new PmCliError("--remove requires command or path value",EXIT_CODE.USAGE);const identityPrefix=/^(command|path)\s*=/i.exec(trimmed);if(identityPrefix){const value=trimmed.slice(identityPrefix[0].length).trim();if(!value)throw new PmCliError("--remove requires a non-empty command=<value> or path=<value>",EXIT_CODE.USAGE);return{kind:identityPrefix[1].toLowerCase(),value}}if(/^[A-Za-z_][\w-]*=\S+$/u.test(trimmed))throw new PmCliError("--remove structured selectors require command=<value> or path=<value>",EXIT_CODE.USAGE);if(/^(?:[-*+]\s+)?(?:path|command)\s*:/i.test(trimmed)||trimmed.startsWith("```")){const kv=parseCsvKv(trimmed,"--remove"),kind=kv.path!==void 0?"path":"command",value=kv.path??kv.command;if(!value?.trim())throw new PmCliError("--remove requires command=<value> and/or path=<value>",EXIT_CODE.USAGE);return{kind,value:value.trim()}}return{kind:"either",value:trimmed}}):[]}function parseRemoveIndexes(raw){return raw?[...new Set(raw.map(value=>{const parsed=typeof value=="number"?value:Number.parseInt(value,10);if(!Number.isInteger(parsed)||parsed<1||String(parsed)!==String(value).trim())throw new PmCliError("--remove-index must be a positive integer",EXIT_CODE.USAGE);return parsed}))]:[]}function closeLinkedTestStdin(child){try{child.stdin?.end()}catch{}}function summarizeLinkedTestCommand(command){const normalized=command.trim().replaceAll(/\s+/g," ");return normalized.length<=MAX_LINKED_TEST_COMMAND_LABEL_LENGTH?normalized:`${normalized.slice(0,MAX_LINKED_TEST_COMMAND_LABEL_LENGTH-3)}...`}function shouldEmitLinkedTestProgress(mode){return mode==="off"?!1:mode==="always"?!0:process.stderr.isTTY===!0}function emitLinkedTestProgress(message){try{process.stderr.write(`${message}
|
|
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]="0ba78f5c-3dbc-50eb-b6a4-df29b4d9b251")}catch{}})();import{readJsonPathValue,splitJsonPathSegments}from"./json-path.js";import{isItemRevisionConflict}from"../lifecycle/item-revision.js";import{detectEmptyLinkedTestRun,findLinkedTestExecutionReceipt,renderLinkedTestRestoreCommand,resolveLinkedTestRestoreScope}from"./execution-receipts.js";import{seedLinkedTestWorkspaceSnapshot}from"./workspace-snapshot.js";import{detectLinkedTestCommandNotFound}from"./command-resolution.js";import{updateLinkedTest,parseLinkedTestUpdateOptions,assertLinkedTestEditUsage}from"./update.js";import{admitLinkedTestRequest,assertLinkedTestCommand,linkedTestsHaveSameIdentity}from"./entry.js";import{assertInitializedTracker}from"../environment/tracker-preflight.js";import{spawn}from"node:child_process";import{cp,mkdir,mkdtemp,open,readdir,rm,symlink}from"node:fs/promises";import{tmpdir}from"node:os";import path from"node:path";import{getActiveExtensionRegistrations}from"../../core/extensions/index.js";import{isFileMissingError,pathExists,readFileIfExists}from"../../core/fs/fs-utils.js";import{getWorkspaceHistoryPath,writeWorkspaceJsonWithHistory}from"../../core/history/workspace-history.js";import{resolveItemTypeRegistry}from"../../core/item/type-registry.js";import{createStdinTokenResolver,parseCsvKv,parseOptionalNumber}from"../../core/item/parse.js";import{EXIT_CODE}from"../../core/shared/constants.js";import{resolveAuthor}from"../../core/shared/author.js";import{PmCliError}from"../../core/shared/errors.js";import{stableValueEquals}from"../../core/shared/serialization.js";import{nowIso}from"../../core/shared/time.js";import{locateItem,mutateItem,readLocatedItem}from"../../core/store/item-store.js";import{getSettingsPath,ITEM_FILE_EXTENSIONS,resolveGlobalPmRoot,resolvePmRoot}from"../../core/store/paths.js";import{readSettings}from"../../core/store/settings.js";import{appendTrackedTestRunSummary,buildTrackedTestRunHistory}from"../../core/test/item-test-run-tracking.js";import{runInit}from"../init.js";import{looksLikeStructuredLinkedTestEntry,normalizeStructuredLinkedTestEntry}from"./entry.js";import{LINKED_TEST_PM_CONTEXT_MODE_VALUES as PM_CONTEXT_MODE_VALUES,LINKED_TEST_PROTECTED_ENV_KEYS,parseLinkedTestAssertionEqualsMap,parseLinkedTestAssertionGteMap,parseLinkedTestBoolean as parseLinkedTestBooleanValue,parseLinkedTestContextMode as parseLinkedTestContextModeValue,parseLinkedTestEnvClear as parseLinkedTestEnvClearValue,parseLinkedTestEnvSet as parseLinkedTestEnvSetValue,parseLinkedTestJsonEntries,parseLinkedTestMinLines,parseLinkedTestRegexList,parseLinkedTestStringList,parseLinkedTestWorkspaceContextMode}from"./parsers.js";import{parseOnlyIndexValue,resolveLinkedTestRunSelection}from"../../core/test/run-selectors.js";import{SCOPE_VALUES}from"../../types/index.js";import{diffTestRunMeasurements,parseTestRunMeasurements,queryTestRunMeasurementsBelow}from"./measurements.js";import{withHostEnvironmentBoundary}from"../environment/host-environment-errors.js";import{SOURCE_CONTEXT_ACCESS_ENV}from"../environment/source-context.js";import{assertTestMeasurementExecution,assertTestMeasurementRun}from"./prerequisites.js";import{acknowledgeLinkedTests,attachLinkedTestMutationProvenance,resolveLinkedTestSourceRef,resolveLinkedTestSourceWorkspaceRoot,resolveLinkedTestTrust}from"./trust.js";const TEST_OUTPUT_MAX_BUFFER_BYTES=20*1024*1024,DEFAULT_LINKED_TEST_TIMEOUT_FORCE_KILL_DELAY_MS=3e3,DEFAULT_LINKED_TEST_HEARTBEAT_INTERVAL_MS=1e4,DEFAULT_LINKED_TEST_PIPE_CLOSE_GRACE_MS=5e3,MAX_LINKED_TEST_COMMAND_LABEL_LENGTH=120,LINKED_TEST_TRACKER_DIRS_TO_SKIP=new Set(["locks","extensions","runtime"]),LINKED_TEST_ITEM_COUNT_DIRS_TO_SKIP=new Set(["history","index","search","extensions","locks","runtime"]),LINKED_TEST_INFRA_COLLISION_PATTERNS=[/eaddrinuse/i,/address already in use/i,/port\s+\d+\s+is already in use/i,/web server[^.\n]*already running/i,/failed to listen on/i],LINKED_TEST_LOCK_ACQUISITION_PATTERN=/\b(?:could not|failed to|unable to)\s+acquire\b[^\n.]*\block\b/i,LINKED_TEST_LOCK_CONTENTION_CONTEXT_PATTERN=/\b(?:already running|timed?\s*out|timeout|held by another|lock\s+is\s+held)\b/i,PM_TRACKER_READ_SUBCOMMANDS=new Set(["activity","calendar","context","ctx","deps","get","health","history","list","list-all","list-blocked","list-canceled","list-closed","list-draft","list-in-progress","list-open","search","stats","test-all","validate"]);function readPositiveIntegerEnv(name,fallback){const raw=process.env[name];if(typeof raw!="string"||raw.trim().length===0)return fallback;const parsed=Number.parseInt(raw,10);return!Number.isFinite(parsed)||parsed<=0?fallback:parsed}function linkedTestTimeoutForceKillDelayMs(){return readPositiveIntegerEnv("PM_LINKED_TEST_TIMEOUT_FORCE_KILL_DELAY_MS",DEFAULT_LINKED_TEST_TIMEOUT_FORCE_KILL_DELAY_MS)}function linkedTestHeartbeatIntervalMs(){return readPositiveIntegerEnv("PM_LINKED_TEST_HEARTBEAT_INTERVAL_MS",DEFAULT_LINKED_TEST_HEARTBEAT_INTERVAL_MS)}function linkedTestPipeCloseGraceMs(){return readPositiveIntegerEnv("PM_LINKED_TEST_PIPE_CLOSE_GRACE_MS",DEFAULT_LINKED_TEST_PIPE_CLOSE_GRACE_MS)}function resolveTrackedRunId(kind){const fromEnv=process.env.PM_BACKGROUND_TEST_RUN_ID?.trim();return fromEnv&&fromEnv.length>0?fromEnv:`${kind}-local-${Date.now().toString(36)}-${Math.random().toString(36).slice(2,8)}`}function summarizeRunResultStatuses(results){let passed=0,failed=0,skipped=0;for(const entry of results){if(entry.status==="passed"){passed+=1;continue}if(entry.status==="failed"||entry.status==="refused"){failed+=1;continue}skipped+=1}return{passed,failed,skipped}}function summarizeContextPreflight(runResults){let checkedPmCommands=0,trackerReadCommands=0,mismatches=0,autoRemediated=0;for(const result of runResults){const context=result.execution_context;!context||context.is_pm_command!==!0||(checkedPmCommands+=1,context.is_pm_tracker_read_command===!0&&(trackerReadCommands+=1),context.mismatch_detected===!0&&(mismatches+=1),context.auto_pm_context_applied===!0&&(autoRemediated+=1))}return{checked_pm_commands:checkedPmCommands,tracker_read_commands:trackerReadCommands,mismatches,auto_remediated:autoRemediated}}function ensureScope(raw){const value=raw??"project";if(!SCOPE_VALUES.includes(value))throw new PmCliError(`Invalid scope "${raw}"`,EXIT_CODE.USAGE);return value}function parsePmContextMode(raw){if(!raw)return"schema";const normalized=raw.trim().toLowerCase();if(PM_CONTEXT_MODE_VALUES.includes(normalized))return normalized;throw new PmCliError(`Invalid --pm-context value "${raw}". Expected one of: ${PM_CONTEXT_MODE_VALUES.join(", ")}`,EXIT_CODE.USAGE)}function parseWorkspaceContextMode(raw){return parseLinkedTestWorkspaceContextMode(raw??"source","--workspace-context")}function resolveLinkedTestWorkspaceContextMode(linkedTest,runLevelMode,overrideLinkedMode){return overrideLinkedMode?runLevelMode:parseLinkedTestWorkspaceContextMode(linkedTest.workspace_context_mode,"linked test")??runLevelMode}function resolveLinkedTestRequestedContextMode(linkedTest,runLevelMode,overrideLinkedPmContext){return overrideLinkedPmContext||typeof linkedTest.pm_context_mode!="string"||linkedTest.pm_context_mode.trim().length===0?runLevelMode:parsePmContextMode(linkedTest.pm_context_mode)}function resolveLinkedTestEffectiveContextMode(requestedMode,isPmTrackerReadCommand){return requestedMode==="auto"?isPmTrackerReadCommand?"tracker":"schema":requestedMode}function hasLinkedTestAssertions(linkedTest){return Object.entries(linkedTest).filter(([key])=>key.startsWith("assert_")).some(([,value])=>value==null?!1:Array.isArray(value)?value.length>0:typeof value=="object"?Object.keys(value).length>0:typeof value=="string"?value.trim().length>0:!0)}function buildPmContextMismatchHint(params){const{executionContext,runLevelPmContextMode,linkedOverridePmContextMode}=params;return!executionContext.is_pm_tracker_read_command||!executionContext.mismatch_detected?"":runLevelPmContextMode==="tracker"&&linkedOverridePmContextMode==="schema"?" Linked test metadata pm_context_mode=schema overrides run-level --pm-context tracker. Set pm_context_mode=tracker (or auto) on the linked test, or remove the override, to run against seeded tracker data.":executionContext.pm_context_mode==="schema"?" Use --auto-pm-context to route PM tracker-read linked commands through seeded tracker data automatically. Alternatively, use --pm-context tracker for the whole run.":""}function mergeEnvSetDirectives(entries,optionName){const merged={};if(!entries)return merged;for(const entry of entries){const parsed=parseLinkedTestEnvSetValue(entry,optionName);if(parsed)for(const[key,value]of Object.entries(parsed))merged[key]=value}return merged}function mergeEnvClearDirectives(entries,optionName){if(!entries)return[];const values=[];for(const entry of entries){const parsed=parseLinkedTestEnvClearValue(entry,optionName);parsed&&values.push(...parsed)}return[...new Set(values)]}import{BUN_GLOBAL_FLAGS_WITH_VALUE,NPM_GLOBAL_FLAGS_WITH_VALUE,PNPM_GLOBAL_FLAGS_WITH_VALUE,SCRIPT_RUN_FLAGS_WITH_VALUE,SCRIPT_RUN_SUBCOMMANDS,YARN_GLOBAL_FLAGS_WITH_VALUE,extractPmInvocationArgsFromSegment,parseLauncherSubcommand,parseNpmExecCommand,parseNpxCommand,parsePnpmDlxCommand,resolvePmSubcommandContext,splitNormalizedCommandSegments,stripLeadingEnvAssignments,SHELL_COMMAND_FLAG_PATTERNS,assertNoRecursiveTestAllCommand,invokesRecursiveTestAllCommand,normalizeCommandForValidation,segmentInvokesRecursiveTestAll}from"./linked-command-detection.js";import{extractReferencedPmItemIdsFromCommand}from"./linked-command-detection.js";function commandInvokesPmCli(command){const normalizedCommand=normalizeCommandForValidation(command);return splitNormalizedCommandSegments(normalizedCommand).some(segment=>extractPmInvocationArgsFromSegment(segment)!==null)}function commandInvokesPmTrackerReadCommand(command){const normalizedCommand=normalizeCommandForValidation(command);return splitNormalizedCommandSegments(normalizedCommand).some(segment=>{const invocationArgs=extractPmInvocationArgsFromSegment(segment);if(!invocationArgs)return!1;const context=resolvePmSubcommandContext(invocationArgs);return context?PM_TRACKER_READ_SUBCOMMANDS.has(context.subcommand):!1})}function resolveDirectRunnerSubcommand(parsed){if(parsed)return SCRIPT_RUN_SUBCOMMANDS.has(parsed.subcommand)?parseLauncherSubcommand(parsed.args,SCRIPT_RUN_FLAGS_WITH_VALUE)?.subcommand:parsed.subcommand}function firstDirectTestRunnerSubcommand(executable,args){if(executable==="npx"||executable==="bunx")return parseNpxCommand(args)?.command;if(executable==="pnpm")return resolveDirectRunnerSubcommand(parseLauncherSubcommand(args,PNPM_GLOBAL_FLAGS_WITH_VALUE));if(executable==="npm")return resolveDirectRunnerSubcommand(parseLauncherSubcommand(args,NPM_GLOBAL_FLAGS_WITH_VALUE));if(executable==="yarn")return resolveDirectRunnerSubcommand(parseLauncherSubcommand(args,YARN_GLOBAL_FLAGS_WITH_VALUE));if(executable==="bun")return resolveDirectRunnerSubcommand(parseLauncherSubcommand(args,BUN_GLOBAL_FLAGS_WITH_VALUE))}function isDirectTestRunnerSubcommand(token){return token?token==="vitest"||token==="test"||token.startsWith("test:"):!1}function commandUsesSandboxRunner(normalizedCommand){return normalizedCommand.includes("node scripts/run-tests.mjs ")||normalizedCommand.endsWith("node scripts/run-tests.mjs")||normalizedCommand.includes("node ./scripts/run-tests.mjs ")||normalizedCommand.endsWith("node ./scripts/run-tests.mjs")}function segmentInvokesDirectTestRunner(normalizedSegment){const rawTokens=normalizedSegment.split(" ").filter(token=>token.length>0),tokens=stripLeadingEnvAssignments(rawTokens);if(tokens.length===0)return!1;const[executable,...args]=tokens,shellCommandFlagPattern=SHELL_COMMAND_FLAG_PATTERNS.get(executable);if(shellCommandFlagPattern){const commandFlagIndex=args.findIndex(argument=>shellCommandFlagPattern.test(argument));return commandFlagIndex>=0&&segmentInvokesDirectTestRunner(args.slice(commandFlagIndex+1).join(" "))}return executable==="vitest"||executable.endsWith("/vitest")||executable.endsWith("/vitest.mjs")?!0:executable==="node"?nodeArgsInvokeDirectTestRunner(args):packageManagerInvokesDirectTestRunner(executable,args)}function nodeArgsInvokeDirectTestRunner(args){return args.includes("--test")||args.some(arg=>arg==="vitest"||arg==="vitest.mjs"||arg.endsWith("/vitest")||arg.endsWith("/vitest.mjs"))}function packageManagerInvokesDirectTestRunner(executable,args){return executable==="npx"||executable==="bunx"?isDirectTestRunnerSubcommand(parseNpxCommand(args)?.command):executable==="pnpm"||executable==="npm"?isDirectTestRunnerSubcommand((executable==="pnpm"?parsePnpmDlxCommand(args):parseNpmExecCommand(args))?.command)||isDirectTestRunnerSubcommand(firstDirectTestRunnerSubcommand(executable,args)):(executable==="yarn"||executable==="bun")&&isDirectTestRunnerSubcommand(firstDirectTestRunnerSubcommand(executable,args))}function classifyLinkedTestCommandSafety(command){const normalized=normalizeCommandForValidation(command),segments=splitNormalizedCommandSegments(normalized),directRunner=segments.some(segment=>!commandUsesSandboxRunner(segment)&&segmentInvokesDirectTestRunner(segment)),sandboxRunner=segments.some(commandUsesSandboxRunner);return{accepted:!0,command_kind:directRunner?"direct_runner":sandboxRunner?"sandbox_runner":"other",tracker_isolation:"runtime_injected",trust_boundary:"provenance",advisory:directRunner?"Accepted with runtime-injected tracker isolation. Prefer the repository sandbox runner when available for reproducible build and coverage setup.":"Accepted with runtime-injected tracker isolation and provenance checks before execution."}}function getRuntimeSafetySkipReason(command){if(invokesRecursiveTestAllCommand(command))return'Linked test command skipped: Linked test commands must not invoke "pm test-all"; this creates recursive orchestration.'}function parseAddEntries(raw){return raw?raw.map(parseAddEntry):[]}function parseAddEntry(entry){const trimmed=entry.trim(),kv=looksLikeStructuredLinkedTestEntry(trimmed)?normalizeStructuredLinkedTestEntry(parseCsvKv(entry,"--add"),"--add"):{command:trimmed},command=trimLinkedTestEntryField(kv.command);if(!command)throw new PmCliError("--add requires command=<value> or a bare command (path=<value> is optional metadata)",EXIT_CODE.USAGE);return assertNoRecursiveTestAllCommand(command),{command,path:trimLinkedTestEntryField(kv.path),scope:ensureScope(kv.scope),timeout_seconds:parseLinkedTestTimeoutSeconds(trimLinkedTestEntryField(kv.timeout_seconds),trimLinkedTestEntryField(kv.timeout)),pm_context_mode:parseLinkedTestContextModeValue(trimLinkedTestEntryField(kv.pm_context_mode),"--add"),workspace_context_mode:parseLinkedTestWorkspaceContextMode(trimLinkedTestEntryField(kv.workspace_context_mode),"--add"),env_set:parseLinkedTestEnvSetValue(trimLinkedTestEntryField(kv.env_set),"--add"),env_clear:parseLinkedTestEnvClearValue(trimLinkedTestEntryField(kv.env_clear),"--add"),shared_host_safe:parseLinkedTestBooleanValue(trimLinkedTestEntryField(kv.shared_host_safe),"--add","shared_host_safe"),assert_stdout_contains:parseLinkedTestStringList(trimLinkedTestEntryField(kv.assert_stdout_contains)),assert_stdout_regex:parseLinkedTestRegexList(trimLinkedTestEntryField(kv.assert_stdout_regex),"--add","assert_stdout_regex"),assert_stderr_contains:parseLinkedTestStringList(trimLinkedTestEntryField(kv.assert_stderr_contains)),assert_stderr_regex:parseLinkedTestRegexList(trimLinkedTestEntryField(kv.assert_stderr_regex),"--add","assert_stderr_regex"),assert_stdout_min_lines:parseLinkedTestMinLines(trimLinkedTestEntryField(kv.assert_stdout_min_lines),"--add"),assert_json_field_equals:parseLinkedTestAssertionEqualsMap(trimLinkedTestEntryField(kv.assert_json_field_equals),"--add"),assert_json_field_gte:parseLinkedTestAssertionGteMap(trimLinkedTestEntryField(kv.assert_json_field_gte),"--add"),note:trimLinkedTestEntryField(kv.note)}}function trimLinkedTestEntryField(value){const trimmed=value?.trim();return trimmed&&trimmed.length>0?trimmed:void 0}function parseLinkedTestTimeoutSeconds(timeoutSecondsRaw,timeoutAliasRaw){if(timeoutSecondsRaw&&timeoutAliasRaw&&timeoutSecondsRaw!==timeoutAliasRaw)throw new PmCliError("--add timeout and timeout_seconds must match when both are provided",EXIT_CODE.USAGE);const timeoutRaw=timeoutSecondsRaw??timeoutAliasRaw;if(timeoutRaw===void 0)return;const timeout=Math.floor(parseOptionalNumber(timeoutRaw,"timeout_seconds"));if(timeout<=0)throw new PmCliError("--add timeout_seconds must be a positive integer",EXIT_CODE.USAGE);return timeout}function parseAddJsonEntries(raw){return raw?raw.flatMap(entry=>{const parsed=parseLinkedTestJsonEntries(entry,"--add-json");for(const linkedTest of parsed){const command=linkedTest.command;if(!command)throw new PmCliError("--add-json requires a non-empty command string",EXIT_CODE.USAGE);assertNoRecursiveTestAllCommand(command)}return parsed}):[]}function parseRemoveEntries(raw){return raw?raw.map(entry=>{const trimmed=entry.trim();if(!trimmed)throw new PmCliError("--remove requires command or path value",EXIT_CODE.USAGE);const identityPrefix=/^(command|path)\s*=/i.exec(trimmed);if(identityPrefix){const value=trimmed.slice(identityPrefix[0].length).trim();if(!value)throw new PmCliError("--remove requires a non-empty command=<value> or path=<value>",EXIT_CODE.USAGE);return{kind:identityPrefix[1].toLowerCase(),value}}if(/^[A-Za-z_][\w-]*=\S+$/u.test(trimmed))throw new PmCliError("--remove structured selectors require command=<value> or path=<value>",EXIT_CODE.USAGE);if(/^(?:[-*+]\s+)?(?:path|command)\s*:/i.test(trimmed)||trimmed.startsWith("```")){const kv=parseCsvKv(trimmed,"--remove"),kind=kv.path!==void 0?"path":"command",value=kv.path??kv.command;if(!value?.trim())throw new PmCliError("--remove requires command=<value> and/or path=<value>",EXIT_CODE.USAGE);return{kind,value:value.trim()}}return{kind:"either",value:trimmed}}):[]}function parseRemoveIndexes(raw){return raw?[...new Set(raw.map(value=>{const parsed=typeof value=="number"?value:Number.parseInt(value,10);if(!Number.isInteger(parsed)||parsed<1||String(parsed)!==String(value).trim())throw new PmCliError("--remove-index must be a positive integer",EXIT_CODE.USAGE);return parsed}))]:[]}function closeLinkedTestStdin(child){try{child.stdin?.end()}catch{}}function summarizeLinkedTestCommand(command){const normalized=command.trim().replaceAll(/\s+/g," ");return normalized.length<=MAX_LINKED_TEST_COMMAND_LABEL_LENGTH?normalized:`${normalized.slice(0,MAX_LINKED_TEST_COMMAND_LABEL_LENGTH-3)}...`}function shouldEmitLinkedTestProgress(mode){return mode==="off"?!1:mode==="always"?!0:process.stderr.isTTY===!0}function emitLinkedTestProgress(message){try{process.stderr.write(`${message}
|
|
2
2
|
`)}catch{}}function beginLinkedTestProgress(context,mode){if(!shouldEmitLinkedTestProgress(mode))return null;const commandLabel=summarizeLinkedTestCommand(context.command),startAt=Date.now();emitLinkedTestProgress(`[pm test] linked-test ${context.index}/${context.total} start timeout_ms=${context.timeoutMs} command="${commandLabel}"`);const heartbeat=setInterval(()=>{const elapsedMs=Date.now()-startAt;emitLinkedTestProgress(`[pm test] linked-test ${context.index}/${context.total} running elapsed_ms=${elapsedMs} command="${commandLabel}"`)},linkedTestHeartbeatIntervalMs());return heartbeat.unref?.(),heartbeat}function endLinkedTestProgress(context,executionResult,startedAt,mode){if(!shouldEmitLinkedTestProgress(mode))return;const commandLabel=summarizeLinkedTestCommand(context.command),elapsedMs=Date.now()-startedAt,statusLabel=executionResult.timedOut||executionResult.maxBufferExceeded||executionResult.exitCode!==0?"failed":"passed",reasonTokens=[];executionResult.timedOut&&reasonTokens.push("reason=timeout"),executionResult.maxBufferExceeded&&reasonTokens.push("reason=max_buffer"),executionResult.outputTruncated&&reasonTokens.push("output=truncated"),executionResult.signal&&reasonTokens.push(`signal=${executionResult.signal}`);const exitLabel=executionResult.exitCode===null?"null":String(executionResult.exitCode),reasonSuffix=reasonTokens.length>0?` ${reasonTokens.join(" ")}`:"";emitLinkedTestProgress(`[pm test] linked-test ${context.index}/${context.total} end status=${statusLabel} exit_code=${exitLabel} elapsed_ms=${elapsedMs}${reasonSuffix} command="${commandLabel}"`)}async function killProcessTree(pid){if(!(!Number.isInteger(pid)||pid<=0)){if(process.platform==="win32"){await new Promise(resolve=>{const killer=spawn("taskkill",["/pid",String(pid),"/T","/F"],{stdio:"ignore",windowsHide:!0});killer.on("error",()=>resolve()),killer.on("close",()=>resolve())});return}try{process.kill(-pid,"SIGKILL");return}catch{}try{process.kill(pid,"SIGKILL")}catch{}}}function clearLinkedTestTimers(timers){timers.heartbeat&&clearInterval(timers.heartbeat),timers.timedOutTimer&&(clearTimeout(timers.timedOutTimer),timers.timedOutTimer=null),timers.forceKillTimer&&(clearTimeout(timers.forceKillTimer),timers.forceKillTimer=null)}function createLinkedTestTerminationRequester(child,timers){let terminationRequested=!1;return async()=>{if(terminationRequested)return;terminationRequested=!0;const pid=child.pid;if(!pid||pid<=0){try{child.kill("SIGTERM")}catch{}return}if(process.platform==="win32"){await killProcessTree(pid);return}try{process.kill(-pid,"SIGTERM")}catch{try{child.kill("SIGTERM")}catch{}}timers.forceKillTimer=setTimeout(()=>{killProcessTree(pid)},linkedTestTimeoutForceKillDelayMs()),timers.forceKillTimer.unref?.()}}function waitForLinkedTestChildClose(child){return new Promise(resolve=>{let settled=!1,pipeCloseGraceTimer=null;const settle=(value,destroyPipes=!1)=>{settled||(settled=!0,pipeCloseGraceTimer&&(clearTimeout(pipeCloseGraceTimer),pipeCloseGraceTimer=null),destroyPipes&&(child.stdin?.destroy(),child.stdout?.destroy(),child.stderr?.destroy()),resolve(value))};child.on("exit",(exitCode,exitSignal)=>{settled||(pipeCloseGraceTimer=setTimeout(()=>{settle({code:exitCode,signal:exitSignal},!0)},linkedTestPipeCloseGraceMs()),pipeCloseGraceTimer.unref?.())}),child.on("close",(closeCode,closeSignal)=>{settle({code:closeCode,signal:closeSignal})})})}function createLinkedTestChild(command,env,stdoutFd,stderrFd,cwd){return spawn(command,{cwd,env,shell:!0,windowsHide:!0,detached:process.platform!=="win32",stdio:["pipe",stdoutFd,stderrFd]})}async function readLinkedTestCapture(capturePath){const handle=await open(capturePath,"r");try{const size=(await handle.stat()).size,retainedBytes=Math.min(size,TEST_OUTPUT_MAX_BUFFER_BYTES),buffer=Buffer.alloc(retainedBytes);return retainedBytes>0&&await handle.read(buffer,0,retainedBytes,0),{text:buffer.toString("utf8"),truncated:size>TEST_OUTPUT_MAX_BUFFER_BYTES}}finally{await handle.close()}}async function runLinkedTestCommand(command,timeoutMs,env,progressContext,progressMode,cwd=process.cwd()){const startedAt=Date.now(),captureRoot=await mkdtemp(path.join(tmpdir(),"pm-linked-test-output-"));try{const stdoutPath=path.join(captureRoot,"stdout.log"),stderrPath=path.join(captureRoot,"stderr.log"),[stdoutHandle,stderrHandle]=await Promise.all([open(stdoutPath,"w"),open(stderrPath,"w")]);let child;try{child=createLinkedTestChild(command,env,stdoutHandle.fd,stderrHandle.fd,cwd)}catch(error){throw await Promise.all([stdoutHandle.close(),stderrHandle.close()]),error}const childClose=waitForLinkedTestChildClose(child);let spawnError;child.on("error",error=>{spawnError=error.message}),await Promise.all([stdoutHandle.close(),stderrHandle.close()]),closeLinkedTestStdin(child);const timers={heartbeat:beginLinkedTestProgress(progressContext,progressMode),forceKillTimer:null,timedOutTimer:null},requestTermination=createLinkedTestTerminationRequester(child,timers);let timedOut=!1;timers.timedOutTimer=setTimeout(()=>{timedOut=!0,requestTermination()},timeoutMs),timers.timedOutTimer.unref?.();const{code,signal}=await childClose;clearLinkedTestTimers(timers);const[stdout,stderr]=await Promise.all([readLinkedTestCapture(stdoutPath),readLinkedTestCapture(stderrPath)]),outputTruncated=stdout.truncated||stderr.truncated,truncationNotice=outputTruncated?`
|
|
3
3
|
[pm linked-test output truncated at ${TEST_OUTPUT_MAX_BUFFER_BYTES} bytes per stream; the child process continued to completion]
|
|
4
4
|
`:"";!spawnError&&await detectLinkedTestCommandNotFound(command,{exitCode:code,stderr:stderr.text},env,cwd)&&(spawnError="Linked test executable was not found. Install the tool or update the linked command before retrying.");const executionResult={stdout:`${stdout.text}${truncationNotice}`,stderr:stderr.text,exitCode:code,signal,timedOut,maxBufferExceeded:!1,outputTruncated,spawnError};return endLinkedTestProgress(progressContext,executionResult,startedAt,progressMode),executionResult}finally{await rm(captureRoot,{recursive:!0,force:!0})}}function formatLinkedTestExecutionError(result,timeoutMs){const details=[];result.maxBufferExceeded&&details.push(`Linked test output exceeded maxBuffer=${TEST_OUTPUT_MAX_BUFFER_BYTES} bytes. Reduce output volume or split the command.`),result.timedOut&&timeoutMs>0&&details.push(`Linked test timed out after ${timeoutMs}ms.`);const signalMessage=result.signal?`Linked test command terminated by signal ${result.signal}.`:void 0,baseMessage=result.spawnError?.trim()||signalMessage||"Linked test command failed.";return details.length===0?baseMessage:`${baseMessage} ${details.join(" ")}`}function hasInfraCollisionSignal(result){const combined=[result.spawnError??"",result.stderr,result.stdout].join(`
|
|
5
|
-
`);return LINKED_TEST_INFRA_COLLISION_PATTERNS.some(pattern=>pattern.test(combined))||combined.split(/\r?\n/u).some(line=>LINKED_TEST_LOCK_ACQUISITION_PATTERN.test(line)&&LINKED_TEST_LOCK_CONTENTION_CONTEXT_PATTERN.test(line))}function classifyLinkedTestFailure(result){return hasInfraCollisionSignal(result)?"infra_collision":result.timedOut?"timeout":result.maxBufferExceeded?"max_buffer":result.spawnError?"spawn_error":result.signal?"signal":"assertion_failure"}function createEmptyFailureCategoryCounts(){return{infra_collision:0,trust_refusal:0,config_refusal:0,assertion_failure:0,empty_run:0,timeout:0,max_buffer:0,spawn_error:0,signal:0}}function countFailureCategories(runResults){const counts=createEmptyFailureCategoryCounts();for(const result of runResults)result.status!=="failed"&&result.status!=="refused"||!result.failure_category||(counts[result.failure_category]+=1);return counts}function applyEnvDirectiveStage(env,directives){for(const[key,value]of Object.entries(directives.env_set))LINKED_TEST_PROTECTED_ENV_KEYS.has(key.toUpperCase())||(env[key]=value);for(const key of directives.env_clear)LINKED_TEST_PROTECTED_ENV_KEYS.has(key.toUpperCase())||delete env[key]}function applySharedHostSafeDefaults(env){env.PORT===void 0&&(env.PORT="0"),env.HOST===void 0&&(env.HOST="127.0.0.1"),env.PM_SHARED_HOST_SAFE===void 0&&(env.PM_SHARED_HOST_SAFE="1"),env.PLAYWRIGHT_HTML_OPEN===void 0&&(env.PLAYWRIGHT_HTML_OPEN="never"),env.PW_TEST_HTML_REPORT_OPEN===void 0&&(env.PW_TEST_HTML_REPORT_OPEN="never")}function resolveEffectiveLinkedTestDirectives(runtimeDirectives,linkedTest){const envSet={...runtimeDirectives.env_set,...linkedTest.env_set},envClear=[...new Set([...runtimeDirectives.env_clear,...linkedTest.env_clear??[]])],sharedHostSafe=linkedTest.shared_host_safe??runtimeDirectives.shared_host_safe;return{env_set:envSet,env_clear:envClear,shared_host_safe:sharedHostSafe}}function resolveRuntimeDirectives(envSetEntries,envClearEntries,sharedHostSafe){return{env_set:mergeEnvSetDirectives(envSetEntries,"--env-set"),env_clear:mergeEnvClearDirectives(envClearEntries,"--env-clear"),shared_host_safe:sharedHostSafe===!0}}async function copyIntoSandboxIfPresent(sourcePath,targetPath,recursive=!1,makeDirectory=mkdir){if(await pathExists(sourcePath))try{await withHostEnvironmentBoundary("seed_linked_test",async()=>{if(await makeDirectory(path.dirname(targetPath),{recursive:!0}),recursive){await cp(sourcePath,targetPath,{recursive:!0,force:!0});return}await cp(sourcePath,targetPath,{force:!0})},{why:"Linked tests need an isolated PM context without mutating the source tracker.",nextSteps:["Reclaim capacity on the filesystem that hosts the linked-test temporary directory."]})}catch(error){if(typeof error=="object"&&error!==null&&"code"in error&&isFileMissingError(error))return;throw error}}async function seedLinkedTestSandbox(sandboxPmPath,sandboxGlobalPath,sourceRoots,auditSettings=!1){for(const[sourceRoot,sandboxRoot]of[[sourceRoots.projectPmRoot,sandboxPmPath],[sourceRoots.globalPmRoot,sandboxGlobalPath]])if(auditSettings){const raw=await readFileIfExists(getSettingsPath(sourceRoot));if(raw!==null){const settings=await readSettings(sandboxRoot);await writeWorkspaceJsonWithHistory({pmRoot:sandboxRoot,filePath:getSettingsPath(sandboxRoot),raw,op:"settings:write",author:resolveAuthor(void 0,settings.author_default),lockTtlSeconds:settings.locks.ttl_seconds,lockWaitMs:settings.locks.wait_ms,recordCreation:!0,message:"Seed linked-test schema settings history"})}}else await copyIntoSandboxIfPresent(getSettingsPath(sourceRoot),getSettingsPath(sandboxRoot));await copyIntoSandboxIfPresent(path.join(sourceRoots.projectPmRoot,"extensions"),path.join(sandboxPmPath,"extensions"),!0),await copyIntoSandboxIfPresent(path.join(sourceRoots.globalPmRoot,"extensions"),path.join(sandboxGlobalPath,"extensions"),!0)}async function seedLinkedTestTrackerData(sourceRoot,sandboxRoot){if(!await pathExists(sourceRoot))return;const entries=await readdir(sourceRoot,{withFileTypes:!0});for(const entry of entries){if(LINKED_TEST_TRACKER_DIRS_TO_SKIP.has(entry.name))continue;const sourcePath=path.join(sourceRoot,entry.name),targetPath=path.join(sandboxRoot,entry.name);if(entry.isDirectory()){await copyIntoSandboxIfPresent(sourcePath,targetPath,!0);continue}entry.isFile()&&await copyIntoSandboxIfPresent(sourcePath,targetPath)}}async function seedMissingLinkedTestSettingsHistory(sourceRoot,sandboxRoot){const sourceSettingsRaw=await readFileIfExists(getSettingsPath(sourceRoot));if(sourceSettingsRaw===null||await pathExists(getWorkspaceHistoryPath(sourceRoot)))return;const sandboxSettings=await readSettings(sandboxRoot);await rm(getWorkspaceHistoryPath(sandboxRoot),{force:!0}),await rm(getSettingsPath(sandboxRoot),{force:!0}),await writeWorkspaceJsonWithHistory({pmRoot:sandboxRoot,filePath:getSettingsPath(sandboxRoot),raw:sourceSettingsRaw,op:"settings:write",author:resolveAuthor(void 0,sandboxSettings.author_default),lockTtlSeconds:sandboxSettings.locks.ttl_seconds,lockWaitMs:sandboxSettings.locks.wait_ms,recordCreation:!0,message:"Seed linked-test tracker settings history"})}async function countLinkedTestItemFiles(pmRoot){if(!await pathExists(pmRoot))return 0;let total=0;const entries=await readdir(pmRoot,{withFileTypes:!0});for(const entry of entries){if(!entry.isDirectory()||LINKED_TEST_ITEM_COUNT_DIRS_TO_SKIP.has(entry.name))continue;const folderPath=path.join(pmRoot,entry.name);let files;try{files=await readdir(folderPath,{withFileTypes:!0})}catch{continue}for(const file of files)file.isFile()&&ITEM_FILE_EXTENSIONS.some(extension=>file.name.toLowerCase().endsWith(extension))&&(total+=1)}return total}function resolveLinkedTestFailureExitCode(execution){const rawExitCode=typeof execution.exitCode=="number"?execution.exitCode:1;return(execution.timedOut||execution.maxBufferExceeded)&&rawExitCode===0?1:rawExitCode}function parseAssertionLiteral(raw){const trimmed=raw.trim();if(trimmed==="true")return!0;if(trimmed==="false")return!1;if(trimmed==="null")return null;const numeric=Number(trimmed);if(trimmed.length>0&&Number.isFinite(numeric))return numeric;if(trimmed.startsWith("{")&&trimmed.endsWith("}")||trimmed.startsWith("[")&&trimmed.endsWith("]"))try{return JSON.parse(trimmed)}catch{}return trimmed}function compareAssertionValues(actual,expected){return typeof actual=="object"&&actual!==null&&typeof expected=="object"&&expected!==null?stableValueEquals(actual,expected):Object.is(actual,expected)}function evaluateLinkedTestAssertions(linkedTest,stdout,stderr){const failures=[];failures.push(...evaluateContainsAssertions("stdout",stdout,linkedTest.assert_stdout_contains)),failures.push(...evaluateRegexAssertions("stdout",stdout,linkedTest.assert_stdout_regex)),failures.push(...evaluateContainsAssertions("stderr",stderr,linkedTest.assert_stderr_contains)),failures.push(...evaluateRegexAssertions("stderr",stderr,linkedTest.assert_stderr_regex)),failures.push(...evaluateStdoutLineCountAssertion(stdout,linkedTest.assert_stdout_min_lines));const jsonEqualsAssertions=linkedTest.assert_json_field_equals??{},jsonGteAssertions=linkedTest.assert_json_field_gte??{};if(!(Object.keys(jsonEqualsAssertions).length>0||Object.keys(jsonGteAssertions).length>0))return failures;let parsedJson;try{parsedJson=JSON.parse(stdout)}catch(error){return failures.push(`stdout is not valid JSON for assert_json_field_* checks: ${error instanceof Error?error.message:String(error)}`),failures}for(const[fieldPath,expectedRaw]of Object.entries(jsonEqualsAssertions)){const resolved=readJsonPathValue(parsedJson,fieldPath);if(!resolved.found){failures.push(`assert_json_field_equals missing path "${fieldPath}"`);continue}const expected=parseAssertionLiteral(expectedRaw);compareAssertionValues(resolved.value,expected)||failures.push(`assert_json_field_equals mismatch at "${fieldPath}" (expected=${JSON.stringify(expected)} actual=${JSON.stringify(resolved.value)})`)}for(const[fieldPath,expectedMinimum]of Object.entries(jsonGteAssertions)){const resolved=readJsonPathValue(parsedJson,fieldPath);if(!resolved.found){failures.push(`assert_json_field_gte missing path "${fieldPath}"`);continue}if(typeof resolved.value!="number"||!Number.isFinite(resolved.value)){failures.push(`assert_json_field_gte path "${fieldPath}" resolved to non-numeric value`);continue}resolved.value<expectedMinimum&&failures.push(`assert_json_field_gte failed at "${fieldPath}" (expected >= ${expectedMinimum}, actual ${resolved.value})`)}return failures}function evaluateContainsAssertions(streamName,output,expectedValues){const failures=[];for(const expected of expectedValues??[])output.includes(expected)||failures.push(`${streamName} missing required text: "${expected}"`);return failures}function evaluateRegexAssertions(streamName,output,patterns){const failures=[];for(const pattern of patterns??[])try{new RegExp(pattern,"m").test(output)||failures.push(`${streamName} failed regex assertion: /${pattern}/m`)}catch(error){failures.push(`${streamName} regex assertion is invalid: /${pattern}/ (${error instanceof Error?error.message:String(error)})`)}return failures}function evaluateStdoutLineCountAssertion(stdout,minimum){if(typeof minimum!="number")return[];const lineCount=stdout.split(/\r?\n/).map(line=>line.trim()).filter(line=>line.length>0).length;return lineCount<minimum?[`stdout line count ${lineCount} is below required minimum ${minimum}`]:[]}function commandUsesTestNameFilter(command){const normalized=normalizeCommandForValidation(command);return/(?:^|\s)(?:--test-name-pattern|--testnamepattern)(?:=|\s)/u.test(normalized)?!0:splitNormalizedCommandSegments(normalized).some(segment=>/(?:^|\s)-t(?:=|\s)/u.test(segment)&&segmentInvokesDirectTestRunner(segment))}function createLinkedTestSandboxLayout(sandboxRoot){return{root:sandboxRoot,schemaProjectPmPath:path.join(sandboxRoot,"schema","project",".agents","pm"),schemaGlobalPmPath:path.join(sandboxRoot,"schema","global"),trackerProjectPmPath:path.join(sandboxRoot,"tracker","project",".agents","pm"),trackerGlobalPmPath:path.join(sandboxRoot,"tracker","global"),workspaceSnapshotRoot:path.join(sandboxRoot,"workspace","project"),workspaceIsolatedRoot:path.join(sandboxRoot,"workspace","isolated")}}function linkedTestsRequireWorkspaceSnapshot(tests,runLevelMode,overrideLinkedMode){return tests.some(linkedTest=>resolveLinkedTestWorkspaceContextMode(linkedTest,runLevelMode,overrideLinkedMode)==="snapshot")}async function bindSnapshotTracker(snapshotRoot,sandboxPmPath){const agentsRoot=path.join(snapshotRoot,".agents"),target=path.join(agentsRoot,"pm");await mkdir(agentsRoot,{recursive:!0}),await rm(target,{recursive:!0,force:!0}),await symlink(sandboxPmPath,target,process.platform==="win32"?"junction":"dir")}async function initializeLinkedTestSandboxes(layout,initialize=runInit,includeTrackerData=!0){const initOptions={defaults:!0,agentGuidance:"skip"};await initialize(void 0,{path:layout.schemaProjectPmPath},initOptions),await initialize(void 0,{path:layout.schemaGlobalPmPath},initOptions),includeTrackerData&&(await initialize(void 0,{path:layout.trackerProjectPmPath},initOptions),await initialize(void 0,{path:layout.trackerGlobalPmPath},initOptions))}async function seedLinkedTestSandboxesFromSource(layout,sourceRoots,includeTrackerData){sourceRoots&&(await seedLinkedTestSandbox(layout.schemaProjectPmPath,layout.schemaGlobalPmPath,sourceRoots,!0),includeTrackerData&&(await seedLinkedTestSandbox(layout.trackerProjectPmPath,layout.trackerGlobalPmPath,sourceRoots),await seedLinkedTestTrackerData(sourceRoots.projectPmRoot,layout.trackerProjectPmPath),await seedLinkedTestTrackerData(sourceRoots.globalPmRoot,layout.trackerGlobalPmPath),await seedMissingLinkedTestSettingsHistory(sourceRoots.projectPmRoot,layout.trackerProjectPmPath),await seedMissingLinkedTestSettingsHistory(sourceRoots.globalPmRoot,layout.trackerGlobalPmPath)))}function linkedTestsRequireTrackerData(tests,runLevelPmContextMode,options){return tests.some(linkedTest=>{const command=linkedTest.command??"",isPmTrackerReadCommand=command.length>0&&commandInvokesPmTrackerReadCommand(command),requestedPmContextMode=options?.autoPmContext===!0&&isPmTrackerReadCommand?"auto":resolveLinkedTestRequestedContextMode(linkedTest,runLevelPmContextMode,options?.overrideLinkedPmContext===!0);return resolveLinkedTestEffectiveContextMode(requestedPmContextMode,isPmTrackerReadCommand)==="tracker"})}async function countLinkedTestSandboxItems(layout,sourceRoots){return{sourceProjectItemCount:sourceRoots?await countLinkedTestItemFiles(sourceRoots.projectPmRoot):0,sourceGlobalItemCount:sourceRoots?await countLinkedTestItemFiles(sourceRoots.globalPmRoot):0,schemaProjectItemCount:await countLinkedTestItemFiles(layout.schemaProjectPmPath),schemaGlobalItemCount:await countLinkedTestItemFiles(layout.schemaGlobalPmPath),trackerProjectItemCount:await countLinkedTestItemFiles(layout.trackerProjectPmPath),trackerGlobalItemCount:await countLinkedTestItemFiles(layout.trackerGlobalPmPath)}}function buildLinkedTestExecutionContext(params){const trackerMode=params.effectivePmContextMode==="tracker",selectedSandboxProjectPmPath=trackerMode?params.layout.trackerProjectPmPath:params.layout.schemaProjectPmPath,selectedSandboxGlobalPmPath=trackerMode?params.layout.trackerGlobalPmPath:params.layout.schemaGlobalPmPath,selectedSandboxProjectItemCount=trackerMode?params.counts.trackerProjectItemCount:params.counts.schemaProjectItemCount,selectedSandboxGlobalItemCount=trackerMode?params.counts.trackerGlobalItemCount:params.counts.schemaGlobalItemCount,workingDirectory=params.workspaceContextMode==="snapshot"?params.layout.workspaceSnapshotRoot:params.workspaceContextMode==="isolated"?params.layout.workspaceIsolatedRoot:params.sourceWorkspaceRoot,exposedSourceWorkspaceRoot=params.workspaceContextMode==="isolated"?"":workingDirectory;return{requested_pm_context_mode:params.requestedPmContextMode,pm_context_mode:params.effectivePmContextMode,auto_pm_context_applied:params.autoPmContextApplied,is_pm_command:params.isPmCommand,is_pm_tracker_read_command:params.isPmTrackerReadCommand,source_project_pm_path:params.sourceRoots?.projectPmRoot??"",sandbox_project_pm_path:selectedSandboxProjectPmPath,source_global_pm_path:params.sourceRoots?.globalPmRoot??"",sandbox_global_pm_path:selectedSandboxGlobalPmPath,source_project_item_count:params.counts.sourceProjectItemCount,sandbox_project_item_count:selectedSandboxProjectItemCount,source_global_item_count:params.counts.sourceGlobalItemCount,sandbox_global_item_count:selectedSandboxGlobalItemCount,mismatch_detected:params.isPmCommand&&(params.counts.sourceProjectItemCount!==selectedSandboxProjectItemCount||params.counts.sourceGlobalItemCount!==selectedSandboxGlobalItemCount),project_extensions_seeded:!!params.sourceRoots,global_extensions_seeded:!!params.sourceRoots,requested_workspace_context_mode:params.requestedWorkspaceContextMode,workspace_context_mode:params.workspaceContextMode,working_directory:workingDirectory,source_workspace_root:exposedSourceWorkspaceRoot,trust:params.trust}}async function resolveLinkedTestCommandContext(params){const linkedOverridePmContextMode=typeof params.linkedTest.pm_context_mode=="string"&¶ms.linkedTest.pm_context_mode.trim().length>0?parsePmContextMode(params.linkedTest.pm_context_mode):void 0,command=typeof params.linkedTest.command=="string"&¶ms.linkedTest.command.length>0?params.linkedTest.command:void 0,isPmCommand=command?commandInvokesPmCli(command):!1,isPmTrackerReadCommand=isPmCommand&&command?commandInvokesPmTrackerReadCommand(command):!1,autoPmContextApplied=params.options?.autoPmContext===!0&&isPmTrackerReadCommand,requestedPmContextMode=autoPmContextApplied?"auto":resolveLinkedTestRequestedContextMode(params.linkedTest,params.runLevelPmContextMode,params.options?.overrideLinkedPmContext===!0),effectivePmContextMode=resolveLinkedTestEffectiveContextMode(requestedPmContextMode,isPmTrackerReadCommand),selectedSandboxProjectPmPath=effectivePmContextMode==="tracker"?params.layout.trackerProjectPmPath:params.layout.schemaProjectPmPath;params.workspaceContextMode==="snapshot"&&await bindSnapshotTracker(params.layout.workspaceSnapshotRoot,selectedSandboxProjectPmPath);const trust=params.options?.pmRoot?await resolveLinkedTestTrust(params.options.pmRoot,params.linkedTest,params.currentSourceRef):await resolveLinkedTestTrust(params.layout.root,params.linkedTest,params.currentSourceRef);return{linkedOverridePmContextMode,executionContext:buildLinkedTestExecutionContext({layout:params.layout,counts:params.counts,sourceRoots:params.sourceRoots,isPmCommand,isPmTrackerReadCommand,requestedPmContextMode,effectivePmContextMode,autoPmContextApplied,requestedWorkspaceContextMode:params.requestedWorkspaceContextMode,workspaceContextMode:params.workspaceContextMode,sourceWorkspaceRoot:params.sourceWorkspaceRoot,trust})}}function buildLinkedTestSkippedResult(linkedTest,executionContext,error){return{command:linkedTest.command,path:linkedTest.path,status:"skipped",execution_context:executionContext,error}}function buildLinkedTestAssertionFailureResult(linkedTest,executionContext,error,output){return{command:linkedTest.command,path:linkedTest.path,status:"failed",exit_code:1,failure_category:"assertion_failure",execution_context:executionContext,...output?{stdout:output.stdout,stderr:output.stderr}:{},error}}function buildLinkedTestTrustRefusalResult(linkedTest,executionContext,error){return{command:linkedTest.command,path:linkedTest.path,status:"failed",exit_code:1,failure_category:"trust_refusal",execution_context:executionContext,error}}function resolveLinkedTestTrustPreflightResult(params){if(params.executionContext.trust.trusted)return null;const overrideRequested=params.options?.allowUntrustedLinkedTests===!0,policyEnabled=params.options?.untrustedLinkedTestsPolicyEnabled===!0;if(overrideRequested&&policyEnabled)return null;const recovery=policyEnabled?"Retry with --allow-untrusted-linked-tests for this invocation, or acknowledge the current commands with --acknowledge-linked-tests.":"Set testing.allow_untrusted_linked_tests=true, then retry with --allow-untrusted-linked-tests; alternatively acknowledge the current commands with --acknowledge-linked-tests.";return buildLinkedTestTrustRefusalResult(params.linkedTest,params.executionContext,`Linked test command is not trusted by this clone (source_ref=${params.executionContext.trust.source_ref??"unknown"}, current_ref=${params.executionContext.trust.current_source_ref??"unknown"}). ${recovery}`)}function resolveLinkedTestSafetyPreflightResult(params){if(!params.linkedTest.command)return buildLinkedTestSkippedResult(params.linkedTest,params.executionContext,"No command configured for this linked test.");const runtimeSafetySkipReason=getRuntimeSafetySkipReason(params.linkedTest.command);return runtimeSafetySkipReason?buildLinkedTestSkippedResult(params.linkedTest,params.executionContext,runtimeSafetySkipReason):null}function resolveUnboundPmContextFailure(params){return params.executionContext.pm_context_mode!=="none"?null:params.executionContext.workspace_context_mode==="source"?buildLinkedTestConfigurationRefusalResult(params.linkedTest,params.executionContext,"pm_context_mode=none requires workspace_context_mode=isolated or snapshot so implicit tracker discovery cannot reach the source workspace."):params.executionContext.is_pm_command?buildLinkedTestConfigurationRefusalResult(params.linkedTest,params.executionContext,"pm_context_mode=none is for self-isolating SDK tests; direct PM commands require schema, tracker, or auto context."):null}function buildLinkedTestConfigurationRefusalResult(linkedTest,executionContext,error){return{command:linkedTest.command,path:linkedTest.path,status:"refused",exit_code:null,failure_category:"config_refusal",execution_context:executionContext,error}}function resolvePmContextMismatchFailure(params){const failOnMismatchByDefault=params.executionContext.pm_context_mode==="schema"&¶ms.executionContext.is_pm_tracker_read_command&¶ms.executionContext.mismatch_detected,failOnMismatchByFlag=params.options?.failOnContextMismatch===!0&¶ms.executionContext.is_pm_command&¶ms.executionContext.mismatch_detected;if(!failOnMismatchByDefault&&!failOnMismatchByFlag)return null;const mismatchHint=buildPmContextMismatchHint({executionContext:params.executionContext,runLevelPmContextMode:params.runLevelPmContextMode,linkedOverridePmContextMode:params.linkedOverridePmContextMode}),mismatchPrefix=params.options?.checkContext===!0?"Linked test preflight PM context mismatch detected":"Linked test PM context mismatch detected";return buildLinkedTestConfigurationRefusalResult(params.linkedTest,params.executionContext,`${mismatchPrefix} (source_project_items=${params.executionContext.source_project_item_count}, sandbox_project_items=${params.executionContext.sandbox_project_item_count}).${mismatchHint}`)}function resolveLinkedTestPreflightResult(params){return resolveLinkedTestSafetyPreflightResult(params)||resolveLinkedTestTrustPreflightResult(params)||resolveUnboundPmContextFailure(params)||resolvePmContextMismatchFailure(params)||(params.options?.requireAssertionsForPm===!0&¶ms.executionContext.is_pm_command&&!hasLinkedTestAssertions(params.linkedTest)?buildLinkedTestConfigurationRefusalResult(params.linkedTest,params.executionContext,"Linked PM command requires assertions when --require-assertions-for-pm is enabled."):null)}function buildLinkedTestExecutionEnv(params){const effectiveDirectives=resolveEffectiveLinkedTestDirectives(params.runtimeDirectives,params.linkedTest),executionEnv={...process.env,SENTRY_ENVIRONMENT:"test"};return applyEnvDirectiveStage(executionEnv,params.runtimeDirectives),applyEnvDirectiveStage(executionEnv,{env_set:params.linkedTest.env_set??{},env_clear:params.linkedTest.env_clear??[]}),effectiveDirectives.shared_host_safe&&applySharedHostSafeDefaults(executionEnv),executionEnv.FORCE_COLOR="0",params.executionContext.pm_context_mode==="none"?delete executionEnv.PM_PATH:executionEnv.PM_PATH=params.executionContext.sandbox_project_pm_path,executionEnv.PM_GLOBAL_PATH=params.executionContext.sandbox_global_pm_path,params.executionContext.workspace_context_mode!=="source"&&(delete executionEnv.PM_BUILD_CONSUMER_LEASE,delete executionEnv.PM_BUILD_CONSUMER_LEASE_ROOT),params.executionContext.workspace_context_mode==="isolated"?(delete executionEnv.PM_SOURCE_WORKSPACE_ROOT,delete executionEnv.PM_SOURCE_PM_PATH,delete executionEnv[SOURCE_CONTEXT_ACCESS_ENV]):(executionEnv.PM_SOURCE_WORKSPACE_ROOT=params.executionContext.source_workspace_root,executionEnv.PM_SOURCE_PM_PATH=params.executionContext.workspace_context_mode==="snapshot"?params.executionContext.sandbox_project_pm_path:params.executionContext.source_project_pm_path,executionEnv[SOURCE_CONTEXT_ACCESS_ENV]="read_only"),executionEnv}function buildLinkedTestPassedResult(linkedTest,executionContext,execution,receipt){return{command:linkedTest.command,path:linkedTest.path,status:"passed",exit_code:0,execution_context:executionContext,stdout:execution.stdout,stderr:execution.stderr,...receipt?{execution_receipt:receipt}:{}}}function buildLinkedTestEmptyRunResult(params){return{command:params.linkedTest.command,path:params.linkedTest.path,status:"failed",exit_code:1,failure_category:"empty_run",execution_context:params.executionContext,stdout:params.execution.stdout,stderr:params.execution.stderr,error:`Linked test reported an empty test run (${params.emptyRunSignal.code}). Update the test selection so at least one test executes; unfiltered commands may opt out by omitting --fail-on-empty-test-run.`}}function buildLinkedTestCommandFailureResult(params){return{command:params.linkedTest.command,path:params.linkedTest.path,status:"failed",exit_code:resolveLinkedTestFailureExitCode(params.execution),failure_category:classifyLinkedTestFailure(params.execution),execution_context:params.executionContext,stdout:params.execution.stdout,stderr:params.execution.stderr,error:formatLinkedTestExecutionError(params.execution,params.timeoutMs)}}function buildLinkedTestPassedExecutionResult(params){const receipt=findLinkedTestExecutionReceipt(params.execution.stdout,params.execution.stderr);if(params.options?.failOnEmptyTestRun===!0||commandUsesTestNameFilter(params.linkedTest.command??"")){const emptyRunSignal=detectEmptyLinkedTestRun(params.execution.stdout,params.execution.stderr);if(emptyRunSignal)return buildLinkedTestEmptyRunResult({...params,emptyRunSignal});if(commandUsesTestNameFilter(params.linkedTest.command??"")&&!receipt)return buildLinkedTestEmptyRunResult({...params,emptyRunSignal:{code:"missing_positive_execution_receipt"}})}const assertionFailures=evaluateLinkedTestAssertions(params.linkedTest,params.execution.stdout,params.execution.stderr);return assertionFailures.length>0?buildLinkedTestAssertionFailureResult(params.linkedTest,params.executionContext,`Linked test assertion(s) failed: ${assertionFailures.join("; ")}`,params.execution):buildLinkedTestPassedResult(params.linkedTest,params.executionContext,params.execution,receipt)}async function runSingleLinkedTest(params){const command=params.linkedTest.command??"",timeoutMs=(params.linkedTest.timeout_seconds??params.defaultTimeoutSeconds??120)*1e3,execution=await runLinkedTestCommand(command,timeoutMs,buildLinkedTestExecutionEnv(params),{index:params.index+1,total:params.total,timeoutMs,command},params.progressMode,params.executionContext.working_directory);return execution.exitCode===0&&!execution.timedOut&&!execution.maxBufferExceeded?buildLinkedTestPassedExecutionResult({...params,execution}):buildLinkedTestCommandFailureResult({...params,execution,timeoutMs})}async function runLinkedTests(tests,defaultTimeoutSeconds,options){for(const test of tests)assertLinkedTestCommand(test.command);const results=[],sandboxRoot=await mkdtemp(path.join(tmpdir(),"pm-linked-test-")),layout=createLinkedTestSandboxLayout(sandboxRoot),runLevelPmContextMode=parsePmContextMode(options?.pmContext),runLevelWorkspaceContextMode=parseWorkspaceContextMode(options?.workspaceContext),progressMode=options?.progress===!0?"always":"auto",runtimeDirectives=resolveRuntimeDirectives(options?.envSet,options?.envClear,options?.sharedHostSafe),sourceRoots=options?.sourceRoots,sourceWorkspaceRoot=resolveLinkedTestSourceWorkspaceRoot(),includeTrackerData=linkedTestsRequireTrackerData(tests,runLevelPmContextMode,options),includeWorkspaceSnapshot=linkedTestsRequireWorkspaceSnapshot(tests,runLevelWorkspaceContextMode,options?.overrideLinkedWorkspaceContext===!0),currentSourceRef=await resolveLinkedTestSourceRef(sourceWorkspaceRoot);try{await initializeLinkedTestSandboxes(layout,runInit,includeTrackerData),await mkdir(layout.workspaceIsolatedRoot,{recursive:!0}),await seedLinkedTestSandboxesFromSource(layout,sourceRoots,includeTrackerData),includeWorkspaceSnapshot&&await seedLinkedTestWorkspaceSnapshot(sourceWorkspaceRoot,layout.workspaceSnapshotRoot,sourceRoots?.projectPmRoot,includeTrackerData);const counts=await countLinkedTestSandboxItems(layout,sourceRoots);for(let index=0;index<tests.length;index+=1){const linkedTest=tests[index],workspaceContextMode=resolveLinkedTestWorkspaceContextMode(linkedTest,runLevelWorkspaceContextMode,options?.overrideLinkedWorkspaceContext===!0),commandContext=await resolveLinkedTestCommandContext({linkedTest,layout,counts,sourceRoots,runLevelPmContextMode,options,requestedWorkspaceContextMode:runLevelWorkspaceContextMode,workspaceContextMode,currentSourceRef,sourceWorkspaceRoot}),preflightResult=resolveLinkedTestPreflightResult({linkedTest,executionContext:commandContext.executionContext,runLevelPmContextMode,linkedOverridePmContextMode:commandContext.linkedOverridePmContextMode,options});if(preflightResult){results.push(preflightResult);continue}results.push(await runSingleLinkedTest({linkedTest,index,total:tests.length,defaultTimeoutSeconds,runtimeDirectives,progressMode,executionContext:commandContext.executionContext,options}))}}finally{await rm(layout.root,{recursive:!0,force:!0,maxRetries:3,retryDelay:100})}return results}async function readLinkedTestItem(params){const{id,pmRoot,settings,typeToFolder}=params,located=await locateItem(pmRoot,id,settings.id_prefix,settings.item_format,typeToFolder);if(!located)throw new PmCliError(`Item ${id} not found`,EXIT_CODE.NOT_FOUND);const loaded=await readLocatedItem(located,{schema:settings.schema});return{itemId:located.id,tests:loaded.document.metadata.tests??[],testRuns:loaded.document.metadata.test_runs??[],changed:!1,removed:0}}function appendMissingLinkedTests(current,additions){const next=[...current];for(const addition of additions)next.some(entry=>linkedTestsHaveSameIdentity(entry,addition))||next.push(addition);return next}function removeLinkedTestsBySelector(current,removals,removeIndexes){if(removals.length===0&&removeIndexes.length===0)return{tests:current,removed:0};const indexSet=new Set(removeIndexes),tests=current.filter((entry,index)=>indexSet.has(index+1)?!1:!removals.some(selector=>selector.kind==="command"?entry.command===selector.value:selector.kind==="path"?entry.path===selector.value:entry.command===selector.value||entry.path===selector.value));return{tests,removed:current.length-tests.length}}function applyLinkedTestMutations(previous,adds,removes,removeIndexes){return removeLinkedTestsBySelector(appendMissingLinkedTests(previous,adds),removes,removeIndexes)}function hasTestRuntimeDirectiveFlags(options){return[options.timeout!==void 0,options.progress===!0,(options.envSet?.length??0)>0,(options.envClear?.length??0)>0,options.sharedHostSafe===!0,options.pmContext!==void 0,options.overrideLinkedPmContext===!0,options.failOnContextMismatch===!0,options.failOnSkipped===!0,options.failOnEmptyTestRun===!0,options.requireAssertionsForPm===!0,options.checkContext===!0,options.autoPmContext===!0,options.workspaceContext!==void 0,options.overrideLinkedWorkspaceContext===!0,options.allowUntrustedLinkedTests===!0].some(Boolean)}function assertTestRunFlagUsage(options){if(hasTestRuntimeDirectiveFlags(options)&&options.run!==!0)throw new PmCliError("--timeout, --progress, --env-set, --env-clear, --shared-host-safe, --pm-context, --override-linked-pm-context, --workspace-context, --override-linked-workspace-context, --allow-untrusted-linked-tests, --fail-on-context-mismatch, --fail-on-skipped, --fail-on-empty-test-run, --require-assertions-for-pm, --check-context, and --auto-pm-context require --run",EXIT_CODE.USAGE);if(options.acknowledgeLinkedTests===!0&&options.run===!0)throw new PmCliError("--acknowledge-linked-tests records trust without executing commands; run it separately before --run",EXIT_CODE.USAGE);if((options.match!==void 0||options.onlyIndex!==void 0||options.onlyLast===!0)&&options.run!==!0)throw new PmCliError("--match, --only-index, and --only-last require --run",EXIT_CODE.USAGE)}async function resolveLinkedTestItem(params){const{id,options,pmRoot,settings,typeToFolder,adds,removes,removeIndexes}=params;if(adds.length===0&&removes.length===0&&removeIndexes.length===0)return readLinkedTestItem({id,pmRoot,settings,typeToFolder});let removed=0;const result=await mutateItem({pmRoot,settings,id,op:adds.length>0?"tests_add":"tests_remove",author:resolveAuthor(options.author,settings.author_default),message:options.message,force:options.force,skipNoop:!0,mutate(document){const previous=document.metadata.tests??[],mutation=applyLinkedTestMutations(previous,adds,removes,removeIndexes);if(admitLinkedTestRequest(()=>resolveTestRunOptions(options,mutation.tests)),removed=mutation.removed,(removes.length>0||removeIndexes.length>0)&&removed===0)throw new PmCliError("No linked tests matched the requested removal selector",EXIT_CODE.NOT_FOUND,{code:"linked_test_remove_no_match",unmatched:[...removes.map(entry=>`${entry.kind}=${entry.value}`),...removeIndexes.map(index=>`index=${index}`)],required:"List linked tests and retry with an exact command/path or a current 1-based --remove-index.",examples:[`pm test ${id} --list`,`pm test ${id} --remove-index 1`]});return document.metadata.tests=mutation.tests,{changedFields:stableValueEquals(previous,mutation.tests)?[]:["tests"]}}});return{itemId:result.item.id,tests:result.item.tests??[],testRuns:result.item.test_runs??[],changed:result.changedFields.length>0,removed}}function resolveTestRunOptions(options,tests){const pmContextMode=parsePmContextMode(options.pmContext);parseWorkspaceContextMode(options.workspaceContext),resolveRuntimeDirectives(options.envSet,options.envClear,options.sharedHostSafe);const selectorInput={match:options.match,onlyIndex:options.onlyIndex===void 0?void 0:parseOnlyIndexValue(options.onlyIndex),onlyLast:options.onlyLast},defaultTimeoutSeconds=options.timeout===void 0?void 0:parseOptionalNumber(options.timeout,"--timeout");if(defaultTimeoutSeconds!==void 0&&defaultTimeoutSeconds<=0)throw new PmCliError(`--timeout must be a positive number (got "${options.timeout}")`,EXIT_CODE.USAGE);assertTestRunFlagUsage(options);const runSelection=options.run===!0?resolveLinkedTestRunSelection(tests,selectorInput):void 0;if(runSelection)for(const test of runSelection.selected)assertLinkedTestCommand(test.command);return{defaultTimeoutSeconds,pmContextMode,runSelection,testsToRun:runSelection?.selected??tests}}async function executeSelectedLinkedTests(params){const{options,runOptions,pmRoot}=params;return options.run!==!0?[]:await runLinkedTests(runOptions.testsToRun,runOptions.defaultTimeoutSeconds,{progress:options.progress,envSet:options.envSet,envClear:options.envClear,sharedHostSafe:options.sharedHostSafe,pmContext:runOptions.pmContextMode,overrideLinkedPmContext:options.overrideLinkedPmContext,failOnContextMismatch:options.failOnContextMismatch,failOnEmptyTestRun:options.failOnEmptyTestRun===!0||(options.measure?.length??0)>0,requireAssertionsForPm:options.requireAssertionsForPm,checkContext:options.checkContext,autoPmContext:options.autoPmContext,workspaceContext:options.workspaceContext,overrideLinkedWorkspaceContext:options.overrideLinkedWorkspaceContext,pmRoot,allowUntrustedLinkedTests:options.allowUntrustedLinkedTests,untrustedLinkedTestsPolicyEnabled:params.untrustedLinkedTestsPolicyEnabled,sourceRoots:{projectPmRoot:pmRoot,globalPmRoot:resolveGlobalPmRoot(process.cwd())}})}function buildTestWarnings(options,runResults){const warnings=[];if(options.run===!0&&options.checkContext===!0){const preflight=summarizeContextPreflight(runResults);warnings.push(`context_preflight:checked_pm_commands=${preflight.checked_pm_commands};tracker_read_commands=${preflight.tracker_read_commands};mismatches=${preflight.mismatches};auto_remediated=${preflight.auto_remediated}`)}return warnings}function buildTrackedTestRunSummary(params){const summary=summarizeRunResultStatuses(params.runResults),parsedAttempt=Number.parseInt(process.env.PM_BACKGROUND_TEST_RUN_ATTEMPT?.trim()??"",10),resumedFrom=process.env.PM_BACKGROUND_TEST_RUN_RESUMED_FROM?.trim(),recordedAt=nowIso(),measurements=parseTestRunMeasurements(params.options.measure,recordedAt);return{run_id:resolveTrackedRunId("test"),kind:"test",status:summary.failed>0||params.failOnSkippedTriggered?"failed":"passed",started_at:params.runStartedAt,finished_at:recordedAt,recorded_at:recordedAt,attempt:Number.isFinite(parsedAttempt)&&parsedAttempt>=1?parsedAttempt:void 0,resumed_from:resumedFrom&&resumedFrom.length>0?resumedFrom:void 0,passed:summary.passed,failed:summary.failed,skipped:summary.skipped,fail_on_skipped_triggered:params.failOnSkippedTriggered?!0:void 0,measurements:measurements.length>0?measurements:void 0,executions:params.runResults.filter(result=>result.command?.trim()).slice(0,32).map(result=>({command:result.command.trim(),requested_pm_context_mode:result.execution_context?.requested_pm_context_mode,pm_context_mode:result.execution_context?.pm_context_mode,workspace_context_mode:result.execution_context?.workspace_context_mode,trust_reason:result.execution_context?.trust.reason}))}}async function recordTestRunSummary(params){const{options,pmRoot,settings,itemId,runStartedAt,runResults,failOnSkippedTriggered,warnings}=params;if(options.run!==!0||!runStartedAt)return;const entry=buildTrackedTestRunSummary({options,runStartedAt,runResults,failOnSkippedTriggered});if(settings.testing.record_results_to_items!==!0)return{entry,receipt:{recorded:!1,reason:"tracking_disabled",run_id:entry.run_id,recovery_command:"pm config project set test-result-tracking --policy enabled"}};try{return await appendTrackedTestRunSummary({pmRoot,settings,itemId,author:resolveAuthor(options.author,settings.author_default),message:`Track test run summary (${entry.run_id})`,entry}),{entry,receipt:{recorded:!0,reason:"recorded",run_id:entry.run_id}}}catch(error){return warnings.push(`test_result_tracking_failed:${itemId}:${error instanceof Error?error.message:String(error)}`),{entry,receipt:{recorded:!1,reason:"write_failed",run_id:entry.run_id}}}}function buildTestMeasurementProjection(options,runs,recordedRun){let metricBelow;if(options.metricBelow!==void 0){const separator=options.metricBelow.indexOf("="),name=options.metricBelow.slice(0,separator).trim(),thresholdText=options.metricBelow.slice(separator+1).trim(),threshold=Number(thresholdText);if(separator<=0||name.length===0||thresholdText.length===0||!Number.isFinite(threshold))throw new PmCliError("--metric-below must use name=value",EXIT_CODE.USAGE);metricBelow=queryTestRunMeasurementsBelow(runs,name,threshold)}return{measurements:recordedRun?.measurements,metric_below:metricBelow,metric_diff:options.metricDiff===void 0?void 0:diffTestRunMeasurements(runs,options.metricDiff)}}async function maybeAcknowledgeLinkedTests(options,pmRoot,tests){if(options.acknowledgeLinkedTests===!0)return acknowledgeLinkedTests(pmRoot,tests,nowIso())}function projectTestResult(input){const{item,runResults,failureCategories,runSelection,failOnSkippedTriggered,warnings,recording,measurementProjection,restoreScope}=input,narrowed=runSelection!==void 0&&runSelection.selector!==null&&runSelection.skipped_count>0,restoreWith=renderLinkedTestRestoreCommand(item.itemId,restoreScope);return{ok:runResults.every(entry=>entry.status!=="failed"&&entry.status!=="refused")&&failOnSkippedTriggered!==!0,id:item.itemId,tests:narrowed?runSelection.selected:item.tests,...narrowed?{tests_omitted:{count:item.tests.length-runSelection.selected.length,restore_with:restoreWith}}:{},run_results:runResults,failure_categories:failureCategories,selection:runSelection?{selector:runSelection.selector,requested:runSelection.requested,selected_indexes:runSelection.selected_indexes,selected_count:runSelection.selected_count,skipped_count:runSelection.skipped_count}:void 0,fail_on_skipped_triggered:failOnSkippedTriggered?!0:void 0,warnings:warnings.length>0?warnings:void 0,changed:item.changed||recording?.receipt.recorded===!0,...recording?{evidence_recording:recording.receipt}:{},count:item.tests.length,...item.removed>0?{removed:item.removed}:{},...measurementProjection}}async function runTest(id,options,global2){const edit=parseLinkedTestUpdateOptions(options);if(edit)return assertLinkedTestEditUsage(options),assertTestRunFlagUsage(options),assertTestMeasurementRun(id,options,global2,resolvePmRoot(process.cwd(),global2.path)),{...await updateLinkedTest(id,{...edit,author:options.author,message:options.message,force:options.force},global2),run_results:[],failure_categories:countFailureCategories([])};const stdinResolver=createStdinTokenResolver(),pmRoot=resolvePmRoot(process.cwd(),global2.path);await assertInitializedTracker(pmRoot);const settings=await readSettings(pmRoot);assertTestMeasurementRun(id,options,global2,pmRoot),parseTestRunMeasurements(options.measure,"validation"),buildTestMeasurementProjection(options,[],void 0);const typeRegistry=resolveItemTypeRegistry(settings,getActiveExtensionRegistrations()),resolvedAdds=await stdinResolver.resolveList(options.add,"--add"),resolvedAddJsons=await stdinResolver.resolveList(options.addJson,"--add-json"),resolvedRemoves=await stdinResolver.resolveList(options.remove,"--remove"),parsedAdds=[...parseAddEntries(resolvedAdds),...parseAddJsonEntries(resolvedAddJsons)],adds=await attachLinkedTestMutationProvenance(parsedAdds,resolveAuthor(options.author,settings.author_default),nowIso())??[],removes=parseRemoveEntries(resolvedRemoves),removeIndexes=parseRemoveIndexes(options.removeIndex),item=await resolveLinkedTestItem({id,options,pmRoot,settings,typeToFolder:typeRegistry.type_to_folder,adds,removes,removeIndexes}),runOptions=admitLinkedTestRequest(()=>resolveTestRunOptions(options,item.tests)),trustAcknowledgement=await maybeAcknowledgeLinkedTests(options,pmRoot,item.tests),runStartedAt=options.run===!0?nowIso():void 0,runResults=await executeSelectedLinkedTests({options,runOptions,pmRoot,untrustedLinkedTestsPolicyEnabled:settings.testing.allow_untrusted_linked_tests});assertTestMeasurementExecution(id,options,global2,pmRoot,runResults);const failureCategories=countFailureCategories(runResults),failOnSkippedTriggered=options.run===!0&&options.failOnSkipped===!0&&runResults.some(entry=>entry.status==="skipped"),warnings=buildTestWarnings(options,runResults);trustAcknowledgement&&warnings.push(`linked_test_trust_acknowledged:${trustAcknowledgement.acknowledged}`);const recording=await recordTestRunSummary({options,pmRoot,settings,itemId:item.itemId,runStartedAt,runResults,failOnSkippedTriggered,warnings}),runs=recording?.receipt.recorded?buildTrackedTestRunHistory([...item.testRuns,recording.entry]):item.testRuns,measurementProjection=buildTestMeasurementProjection(options,runs,recording?.entry);return projectTestResult({item,runResults,failureCategories,runSelection:runOptions.runSelection,failOnSkippedTriggered,warnings,recording,measurementProjection,restoreScope:resolveLinkedTestRestoreScope(global2,pmRoot)})}const _testOnlyTestCommand={buildLinkedTestExecutionContext,buildPmContextMismatchHint,commandInvokesPmCli,commandInvokesPmTrackerReadCommand,commandUsesTestNameFilter,copyIntoSandboxIfPresent,countLinkedTestItemFiles,ensureScope,evaluateLinkedTestAssertions,endLinkedTestProgress,extractPmInvocationArgsFromSegment,firstDirectTestRunnerSubcommand,formatLinkedTestExecutionError,hasLinkedTestAssertions,initializeLinkedTestSandboxes,linkedTestsRequireTrackerData,parseAddJsonEntries,parseLinkedTestTimeoutSeconds,parsePmContextMode,readJsonPathValue,resolveAuthor,resolveDirectRunnerSubcommand,resolveLinkedTestEffectiveContextMode,resolveLinkedTestFailureExitCode,resolveLinkedTestRequestedContextMode,resolveTestRunOptions,resolveTrackedRunId,runLinkedTestCommand,seedMissingLinkedTestSettingsHistory,seedLinkedTestSandbox,seedLinkedTestTrackerData,segmentInvokesRecursiveTestAll,splitJsonPathSegments,summarizeRunResultStatuses};export{_testOnlyTestCommand,classifyLinkedTestCommandSafety,classifyLinkedTestFailure,countFailureCategories,extractReferencedPmItemIdsFromCommand,resolveLinkedTestFailureExitCode,runLinkedTests,runTest,summarizeContextPreflight};
|
|
5
|
+
`);return LINKED_TEST_INFRA_COLLISION_PATTERNS.some(pattern=>pattern.test(combined))||combined.split(/\r?\n/u).some(line=>LINKED_TEST_LOCK_ACQUISITION_PATTERN.test(line)&&LINKED_TEST_LOCK_CONTENTION_CONTEXT_PATTERN.test(line))}function classifyLinkedTestFailure(result){return hasInfraCollisionSignal(result)?"infra_collision":result.timedOut?"timeout":result.maxBufferExceeded?"max_buffer":result.spawnError?"spawn_error":result.signal?"signal":"assertion_failure"}function createEmptyFailureCategoryCounts(){return{infra_collision:0,trust_refusal:0,config_refusal:0,assertion_failure:0,empty_run:0,timeout:0,max_buffer:0,spawn_error:0,signal:0}}function countFailureCategories(runResults){const counts=createEmptyFailureCategoryCounts();for(const result of runResults)result.status!=="failed"&&result.status!=="refused"||!result.failure_category||(counts[result.failure_category]+=1);return counts}function applyEnvDirectiveStage(env,directives){for(const[key,value]of Object.entries(directives.env_set))LINKED_TEST_PROTECTED_ENV_KEYS.has(key.toUpperCase())||(env[key]=value);for(const key of directives.env_clear)LINKED_TEST_PROTECTED_ENV_KEYS.has(key.toUpperCase())||delete env[key]}function applySharedHostSafeDefaults(env){env.PORT===void 0&&(env.PORT="0"),env.HOST===void 0&&(env.HOST="127.0.0.1"),env.PM_SHARED_HOST_SAFE===void 0&&(env.PM_SHARED_HOST_SAFE="1"),env.PLAYWRIGHT_HTML_OPEN===void 0&&(env.PLAYWRIGHT_HTML_OPEN="never"),env.PW_TEST_HTML_REPORT_OPEN===void 0&&(env.PW_TEST_HTML_REPORT_OPEN="never")}function resolveEffectiveLinkedTestDirectives(runtimeDirectives,linkedTest){const envSet={...runtimeDirectives.env_set,...linkedTest.env_set},envClear=[...new Set([...runtimeDirectives.env_clear,...linkedTest.env_clear??[]])],sharedHostSafe=linkedTest.shared_host_safe??runtimeDirectives.shared_host_safe;return{env_set:envSet,env_clear:envClear,shared_host_safe:sharedHostSafe}}function resolveRuntimeDirectives(envSetEntries,envClearEntries,sharedHostSafe){return{env_set:mergeEnvSetDirectives(envSetEntries,"--env-set"),env_clear:mergeEnvClearDirectives(envClearEntries,"--env-clear"),shared_host_safe:sharedHostSafe===!0}}async function copyIntoSandboxIfPresent(sourcePath,targetPath,recursive=!1,makeDirectory=mkdir){if(await pathExists(sourcePath))try{await withHostEnvironmentBoundary("seed_linked_test",async()=>{if(await makeDirectory(path.dirname(targetPath),{recursive:!0}),recursive){await cp(sourcePath,targetPath,{recursive:!0,force:!0});return}await cp(sourcePath,targetPath,{force:!0})},{why:"Linked tests need an isolated PM context without mutating the source tracker.",nextSteps:["Reclaim capacity on the filesystem that hosts the linked-test temporary directory."]})}catch(error){if(typeof error=="object"&&error!==null&&"code"in error&&isFileMissingError(error))return;throw error}}async function seedLinkedTestSandbox(sandboxPmPath,sandboxGlobalPath,sourceRoots,auditSettings=!1){for(const[sourceRoot,sandboxRoot]of[[sourceRoots.projectPmRoot,sandboxPmPath],[sourceRoots.globalPmRoot,sandboxGlobalPath]])if(auditSettings){const raw=await readFileIfExists(getSettingsPath(sourceRoot));if(raw!==null){const settings=await readSettings(sandboxRoot);await writeWorkspaceJsonWithHistory({pmRoot:sandboxRoot,filePath:getSettingsPath(sandboxRoot),raw,op:"settings:write",author:resolveAuthor(void 0,settings.author_default),lockTtlSeconds:settings.locks.ttl_seconds,lockWaitMs:settings.locks.wait_ms,recordCreation:!0,message:"Seed linked-test schema settings history"})}}else await copyIntoSandboxIfPresent(getSettingsPath(sourceRoot),getSettingsPath(sandboxRoot));await copyIntoSandboxIfPresent(path.join(sourceRoots.projectPmRoot,"extensions"),path.join(sandboxPmPath,"extensions"),!0),await copyIntoSandboxIfPresent(path.join(sourceRoots.globalPmRoot,"extensions"),path.join(sandboxGlobalPath,"extensions"),!0)}async function seedLinkedTestTrackerData(sourceRoot,sandboxRoot){if(!await pathExists(sourceRoot))return;const entries=await readdir(sourceRoot,{withFileTypes:!0});for(const entry of entries){if(LINKED_TEST_TRACKER_DIRS_TO_SKIP.has(entry.name))continue;const sourcePath=path.join(sourceRoot,entry.name),targetPath=path.join(sandboxRoot,entry.name);if(entry.isDirectory()){await copyIntoSandboxIfPresent(sourcePath,targetPath,!0);continue}entry.isFile()&&await copyIntoSandboxIfPresent(sourcePath,targetPath)}}async function seedMissingLinkedTestSettingsHistory(sourceRoot,sandboxRoot){const sourceSettingsRaw=await readFileIfExists(getSettingsPath(sourceRoot));if(sourceSettingsRaw===null||await pathExists(getWorkspaceHistoryPath(sourceRoot)))return;const sandboxSettings=await readSettings(sandboxRoot);await rm(getWorkspaceHistoryPath(sandboxRoot),{force:!0}),await rm(getSettingsPath(sandboxRoot),{force:!0}),await writeWorkspaceJsonWithHistory({pmRoot:sandboxRoot,filePath:getSettingsPath(sandboxRoot),raw:sourceSettingsRaw,op:"settings:write",author:resolveAuthor(void 0,sandboxSettings.author_default),lockTtlSeconds:sandboxSettings.locks.ttl_seconds,lockWaitMs:sandboxSettings.locks.wait_ms,recordCreation:!0,message:"Seed linked-test tracker settings history"})}async function countLinkedTestItemFiles(pmRoot){if(!await pathExists(pmRoot))return 0;let total=0;const entries=await readdir(pmRoot,{withFileTypes:!0});for(const entry of entries){if(!entry.isDirectory()||LINKED_TEST_ITEM_COUNT_DIRS_TO_SKIP.has(entry.name))continue;const folderPath=path.join(pmRoot,entry.name);let files;try{files=await readdir(folderPath,{withFileTypes:!0})}catch{continue}for(const file of files)file.isFile()&&ITEM_FILE_EXTENSIONS.some(extension=>file.name.toLowerCase().endsWith(extension))&&(total+=1)}return total}function resolveLinkedTestFailureExitCode(execution){const rawExitCode=typeof execution.exitCode=="number"?execution.exitCode:1;return(execution.timedOut||execution.maxBufferExceeded)&&rawExitCode===0?1:rawExitCode}function parseAssertionLiteral(raw){const trimmed=raw.trim();if(trimmed==="true")return!0;if(trimmed==="false")return!1;if(trimmed==="null")return null;const numeric=Number(trimmed);if(trimmed.length>0&&Number.isFinite(numeric))return numeric;if(trimmed.startsWith("{")&&trimmed.endsWith("}")||trimmed.startsWith("[")&&trimmed.endsWith("]"))try{return JSON.parse(trimmed)}catch{}return trimmed}function compareAssertionValues(actual,expected){return typeof actual=="object"&&actual!==null&&typeof expected=="object"&&expected!==null?stableValueEquals(actual,expected):Object.is(actual,expected)}function evaluateLinkedTestAssertions(linkedTest,stdout,stderr){const failures=[];failures.push(...evaluateContainsAssertions("stdout",stdout,linkedTest.assert_stdout_contains)),failures.push(...evaluateRegexAssertions("stdout",stdout,linkedTest.assert_stdout_regex)),failures.push(...evaluateContainsAssertions("stderr",stderr,linkedTest.assert_stderr_contains)),failures.push(...evaluateRegexAssertions("stderr",stderr,linkedTest.assert_stderr_regex)),failures.push(...evaluateStdoutLineCountAssertion(stdout,linkedTest.assert_stdout_min_lines));const jsonEqualsAssertions=linkedTest.assert_json_field_equals??{},jsonGteAssertions=linkedTest.assert_json_field_gte??{};if(!(Object.keys(jsonEqualsAssertions).length>0||Object.keys(jsonGteAssertions).length>0))return failures;let parsedJson;try{parsedJson=JSON.parse(stdout)}catch(error){return failures.push(`stdout is not valid JSON for assert_json_field_* checks: ${error instanceof Error?error.message:String(error)}`),failures}for(const[fieldPath,expectedRaw]of Object.entries(jsonEqualsAssertions)){const resolved=readJsonPathValue(parsedJson,fieldPath);if(!resolved.found){failures.push(`assert_json_field_equals missing path "${fieldPath}"`);continue}const expected=parseAssertionLiteral(expectedRaw);compareAssertionValues(resolved.value,expected)||failures.push(`assert_json_field_equals mismatch at "${fieldPath}" (expected=${JSON.stringify(expected)} actual=${JSON.stringify(resolved.value)})`)}for(const[fieldPath,expectedMinimum]of Object.entries(jsonGteAssertions)){const resolved=readJsonPathValue(parsedJson,fieldPath);if(!resolved.found){failures.push(`assert_json_field_gte missing path "${fieldPath}"`);continue}if(typeof resolved.value!="number"||!Number.isFinite(resolved.value)){failures.push(`assert_json_field_gte path "${fieldPath}" resolved to non-numeric value`);continue}resolved.value<expectedMinimum&&failures.push(`assert_json_field_gte failed at "${fieldPath}" (expected >= ${expectedMinimum}, actual ${resolved.value})`)}return failures}function evaluateContainsAssertions(streamName,output,expectedValues){const failures=[];for(const expected of expectedValues??[])output.includes(expected)||failures.push(`${streamName} missing required text: "${expected}"`);return failures}function evaluateRegexAssertions(streamName,output,patterns){const failures=[];for(const pattern of patterns??[])try{new RegExp(pattern,"m").test(output)||failures.push(`${streamName} failed regex assertion: /${pattern}/m`)}catch(error){failures.push(`${streamName} regex assertion is invalid: /${pattern}/ (${error instanceof Error?error.message:String(error)})`)}return failures}function evaluateStdoutLineCountAssertion(stdout,minimum){if(typeof minimum!="number")return[];const lineCount=stdout.split(/\r?\n/).map(line=>line.trim()).filter(line=>line.length>0).length;return lineCount<minimum?[`stdout line count ${lineCount} is below required minimum ${minimum}`]:[]}function commandUsesTestNameFilter(command){const normalized=normalizeCommandForValidation(command);return/(?:^|\s)(?:--test-name-pattern|--testnamepattern)(?:=|\s)/u.test(normalized)?!0:splitNormalizedCommandSegments(normalized).some(segment=>/(?:^|\s)-t(?:=|\s)/u.test(segment)&&segmentInvokesDirectTestRunner(segment))}function createLinkedTestSandboxLayout(sandboxRoot){return{root:sandboxRoot,schemaProjectPmPath:path.join(sandboxRoot,"schema","project",".agents","pm"),schemaGlobalPmPath:path.join(sandboxRoot,"schema","global"),trackerProjectPmPath:path.join(sandboxRoot,"tracker","project",".agents","pm"),trackerGlobalPmPath:path.join(sandboxRoot,"tracker","global"),workspaceSnapshotRoot:path.join(sandboxRoot,"workspace","project"),workspaceIsolatedRoot:path.join(sandboxRoot,"workspace","isolated")}}function linkedTestsRequireWorkspaceSnapshot(tests,runLevelMode,overrideLinkedMode){return tests.some(linkedTest=>resolveLinkedTestWorkspaceContextMode(linkedTest,runLevelMode,overrideLinkedMode)==="snapshot")}async function bindSnapshotTracker(snapshotRoot,sandboxPmPath){const agentsRoot=path.join(snapshotRoot,".agents"),target=path.join(agentsRoot,"pm");await mkdir(agentsRoot,{recursive:!0}),await rm(target,{recursive:!0,force:!0}),await symlink(sandboxPmPath,target,process.platform==="win32"?"junction":"dir")}async function initializeLinkedTestSandboxes(layout,initialize=runInit,includeTrackerData=!0){const initOptions={defaults:!0,agentGuidance:"skip"};await initialize(void 0,{path:layout.schemaProjectPmPath},initOptions),await initialize(void 0,{path:layout.schemaGlobalPmPath},initOptions),includeTrackerData&&(await initialize(void 0,{path:layout.trackerProjectPmPath},initOptions),await initialize(void 0,{path:layout.trackerGlobalPmPath},initOptions))}async function seedLinkedTestSandboxesFromSource(layout,sourceRoots,includeTrackerData){sourceRoots&&(await seedLinkedTestSandbox(layout.schemaProjectPmPath,layout.schemaGlobalPmPath,sourceRoots,!0),includeTrackerData&&(await seedLinkedTestSandbox(layout.trackerProjectPmPath,layout.trackerGlobalPmPath,sourceRoots),await seedLinkedTestTrackerData(sourceRoots.projectPmRoot,layout.trackerProjectPmPath),await seedLinkedTestTrackerData(sourceRoots.globalPmRoot,layout.trackerGlobalPmPath),await seedMissingLinkedTestSettingsHistory(sourceRoots.projectPmRoot,layout.trackerProjectPmPath),await seedMissingLinkedTestSettingsHistory(sourceRoots.globalPmRoot,layout.trackerGlobalPmPath)))}function linkedTestsRequireTrackerData(tests,runLevelPmContextMode,options){return tests.some(linkedTest=>{const command=linkedTest.command??"",isPmTrackerReadCommand=command.length>0&&commandInvokesPmTrackerReadCommand(command),requestedPmContextMode=options?.autoPmContext===!0&&isPmTrackerReadCommand?"auto":resolveLinkedTestRequestedContextMode(linkedTest,runLevelPmContextMode,options?.overrideLinkedPmContext===!0);return resolveLinkedTestEffectiveContextMode(requestedPmContextMode,isPmTrackerReadCommand)==="tracker"})}async function countLinkedTestSandboxItems(layout,sourceRoots){return{sourceProjectItemCount:sourceRoots?await countLinkedTestItemFiles(sourceRoots.projectPmRoot):0,sourceGlobalItemCount:sourceRoots?await countLinkedTestItemFiles(sourceRoots.globalPmRoot):0,schemaProjectItemCount:await countLinkedTestItemFiles(layout.schemaProjectPmPath),schemaGlobalItemCount:await countLinkedTestItemFiles(layout.schemaGlobalPmPath),trackerProjectItemCount:await countLinkedTestItemFiles(layout.trackerProjectPmPath),trackerGlobalItemCount:await countLinkedTestItemFiles(layout.trackerGlobalPmPath)}}function buildLinkedTestExecutionContext(params){const trackerMode=params.effectivePmContextMode==="tracker",selectedSandboxProjectPmPath=trackerMode?params.layout.trackerProjectPmPath:params.layout.schemaProjectPmPath,selectedSandboxGlobalPmPath=trackerMode?params.layout.trackerGlobalPmPath:params.layout.schemaGlobalPmPath,selectedSandboxProjectItemCount=trackerMode?params.counts.trackerProjectItemCount:params.counts.schemaProjectItemCount,selectedSandboxGlobalItemCount=trackerMode?params.counts.trackerGlobalItemCount:params.counts.schemaGlobalItemCount,workingDirectory=params.workspaceContextMode==="snapshot"?params.layout.workspaceSnapshotRoot:params.workspaceContextMode==="isolated"?params.layout.workspaceIsolatedRoot:params.sourceWorkspaceRoot,exposedSourceWorkspaceRoot=params.workspaceContextMode==="isolated"?"":workingDirectory;return{requested_pm_context_mode:params.requestedPmContextMode,pm_context_mode:params.effectivePmContextMode,auto_pm_context_applied:params.autoPmContextApplied,is_pm_command:params.isPmCommand,is_pm_tracker_read_command:params.isPmTrackerReadCommand,source_project_pm_path:params.sourceRoots?.projectPmRoot??"",sandbox_project_pm_path:selectedSandboxProjectPmPath,source_global_pm_path:params.sourceRoots?.globalPmRoot??"",sandbox_global_pm_path:selectedSandboxGlobalPmPath,source_project_item_count:params.counts.sourceProjectItemCount,sandbox_project_item_count:selectedSandboxProjectItemCount,source_global_item_count:params.counts.sourceGlobalItemCount,sandbox_global_item_count:selectedSandboxGlobalItemCount,mismatch_detected:params.isPmCommand&&(params.counts.sourceProjectItemCount!==selectedSandboxProjectItemCount||params.counts.sourceGlobalItemCount!==selectedSandboxGlobalItemCount),project_extensions_seeded:!!params.sourceRoots,global_extensions_seeded:!!params.sourceRoots,requested_workspace_context_mode:params.requestedWorkspaceContextMode,workspace_context_mode:params.workspaceContextMode,working_directory:workingDirectory,source_workspace_root:exposedSourceWorkspaceRoot,trust:params.trust}}async function resolveLinkedTestCommandContext(params){const linkedOverridePmContextMode=typeof params.linkedTest.pm_context_mode=="string"&¶ms.linkedTest.pm_context_mode.trim().length>0?parsePmContextMode(params.linkedTest.pm_context_mode):void 0,command=typeof params.linkedTest.command=="string"&¶ms.linkedTest.command.length>0?params.linkedTest.command:void 0,isPmCommand=command?commandInvokesPmCli(command):!1,isPmTrackerReadCommand=isPmCommand&&command?commandInvokesPmTrackerReadCommand(command):!1,autoPmContextApplied=params.options?.autoPmContext===!0&&isPmTrackerReadCommand,requestedPmContextMode=autoPmContextApplied?"auto":resolveLinkedTestRequestedContextMode(params.linkedTest,params.runLevelPmContextMode,params.options?.overrideLinkedPmContext===!0),effectivePmContextMode=resolveLinkedTestEffectiveContextMode(requestedPmContextMode,isPmTrackerReadCommand),selectedSandboxProjectPmPath=effectivePmContextMode==="tracker"?params.layout.trackerProjectPmPath:params.layout.schemaProjectPmPath;params.workspaceContextMode==="snapshot"&&await bindSnapshotTracker(params.layout.workspaceSnapshotRoot,selectedSandboxProjectPmPath);const trust=params.options?.pmRoot?await resolveLinkedTestTrust(params.options.pmRoot,params.linkedTest,params.currentSourceRef):await resolveLinkedTestTrust(params.layout.root,params.linkedTest,params.currentSourceRef);return{linkedOverridePmContextMode,executionContext:buildLinkedTestExecutionContext({layout:params.layout,counts:params.counts,sourceRoots:params.sourceRoots,isPmCommand,isPmTrackerReadCommand,requestedPmContextMode,effectivePmContextMode,autoPmContextApplied,requestedWorkspaceContextMode:params.requestedWorkspaceContextMode,workspaceContextMode:params.workspaceContextMode,sourceWorkspaceRoot:params.sourceWorkspaceRoot,trust})}}function buildLinkedTestSkippedResult(linkedTest,executionContext,error){return{command:linkedTest.command,path:linkedTest.path,status:"skipped",execution_context:executionContext,error}}function buildLinkedTestAssertionFailureResult(linkedTest,executionContext,error,output){return{command:linkedTest.command,path:linkedTest.path,status:"failed",exit_code:1,failure_category:"assertion_failure",execution_context:executionContext,...output?{stdout:output.stdout,stderr:output.stderr}:{},error}}function buildLinkedTestTrustRefusalResult(linkedTest,executionContext,error){return{command:linkedTest.command,path:linkedTest.path,status:"failed",exit_code:1,failure_category:"trust_refusal",execution_context:executionContext,error}}function resolveLinkedTestTrustPreflightResult(params){if(params.executionContext.trust.trusted)return null;const overrideRequested=params.options?.allowUntrustedLinkedTests===!0,policyEnabled=params.options?.untrustedLinkedTestsPolicyEnabled===!0;if(overrideRequested&&policyEnabled)return null;const recovery=policyEnabled?"Retry with --allow-untrusted-linked-tests for this invocation, or acknowledge the current commands with --acknowledge-linked-tests.":"Set testing.allow_untrusted_linked_tests=true, then retry with --allow-untrusted-linked-tests; alternatively acknowledge the current commands with --acknowledge-linked-tests.";return buildLinkedTestTrustRefusalResult(params.linkedTest,params.executionContext,`Linked test command is not trusted by this clone (source_ref=${params.executionContext.trust.source_ref??"unknown"}, current_ref=${params.executionContext.trust.current_source_ref??"unknown"}). ${recovery}`)}function resolveLinkedTestSafetyPreflightResult(params){if(!params.linkedTest.command)return buildLinkedTestSkippedResult(params.linkedTest,params.executionContext,"No command configured for this linked test.");const runtimeSafetySkipReason=getRuntimeSafetySkipReason(params.linkedTest.command);return runtimeSafetySkipReason?buildLinkedTestSkippedResult(params.linkedTest,params.executionContext,runtimeSafetySkipReason):null}function resolveUnboundPmContextFailure(params){return params.executionContext.pm_context_mode!=="none"?null:params.executionContext.workspace_context_mode==="source"?buildLinkedTestConfigurationRefusalResult(params.linkedTest,params.executionContext,"pm_context_mode=none requires workspace_context_mode=isolated or snapshot so implicit tracker discovery cannot reach the source workspace."):params.executionContext.is_pm_command?buildLinkedTestConfigurationRefusalResult(params.linkedTest,params.executionContext,"pm_context_mode=none is for self-isolating SDK tests; direct PM commands require schema, tracker, or auto context."):null}function buildLinkedTestConfigurationRefusalResult(linkedTest,executionContext,error){return{command:linkedTest.command,path:linkedTest.path,status:"refused",exit_code:null,failure_category:"config_refusal",execution_context:executionContext,error}}function resolvePmContextMismatchFailure(params){const failOnMismatchByDefault=params.executionContext.pm_context_mode==="schema"&¶ms.executionContext.is_pm_tracker_read_command&¶ms.executionContext.mismatch_detected,failOnMismatchByFlag=params.options?.failOnContextMismatch===!0&¶ms.executionContext.is_pm_command&¶ms.executionContext.mismatch_detected;if(!failOnMismatchByDefault&&!failOnMismatchByFlag)return null;const mismatchHint=buildPmContextMismatchHint({executionContext:params.executionContext,runLevelPmContextMode:params.runLevelPmContextMode,linkedOverridePmContextMode:params.linkedOverridePmContextMode}),mismatchPrefix=params.options?.checkContext===!0?"Linked test preflight PM context mismatch detected":"Linked test PM context mismatch detected";return buildLinkedTestConfigurationRefusalResult(params.linkedTest,params.executionContext,`${mismatchPrefix} (source_project_items=${params.executionContext.source_project_item_count}, sandbox_project_items=${params.executionContext.sandbox_project_item_count}).${mismatchHint}`)}function resolveLinkedTestPreflightResult(params){return resolveLinkedTestSafetyPreflightResult(params)||resolveLinkedTestTrustPreflightResult(params)||resolveUnboundPmContextFailure(params)||resolvePmContextMismatchFailure(params)||(params.options?.requireAssertionsForPm===!0&¶ms.executionContext.is_pm_command&&!hasLinkedTestAssertions(params.linkedTest)?buildLinkedTestConfigurationRefusalResult(params.linkedTest,params.executionContext,"Linked PM command requires assertions when --require-assertions-for-pm is enabled."):null)}function buildLinkedTestExecutionEnv(params){const effectiveDirectives=resolveEffectiveLinkedTestDirectives(params.runtimeDirectives,params.linkedTest),executionEnv={...process.env,SENTRY_ENVIRONMENT:"test"};return applyEnvDirectiveStage(executionEnv,params.runtimeDirectives),applyEnvDirectiveStage(executionEnv,{env_set:params.linkedTest.env_set??{},env_clear:params.linkedTest.env_clear??[]}),effectiveDirectives.shared_host_safe&&applySharedHostSafeDefaults(executionEnv),executionEnv.FORCE_COLOR="0",params.executionContext.pm_context_mode==="none"?delete executionEnv.PM_PATH:executionEnv.PM_PATH=params.executionContext.sandbox_project_pm_path,executionEnv.PM_GLOBAL_PATH=params.executionContext.sandbox_global_pm_path,params.executionContext.workspace_context_mode!=="source"&&(delete executionEnv.PM_BUILD_CONSUMER_LEASE,delete executionEnv.PM_BUILD_CONSUMER_LEASE_ROOT),params.executionContext.workspace_context_mode==="isolated"?(delete executionEnv.PM_SOURCE_WORKSPACE_ROOT,delete executionEnv.PM_SOURCE_PM_PATH,delete executionEnv[SOURCE_CONTEXT_ACCESS_ENV]):(executionEnv.PM_SOURCE_WORKSPACE_ROOT=params.executionContext.source_workspace_root,executionEnv.PM_SOURCE_PM_PATH=params.executionContext.workspace_context_mode==="snapshot"?params.executionContext.sandbox_project_pm_path:params.executionContext.source_project_pm_path,executionEnv[SOURCE_CONTEXT_ACCESS_ENV]="read_only"),executionEnv}function buildLinkedTestPassedResult(linkedTest,executionContext,execution,receipt){return{command:linkedTest.command,path:linkedTest.path,status:"passed",exit_code:0,execution_context:executionContext,stdout:execution.stdout,stderr:execution.stderr,...receipt?{execution_receipt:receipt}:{}}}function buildLinkedTestEmptyRunResult(params){return{command:params.linkedTest.command,path:params.linkedTest.path,status:"failed",exit_code:1,failure_category:"empty_run",execution_context:params.executionContext,stdout:params.execution.stdout,stderr:params.execution.stderr,error:`Linked test reported an empty test run (${params.emptyRunSignal.code}). Update the test selection so at least one test executes; unfiltered commands may opt out by omitting --fail-on-empty-test-run.`}}function buildLinkedTestCommandFailureResult(params){return{command:params.linkedTest.command,path:params.linkedTest.path,status:"failed",exit_code:resolveLinkedTestFailureExitCode(params.execution),failure_category:classifyLinkedTestFailure(params.execution),execution_context:params.executionContext,stdout:params.execution.stdout,stderr:params.execution.stderr,error:formatLinkedTestExecutionError(params.execution,params.timeoutMs)}}function buildLinkedTestPassedExecutionResult(params){const receipt=findLinkedTestExecutionReceipt(params.execution.stdout,params.execution.stderr);if(params.options?.failOnEmptyTestRun===!0||commandUsesTestNameFilter(params.linkedTest.command??"")){const emptyRunSignal=detectEmptyLinkedTestRun(params.execution.stdout,params.execution.stderr);if(emptyRunSignal)return buildLinkedTestEmptyRunResult({...params,emptyRunSignal});if(commandUsesTestNameFilter(params.linkedTest.command??"")&&!receipt)return buildLinkedTestEmptyRunResult({...params,emptyRunSignal:{code:"missing_positive_execution_receipt"}})}const assertionFailures=evaluateLinkedTestAssertions(params.linkedTest,params.execution.stdout,params.execution.stderr);return assertionFailures.length>0?buildLinkedTestAssertionFailureResult(params.linkedTest,params.executionContext,`Linked test assertion(s) failed: ${assertionFailures.join("; ")}`,params.execution):buildLinkedTestPassedResult(params.linkedTest,params.executionContext,params.execution,receipt)}async function runSingleLinkedTest(params){const command=params.linkedTest.command??"",timeoutMs=(params.linkedTest.timeout_seconds??params.defaultTimeoutSeconds??120)*1e3,execution=await runLinkedTestCommand(command,timeoutMs,buildLinkedTestExecutionEnv(params),{index:params.index+1,total:params.total,timeoutMs,command},params.progressMode,params.executionContext.working_directory);return execution.exitCode===0&&!execution.timedOut&&!execution.maxBufferExceeded?buildLinkedTestPassedExecutionResult({...params,execution}):buildLinkedTestCommandFailureResult({...params,execution,timeoutMs})}async function runLinkedTests(tests,defaultTimeoutSeconds,options){for(const test of tests)assertLinkedTestCommand(test.command);const results=[],sandboxRoot=await mkdtemp(path.join(tmpdir(),"pm-linked-test-")),layout=createLinkedTestSandboxLayout(sandboxRoot),runLevelPmContextMode=parsePmContextMode(options?.pmContext),runLevelWorkspaceContextMode=parseWorkspaceContextMode(options?.workspaceContext),progressMode=options?.progress===!0?"always":"auto",runtimeDirectives=resolveRuntimeDirectives(options?.envSet,options?.envClear,options?.sharedHostSafe),sourceRoots=options?.sourceRoots,sourceWorkspaceRoot=resolveLinkedTestSourceWorkspaceRoot(),includeTrackerData=linkedTestsRequireTrackerData(tests,runLevelPmContextMode,options),includeWorkspaceSnapshot=linkedTestsRequireWorkspaceSnapshot(tests,runLevelWorkspaceContextMode,options?.overrideLinkedWorkspaceContext===!0),currentSourceRef=await resolveLinkedTestSourceRef(sourceWorkspaceRoot);try{await initializeLinkedTestSandboxes(layout,runInit,includeTrackerData),await mkdir(layout.workspaceIsolatedRoot,{recursive:!0}),await seedLinkedTestSandboxesFromSource(layout,sourceRoots,includeTrackerData),includeWorkspaceSnapshot&&await seedLinkedTestWorkspaceSnapshot(sourceWorkspaceRoot,layout.workspaceSnapshotRoot,sourceRoots?.projectPmRoot,includeTrackerData);const counts=await countLinkedTestSandboxItems(layout,sourceRoots);for(let index=0;index<tests.length;index+=1){const linkedTest=tests[index],workspaceContextMode=resolveLinkedTestWorkspaceContextMode(linkedTest,runLevelWorkspaceContextMode,options?.overrideLinkedWorkspaceContext===!0),commandContext=await resolveLinkedTestCommandContext({linkedTest,layout,counts,sourceRoots,runLevelPmContextMode,options,requestedWorkspaceContextMode:runLevelWorkspaceContextMode,workspaceContextMode,currentSourceRef,sourceWorkspaceRoot}),preflightResult=resolveLinkedTestPreflightResult({linkedTest,executionContext:commandContext.executionContext,runLevelPmContextMode,linkedOverridePmContextMode:commandContext.linkedOverridePmContextMode,options});if(preflightResult){results.push(preflightResult);continue}results.push(await runSingleLinkedTest({linkedTest,index,total:tests.length,defaultTimeoutSeconds,runtimeDirectives,progressMode,executionContext:commandContext.executionContext,options}))}}finally{await rm(layout.root,{recursive:!0,force:!0,maxRetries:3,retryDelay:100})}return results}async function readLinkedTestItem(params){const{id,pmRoot,settings,typeToFolder}=params,located=await locateItem(pmRoot,id,settings.id_prefix,settings.item_format,typeToFolder);if(!located)throw new PmCliError(`Item ${id} not found`,EXIT_CODE.NOT_FOUND);const loaded=await readLocatedItem(located,{schema:settings.schema});return{itemId:located.id,tests:loaded.document.metadata.tests??[],testRuns:loaded.document.metadata.test_runs??[],changed:!1,removed:0}}function appendMissingLinkedTests(current,additions){const next=[...current];for(const addition of additions)next.some(entry=>linkedTestsHaveSameIdentity(entry,addition))||next.push(addition);return next}function removeLinkedTestsBySelector(current,removals,removeIndexes){if(removals.length===0&&removeIndexes.length===0)return{tests:current,removed:0};const indexSet=new Set(removeIndexes),tests=current.filter((entry,index)=>indexSet.has(index+1)?!1:!removals.some(selector=>selector.kind==="command"?entry.command===selector.value:selector.kind==="path"?entry.path===selector.value:entry.command===selector.value||entry.path===selector.value));return{tests,removed:current.length-tests.length}}function applyLinkedTestMutations(previous,adds,removes,removeIndexes){return removeLinkedTestsBySelector(appendMissingLinkedTests(previous,adds),removes,removeIndexes)}function hasTestRuntimeDirectiveFlags(options){return[options.timeout!==void 0,options.progress===!0,(options.envSet?.length??0)>0,(options.envClear?.length??0)>0,options.sharedHostSafe===!0,options.pmContext!==void 0,options.overrideLinkedPmContext===!0,options.failOnContextMismatch===!0,options.failOnSkipped===!0,options.failOnEmptyTestRun===!0,options.requireAssertionsForPm===!0,options.checkContext===!0,options.autoPmContext===!0,options.workspaceContext!==void 0,options.overrideLinkedWorkspaceContext===!0,options.allowUntrustedLinkedTests===!0].some(Boolean)}function assertTestRunFlagUsage(options){if(hasTestRuntimeDirectiveFlags(options)&&options.run!==!0)throw new PmCliError("--timeout, --progress, --env-set, --env-clear, --shared-host-safe, --pm-context, --override-linked-pm-context, --workspace-context, --override-linked-workspace-context, --allow-untrusted-linked-tests, --fail-on-context-mismatch, --fail-on-skipped, --fail-on-empty-test-run, --require-assertions-for-pm, --check-context, and --auto-pm-context require --run",EXIT_CODE.USAGE);if(options.acknowledgeLinkedTests===!0&&options.run===!0)throw new PmCliError("--acknowledge-linked-tests records trust without executing commands; run it separately before --run",EXIT_CODE.USAGE);if((options.match!==void 0||options.onlyIndex!==void 0||options.onlyLast===!0)&&options.run!==!0)throw new PmCliError("--match, --only-index, and --only-last require --run",EXIT_CODE.USAGE)}async function resolveLinkedTestItem(params){const{id,options,pmRoot,settings,typeToFolder,adds,removes,removeIndexes}=params;if(adds.length===0&&removes.length===0&&removeIndexes.length===0)return readLinkedTestItem({id,pmRoot,settings,typeToFolder});let removed=0;const result=await mutateItem({pmRoot,settings,id,op:adds.length>0?"tests_add":"tests_remove",author:resolveAuthor(options.author,settings.author_default),message:options.message,force:options.force,skipNoop:!0,mutate(document){const previous=document.metadata.tests??[],mutation=applyLinkedTestMutations(previous,adds,removes,removeIndexes);if(admitLinkedTestRequest(()=>resolveTestRunOptions(options,mutation.tests)),removed=mutation.removed,(removes.length>0||removeIndexes.length>0)&&removed===0)throw new PmCliError("No linked tests matched the requested removal selector",EXIT_CODE.NOT_FOUND,{code:"linked_test_remove_no_match",unmatched:[...removes.map(entry=>`${entry.kind}=${entry.value}`),...removeIndexes.map(index=>`index=${index}`)],required:"List linked tests and retry with an exact command/path or a current 1-based --remove-index.",examples:[`pm test ${id} --list`,`pm test ${id} --remove-index 1`]});return document.metadata.tests=mutation.tests,{changedFields:stableValueEquals(previous,mutation.tests)?[]:["tests"]}}});return{itemId:result.item.id,tests:result.item.tests??[],testRuns:result.item.test_runs??[],changed:result.changedFields.length>0,removed}}function resolveTestRunOptions(options,tests){const pmContextMode=parsePmContextMode(options.pmContext);parseWorkspaceContextMode(options.workspaceContext),resolveRuntimeDirectives(options.envSet,options.envClear,options.sharedHostSafe);const selectorInput={match:options.match,onlyIndex:options.onlyIndex===void 0?void 0:parseOnlyIndexValue(options.onlyIndex),onlyLast:options.onlyLast},defaultTimeoutSeconds=options.timeout===void 0?void 0:parseOptionalNumber(options.timeout,"--timeout");if(defaultTimeoutSeconds!==void 0&&defaultTimeoutSeconds<=0)throw new PmCliError(`--timeout must be a positive number (got "${options.timeout}")`,EXIT_CODE.USAGE);assertTestRunFlagUsage(options);const runSelection=options.run===!0?resolveLinkedTestRunSelection(tests,selectorInput):void 0;if(runSelection)for(const test of runSelection.selected)assertLinkedTestCommand(test.command);return{defaultTimeoutSeconds,pmContextMode,runSelection,testsToRun:runSelection?.selected??tests}}async function executeSelectedLinkedTests(params){const{options,runOptions,pmRoot}=params;return options.run!==!0?[]:await runLinkedTests(runOptions.testsToRun,runOptions.defaultTimeoutSeconds,{progress:options.progress,envSet:options.envSet,envClear:options.envClear,sharedHostSafe:options.sharedHostSafe,pmContext:runOptions.pmContextMode,overrideLinkedPmContext:options.overrideLinkedPmContext,failOnContextMismatch:options.failOnContextMismatch,failOnEmptyTestRun:options.failOnEmptyTestRun===!0||(options.measure?.length??0)>0,requireAssertionsForPm:options.requireAssertionsForPm,checkContext:options.checkContext,autoPmContext:options.autoPmContext,workspaceContext:options.workspaceContext,overrideLinkedWorkspaceContext:options.overrideLinkedWorkspaceContext,pmRoot,allowUntrustedLinkedTests:options.allowUntrustedLinkedTests,untrustedLinkedTestsPolicyEnabled:params.untrustedLinkedTestsPolicyEnabled,sourceRoots:{projectPmRoot:pmRoot,globalPmRoot:resolveGlobalPmRoot(process.cwd())}})}function buildTestWarnings(options,runResults){const warnings=[];if(options.run===!0&&options.checkContext===!0){const preflight=summarizeContextPreflight(runResults);warnings.push(`context_preflight:checked_pm_commands=${preflight.checked_pm_commands};tracker_read_commands=${preflight.tracker_read_commands};mismatches=${preflight.mismatches};auto_remediated=${preflight.auto_remediated}`)}return warnings}function buildTrackedTestRunSummary(params){const summary=summarizeRunResultStatuses(params.runResults),parsedAttempt=Number.parseInt(process.env.PM_BACKGROUND_TEST_RUN_ATTEMPT?.trim()??"",10),resumedFrom=process.env.PM_BACKGROUND_TEST_RUN_RESUMED_FROM?.trim(),recordedAt=nowIso(),measurements=parseTestRunMeasurements(params.options.measure,recordedAt);return{run_id:resolveTrackedRunId("test"),kind:"test",status:summary.failed>0||params.failOnSkippedTriggered?"failed":"passed",started_at:params.runStartedAt,finished_at:recordedAt,recorded_at:recordedAt,attempt:Number.isFinite(parsedAttempt)&&parsedAttempt>=1?parsedAttempt:void 0,resumed_from:resumedFrom&&resumedFrom.length>0?resumedFrom:void 0,passed:summary.passed,failed:summary.failed,skipped:summary.skipped,fail_on_skipped_triggered:params.failOnSkippedTriggered?!0:void 0,measurements:measurements.length>0?measurements:void 0,executions:params.runResults.filter(result=>result.command?.trim()).slice(0,32).map(result=>({command:result.command.trim(),requested_pm_context_mode:result.execution_context?.requested_pm_context_mode,pm_context_mode:result.execution_context?.pm_context_mode,workspace_context_mode:result.execution_context?.workspace_context_mode,trust_reason:result.execution_context?.trust.reason}))}}async function recordTestRunSummary(params){const{options,pmRoot,settings,itemId,runStartedAt,runResults,failOnSkippedTriggered,warnings}=params;if(options.run!==!0||!runStartedAt)return;const entry=buildTrackedTestRunSummary({options,runStartedAt,runResults,failOnSkippedTriggered});if(settings.testing.record_results_to_items!==!0)return{entry,receipt:{recorded:!1,reason:"tracking_disabled",run_id:entry.run_id,recovery_command:"pm config project set test-result-tracking --policy enabled"}};try{return await appendTrackedTestRunSummary({pmRoot,settings,itemId,author:resolveAuthor(options.author,settings.author_default),message:`Track test run summary (${entry.run_id})`,entry}),{entry,receipt:{recorded:!0,reason:"recorded",run_id:entry.run_id}}}catch(error){if(isItemRevisionConflict(error))throw error;return warnings.push(`test_result_tracking_failed:${itemId}:${error instanceof Error?error.message:String(error)}`),{entry,receipt:{recorded:!1,reason:"write_failed",run_id:entry.run_id}}}}function buildTestMeasurementProjection(options,runs,recordedRun){let metricBelow;if(options.metricBelow!==void 0){const separator=options.metricBelow.indexOf("="),name=options.metricBelow.slice(0,separator).trim(),thresholdText=options.metricBelow.slice(separator+1).trim(),threshold=Number(thresholdText);if(separator<=0||name.length===0||thresholdText.length===0||!Number.isFinite(threshold))throw new PmCliError("--metric-below must use name=value",EXIT_CODE.USAGE);metricBelow=queryTestRunMeasurementsBelow(runs,name,threshold)}return{measurements:recordedRun?.measurements,metric_below:metricBelow,metric_diff:options.metricDiff===void 0?void 0:diffTestRunMeasurements(runs,options.metricDiff)}}async function maybeAcknowledgeLinkedTests(options,pmRoot,tests){if(options.acknowledgeLinkedTests===!0)return acknowledgeLinkedTests(pmRoot,tests,nowIso())}function projectTestResult(input){const{item,runResults,failureCategories,runSelection,failOnSkippedTriggered,warnings,recording,measurementProjection,restoreScope}=input,narrowed=runSelection!==void 0&&runSelection.selector!==null&&runSelection.skipped_count>0,restoreWith=renderLinkedTestRestoreCommand(item.itemId,restoreScope);return{ok:runResults.every(entry=>entry.status!=="failed"&&entry.status!=="refused")&&failOnSkippedTriggered!==!0,id:item.itemId,tests:narrowed?runSelection.selected:item.tests,...narrowed?{tests_omitted:{count:item.tests.length-runSelection.selected.length,restore_with:restoreWith}}:{},run_results:runResults,failure_categories:failureCategories,selection:runSelection?{selector:runSelection.selector,requested:runSelection.requested,selected_indexes:runSelection.selected_indexes,selected_count:runSelection.selected_count,skipped_count:runSelection.skipped_count}:void 0,fail_on_skipped_triggered:failOnSkippedTriggered?!0:void 0,warnings:warnings.length>0?warnings:void 0,changed:item.changed||recording?.receipt.recorded===!0,...recording?{evidence_recording:recording.receipt}:{},count:item.tests.length,...item.removed>0?{removed:item.removed}:{},...measurementProjection}}async function runTest(id,options,global2){const edit=parseLinkedTestUpdateOptions(options);if(edit)return assertLinkedTestEditUsage(options),assertTestRunFlagUsage(options),assertTestMeasurementRun(id,options,global2,resolvePmRoot(process.cwd(),global2.path)),{...await updateLinkedTest(id,{...edit,author:options.author,message:options.message,force:options.force},global2),run_results:[],failure_categories:countFailureCategories([])};const stdinResolver=createStdinTokenResolver(),pmRoot=resolvePmRoot(process.cwd(),global2.path);await assertInitializedTracker(pmRoot);const settings=await readSettings(pmRoot);assertTestMeasurementRun(id,options,global2,pmRoot),parseTestRunMeasurements(options.measure,"validation"),buildTestMeasurementProjection(options,[],void 0);const typeRegistry=resolveItemTypeRegistry(settings,getActiveExtensionRegistrations()),resolvedAdds=await stdinResolver.resolveList(options.add,"--add"),resolvedAddJsons=await stdinResolver.resolveList(options.addJson,"--add-json"),resolvedRemoves=await stdinResolver.resolveList(options.remove,"--remove"),parsedAdds=[...parseAddEntries(resolvedAdds),...parseAddJsonEntries(resolvedAddJsons)],adds=await attachLinkedTestMutationProvenance(parsedAdds,resolveAuthor(options.author,settings.author_default),nowIso())??[],removes=parseRemoveEntries(resolvedRemoves),removeIndexes=parseRemoveIndexes(options.removeIndex),item=await resolveLinkedTestItem({id,options,pmRoot,settings,typeToFolder:typeRegistry.type_to_folder,adds,removes,removeIndexes}),runOptions=admitLinkedTestRequest(()=>resolveTestRunOptions(options,item.tests)),trustAcknowledgement=await maybeAcknowledgeLinkedTests(options,pmRoot,item.tests),runStartedAt=options.run===!0?nowIso():void 0,runResults=await executeSelectedLinkedTests({options,runOptions,pmRoot,untrustedLinkedTestsPolicyEnabled:settings.testing.allow_untrusted_linked_tests});assertTestMeasurementExecution(id,options,global2,pmRoot,runResults);const failureCategories=countFailureCategories(runResults),failOnSkippedTriggered=options.run===!0&&options.failOnSkipped===!0&&runResults.some(entry=>entry.status==="skipped"),warnings=buildTestWarnings(options,runResults);trustAcknowledgement&&warnings.push(`linked_test_trust_acknowledged:${trustAcknowledgement.acknowledged}`);const recording=await recordTestRunSummary({options,pmRoot,settings,itemId:item.itemId,runStartedAt,runResults,failOnSkippedTriggered,warnings}),runs=recording?.receipt.recorded?buildTrackedTestRunHistory([...item.testRuns,recording.entry]):item.testRuns,measurementProjection=buildTestMeasurementProjection(options,runs,recording?.entry);return projectTestResult({item,runResults,failureCategories,runSelection:runOptions.runSelection,failOnSkippedTriggered,warnings,recording,measurementProjection,restoreScope:resolveLinkedTestRestoreScope(global2,pmRoot)})}const _testOnlyTestCommand={buildLinkedTestExecutionContext,buildPmContextMismatchHint,commandInvokesPmCli,commandInvokesPmTrackerReadCommand,commandUsesTestNameFilter,copyIntoSandboxIfPresent,countLinkedTestItemFiles,ensureScope,evaluateLinkedTestAssertions,endLinkedTestProgress,extractPmInvocationArgsFromSegment,firstDirectTestRunnerSubcommand,formatLinkedTestExecutionError,hasLinkedTestAssertions,initializeLinkedTestSandboxes,linkedTestsRequireTrackerData,parseAddJsonEntries,parseLinkedTestTimeoutSeconds,parsePmContextMode,readJsonPathValue,resolveAuthor,resolveDirectRunnerSubcommand,resolveLinkedTestEffectiveContextMode,resolveLinkedTestFailureExitCode,resolveLinkedTestRequestedContextMode,resolveTestRunOptions,resolveTrackedRunId,runLinkedTestCommand,seedMissingLinkedTestSettingsHistory,seedLinkedTestSandbox,seedLinkedTestTrackerData,segmentInvokesRecursiveTestAll,splitJsonPathSegments,summarizeRunResultStatuses};export{_testOnlyTestCommand,classifyLinkedTestCommandSafety,classifyLinkedTestFailure,countFailureCategories,extractReferencedPmItemIdsFromCommand,resolveLinkedTestFailureExitCode,runLinkedTests,runTest,summarizeContextPreflight};
|
|
6
6
|
//# sourceMappingURL=execution.js.map
|