@unbrained/pm-cli 2026.10.23 → 2026.10.24
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 +21 -0
- package/dist/cli/error-guidance.js +2 -2
- package/dist/cli-bundle/bundle-manifest.json +1 -1
- package/dist/cli-bundle/chunks/{chunk-MTRYEVZR.js → chunk-3VI4XDP2.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-AAX7CNL7.js → chunk-4PNAUT2E.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-2UX2TAN2.js → chunk-CDR5ES7Q.js} +54 -54
- package/dist/cli-bundle/chunks/chunk-CNEUJHA4.js +2 -0
- package/dist/cli-bundle/chunks/{chunk-AXGFNNI3.js → chunk-CSE47ZAN.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-ZEI3DC5E.js → chunk-FASDGYQC.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-5DKCD4KV.js → chunk-HZNFNETE.js} +35 -34
- package/dist/cli-bundle/chunks/{chunk-KY34HTT2.js → chunk-KGZGXC7T.js} +29 -29
- package/dist/cli-bundle/chunks/{chunk-REXCO5AG.js → chunk-MA7SNKYK.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-ZNDYAZZ6.js → chunk-TZYQ4JDZ.js} +3 -3
- package/dist/cli-bundle/chunks/{chunk-F76ZUP73.js → chunk-URZB3USE.js} +2 -2
- package/dist/cli-bundle/chunks/chunk-UY5UHI4I.js +2 -0
- package/dist/cli-bundle/chunks/{chunk-MMVX5L32.js → chunk-VPMQEVU6.js} +14 -14
- package/dist/cli-bundle/chunks/{chunk-ZMJF5HQW.js → chunk-VSSHB6XI.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-5V4HU6GK.js → chunk-YEI4N4A6.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-HDKY2YX3.js → chunk-Z62RXNHW.js} +2 -2
- package/dist/cli-bundle/chunks/{mutation-admission-P6LOBNLO.js → mutation-admission-NVTBZ7DH.js} +2 -2
- package/dist/cli-bundle/chunks/{register-list-query-JOB65KDN.js → register-list-query-2FE6K7FQ.js} +2 -2
- package/dist/cli-bundle/chunks/{register-mutation-QMNIN3RJ.js → register-mutation-KZXQUZUH.js} +2 -2
- package/dist/cli-bundle/chunks/{register-operations-Z4VET3I7.js → register-operations-BOCBWNKJ.js} +2 -2
- package/dist/cli-bundle/chunks/{register-setup-GNKWL3VL.js → register-setup-P3VLCTDY.js} +2 -2
- package/dist/cli-bundle/chunks/{stage-FW5OXCCR.js → stage-WRDTMCCF.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-LEHGCPZ6.js → chunk-3QYTS4OY.js} +4 -4
- package/dist/cli-bundle/focused-chunks/{chunk-I2USTQNQ.js → chunk-5MKNVJCM.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-CGL36F6I.js → chunk-6HJCSHBG.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-OMXMYTEX.js → chunk-7RQOFHMH.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-AGCEWJ27.js +21 -0
- package/dist/cli-bundle/focused-chunks/{chunk-BM652VCB.js → chunk-CEEOWDB7.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-NSMZY6IK.js → chunk-ES2NGVHZ.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-GXAXDPD4.js +2 -0
- package/dist/cli-bundle/focused-chunks/{chunk-P7DPS5M4.js → chunk-IUAYTD2S.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-DWQMYLFM.js → chunk-JYXA4JIP.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-XIZYJDYW.js → chunk-NFFPNMTH.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-MLET6I3I.js → chunk-NX7TVNUM.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-UNTCCJPL.js → chunk-POLMFGPQ.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-ZHMF34DT.js → chunk-RSEGFZTY.js} +52 -52
- package/dist/cli-bundle/focused-chunks/{chunk-4MNUQCGR.js → chunk-RVUGTIIU.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-6GTZ24YV.js → chunk-SBJ2IKTH.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-EDXTOAXP.js → chunk-SHUPX6G2.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-KI5YFKNN.js → chunk-TAHSXKAT.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-X5YRJMAY.js +2 -0
- package/dist/cli-bundle/focused-chunks/chunk-XTYEASIY.js +2 -0
- package/dist/cli-bundle/focused-chunks/chunk-ZMWLV2CE.js +91 -0
- package/dist/cli-bundle/focused-chunks/{mutation-admission-HTFAIHJL.js → mutation-admission-PB6YFY7S.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{stage-X3BUGEJT.js → stage-MQE4PFWT.js} +2 -2
- package/dist/cli-bundle/main.js +21 -21
- package/dist/cli-bundle/sdk-authoring.js +1 -1
- package/dist/cli-bundle/sdk-contracts.js +1 -1
- package/dist/cli-bundle/sdk-core.js +24 -24
- 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 +53 -53
- 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 +9 -9
- package/dist/core/checkpoint/mutation-checkpoint.d.ts +6 -3
- package/dist/core/checkpoint/mutation-checkpoint.js +2 -2
- package/dist/core/checkpoint/mutation-context.d.ts +10 -0
- package/dist/core/checkpoint/mutation-context.js +2 -0
- package/dist/core/store/item-store.js +1 -1
- package/dist/sdk/agent/command-recovery.d.ts +1 -0
- package/dist/sdk/agent/command-recovery.js +1 -1
- package/dist/sdk/agent/linked-resource-recovery.d.ts +13 -0
- package/dist/sdk/agent/linked-resource-recovery.js +2 -0
- package/dist/sdk/cli-contracts/grammar-contracts.js +1 -1
- package/dist/sdk/cli-contracts/typed-action-inputs.d.ts +3 -2
- package/dist/sdk/core.d.ts +1 -1
- package/dist/sdk/core.js +1 -1
- package/dist/sdk/generated/generated-error-code-catalog-part-1.js +1 -1
- package/dist/sdk/generated/generated-error-code-catalog-part-2.js +1 -1
- package/dist/sdk/index.d.ts +1 -0
- package/dist/sdk/index.js +1 -1
- package/dist/sdk/lifecycle/checkpoint-rollback.d.ts +7 -0
- package/dist/sdk/lifecycle/checkpoint-rollback.js +2 -0
- package/dist/sdk/lifecycle/close-many.d.ts +1 -1
- package/dist/sdk/lifecycle/close-many.js +1 -1
- package/dist/sdk/lifecycle/update-many.d.ts +1 -1
- package/dist/sdk/lifecycle/update-many.js +1 -1
- package/dist/sdk/public-surface.json +1 -1
- package/dist/sdk/query/context.d.ts +2 -1
- package/dist/sdk/query/context.js +1 -1
- package/dist/sdk/query/get.d.ts +2 -1
- package/dist/sdk/query/get.js +1 -1
- package/dist/sdk/query/list.d.ts +2 -1
- package/dist/sdk/query/list.js +1 -1
- package/dist/sdk/query/search/types.d.ts +2 -1
- package/dist/sdk/query.d.ts +1 -0
- package/dist/sdk/query.js +1 -1
- package/dist/sdk/read-output-contracts.d.ts +10 -2
- package/dist/sdk/read-output-contracts.js +1 -1
- package/dist/sdk/runtime-input.js +1 -1
- package/dist/sdk/runtime-primitives.d.ts +1 -0
- package/dist/sdk/runtime-primitives.js +1 -1
- package/dist/sdk/runtime.d.ts +10 -10
- package/dist/sdk/runtime.js +1 -1
- package/dist/sdk/test/batch.js +2 -2
- package/dist/sdk/test/entry.d.ts +5 -1
- package/dist/sdk/test/entry.js +2 -1
- package/dist/sdk/test/execution.js +2 -2
- package/docs/COMMANDS.md +21 -2
- package/docs/READ_OUTPUT_CONTRACTS.md +15 -0
- package/docs/SDK.md +45 -0
- package/docs/TESTING.md +28 -0
- package/docs/generated/REFUSAL_CLOSURE_CENSUS.md +5 -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-2LTGJ5DL.js +0 -2
- package/dist/cli-bundle/chunks/chunk-QSY55FTA.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-FO4U3FIP.js +0 -91
- package/dist/cli-bundle/focused-chunks/chunk-GXKP56W7.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-JDN34LEZ.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-Z5LDTMZ3.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-ZR6ZKQUK.js +0 -20
|
@@ -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]="7ae1ed4c-40b1-5bfb-90d3-27e48f152db5")}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{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]="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}
|
|
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){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(admitTestRunOptions(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;return{defaultTimeoutSeconds,pmContextMode,runSelection,testsToRun:runSelection?.selected??tests}}function admitTestRunOptions(options,tests){try{return resolveTestRunOptions(options,tests)}catch(error){throw!(error instanceof PmCliError)||error.exitCode!==EXIT_CODE.USAGE?error:new PmCliError(`${error.message} No changes were made.`,EXIT_CODE.USAGE,{...error.context,code:"invalid_argument_value",reason:"test_option_invalid",changed:!1,required:error.message})}}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=admitTestRunOptions(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);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
|
package/docs/COMMANDS.md
CHANGED
|
@@ -466,8 +466,8 @@ pm update many --filter-tag wave:7 --reviewer maintainer-review
|
|
|
466
466
|
pm update many --filter-status open --filter-type Task --filter-estimates-missing --estimate 60 --dry-run
|
|
467
467
|
|
|
468
468
|
# Audited bulk close: routes EACH match through full `pm close` semantics
|
|
469
|
-
# (close validation, active-child orphan checks, blocked-edge cleanup)
|
|
470
|
-
#
|
|
469
|
+
# (close validation, active-child orphan checks, blocked-edge cleanup).
|
|
470
|
+
# A shared --reason is required
|
|
471
471
|
# and at least one filter is required so it never matches every item.
|
|
472
472
|
pm close many --filter-sprint S-12 --reason "Sprint S-12 acceptance criteria met" --dry-run
|
|
473
473
|
pm close many --filter-sprint S-12 --reason "Sprint S-12 acceptance criteria met"
|
|
@@ -476,6 +476,25 @@ pm close many --rollback close-many-20260604-abc123 # restore the batch
|
|
|
476
476
|
|
|
477
477
|
`close many` skips already-terminal matches by default (pass `--force` to re-close), accepts `--completed-at <timestamp>` to preserve one shared actual-completion time across the batch, reports a per-item plan (`close`/`skip`, plus `active_child_ids` for parents that would be orphaned) under `--dry-run`, and writes a checkpoint by default (`--no-checkpoint` to disable). Checkpoints for both commands live under `.agents/pm/checkpoints/<command>/` and are restored with `--rollback <checkpoint-id>`.
|
|
478
478
|
|
|
479
|
+
Rollback safety is tracked by [pm-gh1563](../.agents/pm/issues/pm-gh1563.toon)
|
|
480
|
+
and the public checkpoint option by [pm-gh1564](../.agents/pm/issues/pm-gh1564.toon).
|
|
481
|
+
Rollback reverses only committed effects attributed to that batch. It preserves
|
|
482
|
+
later edits to unrelated fields, including peer comments, notes and links. If a
|
|
483
|
+
batch-modified field has since changed, that row fails without changing any of
|
|
484
|
+
its fields; inspect `get` and `history` and reconcile the conflict explicitly.
|
|
485
|
+
Collections are guarded as complete fields, so a later edit to a collection the
|
|
486
|
+
batch changed also conflicts. Other rows can recover independently.
|
|
487
|
+
|
|
488
|
+
Rows with no committed effect, and rows already rolled back, report `skipped`
|
|
489
|
+
and do not append history. The result includes `restored_count`, `skipped_count`
|
|
490
|
+
and `failed_count`; a repeated completed rollback returns the normal no-effect
|
|
491
|
+
exit. Checkpoints from older versions without attributed effects refuse rather
|
|
492
|
+
than overwrite later context. Inspect their history before choosing an explicit
|
|
493
|
+
point-in-time `restore`. Rollback respects current ownership and mutation policy;
|
|
494
|
+
it does not implicitly force an ownership or lock override.
|
|
495
|
+
New checkpoints use schema version 2 so older CLI versions also refuse them
|
|
496
|
+
instead of applying their former whole-item restoration behavior.
|
|
497
|
+
|
|
479
498
|
When a flag is rejected with `Unknown option`, the error guidance now suggests the nearest supported flag (including abbreviations like `--desc` → `--description`) and notes when the flag is valid on a different command (for example `--type` on `test-all` points to `create`/`list`). Unknown-command guidance ranks agent verb synonyms first, bounded edit distance second, and substring matches last; `pm log` therefore points to `history`, `comments`, and `notes`, never a `catalog` command.
|
|
480
499
|
|
|
481
500
|
Commands addressed to one item retain their canonical positional form and also accept `--id <item-id>` consistently. For example, `pm get --id pm-a1`, `pm comments --id pm-a1 --add "..."`, and `pm test --id pm-a1 --run` are equivalent to their positional forms. Supplying both forms is a usage error. Command-scoped `pm contracts --flags-only`, completion, and help expose the same alias.
|
|
@@ -87,6 +87,17 @@ apply. `estimated_tokens` measures the emitted envelope;
|
|
|
87
87
|
|
|
88
88
|
## Typed producer pages
|
|
89
89
|
|
|
90
|
+
Receipt typing is tracked by [pm-gh1551](https://github.com/unbraind/pm-cli/blob/main/.agents/pm/issues/pm-gh1551.toon)
|
|
91
|
+
(repository-only tracker record excluded from npm).
|
|
92
|
+
Finalized `list`, `context`, `search`, and `get` first pages expose the shared
|
|
93
|
+
`PmReadOutputMetadata` contract. Their `omission_receipt` is present; accounting
|
|
94
|
+
receipts `read_output`, `read_session`, and `output_budget_truncation` remain
|
|
95
|
+
optional. The raw query result types expose these receipts optionally because
|
|
96
|
+
`runList`, `runContext`, `runSearch`, and `runGet` return before final projection.
|
|
97
|
+
The metadata type is available from the root, core, query, contracts, and runtime
|
|
98
|
+
entrypoints. Producer continuation pages may reference the initial omission
|
|
99
|
+
receipt, and whole-result budget omissions retain their separate discriminant.
|
|
100
|
+
|
|
90
101
|
Tracked by [pm-oybmsd](https://github.com/unbraind/pm-cli/blob/main/.agents/pm/issues/pm-oybmsd.toon),
|
|
91
102
|
[pm-jqgkis](https://github.com/unbraind/pm-cli/blob/main/.agents/pm/issues/pm-jqgkis.toon), and
|
|
92
103
|
[pm-qal1uw](https://github.com/unbraind/pm-cli/blob/main/.agents/pm/tasks/pm-qal1uw.toon)
|
|
@@ -250,6 +261,10 @@ The estimate is `ceil(utf8_bytes / 4)` for the complete envelope. The CLI uses
|
|
|
250
261
|
its selected built-in JSON or TOON representation, including indentation and
|
|
251
262
|
the final newline. SDK calls that select `outputFormat` use that representation;
|
|
252
263
|
structured SDK calls without a renderer retain compact JSON measurement.
|
|
264
|
+
The native SDK dispatcher preserves that distinction for packing decisions,
|
|
265
|
+
infeasible-budget receipts, and session debits, including the returned next state
|
|
266
|
+
([pm-gh1552](https://github.com/unbraind/pm-cli/blob/main/.agents/pm/issues/pm-gh1552.toon),
|
|
267
|
+
repository-only tracker record excluded from npm).
|
|
253
268
|
Those unformatted calls keep the conservative workload-class ceiling
|
|
254
269
|
(`default_max_estimated_tokens`, also used as the generated TOON ceiling).
|
|
255
270
|
Selecting JSON explicitly opts into its larger declared rendering allowance;
|
package/docs/SDK.md
CHANGED
|
@@ -63,6 +63,10 @@ Terminal recurrence and executable recovery are tracked by
|
|
|
63
63
|
[pm-surv](../.agents/pm/issues/pm-surv.toon); see
|
|
64
64
|
[Recurrence and Executable Recovery Contracts](RECURRENCE_RECOVERY_CONTRACTS.md).
|
|
65
65
|
|
|
66
|
+
Linked-resource argument recovery and assignment-only test admission are tracked
|
|
67
|
+
by [pm-t2dqzijokl31](../.agents/pm/issues/pm-t2dqzijokl31.toon) and
|
|
68
|
+
[pm-gh1555](../.agents/pm/issues/pm-gh1555.toon).
|
|
69
|
+
|
|
66
70
|
Use it for extension authoring, package authoring, command/action contract discovery, and deterministic app or CI automation. Do not import private `src/core/...` modules from external integrations or packages.
|
|
67
71
|
|
|
68
72
|
MCP hosts can build on the public stateless protocol, MRTR, cache/schema, and
|
|
@@ -223,6 +227,16 @@ follow the migration guidance for changed unions or error codes. The shared
|
|
|
223
227
|
[primitive inventory](SDK_PRIMITIVE_INVENTORY.md) records CLI/MCP ownership:
|
|
224
228
|
presentation modules import the SDK and have zero private-core edges.
|
|
225
229
|
|
|
230
|
+
Bulk recovery is tracked by [pm-gh1563](../.agents/pm/issues/pm-gh1563.toon)
|
|
231
|
+
and [pm-gh1564](../.agents/pm/issues/pm-gh1564.toon). `PmUpdateManyActionOptions`
|
|
232
|
+
and `PmCloseManyActionOptions` accept `rollback: "checkpoint-id"`; boolean
|
|
233
|
+
values are refused before dispatch. The shared SDK inverse writer verifies
|
|
234
|
+
attributed history and current field postimages under the item lock, preserving
|
|
235
|
+
unrelated peer context. Rollback rows can now be `skipped` when no committed
|
|
236
|
+
effect remains. Exhaustive consumers should handle that additional row state.
|
|
237
|
+
See [bulk operations](COMMANDS.md#bulk-operations) for conflict, collection,
|
|
238
|
+
legacy-checkpoint and ownership semantics.
|
|
239
|
+
|
|
226
240
|
### Lock ownership and recovery
|
|
227
241
|
|
|
228
242
|
Use the public `acquireLock` primitive when a package must serialize its own
|
|
@@ -2599,6 +2613,37 @@ time.
|
|
|
2599
2613
|
|
|
2600
2614
|
## Expected CLI Errors
|
|
2601
2615
|
|
|
2616
|
+
Packages and adapters can derive explicit retries for misplaced linked-resource
|
|
2617
|
+
values with the public SDK:
|
|
2618
|
+
|
|
2619
|
+
```ts
|
|
2620
|
+
import { resolveLinkedResourcePositionalRecovery } from "@unbrained/pm-cli/sdk";
|
|
2621
|
+
|
|
2622
|
+
const recovery = resolveLinkedResourcePositionalRecovery("docs", [
|
|
2623
|
+
"docs", "pm-example", "design, notes.md", "--json",
|
|
2624
|
+
]);
|
|
2625
|
+
// recovery.retryArgs retains the item and presentation flags, with an explicit
|
|
2626
|
+
// --add value that preserves the literal comma through structured parsing.
|
|
2627
|
+
```
|
|
2628
|
+
|
|
2629
|
+
The helper returns `undefined` for ambiguous input. When a positional test value
|
|
2630
|
+
contains metadata without its actual command, the receipt retains `itemId` and
|
|
2631
|
+
`value` and omits `retryArgs`. Its `templateArgs` retains the selected tracker
|
|
2632
|
+
and output options while requiring the actual-command placeholder to be filled.
|
|
2633
|
+
The adapter can request that command using the receipt. Explicit `command=` or
|
|
2634
|
+
`cmd=` entries retain their structured metadata; empty commands receive templates.
|
|
2635
|
+
Suggested test retries pass the same command-admission guard as registration;
|
|
2636
|
+
assignment/comment-only candidates receive command-required templates.
|
|
2637
|
+
`runTestAll()` retains these legacy configuration refusals as per-test results
|
|
2638
|
+
and continues valid tests and later items. Refused entries retain their owning
|
|
2639
|
+
item and result position; aggregate and per-item tracking still report failure.
|
|
2640
|
+
Linked tests containing only assignments or shell comments fail with
|
|
2641
|
+
`linked_test_command_not_executable` before registration or execution; quoted
|
|
2642
|
+
environment assignments and later-line real commands remain supported. The
|
|
2643
|
+
guard also recognizes assignment-only lists separated by `;`, `&&`, `||`, `|`
|
|
2644
|
+
or `&`, including a trailing semicolon. Quoted and escaped separators stay
|
|
2645
|
+
inside assignment values; lists containing executable commands remain supported.
|
|
2646
|
+
|
|
2602
2647
|
Package commands should throw expected user/action errors with the public SDK shape so the CLI can preserve exit codes and Sentry can filter expected retry failures:
|
|
2603
2648
|
|
|
2604
2649
|
```ts
|
package/docs/TESTING.md
CHANGED
|
@@ -789,6 +789,34 @@ pm test <item-id> --run --pm-context auto --check-context --auto-pm-context
|
|
|
789
789
|
pm test <item-id> --add-json '{"command":"pnpm exec vitest run tests/integration/linked-test-context-trust.integration.spec.ts","pm_context_mode":"none","workspace_context_mode":"snapshot"}'
|
|
790
790
|
```
|
|
791
791
|
|
|
792
|
+
Command admission and positional recovery are tracked by
|
|
793
|
+
[pm-gh1555](../.agents/pm/issues/pm-gh1555.toon) and
|
|
794
|
+
[pm-t2dqzijokl31](../.agents/pm/issues/pm-t2dqzijokl31.toon).
|
|
795
|
+
Commands consisting only of assignments or shell comments, such as
|
|
796
|
+
`status=passed`, `RESULT=ok # note` or `RESULT=ok;`, are
|
|
797
|
+
refused with `linked_test_command_not_executable` before registration or
|
|
798
|
+
execution, including already-stored entries. They do not establish test-pass
|
|
799
|
+
evidence. Remove a legacy entry with `pm test <id> --remove-index <index>`,
|
|
800
|
+
then register the actual command. Environment assignments preceding a real
|
|
801
|
+
command remain supported on shells that accept that syntax, including commands
|
|
802
|
+
on a later line after a comment.
|
|
803
|
+
Assignment-only lists separated by `;`, `&&`, `||`, `|` or `&` also fail
|
|
804
|
+
admission. Quoted or escaped separators remain part of their assignment value;
|
|
805
|
+
the guard preserves lists containing an actual executable command.
|
|
806
|
+
|
|
807
|
+
A misplaced positional path on `files` or `docs` is refused with an explicit
|
|
808
|
+
`--add path=...` retry in `recovery.suggested_retry_args`; supplied values and
|
|
809
|
+
presentation options are preserved. A quoted positional test command receives
|
|
810
|
+
an explicit `--add-json` retry. Explicit `command=` or `cmd=` entries retain their
|
|
811
|
+
structured metadata through `--add`. An empty command or metadata alone, such
|
|
812
|
+
as `timeout_seconds=3600`, receives a template requiring the actual command, without a guessed executable
|
|
813
|
+
retry. Refused invocations do not modify the item.
|
|
814
|
+
Assignment/comment-only positional commands also receive templates instead of
|
|
815
|
+
retries that the registration guard would immediately refuse.
|
|
816
|
+
For legacy entries, `test-all` reports each refusal in its owning item's ordered
|
|
817
|
+
`run_results` with `failure_category: "config_refusal"`. Valid tests and later
|
|
818
|
+
items continue; refusals count as failures in aggregate and persisted summaries.
|
|
819
|
+
|
|
792
820
|
For complex linked-test commands, prefer JSON input so shell syntax survives unchanged:
|
|
793
821
|
|
|
794
822
|
```bash
|
|
@@ -4,12 +4,12 @@ Tracker: `pm-f05lsg`.
|
|
|
4
4
|
|
|
5
5
|
Every catalog code is listed. An `uncovered` row is an explicit closure obligation, never an omission or implied approval.
|
|
6
6
|
|
|
7
|
-
- Catalog error codes:
|
|
7
|
+
- Catalog error codes: 427
|
|
8
8
|
- Executable error codes: 19
|
|
9
9
|
- Executable-code ratchet floor: 18
|
|
10
10
|
- Required executable canonical codes: `bulk_ids_input_empty`, `bulk_ids_input_missing_path`, `bulk_ids_input_unreadable`, `invalid_argument_value`, `manifest_unknown_key`, `missing_lifecycle_target`, `missing_required_argument`, `no_version_bounds_declared`, `projection_options_mutually_exclusive`, `tracker_not_initialized`, `tracker_root_missing`, `tracker_root_not_directory`, `tracker_root_unreadable`, `unknown_context_intent`, `unknown_field_projection`, `unknown_option`, `unknown_subcommand`
|
|
11
|
-
- Uncovered error codes:
|
|
12
|
-
- Coverage fraction: 0.
|
|
11
|
+
- Uncovered error codes: 408
|
|
12
|
+
- Coverage fraction: 0.044496
|
|
13
13
|
- Closed-domain probes: 19
|
|
14
14
|
- Grammar probes: 117
|
|
15
15
|
|
|
@@ -191,6 +191,8 @@ Every catalog code is listed. An `uncovered` row is an explicit closure obligati
|
|
|
191
191
|
| `item_not_found` | `item_not_found` | uncovered | none | 0 |
|
|
192
192
|
| `likely_duplicate` | `likely_duplicate` | uncovered | none | 0 |
|
|
193
193
|
| `linked_artifact_mutation_conflict` | `linked_artifact_mutation_conflict` | uncovered | none | 0 |
|
|
194
|
+
| `linked_resource_positional_value` | `linked_resource_positional_value` | uncovered | none | 0 |
|
|
195
|
+
| `linked_test_command_not_executable` | `linked_test_command_not_executable` | uncovered | none | 0 |
|
|
194
196
|
| `linked_test_context_invalid` | `linked_test_context_invalid` | uncovered | none | 0 |
|
|
195
197
|
| `linked_test_remove_no_match` | `linked_test_remove_no_match` | uncovered | none | 0 |
|
|
196
198
|
| `linked_test_update_ambiguous` | `linked_test_update_ambiguous` | uncovered | none | 0 |
|
package/marketplace.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"name":"pm","owner":{"name":"unbrained","url":"https://github.com/unbraind/pm-cli"},"description":"Official marketplace for pm CLI — native git-based project management for Claude Code and AI coding agents.","version":"2026.10.
|
|
1
|
+
{"name":"pm","owner":{"name":"unbrained","url":"https://github.com/unbraind/pm-cli"},"description":"Official marketplace for pm CLI — native git-based project management for Claude Code and AI coding agents.","version":"2026.10.24","plugins":[{"name":"pm-claude","source":"./plugins/pm-claude","description":"Native pm CLI integration for Claude Code — 32 MCP tools, 14 workflow skills with slash invocations, 4 subagents, hybrid TUI task tracking, session context injection, and coordination subagents for git-based project management without leaving Claude Code.","version":"2026.10.24","author":{"name":"unbrained","url":"https://github.com/unbraind/pm-cli"},"homepage":"https://github.com/unbraind/pm-cli","repository":"https://github.com/unbraind/pm-cli","license":"MIT","keywords":["pm-cli","project-management","mcp","tasks","agents","workflows","git-native","task-tracker","ai"],"category":"productivity"}]}
|
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"name":"@unbrained/pm-beads","version":"2026.10.
|
|
1
|
+
{"name":"@unbrained/pm-beads","version":"2026.10.24","private":true,"type":"module","description":"First-party pm package for importing Beads JSONL records.","homepage":"https://github.com/unbraind/pm-cli/tree/main/packages/pm-beads#readme","repository":{"type":"git","url":"git+https://github.com/unbraind/pm-cli.git","directory":"packages/pm-beads"},"bugs":{"url":"https://github.com/unbraind/pm-cli/issues"},"keywords":["pm-package","project-management","beads"],"pm":{"aliases":["beads"],"extensions":["extensions/beads"],"catalog":{"display_name":"Beads Import","category":"migration","summary":"Import Beads JSONL records into pm while preserving source IDs.","tags":["migration","beads","import"],"links":{"docs":"https://github.com/unbraind/pm-cli/tree/main/packages/pm-beads#readme","repository":"https://github.com/unbraind/pm-cli/tree/main/packages/pm-beads","report":"https://github.com/unbraind/pm-cli/issues"}},"docs":["README.md"],"examples":["README.md"]}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"name":"@unbrained/pm-calendar","version":"2026.10.
|
|
1
|
+
{"name":"@unbrained/pm-calendar","version":"2026.10.24","private":true,"type":"module","description":"First-party pm package for calendar and agenda views.","homepage":"https://github.com/unbraind/pm-cli/tree/main/packages/pm-calendar#readme","repository":{"type":"git","url":"git+https://github.com/unbraind/pm-cli.git","directory":"packages/pm-calendar"},"bugs":{"url":"https://github.com/unbraind/pm-cli/issues"},"keywords":["pm-package","project-management","calendar","agenda"],"pm":{"aliases":["calendar"],"extensions":["extensions/calendar"],"catalog":{"display_name":"Calendar Views","category":"workflow","summary":"Show agenda, day, week, and month views for deadlines, reminders, and scheduled events.","tags":["calendar","agenda","workflow"],"links":{"docs":"https://github.com/unbraind/pm-cli/tree/main/packages/pm-calendar#readme","repository":"https://github.com/unbraind/pm-cli/tree/main/packages/pm-calendar","report":"https://github.com/unbraind/pm-cli/issues"}},"docs":["README.md"],"examples":["README.md"]}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"name":"@unbrained/pm-command-kit","version":"2026.10.
|
|
1
|
+
{"name":"@unbrained/pm-command-kit","version":"2026.10.24","private":true,"type":"module","description":"First-party pm exemplar package for the commands capability: registerCommand, registerFlags, and registerParser.","homepage":"https://github.com/unbraind/pm-cli/tree/main/packages/pm-command-kit#readme","repository":{"type":"git","url":"git+https://github.com/unbraind/pm-cli.git","directory":"packages/pm-command-kit"},"bugs":{"url":"https://github.com/unbraind/pm-cli/issues"},"keywords":["pm-package","project-management","sdk","commands","exemplar"],"pm":{"aliases":["command-kit"],"extensions":["extensions/command-kit"],"catalog":{"display_name":"Command Kit Exemplar","category":"sdk","summary":"Copy-paste-able exemplar for registerCommand, registerFlags, and registerParser.","tags":["sdk","commands","parser","exemplar"],"links":{"docs":"https://github.com/unbraind/pm-cli/tree/main/packages/pm-command-kit#readme","repository":"https://github.com/unbraind/pm-cli/tree/main/packages/pm-command-kit","report":"https://github.com/unbraind/pm-cli/issues"}},"docs":["README.md"],"examples":["README.md"]}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"name":"@unbrained/pm-digital-twin","version":"2026.10.
|
|
1
|
+
{"name":"@unbrained/pm-digital-twin","version":"2026.10.24","private":true,"type":"module","description":"Beyond-PM exemplar: a temporal production-facility digital twin built entirely on public pm SDK and extension primitives.","homepage":"https://github.com/unbraind/pm-cli/tree/main/packages/pm-digital-twin#readme","repository":{"type":"git","url":"git+https://github.com/unbraind/pm-cli.git","directory":"packages/pm-digital-twin"},"bugs":{"url":"https://github.com/unbraind/pm-cli/issues"},"keywords":["pm-package","sdk","digital-twin","event-sourcing","temporal-graph","exemplar"],"pm":{"aliases":["digital-twin","twin"],"extensions":["extensions/digital-twin"],"catalog":{"display_name":"Digital Twin SDK Exemplar","category":"sdk","summary":"Temporal facility graph proving event replay, point-in-time state, invariants, federation, checkpoints, and compact exports through public SDK contracts.","tags":["sdk","digital-twin","event-sourcing","graph","offline-first","exemplar"],"links":{"docs":"https://github.com/unbraind/pm-cli/tree/main/packages/pm-digital-twin#readme","repository":"https://github.com/unbraind/pm-cli/tree/main/packages/pm-digital-twin","report":"https://github.com/unbraind/pm-cli/tree/main/packages/pm-digital-twin/GAP_REPORT.md"}},"docs":["README.md","GAP_REPORT.md"],"examples":["README.md"]}}
|