@unbrained/pm-cli 2026.10.26 → 2026.10.27
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 +16 -1
- package/dist/cli/register-list-query.js +5 -5
- package/dist/cli-bundle/bundle-manifest.json +1 -1
- package/dist/cli-bundle/chunks/{chunk-MWCG25PO.js → chunk-2XUDAQK5.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-Y42DYMZX.js → chunk-32QK43XG.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-T7T4WLUY.js → chunk-34OQP43K.js} +3 -3
- package/dist/cli-bundle/chunks/{chunk-ZLVFUZI4.js → chunk-5PTMQBKJ.js} +30 -30
- package/dist/cli-bundle/chunks/{chunk-NGKTROAO.js → chunk-EKV5GP6P.js} +4 -4
- package/dist/cli-bundle/chunks/{chunk-OCDRHIVQ.js → chunk-F26MRQZY.js} +3 -3
- package/dist/cli-bundle/chunks/{chunk-XAIR53QZ.js → chunk-H434DKYU.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-TGOG5BFM.js → chunk-HIX2OMEZ.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-7UDQAINZ.js → chunk-NNIYQHYT.js} +2 -2
- package/dist/cli-bundle/chunks/chunk-O3LPT34G.js +20 -0
- package/dist/cli-bundle/chunks/{chunk-Z35XAHUN.js → chunk-OQOEBWHU.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-UF6EXZSP.js → chunk-Q55KZDNF.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-VS2IIV3F.js → chunk-QQYLNHZK.js} +5 -5
- package/dist/cli-bundle/chunks/chunk-SPPHDVZE.js +2 -0
- package/dist/cli-bundle/chunks/chunk-UHKN6TJM.js +114 -0
- package/dist/cli-bundle/chunks/{chunk-ZT64RDMF.js → chunk-X5TMR7KC.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-YJBIPNDL.js → chunk-XERBUA6A.js} +2 -2
- package/dist/cli-bundle/chunks/indexed-receipt-blobs-63WYG2PY.js +4 -0
- package/dist/cli-bundle/chunks/{mutation-admission-VTNV6JV2.js → mutation-admission-L3A6FSZ7.js} +2 -2
- package/dist/cli-bundle/chunks/{register-list-query-LZDWUZS4.js → register-list-query-YSQ7UTFI.js} +9 -9
- package/dist/cli-bundle/chunks/{register-mutation-M2A7PH42.js → register-mutation-6HRXYASW.js} +2 -2
- package/dist/cli-bundle/chunks/{register-operations-E7P2JTXM.js → register-operations-4O3YOYXF.js} +2 -2
- package/dist/cli-bundle/chunks/{register-setup-DOV7KGSH.js → register-setup-3WAXNDMY.js} +2 -2
- package/dist/cli-bundle/chunks/{settlement-hook-SVD6CO7U.js → settlement-hook-CRW6PVQK.js} +2 -2
- package/dist/cli-bundle/chunks/{stage-LYLEOAQB.js → stage-I46WD4EN.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-WDWHUJQW.js → chunk-4TXZBGOR.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-EGN2HFRU.js +2 -0
- package/dist/cli-bundle/focused-chunks/{chunk-IF6C43IP.js → chunk-HWD63G54.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-NE27VCU7.js → chunk-J67PFM3O.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-PK5FJIVA.js → chunk-MVY6JDIH.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-UP765R2V.js → chunk-R2MTHS2H.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-SM2KGD4J.js +79 -0
- package/dist/cli-bundle/focused-chunks/{chunk-226XKW3W.js → chunk-SYL73MEI.js} +3 -3
- package/dist/cli-bundle/focused-chunks/{chunk-CSCZYSNZ.js → chunk-TAFMNTA5.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-I7H3XF25.js → chunk-TGUOEOKP.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-U4UBQBS7.js +2 -0
- package/dist/cli-bundle/focused-chunks/{chunk-D5YQ4KAE.js → chunk-WDKZB3V6.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-KERJNFXY.js → chunk-XK3XMZM4.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-ADSBGYLW.js → chunk-Y57A4ZDI.js} +55 -55
- package/dist/cli-bundle/focused-chunks/{chunk-BL77VCRB.js → chunk-YC6NCPLK.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-ZA6AEYIM.js +2 -0
- package/dist/cli-bundle/focused-chunks/{chunk-YTEC2EPM.js → chunk-ZH7GTEWL.js} +3 -3
- package/dist/cli-bundle/focused-chunks/indexed-receipt-blobs-GAFC52LQ.js +4 -0
- package/dist/cli-bundle/focused-chunks/{mutation-admission-L42E3U5U.js → mutation-admission-ENBFMV2X.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{settlement-hook-3CADO4XZ.js → settlement-hook-PUYJJDFL.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{stage-TUQHWU7N.js → stage-MAWZTM2H.js} +2 -2
- package/dist/cli-bundle/main.js +2 -2
- package/dist/cli-bundle/sdk-authoring.js +1 -1
- package/dist/cli-bundle/sdk-contracts.js +2 -2
- package/dist/cli-bundle/sdk-core.js +33 -33
- 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 +3 -3
- package/dist/core/history/event-index.d.ts +6 -0
- package/dist/core/history/event-index.js +7 -6
- package/dist/core/history/read.d.ts +3 -1
- package/dist/core/history/read.js +1 -1
- package/dist/mcp/tool-definitions.js +1 -1
- package/dist/sdk/cli-contracts/flag-contracts.js +1 -1
- package/dist/sdk/cli-contracts/flag-lexicon-contracts.js +1 -1
- package/dist/sdk/cli-contracts/grammar-contracts.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/generated/generated-error-code-catalog-part-1.js +1 -1
- package/dist/sdk/merge/indexed-receipt-blobs.d.ts +3 -1
- package/dist/sdk/merge/indexed-receipt-blobs.js +2 -2
- package/dist/sdk/merge/receipt-file-boundary.d.ts +2 -0
- package/dist/sdk/merge/receipt-file-boundary.js +1 -1
- package/dist/sdk/merge/receipts.js +2 -2
- package/dist/sdk/mutation-events.d.ts +6 -0
- package/dist/sdk/mutation-events.js +1 -1
- package/dist/sdk/public-surface.json +1 -1
- package/dist/sdk/runtime-extended-actions.js +1 -1
- package/dist/sdk/test/execution.js +2 -2
- package/dist/sdk/test/receipt-index-snapshot.d.ts +2 -0
- package/dist/sdk/test/receipt-index-snapshot.js +2 -0
- package/dist/sdk/test/workspace-snapshot.d.ts +1 -1
- package/dist/sdk/test/workspace-snapshot.js +1 -1
- package/docs/PERFORMANCE.md +21 -6
- package/docs/RELEASING.md +11 -0
- package/docs/SDK_CONTEXT_COORDINATION.md +35 -4
- package/docs/TESTING.md +14 -1
- package/docs/generated/FLAG_LEXICON_BUDGETS.md +2 -2
- package/docs/generated/REFUSAL_CLOSURE_CENSUS.md +4 -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-N6FTEILW.js +0 -20
- package/dist/cli-bundle/chunks/chunk-OUDR2HWR.js +0 -113
- package/dist/cli-bundle/chunks/chunk-RUKSEZE7.js +0 -2
- package/dist/cli-bundle/chunks/indexed-receipt-blobs-NWQIDTH7.js +0 -4
- package/dist/cli-bundle/focused-chunks/chunk-ABMNYWV6.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-NFN2P4TW.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-UHWTPOM4.js +0 -78
- package/dist/cli-bundle/focused-chunks/chunk-VVRXF4JO.js +0 -2
- package/dist/cli-bundle/focused-chunks/indexed-receipt-blobs-YFDNGHCM.js +0 -4
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
(function(){try{var e=typeof window<"u"?window:typeof global<"u"?global:typeof globalThis<"u"?globalThis:typeof self<"u"?self:{},n=new e.Error().stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="
|
|
1
|
+
(function(){try{var e=typeof window<"u"?window:typeof global<"u"?global:typeof globalThis<"u"?globalThis:typeof self<"u"?self:{},n=new e.Error().stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="d755ad5c-14c7-567f-8467-63b3fce6796d")}catch{}})();import{EXIT_CODE}from"../core/shared/constants.js";import{PmCliError}from"../core/shared/errors.js";import{transferMutationStdinTokenPolicy}from"./runtime-primitives.js";import{createUnknownSubcommandError}from"./agent/subcommand-recovery.js";import{resolvePmRoot}from"../core/store/paths.js";import{runEval}from"./eval.js";import{runMergeDriver}from"./merge/driver.js";import{runMergeInstall}from"./merge/install.js";import{runMergeReconcile}from"./merge/reconcile.js";import{runMergeReceiptEvidenceReport}from"./merge/receipts.js";import{listMutationEvents}from"./mutation-events.js";import{parseRuntimeInteger,readRuntimeString,readRuntimeStringArray}from"./runtime-input.js";import{runEvent,runMeet,runRemind}from"./scheduling-shortcuts.js";import{createWorkspaceSnapshot,deleteWorkspaceSnapshot,inspectWorkspaceSnapshot,listWorkspaceSnapshots,planWorkspaceSnapshotRestore,restoreWorkspaceSnapshotWithRecovery}from"./workspace-snapshot.js";import{readWorkspacePosition}from"./governance/workspace-position.js";function mergedInput(context){const input={...context.args,...context.options};return transferMutationStdinTokenPolicy(context.options,input)}function requiredString(input,key){const value=readRuntimeString(input,key);if(value!==void 0)return value;throw new PmCliError(`Missing required parameter: ${key}`,EXIT_CODE.USAGE)}function stringList(value){return typeof value=="string"?[value]:readRuntimeStringArray(value)}function runRuntimeEvalAction(context){return runEval(mergedInput(context),context.global)}function runRuntimeEventsAction(context){const input=mergedInput(context),rawCursorMode=input.cursorMode;if(rawCursorMode!==void 0&&rawCursorMode!=="batch"&&rawCursorMode!=="row")throw new PmCliError("Mutation event cursor mode must be batch or row.",EXIT_CODE.USAGE,{code:"invalid_event_cursor_mode"});const cursorMode=rawCursorMode;return listMutationEvents({cwd:readRuntimeString(input,"cwd"),pmRoot:readRuntimeString(input,"path"),since:readRuntimeString(input,"since"),type:stringList(input.type),author:stringList(input.author),item:stringList(input.item),limit:input.limit===void 0?void 0:parseRuntimeInteger(input.limit,"limit"),full:input.full===!0,...input.verify===!0?{verify:!0}:{},...cursorMode===void 0?{}:{cursorMode},...input.provenance===void 0?{}:{provenance:input.provenance===!0},...input.provenanceSummary===void 0?{}:{provenanceSummary:input.provenanceSummary===!0},...input.harness===void 0?{}:{harness:stringList(input.harness)},...input.agentInstance===void 0?{}:{agentInstance:stringList(input.agentInstance)},...input.provenanceFilter===void 0?{}:{provenanceFilter:stringList(input.provenanceFilter)}})}function runRuntimeMergeAction(context){const input=mergedInput(context),subcommand=requiredString(input,"subcommand");if(subcommand==="install")return runMergeInstall({dryRun:input.dryRun===!0,preCommitHook:input.preCommitHook===!0},context.global);if(subcommand==="reconcile")return runMergeReconcile({dryRun:input.dryRun===!0,author:readRuntimeString(input,"author"),message:readRuntimeString(input,"message"),force:input.force===!0,stage:input.stage===!0},context.global);if(subcommand==="report")return runMergeReceiptEvidenceReport({includeReconciled:input.includeReconciled===!0,cwd:readRuntimeString(input,"cwd")});if(subcommand==="driver")return runMergeDriver({artifact:requiredString(input,"artifact"),basePath:requiredString(input,"basePath"),oursPath:requiredString(input,"oursPath"),theirsPath:requiredString(input,"theirsPath"),outputPath:readRuntimeString(input,"outputPath")??readRuntimeString(input,"output"),itemPath:readRuntimeString(input,"itemPath"),prefer:readRuntimeString(input,"prefer")},context.global);throw createUnknownSubcommandError({command_path:"merge",token:subcommand,allowed:["install","reconcile","report","driver"],display_name:"merge"})}function runRuntimeWorkspaceAction(context){const input=mergedInput(context),subcommand=requiredString(input,"subcommand");if(subcommand==="position")return readWorkspacePosition(context.global);if(subcommand!=="snapshot")throw createUnknownSubcommandError({command_path:"workspace",token:subcommand,allowed:["position","snapshot"],display_name:"workspace"});const snapshotAction=readRuntimeString(input,"snapshotAction")??requiredString(input,"snapshot_action"),pmRoot=resolvePmRoot(process.cwd(),context.global.path);if(snapshotAction==="create")return createWorkspaceSnapshot(pmRoot,{name:readRuntimeString(input,"name")});if(snapshotAction==="list")return listWorkspaceSnapshots(pmRoot);const target=requiredString(input,"target");if(snapshotAction==="inspect")return inspectWorkspaceSnapshot(pmRoot,target);if(snapshotAction==="delete")return deleteWorkspaceSnapshot(pmRoot,target);if(snapshotAction==="restore")return input.dryRun===!0?planWorkspaceSnapshotRestore(pmRoot,target):restoreWorkspaceSnapshotWithRecovery(pmRoot,target,{force:input.force===!0,author:readRuntimeString(input,"author"),message:readRuntimeString(input,"message"),lockTtlSeconds:parseRuntimeInteger(input.lockTtlSeconds,"lockTtlSeconds"),lockWaitMs:parseRuntimeInteger(input.lockWaitMs,"lockWaitMs")});throw createUnknownSubcommandError({command_path:"workspace snapshot",token:snapshotAction,allowed:["create","list","inspect","restore","delete"],display_name:"workspace snapshot",token_kind:"action"})}function runRuntimeSchedulingAction(context){const input=mergedInput(context),title=requiredString(input,"title");return context.action==="meet"?runMeet(title,input,context.global):context.action==="event"?runEvent(title,input,context.global):runRemind(title,input,context.global)}export{runRuntimeEvalAction,runRuntimeEventsAction,runRuntimeMergeAction,runRuntimeSchedulingAction,runRuntimeWorkspaceAction};
|
|
2
2
|
//# sourceMappingURL=runtime-extended-actions.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]="a79b0beb-f634-5d7b-9b9a-f32cab5fa523")}catch{}})();import{readJsonPathValue,splitJsonPathSegments}from"./json-path.js";import{detectEmptyLinkedTestRun,findLinkedTestExecutionReceipt}from"./execution-receipts.js";import{seedLinkedTestWorkspaceSnapshot}from"./workspace-snapshot.js";import{updateLinkedTest,parseLinkedTestUpdateOptions,assertLinkedTestEditUsage}from"./update.js";import{admitLinkedTestRequest,assertLinkedTestCommand}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,firstPmSubcommand,extractPmInvocationArgsFromSegment,isPmCliPackageToken,isPmCliScriptToken,isPmExecutableToken,parseLauncherSubcommand,parseNpmExecCommand,parseNpxCommand,parsePnpmDlxCommand,resolvePmSubcommandContext,splitNormalizedCommandSegments,stripLeadingEnvAssignments}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 parsedLauncherInvokesRecursiveTestAll(parsed){return!parsed||!isPmExecutableToken(parsed.command)&&!isPmCliPackageToken(parsed.command)?!1:firstPmSubcommand(parsed.args)==="test-all"}const SHELL_COMMAND_FLAG_PATTERNS=new Map([...["sh","bash","dash","zsh"].map(executable=>[executable,/^(?:-c|-[a-z]*c[a-z]*)$/u]),...["cmd","cmd.exe"].map(executable=>[executable,/^\/[ck]$/u]),...["powershell","powershell.exe","pwsh","pwsh.exe"].map(executable=>[executable,/^-(?:c|command)$/u])]),PACKAGE_EXEC_EXECUTABLES=new Set(["npx","bunx"]);function segmentInvokesRecursiveTestAll(segment){const rawTokens=segment.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(arg=>shellCommandFlagPattern.test(arg));return commandFlagIndex>=0?segmentInvokesRecursiveTestAll(args.slice(commandFlagIndex+1).join(" ")):!1}return isPmExecutableToken(executable)||isPmCliScriptToken(executable)?firstPmSubcommand(args)==="test-all":executable==="node"&&args.length>0&&isPmCliScriptToken(args[0])?firstPmSubcommand(args.slice(1))==="test-all":PACKAGE_EXEC_EXECUTABLES.has(executable)?parsedLauncherInvokesRecursiveTestAll(parseNpxCommand(args)):executable==="pnpm"?parsedLauncherInvokesRecursiveTestAll(parsePnpmDlxCommand(args)):executable==="npm"?parsedLauncherInvokesRecursiveTestAll(parseNpmExecCommand(args)):!1}function invokesRecursiveTestAllCommand(command){const normalized=normalizeCommandForValidation(command);return splitNormalizedCommandSegments(normalized).some(segment=>segmentInvokesRecursiveTestAll(segment))}function assertNoRecursiveTestAllCommand(command){if(invokesRecursiveTestAllCommand(command))throw new PmCliError('Linked test commands must not invoke "pm test-all"; this creates recursive orchestration.',EXIT_CODE.USAGE)}function normalizeCommandForValidation(command){return command.trim().replaceAll("\\","/").replaceAll('"',"").replaceAll("'","").replaceAll(/\s+/g," ").toLowerCase()}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]="4750005e-fc78-5b48-8bba-18790c376a9f")}catch{}})();import{readJsonPathValue,splitJsonPathSegments}from"./json-path.js";import{detectEmptyLinkedTestRun,findLinkedTestExecutionReceipt}from"./execution-receipts.js";import{seedLinkedTestWorkspaceSnapshot}from"./workspace-snapshot.js";import{updateLinkedTest,parseLinkedTestUpdateOptions,assertLinkedTestEditUsage}from"./update.js";import{admitLinkedTestRequest,assertLinkedTestCommand}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,firstPmSubcommand,extractPmInvocationArgsFromSegment,isPmCliPackageToken,isPmCliScriptToken,isPmExecutableToken,parseLauncherSubcommand,parseNpmExecCommand,parseNpxCommand,parsePnpmDlxCommand,resolvePmSubcommandContext,splitNormalizedCommandSegments,stripLeadingEnvAssignments}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 parsedLauncherInvokesRecursiveTestAll(parsed){return!parsed||!isPmExecutableToken(parsed.command)&&!isPmCliPackageToken(parsed.command)?!1:firstPmSubcommand(parsed.args)==="test-all"}const SHELL_COMMAND_FLAG_PATTERNS=new Map([...["sh","bash","dash","zsh"].map(executable=>[executable,/^(?:-c|-[a-z]*c[a-z]*)$/u]),...["cmd","cmd.exe"].map(executable=>[executable,/^\/[ck]$/u]),...["powershell","powershell.exe","pwsh","pwsh.exe"].map(executable=>[executable,/^-(?:c|command)$/u])]),PACKAGE_EXEC_EXECUTABLES=new Set(["npx","bunx"]);function segmentInvokesRecursiveTestAll(segment){const rawTokens=segment.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(arg=>shellCommandFlagPattern.test(arg));return commandFlagIndex>=0?segmentInvokesRecursiveTestAll(args.slice(commandFlagIndex+1).join(" ")):!1}return isPmExecutableToken(executable)||isPmCliScriptToken(executable)?firstPmSubcommand(args)==="test-all":executable==="node"&&args.length>0&&isPmCliScriptToken(args[0])?firstPmSubcommand(args.slice(1))==="test-all":PACKAGE_EXEC_EXECUTABLES.has(executable)?parsedLauncherInvokesRecursiveTestAll(parseNpxCommand(args)):executable==="pnpm"?parsedLauncherInvokesRecursiveTestAll(parsePnpmDlxCommand(args)):executable==="npm"?parsedLauncherInvokesRecursiveTestAll(parseNpmExecCommand(args)):!1}function invokesRecursiveTestAllCommand(command){const normalized=normalizeCommandForValidation(command);return splitNormalizedCommandSegments(normalized).some(segment=>segmentInvokesRecursiveTestAll(segment))}function assertNoRecursiveTestAllCommand(command){if(invokesRecursiveTestAllCommand(command))throw new PmCliError('Linked test commands must not invoke "pm test-all"; this creates recursive orchestration.',EXIT_CODE.USAGE)}function normalizeCommandForValidation(command){return command.trim().replaceAll("\\","/").replaceAll('"',"").replaceAll("'","").replaceAll(/\s+/g," ").toLowerCase()}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
|
`:"",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);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 linkedTestsHaveSameIdentity(left,right){return left.command===right.command&&left.path===right.path&&left.scope===right.scope&&left.pm_context_mode===right.pm_context_mode&&left.workspace_context_mode===right.workspace_context_mode}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,runSelection,runResults){const warnings=[];if(runSelection&&runSelection.selector!==null&&warnings.push(`linked_test_selection:${runSelection.selector}=${runSelection.requested};selected=${runSelection.selected_count};skipped=${runSelection.skipped_count};indexes=${runSelection.selected_indexes.join(",")}`),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}=input;return{ok:runResults.every(entry=>entry.status!=="failed"&&entry.status!=="refused")&&failOnSkippedTriggered!==!0,id:item.itemId,tests:item.tests,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,runOptions.runSelection,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})}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 linkedTestsHaveSameIdentity(left,right){return left.command===right.command&&left.path===right.path&&left.scope===right.scope&&left.pm_context_mode===right.pm_context_mode&&left.workspace_context_mode===right.workspace_context_mode}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,runSelection,runResults){const warnings=[];if(runSelection&&runSelection.selector!==null&&warnings.push(`linked_test_selection:${runSelection.selector}=${runSelection.requested};selected=${runSelection.selected_count};skipped=${runSelection.skipped_count};indexes=${runSelection.selected_indexes.join(",")}`),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}=input;return{ok:runResults.every(entry=>entry.status!=="failed"&&entry.status!=="refused")&&failOnSkippedTriggered!==!0,id:item.itemId,tests:item.tests,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,runOptions.runSelection,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})}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
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
(function(){try{var e=typeof window<"u"?window:typeof global<"u"?global:typeof globalThis<"u"?globalThis:typeof self<"u"?self:{},n=new e.Error().stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="e67d1a06-77f7-5e86-8f15-bde8b6db0d5a")}catch{}})();import{execFile}from"node:child_process";import{readdir,realpath}from"node:fs/promises";import path from"node:path";import{promisify}from"node:util";import{isFileMissingError}from"../../core/fs/fs-utils.js";import{MERGE_RECEIPT_FILE_MAX_BYTES}from"../merge/receipt-file-boundary.js";import{gitWorkspaceEnvironment}from"../merge/worktree-config.js";const execFileAsync=promisify(execFile),RECEIPT_NAMESPACE=".agents/pm/merge-receipts";async function seedSnapshotReceiptIndex(sourceRoot,snapshotRoot,sourcePmRoot){const directory=path.join(sourcePmRoot,"merge-receipts"),names=(await readdir(directory).catch(error=>{if(isFileMissingError(error))return[];throw error})).filter(name=>name.endsWith(".json")),{readIndexedReceiptBlobs}=await import("../merge/indexed-receipt-blobs.js"),blobs=names.length===0?new Map:await readIndexedReceiptBlobs(sourceRoot,directory,names,MERGE_RECEIPT_FILE_MAX_BYTES,await realpath(directory),{failOnReadError:!0}),options={cwd:snapshotRoot,env:gitWorkspaceEnvironment(),timeout:1e4,maxBuffer:1024*1024,windowsHide:!0};await execFileAsync("git",["rm","-q","-r","--cached","--ignore-unmatch","--",RECEIPT_NAMESPACE],options);for(const[name,raw]of blobs){const pending=execFileAsync("git",["hash-object","-w","--stdin"],options),input=pending.child.stdin;input!==null&&(input.on("error",()=>{pending.child.kill()}),input.end(raw));const objectId=(await pending).stdout.trim();if(!/^(?:[a-f\d]{40}|[a-f\d]{64})$/u.test(objectId))throw new Error("Invalid snapshot receipt object identity.");await execFileAsync("git",["update-index","--add","--cacheinfo","100644",objectId,`${RECEIPT_NAMESPACE}/${name}`],options)}}export{seedSnapshotReceiptIndex};
|
|
2
|
+
//# sourceMappingURL=receipt-index-snapshot.js.map
|
|
@@ -8,4 +8,4 @@
|
|
|
8
8
|
* Other external or excluded targets and overlapping physical source/destination
|
|
9
9
|
* trees are refused before the command can run.
|
|
10
10
|
*/
|
|
11
|
-
export declare function seedLinkedTestWorkspaceSnapshot(sourceRoot:string,snapshotRoot:string):Promise<void>;
|
|
11
|
+
export declare function seedLinkedTestWorkspaceSnapshot(sourceRoot:string,snapshotRoot:string,sourcePmRoot?:string,includeTrackerData?:boolean):Promise<void>;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
(function(){try{var e=typeof window<"u"?window:typeof global<"u"?global:typeof globalThis<"u"?globalThis:typeof self<"u"?self:{},n=new e.Error().stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="
|
|
1
|
+
(function(){try{var e=typeof window<"u"?window:typeof global<"u"?global:typeof globalThis<"u"?globalThis:typeof self<"u"?self:{},n=new e.Error().stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="6638f21c-6cf5-5dec-a0d9-91ae323afce3")}catch{}})();import{cp,lstat,mkdir,readlink,realpath,stat,symlink}from"node:fs/promises";import{execFile}from"node:child_process";import path from"node:path";import{promisify}from"node:util";import{seedSnapshotReceiptIndex}from"./receipt-index-snapshot.js";import{isFileMissingError}from"../../core/fs/fs-utils.js";const EXCLUDED_SEGMENTS=new Set([".agents",".git",".nyc_output",".turbo","coverage"]),execFileAsync=promisify(execFile);async function seedSnapshotGitIdentity(sourceRoot,snapshotRoot){const marker=await statSnapshotTarget(path.join(sourceRoot,".git"));if(!marker||marker.isDirectory()&&!await statSnapshotTarget(path.join(sourceRoot,".git/HEAD")))return!1;const env=Object.fromEntries(Object.entries(process.env).filter(([key])=>!key.toUpperCase().startsWith("GIT_")));await execFileAsync("git",["clone","--no-local","--no-checkout","--template=","--origin=origin","--",sourceRoot,snapshotRoot],{env,timeout:12e4,maxBuffer:1024*1024,windowsHide:!0}),await execFileAsync("git",["-C",snapshotRoot,"remote","remove","origin"],{env,timeout:1e4,windowsHide:!0});try{await execFileAsync("git",["-C",snapshotRoot,"rev-parse","--verify","--quiet","HEAD"],{env,timeout:1e4,windowsHide:!0})}catch(error){if(error.code===1)return!0;throw error}return await execFileAsync("git",["-C",snapshotRoot,"read-tree","HEAD"],{env,timeout:1e4,windowsHide:!0}),!0}async function seedSnapshotGitAuthority(sourceRoot,snapshotRoot,sourcePmRoot,includeTrackerData){await seedSnapshotGitIdentity(sourceRoot,snapshotRoot)&&sourcePmRoot&&includeTrackerData!==!1&&await seedSnapshotReceiptIndex(sourceRoot,snapshotRoot,sourcePmRoot)}function hasExcludedSegment(relative){return relative.split(path.sep).some(segment=>EXCLUDED_SEGMENTS.has(segment.toLowerCase()))}function isOutsideWorkspace(relative){return relative===".."||relative.startsWith(`..${path.sep}`)||path.isAbsolute(relative)}async function statSnapshotTarget(source){try{return await stat(source)}catch(error){if(!isFileMissingError(error))throw error;return}}async function resolveLinkTarget(target){const missing=[];let ancestor=target;for(;path.dirname(ancestor)!==ancestor;)try{return path.join(await realpath(ancestor),...missing)}catch(error){if(!isFileMissingError(error))throw error;missing.unshift(path.basename(ancestor)),ancestor=path.dirname(ancestor)}return path.join(await realpath(ancestor),...missing)}async function resolveSnapshotTarget(target,sourceRoot,dependencyRoots){const relative=path.relative(sourceRoot,await resolveLinkTarget(target));if(!isOutsideWorkspace(relative))return relative;const suffix=[];let ancestor=target;for(;path.dirname(ancestor)!==ancestor;){const physicalAncestor=await resolveLinkTarget(ancestor),dependency=dependencyRoots.find(([,root])=>root===physicalAncestor);if(dependency)return path.join(dependency[0],...suffix);suffix.unshift(path.basename(ancestor)),ancestor=path.dirname(ancestor)}return relative}async function seedLinkedTestWorkspaceSnapshot(sourceRoot,snapshotRoot,sourcePmRoot,includeTrackerData){const dependencyRoots=[],sourceLinks=[],resolvedSourceRoot=await realpath(sourceRoot),resolvedSnapshotRoot=await resolveLinkTarget(path.resolve(snapshotRoot));if(![path.relative(resolvedSourceRoot,resolvedSnapshotRoot),path.relative(resolvedSnapshotRoot,resolvedSourceRoot)].every(isOutsideWorkspace))throw new Error("Snapshot destination must be disjoint from the source workspace; choose a temporary root outside the checkout.");await mkdir(path.dirname(snapshotRoot),{recursive:!0}),await seedSnapshotGitAuthority(resolvedSourceRoot,snapshotRoot,sourcePmRoot,includeTrackerData),await cp(resolvedSourceRoot,snapshotRoot,{recursive:!0,force:!0,async filter(source){const relative=path.relative(resolvedSourceRoot,source);if(!relative)return!0;if(hasExcludedSegment(relative))return!1;if(path.basename(source).toLowerCase()==="node_modules"){if((await statSnapshotTarget(source))?.isDirectory()){const target=await resolveLinkTarget(source),targetRelative=path.relative(resolvedSourceRoot,target);if(!isOutsideWorkspace(path.relative(target,resolvedSourceRoot))||!isOutsideWorkspace(targetRelative)&&(hasExcludedSegment(targetRelative)||!targetRelative.split(path.sep).some(segment=>segment.toLowerCase()==="node_modules")))throw new Error(`Dependency symlink must target an included dependency directory or external installation: ${relative}`);dependencyRoots.push([relative,target])}return!1}return(await lstat(source)).isSymbolicLink()?(sourceLinks.push([relative,path.resolve(path.dirname(source),await readlink(source)),(await statSnapshotTarget(source))?.isDirectory()?"junction":"file"]),!1):!0}});for(const[relative]of dependencyRoots)await symlink(path.resolve(resolvedSourceRoot,relative),path.join(snapshotRoot,relative),"junction");for(const[relative,target,type]of sourceLinks){const targetRelative=await resolveSnapshotTarget(target,resolvedSourceRoot,dependencyRoots);if(isOutsideWorkspace(targetRelative)||hasExcludedSegment(targetRelative))throw new Error(`Source symlink must target included workspace source: ${relative}`);await symlink(path.resolve(snapshotRoot,targetRelative),path.join(snapshotRoot,relative),type)}}export{seedLinkedTestWorkspaceSnapshot};
|
|
2
2
|
//# sourceMappingURL=workspace-snapshot.js.map
|
package/docs/PERFORMANCE.md
CHANGED
|
@@ -72,7 +72,7 @@ A supported create, update, move, or delete acquires the cross-process
|
|
|
72
72
|
derived-index writer lock before the authoritative item commit and atomically
|
|
73
73
|
publishes one collapsed delta containing every compatible tier projection,
|
|
74
74
|
directory signatures, and the next cursor before releasing the lock. Mutation
|
|
75
|
-
cost therefore follows changed items rather than total workspace size. A torn,
|
|
75
|
+
cost for this metadata projection therefore follows changed items rather than total workspace size. A torn,
|
|
76
76
|
corrupt, or base-mismatched delta is rejected and rebuilt from source.
|
|
77
77
|
|
|
78
78
|
Large indexes also carry `runtime/metadata-query-index.sqlite`, a rebuildable
|
|
@@ -91,11 +91,26 @@ create/copy duplicate-governance probe. Minimal/default governance keeps the
|
|
|
91
91
|
probe off and performs no query on the create path; advisory/strict modes reuse
|
|
92
92
|
one dependency-light SDK scorer with the package-owned dedupe audit. Mutation
|
|
93
93
|
history events use a separate rebuildable ordered SQLite projection so
|
|
94
|
-
`pm events --since <cursor>`
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
94
|
+
`pm events --since <cursor>` retrieves only matching rows from the index.
|
|
95
|
+
As required by [pm-gh1593](../.agents/pm/issues/pm-gh1593.toon), cache validation
|
|
96
|
+
hashes the complete authoritative bytes of selected streams on every read;
|
|
97
|
+
its cost therefore grows with selected history size. An `--item` filter bounds
|
|
98
|
+
that source population. Opt-in `--verify` additionally replays each selected
|
|
99
|
+
chain, before filtering or pagination. These checks detect same-length rewrites
|
|
100
|
+
without trusting timestamps or a last-record hash. The version-7 schema retains
|
|
101
|
+
substantive-versus-maintenance indexing, so context recency still retrieves only
|
|
102
|
+
the latest substantive row per requested stream after content validation.
|
|
103
|
+
|
|
104
|
+
An indexed history append reads and SHA-256-hashes the complete affected stream
|
|
105
|
+
before the append to certify its existing index base, then reads and hashes it
|
|
106
|
+
again after the authoritative append to publish the new content receipt. Both
|
|
107
|
+
checks run under the history-index writer lock. This history validation cost is
|
|
108
|
+
O(stream bytes), in addition to the changed-item metadata projection work.
|
|
109
|
+
Unfiltered follow queries repeat selected-stream validation on every iteration;
|
|
110
|
+
use an item filter and an explicit interval appropriate to the history size.
|
|
111
|
+
Further append and follow measurements are tracked by
|
|
112
|
+
[pm-history-receipt-cost](../.agents/pm/tasks/pm-history-receipt-cost.toon).
|
|
113
|
+
See [SDK Context Coordination](SDK_CONTEXT_COORDINATION.md).
|
|
99
114
|
|
|
100
115
|
SDK hosts that commit authoritative item documents outside the stock mutation
|
|
101
116
|
commands use `acquireItemMetadataDerivedIndexLock` and
|
package/docs/RELEASING.md
CHANGED
|
@@ -572,6 +572,17 @@ never move a published tag or mint a second automatic version for the same UTC d
|
|
|
572
572
|
- GitHub Release creation
|
|
573
573
|
- GitHub Release metadata verification through the same local verification script
|
|
574
574
|
|
|
575
|
+
The Auto Release parent has a 180-minute orchestration limit: 60 minutes for
|
|
576
|
+
preparation and gates, 30 for aggregate runner queue delays, the unchanged
|
|
577
|
+
60-minute publication, 15-minute acceptance and 5-minute advertisement job
|
|
578
|
+
limits, and 10 minutes for orchestration overhead. Runner queue time consumes
|
|
579
|
+
the waiting parent's wall time even when no child job is executing. The
|
|
580
|
+
reserve accommodates delays without extending a child job or changing a
|
|
581
|
+
quality gate. Extreme queue delays can still exhaust the parent; recover the
|
|
582
|
+
existing immutable tag after checking its publication evidence rather than
|
|
583
|
+
creating another daily version. Tracked by
|
|
584
|
+
[pm-29bwud](../.agents/pm/issues/pm-29bwud.toon).
|
|
585
|
+
|
|
575
586
|
Monitor:
|
|
576
587
|
|
|
577
588
|
```bash
|
|
@@ -22,6 +22,8 @@ bounded at scale without shelling out from an SDK host.
|
|
|
22
22
|
|
|
23
23
|
## Durable mutation events
|
|
24
24
|
|
|
25
|
+
Integrity contracts: [pm-gh1593](https://github.com/unbraind/pm-cli/blob/main/.agents/pm/issues/pm-gh1593.toon).
|
|
26
|
+
|
|
25
27
|
Read committed history facts as newline-delimited JSON:
|
|
26
28
|
|
|
27
29
|
```bash
|
|
@@ -33,13 +35,42 @@ pm events --cursor-mode row --since <cursor>
|
|
|
33
35
|
|
|
34
36
|
By default, event rows carry `item_id`, `version`, `ts`, `author`, `type`, and
|
|
35
37
|
`patch_count`, followed by one `pm.stream.trailer` record with `count`,
|
|
36
|
-
`has_more`, `next_cursor`, and `
|
|
38
|
+
`has_more`, `next_cursor`, `source`, and `integrity`. Persist the trailer cursor only after
|
|
37
39
|
the batch is durable. A crash before cursor persistence can replay the last
|
|
38
40
|
batch repeatedly, including across multiple crashes. Consumers must process
|
|
39
41
|
idempotently or deduplicate replays. `--cursor-mode row` preserves the previous
|
|
40
42
|
shape with one `cursor` per event and no trailer for consumers that checkpoint
|
|
41
43
|
every row. Already issued version-1 cursors remain accepted by `--since`.
|
|
42
44
|
|
|
45
|
+
Every SDK page declares `integrity: "unverified"` by default. Full-file content
|
|
46
|
+
matching prevents a stale cache from hiding same-length edits to any record,
|
|
47
|
+
including an earlier record when the final record is unchanged. This freshness
|
|
48
|
+
check alone does not verify the chain.
|
|
49
|
+
The public `readHistorySnapshot` result retains `raw_bytes` alongside decoded
|
|
50
|
+
`raw` and `entries`. Content receipts use those original bytes, including when
|
|
51
|
+
UTF-8 decoding replaces an invalid byte; parsing behavior remains unchanged.
|
|
52
|
+
|
|
53
|
+
Use `listMutationEvents({ item: "pm-abcd", verify: true })` or
|
|
54
|
+
`pm events --item pm-abcd --verify --json` before acting on verified events.
|
|
55
|
+
The SDK reads authoritative bytes and verifies each complete selected stream
|
|
56
|
+
before applying operation, author, timestamp, cursor, or row-limit filters.
|
|
57
|
+
Selected-record failures are detected even outside the returned page. JSON
|
|
58
|
+
parsing failures from `readHistorySnapshot` report `history_json_invalid`;
|
|
59
|
+
chain-verification failures report `event_history_integrity_failed` with
|
|
60
|
+
history-inspection recovery. Follow the returned typed recovery guidance. Select
|
|
61
|
+
`item` to bound verification cost and exclude unrelated streams. With no item
|
|
62
|
+
filter, all available item and workspace streams are checked. Successful pages
|
|
63
|
+
return `integrity: "verified"`, `verified_streams`, and
|
|
64
|
+
`source: "authoritative_history"`; this proves the recorded chain, not agreement
|
|
65
|
+
with the current materialized item or an external author signature. Empty
|
|
66
|
+
selections report zero verified streams. Subscriptions verify each yielded
|
|
67
|
+
batch with the same options. MCP `pm_events` accepts `verify: true` too.
|
|
68
|
+
Both strict reads and the authoritative fallback preserve typed history refusals
|
|
69
|
+
and report other host read failures as `event_index_unavailable`. Cache receipts
|
|
70
|
+
and ordered rows are published together, including across concurrent reads.
|
|
71
|
+
Batch-mode NDJSON trailers retain the integrity receipt; compatibility row mode
|
|
72
|
+
has no trailer, so use JSON or batch mode when persisting page receipts.
|
|
73
|
+
|
|
43
74
|
Version-1 cursor ordering uses recorded timestamps, stream ids, and offsets.
|
|
44
75
|
A fact committed later with an older timestamp falls before an advanced cursor.
|
|
45
76
|
Clock-independent ordering and late-commit recovery remain tracked by
|
|
@@ -53,15 +84,15 @@ streams with the same ordering, filters, pagination, and cursor contract; pages
|
|
|
53
84
|
identify that path with `source: "authoritative_history"`.
|
|
54
85
|
Writers without SQLite use the same pending and committed invalidation protocol,
|
|
55
86
|
so a later Node writer cannot certify a cache that omitted their facts.
|
|
56
|
-
Incremental writes also verify the previous
|
|
87
|
+
Incremental writes also verify the previous full-content receipt before advancing it,
|
|
57
88
|
so merged history cannot be hidden by a later append. Upgraded runtimes rebuild
|
|
58
89
|
legacy projections that could contain omitted facts with apparently current
|
|
59
90
|
receipts; authoritative history does not require a format migration.
|
|
60
91
|
|
|
61
92
|
Index readers take a synchronous SQLite read transaction containing both event
|
|
62
|
-
rows and their stream-
|
|
93
|
+
rows and their full-stream SHA-256 content receipts, then close the database before checking
|
|
63
94
|
authoritative files. They acquire neither history-index writer lock. File
|
|
64
|
-
identity and write timestamps, authoritative
|
|
95
|
+
identity and write timestamps, complete authoritative content digests, and pending or
|
|
65
96
|
committed invalidations must still match before a snapshot is returned. A
|
|
66
97
|
concurrent change refuses the optional snapshot; it cannot certify older rows
|
|
67
98
|
using a newer size receipt.
|
package/docs/TESTING.md
CHANGED
|
@@ -678,8 +678,21 @@ pm test <item-id> --run --workspace-context snapshot --override-linked-workspace
|
|
|
678
678
|
`.git`, `.agents`, coverage output, and common cache directories are excluded
|
|
679
679
|
at every directory depth, with reserved names matched case-insensitively for
|
|
680
680
|
portable admission. Git sources receive an independent commit/tag object store
|
|
681
|
-
with fresh configuration, no source hooks,
|
|
681
|
+
with an index initially based on HEAD, fresh configuration, no source hooks,
|
|
682
|
+
and no origin remote. Repository
|
|
682
683
|
history remains available; this is execution isolation, not history redaction.
|
|
684
|
+
Receipt verification is tracked by [pm-ycyymg](../.agents/pm/issues/pm-ycyymg.toon).
|
|
685
|
+
Source-mode tracker copies authenticate matching durable receipts against the
|
|
686
|
+
explicitly inherited read-only source index. Snapshot-mode copies use their
|
|
687
|
+
own index and the exact bound tracker path. Only bounded stage-zero receipt
|
|
688
|
+
blobs from the selected source tracker index are copied into that independent
|
|
689
|
+
object store and rebased into the snapshot namespace, including staged-only
|
|
690
|
+
receipts and custom tracker roots. Inherited receipts from another tracker
|
|
691
|
+
namespace are removed. Untracked, missing, conflicted, malformed, or
|
|
692
|
+
provenance-divergent sidecars remain untrusted; no source receipt, index,
|
|
693
|
+
or history is written by verification. A failed source index/object read aborts
|
|
694
|
+
snapshot setup before its receipt namespace is changed. A workspace without
|
|
695
|
+
Git authority or without indexed receipts still supplies no durable proof.
|
|
683
696
|
See [Faithful package acceptance](PACKAGE_GATE_ACCEPTANCE.md) for limits.
|
|
684
697
|
Each existing root or nested `node_modules`
|
|
685
698
|
directory is linked at its original relative path, including installations
|