@unbrained/pm-cli 2026.9.17 → 2026.9.19
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 +2 -2
- package/CHANGELOG.md +49 -0
- package/dist/cli/invocation-profile.d.ts +26 -0
- package/dist/cli/invocation-profile.js +3 -0
- package/dist/cli/register-operations.js +1 -1
- package/dist/cli-bundle/bundle-manifest.json +149 -149
- package/dist/cli-bundle/chunks/{chunk-LD2FH2YE.js → chunk-7CN53F4S.js} +2 -2
- package/dist/cli-bundle/chunks/chunk-AACGU5IA.js +3 -0
- package/dist/cli-bundle/chunks/{chunk-Q6KPZGMP.js → chunk-CCLWCESZ.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-T526C5DX.js → chunk-J7NP7QLW.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-UF75LUAK.js → chunk-K4VAU4HU.js} +63 -63
- package/dist/cli-bundle/chunks/chunk-KHN4NMKT.js +238 -0
- package/dist/cli-bundle/chunks/{chunk-YZXB4XGA.js → chunk-KYG2QKNY.js} +29 -29
- package/dist/cli-bundle/chunks/{chunk-CPFBDM53.js → chunk-OQS2Y6VN.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-G2IJF244.js → chunk-QA4GUEKJ.js} +20 -20
- package/dist/cli-bundle/chunks/chunk-QTHR3LXF.js +20 -0
- package/dist/cli-bundle/chunks/{chunk-5ZY4WE3F.js → chunk-RVMDPIGD.js} +2 -2
- package/dist/cli-bundle/chunks/chunk-WASMH647.js +8 -0
- package/dist/cli-bundle/chunks/{register-list-query-ISM35VIX.js → register-list-query-RMZNHSII.js} +2 -2
- package/dist/cli-bundle/chunks/{register-mutation-KHMBJJS6.js → register-mutation-HJGSGX3H.js} +2 -2
- package/dist/cli-bundle/chunks/register-operations-LBEMFUMM.js +2 -0
- package/dist/cli-bundle/chunks/{register-setup-YAURJMY5.js → register-setup-MU557JAA.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-G4TKJZ4A.js → chunk-5SZ4JSPZ.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-NOK652OP.js → chunk-7Y7PAC56.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-5KYHDFDN.js → chunk-ARK4FIO4.js} +3 -3
- package/dist/cli-bundle/focused-chunks/{chunk-LNM3BS45.js → chunk-CJ4QA4RW.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-I32L3FWW.js +12 -0
- package/dist/cli-bundle/focused-chunks/{chunk-267N774W.js → chunk-I4WDON3K.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-7ZC4CKOX.js → chunk-IKPR4VVN.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-J3GN4SQT.js +2 -0
- package/dist/cli-bundle/focused-chunks/{chunk-GKSAMNBG.js → chunk-OOZHQFBF.js} +16 -16
- package/dist/cli-bundle/focused-chunks/{chunk-JZSLDLR4.js → chunk-P4BA7OX7.js} +27 -26
- package/dist/cli-bundle/focused-chunks/chunk-QL4MO5M2.js +30 -0
- package/dist/cli-bundle/focused-chunks/chunk-TDHVROGL.js +3 -0
- package/dist/cli-bundle/focused-chunks/chunk-V3YMNAO7.js +2 -0
- package/dist/cli-bundle/focused-chunks/chunk-YDFEWXXZ.js +59 -0
- package/dist/cli-bundle/focused-chunks/{chunk-WUMGGQYN.js → chunk-ZDBKYZFE.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-BFUXTNZU.js → chunk-ZI2YBJFH.js} +2 -2
- package/dist/cli-bundle/main.js +2 -2
- package/dist/cli-bundle/sdk-authoring.js +1 -1
- package/dist/cli-bundle/sdk-contracts.js +2 -2
- package/dist/cli-bundle/sdk-core.js +25 -25
- 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 +41 -41
- 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 +8 -8
- package/dist/cli.js +1 -1
- package/dist/core/checkpoint/mutation-checkpoint.d.ts +1 -1
- package/dist/core/checkpoint/mutation-checkpoint.js +2 -2
- package/dist/core/config/nested-settings.js +1 -1
- package/dist/core/fs/atomic-create.d.ts +7 -0
- package/dist/core/fs/atomic-create.js +2 -0
- package/dist/core/history/history.js +1 -1
- package/dist/core/schema/runtime-schema.d.ts +1 -1
- package/dist/core/schema/runtime-schema.js +2 -2
- package/dist/core/session/session-state.js +1 -1
- package/dist/core/shared/secret-rules.d.ts +110 -0
- package/dist/core/shared/secret-rules.js +2 -0
- package/dist/core/store/settings-concurrency.d.ts +7 -0
- package/dist/core/store/settings-concurrency.js +2 -0
- package/dist/core/store/settings.d.ts +8 -1
- package/dist/core/store/settings.js +3 -2
- package/dist/mcp/server.d.ts +4 -0
- package/dist/mcp/server.js +1 -1
- package/dist/sdk/cli-contracts/command-aliases.js +1 -1
- package/dist/sdk/cli-contracts/flag-contracts.js +1 -1
- package/dist/sdk/cli-contracts/runtime-contracts.js +1 -1
- package/dist/sdk/cli-program.js +1 -1
- package/dist/sdk/context-intent-contracts.js +1 -1
- package/dist/sdk/context-relevance.js +1 -1
- package/dist/sdk/contracts.d.ts +1 -0
- package/dist/sdk/contracts.js +1 -1
- package/dist/sdk/extension/install-runtime.d.ts +1 -1
- package/dist/sdk/extension/install-runtime.js +2 -2
- package/dist/sdk/governance/health.js +1 -1
- package/dist/sdk/governance/provenance-health.d.ts +27 -0
- package/dist/sdk/governance/provenance-health.js +1 -1
- package/dist/sdk/graph/index.d.ts +1 -0
- package/dist/sdk/graph/index.js +1 -1
- package/dist/sdk/graph/query-contracts.d.ts +18 -0
- package/dist/sdk/graph/query-contracts.js +2 -0
- package/dist/sdk/graph/run.d.ts +2 -0
- package/dist/sdk/graph/run.js +1 -1
- package/dist/sdk/graph/traversal.js +1 -1
- package/dist/sdk/guide-topics.js +1 -1
- package/dist/sdk/lifecycle/event-validation-messages.d.ts +1 -1
- package/dist/sdk/lifecycle/event-validation-messages.js +1 -1
- package/dist/sdk/lifecycle-policy.js +1 -1
- package/dist/sdk/mutation-guard.d.ts +8 -1
- package/dist/sdk/mutation-guard.js +1 -1
- package/dist/sdk/public-surface.json +1 -1
- package/dist/sdk/query/next-ready-budget.d.ts +20 -0
- package/dist/sdk/query/next-ready-budget.js +2 -0
- package/dist/sdk/query/next.d.ts +7 -2
- package/dist/sdk/query/next.js +1 -1
- package/dist/sdk/read-output-budget.d.ts +3 -1
- package/dist/sdk/read-output-budget.js +1 -1
- package/dist/sdk/read-output-contracts.js +1 -1
- package/dist/sdk/read-output-session.js +1 -1
- package/dist/sdk/test/execution.d.ts +6 -5
- package/dist/sdk/test/execution.js +2 -2
- package/dist/sdk/test/json-path.d.ts +11 -0
- package/dist/sdk/test/json-path.js +2 -0
- package/docs/BUILD_AND_RELEASE_ACCEPTANCE.md +66 -0
- package/docs/MCP_OPTION_CONFORMANCE.md +41 -0
- package/docs/MUTATION_INTEGRITY.md +36 -4
- package/docs/NEXT_SELECTION_BUDGETS.md +93 -0
- package/docs/OUTPUT_PROJECTION_CONTRACTS.md +18 -2
- package/docs/PACKED_FIRST_RUN.md +8 -4
- package/docs/PERFORMANCE.md +37 -0
- package/docs/README.md +2 -0
- package/docs/READ_OUTPUT_CONTRACTS.md +8 -4
- package/docs/SDK_AGENT_SESSION_CONTEXT.md +28 -4
- package/docs/SDK_LIFECYCLE.md +16 -2
- package/docs/WORKSPACE_CONCURRENCY.md +67 -0
- package/docs/agent-task-token-baseline.json +7 -7
- package/docs/generated/AGENT_CAPABILITY_ROUTING.md +1 -1
- package/docs/generated/AGENT_COMMAND_SURFACE.md +3 -0
- package/docs/generated/FLAG_LEXICON_BUDGETS.md +3 -0
- package/marketplace.json +2 -2
- package/package.json +11 -9
- package/packages/pm-beads/package.json +1 -1
- package/packages/pm-calendar/README.md +43 -2
- package/packages/pm-calendar/extensions/calendar/command-definitions.ts +52 -0
- package/packages/pm-calendar/extensions/calendar/index.ts +2 -0
- package/packages/pm-calendar/extensions/calendar/manifest.json +3 -3
- package/packages/pm-calendar/extensions/calendar/shortcuts.ts +15 -0
- 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-codex/.codex-plugin/plugin.json +1 -1
- package/scripts/build-lease.mjs +43 -0
- package/scripts/build.mjs +40 -0
- package/dist/cli-bundle/chunks/chunk-C3MFMGCR.js +0 -20
- package/dist/cli-bundle/chunks/chunk-DN2RIHRN.js +0 -3
- package/dist/cli-bundle/chunks/chunk-E67MIO3Z.js +0 -8
- package/dist/cli-bundle/chunks/chunk-UJSB6NO6.js +0 -237
- package/dist/cli-bundle/chunks/register-operations-HHDROJD6.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-5FSGN4MP.js +0 -12
- package/dist/cli-bundle/focused-chunks/chunk-7S7XWMXC.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-IVWUQE7T.js +0 -59
- package/dist/cli-bundle/focused-chunks/chunk-OCTTDZOU.js +0 -3
- package/dist/cli-bundle/focused-chunks/chunk-OWDDVMAP.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-Z4PWEIMX.js +0 -30
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
!(function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=new e.Error().stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="2833cfc3-db98-5f7f-82e0-388e320ef740")}catch(e2){}})();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{acknowledgeLinkedTests,attachLinkedTestMutationProvenance,resolveLinkedTestSourceRef,resolveLinkedTestSourceWorkspaceRoot,resolveLinkedTestTrust}from"./trust.js";const TEST_OUTPUT_MAX_BUFFER_BYTES=20*1024*1024;const DEFAULT_LINKED_TEST_TIMEOUT_FORCE_KILL_DELAY_MS=3e3;const DEFAULT_LINKED_TEST_HEARTBEAT_INTERVAL_MS=1e4;const DEFAULT_LINKED_TEST_PIPE_CLOSE_GRACE_MS=5e3;const MAX_LINKED_TEST_COMMAND_LABEL_LENGTH=120;const LINKED_TEST_TRACKER_DIRS_TO_SKIP=new Set(["locks","extensions","runtime"]);const LINKED_TEST_ITEM_COUNT_DIRS_TO_SKIP=new Set(["history","index","search","extensions","locks","runtime"]);const 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];const LINKED_TEST_LOCK_ACQUISITION_PATTERN=/\b(?:could not|failed to|unable to)\s+acquire\b[^\n.]*\block\b/i;const LINKED_TEST_LOCK_CONTENTION_CONTEXT_PATTERN=/\b(?:already running|timed?\s*out|timeout|held by another|lock\s+is\s+held)\b/i;const 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);if(!Number.isFinite(parsed)||parsed<=0){return fallback}return 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();if(fromEnv&&fromEnv.length>0){return fromEnv}return`${kind}-local-${Date.now().toString(36)}-${Math.random().toString(36).slice(2,8)}`}function summarizeRunResultStatuses(results){let passed=0;let failed=0;let skipped=0;for(const entry of results){if(entry.status==="passed"){passed+=1;continue}if(entry.status==="failed"){failed+=1;continue}skipped+=1}return{passed,failed,skipped}}function summarizeContextPreflight(runResults){let checkedPmCommands=0;let trackerReadCommands=0;let mismatches=0;let autoRemediated=0;for(const result of runResults){const context=result.execution_context;if(!context||context.is_pm_command!==true){continue}checkedPmCommands+=1;if(context.is_pm_tracker_read_command===true){trackerReadCommands+=1}if(context.mismatch_detected===true){mismatches+=1}if(context.auto_pm_context_applied===true){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){if(overrideLinkedMode){return runLevelMode}return parseLinkedTestWorkspaceContextMode(linkedTest.workspace_context_mode,"linked test")??runLevelMode}function resolveLinkedTestRequestedContextMode(linkedTest,runLevelMode,overrideLinkedPmContext){if(overrideLinkedPmContext){return runLevelMode}if(typeof linkedTest.pm_context_mode!=="string"||linkedTest.pm_context_mode.trim().length===0){return runLevelMode}return parsePmContextMode(linkedTest.pm_context_mode)}function resolveLinkedTestEffectiveContextMode(requestedMode,isPmTrackerReadCommand){if(requestedMode==="auto"){return isPmTrackerReadCommand?"tracker":"schema"}return requestedMode}function hasLinkedTestAssertions(linkedTest){return Object.entries(linkedTest).filter(([key])=>key.startsWith("assert_")).some(([,value])=>{if(value===void 0||value===null)return false;if(Array.isArray(value))return value.length>0;if(typeof value==="object")return Object.keys(value).length>0;if(typeof value==="string")return value.trim().length>0;return true})}function buildPmContextMismatchHint(params){const{executionContext,runLevelPmContextMode,linkedOverridePmContextMode}=params;if(!executionContext.is_pm_tracker_read_command||!executionContext.mismatch_detected){return""}if(runLevelPmContextMode==="tracker"&&linkedOverridePmContextMode==="schema"){return" 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."}if(executionContext.pm_context_mode==="schema"){return" 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."}return""}function mergeEnvSetDirectives(entries,optionName){const merged={};if(!entries){return merged}for(const entry of entries){const parsed=parseLinkedTestEnvSetValue(entry,optionName);if(!parsed){continue}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);if(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 false}const context=resolvePmSubcommandContext(invocationArgs);if(!context){return false}return PM_TRACKER_READ_SUBCOMMANDS.has(context.subcommand)})}function resolveDirectRunnerSubcommand(parsed){if(!parsed){return void 0}if(!SCRIPT_RUN_SUBCOMMANDS.has(parsed.subcommand)){return parsed.subcommand}return parseLauncherSubcommand(parsed.args,SCRIPT_RUN_FLAGS_WITH_VALUE)?.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))}return void 0}function isDirectTestRunnerSubcommand(token){if(!token){return false}return token==="vitest"||token==="test"||token.startsWith("test:")}function parsedLauncherInvokesRecursiveTestAll(parsed){if(!parsed){return false}if(!isPmExecutableToken(parsed.command)&&!isPmCliPackageToken(parsed.command)){return false}return 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])]);const PACKAGE_EXEC_EXECUTABLES=new Set(["npx","bunx"]);function segmentInvokesRecursiveTestAll(segment){const rawTokens=segment.split(" ").filter(token=>token.length>0);const tokens=stripLeadingEnvAssignments(rawTokens);if(tokens.length===0){return false}const[executable,...args]=tokens;const 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(" ")):false}if(isPmExecutableToken(executable)||isPmCliScriptToken(executable)){return firstPmSubcommand(args)==="test-all"}if(executable==="node"&&args.length>0&&isPmCliScriptToken(args[0])){return firstPmSubcommand(args.slice(1))==="test-all"}if(PACKAGE_EXEC_EXECUTABLES.has(executable)){return parsedLauncherInvokesRecursiveTestAll(parseNpxCommand(args))}if(executable==="pnpm"){return parsedLauncherInvokesRecursiveTestAll(parsePnpmDlxCommand(args))}if(executable==="npm"){return parsedLauncherInvokesRecursiveTestAll(parseNpmExecCommand(args))}return false}function invokesRecursiveTestAllCommand(command){const normalized=normalizeCommandForValidation(command);return splitNormalizedCommandSegments(normalized).some(segment=>segmentInvokesRecursiveTestAll(segment))}function assertNoRecursiveTestAllCommand(command){if(!invokesRecursiveTestAllCommand(command))return;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);const tokens=stripLeadingEnvAssignments(rawTokens);if(tokens.length===0){return false}const[executable,...args]=tokens;const 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(" "))}if(executable==="vitest"||executable.endsWith("/vitest")||executable.endsWith("/vitest.mjs")){return true}if(executable==="node"){return nodeArgsInvokeDirectTestRunner(args)}return 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){if(executable==="npx"||executable==="bunx"){return isDirectTestRunnerSubcommand(parseNpxCommand(args)?.command)}if(executable==="pnpm"||executable==="npm"){return isDirectTestRunnerSubcommand((executable==="pnpm"?parsePnpmDlxCommand(args):parseNpmExecCommand(args))?.command)||isDirectTestRunnerSubcommand(firstDirectTestRunnerSubcommand(executable,args))}return(executable==="yarn"||executable==="bun")&&isDirectTestRunnerSubcommand(firstDirectTestRunnerSubcommand(executable,args))}function classifyLinkedTestCommandSafety(command){const normalized=normalizeCommandForValidation(command);const segments=splitNormalizedCommandSegments(normalized);const directRunner=segments.some(segment=>!commandUsesSandboxRunner(segment)&&segmentInvokesDirectTestRunner(segment));const sandboxRunner=segments.some(commandUsesSandboxRunner);return{accepted:true,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 void 0;return'Linked test command skipped: Linked test commands must not invoke "pm test-all"; this creates recursive orchestration.'}function parseAddEntries(raw){if(!raw)return[];return raw.map(parseAddEntry)}function parseAddEntry(entry){const trimmed=entry.trim();const kv=looksLikeStructuredLinkedTestEntry(trimmed)?normalizeStructuredLinkedTestEntry(parseCsvKv(entry,"--add"),"--add"):{command:trimmed};const 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)}assertNoRecursiveTestAllCommand(command);return{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 void 0;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){if(!raw)return[];return 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){if(!raw)return[];return 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");const kind=kv.path!==void 0?"path":"command";const 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){if(!raw)return[];return[...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," ");if(normalized.length<=MAX_LINKED_TEST_COMMAND_LABEL_LENGTH){return normalized}return`${normalized.slice(0,MAX_LINKED_TEST_COMMAND_LABEL_LENGTH-3)}...`}function shouldEmitLinkedTestProgress(mode){if(mode==="off"){return false}if(mode==="always"){return true}return process.stderr.isTTY===true}function emitLinkedTestProgress(message){try{process.stderr.write(`${message}
|
|
1
|
+
!(function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=new e.Error().stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="cbc188f2-47f8-5e71-ab9f-feaac4ebf257")}catch(e2){}})();import{readJsonPathValue,splitJsonPathSegments}from"./json-path.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{acknowledgeLinkedTests,attachLinkedTestMutationProvenance,resolveLinkedTestSourceRef,resolveLinkedTestSourceWorkspaceRoot,resolveLinkedTestTrust}from"./trust.js";const TEST_OUTPUT_MAX_BUFFER_BYTES=20*1024*1024;const DEFAULT_LINKED_TEST_TIMEOUT_FORCE_KILL_DELAY_MS=3e3;const DEFAULT_LINKED_TEST_HEARTBEAT_INTERVAL_MS=1e4;const DEFAULT_LINKED_TEST_PIPE_CLOSE_GRACE_MS=5e3;const MAX_LINKED_TEST_COMMAND_LABEL_LENGTH=120;const LINKED_TEST_TRACKER_DIRS_TO_SKIP=new Set(["locks","extensions","runtime"]);const LINKED_TEST_ITEM_COUNT_DIRS_TO_SKIP=new Set(["history","index","search","extensions","locks","runtime"]);const 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];const LINKED_TEST_LOCK_ACQUISITION_PATTERN=/\b(?:could not|failed to|unable to)\s+acquire\b[^\n.]*\block\b/i;const LINKED_TEST_LOCK_CONTENTION_CONTEXT_PATTERN=/\b(?:already running|timed?\s*out|timeout|held by another|lock\s+is\s+held)\b/i;const 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);if(!Number.isFinite(parsed)||parsed<=0){return fallback}return 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();if(fromEnv&&fromEnv.length>0){return fromEnv}return`${kind}-local-${Date.now().toString(36)}-${Math.random().toString(36).slice(2,8)}`}function summarizeRunResultStatuses(results){let passed=0;let failed=0;let skipped=0;for(const entry of results){if(entry.status==="passed"){passed+=1;continue}if(entry.status==="failed"){failed+=1;continue}skipped+=1}return{passed,failed,skipped}}function summarizeContextPreflight(runResults){let checkedPmCommands=0;let trackerReadCommands=0;let mismatches=0;let autoRemediated=0;for(const result of runResults){const context=result.execution_context;if(!context||context.is_pm_command!==true){continue}checkedPmCommands+=1;if(context.is_pm_tracker_read_command===true){trackerReadCommands+=1}if(context.mismatch_detected===true){mismatches+=1}if(context.auto_pm_context_applied===true){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){if(overrideLinkedMode){return runLevelMode}return parseLinkedTestWorkspaceContextMode(linkedTest.workspace_context_mode,"linked test")??runLevelMode}function resolveLinkedTestRequestedContextMode(linkedTest,runLevelMode,overrideLinkedPmContext){if(overrideLinkedPmContext){return runLevelMode}if(typeof linkedTest.pm_context_mode!=="string"||linkedTest.pm_context_mode.trim().length===0){return runLevelMode}return parsePmContextMode(linkedTest.pm_context_mode)}function resolveLinkedTestEffectiveContextMode(requestedMode,isPmTrackerReadCommand){if(requestedMode==="auto"){return isPmTrackerReadCommand?"tracker":"schema"}return requestedMode}function hasLinkedTestAssertions(linkedTest){return Object.entries(linkedTest).filter(([key])=>key.startsWith("assert_")).some(([,value])=>{if(value===void 0||value===null)return false;if(Array.isArray(value))return value.length>0;if(typeof value==="object")return Object.keys(value).length>0;if(typeof value==="string")return value.trim().length>0;return true})}function buildPmContextMismatchHint(params){const{executionContext,runLevelPmContextMode,linkedOverridePmContextMode}=params;if(!executionContext.is_pm_tracker_read_command||!executionContext.mismatch_detected){return""}if(runLevelPmContextMode==="tracker"&&linkedOverridePmContextMode==="schema"){return" 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."}if(executionContext.pm_context_mode==="schema"){return" 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."}return""}function mergeEnvSetDirectives(entries,optionName){const merged={};if(!entries){return merged}for(const entry of entries){const parsed=parseLinkedTestEnvSetValue(entry,optionName);if(!parsed){continue}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);if(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 false}const context=resolvePmSubcommandContext(invocationArgs);if(!context){return false}return PM_TRACKER_READ_SUBCOMMANDS.has(context.subcommand)})}function resolveDirectRunnerSubcommand(parsed){if(!parsed){return void 0}if(!SCRIPT_RUN_SUBCOMMANDS.has(parsed.subcommand)){return parsed.subcommand}return parseLauncherSubcommand(parsed.args,SCRIPT_RUN_FLAGS_WITH_VALUE)?.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))}return void 0}function isDirectTestRunnerSubcommand(token){if(!token){return false}return token==="vitest"||token==="test"||token.startsWith("test:")}function parsedLauncherInvokesRecursiveTestAll(parsed){if(!parsed){return false}if(!isPmExecutableToken(parsed.command)&&!isPmCliPackageToken(parsed.command)){return false}return 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])]);const PACKAGE_EXEC_EXECUTABLES=new Set(["npx","bunx"]);function segmentInvokesRecursiveTestAll(segment){const rawTokens=segment.split(" ").filter(token=>token.length>0);const tokens=stripLeadingEnvAssignments(rawTokens);if(tokens.length===0){return false}const[executable,...args]=tokens;const 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(" ")):false}if(isPmExecutableToken(executable)||isPmCliScriptToken(executable)){return firstPmSubcommand(args)==="test-all"}if(executable==="node"&&args.length>0&&isPmCliScriptToken(args[0])){return firstPmSubcommand(args.slice(1))==="test-all"}if(PACKAGE_EXEC_EXECUTABLES.has(executable)){return parsedLauncherInvokesRecursiveTestAll(parseNpxCommand(args))}if(executable==="pnpm"){return parsedLauncherInvokesRecursiveTestAll(parsePnpmDlxCommand(args))}if(executable==="npm"){return parsedLauncherInvokesRecursiveTestAll(parseNpmExecCommand(args))}return false}function invokesRecursiveTestAllCommand(command){const normalized=normalizeCommandForValidation(command);return splitNormalizedCommandSegments(normalized).some(segment=>segmentInvokesRecursiveTestAll(segment))}function assertNoRecursiveTestAllCommand(command){if(!invokesRecursiveTestAllCommand(command))return;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);const tokens=stripLeadingEnvAssignments(rawTokens);if(tokens.length===0){return false}const[executable,...args]=tokens;const 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(" "))}if(executable==="vitest"||executable.endsWith("/vitest")||executable.endsWith("/vitest.mjs")){return true}if(executable==="node"){return nodeArgsInvokeDirectTestRunner(args)}return 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){if(executable==="npx"||executable==="bunx"){return isDirectTestRunnerSubcommand(parseNpxCommand(args)?.command)}if(executable==="pnpm"||executable==="npm"){return isDirectTestRunnerSubcommand((executable==="pnpm"?parsePnpmDlxCommand(args):parseNpmExecCommand(args))?.command)||isDirectTestRunnerSubcommand(firstDirectTestRunnerSubcommand(executable,args))}return(executable==="yarn"||executable==="bun")&&isDirectTestRunnerSubcommand(firstDirectTestRunnerSubcommand(executable,args))}function classifyLinkedTestCommandSafety(command){const normalized=normalizeCommandForValidation(command);const segments=splitNormalizedCommandSegments(normalized);const directRunner=segments.some(segment=>!commandUsesSandboxRunner(segment)&&segmentInvokesDirectTestRunner(segment));const sandboxRunner=segments.some(commandUsesSandboxRunner);return{accepted:true,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 void 0;return'Linked test command skipped: Linked test commands must not invoke "pm test-all"; this creates recursive orchestration.'}function parseAddEntries(raw){if(!raw)return[];return raw.map(parseAddEntry)}function parseAddEntry(entry){const trimmed=entry.trim();const kv=looksLikeStructuredLinkedTestEntry(trimmed)?normalizeStructuredLinkedTestEntry(parseCsvKv(entry,"--add"),"--add"):{command:trimmed};const 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)}assertNoRecursiveTestAllCommand(command);return{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 void 0;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){if(!raw)return[];return 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){if(!raw)return[];return 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");const kind=kv.path!==void 0?"path":"command";const 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){if(!raw)return[];return[...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," ");if(normalized.length<=MAX_LINKED_TEST_COMMAND_LABEL_LENGTH){return normalized}return`${normalized.slice(0,MAX_LINKED_TEST_COMMAND_LABEL_LENGTH-3)}...`}function shouldEmitLinkedTestProgress(mode){if(mode==="off"){return false}if(mode==="always"){return true}return process.stderr.isTTY===true}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);const 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());heartbeat.unref?.();return heartbeat}function endLinkedTestProgress(context,executionResult,startedAt,mode){if(!shouldEmitLinkedTestProgress(mode)){return}const commandLabel=summarizeLinkedTestCommand(context.command);const elapsedMs=Date.now()-startedAt;const failed=executionResult.timedOut||executionResult.maxBufferExceeded||executionResult.exitCode!==0;const statusLabel=failed?"failed":"passed";const reasonTokens=[];if(executionResult.timedOut){reasonTokens.push("reason=timeout")}if(executionResult.maxBufferExceeded){reasonTokens.push("reason=max_buffer")}if(executionResult.outputTruncated){reasonTokens.push("output=truncated")}if(executionResult.signal){reasonTokens.push(`signal=${executionResult.signal}`)}const exitLabel=executionResult.exitCode===null?"null":String(executionResult.exitCode);const 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){return}if(process.platform==="win32"){await new Promise(resolve=>{const killer=spawn("taskkill",["/pid",String(pid),"/T","/F"],{stdio:"ignore",windowsHide:true});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){if(timers.heartbeat){clearInterval(timers.heartbeat)}if(timers.timedOutTimer){clearTimeout(timers.timedOutTimer);timers.timedOutTimer=null}if(timers.forceKillTimer){clearTimeout(timers.forceKillTimer);timers.forceKillTimer=null}}function createLinkedTestTerminationRequester(child,timers){let terminationRequested=false;return async()=>{if(terminationRequested){return}terminationRequested=true;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(()=>{void killProcessTree(pid)},linkedTestTimeoutForceKillDelayMs());timers.forceKillTimer.unref?.()}}function waitForLinkedTestChildClose(child){return new Promise(resolve=>{let settled=false;let pipeCloseGraceTimer=null;const settle=(value,destroyPipes=false)=>{if(settled){return}settled=true;if(pipeCloseGraceTimer){clearTimeout(pipeCloseGraceTimer);pipeCloseGraceTimer=null}if(destroyPipes){child.stdin?.destroy();child.stdout?.destroy();child.stderr?.destroy()}resolve(value)};child.on("exit",(exitCode,exitSignal)=>{if(settled){return}pipeCloseGraceTimer=setTimeout(()=>{settle({code:exitCode,signal:exitSignal},true)},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:true,windowsHide:true,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;const retainedBytes=Math.min(size,TEST_OUTPUT_MAX_BUFFER_BYTES);const buffer=Buffer.alloc(retainedBytes);if(retainedBytes>0){await handle.read(buffer,0,retainedBytes,0)}return{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();const captureRoot=await mkdtemp(path.join(tmpdir(),"pm-linked-test-output-"));try{const stdoutPath=path.join(captureRoot,"stdout.log");const stderrPath=path.join(captureRoot,"stderr.log");const[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){await Promise.all([stdoutHandle.close(),stderrHandle.close()]);throw 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};const requestTermination=createLinkedTestTerminationRequester(child,timers);let timedOut=false;timers.timedOutTimer=setTimeout(()=>{timedOut=true;void requestTermination()},timeoutMs);timers.timedOutTimer.unref?.();const{code,signal}=await childClose;clearLinkedTestTimers(timers);const[stdout,stderr]=await Promise.all([readLinkedTestCapture(stdoutPath),readLinkedTestCapture(stderrPath)]);const outputTruncated=stdout.truncated||stderr.truncated;const 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
|
-
`:"";const executionResult={stdout:`${stdout.text}${truncationNotice}`,stderr:stderr.text,exitCode:code,signal,timedOut,maxBufferExceeded:false,outputTruncated,spawnError};endLinkedTestProgress(progressContext,executionResult,startedAt,progressMode);return executionResult}finally{await rm(captureRoot,{recursive:true,force:true})}}function formatLinkedTestExecutionError(result,timeoutMs){const details=[];if(result.maxBufferExceeded){details.push(`Linked test output exceeded maxBuffer=${TEST_OUTPUT_MAX_BUFFER_BYTES} bytes. Reduce output volume or split the command.`)}if(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;const baseMessage=result.spawnError?.trim()||signalMessage||"Linked test command failed.";if(details.length===0){return baseMessage}return`${baseMessage} ${details.join(" ")}`}function hasInfraCollisionSignal(result){const combined=[result.spawnError??"",result.stderr,result.stdout].join("\n");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){if(hasInfraCollisionSignal(result)){return"infra_collision"}if(result.timedOut){return"timeout"}if(result.maxBufferExceeded){return"max_buffer"}if(result.spawnError){return"spawn_error"}if(result.signal){return"signal"}return"assertion_failure"}function createEmptyFailureCategoryCounts(){return{infra_collision:0,trust_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){if(result.status!=="failed"||!result.failure_category){continue}counts[result.failure_category]+=1}return counts}function applyEnvDirectiveStage(env,directives){for(const[key,value]of Object.entries(directives.env_set)){if(LINKED_TEST_PROTECTED_ENV_KEYS.has(key.toUpperCase())){continue}env[key]=value}for(const key of directives.env_clear){if(LINKED_TEST_PROTECTED_ENV_KEYS.has(key.toUpperCase())){continue}delete env[key]}}function applySharedHostSafeDefaults(env){if(env.PORT===void 0){env.PORT="0"}if(env.HOST===void 0){env.HOST="127.0.0.1"}if(env.PM_SHARED_HOST_SAFE===void 0){env.PM_SHARED_HOST_SAFE="1"}if(env.PLAYWRIGHT_HTML_OPEN===void 0){env.PLAYWRIGHT_HTML_OPEN="never"}if(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};const envClear=[...new Set([...runtimeDirectives.env_clear,...linkedTest.env_clear??[]])];const 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===true}}async function copyIntoSandboxIfPresent(sourcePath,targetPath,recursive=false,makeDirectory=mkdir){if(!await pathExists(sourcePath)){return}try{await withHostEnvironmentBoundary("seed_linked_test",async()=>{await makeDirectory(path.dirname(targetPath),{recursive:true});if(recursive){await cp(sourcePath,targetPath,{recursive:true,force:true});return}await cp(sourcePath,targetPath,{force:true})},{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){await copyIntoSandboxIfPresent(getSettingsPath(sourceRoots.projectPmRoot),getSettingsPath(sandboxPmPath));await copyIntoSandboxIfPresent(path.join(sourceRoots.projectPmRoot,"extensions"),path.join(sandboxPmPath,"extensions"),true);await copyIntoSandboxIfPresent(getSettingsPath(sourceRoots.globalPmRoot),getSettingsPath(sandboxGlobalPath));await copyIntoSandboxIfPresent(path.join(sourceRoots.globalPmRoot,"extensions"),path.join(sandboxGlobalPath,"extensions"),true)}async function seedLinkedTestTrackerData(sourceRoot,sandboxRoot){if(!await pathExists(sourceRoot)){return}const entries=await readdir(sourceRoot,{withFileTypes:true});for(const entry of entries){if(LINKED_TEST_TRACKER_DIRS_TO_SKIP.has(entry.name)){continue}const sourcePath=path.join(sourceRoot,entry.name);const targetPath=path.join(sandboxRoot,entry.name);if(entry.isDirectory()){await copyIntoSandboxIfPresent(sourcePath,targetPath,true);continue}if(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:true});await rm(getSettingsPath(sandboxRoot),{force:true});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:true,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:true});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:true})}catch{continue}for(const file of files){if(!file.isFile()){continue}if(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;if((execution.timedOut||execution.maxBufferExceeded)&&rawExitCode===0){return 1}return rawExitCode}function splitJsonPathSegments(fieldPath){const segments=[];const tokens=fieldPath.match(/[^.[\]]+|\[\d+\]/g)??[];for(const token of tokens){if(token.startsWith("[")&&token.endsWith("]")){const parsedIndex=Number.parseInt(token.slice(1,-1),10);if(!Number.isInteger(parsedIndex)||parsedIndex<0){return[]}segments.push(parsedIndex);continue}segments.push(token)}return segments}function readJsonPathValue(root,fieldPath){const normalizedPath=fieldPath.trim();if(normalizedPath.length===0){return{found:false,value:void 0}}const segments=splitJsonPathSegments(normalizedPath);if(segments.length===0){return{found:false,value:void 0}}let current=root;for(const segment of segments){if(typeof segment==="number"){if(!Array.isArray(current)||segment>=current.length){return{found:false,value:void 0}}current=current[segment];continue}if(typeof current!=="object"||current===null||!Object.prototype.hasOwnProperty.call(current,segment)){return{found:false,value:void 0}}current=current[segment]}return{found:true,value:current}}function parseAssertionLiteral(raw){const trimmed=raw.trim();if(trimmed==="true"){return true}if(trimmed==="false"){return false}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){if(typeof actual==="object"&&actual!==null&&typeof expected==="object"&&expected!==null){return stableValueEquals(actual,expected)}return 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??{};const jsonGteAssertions=linkedTest.assert_json_field_gte??{};const needsJsonAssertions=Object.keys(jsonEqualsAssertions).length>0||Object.keys(jsonGteAssertions).length>0;if(!needsJsonAssertions){return failures}let parsedJson;try{parsedJson=JSON.parse(stdout)}catch(error){failures.push(`stdout is not valid JSON for assert_json_field_* checks: ${error instanceof Error?error.message:String(error)}`);return 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);if(!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}if(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??[]){if(!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{const regex=new RegExp(pattern,"m");if(!regex.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}`]:[]}const EMPTY_LINKED_TEST_RUN_PATTERNS=[{code:"no_projects_matched_filters",regex:/\bNo projects matched the filters\b/i},{code:"no_test_files_found",regex:/\bNo test files found\b/i},{code:"no_tests_found",regex:/\bNo tests found\b/i},{code:"no_matching_tests",regex:/\bNo matching tests?\b/i},{code:"collected_zero_items",regex:/\bcollected 0 items?\b/i},{code:"reported_zero_passes",regex:/(?:^\s*(?:#|ℹ)?\s*tests\s+0\s*$[\s\S]*^\s*(?:#|ℹ)?\s*pass\s+0\s*$|^\s*(?:#|ℹ)?\s*pass\s+0\s*$[\s\S]*^\s*(?:#|ℹ)?\s*tests\s+0\s*$)/imu},{code:"reported_zero_tests",regex:/^\s*(?:#|ℹ)?\s*tests\s+0\s*$/imu}];const POSITIVE_LINKED_TEST_RUN_PATTERNS=[/^\s*(?:#|ℹ)?\s*tests\s+[1-9]\d*\s*$/imu,/^\s*(?:#|ℹ)?\s*pass\s+[1-9]\d*\s*$/imu,/\bTests?\s+[1-9]\d*\s+passed\b/iu,/\b[1-9]\d*\s+passed\b/iu,/\bTests:\s+(?:.*\b)?[1-9]\d*\s+passed\b/iu];function detectEmptyLinkedTestRun(stdout,stderr){const combined=`${stdout}
|
|
4
|
+
`:"";const executionResult={stdout:`${stdout.text}${truncationNotice}`,stderr:stderr.text,exitCode:code,signal,timedOut,maxBufferExceeded:false,outputTruncated,spawnError};endLinkedTestProgress(progressContext,executionResult,startedAt,progressMode);return executionResult}finally{await rm(captureRoot,{recursive:true,force:true})}}function formatLinkedTestExecutionError(result,timeoutMs){const details=[];if(result.maxBufferExceeded){details.push(`Linked test output exceeded maxBuffer=${TEST_OUTPUT_MAX_BUFFER_BYTES} bytes. Reduce output volume or split the command.`)}if(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;const baseMessage=result.spawnError?.trim()||signalMessage||"Linked test command failed.";if(details.length===0){return baseMessage}return`${baseMessage} ${details.join(" ")}`}function hasInfraCollisionSignal(result){const combined=[result.spawnError??"",result.stderr,result.stdout].join("\n");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){if(hasInfraCollisionSignal(result)){return"infra_collision"}if(result.timedOut){return"timeout"}if(result.maxBufferExceeded){return"max_buffer"}if(result.spawnError){return"spawn_error"}if(result.signal){return"signal"}return"assertion_failure"}function createEmptyFailureCategoryCounts(){return{infra_collision:0,trust_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){if(result.status!=="failed"||!result.failure_category){continue}counts[result.failure_category]+=1}return counts}function applyEnvDirectiveStage(env,directives){for(const[key,value]of Object.entries(directives.env_set)){if(LINKED_TEST_PROTECTED_ENV_KEYS.has(key.toUpperCase())){continue}env[key]=value}for(const key of directives.env_clear){if(LINKED_TEST_PROTECTED_ENV_KEYS.has(key.toUpperCase())){continue}delete env[key]}}function applySharedHostSafeDefaults(env){if(env.PORT===void 0){env.PORT="0"}if(env.HOST===void 0){env.HOST="127.0.0.1"}if(env.PM_SHARED_HOST_SAFE===void 0){env.PM_SHARED_HOST_SAFE="1"}if(env.PLAYWRIGHT_HTML_OPEN===void 0){env.PLAYWRIGHT_HTML_OPEN="never"}if(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};const envClear=[...new Set([...runtimeDirectives.env_clear,...linkedTest.env_clear??[]])];const 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===true}}async function copyIntoSandboxIfPresent(sourcePath,targetPath,recursive=false,makeDirectory=mkdir){if(!await pathExists(sourcePath)){return}try{await withHostEnvironmentBoundary("seed_linked_test",async()=>{await makeDirectory(path.dirname(targetPath),{recursive:true});if(recursive){await cp(sourcePath,targetPath,{recursive:true,force:true});return}await cp(sourcePath,targetPath,{force:true})},{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){await copyIntoSandboxIfPresent(getSettingsPath(sourceRoots.projectPmRoot),getSettingsPath(sandboxPmPath));await copyIntoSandboxIfPresent(path.join(sourceRoots.projectPmRoot,"extensions"),path.join(sandboxPmPath,"extensions"),true);await copyIntoSandboxIfPresent(getSettingsPath(sourceRoots.globalPmRoot),getSettingsPath(sandboxGlobalPath));await copyIntoSandboxIfPresent(path.join(sourceRoots.globalPmRoot,"extensions"),path.join(sandboxGlobalPath,"extensions"),true)}async function seedLinkedTestTrackerData(sourceRoot,sandboxRoot){if(!await pathExists(sourceRoot)){return}const entries=await readdir(sourceRoot,{withFileTypes:true});for(const entry of entries){if(LINKED_TEST_TRACKER_DIRS_TO_SKIP.has(entry.name)){continue}const sourcePath=path.join(sourceRoot,entry.name);const targetPath=path.join(sandboxRoot,entry.name);if(entry.isDirectory()){await copyIntoSandboxIfPresent(sourcePath,targetPath,true);continue}if(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:true});await rm(getSettingsPath(sandboxRoot),{force:true});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:true,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:true});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:true})}catch{continue}for(const file of files){if(!file.isFile()){continue}if(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;if((execution.timedOut||execution.maxBufferExceeded)&&rawExitCode===0){return 1}return rawExitCode}function parseAssertionLiteral(raw){const trimmed=raw.trim();if(trimmed==="true"){return true}if(trimmed==="false"){return false}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){if(typeof actual==="object"&&actual!==null&&typeof expected==="object"&&expected!==null){return stableValueEquals(actual,expected)}return 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??{};const jsonGteAssertions=linkedTest.assert_json_field_gte??{};const needsJsonAssertions=Object.keys(jsonEqualsAssertions).length>0||Object.keys(jsonGteAssertions).length>0;if(!needsJsonAssertions){return failures}let parsedJson;try{parsedJson=JSON.parse(stdout)}catch(error){failures.push(`stdout is not valid JSON for assert_json_field_* checks: ${error instanceof Error?error.message:String(error)}`);return 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);if(!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}if(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??[]){if(!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{const regex=new RegExp(pattern,"m");if(!regex.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}`]:[]}const EMPTY_LINKED_TEST_RUN_PATTERNS=[{code:"no_projects_matched_filters",regex:/\bNo projects matched the filters\b/i},{code:"no_test_files_found",regex:/\bNo test files found\b/i},{code:"no_tests_found",regex:/\bNo tests found\b/i},{code:"no_matching_tests",regex:/\bNo matching tests?\b/i},{code:"collected_zero_items",regex:/\bcollected 0 items?\b/i},{code:"reported_zero_passes",regex:/(?:^\s*(?:#|ℹ)?\s*tests\s+0\s*$[\s\S]*^\s*(?:#|ℹ)?\s*pass\s+0\s*$|^\s*(?:#|ℹ)?\s*pass\s+0\s*$[\s\S]*^\s*(?:#|ℹ)?\s*tests\s+0\s*$)/imu},{code:"reported_zero_tests",regex:/^\s*(?:#|ℹ)?\s*tests\s+0\s*$/imu}];const POSITIVE_LINKED_TEST_RUN_PATTERNS=[/^\s*(?:#|ℹ)?\s*tests\s+[1-9]\d*\s*$/imu,/^\s*(?:#|ℹ)?\s*pass\s+[1-9]\d*\s*$/imu,/\bTests?\s+[1-9]\d*\s+passed\b/iu,/\b[1-9]\d*\s+passed\b/iu,/\bTests:\s+(?:.*\b)?[1-9]\d*\s+passed\b/iu];function detectEmptyLinkedTestRun(stdout,stderr){const combined=`${stdout}
|
|
5
5
|
${stderr}`;for(const pattern of EMPTY_LINKED_TEST_RUN_PATTERNS){if(pattern.regex.test(combined)){return{code:pattern.code}}}return null}function hasPositiveLinkedTestRunReceipt(stdout,stderr){const combined=`${stdout}
|
|
6
6
|
${stderr}`;return POSITIVE_LINKED_TEST_RUN_PATTERNS.some(pattern=>pattern.test(combined))}function commandUsesTestNameFilter(command){const normalized=normalizeCommandForValidation(command);if(/(?:^|\s)(?:--test-name-pattern|--testnamepattern)(?:=|\s)/u.test(normalized)){return true}return 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 seedLinkedTestWorkspaceSnapshot(layout,sourceWorkspaceRoot){const excludedSegments=new Set([".agents",".git",".nyc_output",".turbo","coverage","node_modules"]);await mkdir(path.dirname(layout.workspaceSnapshotRoot),{recursive:true});await cp(sourceWorkspaceRoot,layout.workspaceSnapshotRoot,{recursive:true,force:true,filter(source){const relative=path.relative(sourceWorkspaceRoot,source);if(!relative){return true}return!relative.split(path.sep).some(segment=>excludedSegments.has(segment))}});const sourceNodeModules=path.join(sourceWorkspaceRoot,"node_modules");if(await pathExists(sourceNodeModules)){await symlink(sourceNodeModules,path.join(layout.workspaceSnapshotRoot,"node_modules"),process.platform==="win32"?"junction":"dir")}}async function bindSnapshotTracker(snapshotRoot,sandboxPmPath){const agentsRoot=path.join(snapshotRoot,".agents");const target=path.join(agentsRoot,"pm");await mkdir(agentsRoot,{recursive:true});await rm(target,{recursive:true,force:true});await symlink(sandboxPmPath,target,process.platform==="win32"?"junction":"dir")}async function initializeLinkedTestSandboxes(layout,initialize=runInit,includeTrackerData=true){const initOptions={defaults:true,agentGuidance:"skip"};await initialize(void 0,{path:layout.schemaProjectPmPath},initOptions);await initialize(void 0,{path:layout.schemaGlobalPmPath},initOptions);if(!includeTrackerData){return}await initialize(void 0,{path:layout.trackerProjectPmPath},initOptions);await initialize(void 0,{path:layout.trackerGlobalPmPath},initOptions)}async function seedLinkedTestSandboxesFromSource(layout,sourceRoots,includeTrackerData){if(!sourceRoots){return}await seedLinkedTestSandbox(layout.schemaProjectPmPath,layout.schemaGlobalPmPath,sourceRoots);if(!includeTrackerData){return}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??"";const isPmTrackerReadCommand=command.length>0&&commandInvokesPmTrackerReadCommand(command);const requestedPmContextMode=options?.autoPmContext===true&&isPmTrackerReadCommand?"auto":resolveLinkedTestRequestedContextMode(linkedTest,runLevelPmContextMode,options?.overrideLinkedPmContext===true);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";const selectedSandboxProjectPmPath=trackerMode?params.layout.trackerProjectPmPath:params.layout.schemaProjectPmPath;const selectedSandboxGlobalPmPath=trackerMode?params.layout.trackerGlobalPmPath:params.layout.schemaGlobalPmPath;const selectedSandboxProjectItemCount=trackerMode?params.counts.trackerProjectItemCount:params.counts.schemaProjectItemCount;const selectedSandboxGlobalItemCount=trackerMode?params.counts.trackerGlobalItemCount:params.counts.schemaGlobalItemCount;const workingDirectory=params.workspaceContextMode==="snapshot"?params.layout.workspaceSnapshotRoot:params.workspaceContextMode==="isolated"?params.layout.workspaceIsolatedRoot:params.sourceWorkspaceRoot;const 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:Boolean(params.sourceRoots),global_extensions_seeded:Boolean(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;const command=typeof params.linkedTest.command==="string"&¶ms.linkedTest.command.length>0?params.linkedTest.command:void 0;const isPmCommand=command?commandInvokesPmCli(command):false;const isPmTrackerReadCommand=isPmCommand&&command?commandInvokesPmTrackerReadCommand(command):false;const autoPmContextApplied=params.options?.autoPmContext===true&&isPmTrackerReadCommand;const requestedPmContextMode=autoPmContextApplied?"auto":resolveLinkedTestRequestedContextMode(params.linkedTest,params.runLevelPmContextMode,params.options?.overrideLinkedPmContext===true);const effectivePmContextMode=resolveLinkedTestEffectiveContextMode(requestedPmContextMode,isPmTrackerReadCommand);const selectedSandboxProjectPmPath=effectivePmContextMode==="tracker"?params.layout.trackerProjectPmPath:params.layout.schemaProjectPmPath;if(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===true;const policyEnabled=params.options?.untrustedLinkedTestsPolicyEnabled===true;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 resolveLinkedTestPreflightResult(params){const safetyFailure=resolveLinkedTestSafetyPreflightResult(params);if(safetyFailure)return safetyFailure;const trustFailure=resolveLinkedTestTrustPreflightResult(params);if(trustFailure)return trustFailure;const failOnMismatchByDefault=params.executionContext.pm_context_mode==="schema"&¶ms.executionContext.is_pm_tracker_read_command&¶ms.executionContext.mismatch_detected;const failOnMismatchByFlag=params.options?.failOnContextMismatch===true&¶ms.executionContext.is_pm_command&¶ms.executionContext.mismatch_detected;if(failOnMismatchByDefault||failOnMismatchByFlag){const mismatchHint=buildPmContextMismatchHint({executionContext:params.executionContext,runLevelPmContextMode:params.runLevelPmContextMode,linkedOverridePmContextMode:params.linkedOverridePmContextMode});const mismatchPrefix=params.options?.checkContext===true?"Linked test preflight PM context mismatch detected":"Linked test PM context mismatch detected";return buildLinkedTestAssertionFailureResult(params.linkedTest,params.executionContext,`${mismatchPrefix} (source_project_items=${params.executionContext.source_project_item_count}, sandbox_project_items=${params.executionContext.sandbox_project_item_count}).${mismatchHint}`)}if(params.options?.requireAssertionsForPm===true&¶ms.executionContext.is_pm_command&&!hasLinkedTestAssertions(params.linkedTest)){return buildLinkedTestAssertionFailureResult(params.linkedTest,params.executionContext,"Linked PM command requires assertions when --require-assertions-for-pm is enabled.")}return null}function buildLinkedTestExecutionEnv(params){const effectiveDirectives=resolveEffectiveLinkedTestDirectives(params.runtimeDirectives,params.linkedTest);const executionEnv={...process.env,SENTRY_ENVIRONMENT:"test"};applyEnvDirectiveStage(executionEnv,params.runtimeDirectives);applyEnvDirectiveStage(executionEnv,{env_set:params.linkedTest.env_set??{},env_clear:params.linkedTest.env_clear??[]});if(effectiveDirectives.shared_host_safe){applySharedHostSafeDefaults(executionEnv)}executionEnv.FORCE_COLOR="0";executionEnv.PM_PATH=params.executionContext.sandbox_project_pm_path;executionEnv.PM_GLOBAL_PATH=params.executionContext.sandbox_global_pm_path;if(params.executionContext.workspace_context_mode==="isolated"){delete executionEnv.PM_SOURCE_WORKSPACE_ROOT;delete executionEnv.PM_SOURCE_PM_PATH;delete executionEnv[SOURCE_CONTEXT_ACCESS_ENV]}else{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"}return executionEnv}function buildLinkedTestPassedResult(linkedTest,executionContext,execution){return{command:linkedTest.command,path:linkedTest.path,status:"passed",exit_code:0,execution_context:executionContext,stdout:execution.stdout,stderr:execution.stderr}}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){if(params.options?.failOnEmptyTestRun===true||commandUsesTestNameFilter(params.linkedTest.command??"")){const emptyRunSignal=detectEmptyLinkedTestRun(params.execution.stdout,params.execution.stderr);if(emptyRunSignal){return buildLinkedTestEmptyRunResult({...params,emptyRunSignal})}if(commandUsesTestNameFilter(params.linkedTest.command??"")&&!hasPositiveLinkedTestRunReceipt(params.execution.stdout,params.execution.stderr)){return buildLinkedTestEmptyRunResult({...params,emptyRunSignal:{code:"missing_positive_execution_receipt"}})}}const assertionFailures=evaluateLinkedTestAssertions(params.linkedTest,params.execution.stdout,params.execution.stderr);if(assertionFailures.length>0){return buildLinkedTestAssertionFailureResult(params.linkedTest,params.executionContext,`Linked test assertion(s) failed: ${assertionFailures.join("; ")}`,params.execution)}return buildLinkedTestPassedResult(params.linkedTest,params.executionContext,params.execution)}async function runSingleLinkedTest(params){const command=params.linkedTest.command??"";const timeoutMs=(params.linkedTest.timeout_seconds??params.defaultTimeoutSeconds??120)*1e3;const execution=await runLinkedTestCommand(command,timeoutMs,buildLinkedTestExecutionEnv(params),{index:params.index+1,total:params.total,timeoutMs,command},params.progressMode,params.executionContext.working_directory);const passed=execution.exitCode===0&&!execution.timedOut&&!execution.maxBufferExceeded;return passed?buildLinkedTestPassedExecutionResult({...params,execution}):buildLinkedTestCommandFailureResult({...params,execution,timeoutMs})}async function runLinkedTests(tests,defaultTimeoutSeconds,options){const results=[];const sandboxRoot=await mkdtemp(path.join(tmpdir(),"pm-linked-test-"));const layout=createLinkedTestSandboxLayout(sandboxRoot);const runLevelPmContextMode=parsePmContextMode(options?.pmContext);const runLevelWorkspaceContextMode=parseWorkspaceContextMode(options?.workspaceContext);const progressMode=options?.progress===true?"always":"auto";const runtimeDirectives=resolveRuntimeDirectives(options?.envSet,options?.envClear,options?.sharedHostSafe);const sourceRoots=options?.sourceRoots;const sourceWorkspaceRoot=resolveLinkedTestSourceWorkspaceRoot();const includeTrackerData=linkedTestsRequireTrackerData(tests,runLevelPmContextMode,options);const includeWorkspaceSnapshot=linkedTestsRequireWorkspaceSnapshot(tests,runLevelWorkspaceContextMode,options?.overrideLinkedWorkspaceContext===true);const currentSourceRef=await resolveLinkedTestSourceRef(sourceWorkspaceRoot);try{await initializeLinkedTestSandboxes(layout,runInit,includeTrackerData);await mkdir(layout.workspaceIsolatedRoot,{recursive:true});await seedLinkedTestSandboxesFromSource(layout,sourceRoots,includeTrackerData);if(includeWorkspaceSnapshot){await seedLinkedTestWorkspaceSnapshot(layout,sourceWorkspaceRoot)}const counts=await countLinkedTestSandboxItems(layout,sourceRoots);for(let index=0;index<tests.length;index+=1){const linkedTest=tests[index];const workspaceContextMode=resolveLinkedTestWorkspaceContextMode(linkedTest,runLevelWorkspaceContextMode,options?.overrideLinkedWorkspaceContext===true);const commandContext=await resolveLinkedTestCommandContext({linkedTest,layout,counts,sourceRoots,runLevelPmContextMode,options,requestedWorkspaceContextMode:runLevelWorkspaceContextMode,workspaceContextMode,currentSourceRef,sourceWorkspaceRoot});const 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:true,force:true,maxRetries:3,retryDelay:100})}return results}async function readLinkedTestItem(params){const{id,pmRoot,settings,typeToFolder}=params;const 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:false,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){if(!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);const tests=current.filter((entry,index)=>{if(indexSet.has(index+1))return false;return!removals.some(selector=>{if(selector.kind==="command")return entry.command===selector.value;if(selector.kind==="path")return entry.path===selector.value;return 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.envSet?.length??0)>0,(options.envClear?.length??0)>0,options.sharedHostSafe===true,options.pmContext!==void 0,options.overrideLinkedPmContext===true,options.failOnContextMismatch===true,options.failOnSkipped===true,options.failOnEmptyTestRun===true,options.requireAssertionsForPm===true,options.checkContext===true,options.autoPmContext===true,options.workspaceContext!==void 0,options.overrideLinkedWorkspaceContext===true,options.allowUntrustedLinkedTests===true].some(Boolean)}function assertTestRunFlagUsage(options){if(hasTestRuntimeDirectiveFlags(options)&&options.run!==true){throw new PmCliError("--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===true&&options.run===true){throw new PmCliError("--acknowledge-linked-tests records trust without executing commands; run it separately before --run",EXIT_CODE.USAGE)}const hasSelectorFlags=options.match!==void 0||options.onlyIndex!==void 0||options.onlyLast===true;if(hasSelectorFlags&&options.run!==true){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:true,mutate(document){const previous=document.metadata.tests??[];const mutation=applyLinkedTestMutations(previous,adds,removes,removeIndexes);removed=mutation.removed;if((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`]})}document.metadata.tests=mutation.tests;return{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){assertTestRunFlagUsage(options);const selectorInput={match:options.match,onlyIndex:options.onlyIndex===void 0?void 0:parseOnlyIndexValue(options.onlyIndex),onlyLast:options.onlyLast};const runSelection=options.run===true?resolveLinkedTestRunSelection(tests,selectorInput):void 0;const defaultTimeoutSeconds=options.timeout===void 0?void 0:parseOptionalNumber(options.timeout,"timeout");if(defaultTimeoutSeconds!==void 0&&defaultTimeoutSeconds<=0){throw new PmCliError("Test timeout must be a positive number",EXIT_CODE.USAGE)}return{defaultTimeoutSeconds,pmContextMode:parsePmContextMode(options.pmContext),runSelection,testsToRun:runSelection?.selected??tests}}async function executeSelectedLinkedTests(params){const{options,runOptions,pmRoot}=params;if(options.run!==true){return[]}return 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,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(",")}`)}if(options.run===true&&options.checkContext===true){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);const parsedAttempt=Number.parseInt(process.env.PM_BACKGROUND_TEST_RUN_ATTEMPT?.trim()??"",10);const resumedFrom=process.env.PM_BACKGROUND_TEST_RUN_RESUMED_FROM?.trim();const recordedAt=nowIso();const 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?true: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!==true||!runStartedAt){return void 0}const entry=buildTrackedTestRunSummary({options,runStartedAt,runResults,failOnSkippedTriggered});if(settings.testing.record_results_to_items!==true){return entry}try{await appendTrackedTestRunSummary({pmRoot,settings,itemId,author:resolveAuthor(options.author,settings.author_default),message:`Track test run summary (${entry.run_id})`,entry});return entry}catch(error){warnings.push(`test_result_tracking_failed:${itemId}:${error instanceof Error?error.message:String(error)}`);return entry}}function buildTestMeasurementProjection(options,runs,recordedRun){let metricBelow;if(options.metricBelow!==void 0){const separator=options.metricBelow.indexOf("=");const name=options.metricBelow.slice(0,separator).trim();const thresholdText=options.metricBelow.slice(separator+1).trim();const 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!==true)return void 0;return acknowledgeLinkedTests(pmRoot,tests,nowIso())}async function runTest(id,options,global2){const stdinResolver=createStdinTokenResolver();const pmRoot=resolvePmRoot(process.cwd(),global2.path);await assertInitializedTracker(pmRoot);const settings=await readSettings(pmRoot);if((options.measure?.length??0)>0&&options.run!==true){throw new PmCliError("--measure requires --run",EXIT_CODE.USAGE)}parseTestRunMeasurements(options.measure,"validation");buildTestMeasurementProjection(options,[],void 0);const typeRegistry=resolveItemTypeRegistry(settings,getActiveExtensionRegistrations());const resolvedAdds=await stdinResolver.resolveList(options.add,"--add");const resolvedAddJsons=await stdinResolver.resolveList(options.addJson,"--add-json");const resolvedRemoves=await stdinResolver.resolveList(options.remove,"--remove");const parsedAdds=[...parseAddEntries(resolvedAdds),...parseAddJsonEntries(resolvedAddJsons)];const adds=await attachLinkedTestMutationProvenance(parsedAdds,resolveAuthor(options.author,settings.author_default),nowIso())??[];const removes=parseRemoveEntries(resolvedRemoves);const removeIndexes=parseRemoveIndexes(options.removeIndex);const item=await resolveLinkedTestItem({id,options,pmRoot,settings,typeToFolder:typeRegistry.type_to_folder,adds,removes,removeIndexes});const runOptions=resolveTestRunOptions(options,item.tests);const trustAcknowledgement=await maybeAcknowledgeLinkedTests(options,pmRoot,item.tests);const runStartedAt=options.run===true?nowIso():void 0;const runResults=await executeSelectedLinkedTests({options,runOptions,pmRoot,untrustedLinkedTestsPolicyEnabled:settings.testing.allow_untrusted_linked_tests});const failureCategories=countFailureCategories(runResults);const failOnSkippedTriggered=options.run===true&&options.failOnSkipped===true&&runResults.some(entry=>entry.status==="skipped");const warnings=buildTestWarnings(options,runOptions.runSelection,runResults);if(trustAcknowledgement){warnings.push(`linked_test_trust_acknowledged:${trustAcknowledgement.acknowledged}`)}const recordedRun=await recordTestRunSummary({options,pmRoot,settings,itemId:item.itemId,runStartedAt,runResults,failOnSkippedTriggered,warnings});const runs=recordedRun?buildTrackedTestRunHistory([...item.testRuns,recordedRun]):item.testRuns;const measurementProjection=buildTestMeasurementProjection(options,runs,recordedRun);return{ok:runResults.every(entry=>entry.status!=="failed")&&failOnSkippedTriggered!==true,id:item.itemId,tests:item.tests,run_results:runResults,failure_categories:failureCategories,selection:runOptions.runSelection?{selector:runOptions.runSelection.selector,requested:runOptions.runSelection.requested,selected_indexes:runOptions.runSelection.selected_indexes,selected_count:runOptions.runSelection.selected_count,skipped_count:runOptions.runSelection.skipped_count}:void 0,fail_on_skipped_triggered:failOnSkippedTriggered?true:void 0,warnings:warnings.length>0?warnings:void 0,changed:item.changed,count:item.tests.length,...item.removed>0?{removed:item.removed}:{},...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};
|
|
7
7
|
//# sourceMappingURL=execution.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module sdk/test/json-path
|
|
3
|
+
* Own-data lookup for linked-test assertions.
|
|
4
|
+
*/
|
|
5
|
+
/** Parse the linked-test dotted and bracket-index path grammar. */
|
|
6
|
+
export declare function splitJsonPathSegments(fieldPath: string): Array<string | number>;
|
|
7
|
+
/** Read own JSON data properties without invoking getters or following prototypes. */
|
|
8
|
+
export declare function readJsonPathValue(root: unknown, fieldPath: string): {
|
|
9
|
+
found: boolean;
|
|
10
|
+
value: unknown;
|
|
11
|
+
};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
!(function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=new e.Error().stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="3570c976-1b25-501e-b51a-5ecfd1b70c15")}catch(e2){}})();function splitJsonPathSegments(fieldPath){if(!/^(?:[^.[\]]+|\[\d+\])(?:\.[^.[\]]+|\[\d+\])*$/u.test(fieldPath)){return[]}const segments=[];const tokens=fieldPath.matchAll(/[^.[\]]+|\[\d+\]/g);for(const[token]of tokens){if(token.startsWith("[")&&token.endsWith("]")){const parsedIndex=Number.parseInt(token.slice(1,-1),10);if(!Number.isSafeInteger(parsedIndex)){return[]}segments.push(parsedIndex);continue}segments.push(token)}return segments}function readJsonPathValue(root,fieldPath){const normalizedPath=fieldPath.trim();if(normalizedPath.length===0){return{found:false,value:void 0}}const segments=splitJsonPathSegments(normalizedPath);if(segments.length===0){return{found:false,value:void 0}}let current=root;for(const segment of segments){if(typeof current!=="object"||current===null){return{found:false,value:void 0}}if(typeof segment==="number"&&(!Array.isArray(current)||segment>=current.length)){return{found:false,value:void 0}}const property=Object.getOwnPropertyDescriptor(current,segment);if(!property||!Object.hasOwn(property,"value")){return{found:false,value:void 0}}current=property.value}return{found:true,value:current}}export{readJsonPathValue,splitJsonPathSegments};
|
|
2
|
+
//# sourceMappingURL=json-path.js.map
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Build generations and registry acceptance
|
|
2
|
+
|
|
3
|
+
Tracked by [pm-cxc4jc](../.agents/pm/tasks/pm-cxc4jc.toon) and
|
|
4
|
+
[pm-ygli86](../.agents/pm/tasks/pm-ygli86.toon).
|
|
5
|
+
|
|
6
|
+
## Complete build ownership
|
|
7
|
+
|
|
8
|
+
`pnpm build` owns one checkout-local lease from build-cache preparation through
|
|
9
|
+
TypeScript compilation, bundling, and finalization. The sandboxed test runner
|
|
10
|
+
uses the same lease for the entire test and coverage session. A second producer
|
|
11
|
+
or consumer waits up to twenty minutes instead of reading partially rewritten
|
|
12
|
+
JavaScript. The lease is under `.cache/build-lease`; its owner receipt records a
|
|
13
|
+
process identifier and an opaque inheritance token.
|
|
14
|
+
|
|
15
|
+
Nested read-only test runners inherit the verified live-owner receipt. Starting
|
|
16
|
+
a build inside a test session is refused: rebuilding shared artifacts while
|
|
17
|
+
other test workers consume them would violate the generation boundary. Fixtures
|
|
18
|
+
that exercise the build orchestrator must use independent temporary workspaces.
|
|
19
|
+
|
|
20
|
+
The lease never expires based on its age and is never automatically stolen.
|
|
21
|
+
After an interrupted process, inspect the owner and its children before removing
|
|
22
|
+
an abandoned lease. A failed build leaves `.cache/build-incomplete`; prebuilt
|
|
23
|
+
tests refuse that generation until a successful `pnpm build` clears the marker.
|
|
24
|
+
Direct invocations of individual build stages or arbitrary reads of `dist` do
|
|
25
|
+
not participate in this protocol. Use the build and test entrypoints for
|
|
26
|
+
concurrent validation.
|
|
27
|
+
|
|
28
|
+
## Published candidate and control
|
|
29
|
+
|
|
30
|
+
The Release workflow publishes npm artifacts once, verifies executors, and
|
|
31
|
+
selects the closest older calendar version from the public registry inventory.
|
|
32
|
+
It then runs the same eleven-step agent session against the control first and
|
|
33
|
+
the candidate second in independent temporary installation roots.
|
|
34
|
+
|
|
35
|
+
The matrix covers Linux, macOS, and Windows with npm local, npm global, and Bun
|
|
36
|
+
local installations. Each session initializes, orients, creates, claims,
|
|
37
|
+
annotates, links evidence, closes, validates, reads the closed item and context,
|
|
38
|
+
and verifies a complete corpus read through the installed public SDK. Each
|
|
39
|
+
subprocess has a two-minute deadline; command output has per-step bounds and the
|
|
40
|
+
report records estimated token cost. Telemetry and external error reporting are
|
|
41
|
+
disabled in these acceptance workspaces.
|
|
42
|
+
|
|
43
|
+
GitHub release advertisement is a separate job that depends on every matrix
|
|
44
|
+
leg succeeding. A failed control identifies a harness or environment problem;
|
|
45
|
+
a passing control followed by a failing candidate identifies a candidate
|
|
46
|
+
regression to investigate. Neither failure permits advertisement. Reports are
|
|
47
|
+
retained as workflow artifacts; failures also name the manager and failing step.
|
|
48
|
+
Existing npm versions are never republished during exact-tag recovery.
|
|
49
|
+
|
|
50
|
+
The prepublication artifact gate accepts both npm's array report and npm 12's
|
|
51
|
+
package-keyed report. It requires exactly one artifact, validates keyed package
|
|
52
|
+
identity, and applies the same file, source-map, and size limits to either shape.
|
|
53
|
+
Exact-tag recovery preserves the reviewed control selector before checking out
|
|
54
|
+
older unpublished source. The automatic-release parent budgets the complete
|
|
55
|
+
publish, acceptance, and advertisement sequence plus its preparation work.
|
|
56
|
+
|
|
57
|
+
Run the same comparison locally:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
npm run release:verify-installed-agent -- --version 2026.9.16 --previous-version 2026.9.15 --manager npm --global --json
|
|
61
|
+
npm run release:verify-installed-agent -- --version 2026.9.16 --previous-version 2026.9.15 --manager bun --json
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Use `npm run` on Windows so the verifier receives the actual npm JavaScript
|
|
65
|
+
entrypoint. CLI and SDK execution use the installed package and the selected
|
|
66
|
+
runtime rather than assuming shell shim names.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# MCP Option Conformance
|
|
2
|
+
|
|
3
|
+
Tracked by [pm-v0s2rm](../.agents/pm/chores/pm-v0s2rm.toon).
|
|
4
|
+
|
|
5
|
+
An integration test can pass while an undeclared option is ignored. That leaves
|
|
6
|
+
the test asserting a behavior it never exercised. The repository therefore
|
|
7
|
+
treats emitted MCP unknown-option diagnostics as test failures, while the
|
|
8
|
+
production server retains its advisory warning behavior for clients.
|
|
9
|
+
|
|
10
|
+
Every run using [vitest.config.ts](../vitest.config.ts) installs
|
|
11
|
+
[McpContractReporter](../scripts/mcp-contract-reporter.mts). The reporter records
|
|
12
|
+
the emitting file, full test name, option, tool, and action, and rejects the run
|
|
13
|
+
after execution even when all assertions pass. Unattributed warnings also fail.
|
|
14
|
+
Diagnostics retain option names, never option values.
|
|
15
|
+
|
|
16
|
+
The sandboxed test runner also retains the reporter when command-line reporter
|
|
17
|
+
selection overrides the config, including CI coverage shards that emit blobs.
|
|
18
|
+
|
|
19
|
+
Intentional negative tests must have an exact entry in
|
|
20
|
+
`MCP_OPTION_WARNING_ALLOWLIST`, including a reason. A matching file or test alone
|
|
21
|
+
does not exempt other warning identities. The current exemption verifies that
|
|
22
|
+
mutation-shaped options cannot mutate a read tool. New integration behavior
|
|
23
|
+
should use declared parameters and assert the actual resulting state.
|
|
24
|
+
|
|
25
|
+
The [gate registry](../scripts/release/gate-registry.json) registers this check
|
|
26
|
+
as `mcp-option-contracts`. Its
|
|
27
|
+
[negative control](../tests/unit/scripts/mcp-contract-reporter.spec.ts) starts a
|
|
28
|
+
real Vitest child process whose assertion passes but whose undeclared-option
|
|
29
|
+
diagnostic must produce exit status 1. Run the gate and its integration consumers
|
|
30
|
+
with the sandboxed repository runner:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
node scripts/run-tests.mjs test -- tests/unit/scripts/mcp-contract-reporter.spec.ts tests/integration/mcp-dynamic-package-actions.spec.ts tests/unit/mcp/mcp-server-nested-options.spec.ts
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Published MCP acceptance also isolates project and global configuration, tracked
|
|
37
|
+
by [pm-prmt1y](../.agents/pm/issues/pm-prmt1y.toon). The npm, npx, and bunx checks
|
|
38
|
+
use temporary tracker roots for both stdio and HTTP servers. This prevents a
|
|
39
|
+
caller's installed skills from changing discovery pagination and hiding the
|
|
40
|
+
bundled skills being verified. Protocol and skill-content assertions remain
|
|
41
|
+
mandatory; acceptance processes disable production telemetry and Sentry.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Mutation Integrity
|
|
2
2
|
|
|
3
|
-
Tracker references: [pm-h90s](../.agents/pm/issues/pm-h90s.toon), [pm-pim7](../.agents/pm/features/pm-pim7.toon), [pm-w8q4](../.agents/pm/features/pm-w8q4.toon), [pm-9yfo](../.agents/pm/tasks/pm-9yfo.toon)
|
|
3
|
+
Tracker references: [pm-h90s](../.agents/pm/issues/pm-h90s.toon), [pm-pim7](../.agents/pm/features/pm-pim7.toon), [pm-w8q4](../.agents/pm/features/pm-w8q4.toon), [pm-9yfo](../.agents/pm/tasks/pm-9yfo.toon), [pm-wwooxx](../.agents/pm/issues/pm-wwooxx.toon), [pm-3kk6](../.agents/pm/issues/pm-3kk6.toon)
|
|
4
4
|
|
|
5
5
|
`pm` treats mutation provenance and content safety as shared SDK policy. CLI,
|
|
6
6
|
MCP, and package hosts can therefore enforce the same rules without duplicating
|
|
@@ -70,8 +70,30 @@ whether a blocking policy was explicitly overridden. `scanMutationSecrets` is
|
|
|
70
70
|
available separately for package-specific preflight UIs. Cyclic inputs are
|
|
71
71
|
safe; scanner failures fail open with `secret_guard_scan_failed_open`.
|
|
72
72
|
|
|
73
|
-
The
|
|
74
|
-
|
|
73
|
+
The SDK guard and repository scanner consume one inventory in
|
|
74
|
+
`src/core/shared/secret-rules.ts`. It covers GitHub, npm, Slack, AWS access
|
|
75
|
+
keys (including temporary keys), Google, Anthropic, OpenAI, GitLab, Sentry,
|
|
76
|
+
private keys, JWT-shaped tokens, npm authentication assignments, password
|
|
77
|
+
assignments, inline sshpass passwords, private IPv4 addresses, private SSH
|
|
78
|
+
hosts, and absolute home-directory paths. Every repository rule also runs
|
|
79
|
+
against mutation string leaves in `advise` and `block` modes.
|
|
80
|
+
|
|
81
|
+
Two repository fixture exemptions apply only to `tests/` and `examples/`:
|
|
82
|
+
absolute home paths and user-at-private-host matches. They never exempt
|
|
83
|
+
mutation content. The inventory explicitly records one mutation-only heuristic:
|
|
84
|
+
high-entropy assignments. Source identifiers can resemble generic assignments,
|
|
85
|
+
so that heuristic does not run over repository source text. No rule is silently
|
|
86
|
+
gate-only. Repository diagnostics retain hyphenated names; SDK findings retain
|
|
87
|
+
underscore names. Adding a pattern therefore changes both consumers together.
|
|
88
|
+
|
|
89
|
+
A private address or host path can be legitimate project context. The default
|
|
90
|
+
remains advisory; a workspace selecting `block` must replace those literals
|
|
91
|
+
with references or explicitly review a force override. CLI routing paths and
|
|
92
|
+
payload content are distinct: pass only content to package-owned preflight.
|
|
93
|
+
|
|
94
|
+
Nested settings reads use own properties. Writes reject empty and prototype
|
|
95
|
+
path segments before changing any object, create local containers for inherited
|
|
96
|
+
values, and define own data properties without invoking inherited setters. Findings expose only the stable rule
|
|
75
97
|
name and object path. Matched credential text is never returned, logged, or
|
|
76
98
|
included in a recovery bundle.
|
|
77
99
|
|
|
@@ -87,7 +109,7 @@ list.
|
|
|
87
109
|
import { PM_MUTATION_ACTION_CONTRACTS } from "@unbrained/pm-cli/sdk";
|
|
88
110
|
|
|
89
111
|
const itemHistoryActions = PM_MUTATION_ACTION_CONTRACTS.filter(
|
|
90
|
-
(contract) => contract.
|
|
112
|
+
(contract) => contract.history_scope === "item",
|
|
91
113
|
);
|
|
92
114
|
```
|
|
93
115
|
|
|
@@ -98,6 +120,16 @@ operation and author before the repository can treat that invariant as fully
|
|
|
98
120
|
enforced. Workspace actions remain explicitly classified while that executable
|
|
99
121
|
gate is completed.
|
|
100
122
|
|
|
123
|
+
## Linked-Test JSON Assertions
|
|
124
|
+
|
|
125
|
+
Tracked by [pm-czr31p](../.agents/pm/issues/pm-czr31p.toon).
|
|
126
|
+
|
|
127
|
+
JSON assertions accept dotted fields and non-negative safe integer indexes,
|
|
128
|
+
such as `result.items[0].count` or `[0][1].count`. The whole path must match:
|
|
129
|
+
empty segments, empty brackets, negative indexes, and omitted separators fail
|
|
130
|
+
the assertion instead of silently selecting a different field. Traversal reads
|
|
131
|
+
only own data properties and never invokes getters or follows prototypes.
|
|
132
|
+
|
|
101
133
|
## Unknown-Author Disposition
|
|
102
134
|
|
|
103
135
|
Use `scanHistoryAuthorAttribution` to obtain exact item and one-based line
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# Next-work selection budgets
|
|
2
|
+
|
|
3
|
+
Tracked by [pm-v53h9j](../.agents/pm/issues/pm-v53h9j.toon) and
|
|
4
|
+
[pm-lixy](../.agents/pm/tasks/pm-lixy.toon).
|
|
5
|
+
|
|
6
|
+
`pm next` distinguishes two costs:
|
|
7
|
+
|
|
8
|
+
- `--token-budget` bounds the `recommended` and `ready` fields together. It
|
|
9
|
+
retains a prefix of the ranking, dropping alternatives before the
|
|
10
|
+
recommendation. An oversized first candidate is omitted explicitly rather
|
|
11
|
+
than silently replaced by a lower-ranked task.
|
|
12
|
+
- `--output-budget` bounds the complete response, including companion queues,
|
|
13
|
+
explanations, receipts, and recovery instructions. Use this control when
|
|
14
|
+
reserving space in an agent's context window.
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pm next --token-budget 500 --output-budget 2000
|
|
18
|
+
pm next --for execute --token-budget 1200
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
The same options are available as `tokenBudget` and `outputBudget` on
|
|
22
|
+
`PmClient.next()` and through the MCP next operation. Selection accounting uses
|
|
23
|
+
the greater of the built-in JSON and TOON rendered UTF-8 byte costs divided by
|
|
24
|
+
four, rounded up. It includes both selection field names. This is a
|
|
25
|
+
deterministic estimate, not a model-specific tokenizer count.
|
|
26
|
+
|
|
27
|
+
SDK callers that request a whole-response budget must handle its omission union
|
|
28
|
+
before using the normal `NextResult` fields:
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
import { PmClient } from "@unbrained/pm-cli/sdk";
|
|
32
|
+
|
|
33
|
+
const result = await new PmClient().next({ tokenBudget: 500, outputBudget: 2000 });
|
|
34
|
+
if ("output_budget_exceeded" in result) {
|
|
35
|
+
console.log(result.output_budget_exceeded.restore_with);
|
|
36
|
+
} else {
|
|
37
|
+
console.log(result.recommended?.id, result.truncation?.ready_budget);
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
`truncation.ready_budget` identifies the measured scope, requested budget,
|
|
42
|
+
actual estimate, omitted row count, recommendation omission, feasibility, and
|
|
43
|
+
the budget needed to restore the original row-limited selection. A budget too
|
|
44
|
+
small even for `{ recommended: null, ready: [] }` reports
|
|
45
|
+
`within_budget: false`. Empty output in this case does not mean no work exists:
|
|
46
|
+
`summary.ready` still reports the complete ready population, and recovery names
|
|
47
|
+
the larger selection budget.
|
|
48
|
+
|
|
49
|
+
Intent reads (`--for execute`) already include a complete-response budget
|
|
50
|
+
receipt. They add the selection receipt only when selection rows were omitted
|
|
51
|
+
or the selection ceiling was infeasible, avoiding redundant context overhead.
|
|
52
|
+
|
|
53
|
+
The independent row cap still applies. Raising a selection budget does not
|
|
54
|
+
raise `--limit`, and budget omission counts exclude rows already withheld by
|
|
55
|
+
that cap. `truncation.ready_total` reports the complete ready population when
|
|
56
|
+
either the row cap or the selection budget removes rows, including when a
|
|
57
|
+
generous budget fits every row allowed by the cap.
|
|
58
|
+
Companion decisions, gates, containers, and blockers are outside the
|
|
59
|
+
selection budget; their output is covered by the complete-response budget.
|
|
60
|
+
Selection accounting precedes whole-response projection. If the latter removes
|
|
61
|
+
more data, its outer output-budget receipt describes that additional omission.
|
|
62
|
+
Without an explicit selection budget, the established row-limited behavior
|
|
63
|
+
remains in place. Generic `packing` diagnostics describe the candidate
|
|
64
|
+
optimizer; `truncation.ready_budget` describes the emitted executable answer.
|
|
65
|
+
|
|
66
|
+
Ranking explanations and usage-feedback inclusion refer to the delivered
|
|
67
|
+
recommendation and alternatives. They do not mark hidden ranked candidates as
|
|
68
|
+
included merely because the generic optimizer considered them affordable.
|
|
69
|
+
|
|
70
|
+
## Reproducible quality checks
|
|
71
|
+
|
|
72
|
+
The committed golden corpus includes scratch, hierarchy, continuity,
|
|
73
|
+
served-then-used, and medium backlog cases at different selection budgets.
|
|
74
|
+
The gate independently measures the selection instead of trusting its receipt.
|
|
75
|
+
Negative controls reject missing receipts, ignored budgets, false feasibility
|
|
76
|
+
claims, and budgets that fit compact JSON but fail the actual rendered cost.
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
node scripts/release/context-eval-gate.mjs
|
|
80
|
+
node scripts/release/context-eval-gate.mjs \
|
|
81
|
+
--corpus tests/context-eval/scale-scenarios.json \
|
|
82
|
+
--baseline tests/context-eval/scale-baseline.json
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
The required CI smoke check also runs the scale corpus, using the shared SDK-backed shape generator: a 10,000-item
|
|
86
|
+
representative workspace and a 100,000-item scratch-shaped workspace, each
|
|
87
|
+
with a current claimed anchor. Each run creates and removes isolated temporary
|
|
88
|
+
trackers. The public SDK evaluation runner respects a scenario's explicit
|
|
89
|
+
`outputBudget`; absent that option, it retains exhaustive diagnostic reads.
|
|
90
|
+
|
|
91
|
+
Review changed judgments and scenario metrics before intentionally refreshing
|
|
92
|
+
either baseline with the corresponding command plus `--update`. A refreshed
|
|
93
|
+
baseline does not waive the corpus's absolute quality thresholds.
|
|
@@ -19,7 +19,11 @@ and canonical TOON tables are tracked by
|
|
|
19
19
|
is tracked by [pm-8nev0o](../.agents/pm/issues/pm-8nev0o.toon) and
|
|
20
20
|
[pm-oahhyc](../.agents/pm/issues/pm-oahhyc.toon). Exact command-local
|
|
21
21
|
projection discovery is tracked by
|
|
22
|
-
[pm-q4isdq](../.agents/pm/issues/pm-q4isdq.toon).
|
|
22
|
+
[pm-q4isdq](../.agents/pm/issues/pm-q4isdq.toon). Shared graph defaults and
|
|
23
|
+
scale enforcement are tracked by [pm-mfy1ux](../.agents/pm/issues/pm-mfy1ux.toon).
|
|
24
|
+
|
|
25
|
+
For enforcement of declared MCP input options in repository tests, see
|
|
26
|
+
[MCP Option Conformance](MCP_OPTION_CONFORMANCE.md).
|
|
23
27
|
|
|
24
28
|
## Agent Quick Context
|
|
25
29
|
|
|
@@ -167,7 +171,19 @@ Passing `--full` with the corresponding compact flag is a usage error. An
|
|
|
167
171
|
explicit full request and the default full request produce the same payload
|
|
168
172
|
shape; the flag exists as a machine-actionable restore instruction.
|
|
169
173
|
|
|
170
|
-
|
|
174
|
+
Graph row collections default to ten rows, including hierarchy and ordering
|
|
175
|
+
traversals, audit findings, and slack's critical path. The public
|
|
176
|
+
`GRAPH_QUERY_DEFAULTS` contract is available from `@unbrained/pm-cli/sdk/graph`
|
|
177
|
+
and `@unbrained/pm-cli/sdk/contracts`; `pm contracts --command graph --flags-only`
|
|
178
|
+
declares the same defaults before invoking a graph query. Analytics still compute over the complete graph. `--full`
|
|
179
|
+
restores row collections, while `--limit` chooses a finite cap; combining those
|
|
180
|
+
two controls is rejected. Path enumeration retains its separate five-path,
|
|
181
|
+
eight-edge-depth and 10,000-expansion safety defaults. Audit evidence uses
|
|
182
|
+
`--sample` independently of the finding-row limit. The thousand-item graph
|
|
183
|
+
regression enumerates every registered subcommand, bounds serialized output,
|
|
184
|
+
and proves full traversal and cursor continuation.
|
|
185
|
+
|
|
186
|
+
`graph impact` returns `next_cursor` when
|
|
171
187
|
more affected nodes exist, and accepts `--after <cursor>` to resume in stable
|
|
172
188
|
breadth-first order. `graph impact --full` is the explicit unbounded override;
|
|
173
189
|
combining `--full` with `--limit` is rejected. A zero-row page remains
|
package/docs/PACKED_FIRST_RUN.md
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# Packed First-Run Acceptance
|
|
2
2
|
|
|
3
|
-
Tracked by [pm-ygli86](../.agents/pm/tasks/pm-ygli86.toon)
|
|
4
|
-
[pm-rcjyft](../.agents/pm/issues/pm-rcjyft.toon)
|
|
3
|
+
Tracked by [pm-ygli86](../.agents/pm/tasks/pm-ygli86.toon),
|
|
4
|
+
[pm-rcjyft](../.agents/pm/issues/pm-rcjyft.toon), and
|
|
5
|
+
[pm-07m41m](../.agents/pm/issues/pm-07m41m.toon).
|
|
5
6
|
|
|
6
7
|
The `Packed first run` CI matrix installs the same tarball globally into an
|
|
7
8
|
isolated npm prefix on Ubuntu, macOS, and Windows, using Node 22.18.0 and Node 24.
|
|
@@ -24,8 +25,11 @@ must preserve both comments and their history. The installed reconciliation and
|
|
|
24
25
|
strict history verifier must succeed. The installed MCP server must complete an
|
|
25
26
|
initialize round trip and advertise the installed package version. An additional
|
|
26
27
|
mutation enables telemetry only against a loopback HTTP collector. The harness
|
|
27
|
-
|
|
28
|
-
empty and a successful flush is recorded.
|
|
28
|
+
acknowledges detached delivery batches until it receives `command_finish`, then
|
|
29
|
+
verifies the physical queue is empty and a successful flush is recorded.
|
|
30
|
+
One 30-second deadline covers the entire collection, including incomplete request
|
|
31
|
+
bodies; an initial `command_start` batch alone cannot satisfy acceptance.
|
|
32
|
+
It removes inherited inline-flush and
|
|
29
33
|
test-runner overrides so this exercises the real detached process. Other commands
|
|
30
34
|
and MCP keep telemetry disabled; the install and workspace use isolated home
|
|
31
35
|
and global tracker directories.
|