@unbrained/pm-cli 2026.10.2 → 2026.10.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +2 -2
- package/CHANGELOG.md +31 -1
- package/dist/cli/error-guidance.js +1 -1
- package/dist/cli/runtime/invocation-options.js +1 -1
- package/dist/cli/runtime/selection.js +1 -1
- package/dist/cli/runtime-compatibility-boundary.d.ts +1 -1
- package/dist/cli/runtime-compatibility-boundary.js +3 -3
- package/dist/cli-bundle/bundle-manifest.json +149 -149
- package/dist/cli-bundle/chunks/{chunk-HY6M34B4.js → chunk-7CE2DJMA.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-WP6NRH72.js → chunk-7UVXKI7K.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-7FUEQOYG.js → chunk-D7Z2ZMJL.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-TRYXVQ7W.js → chunk-GZMWISWW.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-ZI7F3FVE.js → chunk-J52QIRRZ.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-R4TUD355.js → chunk-JDKD3WGG.js} +6 -6
- package/dist/cli-bundle/chunks/chunk-JQ7DW663.js +2 -0
- package/dist/cli-bundle/chunks/{chunk-SV7CZEVV.js → chunk-QXIDOOWT.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-XOUBIF6Z.js → chunk-SMKHR6BM.js} +49 -49
- package/dist/cli-bundle/chunks/{chunk-MTJMOI4I.js → chunk-V3U77H3V.js} +31 -31
- package/dist/cli-bundle/chunks/{chunk-Z2GYDSAO.js → chunk-XZDWAYJC.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-R7ULWMBG.js → chunk-Y6VRBTP3.js} +71 -71
- package/dist/cli-bundle/chunks/{chunk-CZLAWXDR.js → chunk-YAAFYFS4.js} +2 -2
- package/dist/cli-bundle/chunks/{register-list-query-R4PHNB7W.js → register-list-query-QBGXO5JJ.js} +2 -2
- package/dist/cli-bundle/chunks/{register-mutation-PDDAUDYV.js → register-mutation-53ZLT4RX.js} +2 -2
- package/dist/cli-bundle/chunks/{register-operations-A5HJT46K.js → register-operations-OBK2FXUU.js} +2 -2
- package/dist/cli-bundle/chunks/{register-setup-EZ2F25JI.js → register-setup-HM3OCBH2.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-FKK2HI3S.js → chunk-2JFYR56O.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-UTRSM3ZK.js → chunk-32EVUSS4.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-DBD4W4QP.js → chunk-467EW5RL.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-IR4UXLRP.js → chunk-5GUSO2P4.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-GMYUNHST.js → chunk-6XBRADQQ.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-EJN7DY3B.js → chunk-7O4NUJVO.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-E754JDLY.js → chunk-CURTIIQT.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-4FFPGNCJ.js → chunk-IJUWEEEB.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-XRSCIYHR.js → chunk-JCVMOSWQ.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-ZIXFMIUL.js → chunk-JMJK2L4Z.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-WCY6HRZO.js → chunk-KG3H4UZZ.js} +14 -14
- package/dist/cli-bundle/focused-chunks/{chunk-VVWYI2X7.js → chunk-MHQ2XICU.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-3BIYG72P.js → chunk-Q6DJ5DFQ.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-BLCTBSGT.js → chunk-RJV6QVJE.js} +26 -26
- package/dist/cli-bundle/focused-chunks/{chunk-GBD4EN6S.js → chunk-ZE2JZ7R6.js} +11 -11
- package/dist/cli-bundle/focused-chunks/chunk-ZLEN4HZD.js +165 -0
- package/dist/cli-bundle/main.js +22 -22
- package/dist/cli-bundle/sdk-authoring.js +1 -1
- package/dist/cli-bundle/sdk-contracts.js +1 -1
- package/dist/cli-bundle/sdk-core.js +31 -31
- 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 +23 -23
- 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 +1 -1
- package/dist/core/extensions/extension-types.d.ts +8 -1
- package/dist/core/extensions/extension-types.js +1 -1
- package/dist/core/shared/secret-rules.js +1 -1
- package/dist/core/shared/time.js +1 -1
- package/dist/core/store/settings.d.ts +11 -2
- package/dist/core/store/settings.js +2 -2
- package/dist/sdk/agent/subcommand-recovery.js +1 -1
- package/dist/sdk/cli-bootstrap.js +1 -1
- package/dist/sdk/context-intent-contracts.js +1 -1
- package/dist/sdk/extension-command-context.js +1 -1
- package/dist/sdk/generated/generated-error-code-catalog-part-2.js +1 -1
- package/dist/sdk/item-transaction.js +1 -1
- package/dist/sdk/lifecycle/recurrence-parsers.js +1 -1
- package/dist/sdk/lifecycle/task-composition.js +1 -1
- package/dist/sdk/linked-artifacts.d.ts +1 -1
- package/dist/sdk/linked-artifacts.js +1 -1
- package/dist/sdk/output-projection.js +1 -1
- package/dist/sdk/public-surface.json +1 -1
- package/dist/sdk/query/get.js +1 -1
- package/dist/sdk/query/search-pagination.d.ts +1 -1
- package/dist/sdk/query/search-pagination.js +1 -1
- package/dist/sdk/test/execution.js +2 -2
- package/dist/sdk/test/workspace-snapshot.d.ts +11 -0
- package/dist/sdk/test/workspace-snapshot.js +2 -0
- package/docs/DEVELOPMENT_DEPENDENCY_SECURITY.md +109 -0
- package/docs/DIAGNOSTIC_OUTPUT_CONTRACTS.md +25 -0
- package/docs/OUTPUT_PROJECTION_CONTRACTS.md +23 -0
- package/docs/SDK_AUDITED_MUTATIONS.md +35 -0
- package/docs/TESTING.md +40 -5
- package/docs/ecosystem-review-2026-09-28.json +2864 -3157
- package/marketplace.json +2 -2
- package/package.json +5 -4
- package/packages/pm-beads/package.json +1 -1
- package/packages/pm-calendar/package.json +1 -1
- package/packages/pm-command-kit/package.json +1 -1
- package/packages/pm-digital-twin/package.json +1 -1
- package/packages/pm-governance-audit/package.json +1 -1
- package/packages/pm-guide-shell/package.json +1 -1
- package/packages/pm-kanban/package.json +1 -1
- package/packages/pm-lifecycle-hooks/package.json +1 -1
- package/packages/pm-linked-test-adapters/package.json +1 -1
- package/packages/pm-search-advanced/package.json +1 -1
- package/packages/pm-templates/package.json +1 -1
- package/packages/pm-todos/package.json +1 -1
- package/packages/pm-vcs/package.json +1 -1
- package/plugins/pm-claude/.claude-plugin/plugin.json +1 -1
- package/plugins/pm-claude/package.json +2 -2
- package/plugins/pm-codex/.codex-plugin/plugin.json +1 -1
- package/plugins/pm-codex/package.json +2 -2
- package/plugins/pm-codex/plugin.json +1 -1
- package/dist/cli-bundle/chunks/chunk-7NL3A2BY.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-YC32ZSLS.js +0 -165
package/dist/sdk/query/get.js
CHANGED
|
@@ -1,2 +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]="33c1e75c-e348-5ab1-933f-2f7b6394fe40")}catch(e2){}})();import{assertInitializedTracker}from"../environment/tracker-preflight.js";import{getActiveExtensionRegistrations,toItemRecord,resolveItemTypeRegistry,resolveRuntimeFieldRegistry,resolveRuntimeStatusRegistry,EXIT_CODE,TYPE_TO_FOLDER,PmCliError,buildItemNotFoundError,listAllItemMetadataLight,locateItem,readLocatedItem,getHistoryPath,resolvePmRoot,readSettings,resolveAuthor}from"../runtime-primitives.js";import{readHistoryEntries}from"../history-read.js";import{renderPmCommand}from"../command-line.js";import{recordContextUsageTouches}from"../context-usage.js";import{buildItemChildrenRollup}from"../item-children.js";import{buildItemSchedule}from"../item-schedule.js";import{getItemAt}from"../history-read.js";import{parseIntegerLimit}from"./parsers.js";import{runList}from"./list.js";import{registerOutputMaterialFieldGroups}from"../output-projection.js";import{GET_DEPTH_VALUES,listGetProjectionFields}from"./projection-contracts.js";import{GET_DEPTH_VALUES as GET_DEPTH_VALUES2,listGetProjectionFields as listGetProjectionFields2}from"./projection-contracts.js";const AUTOMATIC_CHILD_ROLLUP_TYPES=new Set(["epic","feature","milestone","plan"]);const BUILTIN_ITEM_TYPES=new Set(Object.keys(TYPE_TO_FOLDER).map(type=>type.toLowerCase()));function itemCollectionCounts(item){const lengths=Object.fromEntries(["comments","notes","learnings","files","tests","docs","reminders","events"].map(key=>[key,item[key]?.length??0]));return lengths}function itemMaterialFieldGroups(item,body,children){const collectionCounts=itemCollectionCounts(item);return[...body.length>0?["body"]:[],...Object.entries(collectionCounts).flatMap(([name,count])=>count>0?[name]:[]),...collectionCounts.files+collectionCounts.tests+collectionCounts.docs>0?["linked"]:[],...children===void 0?["children"]:children.count>0?["children"]:[],...typeof item.assignee==="string"&&item.assignee.trim().length>0?["claim_state"]:[],...buildItemSchedule(item)?["schedule"]:[]]}function shouldAutoIncludeGetChildren(itemType){const normalizedType=itemType.trim().toLowerCase();return normalizedType.length>0&&(AUTOMATIC_CHILD_ROLLUP_TYPES.has(normalizedType)||!BUILTIN_ITEM_TYPES.has(normalizedType))}function toClaimHistoryContext(entry){return{ts:entry.ts,author:entry.author,message:entry.message??null}}function resolveClaimStateContext(assigneeValue,history){const assignee=assigneeValue?.trim();const normalizedAssignee=assignee&&assignee.length>0?assignee:null;const lastClaim=[...history].reverse().find(entry=>entry.op==="claim");const lastRelease=[...history].reverse().find(entry=>entry.op==="release");return{claimed:normalizedAssignee!==null,assignee:normalizedAssignee,last_claim:lastClaim?toClaimHistoryContext(lastClaim):null,last_release:lastRelease?toClaimHistoryContext(lastRelease):null}}function parseGetDepth(raw){if(raw===void 0||raw.trim().length===0){return"standard"}const normalized=raw.trim().toLowerCase();if(normalized==="full"){return"deep"}if(GET_DEPTH_VALUES.includes(normalized)){return normalized}throw new PmCliError("Get --depth must be one of brief|standard|deep|full",EXIT_CODE.USAGE)}function projectItemForDepth(item,depth){const collectionCounts=itemCollectionCounts(item);if(depth==="deep"){return{...item,comments:item.comments??[],notes:item.notes??[],learnings:item.learnings??[],files:item.files??[],tests:item.tests??[],docs:item.docs??[],reminders:item.reminders??[],events:item.events??[],notes_count:collectionCounts.notes,tests_count:collectionCounts.tests,collection_counts:collectionCounts}}const{comments:_comments,notes:_notes,learnings:_learnings,files:_files,tests:_tests,docs:_docs,reminders:_reminders,events:_events,...projected}=item;return{...projected,notes_count:item.notes?.length??0,tests_count:item.tests?.length??0,collection_counts:collectionCounts}}function parseGetFields(raw){if(raw===void 0){return null}const fields=raw.split(",").map(entry=>entry.trim()).filter(entry=>entry.length>0);if(fields.length===0){throw new PmCliError("Get --fields requires a comma-separated list of field names",EXIT_CODE.USAGE)}return fields}function normalizeGetField(field){return field.startsWith("item.")?field.slice("item.".length):field}function validateGetFields(fields,runtimeMetadataKeys,id){if(fields===null){return}const allowedValues=listGetProjectionFields(runtimeMetadataKeys);const allowed=new Set(allowedValues);const unknown=fields.filter(field=>{const normalized=normalizeGetField(field);return!allowed.has(field)&&!allowed.has(normalized)});if(unknown.length>0){const suggestedRetryArguments=["get",id,"--fields",fields.filter(field=>!unknown.includes(field)).join(",")||"id,title,status"];throw new PmCliError(`Unknown get --fields value(s): ${unknown.join(", ")}`,EXIT_CODE.USAGE,{code:"unknown_field_projection",examples:["pm get <id> --fields id,title,status,type,updated_at","pm get <id> --fields id,title,claim_state","pm get <id> --fields id,title,body,linked.files"],recovery:{allowed_values:allowedValues,suggested_retry:renderPmCommand(suggestedRetryArguments),suggested_retry_args:suggestedRetryArguments}})}}const EMPTY_PROJECTED_COLLECTION_FIELDS=new Set(["comments","notes","learnings","files","tests","test_runs","docs","reminders","events","dependencies","plan_steps","plan_decisions","plan_discoveries","plan_validation"]);function projectedMetadataValue(source,counts,field){if(Object.hasOwn(counts,field))return counts[field];if(source[field]!==void 0)return source[field];return EMPTY_PROJECTED_COLLECTION_FIELDS.has(field)?[]:void 0}function projectItemForFields(item,fields){const source=toItemRecord(item);const omittedCounts={notes_count:item.notes?.length??0,tests_count:item.tests?.length??0,collection_counts:itemCollectionCounts(item)};const projected={id:item.id};for(const field of fields){const normalized=normalizeGetField(field);if(normalized==="body"||normalized==="linked"||normalized.startsWith("linked.")||normalized==="claim_state"||normalized.startsWith("claim_state.")||normalized==="children"||normalized.startsWith("children.")||normalized==="schedule"||normalized.startsWith("schedule.")){continue}projected[normalized]=projectedMetadataValue(source,omittedCounts,normalized)}return projected}function fieldsInclude(fields,name){return fields?.some(field=>field===name||field===`item.${name}`)??false}function fieldsIncludeRoot(fields,name){return fields.some(field=>{const normalized=normalizeGetField(field);return normalized===name||normalized.startsWith(`${name}.`)})}function resolveGetProjection(options,id){if(options.full&&(options.fields!==void 0||options.depth!==void 0)){throw new PmCliError("Get projection options are mutually exclusive; remove the extra projection flag and retry.",EXIT_CODE.USAGE,{code:"projection_options_mutually_exclusive",recovery:{suggested_retry:renderPmCommand(["get",id,"--full"]),suggested_retry_args:["get",id,"--full"]}})}if(options.tree!==true&&options.treeDepth!==void 0){throw new PmCliError("Get --tree-depth requires --tree",EXIT_CODE.USAGE)}if(options.at!==void 0&&options.tree===true){throw new PmCliError("Get --at cannot be combined with --tree because workspace-level historical projections are not yet indexed.",EXIT_CODE.USAGE)}return{depth:options.full?"deep":parseGetDepth(options.depth),full:options.full===true||options.depth?.trim().toLowerCase()==="full",treeDepth:options.tree===true?parseIntegerLimit(options.treeDepth,"--tree-depth"):void 0,fields:parseGetFields(options.fields),fieldProjection:options.fields!==void 0}}async function loadGetItemContext(id,global2,at){const pmRoot=resolvePmRoot(process.cwd(),global2.path);await assertInitializedTracker(pmRoot);const settings=await readSettings(pmRoot);const typeRegistry=resolveItemTypeRegistry(settings,getActiveExtensionRegistrations());if(at!==void 0){const historical=await getItemAt(id,at,{pmRoot:global2.path});return{pmRoot,settings,typeToFolder:typeRegistry.type_to_folder,locatedId:historical.document.metadata.id,metadata:historical.document.metadata,body:historical.document.body,historical}}const located=await locateItem(pmRoot,id,settings.id_prefix,settings.item_format,typeRegistry.type_to_folder);if(!located){throw await buildItemNotFoundError(pmRoot,id,settings.id_prefix,typeRegistry.type_to_folder)}const loaded=await readLocatedItem(located,{schema:settings.schema});return{pmRoot,settings,typeToFolder:typeRegistry.type_to_folder,locatedId:located.id,metadata:loaded.document.metadata,body:loaded.document.body}}function validateGetProjectionFields(fields,settings,historical,id){const runtimeMetadataKeys=resolveRuntimeFieldRegistry(settings.schema).definitions.map(field=>field.metadata_key);validateGetFields(fields,runtimeMetadataKeys,id);if(historical&&fieldsIncludeRoot(fields??[],"children")){throw new PmCliError("Get --at cannot project children because workspace-level historical relationships are not yet indexed.",EXIT_CODE.USAGE)}}function shouldIncludeGetField(params){const{fieldProjection,depth,fields,field}=params;if(fieldProjection){if(field==="body"||field==="linked"){return fieldsInclude(fields,field)}return fieldsIncludeRoot(fields,field)}return depth!=="brief"}async function resolveGetClaimState(context,includeClaimState){if(!includeClaimState){return void 0}const historyPath=getHistoryPath(context.pmRoot,context.locatedId);const history=await readHistoryEntries(historyPath,context.locatedId);return resolveClaimStateContext(context.metadata.assignee,context.historical?history.slice(0,context.historical.target.historyIndex+1):history)}function attachGetLinked(result,context,fields,includeLinked){const includeLinkedFiles=includeLinked||fieldsInclude(fields,"linked.files");const includeLinkedTests=includeLinked||fieldsInclude(fields,"linked.tests");const includeLinkedDocs=includeLinked||fieldsInclude(fields,"linked.docs");if(!includeLinked&&!includeLinkedFiles&&!includeLinkedTests&&!includeLinkedDocs){return}result.linked={files:includeLinkedFiles?context.metadata.files??[]:[],tests:includeLinkedTests?context.metadata.tests??[]:[],docs:includeLinkedDocs?context.metadata.docs??[]:[]}}async function buildGetChildrenRollup(context,includeChildren){if(!includeChildren){return void 0}const statusRegistry=resolveRuntimeStatusRegistry(context.settings.schema);const corpus=await listAllItemMetadataLight(context.pmRoot,context.settings.item_format,context.typeToFolder,void 0,context.settings.schema);return buildItemChildrenRollup(context.locatedId,corpus,statusRegistry)}function attachGetSchedule(result,context,fields,includeSchedule){if(!includeSchedule){return}const schedule=buildItemSchedule(context.metadata);if(!schedule){return}if(fields===null||fieldsInclude(fields,"schedule")){result.schedule=schedule;return}result.schedule=Object.fromEntries(Object.entries(schedule).filter(([key])=>fields.some(field=>normalizeGetField(field)===`schedule.${key}`)))}async function buildGetTree(context,options,treeDepth,global2){if(options.tree!==true){return void 0}const subtree=await runList(void 0,{parent:context.locatedId,tree:true,treeDepth:treeDepth===void 0?void 0:String(treeDepth),full:true},global2);return{root_id:context.locatedId,root_title:context.metadata.title,depth_limit:treeDepth??null,count:subtree.count,items:subtree.items.map(entry=>toItemRecord(entry))}}async function runGet(id,global2,options={}){const projection=resolveGetProjection(options,id);const context=await loadGetItemContext(id,global2,options.at);validateGetProjectionFields(projection.fields,context.settings,context.historical!==void 0,context.locatedId);const includeBody=shouldIncludeGetField({...projection,field:"body"});const includeLinked=shouldIncludeGetField({...projection,field:"linked"});const includeClaimState=shouldIncludeGetField({...projection,field:"claim_state"});const includeChildren=context.historical===void 0&&(projection.fieldProjection?fieldsIncludeRoot(projection.fields,"children"):projection.depth==="deep"&&(projection.full||shouldAutoIncludeGetChildren(context.metadata.type)));const includeSchedule=projection.fieldProjection?fieldsIncludeRoot(projection.fields,"schedule"):projection.depth!=="brief";const claimState=await resolveGetClaimState(context,includeClaimState);const result={item:projection.fieldProjection?projectItemForFields(context.metadata,projection.fields):projectItemForDepth(context.metadata,projection.depth)};if(includeBody){result.item.body=context.body}attachGetLinked(result,context,projection.fields,includeLinked);if(claimState){result.claim_state=claimState}attachGetSchedule(result,context,projection.fields,includeSchedule);const children=await buildGetChildrenRollup(context,includeChildren);if(children!==void 0){result.children=children}const tree=await buildGetTree(context,options,projection.treeDepth,global2);if(tree!==void 0){result.tree=tree}if(context.historical){result.reconstructed=true;result.as_of_version=context.historical.as_of_version;result.as_of_timestamp=context.historical.as_of_timestamp}if(context.historical===void 0){try{await recordContextUsageTouches({pmRoot:context.pmRoot,author:resolveAuthor(void 0,context.settings.author_default),itemIds:[context.locatedId],intent:"get"})}catch{}}registerOutputMaterialFieldGroups(result,itemMaterialFieldGroups(context.metadata,context.body,children));return result}const _testOnlyGetCommand={shouldAutoIncludeGetChildren};export{GET_DEPTH_VALUES2 as GET_DEPTH_VALUES,_testOnlyGetCommand,listGetProjectionFields2 as listGetProjectionFields,runGet};
|
|
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]="deaedc1c-6f20-568e-bed6-970d43fb98dc")}catch(e2){}})();import{assertInitializedTracker}from"../environment/tracker-preflight.js";import{getActiveExtensionRegistrations,toItemRecord,resolveItemTypeRegistry,resolveRuntimeFieldRegistry,resolveRuntimeStatusRegistry,EXIT_CODE,TYPE_TO_FOLDER,PmCliError,buildItemNotFoundError,listAllItemMetadataLight,locateItem,readLocatedItem,getHistoryPath,resolvePmRoot,readSettings,resolveAuthor}from"../runtime-primitives.js";import{readHistoryEntries}from"../history-read.js";import{renderPmCommand}from"../command-line.js";import{recordContextUsageTouches}from"../context-usage.js";import{buildItemChildrenRollup}from"../item-children.js";import{buildItemSchedule}from"../item-schedule.js";import{getItemAt}from"../history-read.js";import{parseIntegerLimit}from"./parsers.js";import{runList}from"./list.js";import{registerOutputMaterialFieldGroups}from"../output-projection.js";import{GET_DEPTH_VALUES,listGetProjectionFields}from"./projection-contracts.js";import{GET_DEPTH_VALUES as GET_DEPTH_VALUES2,listGetProjectionFields as listGetProjectionFields2}from"./projection-contracts.js";const AUTOMATIC_CHILD_ROLLUP_TYPES=new Set(["epic","feature","milestone","plan"]);const BUILTIN_ITEM_TYPES=new Set(Object.keys(TYPE_TO_FOLDER).map(type=>type.toLowerCase()));function itemCollectionCounts(item){const lengths=Object.fromEntries(["comments","notes","learnings","files","tests","docs","reminders","events"].map(key=>[key,item[key]?.length??0]));return lengths}function itemMaterialFieldGroups(item,body,children){const collectionCounts=itemCollectionCounts(item);return[...body.length>0?["body"]:[],...Object.entries(collectionCounts).flatMap(([name,count])=>count>0?[name]:[]),...collectionCounts.files+collectionCounts.tests+collectionCounts.docs>0?["linked"]:[],...children===void 0?["children"]:children.count>0?["children"]:[],...typeof item.assignee==="string"&&item.assignee.trim().length>0?["claim_state"]:[],...buildItemSchedule(item)?["schedule"]:[]]}function shouldAutoIncludeGetChildren(itemType){const normalizedType=itemType.trim().toLowerCase();return normalizedType.length>0&&(AUTOMATIC_CHILD_ROLLUP_TYPES.has(normalizedType)||!BUILTIN_ITEM_TYPES.has(normalizedType))}function toClaimHistoryContext(entry){return{ts:entry.ts,author:entry.author,message:entry.message??null}}function resolveClaimStateContext(assigneeValue,history){const assignee=assigneeValue?.trim();const normalizedAssignee=assignee&&assignee.length>0?assignee:null;const lastClaim=[...history].reverse().find(entry=>entry.op==="claim");const lastRelease=[...history].reverse().find(entry=>entry.op==="release");return{claimed:normalizedAssignee!==null,assignee:normalizedAssignee,last_claim:lastClaim?toClaimHistoryContext(lastClaim):null,last_release:lastRelease?toClaimHistoryContext(lastRelease):null}}function parseGetDepth(raw){if(raw===void 0||raw.trim().length===0){return"standard"}const normalized=raw.trim().toLowerCase();if(normalized==="full"){return"deep"}if(GET_DEPTH_VALUES.includes(normalized)){return normalized}throw new PmCliError("Get --depth must be one of brief|standard|deep|full",EXIT_CODE.USAGE,{field:"depth",value:raw,recovery:{allowed_values:[...GET_DEPTH_VALUES,"full"]}})}function projectItemForDepth(item,depth){const collectionCounts=itemCollectionCounts(item);if(depth==="deep"){return{...item,comments:item.comments??[],notes:item.notes??[],learnings:item.learnings??[],files:item.files??[],tests:item.tests??[],docs:item.docs??[],reminders:item.reminders??[],events:item.events??[],notes_count:collectionCounts.notes,tests_count:collectionCounts.tests,collection_counts:collectionCounts}}const{comments:_comments,notes:_notes,learnings:_learnings,files:_files,tests:_tests,docs:_docs,reminders:_reminders,events:_events,...projected}=item;return{...projected,notes_count:item.notes?.length??0,tests_count:item.tests?.length??0,collection_counts:collectionCounts}}function parseGetFields(raw){if(raw===void 0){return null}const fields=raw.split(",").map(entry=>entry.trim()).filter(entry=>entry.length>0);if(fields.length===0){throw new PmCliError("Get --fields requires a comma-separated list of field names",EXIT_CODE.USAGE)}return fields}function normalizeGetField(field){return field.startsWith("item.")?field.slice("item.".length):field}function validateGetFields(fields,runtimeMetadataKeys,id){if(fields===null){return}const allowedValues=listGetProjectionFields(runtimeMetadataKeys);const allowed=new Set(allowedValues);const unknown=fields.filter(field=>{const normalized=normalizeGetField(field);return!allowed.has(field)&&!allowed.has(normalized)});if(unknown.length>0){const suggestedRetryArguments=["get",id,"--fields",fields.filter(field=>!unknown.includes(field)).join(",")||"id,title,status"];throw new PmCliError(`Unknown get --fields value(s): ${unknown.join(", ")}`,EXIT_CODE.USAGE,{code:"unknown_field_projection",examples:["pm get <id> --fields id,title,status,type,updated_at","pm get <id> --fields id,title,claim_state","pm get <id> --fields id,title,body,linked.files"],recovery:{allowed_values:allowedValues,suggested_retry:renderPmCommand(suggestedRetryArguments),suggested_retry_args:suggestedRetryArguments}})}}const EMPTY_PROJECTED_COLLECTION_FIELDS=new Set(["comments","notes","learnings","files","tests","test_runs","docs","reminders","events","dependencies","plan_steps","plan_decisions","plan_discoveries","plan_validation"]);function projectedMetadataValue(source,counts,field){if(Object.hasOwn(counts,field))return counts[field];if(source[field]!==void 0)return source[field];return EMPTY_PROJECTED_COLLECTION_FIELDS.has(field)?[]:void 0}function projectItemForFields(item,fields){const source=toItemRecord(item);const omittedCounts={notes_count:item.notes?.length??0,tests_count:item.tests?.length??0,collection_counts:itemCollectionCounts(item)};const projected={id:item.id};for(const field of fields){const normalized=normalizeGetField(field);if(normalized==="body"||normalized==="linked"||normalized.startsWith("linked.")||normalized==="claim_state"||normalized.startsWith("claim_state.")||normalized==="children"||normalized.startsWith("children.")||normalized==="schedule"||normalized.startsWith("schedule.")){continue}projected[normalized]=projectedMetadataValue(source,omittedCounts,normalized)}return projected}function fieldsInclude(fields,name){return fields?.some(field=>field===name||field===`item.${name}`)??false}function fieldsIncludeRoot(fields,name){return fields.some(field=>{const normalized=normalizeGetField(field);return normalized===name||normalized.startsWith(`${name}.`)})}function resolveGetProjection(options,id){if(options.full&&(options.fields!==void 0||options.depth!==void 0)){throw new PmCliError("Get projection options are mutually exclusive; remove the extra projection flag and retry.",EXIT_CODE.USAGE,{code:"projection_options_mutually_exclusive",flag:"--full",recovery:{suggested_retry:renderPmCommand(["get",id,"--full"]),suggested_retry_args:["get",id,"--full"]}})}if(options.tree!==true&&options.treeDepth!==void 0){throw new PmCliError("Get --tree-depth requires --tree",EXIT_CODE.USAGE)}if(options.at!==void 0&&options.tree===true){throw new PmCliError("Get --at cannot be combined with --tree because workspace-level historical projections are not yet indexed.",EXIT_CODE.USAGE)}return{depth:options.full?"deep":parseGetDepth(options.depth),full:options.full===true||options.depth?.trim().toLowerCase()==="full",treeDepth:options.tree===true?parseIntegerLimit(options.treeDepth,"--tree-depth"):void 0,fields:parseGetFields(options.fields),fieldProjection:options.fields!==void 0}}async function loadGetItemContext(id,global2,at){const pmRoot=resolvePmRoot(process.cwd(),global2.path);await assertInitializedTracker(pmRoot);const settings=await readSettings(pmRoot);const typeRegistry=resolveItemTypeRegistry(settings,getActiveExtensionRegistrations());if(at!==void 0){const historical=await getItemAt(id,at,{pmRoot:global2.path});return{pmRoot,settings,typeToFolder:typeRegistry.type_to_folder,locatedId:historical.document.metadata.id,metadata:historical.document.metadata,body:historical.document.body,historical}}const located=await locateItem(pmRoot,id,settings.id_prefix,settings.item_format,typeRegistry.type_to_folder);if(!located){throw await buildItemNotFoundError(pmRoot,id,settings.id_prefix,typeRegistry.type_to_folder)}const loaded=await readLocatedItem(located,{schema:settings.schema});return{pmRoot,settings,typeToFolder:typeRegistry.type_to_folder,locatedId:located.id,metadata:loaded.document.metadata,body:loaded.document.body}}function validateGetProjectionFields(fields,settings,historical,id){const runtimeMetadataKeys=resolveRuntimeFieldRegistry(settings.schema).definitions.map(field=>field.metadata_key);validateGetFields(fields,runtimeMetadataKeys,id);if(historical&&fieldsIncludeRoot(fields??[],"children")){throw new PmCliError("Get --at cannot project children because workspace-level historical relationships are not yet indexed.",EXIT_CODE.USAGE)}}function shouldIncludeGetField(params){const{fieldProjection,depth,fields,field}=params;if(fieldProjection){if(field==="body"||field==="linked"){return fieldsInclude(fields,field)}return fieldsIncludeRoot(fields,field)}return depth!=="brief"}async function resolveGetClaimState(context,includeClaimState){if(!includeClaimState){return void 0}const historyPath=getHistoryPath(context.pmRoot,context.locatedId);const history=await readHistoryEntries(historyPath,context.locatedId);return resolveClaimStateContext(context.metadata.assignee,context.historical?history.slice(0,context.historical.target.historyIndex+1):history)}function attachGetLinked(result,context,fields,includeLinked){const includeLinkedFiles=includeLinked||fieldsInclude(fields,"linked.files");const includeLinkedTests=includeLinked||fieldsInclude(fields,"linked.tests");const includeLinkedDocs=includeLinked||fieldsInclude(fields,"linked.docs");if(!includeLinked&&!includeLinkedFiles&&!includeLinkedTests&&!includeLinkedDocs){return}result.linked={files:includeLinkedFiles?context.metadata.files??[]:[],tests:includeLinkedTests?context.metadata.tests??[]:[],docs:includeLinkedDocs?context.metadata.docs??[]:[]}}async function buildGetChildrenRollup(context,includeChildren){if(!includeChildren){return void 0}const statusRegistry=resolveRuntimeStatusRegistry(context.settings.schema);const corpus=await listAllItemMetadataLight(context.pmRoot,context.settings.item_format,context.typeToFolder,void 0,context.settings.schema);return buildItemChildrenRollup(context.locatedId,corpus,statusRegistry)}function attachGetSchedule(result,context,fields,includeSchedule){if(!includeSchedule){return}const schedule=buildItemSchedule(context.metadata);if(!schedule){return}if(fields===null||fieldsInclude(fields,"schedule")){result.schedule=schedule;return}result.schedule=Object.fromEntries(Object.entries(schedule).filter(([key])=>fields.some(field=>normalizeGetField(field)===`schedule.${key}`)))}async function buildGetTree(context,options,treeDepth,global2){if(options.tree!==true){return void 0}const subtree=await runList(void 0,{parent:context.locatedId,tree:true,treeDepth:treeDepth===void 0?void 0:String(treeDepth),full:true},global2);return{root_id:context.locatedId,root_title:context.metadata.title,depth_limit:treeDepth??null,count:subtree.count,items:subtree.items.map(entry=>toItemRecord(entry))}}async function runGet(id,global2,options={}){const projection=resolveGetProjection(options,id);const context=await loadGetItemContext(id,global2,options.at);validateGetProjectionFields(projection.fields,context.settings,context.historical!==void 0,context.locatedId);const includeBody=shouldIncludeGetField({...projection,field:"body"});const includeLinked=shouldIncludeGetField({...projection,field:"linked"});const includeClaimState=shouldIncludeGetField({...projection,field:"claim_state"});const includeChildren=context.historical===void 0&&(projection.fieldProjection?fieldsIncludeRoot(projection.fields,"children"):projection.depth==="deep"&&(projection.full||shouldAutoIncludeGetChildren(context.metadata.type)));const includeSchedule=projection.fieldProjection?fieldsIncludeRoot(projection.fields,"schedule"):projection.depth!=="brief";const claimState=await resolveGetClaimState(context,includeClaimState);const result={item:projection.fieldProjection?projectItemForFields(context.metadata,projection.fields):projectItemForDepth(context.metadata,projection.depth)};if(includeBody){result.item.body=context.body}attachGetLinked(result,context,projection.fields,includeLinked);if(claimState){result.claim_state=claimState}attachGetSchedule(result,context,projection.fields,includeSchedule);const children=await buildGetChildrenRollup(context,includeChildren);if(children!==void 0){result.children=children}const tree=await buildGetTree(context,options,projection.treeDepth,global2);if(tree!==void 0){result.tree=tree}if(context.historical){result.reconstructed=true;result.as_of_version=context.historical.as_of_version;result.as_of_timestamp=context.historical.as_of_timestamp}if(context.historical===void 0){try{await recordContextUsageTouches({pmRoot:context.pmRoot,author:resolveAuthor(void 0,context.settings.author_default),itemIds:[context.locatedId],intent:"get"})}catch{}}registerOutputMaterialFieldGroups(result,itemMaterialFieldGroups(context.metadata,context.body,children));return result}const _testOnlyGetCommand={shouldAutoIncludeGetChildren};export{GET_DEPTH_VALUES2 as GET_DEPTH_VALUES,_testOnlyGetCommand,listGetProjectionFields2 as listGetProjectionFields,runGet};
|
|
2
2
|
//# sourceMappingURL=get.js.map
|
|
@@ -5,7 +5,7 @@ export declare function createSearchCursorFingerprint(options: {
|
|
|
5
5
|
query: string;
|
|
6
6
|
mode: SearchMode;
|
|
7
7
|
searchOptions: SearchOptions;
|
|
8
|
-
/**
|
|
8
|
+
/** Tracker scope, resolved before hashing so callers share native path identity. */
|
|
9
9
|
pmRoot?: string;
|
|
10
10
|
}): string;
|
|
11
11
|
/** Page ranked search hits without coupling cursor mechanics to search scoring. */
|
|
@@ -1,2 +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]="
|
|
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]="1c634b84-b239-5b9f-be64-b1c8e03fc25b")}catch(e2){}})();import{createQueryFingerprint,paginateQueryRows,selectCursorSemanticOptions}from"../pagination.js";import path from"node:path";import{SEARCH_FLAG_CONTRACTS}from"../cli-contracts/flag-contracts.js";function createSearchCursorFingerprint(options){return createQueryFingerprint("search",{pmRoot:options.pmRoot===void 0?void 0:path.resolve(options.pmRoot),query:options.query.trim(),mode:options.mode,options:selectCursorSemanticOptions(options.searchOptions,SEARCH_FLAG_CONTRACTS)})}function resolveSearchPage(options){const page=paginateQueryRows(options.sorted,{cursor:options.searchOptions.after,fingerprint:createSearchCursorFingerprint(options),limit:options.limit,readId:hit=>hit.item.id});return{limited:page.rows,pageExtras:{applied_limit:options.limit,...page.has_more?{has_more:true,truncated:true}:{},...page.next_cursor?{next_cursor:page.next_cursor}:{}}}}export{createSearchCursorFingerprint,resolveSearchPage};
|
|
2
2
|
//# sourceMappingURL=search-pagination.js.map
|
|
@@ -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]="04ea3495-92b7-5518-b06c-a1668d368d66")}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}
|
|
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]="8cf44754-b3ec-5c75-b635-c7ae89238fca")}catch(e2){}})();import{readJsonPathValue,splitJsonPathSegments}from"./json-path.js";import{seedLinkedTestWorkspaceSnapshot}from"./workspace-snapshot.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
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
|
-
${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 resolveUnboundPmContextFailure(params){if(params.executionContext.pm_context_mode!=="none")return null;if(params.executionContext.workspace_context_mode==="source"){return buildLinkedTestAssertionFailureResult(params.linkedTest,params.executionContext,"pm_context_mode=none requires workspace_context_mode=isolated or snapshot so implicit tracker discovery cannot reach the source workspace.")}if(params.executionContext.is_pm_command){return buildLinkedTestAssertionFailureResult(params.linkedTest,params.executionContext,"pm_context_mode=none is for self-isolating SDK tests; direct PM commands require schema, tracker, or auto context.")}return null}function resolvePmContextMismatchFailure(params){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)return null;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}`)}function resolveLinkedTestPreflightResult(params){const safetyFailure=resolveLinkedTestSafetyPreflightResult(params);if(safetyFailure)return safetyFailure;const trustFailure=resolveLinkedTestTrustPreflightResult(params);if(trustFailure)return trustFailure;const unboundContextFailure=resolveUnboundPmContextFailure(params);if(unboundContextFailure)return unboundContextFailure;const mismatchFailure=resolvePmContextMismatchFailure(params);if(mismatchFailure)return mismatchFailure;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";if(params.executionContext.pm_context_mode==="none"){delete executionEnv.PM_PATH}else{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,receipt:{recorded:false,reason:"tracking_disabled",run_id:entry.run_id,recovery_command:"pm config project set test-result-tracking --policy enabled"}}}try{await appendTrackedTestRunSummary({pmRoot,settings,itemId,author:resolveAuthor(options.author,settings.author_default),message:`Track test run summary (${entry.run_id})`,entry});return{entry,receipt:{recorded:true,reason:"recorded",run_id:entry.run_id}}}catch(error){warnings.push(`test_result_tracking_failed:${itemId}:${error instanceof Error?error.message:String(error)}`);return{entry,receipt:{recorded:false,reason:"write_failed",run_id:entry.run_id}}}}function buildTestMeasurementProjection(options,runs,recordedRun){let metricBelow;if(options.metricBelow!==void 0){const separator=options.metricBelow.indexOf("=");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())}function projectTestResult(input){const{item,runResults,failureCategories,runSelection,failOnSkippedTriggered,warnings,recording,measurementProjection}=input;return{ok:runResults.every(entry=>entry.status!=="failed")&&failOnSkippedTriggered!==true,id:item.itemId,tests:item.tests,run_results:runResults,failure_categories:failureCategories,selection:runSelection?{selector:runSelection.selector,requested:runSelection.requested,selected_indexes:runSelection.selected_indexes,selected_count:runSelection.selected_count,skipped_count:runSelection.skipped_count}:void 0,fail_on_skipped_triggered:failOnSkippedTriggered?true:void 0,warnings:warnings.length>0?warnings:void 0,changed:item.changed||recording?.receipt.recorded===true,...recording?{evidence_recording:recording.receipt}:{},count:item.tests.length,...item.removed>0?{removed:item.removed}:{},...measurementProjection}}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 recording=await recordTestRunSummary({options,pmRoot,settings,itemId:item.itemId,runStartedAt,runResults,failOnSkippedTriggered,warnings});const runs=recording?.receipt.recorded?buildTrackedTestRunHistory([...item.testRuns,recording.entry]):item.testRuns;const measurementProjection=buildTestMeasurementProjection(options,runs,recording?.entry);return projectTestResult({item,runResults,failureCategories,runSelection:runOptions.runSelection,failOnSkippedTriggered,warnings,recording,measurementProjection})}const _testOnlyTestCommand={buildLinkedTestExecutionContext,buildPmContextMismatchHint,commandInvokesPmCli,commandInvokesPmTrackerReadCommand,commandUsesTestNameFilter,copyIntoSandboxIfPresent,countLinkedTestItemFiles,ensureScope,evaluateLinkedTestAssertions,endLinkedTestProgress,extractPmInvocationArgsFromSegment,firstDirectTestRunnerSubcommand,formatLinkedTestExecutionError,hasLinkedTestAssertions,initializeLinkedTestSandboxes,linkedTestsRequireTrackerData,parseAddJsonEntries,parseLinkedTestTimeoutSeconds,parsePmContextMode,readJsonPathValue,resolveAuthor,resolveDirectRunnerSubcommand,resolveLinkedTestEffectiveContextMode,resolveLinkedTestFailureExitCode,resolveLinkedTestRequestedContextMode,resolveTestRunOptions,resolveTrackedRunId,runLinkedTestCommand,seedMissingLinkedTestSettingsHistory,seedLinkedTestSandbox,seedLinkedTestTrackerData,segmentInvokesRecursiveTestAll,splitJsonPathSegments,summarizeRunResultStatuses};export{_testOnlyTestCommand,classifyLinkedTestCommandSafety,classifyLinkedTestFailure,countFailureCategories,extractReferencedPmItemIdsFromCommand,resolveLinkedTestFailureExitCode,runLinkedTests,runTest,summarizeContextPreflight};
|
|
6
|
+
${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 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 resolveUnboundPmContextFailure(params){if(params.executionContext.pm_context_mode!=="none")return null;if(params.executionContext.workspace_context_mode==="source"){return buildLinkedTestAssertionFailureResult(params.linkedTest,params.executionContext,"pm_context_mode=none requires workspace_context_mode=isolated or snapshot so implicit tracker discovery cannot reach the source workspace.")}if(params.executionContext.is_pm_command){return buildLinkedTestAssertionFailureResult(params.linkedTest,params.executionContext,"pm_context_mode=none is for self-isolating SDK tests; direct PM commands require schema, tracker, or auto context.")}return null}function resolvePmContextMismatchFailure(params){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)return null;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}`)}function resolveLinkedTestPreflightResult(params){const safetyFailure=resolveLinkedTestSafetyPreflightResult(params);if(safetyFailure)return safetyFailure;const trustFailure=resolveLinkedTestTrustPreflightResult(params);if(trustFailure)return trustFailure;const unboundContextFailure=resolveUnboundPmContextFailure(params);if(unboundContextFailure)return unboundContextFailure;const mismatchFailure=resolvePmContextMismatchFailure(params);if(mismatchFailure)return mismatchFailure;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";if(params.executionContext.pm_context_mode==="none"){delete executionEnv.PM_PATH}else{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(sourceWorkspaceRoot,layout.workspaceSnapshotRoot)}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,receipt:{recorded:false,reason:"tracking_disabled",run_id:entry.run_id,recovery_command:"pm config project set test-result-tracking --policy enabled"}}}try{await appendTrackedTestRunSummary({pmRoot,settings,itemId,author:resolveAuthor(options.author,settings.author_default),message:`Track test run summary (${entry.run_id})`,entry});return{entry,receipt:{recorded:true,reason:"recorded",run_id:entry.run_id}}}catch(error){warnings.push(`test_result_tracking_failed:${itemId}:${error instanceof Error?error.message:String(error)}`);return{entry,receipt:{recorded:false,reason:"write_failed",run_id:entry.run_id}}}}function buildTestMeasurementProjection(options,runs,recordedRun){let metricBelow;if(options.metricBelow!==void 0){const separator=options.metricBelow.indexOf("=");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())}function projectTestResult(input){const{item,runResults,failureCategories,runSelection,failOnSkippedTriggered,warnings,recording,measurementProjection}=input;return{ok:runResults.every(entry=>entry.status!=="failed")&&failOnSkippedTriggered!==true,id:item.itemId,tests:item.tests,run_results:runResults,failure_categories:failureCategories,selection:runSelection?{selector:runSelection.selector,requested:runSelection.requested,selected_indexes:runSelection.selected_indexes,selected_count:runSelection.selected_count,skipped_count:runSelection.skipped_count}:void 0,fail_on_skipped_triggered:failOnSkippedTriggered?true:void 0,warnings:warnings.length>0?warnings:void 0,changed:item.changed||recording?.receipt.recorded===true,...recording?{evidence_recording:recording.receipt}:{},count:item.tests.length,...item.removed>0?{removed:item.removed}:{},...measurementProjection}}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 recording=await recordTestRunSummary({options,pmRoot,settings,itemId:item.itemId,runStartedAt,runResults,failOnSkippedTriggered,warnings});const runs=recording?.receipt.recorded?buildTrackedTestRunHistory([...item.testRuns,recording.entry]):item.testRuns;const measurementProjection=buildTestMeasurementProjection(options,runs,recording?.entry);return projectTestResult({item,runResults,failureCategories,runSelection:runOptions.runSelection,failOnSkippedTriggered,warnings,recording,measurementProjection})}const _testOnlyTestCommand={buildLinkedTestExecutionContext,buildPmContextMismatchHint,commandInvokesPmCli,commandInvokesPmTrackerReadCommand,commandUsesTestNameFilter,copyIntoSandboxIfPresent,countLinkedTestItemFiles,ensureScope,evaluateLinkedTestAssertions,endLinkedTestProgress,extractPmInvocationArgsFromSegment,firstDirectTestRunnerSubcommand,formatLinkedTestExecutionError,hasLinkedTestAssertions,initializeLinkedTestSandboxes,linkedTestsRequireTrackerData,parseAddJsonEntries,parseLinkedTestTimeoutSeconds,parsePmContextMode,readJsonPathValue,resolveAuthor,resolveDirectRunnerSubcommand,resolveLinkedTestEffectiveContextMode,resolveLinkedTestFailureExitCode,resolveLinkedTestRequestedContextMode,resolveTestRunOptions,resolveTrackedRunId,runLinkedTestCommand,seedMissingLinkedTestSettingsHistory,seedLinkedTestSandbox,seedLinkedTestTrackerData,segmentInvokesRecursiveTestAll,splitJsonPathSegments,summarizeRunResultStatuses};export{_testOnlyTestCommand,classifyLinkedTestCommandSafety,classifyLinkedTestFailure,countFailureCategories,extractReferencedPmItemIdsFromCommand,resolveLinkedTestFailureExitCode,runLinkedTests,runTest,summarizeContextPreflight};
|
|
7
7
|
//# sourceMappingURL=execution.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copy source files into a disposable workspace and link root and nested
|
|
3
|
+
* dependency directories at their original relative paths. Dependency trees
|
|
4
|
+
* are shared by convention, not protected against writes; callers must trust
|
|
5
|
+
* the linked command and use an independent install for dependency mutations.
|
|
6
|
+
* Source aliases are canonicalized through their existing ancestors and rebased
|
|
7
|
+
* into the snapshot; aliases into admitted dependency namespaces stay shared.
|
|
8
|
+
* Other external or excluded targets and overlapping physical source/destination
|
|
9
|
+
* trees are refused before the command can run.
|
|
10
|
+
*/
|
|
11
|
+
export declare function seedLinkedTestWorkspaceSnapshot(sourceRoot: string, snapshotRoot: string): Promise<void>;
|
|
@@ -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]="50a9baf9-931e-5822-a71a-122398a94b8c")}catch(e2){}})();import{cp,lstat,mkdir,readlink,realpath,stat,symlink}from"node:fs/promises";import path from"node:path";import{isFileMissingError}from"../../core/fs/fs-utils.js";const EXCLUDED_SEGMENTS=new Set([".agents",".git",".nyc_output",".turbo","coverage"]);function hasExcludedSegment(relative){return relative.split(path.sep).some(segment=>EXCLUDED_SEGMENTS.has(segment.toLowerCase()))}function isOutsideWorkspace(relative){return relative===".."||relative.startsWith(`..${path.sep}`)||path.isAbsolute(relative)}async function statSnapshotTarget(source){try{return await stat(source)}catch(error){if(!isFileMissingError(error))throw error;return void 0}}async function resolveLinkTarget(target){const missing=[];let ancestor=target;while(path.dirname(ancestor)!==ancestor){try{return path.join(await realpath(ancestor),...missing)}catch(error){if(!isFileMissingError(error))throw error;missing.unshift(path.basename(ancestor));ancestor=path.dirname(ancestor)}}return path.join(await realpath(ancestor),...missing)}async function resolveSnapshotTarget(target,sourceRoot,dependencyRoots){const relative=path.relative(sourceRoot,await resolveLinkTarget(target));if(!isOutsideWorkspace(relative))return relative;const suffix=[];let ancestor=target;while(path.dirname(ancestor)!==ancestor){const physicalAncestor=await resolveLinkTarget(ancestor);const dependency=dependencyRoots.find(([,root])=>root===physicalAncestor);if(dependency)return path.join(dependency[0],...suffix);suffix.unshift(path.basename(ancestor));ancestor=path.dirname(ancestor)}return relative}async function seedLinkedTestWorkspaceSnapshot(sourceRoot,snapshotRoot){const dependencyRoots=[];const sourceLinks=[];const resolvedSourceRoot=await realpath(sourceRoot);const resolvedSnapshotRoot=await resolveLinkTarget(path.resolve(snapshotRoot));if(![path.relative(resolvedSourceRoot,resolvedSnapshotRoot),path.relative(resolvedSnapshotRoot,resolvedSourceRoot)].every(isOutsideWorkspace)){throw new Error("Snapshot destination must be disjoint from the source workspace; choose a temporary root outside the checkout.")}await mkdir(path.dirname(snapshotRoot),{recursive:true});await cp(resolvedSourceRoot,snapshotRoot,{recursive:true,force:true,async filter(source){const relative=path.relative(resolvedSourceRoot,source);if(!relative)return true;if(hasExcludedSegment(relative))return false;if(path.basename(source).toLowerCase()==="node_modules"){if((await statSnapshotTarget(source))?.isDirectory()){const target=await resolveLinkTarget(source);const targetRelative=path.relative(resolvedSourceRoot,target);if(!isOutsideWorkspace(path.relative(target,resolvedSourceRoot))||!isOutsideWorkspace(targetRelative)&&(hasExcludedSegment(targetRelative)||!targetRelative.split(path.sep).some(segment=>segment.toLowerCase()==="node_modules"))){throw new Error(`Dependency symlink must target an included dependency directory or external installation: ${relative}`)}dependencyRoots.push([relative,target])}return false}if(!(await lstat(source)).isSymbolicLink())return true;sourceLinks.push([relative,path.resolve(path.dirname(source),await readlink(source)),(await statSnapshotTarget(source))?.isDirectory()?"junction":"file"]);return false}});for(const[relative]of dependencyRoots){await symlink(path.resolve(resolvedSourceRoot,relative),path.join(snapshotRoot,relative),"junction")}for(const[relative,target,type]of sourceLinks){const targetRelative=await resolveSnapshotTarget(target,resolvedSourceRoot,dependencyRoots);if(isOutsideWorkspace(targetRelative)||hasExcludedSegment(targetRelative)){throw new Error(`Source symlink must target included workspace source: ${relative}`)}await symlink(path.resolve(snapshotRoot,targetRelative),path.join(snapshotRoot,relative),type)}}export{seedLinkedTestWorkspaceSnapshot};
|
|
2
|
+
//# sourceMappingURL=workspace-snapshot.js.map
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# Development dependency security
|
|
2
|
+
|
|
3
|
+
Tracked by [pm-r61juc](../.agents/pm/issues/pm-r61juc.toon) and
|
|
4
|
+
[pm-fnx3np](../.agents/pm/issues/pm-fnx3np.toon).
|
|
5
|
+
|
|
6
|
+
Development tools execute during tests, benchmarks and release preparation.
|
|
7
|
+
Their dependencies have the same admission requirement as production packages:
|
|
8
|
+
no known advisory may pass the required static gate. Run:
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
pnpm install --frozen-lockfile
|
|
12
|
+
pnpm quality:dependencies
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
This runs the full `pnpm audit`, with no production-only or severity filter,
|
|
16
|
+
then verifies the installed CodSpeed bundle policy. Network/audit errors,
|
|
17
|
+
missing packages, incomplete policy entries, changed package files, and unreviewed
|
|
18
|
+
upgrades all fail. `quality:static` includes this command, so the existing CI,
|
|
19
|
+
nightly quality and release paths enforce it. The registry-owned local
|
|
20
|
+
preflight also requires the static gate and permits no skip. Trivy separately
|
|
21
|
+
includes development dependencies in its required repository scan.
|
|
22
|
+
|
|
23
|
+
## File matching dependency removal
|
|
24
|
+
|
|
25
|
+
[GHSA-vfj7-8cjw-p6xm](https://github.com/advisories/GHSA-vfj7-8cjw-p6xm)
|
|
26
|
+
affects `braces` 3.0.3, previously reachable through the production `fast-glob`
|
|
27
|
+
dependency. A pattern under 10,000 characters can exhaust the recursive brace
|
|
28
|
+
expansion stack. No fixed version was published when this change was verified.
|
|
29
|
+
The SDK and repository censuses now use `tinyglobby` 0.2.17, removing `fast-glob`,
|
|
30
|
+
`micromatch` and `braces` from the installed graph. Explicit
|
|
31
|
+
`expandDirectories: false` retains file-only semantics: a directory argument
|
|
32
|
+
does not silently become a recursive match. Dot files, brace and extglob
|
|
33
|
+
patterns, absolute paths and invocation-relative anchoring remain supported.
|
|
34
|
+
The regression uses a real temporary directory and the same deeply nested input
|
|
35
|
+
that failed before the replacement. Audit errors remain mandatory failures.
|
|
36
|
+
|
|
37
|
+
## CodSpeed 5.7.1 patch
|
|
38
|
+
|
|
39
|
+
CodSpeed's declared dependency previously resolved Axios 1.19.0, and both
|
|
40
|
+
`@codspeed/core` bundles embedded Axios 1.4.0. Updating the declared dependency
|
|
41
|
+
alone leaves those embedded implementations and their source-map contents
|
|
42
|
+
intact. The pnpm override selects Axios 1.20.0 or a newer compatible 1.x
|
|
43
|
+
release that satisfies the normal dependency cooldown. The exact-version
|
|
44
|
+
patch in [the patch file](../patches/@codspeed__core@5.7.1.patch) replaces the
|
|
45
|
+
embedded dependency section in both CJS and ESM entries with imports of the
|
|
46
|
+
declared `axios` and `form-data` packages. Each entry creates its own Axios
|
|
47
|
+
client and retains the cancellation constructor, preserving isolation from
|
|
48
|
+
application request and response interceptors.
|
|
49
|
+
|
|
50
|
+
The replacement boundary starts at `function bind$2(fn, thisArg)` and ends
|
|
51
|
+
immediately before `var __defProp$3 = Object.defineProperty`. Only `axios` and
|
|
52
|
+
`FormData$2` from that section are referenced by the retained code. The patch
|
|
53
|
+
preserves the preceding and following benchmark code, declarations, export
|
|
54
|
+
surface and native binaries. Each entry removes 19,201 embedded lines. Its map
|
|
55
|
+
retains 5,375 mappings, removes 39,536 obsolete mappings, and reduces its source
|
|
56
|
+
inventory from 131 to 21. Remaining generated positions are shifted by the
|
|
57
|
+
replacement's line delta; their original source, line, column and names are
|
|
58
|
+
preserved. Removed Axios, redirects, proxy and form-data sources are absent.
|
|
59
|
+
Both maps end with a newline; the integrity policy records the installed bytes.
|
|
60
|
+
|
|
61
|
+
[The integrity policy](../config/dependency-bundle-integrity.json) pins all twelve
|
|
62
|
+
package-owned files by SHA-256: both entries, both maps, all three native binaries,
|
|
63
|
+
the manifest, declarations and declaration map, license and README. Owned
|
|
64
|
+
artifacts must be regular files; symlinks cannot redirect their module resolution
|
|
65
|
+
to an unreviewed dependency tree even when their target bytes have the same hash. Admission
|
|
66
|
+
also checks the exact installed file inventory, so an added executable, map or
|
|
67
|
+
native payload fails even when the previously approved files are unchanged.
|
|
68
|
+
Package-local dependency overrides also fail inventory admission: an added
|
|
69
|
+
`node_modules/axios` could shadow the audited dependency resolved by the bundle.
|
|
70
|
+
Only the three `node-gyp-build` commands and their CMD/PowerShell variants may
|
|
71
|
+
appear directly in `node_modules/.bin`. Each must be a regular file whose complete
|
|
72
|
+
program matches the reviewed [shim templates](../config/codspeed-shims/), with
|
|
73
|
+
targets and module paths derived from the audited installed dependency. Arbitrary
|
|
74
|
+
names, nested payloads, links, altered programs and forged target markers fail;
|
|
75
|
+
these shims are outside the twelve package-owned hashes but are independently
|
|
76
|
+
validated. The shell template records pnpm 11.10.0's installed output; the CMD
|
|
77
|
+
and PowerShell templates come from `@zkochan/cmd-shim` 9.0.7. Template changes
|
|
78
|
+
require renewed real installation evidence on both POSIX and native Windows.
|
|
79
|
+
The CMD suffix follows the installed `PATHEXT` spelling, including `.CMD`;
|
|
80
|
+
format lookup folds its case while validating the complete program. All three
|
|
81
|
+
shell launchers are required, together with the CMD and PowerShell sets on
|
|
82
|
+
Windows. Any additional format must also contain all three commands. Missing
|
|
83
|
+
or duplicate launchers fail admission. Installation roots and rendered paths
|
|
84
|
+
containing shell interpolation syntax fail before template comparison;
|
|
85
|
+
ordinary paths containing spaces remain supported. Templates are comparison
|
|
86
|
+
data: the checker never rewrites pnpm-generated launchers.
|
|
87
|
+
The locked dependency graph is checked by the full audit. An
|
|
88
|
+
upstream upgrade requires a reviewed patch removal or refresh, renewed runtime
|
|
89
|
+
compatibility evidence, and a matching policy update. Never update hashes
|
|
90
|
+
merely to admit an unexplained difference.
|
|
91
|
+
|
|
92
|
+
## Verification and scope
|
|
93
|
+
|
|
94
|
+
The admission suite edits real temporary package copies and proves that each
|
|
95
|
+
changed package file and additional unreviewed payload is refused. The runtime
|
|
96
|
+
integration suite
|
|
97
|
+
uses both public entries in fresh processes, calls a real loopback HTTP server,
|
|
98
|
+
and verifies setup, benchmark start/stop, HTTP failure conversion, exports,
|
|
99
|
+
measurement conversions, root-frame wrappers and isolation from real application
|
|
100
|
+
interceptors. The server observes the
|
|
101
|
+
audited Axios transport rather than a mocked client. The existing CodSpeed
|
|
102
|
+
workflow continues to execute the native benchmark integration.
|
|
103
|
+
|
|
104
|
+
These findings establish vulnerable dependency inventory, not a demonstrated
|
|
105
|
+
production exploit. CodSpeed is a development dependency. Its Mongo
|
|
106
|
+
instrumentation transport requires an explicitly configured instrumentation
|
|
107
|
+
server; ordinary benchmarks do not configure one. Native Scorecard analysis
|
|
108
|
+
of the merged commit must confirm alert retirement independently of local
|
|
109
|
+
audits and PR checks.
|
|
@@ -67,6 +67,31 @@ remains complete and `recovery.suggested_retry_args` is atomic: the projector
|
|
|
67
67
|
either retains the executable argv or removes the recovery rather than slicing
|
|
68
68
|
it into a dead command.
|
|
69
69
|
|
|
70
|
+
Enum, date, get-depth, and minimum intent-budget validation producers attach the failed metadata
|
|
71
|
+
`field` and supplied `value` to `PmCliError.context`; closed domains also attach
|
|
72
|
+
the complete legal values, including the accepted `full` depth alias. CLI
|
|
73
|
+
attribution normalizes camelCase and snake_case fields to an actually supplied
|
|
74
|
+
canonical flag, so `tokenBudget` identifies `--token-budget`. Explicit flag
|
|
75
|
+
metadata retains its spelling. Argument order and unrelated valid flags cannot
|
|
76
|
+
become the refusal owner. The error-context `value` is text, including the
|
|
77
|
+
textual representation of numeric SDK inputs. Compound projection conflicts
|
|
78
|
+
identify their flag without inventing a rejected scalar. Missing items identify
|
|
79
|
+
the `id` operand.
|
|
80
|
+
When the producer cannot identify a field or value, the diagnostic retains the
|
|
81
|
+
command surface and omits the unknown scalar. Policy evidence takes precedence
|
|
82
|
+
over syntactic attribution. Tracked by [pm-gh1365](../.agents/pm/issues/pm-gh1365.toon).
|
|
83
|
+
|
|
84
|
+
`--json` and `--output-format json` (including `--output-format=json`) select
|
|
85
|
+
JSON on stderr for parser and execution errors as well as successful output.
|
|
86
|
+
The canonical `--output-format` selector wins when combined with `--json`, so
|
|
87
|
+
`--output-format toon --json` retains text diagnostics. Exit codes are unchanged.
|
|
88
|
+
The executable's project-runtime compatibility warnings and refusals use that
|
|
89
|
+
same SDK selector, including repeated formats and the `--` terminator. A JSON
|
|
90
|
+
stale-read warning is emitted after successful dispatch; a failed read emits its
|
|
91
|
+
typed error alone so stderr remains one JSON value. Text warnings retain their
|
|
92
|
+
pre-dispatch presentation.
|
|
93
|
+
Tracked by [pm-gh1366](../.agents/pm/issues/pm-gh1366.toon).
|
|
94
|
+
|
|
70
95
|
The deterministic ladder is:
|
|
71
96
|
|
|
72
97
|
1. full diagnostic;
|