@unbrained/pm-cli 2026.9.7 → 2026.9.8

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.
Files changed (718) hide show
  1. package/.agents/skills/pm-developer/SKILL.md +12 -4
  2. package/.agents/skills/pm-developer/references/MULTI_AGENT_MERGE.md +19 -10
  3. package/.agents/skills/pm-extensions/SKILL.md +6 -0
  4. package/.agents/skills/pm-sdk/SKILL.md +11 -2
  5. package/.agents/skills/pm-user/SKILL.md +5 -0
  6. package/.agents/skills/pm-user/references/ECOSYSTEM_PLANNING.md +95 -0
  7. package/.claude-plugin/marketplace.json +2 -2
  8. package/CHANGELOG.md +211 -181
  9. package/dist/cli/after-command-context-usage.js +1 -48
  10. package/dist/cli/argv-utils.js +1 -40
  11. package/dist/cli/bootstrap-args.js +1 -9
  12. package/dist/cli/bundle-integrity.js +2 -110
  13. package/dist/cli/commander-usage.js +11 -771
  14. package/dist/cli/commands/activity.js +1 -9
  15. package/dist/cli/commands/aggregate.js +1 -5
  16. package/dist/cli/commands/append.js +1 -5
  17. package/dist/cli/commands/claim.js +1 -5
  18. package/dist/cli/commands/close-many.js +1 -5
  19. package/dist/cli/commands/close.js +1 -9
  20. package/dist/cli/commands/comments.js +1 -5
  21. package/dist/cli/commands/config.js +1 -9
  22. package/dist/cli/commands/context.js +1 -9
  23. package/dist/cli/commands/contracts.js +1 -5
  24. package/dist/cli/commands/copy.js +1 -9
  25. package/dist/cli/commands/create.js +1 -9
  26. package/dist/cli/commands/delete.js +1 -9
  27. package/dist/cli/commands/deps.js +1 -9
  28. package/dist/cli/commands/docs.js +1 -9
  29. package/dist/cli/commands/duplicates.js +1 -9
  30. package/dist/cli/commands/eval.js +1 -9
  31. package/dist/cli/commands/extension/describe.js +1 -5
  32. package/dist/cli/commands/extension/scaffold.js +1 -5
  33. package/dist/cli/commands/extension/shared.js +1 -5
  34. package/dist/cli/commands/extension.js +1 -9
  35. package/dist/cli/commands/files.js +1 -9
  36. package/dist/cli/commands/focus.js +1 -9
  37. package/dist/cli/commands/gc.js +1 -9
  38. package/dist/cli/commands/get.js +1 -9
  39. package/dist/cli/commands/graph.js +1 -10
  40. package/dist/cli/commands/health.js +1 -9
  41. package/dist/cli/commands/history-compact.js +1 -10
  42. package/dist/cli/commands/history-redact.js +1 -11
  43. package/dist/cli/commands/history-repair.js +1 -10
  44. package/dist/cli/commands/history.js +1 -5
  45. package/dist/cli/commands/index.js +1 -58
  46. package/dist/cli/commands/init-agent-guidance.js +1 -9
  47. package/dist/cli/commands/init.js +1 -9
  48. package/dist/cli/commands/learnings.js +1 -5
  49. package/dist/cli/commands/lifecycle-transitions.js +1 -5
  50. package/dist/cli/commands/list.js +1 -9
  51. package/dist/cli/commands/merge.js +1 -11
  52. package/dist/cli/commands/next.js +1 -9
  53. package/dist/cli/commands/notes.js +1 -5
  54. package/dist/cli/commands/plan.js +1 -9
  55. package/dist/cli/commands/profile.js +1 -9
  56. package/dist/cli/commands/restore.js +1 -9
  57. package/dist/cli/commands/scheduling-shortcuts.js +1 -9
  58. package/dist/cli/commands/schema.js +1 -10
  59. package/dist/cli/commands/search.js +1 -9
  60. package/dist/cli/commands/stats.js +1 -9
  61. package/dist/cli/commands/telemetry.js +1 -9
  62. package/dist/cli/commands/test-all.js +1 -9
  63. package/dist/cli/commands/test-runs.js +1 -9
  64. package/dist/cli/commands/test.js +1 -9
  65. package/dist/cli/commands/update-many.js +1 -5
  66. package/dist/cli/commands/update.js +1 -9
  67. package/dist/cli/commands/upgrade.js +1 -5
  68. package/dist/cli/commands/validate.js +1 -15
  69. package/dist/cli/context-intent-invocation.js +1 -20
  70. package/dist/cli/description-stdin.js +1 -17
  71. package/dist/cli/error-guidance.js +1 -1353
  72. package/dist/cli/extension-command-help.js +5 -636
  73. package/dist/cli/extension-command-options.js +1 -427
  74. package/dist/cli/extension-recovery.js +1 -126
  75. package/dist/cli/help-content.js +2 -1001
  76. package/dist/cli/help-json-payload.js +4 -570
  77. package/dist/cli/main.d.ts +4 -0
  78. package/dist/cli/main.js +5 -2573
  79. package/dist/cli/migration-gates.js +1 -289
  80. package/dist/cli/public.js +1 -20
  81. package/dist/cli/register-annotations.js +1 -270
  82. package/dist/cli/register-assurance.js +1 -62
  83. package/dist/cli/register-files-lookup.js +1 -56
  84. package/dist/cli/register-history-activity.d.ts +8 -0
  85. package/dist/cli/register-history-activity.js +2 -0
  86. package/dist/cli/register-history-author.js +1 -38
  87. package/dist/cli/register-history-maintenance.d.ts +9 -0
  88. package/dist/cli/register-history-maintenance.js +2 -0
  89. package/dist/cli/register-list-query.js +10 -943
  90. package/dist/cli/register-mutation-options.js +1 -9
  91. package/dist/cli/register-mutation.js +19 -1893
  92. package/dist/cli/register-operations.js +1 -920
  93. package/dist/cli/register-setup.js +1 -716
  94. package/dist/cli/register-structured-mutation.js +1 -196
  95. package/dist/cli/registration-helpers.js +1 -5
  96. package/dist/cli/runtime-compatibility-boundary.js +4 -42
  97. package/dist/cli/schema-registration-helpers.js +1 -73
  98. package/dist/cli/search-refresh.js +1 -13
  99. package/dist/cli/stats-analytics-json.js +1 -68
  100. package/dist/cli/telemetry-flush.js +1 -10
  101. package/dist/cli-bundle/bundle-manifest.json +164 -156
  102. package/dist/cli-bundle/chunks/chunk-3BZZA6A3.js +5 -0
  103. package/dist/cli-bundle/chunks/chunk-AMRDVX36.js +235 -0
  104. package/dist/cli-bundle/chunks/chunk-DAM4CBBR.js +2202 -0
  105. package/dist/cli-bundle/chunks/{chunk-WYRZZ5ZE.js → chunk-DEZPZOQ7.js} +2 -2
  106. package/dist/cli-bundle/chunks/chunk-FLDWYEND.js +77 -0
  107. package/dist/cli-bundle/chunks/{chunk-NPN5I67J.js → chunk-G5ADGQ2D.js} +3 -3
  108. package/dist/cli-bundle/chunks/chunk-JAAEYHUA.js +2 -0
  109. package/dist/cli-bundle/chunks/{chunk-JX4DH4WC.js → chunk-KR3RP4FH.js} +3 -3
  110. package/dist/cli-bundle/chunks/chunk-LEZOUXG6.js +2 -0
  111. package/dist/cli-bundle/chunks/chunk-NSAPNEYH.js +2 -0
  112. package/dist/cli-bundle/chunks/chunk-QOEKOECV.js +29 -0
  113. package/dist/cli-bundle/chunks/chunk-RHSFOMHU.js +2 -0
  114. package/dist/cli-bundle/chunks/chunk-XOD2PCB6.js +20 -0
  115. package/dist/cli-bundle/chunks/{chunk-JT6XF6GE.js → chunk-YVEQ2AMO.js} +4 -4
  116. package/dist/cli-bundle/chunks/register-list-query-KSK3R63M.js +11 -0
  117. package/dist/cli-bundle/chunks/register-mutation-IQ5C7U6O.js +20 -0
  118. package/dist/cli-bundle/chunks/{register-operations-I5W3KMGB.js → register-operations-DWVHR2NC.js} +2 -2
  119. package/dist/cli-bundle/chunks/register-setup-BQ5SVD4B.js +2 -0
  120. package/dist/cli-bundle/focused-chunks/chunk-3WRPWLA7.js +3 -0
  121. package/dist/cli-bundle/focused-chunks/chunk-7MBUII7U.js +2 -0
  122. package/dist/cli-bundle/focused-chunks/chunk-COPBO5HW.js +8 -0
  123. package/dist/cli-bundle/focused-chunks/chunk-DUN4IXYN.js +20 -0
  124. package/dist/cli-bundle/focused-chunks/{chunk-LIJIA4TY.js → chunk-EE5QSM4Z.js} +3 -3
  125. package/dist/cli-bundle/focused-chunks/chunk-HJVARYM3.js +6 -0
  126. package/dist/cli-bundle/focused-chunks/chunk-IAUOUGVR.js +177 -0
  127. package/dist/cli-bundle/focused-chunks/chunk-KOOMJ54F.js +2 -0
  128. package/dist/cli-bundle/focused-chunks/chunk-MZDXX7JY.js +56 -0
  129. package/dist/cli-bundle/focused-chunks/{chunk-M6PBRFSH.js → chunk-OPFHFGDD.js} +2 -2
  130. package/dist/cli-bundle/focused-chunks/chunk-QHNETP77.js +9 -0
  131. package/dist/cli-bundle/focused-chunks/{chunk-3BZEOXLT.js → chunk-RMWES4M6.js} +2 -2
  132. package/dist/cli-bundle/focused-chunks/chunk-RXT4P5QH.js +10 -0
  133. package/dist/cli-bundle/focused-chunks/chunk-SDOL6FM2.js +12 -0
  134. package/dist/cli-bundle/focused-chunks/chunk-SQV4AAUC.js +2 -0
  135. package/dist/cli-bundle/focused-chunks/{chunk-PEFABF7E.js → chunk-WE3IUNIW.js} +3 -3
  136. package/dist/cli-bundle/focused-chunks/chunk-YALOUBWJ.js +2 -0
  137. package/dist/cli-bundle/main.js +20 -20
  138. package/dist/cli-bundle/sdk-authoring.js +1 -1
  139. package/dist/cli-bundle/sdk-contracts.js +2 -2
  140. package/dist/cli-bundle/sdk-core.js +29 -29
  141. package/dist/cli-bundle/sdk-governance.js +1 -1
  142. package/dist/cli-bundle/sdk-graph.js +1 -1
  143. package/dist/cli-bundle/sdk-merge.js +42 -42
  144. package/dist/cli-bundle/sdk-query.js +1 -1
  145. package/dist/cli-bundle/sdk-runtime.js +1 -1
  146. package/dist/cli-bundle/sdk-testing.js +1 -1
  147. package/dist/cli-bundle/sdk.js +11 -11
  148. package/dist/cli.js +1 -104
  149. package/dist/core/checkpoint/checkpoint-gc.js +1 -169
  150. package/dist/core/checkpoint/mutation-checkpoint.js +2 -133
  151. package/dist/core/config/nested-settings.js +1 -534
  152. package/dist/core/config/positional-value.js +1 -109
  153. package/dist/core/diagnostics/remediation.js +1 -506
  154. package/dist/core/extensions/activation-summary-markdown.js +2 -145
  155. package/dist/core/extensions/activation-summary.js +1 -92
  156. package/dist/core/extensions/activation-transaction.js +1 -46
  157. package/dist/core/extensions/capability-usage.js +1 -173
  158. package/dist/core/extensions/command-hook-context.js +1 -13
  159. package/dist/core/extensions/command-metadata-contract.js +1 -22
  160. package/dist/core/extensions/command-visibility-tier.js +1 -29
  161. package/dist/core/extensions/contribution-inventory.js +1 -173
  162. package/dist/core/extensions/exporter-output-contract.js +1 -66
  163. package/dist/core/extensions/extension-candidate.js +1 -18
  164. package/dist/core/extensions/extension-capability-aliases.js +1 -158
  165. package/dist/core/extensions/extension-hook-runtime.js +1 -699
  166. package/dist/core/extensions/extension-policy.js +1 -599
  167. package/dist/core/extensions/extension-registries.js +1 -62
  168. package/dist/core/extensions/extension-runtime-helpers.js +1 -33
  169. package/dist/core/extensions/extension-types.js +1 -102
  170. package/dist/core/extensions/flag-definition-validation.js +1 -52
  171. package/dist/core/extensions/flag-value-types.js +1 -64
  172. package/dist/core/extensions/index.js +1 -526
  173. package/dist/core/extensions/item-field-types.js +1 -39
  174. package/dist/core/extensions/item-fields.js +1 -261
  175. package/dist/core/extensions/loader.js +1 -2223
  176. package/dist/core/extensions/manifest-schema.js +1 -69
  177. package/dist/core/extensions/module-graph-snapshot.js +1 -33
  178. package/dist/core/extensions/preflight-ownership.js +1 -41
  179. package/dist/core/extensions/registration-validation.js +1 -82
  180. package/dist/core/extensions/registration-values.js +1 -209
  181. package/dist/core/extensions/reserved-host-flags.js +1 -36
  182. package/dist/core/extensions/runtime-registrations.js +1 -67
  183. package/dist/core/extensions/service-ownership.js +1 -19
  184. package/dist/core/extensions/version-compat.js +1 -216
  185. package/dist/core/fs/fs-utils.js +2 -158
  186. package/dist/core/fs/host-environment-errors.js +1 -141
  187. package/dist/core/fs/index.js +1 -9
  188. package/dist/core/fs/path-utils.js +1 -17
  189. package/dist/core/governance/content-fields.js +1 -160
  190. package/dist/core/governance/issue-codes.js +1 -151
  191. package/dist/core/governance/metadata-coverage.js +1 -358
  192. package/dist/core/history/drift-cache.js +3 -72
  193. package/dist/core/history/drift-scan.js +1 -342
  194. package/dist/core/history/event-classification.js +1 -109
  195. package/dist/core/history/event-index.js +8 -784
  196. package/dist/core/history/history-compact-bulk.js +1 -104
  197. package/dist/core/history/history-diff.js +1 -158
  198. package/dist/core/history/history-rewrite.d.ts +6 -1
  199. package/dist/core/history/history-rewrite.js +1 -107
  200. package/dist/core/history/history-storage-stats.js +1 -102
  201. package/dist/core/history/history-stream-policy.js +1 -62
  202. package/dist/core/history/history.js +2 -617
  203. package/dist/core/history/identity.js +1 -30
  204. package/dist/core/history/index.js +1 -9
  205. package/dist/core/history/projection.js +1 -196
  206. package/dist/core/history/read.d.ts +9 -0
  207. package/dist/core/history/read.js +1 -57
  208. package/dist/core/history/replay.js +2 -439
  209. package/dist/core/history/version-address.js +1 -32
  210. package/dist/core/history/workspace-history.js +2 -433
  211. package/dist/core/io/body-file.js +1 -62
  212. package/dist/core/io/bulk-ids-input.js +1 -112
  213. package/dist/core/item/actionability.js +1 -327
  214. package/dist/core/item/dependency-reference.js +1 -20
  215. package/dist/core/item/id.js +1 -112
  216. package/dist/core/item/index.js +1 -12
  217. package/dist/core/item/item-format-version.js +1 -78
  218. package/dist/core/item/item-format.js +5 -1473
  219. package/dist/core/item/item-record.js +1 -23
  220. package/dist/core/item/item-type-definition.js +1 -133
  221. package/dist/core/item/parent-reference-policy.js +1 -57
  222. package/dist/core/item/parse.js +2 -603
  223. package/dist/core/item/plan-lifecycle.js +1 -19
  224. package/dist/core/item/priority.js +1 -61
  225. package/dist/core/item/sprint-release-format.js +1 -38
  226. package/dist/core/item/status-filter.js +1 -115
  227. package/dist/core/item/status.js +1 -43
  228. package/dist/core/item/toon-decode.js +1 -29
  229. package/dist/core/item/type-registry.js +1 -715
  230. package/dist/core/item/type-synonyms.js +1 -38
  231. package/dist/core/lock/index.js +1 -9
  232. package/dist/core/lock/lock-gc.js +1 -240
  233. package/dist/core/lock/lock.js +3 -590
  234. package/dist/core/output/mutation-projection.js +1 -217
  235. package/dist/core/output/output-control.js +1 -25
  236. package/dist/core/output/output.js +11 -494
  237. package/dist/core/output/query-summary.js +1 -58
  238. package/dist/core/output/tabular.js +1 -77
  239. package/dist/core/packages/manifest.js +1 -244
  240. package/dist/core/packages/root.js +1 -84
  241. package/dist/core/profile/profile-describe.js +1 -83
  242. package/dist/core/profile/profile-lint.js +1 -415
  243. package/dist/core/profile/profile-plan.js +0 -0
  244. package/dist/core/profile/profile-presets.js +1 -370
  245. package/dist/core/reproducibility/context.js +1 -82
  246. package/dist/core/schema/fields-file.js +2 -288
  247. package/dist/core/schema/item-types-file.js +2 -296
  248. package/dist/core/schema/runtime-field-filters.js +1 -49
  249. package/dist/core/schema/runtime-field-values.js +1 -313
  250. package/dist/core/schema/runtime-schema.js +2 -826
  251. package/dist/core/schema/status-defs-file.js +2 -227
  252. package/dist/core/schema/status-token.js +1 -31
  253. package/dist/core/schema/type-inference.js +1 -95
  254. package/dist/core/schema/type-presets.js +1 -87
  255. package/dist/core/schema/type-workflows.js +1 -104
  256. package/dist/core/search/background-refresh.js +2 -286
  257. package/dist/core/search/bm25.js +1 -146
  258. package/dist/core/search/cache.js +2 -534
  259. package/dist/core/search/corpus.js +1 -311
  260. package/dist/core/search/embedding-batches.js +1 -163
  261. package/dist/core/search/eval.js +1 -225
  262. package/dist/core/search/http-client.js +1 -75
  263. package/dist/core/search/providers.js +1 -242
  264. package/dist/core/search/relevance.js +1 -244
  265. package/dist/core/search/semantic-defaults.js +1 -199
  266. package/dist/core/search/staleness.js +1 -30
  267. package/dist/core/search/vector-stores.js +2 -664
  268. package/dist/core/search/vectorization-metadata.js +1 -81
  269. package/dist/core/sentry/helpers.js +1 -272
  270. package/dist/core/sentry/instrument.js +1 -387
  271. package/dist/core/session/session-state.js +1 -260
  272. package/dist/core/shared/agent-session-context.js +1 -133
  273. package/dist/core/shared/author.js +1 -1172
  274. package/dist/core/shared/command-types.js +1 -4
  275. package/dist/core/shared/conflict-markers.js +1 -31
  276. package/dist/core/shared/constants.js +1 -405
  277. package/dist/core/shared/errors.js +1 -20
  278. package/dist/core/shared/html-entity-decode.js +1 -131
  279. package/dist/core/shared/index.js +1 -12
  280. package/dist/core/shared/item-metadata-contract.js +1 -89
  281. package/dist/core/shared/lazy-module.js +1 -16
  282. package/dist/core/shared/levenshtein.js +1 -61
  283. package/dist/core/shared/memo.js +1 -19
  284. package/dist/core/shared/numeric-parsers.js +1 -20
  285. package/dist/core/shared/option-alias-visibility.js +1 -67
  286. package/dist/core/shared/primitives.js +1 -127
  287. package/dist/core/shared/serial-queue.js +1 -79
  288. package/dist/core/shared/serialization.js +1 -210
  289. package/dist/core/shared/split-comma-list.js +1 -30
  290. package/dist/core/shared/text-normalization.js +1 -38
  291. package/dist/core/shared/time.js +1 -306
  292. package/dist/core/store/index.js +1 -12
  293. package/dist/core/store/item-format-migration.js +1 -125
  294. package/dist/core/store/item-metadata-cache.js +1 -1116
  295. package/dist/core/store/item-metadata-query-index.js +9 -394
  296. package/dist/core/store/item-metadata-read-work.js +1 -43
  297. package/dist/core/store/item-store.js +1 -693
  298. package/dist/core/store/paths.d.ts +1 -1
  299. package/dist/core/store/paths.js +1 -214
  300. package/dist/core/store/settings-read-cache.js +1 -85
  301. package/dist/core/store/settings-validator.js +1 -421
  302. package/dist/core/store/settings.js +3 -1309
  303. package/dist/core/store/tracker-preflight.js +1 -141
  304. package/dist/core/telemetry/consent.js +5 -108
  305. package/dist/core/telemetry/observability.js +1 -246
  306. package/dist/core/telemetry/runtime.js +4 -1988
  307. package/dist/core/test/background-runs.js +4 -945
  308. package/dist/core/test/item-test-run-tracking.js +1 -63
  309. package/dist/core/test/run-selectors.js +1 -120
  310. package/dist/core/validate/estimate-defaults.js +1 -89
  311. package/dist/core/validate/fix-planning.js +1 -189
  312. package/dist/core/validate/linked-artifact-reference.js +1 -36
  313. package/dist/core/validate/missing-by-type.js +1 -34
  314. package/dist/core/validate/missing-link-owners.js +1 -95
  315. package/dist/core/validate/stale-file-classification.js +1 -82
  316. package/dist/mcp/http-server.js +5 -448
  317. package/dist/mcp/legacy-adapter.js +1 -77
  318. package/dist/mcp/runtime-capabilities.js +1 -200
  319. package/dist/mcp/server.js +4 -1233
  320. package/dist/mcp/tool-definitions.js +1 -804
  321. package/dist/sdk/actionability.js +1 -13
  322. package/dist/sdk/agent/closed-domain-contracts.js +1 -161
  323. package/dist/sdk/agent/command-recovery.js +1 -59
  324. package/dist/sdk/agent/command-suggestions.js +1 -66
  325. package/dist/sdk/agent/create-option-policy.js +1 -31
  326. package/dist/sdk/agent/identity-config.js +1 -106
  327. package/dist/sdk/agent/item-addressing.js +1 -207
  328. package/dist/sdk/agent/refusal-closure-census.js +1 -151
  329. package/dist/sdk/agent/refusal-closure.js +1 -133
  330. package/dist/sdk/agent/refusal-corpus-contracts.js +1 -193
  331. package/dist/sdk/agent/refusal-reachability.js +1 -593
  332. package/dist/sdk/agent/subcommand-recovery.js +1 -83
  333. package/dist/sdk/agent/task-transcript-contracts.js +1 -197
  334. package/dist/sdk/agent/tracker-preflight-contracts.js +1 -126
  335. package/dist/sdk/agent-capability-contracts.js +1 -428
  336. package/dist/sdk/agent-session-context.js +1 -9
  337. package/dist/sdk/annotations.js +1 -448
  338. package/dist/sdk/author-attribution.js +1 -468
  339. package/dist/sdk/authoring.js +1 -20
  340. package/dist/sdk/cli-bootstrap.js +1 -838
  341. package/dist/sdk/cli-contracts/agent-output-contracts.d.ts +1 -1
  342. package/dist/sdk/cli-contracts/agent-output-contracts.js +4 -476
  343. package/dist/sdk/cli-contracts/bootstrap-command-scanner.js +1 -79
  344. package/dist/sdk/cli-contracts/command-aliases.d.ts +4 -0
  345. package/dist/sdk/cli-contracts/command-aliases.js +1 -107
  346. package/dist/sdk/cli-contracts/command-exit-contracts.js +1 -151
  347. package/dist/sdk/cli-contracts/commander-mutation-options.js +1 -1081
  348. package/dist/sdk/cli-contracts/commander-types.js +1 -159
  349. package/dist/sdk/cli-contracts/completeness.js +1 -393
  350. package/dist/sdk/cli-contracts/enum-contracts.js +1 -222
  351. package/dist/sdk/cli-contracts/flag-contracts.js +1 -1947
  352. package/dist/sdk/cli-contracts/flag-lexicon-contracts.js +1 -347
  353. package/dist/sdk/cli-contracts/grammar-contracts.js +1 -986
  354. package/dist/sdk/cli-contracts/registration-helpers.js +7 -990
  355. package/dist/sdk/cli-contracts/runtime-contracts.d.ts +5 -0
  356. package/dist/sdk/cli-contracts/runtime-contracts.js +1 -2090
  357. package/dist/sdk/cli-contracts/schemas/assurance-definition.js +1 -324
  358. package/dist/sdk/cli-contracts/string-lists.js +1 -16
  359. package/dist/sdk/cli-contracts/tool-option-contracts.js +1 -461
  360. package/dist/sdk/cli-contracts/tool-parameter-tables.js +1 -2167
  361. package/dist/sdk/cli-contracts/tool-schema.d.ts +3 -1
  362. package/dist/sdk/cli-contracts/tool-schema.js +1 -1664
  363. package/dist/sdk/cli-contracts/typed-action-inputs.js +1 -4
  364. package/dist/sdk/cli-contracts.d.ts +1 -1
  365. package/dist/sdk/cli-contracts.js +1 -17
  366. package/dist/sdk/cli-program.js +1 -49
  367. package/dist/sdk/command-line.d.ts +2 -5
  368. package/dist/sdk/command-line.js +1 -78
  369. package/dist/sdk/comments.js +1 -133
  370. package/dist/sdk/compile-cache.js +1 -73
  371. package/dist/sdk/completion.js +157 -837
  372. package/dist/sdk/compose.js +1 -1090
  373. package/dist/sdk/config.js +1 -1204
  374. package/dist/sdk/context/recency.js +1 -33
  375. package/dist/sdk/context/semantic-session-attribution.js +1 -82
  376. package/dist/sdk/context/usage-egress.js +1 -29
  377. package/dist/sdk/context/usage-ledger.js +3 -185
  378. package/dist/sdk/context-intent-contracts.js +1 -709
  379. package/dist/sdk/context-intent-runtime.js +1 -115
  380. package/dist/sdk/context-packing.js +1 -218
  381. package/dist/sdk/context-relevance.js +1 -503
  382. package/dist/sdk/context-signal-store.js +2 -426
  383. package/dist/sdk/context-usage.js +1 -367
  384. package/dist/sdk/contracts.js +1 -23
  385. package/dist/sdk/core-governance.js +1 -14
  386. package/dist/sdk/core.js +1 -14
  387. package/dist/sdk/corpus-shape.js +1 -401
  388. package/dist/sdk/define.js +1 -220
  389. package/dist/sdk/dependencies.js +1 -718
  390. package/dist/sdk/dependency-flag-validation.js +1 -156
  391. package/dist/sdk/dependency-provenance.js +1 -135
  392. package/dist/sdk/docs.js +1 -14
  393. package/dist/sdk/duplicates.js +1 -40
  394. package/dist/sdk/environment/agent-environment.js +1 -466
  395. package/dist/sdk/environment/host-environment-errors.js +1 -8
  396. package/dist/sdk/environment/project-runtime-compatibility.js +1 -481
  397. package/dist/sdk/environment/source-context.js +1 -28
  398. package/dist/sdk/environment/tracker-preflight.js +1 -9
  399. package/dist/sdk/error-code-catalog.js +1 -153
  400. package/dist/sdk/error-runtime.js +1 -58
  401. package/dist/sdk/errors.js +1 -58
  402. package/dist/sdk/eval.js +1 -190
  403. package/dist/sdk/extension/author-manifest.js +1 -92
  404. package/dist/sdk/extension/bundled-catalog.js +1 -332
  405. package/dist/sdk/extension/concurrency.js +1 -25
  406. package/dist/sdk/extension/copy-scope.d.ts +2 -0
  407. package/dist/sdk/extension/copy-scope.js +2 -0
  408. package/dist/sdk/extension/custom-field-collisions.js +1 -30
  409. package/dist/sdk/extension/describe.js +3 -189
  410. package/dist/sdk/extension/doctor.js +1 -606
  411. package/dist/sdk/extension/install-plan.d.ts +69 -0
  412. package/dist/sdk/extension/install-plan.js +2 -0
  413. package/dist/sdk/extension/install-receipts.d.ts +7 -0
  414. package/dist/sdk/extension/install-receipts.js +2 -0
  415. package/dist/sdk/extension/install-runtime.js +3 -313
  416. package/dist/sdk/extension/install-snapshot.js +1 -66
  417. package/dist/sdk/extension/install-sources.js +5 -1057
  418. package/dist/sdk/extension/managed-state.js +2 -252
  419. package/dist/sdk/extension/migrations.js +2 -174
  420. package/dist/sdk/extension/output-ownership.js +1 -25
  421. package/dist/sdk/extension/runtime-summary.js +1 -18
  422. package/dist/sdk/extension/scaffold.js +4 -2308
  423. package/dist/sdk/extension/shared.js +1 -147
  424. package/dist/sdk/extension/source-resolution.js +1 -65
  425. package/dist/sdk/extension/update-check.js +1 -67
  426. package/dist/sdk/extension-command-context.js +1 -44
  427. package/dist/sdk/extension-contracts.js +1 -73
  428. package/dist/sdk/extension.d.ts +7 -2
  429. package/dist/sdk/extension.js +1 -2196
  430. package/dist/sdk/files.js +1 -535
  431. package/dist/sdk/flag-invocation-contracts.d.ts +0 -5
  432. package/dist/sdk/flag-invocation-contracts.js +1 -344
  433. package/dist/sdk/generated/generated-error-code-catalog-part-1.js +1 -2224
  434. package/dist/sdk/generated/generated-error-code-catalog-part-2.js +1 -2236
  435. package/dist/sdk/generated-error-code-catalog.js +1 -16
  436. package/dist/sdk/governance/assurance-action-contracts.js +1 -4
  437. package/dist/sdk/governance/assurance-action.js +1 -411
  438. package/dist/sdk/governance/assurance-limits.js +1 -10
  439. package/dist/sdk/governance/assurance-mutation-error.js +1 -81
  440. package/dist/sdk/governance/assurance-presets.js +1 -135
  441. package/dist/sdk/governance/assurance-runtime.js +1 -238
  442. package/dist/sdk/governance/assurance.js +4 -1182
  443. package/dist/sdk/governance/boundary-fixtures.js +1 -200
  444. package/dist/sdk/governance/coverage-assurance.js +1 -60
  445. package/dist/sdk/governance/defect-recurrence-coverage.js +1 -188
  446. package/dist/sdk/governance/defect-recurrence-graph.js +1 -120
  447. package/dist/sdk/governance/defect-recurrence-signals.js +1 -24
  448. package/dist/sdk/governance/defect-recurrence.js +1 -623
  449. package/dist/sdk/governance/extension-host-version.js +1 -115
  450. package/dist/sdk/governance/gc.js +1 -368
  451. package/dist/sdk/governance/health.js +1 -2367
  452. package/dist/sdk/governance/hierarchy-validation.js +1 -108
  453. package/dist/sdk/governance/linked-file-report.js +1 -71
  454. package/dist/sdk/governance/merge-receipt-history.js +1 -206
  455. package/dist/sdk/governance/provenance-health.js +1 -282
  456. package/dist/sdk/governance/reindex.js +4 -761
  457. package/dist/sdk/governance/stale-work.js +1 -82
  458. package/dist/sdk/governance/status-role-diagnostics.js +1 -35
  459. package/dist/sdk/governance/storage-integrity.js +1 -280
  460. package/dist/sdk/governance/tracked-runtime-cache.js +1 -106
  461. package/dist/sdk/governance/upgrade.js +2 -327
  462. package/dist/sdk/governance/validate-history-drift.js +1 -81
  463. package/dist/sdk/governance/validate-item-reader.js +1 -17
  464. package/dist/sdk/governance/validate-normalization.js +1 -36
  465. package/dist/sdk/governance/validate.js +1 -2261
  466. package/dist/sdk/governance/workspace-position.js +1 -163
  467. package/dist/sdk/governance.d.ts +1 -0
  468. package/dist/sdk/governance.js +1 -31
  469. package/dist/sdk/graph/adapter.js +1 -181
  470. package/dist/sdk/graph/analytics.js +1 -474
  471. package/dist/sdk/graph/assembly.js +1 -377
  472. package/dist/sdk/graph/cache.js +1 -160
  473. package/dist/sdk/graph/centrality.js +1 -258
  474. package/dist/sdk/graph/conformance.js +1 -130
  475. package/dist/sdk/graph/deadline-scheduling.d.ts +90 -0
  476. package/dist/sdk/graph/deadline-scheduling.js +2 -0
  477. package/dist/sdk/graph/durable-cache.d.ts +1 -1
  478. package/dist/sdk/graph/durable-cache.js +1 -361
  479. package/dist/sdk/graph/governance-contracts.js +1 -34
  480. package/dist/sdk/graph/governance.d.ts +13 -1
  481. package/dist/sdk/graph/governance.js +1 -735
  482. package/dist/sdk/graph/hierarchy-integrity.js +1 -310
  483. package/dist/sdk/graph/index.d.ts +3 -2
  484. package/dist/sdk/graph/index.js +1 -26
  485. package/dist/sdk/graph/lifecycle-coverage.d.ts +32 -0
  486. package/dist/sdk/graph/lifecycle-coverage.js +2 -0
  487. package/dist/sdk/graph/mutation-advisory.js +1 -176
  488. package/dist/sdk/graph/remediation.js +1 -197
  489. package/dist/sdk/graph/run.d.ts +15 -1
  490. package/dist/sdk/graph/run.js +1 -768
  491. package/dist/sdk/graph/scheduling.js +1 -124
  492. package/dist/sdk/graph/traversal.js +1 -284
  493. package/dist/sdk/graph/workspace-relationship-kind-registry.js +1 -27
  494. package/dist/sdk/graph.js +1 -15
  495. package/dist/sdk/guide-topics.js +1 -707
  496. package/dist/sdk/guide.js +1 -279
  497. package/dist/sdk/history/maintenance.d.ts +71 -0
  498. package/dist/sdk/history/maintenance.js +2 -0
  499. package/dist/sdk/history/salvage.js +2 -247
  500. package/dist/sdk/history/subject.d.ts +19 -0
  501. package/dist/sdk/history/subject.js +2 -0
  502. package/dist/sdk/history-analytics.js +1 -282
  503. package/dist/sdk/history-compact.js +1 -527
  504. package/dist/sdk/history-mcp.js +1 -68
  505. package/dist/sdk/history-provenance.js +1 -182
  506. package/dist/sdk/history-read.js +1 -96
  507. package/dist/sdk/history-redact.d.ts +1 -14
  508. package/dist/sdk/history-redact.js +1 -643
  509. package/dist/sdk/history-repair.d.ts +5 -0
  510. package/dist/sdk/history-repair.js +1 -657
  511. package/dist/sdk/improvement-ledger-validation.js +1 -62
  512. package/dist/sdk/improvement-ledger.js +2 -257
  513. package/dist/sdk/index.d.ts +4 -0
  514. package/dist/sdk/index.js +1 -183
  515. package/dist/sdk/init-agent-guidance.js +2 -496
  516. package/dist/sdk/init.js +2 -820
  517. package/dist/sdk/invocation-author.js +1 -39
  518. package/dist/sdk/item-children.js +1 -90
  519. package/dist/sdk/item-metadata-index.js +1 -15
  520. package/dist/sdk/item-schedule.js +1 -27
  521. package/dist/sdk/item-transaction.js +1 -396
  522. package/dist/sdk/learnings.js +1 -29
  523. package/dist/sdk/lifecycle/append.js +2 -53
  524. package/dist/sdk/lifecycle/claim.d.ts +2 -0
  525. package/dist/sdk/lifecycle/claim.js +1 -242
  526. package/dist/sdk/lifecycle/close-many.js +1 -438
  527. package/dist/sdk/lifecycle/close.js +1 -468
  528. package/dist/sdk/lifecycle/copy.js +1 -212
  529. package/dist/sdk/lifecycle/create.js +1 -1949
  530. package/dist/sdk/lifecycle/delete.js +1 -45
  531. package/dist/sdk/lifecycle/event-validation-messages.js +1 -91
  532. package/dist/sdk/lifecycle/focus.js +1 -91
  533. package/dist/sdk/lifecycle/hierarchy-mutation-lock.js +1 -24
  534. package/dist/sdk/lifecycle/legacy-none-tokens.js +1 -149
  535. package/dist/sdk/lifecycle/lifecycle-transitions.js +1 -40
  536. package/dist/sdk/lifecycle/mcp-actions.d.ts +2 -0
  537. package/dist/sdk/lifecycle/mcp-actions.js +1 -51
  538. package/dist/sdk/lifecycle/metadata-normalizers.js +1 -46
  539. package/dist/sdk/lifecycle/mutation-command-options.js +1 -9
  540. package/dist/sdk/lifecycle/plan.js +1 -1606
  541. package/dist/sdk/lifecycle/recurrence-parsers.js +1 -147
  542. package/dist/sdk/lifecycle/reopen.js +1 -68
  543. package/dist/sdk/lifecycle/repeatable-metadata-parsers.js +1 -143
  544. package/dist/sdk/lifecycle/restore.d.ts +1 -0
  545. package/dist/sdk/lifecycle/restore.js +1 -330
  546. package/dist/sdk/lifecycle/shared-unset-fields.js +1 -295
  547. package/dist/sdk/lifecycle/update-many.js +1 -861
  548. package/dist/sdk/lifecycle/update.js +1 -2136
  549. package/dist/sdk/lifecycle-completion.js +1 -92
  550. package/dist/sdk/lifecycle-policy.js +1 -120
  551. package/dist/sdk/linked-artifacts.js +1 -530
  552. package/dist/sdk/mcp/apps.js +4 -133
  553. package/dist/sdk/mcp/authorization.js +1 -404
  554. package/dist/sdk/mcp/discovery.js +1 -494
  555. package/dist/sdk/mcp/interactions.js +1 -336
  556. package/dist/sdk/mcp/protocol.d.ts +7 -0
  557. package/dist/sdk/mcp/protocol.js +1 -184
  558. package/dist/sdk/mcp/skills.js +1 -389
  559. package/dist/sdk/mcp/subscriptions.js +1 -211
  560. package/dist/sdk/mcp/tasks.js +2 -430
  561. package/dist/sdk/mcp/transport.js +1 -260
  562. package/dist/sdk/merge/abandoned-receipts.js +1 -46
  563. package/dist/sdk/merge/driver.js +1 -212
  564. package/dist/sdk/merge/index.js +1 -16
  565. package/dist/sdk/merge/install.js +3 -561
  566. package/dist/sdk/merge/receipt-file-boundary.js +1 -46
  567. package/dist/sdk/merge/receipt-operation.js +1 -96
  568. package/dist/sdk/merge/receipt-schema.js +1 -397
  569. package/dist/sdk/merge/receipts.js +4 -525
  570. package/dist/sdk/merge/reconcile.js +1 -352
  571. package/dist/sdk/merge/three-way.js +3 -775
  572. package/dist/sdk/merge.js +1 -10
  573. package/dist/sdk/mutation-events.js +1 -292
  574. package/dist/sdk/mutation-guard.js +1 -216
  575. package/dist/sdk/notes.js +1 -139
  576. package/dist/sdk/output-contracts.js +1 -172
  577. package/dist/sdk/output-projection.js +1 -430
  578. package/dist/sdk/output-token-accounting.js +1 -140
  579. package/dist/sdk/output.js +1 -64
  580. package/dist/sdk/ownership-source.js +1 -21
  581. package/dist/sdk/package-import-adapters.js +1 -442
  582. package/dist/sdk/package-migrations.js +1 -20
  583. package/dist/sdk/package-runtime-options.js +1 -80
  584. package/dist/sdk/pagination.js +1 -162
  585. package/dist/sdk/profile.js +1 -454
  586. package/dist/sdk/provenance.js +1 -258
  587. package/dist/sdk/public-surface.json +1 -1
  588. package/dist/sdk/query/activity.js +1 -401
  589. package/dist/sdk/query/aggregate.d.ts +16 -0
  590. package/dist/sdk/query/aggregate.js +1 -390
  591. package/dist/sdk/query/calendar.js +1 -964
  592. package/dist/sdk/query/complete-list.js +1 -215
  593. package/dist/sdk/query/context.js +1 -1702
  594. package/dist/sdk/query/duplicate-candidates.js +1 -70
  595. package/dist/sdk/query/get.js +1 -450
  596. package/dist/sdk/query/history.js +1 -186
  597. package/dist/sdk/query/item-filter-options.js +1 -9
  598. package/dist/sdk/query/light-metadata.js +1 -18
  599. package/dist/sdk/query/list-filter-shared.js +1 -81
  600. package/dist/sdk/query/list.js +1 -1304
  601. package/dist/sdk/query/multi-value-filters.js +1 -78
  602. package/dist/sdk/query/next.js +1 -513
  603. package/dist/sdk/query/parsers.js +1 -44
  604. package/dist/sdk/query/projection-contracts.js +1 -184
  605. package/dist/sdk/query/search-contracts.js +1 -230
  606. package/dist/sdk/query/search-pagination.js +1 -35
  607. package/dist/sdk/query/search-rendering.js +1 -105
  608. package/dist/sdk/query/search.js +2 -1987
  609. package/dist/sdk/query.js +1 -35
  610. package/dist/sdk/read-output/continuation.js +1 -194
  611. package/dist/sdk/read-output-budget.js +1 -135
  612. package/dist/sdk/read-output-contracts.js +1 -1225
  613. package/dist/sdk/read-output-rows.js +1 -186
  614. package/dist/sdk/read-output-session.js +1 -208
  615. package/dist/sdk/relationship-analytics.js +1 -410
  616. package/dist/sdk/relationship-context.js +1 -284
  617. package/dist/sdk/relationship-history.js +4 -519
  618. package/dist/sdk/relationship-kinds/contract.js +1 -22
  619. package/dist/sdk/relationship-migration.js +1 -77
  620. package/dist/sdk/relationships.js +1 -579
  621. package/dist/sdk/reproducibility/process.js +1 -87
  622. package/dist/sdk/runtime-action-aliases.js +1 -59
  623. package/dist/sdk/runtime-extended-actions.js +1 -177
  624. package/dist/sdk/runtime-input.js +1 -614
  625. package/dist/sdk/runtime-primitives.js +1 -84
  626. package/dist/sdk/runtime-public-contracts.js +1 -4
  627. package/dist/sdk/runtime-stats-options.js +1 -38
  628. package/dist/sdk/runtime.js +1 -1928
  629. package/dist/sdk/scheduling-shortcuts.js +1 -63
  630. package/dist/sdk/schema-migration.js +3 -856
  631. package/dist/sdk/schema.js +1 -1376
  632. package/dist/sdk/search-refresh-worker.js +1 -18
  633. package/dist/sdk/similarity-scoring.js +1 -53
  634. package/dist/sdk/similarity.js +1 -326
  635. package/dist/sdk/start-task-status.js +1 -14
  636. package/dist/sdk/stats.js +1 -270
  637. package/dist/sdk/structured-mutations.js +1 -580
  638. package/dist/sdk/telemetry-flush.js +1 -13
  639. package/dist/sdk/telemetry.js +1 -405
  640. package/dist/sdk/templates.js +2 -292
  641. package/dist/sdk/test/batch.js +2 -509
  642. package/dist/sdk/test/entry.js +1 -70
  643. package/dist/sdk/test/execution.js +6 -2211
  644. package/dist/sdk/test/linked-command-detection.js +1 -546
  645. package/dist/sdk/test/measurements.js +1 -91
  646. package/dist/sdk/test/parsers.js +1 -545
  647. package/dist/sdk/test/runs.js +1 -193
  648. package/dist/sdk/test/trust.js +2 -206
  649. package/dist/sdk/testing.js +4 -1391
  650. package/dist/sdk/traceability/runtime-files-lookup.js +1 -32
  651. package/dist/sdk/traceability/source-traceability.js +1 -292
  652. package/dist/sdk/tracker-root-discovery.js +1 -9
  653. package/dist/sdk/workspace-contracts-cache.js +1 -28
  654. package/dist/sdk/workspace-contracts.js +1 -33
  655. package/dist/sdk/workspace-memory.js +2 -318
  656. package/dist/sdk/workspace-recipe.js +1 -71
  657. package/dist/sdk/workspace-snapshot.js +4 -613
  658. package/dist/sdk/workspace-transaction-gc.js +1 -149
  659. package/dist/sdk/workspace-transaction.js +2 -363
  660. package/dist/sdk/workspace.js +3 -127
  661. package/dist/types/index.js +1 -9
  662. package/dist/types.js +1 -245
  663. package/docs/AGENT_GUIDE.md +1 -1
  664. package/docs/CONTEXT_ALGORITHM_PORTFOLIO.md +167 -0
  665. package/docs/DIAGNOSTIC_OUTPUT_CONTRACTS.md +9 -3
  666. package/docs/EXTENSIONS.md +1 -0
  667. package/docs/HISTORY_MAINTENANCE.md +125 -0
  668. package/docs/MCP_2026_07_28_CONFORMANCE.md +20 -10
  669. package/docs/PACKAGE_EVIDENCE.md +92 -0
  670. package/docs/PLANNING_MEASUREMENTS.md +183 -0
  671. package/docs/README.md +3 -0
  672. package/docs/SCRIPTING.md +21 -0
  673. package/docs/TESTING.md +23 -0
  674. package/docs/generated/FLAG_LEXICON_BUDGETS.md +5 -5
  675. package/docs/generated/REFUSAL_CLOSURE_CENSUS.md +2 -2
  676. package/marketplace.json +2 -2
  677. package/package.json +1 -1
  678. package/packages/pm-beads/package.json +1 -1
  679. package/packages/pm-calendar/package.json +1 -1
  680. package/packages/pm-command-kit/package.json +1 -1
  681. package/packages/pm-digital-twin/package.json +1 -1
  682. package/packages/pm-governance-audit/package.json +1 -1
  683. package/packages/pm-guide-shell/package.json +1 -1
  684. package/packages/pm-kanban/package.json +1 -1
  685. package/packages/pm-lifecycle-hooks/package.json +1 -1
  686. package/packages/pm-linked-test-adapters/package.json +1 -1
  687. package/packages/pm-search-advanced/package.json +1 -1
  688. package/packages/pm-templates/package.json +1 -1
  689. package/packages/pm-todos/package.json +1 -1
  690. package/packages/pm-vcs/package.json +1 -1
  691. package/plugins/pm-claude/.claude-plugin/plugin.json +1 -1
  692. package/plugins/pm-codex/.codex-plugin/plugin.json +1 -1
  693. package/scripts/finalize-build.mjs +45 -3
  694. package/dist/cli-bundle/chunks/chunk-AZDFJGH6.js +0 -9
  695. package/dist/cli-bundle/chunks/chunk-BAX2BYUW.js +0 -2111
  696. package/dist/cli-bundle/chunks/chunk-CKOL5U7J.js +0 -77
  697. package/dist/cli-bundle/chunks/chunk-OF76PSDQ.js +0 -5
  698. package/dist/cli-bundle/chunks/chunk-QUWFABDM.js +0 -232
  699. package/dist/cli-bundle/chunks/chunk-QWRRFYK3.js +0 -2
  700. package/dist/cli-bundle/chunks/chunk-RWWBOWJ5.js +0 -2
  701. package/dist/cli-bundle/chunks/chunk-WGAOYSYH.js +0 -40
  702. package/dist/cli-bundle/chunks/chunk-WI2AYPA7.js +0 -2
  703. package/dist/cli-bundle/chunks/register-list-query-5JL52D33.js +0 -11
  704. package/dist/cli-bundle/chunks/register-mutation-ZPG7KMVP.js +0 -20
  705. package/dist/cli-bundle/chunks/register-setup-H4SVAVX7.js +0 -2
  706. package/dist/cli-bundle/focused-chunks/chunk-AL5ON6K6.js +0 -2
  707. package/dist/cli-bundle/focused-chunks/chunk-CHC2IDXA.js +0 -6
  708. package/dist/cli-bundle/focused-chunks/chunk-CK6QFAPZ.js +0 -20
  709. package/dist/cli-bundle/focused-chunks/chunk-EZR3GHFE.js +0 -2
  710. package/dist/cli-bundle/focused-chunks/chunk-H2WJWK7L.js +0 -56
  711. package/dist/cli-bundle/focused-chunks/chunk-NTPY5GSO.js +0 -3
  712. package/dist/cli-bundle/focused-chunks/chunk-P5O7MP5Y.js +0 -2
  713. package/dist/cli-bundle/focused-chunks/chunk-PDMW2E36.js +0 -177
  714. package/dist/cli-bundle/focused-chunks/chunk-QTVCLMSZ.js +0 -6
  715. package/dist/cli-bundle/focused-chunks/chunk-QTZRBUYA.js +0 -8
  716. package/dist/cli-bundle/focused-chunks/chunk-SGQST3PQ.js +0 -12
  717. package/dist/cli-bundle/focused-chunks/chunk-UKHASMMP.js +0 -10
  718. package/dist/cli-bundle/focused-chunks/chunk-ZPEKQHOC.js +0 -2
@@ -1,2212 +1,7 @@
1
- /**
2
- * @module sdk/test/execution
3
- *
4
- * Implements the pm test command surface and its agent-facing runtime behavior.
5
- */
6
-
7
- !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]="03f2f710-3b96-5671-85fa-d8ea4726ee86")}catch(e){}}();
8
- import { assertInitializedTracker } from "../environment/tracker-preflight.js";
9
- import { spawn } from "node:child_process";
10
- import { cp, mkdir, mkdtemp, open, readdir, rm, symlink, } from "node:fs/promises";
11
- import { tmpdir } from "node:os";
12
- import path from "node:path";
13
- import { getActiveExtensionRegistrations } from "../../core/extensions/index.js";
14
- import { isFileMissingError, pathExists, readFileIfExists, } from "../../core/fs/fs-utils.js";
15
- import { getWorkspaceHistoryPath, writeWorkspaceJsonWithHistory, } from "../../core/history/workspace-history.js";
16
- import { resolveItemTypeRegistry } from "../../core/item/type-registry.js";
17
- import { createStdinTokenResolver, parseCsvKv, parseOptionalNumber, } from "../../core/item/parse.js";
18
- import { EXIT_CODE } from "../../core/shared/constants.js";
19
- import { resolveAuthor } from "../../core/shared/author.js";
20
- import { PmCliError } from "../../core/shared/errors.js";
21
- import { stableValueEquals } from "../../core/shared/serialization.js";
22
- import { nowIso } from "../../core/shared/time.js";
23
- import { locateItem, mutateItem, readLocatedItem, } from "../../core/store/item-store.js";
24
- import { getSettingsPath, ITEM_FILE_EXTENSIONS, resolveGlobalPmRoot, resolvePmRoot, } from "../../core/store/paths.js";
25
- import { readSettings } from "../../core/store/settings.js";
26
- import { appendTrackedTestRunSummary, buildTrackedTestRunHistory, } from "../../core/test/item-test-run-tracking.js";
27
- import { runInit } from "../init.js";
28
- import { looksLikeStructuredLinkedTestEntry, normalizeStructuredLinkedTestEntry, } from "./entry.js";
29
- 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";
30
- import { parseOnlyIndexValue, resolveLinkedTestRunSelection, } from "../../core/test/run-selectors.js";
31
- import { SCOPE_VALUES } from "../../types/index.js";
32
- import { diffTestRunMeasurements, parseTestRunMeasurements, queryTestRunMeasurementsBelow, } from "./measurements.js";
33
- import { withHostEnvironmentBoundary } from "../environment/host-environment-errors.js";
34
- import { SOURCE_CONTEXT_ACCESS_ENV } from "../environment/source-context.js";
35
- import { acknowledgeLinkedTests, attachLinkedTestMutationProvenance, resolveLinkedTestSourceRef, resolveLinkedTestSourceWorkspaceRoot, resolveLinkedTestTrust, } from "./trust.js";
36
- const TEST_OUTPUT_MAX_BUFFER_BYTES = 20 * 1024 * 1024;
37
- const DEFAULT_LINKED_TEST_TIMEOUT_FORCE_KILL_DELAY_MS = 3000;
38
- const DEFAULT_LINKED_TEST_HEARTBEAT_INTERVAL_MS = 10000;
39
- const DEFAULT_LINKED_TEST_PIPE_CLOSE_GRACE_MS = 5000;
40
- const MAX_LINKED_TEST_COMMAND_LABEL_LENGTH = 120;
41
- const LINKED_TEST_TRACKER_DIRS_TO_SKIP = new Set([
42
- "locks",
43
- "extensions",
44
- "runtime",
45
- ]);
46
- const LINKED_TEST_ITEM_COUNT_DIRS_TO_SKIP = new Set([
47
- "history",
48
- "index",
49
- "search",
50
- "extensions",
51
- "locks",
52
- "runtime",
53
- ]);
54
- const LINKED_TEST_INFRA_COLLISION_PATTERNS = [
55
- /eaddrinuse/i,
56
- /address already in use/i,
57
- /port\s+\d+\s+is already in use/i,
58
- /web server[^.\n]*already running/i,
59
- /failed to listen on/i,
60
- ];
61
- const LINKED_TEST_LOCK_ACQUISITION_PATTERN = /\b(?:could not|failed to|unable to)\s+acquire\b[^\n.]*\block\b/i;
62
- const LINKED_TEST_LOCK_CONTENTION_CONTEXT_PATTERN = /\b(?:already running|timed?\s*out|timeout|held by another|lock\s+is\s+held)\b/i;
63
- const PM_TRACKER_READ_SUBCOMMANDS = new Set([
64
- "activity",
65
- "calendar",
66
- "context",
67
- "ctx",
68
- "deps",
69
- "get",
70
- "health",
71
- "history",
72
- "list",
73
- "list-all",
74
- "list-blocked",
75
- "list-canceled",
76
- "list-closed",
77
- "list-draft",
78
- "list-in-progress",
79
- "list-open",
80
- "search",
81
- "stats",
82
- "test-all",
83
- "validate",
84
- ]);
85
- function readPositiveIntegerEnv(name, fallback) {
86
- const raw = process.env[name];
87
- if (typeof raw !== "string" || raw.trim().length === 0) {
88
- return fallback;
89
- }
90
- const parsed = Number.parseInt(raw, 10);
91
- if (!Number.isFinite(parsed) || parsed <= 0) {
92
- return fallback;
93
- }
94
- return parsed;
95
- }
96
- function linkedTestTimeoutForceKillDelayMs() {
97
- return readPositiveIntegerEnv("PM_LINKED_TEST_TIMEOUT_FORCE_KILL_DELAY_MS", DEFAULT_LINKED_TEST_TIMEOUT_FORCE_KILL_DELAY_MS);
98
- }
99
- function linkedTestHeartbeatIntervalMs() {
100
- return readPositiveIntegerEnv("PM_LINKED_TEST_HEARTBEAT_INTERVAL_MS", DEFAULT_LINKED_TEST_HEARTBEAT_INTERVAL_MS);
101
- }
102
- function linkedTestPipeCloseGraceMs() {
103
- return readPositiveIntegerEnv("PM_LINKED_TEST_PIPE_CLOSE_GRACE_MS", DEFAULT_LINKED_TEST_PIPE_CLOSE_GRACE_MS);
104
- }
105
- function resolveTrackedRunId(kind) {
106
- const fromEnv = process.env.PM_BACKGROUND_TEST_RUN_ID?.trim();
107
- if (fromEnv && fromEnv.length > 0) {
108
- return fromEnv;
109
- }
110
- return `${kind}-local-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
111
- }
112
- function summarizeRunResultStatuses(results) {
113
- let passed = 0;
114
- let failed = 0;
115
- let skipped = 0;
116
- for (const entry of results) {
117
- if (entry.status === "passed") {
118
- passed += 1;
119
- continue;
120
- }
121
- if (entry.status === "failed") {
122
- failed += 1;
123
- continue;
124
- }
125
- skipped += 1;
126
- }
127
- return { passed, failed, skipped };
128
- }
129
- /** Implements summarize context preflight for the public runtime surface of this module. */
130
- export function summarizeContextPreflight(runResults) {
131
- let checkedPmCommands = 0;
132
- let trackerReadCommands = 0;
133
- let mismatches = 0;
134
- let autoRemediated = 0;
135
- for (const result of runResults) {
136
- const context = result.execution_context;
137
- if (!context || context.is_pm_command !== true) {
138
- continue;
139
- }
140
- checkedPmCommands += 1;
141
- if (context.is_pm_tracker_read_command === true) {
142
- trackerReadCommands += 1;
143
- }
144
- if (context.mismatch_detected === true) {
145
- mismatches += 1;
146
- }
147
- if (context.auto_pm_context_applied === true) {
148
- autoRemediated += 1;
149
- }
150
- }
151
- return {
152
- checked_pm_commands: checkedPmCommands,
153
- tracker_read_commands: trackerReadCommands,
154
- mismatches,
155
- auto_remediated: autoRemediated,
156
- };
157
- }
158
- function ensureScope(raw) {
159
- const value = (raw ?? "project");
160
- if (!SCOPE_VALUES.includes(value)) {
161
- throw new PmCliError(`Invalid scope "${raw}"`, EXIT_CODE.USAGE);
162
- }
163
- return value;
164
- }
165
- function parsePmContextMode(raw) {
166
- if (!raw) {
167
- return "schema";
168
- }
169
- const normalized = raw.trim().toLowerCase();
170
- if (PM_CONTEXT_MODE_VALUES.includes(normalized)) {
171
- return normalized;
172
- }
173
- throw new PmCliError(`Invalid --pm-context value "${raw}". Expected one of: ${PM_CONTEXT_MODE_VALUES.join(", ")}`, EXIT_CODE.USAGE);
174
- }
175
- function parseWorkspaceContextMode(raw) {
176
- return parseLinkedTestWorkspaceContextMode(raw ?? "source", "--workspace-context");
177
- }
178
- function resolveLinkedTestWorkspaceContextMode(linkedTest, runLevelMode, overrideLinkedMode) {
179
- if (overrideLinkedMode) {
180
- return runLevelMode;
181
- }
182
- return (parseLinkedTestWorkspaceContextMode(linkedTest.workspace_context_mode, "linked test") ?? runLevelMode);
183
- }
184
- function resolveLinkedTestRequestedContextMode(linkedTest, runLevelMode, overrideLinkedPmContext) {
185
- if (overrideLinkedPmContext) {
186
- return runLevelMode;
187
- }
188
- if (typeof linkedTest.pm_context_mode !== "string" ||
189
- linkedTest.pm_context_mode.trim().length === 0) {
190
- return runLevelMode;
191
- }
192
- return parsePmContextMode(linkedTest.pm_context_mode);
193
- }
194
- function resolveLinkedTestEffectiveContextMode(requestedMode, isPmTrackerReadCommand) {
195
- if (requestedMode === "auto") {
196
- return isPmTrackerReadCommand ? "tracker" : "schema";
197
- }
198
- return requestedMode;
199
- }
200
- function hasLinkedTestAssertions(linkedTest) {
201
- return Object.entries(linkedTest)
202
- .filter(([key]) => key.startsWith("assert_"))
203
- .some(([, value]) => {
204
- if (value === undefined || value === null)
205
- return false;
206
- if (Array.isArray(value))
207
- return value.length > 0;
208
- if (typeof value === "object")
209
- return Object.keys(value).length > 0;
210
- if (typeof value === "string")
211
- return value.trim().length > 0;
212
- return true;
213
- });
214
- }
215
- /* c8 ignore start -- pm-context mismatch hint combinations are validated by tracker/schema integration runs */
216
- function buildPmContextMismatchHint(params) {
217
- const { executionContext, runLevelPmContextMode, linkedOverridePmContextMode, } = params;
218
- if (!executionContext.is_pm_tracker_read_command ||
219
- !executionContext.mismatch_detected) {
220
- return "";
221
- }
222
- if (runLevelPmContextMode === "tracker" &&
223
- linkedOverridePmContextMode === "schema") {
224
- return (" Linked test metadata pm_context_mode=schema overrides run-level --pm-context tracker." +
225
- " Set pm_context_mode=tracker (or auto) on the linked test, or remove the override, to run against seeded tracker data.");
226
- }
227
- if (executionContext.pm_context_mode === "schema") {
228
- return (" Use --auto-pm-context to route PM tracker-read linked commands through seeded tracker data automatically." +
229
- " Alternatively, use --pm-context tracker for the whole run.");
230
- }
231
- return "";
232
- }
233
- /* c8 ignore stop */
234
- function mergeEnvSetDirectives(entries, optionName) {
235
- const merged = {};
236
- if (!entries) {
237
- return merged;
238
- }
239
- for (const entry of entries) {
240
- const parsed = parseLinkedTestEnvSetValue(entry, optionName);
241
- if (!parsed) {
242
- continue;
243
- }
244
- for (const [key, value] of Object.entries(parsed)) {
245
- merged[key] = value;
246
- }
247
- }
248
- return merged;
249
- }
250
- function mergeEnvClearDirectives(entries, optionName) {
251
- if (!entries) {
252
- return [];
253
- }
254
- const values = [];
255
- for (const entry of entries) {
256
- const parsed = parseLinkedTestEnvClearValue(entry, optionName);
257
- if (parsed) {
258
- values.push(...parsed);
259
- }
260
- }
261
- return [...new Set(values)];
262
- }
263
- // Linked-test command detection helpers + constants live in a sibling leaf
264
- // module to keep this command file under the per-file LOC budget. Re-exported
265
- // here so no consumer outside this file changes.
266
- 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";
267
- export { extractReferencedPmItemIdsFromCommand } from "./linked-command-detection.js";
268
- function commandInvokesPmCli(command) {
269
- const normalizedCommand = normalizeCommandForValidation(command);
270
- return splitNormalizedCommandSegments(normalizedCommand).some((segment) => extractPmInvocationArgsFromSegment(segment) !== null);
271
- }
272
- function commandInvokesPmTrackerReadCommand(command) {
273
- const normalizedCommand = normalizeCommandForValidation(command);
274
- return splitNormalizedCommandSegments(normalizedCommand).some((segment) => {
275
- const invocationArgs = extractPmInvocationArgsFromSegment(segment);
276
- if (!invocationArgs) {
277
- return false;
278
- }
279
- const context = resolvePmSubcommandContext(invocationArgs);
280
- if (!context) {
281
- return false;
282
- }
283
- return PM_TRACKER_READ_SUBCOMMANDS.has(context.subcommand);
284
- });
285
- }
286
- function resolveDirectRunnerSubcommand(parsed) {
287
- if (!parsed) {
288
- return undefined;
289
- }
290
- if (!SCRIPT_RUN_SUBCOMMANDS.has(parsed.subcommand)) {
291
- return parsed.subcommand;
292
- }
293
- return parseLauncherSubcommand(parsed.args, SCRIPT_RUN_FLAGS_WITH_VALUE)
294
- ?.subcommand;
295
- }
296
- function firstDirectTestRunnerSubcommand(executable, args) {
297
- if (executable === "npx" || executable === "bunx") {
298
- return parseNpxCommand(args)?.command;
299
- }
300
- if (executable === "pnpm") {
301
- return resolveDirectRunnerSubcommand(parseLauncherSubcommand(args, PNPM_GLOBAL_FLAGS_WITH_VALUE));
302
- }
303
- if (executable === "npm") {
304
- return resolveDirectRunnerSubcommand(parseLauncherSubcommand(args, NPM_GLOBAL_FLAGS_WITH_VALUE));
305
- }
306
- if (executable === "yarn") {
307
- return resolveDirectRunnerSubcommand(parseLauncherSubcommand(args, YARN_GLOBAL_FLAGS_WITH_VALUE));
308
- }
309
- if (executable === "bun") {
310
- return resolveDirectRunnerSubcommand(parseLauncherSubcommand(args, BUN_GLOBAL_FLAGS_WITH_VALUE));
311
- }
312
- return undefined;
313
- }
314
- function isDirectTestRunnerSubcommand(token) {
315
- if (!token) {
316
- return false;
317
- }
318
- return token === "vitest" || token === "test" || token.startsWith("test:");
319
- }
320
- function parsedLauncherInvokesRecursiveTestAll(parsed) {
321
- if (!parsed) {
322
- return false;
323
- }
324
- if (!isPmExecutableToken(parsed.command) &&
325
- !isPmCliPackageToken(parsed.command)) {
326
- return false;
327
- }
328
- return firstPmSubcommand(parsed.args) === "test-all";
329
- }
330
- const SHELL_COMMAND_FLAG_PATTERNS = new Map([
331
- ...["sh", "bash", "dash", "zsh"].map((executable) => [executable, /^(?:-c|-[a-z]*c[a-z]*)$/u]),
332
- ...["cmd", "cmd.exe"].map((executable) => [executable, /^\/[ck]$/u]),
333
- ...["powershell", "powershell.exe", "pwsh", "pwsh.exe"].map((executable) => [executable, /^-(?:c|command)$/u]),
334
- ]);
335
- const PACKAGE_EXEC_EXECUTABLES = new Set(["npx", "bunx"]);
336
- function segmentInvokesRecursiveTestAll(segment) {
337
- const rawTokens = segment.split(" ").filter((token) => token.length > 0);
338
- const tokens = stripLeadingEnvAssignments(rawTokens);
339
- if (tokens.length === 0) {
340
- return false;
341
- }
342
- const [executable, ...args] = tokens;
343
- const shellCommandFlagPattern = SHELL_COMMAND_FLAG_PATTERNS.get(executable);
344
- if (shellCommandFlagPattern) {
345
- const commandFlagIndex = args.findIndex((arg) => shellCommandFlagPattern.test(arg));
346
- return commandFlagIndex >= 0
347
- ? segmentInvokesRecursiveTestAll(args.slice(commandFlagIndex + 1).join(" "))
348
- : false;
349
- }
350
- if (isPmExecutableToken(executable) || isPmCliScriptToken(executable)) {
351
- return firstPmSubcommand(args) === "test-all";
352
- }
353
- if (executable === "node" && args.length > 0 && isPmCliScriptToken(args[0])) {
354
- return firstPmSubcommand(args.slice(1)) === "test-all";
355
- }
356
- if (PACKAGE_EXEC_EXECUTABLES.has(executable)) {
357
- return parsedLauncherInvokesRecursiveTestAll(parseNpxCommand(args));
358
- }
359
- if (executable === "pnpm") {
360
- return parsedLauncherInvokesRecursiveTestAll(parsePnpmDlxCommand(args));
361
- }
362
- if (executable === "npm") {
363
- return parsedLauncherInvokesRecursiveTestAll(parseNpmExecCommand(args));
364
- }
365
- return false;
366
- }
367
- function invokesRecursiveTestAllCommand(command) {
368
- const normalized = normalizeCommandForValidation(command);
369
- return splitNormalizedCommandSegments(normalized).some((segment) => segmentInvokesRecursiveTestAll(segment));
370
- }
371
- function assertNoRecursiveTestAllCommand(command) {
372
- if (!invokesRecursiveTestAllCommand(command))
373
- return;
374
- throw new PmCliError('Linked test commands must not invoke "pm test-all"; this creates recursive orchestration.', EXIT_CODE.USAGE);
375
- }
376
- function normalizeCommandForValidation(command) {
377
- return command
378
- .trim()
379
- .replaceAll("\\", "/")
380
- .replaceAll('"', "")
381
- .replaceAll("'", "")
382
- .replaceAll(/\s+/g, " ")
383
- .toLowerCase();
384
- }
385
- function commandUsesSandboxRunner(normalizedCommand) {
386
- return (normalizedCommand.includes("node scripts/run-tests.mjs ") ||
387
- normalizedCommand.endsWith("node scripts/run-tests.mjs") ||
388
- normalizedCommand.includes("node ./scripts/run-tests.mjs ") ||
389
- normalizedCommand.endsWith("node ./scripts/run-tests.mjs"));
390
- }
391
- function segmentInvokesDirectTestRunner(normalizedSegment) {
392
- const rawTokens = normalizedSegment
393
- .split(" ")
394
- .filter((token) => token.length > 0);
395
- const tokens = stripLeadingEnvAssignments(rawTokens);
396
- if (tokens.length === 0) {
397
- return false;
398
- }
399
- const [executable, ...args] = tokens;
400
- const shellCommandFlagPattern = SHELL_COMMAND_FLAG_PATTERNS.get(executable);
401
- if (shellCommandFlagPattern) {
402
- const commandFlagIndex = args.findIndex((argument) => shellCommandFlagPattern.test(argument));
403
- return (commandFlagIndex >= 0 &&
404
- segmentInvokesDirectTestRunner(args.slice(commandFlagIndex + 1).join(" ")));
405
- }
406
- if (executable === "vitest" ||
407
- executable.endsWith("/vitest") ||
408
- executable.endsWith("/vitest.mjs")) {
409
- return true;
410
- }
411
- if (executable === "node") {
412
- return nodeArgsInvokeDirectTestRunner(args);
413
- }
414
- return packageManagerInvokesDirectTestRunner(executable, args);
415
- }
416
- function nodeArgsInvokeDirectTestRunner(args) {
417
- return (args.includes("--test") ||
418
- args.some((arg) => arg === "vitest" ||
419
- arg === "vitest.mjs" ||
420
- arg.endsWith("/vitest") ||
421
- arg.endsWith("/vitest.mjs")));
422
- }
423
- function packageManagerInvokesDirectTestRunner(executable, args) {
424
- if (executable === "npx" || executable === "bunx") {
425
- return isDirectTestRunnerSubcommand(parseNpxCommand(args)?.command);
426
- }
427
- if (executable === "pnpm" || executable === "npm") {
428
- return (isDirectTestRunnerSubcommand((executable === "pnpm"
429
- ? parsePnpmDlxCommand(args)
430
- : parseNpmExecCommand(args))?.command) ||
431
- isDirectTestRunnerSubcommand(firstDirectTestRunnerSubcommand(executable, args)));
432
- }
433
- return ((executable === "yarn" || executable === "bun") &&
434
- isDirectTestRunnerSubcommand(firstDirectTestRunnerSubcommand(executable, args)));
435
- }
436
- /** Classify linked-test commands without pretending string shape is a security boundary. */
437
- export function classifyLinkedTestCommandSafety(command) {
438
- const normalized = normalizeCommandForValidation(command);
439
- const segments = splitNormalizedCommandSegments(normalized);
440
- const directRunner = segments.some((segment) => !commandUsesSandboxRunner(segment) &&
441
- segmentInvokesDirectTestRunner(segment));
442
- const sandboxRunner = segments.some(commandUsesSandboxRunner);
443
- return {
444
- accepted: true,
445
- command_kind: directRunner
446
- ? "direct_runner"
447
- : sandboxRunner
448
- ? "sandbox_runner"
449
- : "other",
450
- tracker_isolation: "runtime_injected",
451
- trust_boundary: "provenance",
452
- advisory: directRunner
453
- ? "Accepted with runtime-injected tracker isolation. Prefer the repository sandbox runner when available for reproducible build and coverage setup."
454
- : "Accepted with runtime-injected tracker isolation and provenance checks before execution.",
455
- };
456
- }
457
- function getRuntimeSafetySkipReason(command) {
458
- if (!invokesRecursiveTestAllCommand(command))
459
- return undefined;
460
- return 'Linked test command skipped: Linked test commands must not invoke "pm test-all"; this creates recursive orchestration.';
461
- }
462
- function parseAddEntries(raw) {
463
- if (!raw)
464
- return [];
465
- return raw.map(parseAddEntry);
466
- }
467
- function parseAddEntry(entry) {
468
- const trimmed = entry.trim();
469
- const kv = looksLikeStructuredLinkedTestEntry(trimmed)
470
- ? normalizeStructuredLinkedTestEntry(parseCsvKv(entry, "--add"), "--add")
471
- : { command: trimmed };
472
- const command = trimLinkedTestEntryField(kv.command);
473
- if (!command) {
474
- throw new PmCliError("--add requires command=<value> or a bare command (path=<value> is optional metadata)", EXIT_CODE.USAGE);
475
- }
476
- assertNoRecursiveTestAllCommand(command);
477
- return {
478
- command,
479
- path: trimLinkedTestEntryField(kv.path),
480
- scope: ensureScope(kv.scope),
481
- timeout_seconds: parseLinkedTestTimeoutSeconds(trimLinkedTestEntryField(kv.timeout_seconds), trimLinkedTestEntryField(kv.timeout)),
482
- pm_context_mode: parseLinkedTestContextModeValue(trimLinkedTestEntryField(kv.pm_context_mode), "--add"),
483
- workspace_context_mode: parseLinkedTestWorkspaceContextMode(trimLinkedTestEntryField(kv.workspace_context_mode), "--add"),
484
- env_set: parseLinkedTestEnvSetValue(trimLinkedTestEntryField(kv.env_set), "--add"),
485
- env_clear: parseLinkedTestEnvClearValue(trimLinkedTestEntryField(kv.env_clear), "--add"),
486
- shared_host_safe: parseLinkedTestBooleanValue(trimLinkedTestEntryField(kv.shared_host_safe), "--add", "shared_host_safe"),
487
- assert_stdout_contains: parseLinkedTestStringList(trimLinkedTestEntryField(kv.assert_stdout_contains)),
488
- assert_stdout_regex: parseLinkedTestRegexList(trimLinkedTestEntryField(kv.assert_stdout_regex), "--add", "assert_stdout_regex"),
489
- assert_stderr_contains: parseLinkedTestStringList(trimLinkedTestEntryField(kv.assert_stderr_contains)),
490
- assert_stderr_regex: parseLinkedTestRegexList(trimLinkedTestEntryField(kv.assert_stderr_regex), "--add", "assert_stderr_regex"),
491
- assert_stdout_min_lines: parseLinkedTestMinLines(trimLinkedTestEntryField(kv.assert_stdout_min_lines), "--add"),
492
- assert_json_field_equals: parseLinkedTestAssertionEqualsMap(trimLinkedTestEntryField(kv.assert_json_field_equals), "--add"),
493
- assert_json_field_gte: parseLinkedTestAssertionGteMap(trimLinkedTestEntryField(kv.assert_json_field_gte), "--add"),
494
- note: trimLinkedTestEntryField(kv.note),
495
- };
496
- }
497
- function trimLinkedTestEntryField(value) {
498
- const trimmed = value?.trim();
499
- return trimmed && trimmed.length > 0 ? trimmed : undefined;
500
- }
501
- function parseLinkedTestTimeoutSeconds(timeoutSecondsRaw, timeoutAliasRaw) {
502
- if (timeoutSecondsRaw &&
503
- timeoutAliasRaw &&
504
- timeoutSecondsRaw !== timeoutAliasRaw) {
505
- throw new PmCliError("--add timeout and timeout_seconds must match when both are provided", EXIT_CODE.USAGE);
506
- }
507
- const timeoutRaw = timeoutSecondsRaw ?? timeoutAliasRaw;
508
- if (timeoutRaw === undefined)
509
- return undefined;
510
- const timeout = Math.floor(parseOptionalNumber(timeoutRaw, "timeout_seconds"));
511
- if (timeout <= 0) {
512
- throw new PmCliError("--add timeout_seconds must be a positive integer", EXIT_CODE.USAGE);
513
- }
514
- return timeout;
515
- }
516
- /* c8 ignore start -- add-json validation matrix is covered by linked-test parser integration suites */
517
- function parseAddJsonEntries(raw) {
518
- if (!raw)
519
- return [];
520
- return raw.flatMap((entry) => {
521
- const parsed = parseLinkedTestJsonEntries(entry, "--add-json");
522
- for (const linkedTest of parsed) {
523
- const command = linkedTest.command;
524
- if (!command) {
525
- throw new PmCliError("--add-json requires a non-empty command string", EXIT_CODE.USAGE);
526
- }
527
- assertNoRecursiveTestAllCommand(command);
528
- }
529
- return parsed;
530
- });
531
- }
532
- function parseRemoveEntries(raw) {
533
- if (!raw)
534
- return [];
535
- return raw.map((entry) => {
536
- const trimmed = entry.trim();
537
- if (!trimmed) {
538
- throw new PmCliError("--remove requires command or path value", EXIT_CODE.USAGE);
539
- }
540
- const identityPrefix = /^(command|path)\s*=/i.exec(trimmed);
541
- if (identityPrefix) {
542
- const value = trimmed.slice(identityPrefix[0].length).trim();
543
- if (!value) {
544
- throw new PmCliError("--remove requires a non-empty command=<value> or path=<value>", EXIT_CODE.USAGE);
545
- }
546
- return {
547
- kind: identityPrefix[1].toLowerCase(),
548
- value,
549
- };
550
- }
551
- if (/^[A-Za-z_][\w-]*=\S+$/u.test(trimmed)) {
552
- throw new PmCliError("--remove structured selectors require command=<value> or path=<value>", EXIT_CODE.USAGE);
553
- }
554
- if (/^(?:[-*+]\s+)?(?:path|command)\s*:/i.test(trimmed) ||
555
- trimmed.startsWith("```")) {
556
- const kv = parseCsvKv(trimmed, "--remove");
557
- const kind = kv.path !== undefined ? "path" : "command";
558
- const value = kv.path ?? kv.command;
559
- if (!value?.trim()) {
560
- throw new PmCliError("--remove requires command=<value> and/or path=<value>", EXIT_CODE.USAGE);
561
- }
562
- return { kind, value: value.trim() };
563
- }
564
- return { kind: "either", value: trimmed };
565
- });
566
- }
567
- function parseRemoveIndexes(raw) {
568
- if (!raw)
569
- return [];
570
- return [
571
- ...new Set(raw.map((value) => {
572
- const parsed = typeof value === "number" ? value : Number.parseInt(value, 10);
573
- if (!Number.isInteger(parsed) ||
574
- parsed < 1 ||
575
- String(parsed) !== String(value).trim()) {
576
- throw new PmCliError("--remove-index must be a positive integer", EXIT_CODE.USAGE);
577
- }
578
- return parsed;
579
- })),
580
- ];
581
- }
582
- function closeLinkedTestStdin(child) {
583
- // Force EOF on child stdin so non-interactive runs do not wait on input.
584
- try {
585
- child.stdin?.end();
586
- }
587
- catch {
588
- // Child stdin can already be closed depending on command startup timing.
589
- }
590
- }
591
- function summarizeLinkedTestCommand(command) {
592
- const normalized = command.trim().replaceAll(/\s+/g, " ");
593
- if (normalized.length <= MAX_LINKED_TEST_COMMAND_LABEL_LENGTH) {
594
- return normalized;
595
- }
596
- return `${normalized.slice(0, MAX_LINKED_TEST_COMMAND_LABEL_LENGTH - 3)}...`;
597
- }
598
- function shouldEmitLinkedTestProgress(mode) {
599
- /* c8 ignore start -- reserved for future explicit "off" mode wiring. */
600
- if (mode === "off") {
601
- return false;
602
- }
603
- /* c8 ignore stop */
604
- if (mode === "always") {
605
- return true;
606
- }
607
- return process.stderr.isTTY === true;
608
- }
609
- function emitLinkedTestProgress(message) {
610
- try {
611
- process.stderr.write(`${message}\n`);
612
- }
613
- catch {
614
- // Ignore transient stderr write failures.
615
- }
616
- }
617
- function beginLinkedTestProgress(context, mode) {
618
- if (!shouldEmitLinkedTestProgress(mode)) {
619
- return null;
620
- }
621
- const commandLabel = summarizeLinkedTestCommand(context.command);
622
- const startAt = Date.now();
623
- emitLinkedTestProgress(`[pm test] linked-test ${context.index}/${context.total} start timeout_ms=${context.timeoutMs} command="${commandLabel}"`);
624
- const heartbeat = setInterval(() => {
625
- const elapsedMs = Date.now() - startAt;
626
- emitLinkedTestProgress(`[pm test] linked-test ${context.index}/${context.total} running elapsed_ms=${elapsedMs} command="${commandLabel}"`);
627
- }, linkedTestHeartbeatIntervalMs());
628
- heartbeat.unref?.();
629
- return heartbeat;
630
- }
631
- function endLinkedTestProgress(context, executionResult, startedAt, mode) {
632
- if (!shouldEmitLinkedTestProgress(mode)) {
633
- return;
634
- }
635
- const commandLabel = summarizeLinkedTestCommand(context.command);
636
- const elapsedMs = Date.now() - startedAt;
637
- const failed = executionResult.timedOut ||
638
- executionResult.maxBufferExceeded ||
639
- executionResult.exitCode !== 0;
640
- const statusLabel = failed ? "failed" : "passed";
641
- const reasonTokens = [];
642
- if (executionResult.timedOut) {
643
- reasonTokens.push("reason=timeout");
644
- }
645
- if (executionResult.maxBufferExceeded) {
646
- reasonTokens.push("reason=max_buffer");
647
- }
648
- if (executionResult.outputTruncated) {
649
- reasonTokens.push("output=truncated");
650
- }
651
- if (executionResult.signal) {
652
- reasonTokens.push(`signal=${executionResult.signal}`);
653
- }
654
- const exitLabel = executionResult.exitCode === null
655
- ? "null"
656
- : String(executionResult.exitCode);
657
- const reasonSuffix = reasonTokens.length > 0 ? ` ${reasonTokens.join(" ")}` : "";
658
- emitLinkedTestProgress(`[pm test] linked-test ${context.index}/${context.total} end status=${statusLabel} exit_code=${exitLabel} elapsed_ms=${elapsedMs}${reasonSuffix} command="${commandLabel}"`);
659
- }
660
- /* c8 ignore start -- process-tree teardown paths are highly platform-dependent. */
661
- async function killProcessTree(pid) {
662
- if (!Number.isInteger(pid) || pid <= 0) {
663
- return;
664
- }
665
- if (process.platform === "win32") {
666
- await new Promise((resolve) => {
667
- const killer = spawn("taskkill", ["/pid", String(pid), "/T", "/F"], {
668
- stdio: "ignore",
669
- windowsHide: true,
670
- });
671
- killer.on("error", () => resolve());
672
- killer.on("close", () => resolve());
673
- });
674
- return;
675
- }
676
- try {
677
- process.kill(-pid, "SIGKILL");
678
- return;
679
- }
680
- catch {
681
- // Fall back to direct child kill when no process group is available.
682
- }
683
- try {
684
- process.kill(pid, "SIGKILL");
685
- }
686
- catch {
687
- // The process can already be gone.
688
- }
689
- }
690
- function clearLinkedTestTimers(timers) {
691
- if (timers.heartbeat) {
692
- clearInterval(timers.heartbeat);
693
- }
694
- if (timers.timedOutTimer) {
695
- clearTimeout(timers.timedOutTimer);
696
- timers.timedOutTimer = null;
697
- }
698
- if (timers.forceKillTimer) {
699
- clearTimeout(timers.forceKillTimer);
700
- timers.forceKillTimer = null;
701
- }
702
- }
703
- function createLinkedTestTerminationRequester(child, timers) {
704
- let terminationRequested = false;
705
- return async () => {
706
- if (terminationRequested) {
707
- return;
708
- }
709
- terminationRequested = true;
710
- const pid = child.pid;
711
- if (!pid || pid <= 0) {
712
- try {
713
- child.kill("SIGTERM");
714
- }
715
- catch {
716
- // Child can already be closed.
717
- }
718
- return;
719
- }
720
- if (process.platform === "win32") {
721
- await killProcessTree(pid);
722
- return;
723
- }
724
- try {
725
- process.kill(-pid, "SIGTERM");
726
- }
727
- catch {
728
- /* c8 ignore next 4 -- platform-specific process-group fallback path. */
729
- try {
730
- child.kill("SIGTERM");
731
- }
732
- catch {
733
- // Child can already be closed.
734
- }
735
- }
736
- /* c8 ignore next 3 -- exercised only when timeout escalation triggers force-kill fallback. */
737
- timers.forceKillTimer = setTimeout(() => {
738
- void killProcessTree(pid);
739
- }, linkedTestTimeoutForceKillDelayMs());
740
- timers.forceKillTimer.unref?.();
741
- };
742
- }
743
- function waitForLinkedTestChildClose(child) {
744
- return new Promise((resolve) => {
745
- let settled = false;
746
- let pipeCloseGraceTimer = null;
747
- const settle = (value, destroyPipes = false) => {
748
- if (settled) {
749
- return;
750
- }
751
- settled = true;
752
- if (pipeCloseGraceTimer) {
753
- clearTimeout(pipeCloseGraceTimer);
754
- pipeCloseGraceTimer = null;
755
- }
756
- if (destroyPipes) {
757
- child.stdin?.destroy();
758
- child.stdout?.destroy();
759
- child.stderr?.destroy();
760
- }
761
- resolve(value);
762
- };
763
- child.on("exit", (exitCode, exitSignal) => {
764
- if (settled) {
765
- return;
766
- }
767
- pipeCloseGraceTimer = setTimeout(() => {
768
- settle({
769
- code: exitCode,
770
- signal: exitSignal,
771
- }, true);
772
- }, linkedTestPipeCloseGraceMs());
773
- pipeCloseGraceTimer.unref?.();
774
- });
775
- child.on("close", (closeCode, closeSignal) => {
776
- settle({
777
- code: closeCode,
778
- signal: closeSignal,
779
- });
780
- });
781
- });
782
- }
783
- function createLinkedTestChild(command, env, stdoutFd, stderrFd, cwd) {
784
- return spawn(command, {
785
- cwd,
786
- env,
787
- shell: true,
788
- windowsHide: true,
789
- detached: process.platform !== "win32",
790
- // Regular files provide blocking writes to descendants. Node-created
791
- // pipes are non-blocking; runtimes such as Bun and Rust can inherit that
792
- // flag and abort on EAGAIN when their stdout briefly outruns the parent.
793
- stdio: ["pipe", stdoutFd, stderrFd],
794
- });
795
- }
796
- async function readLinkedTestCapture(capturePath) {
797
- const handle = await open(capturePath, "r");
798
- try {
799
- const size = (await handle.stat()).size;
800
- const retainedBytes = Math.min(size, TEST_OUTPUT_MAX_BUFFER_BYTES);
801
- const buffer = Buffer.alloc(retainedBytes);
802
- if (retainedBytes > 0) {
803
- await handle.read(buffer, 0, retainedBytes, 0);
804
- }
805
- return {
806
- text: buffer.toString("utf8"),
807
- truncated: size > TEST_OUTPUT_MAX_BUFFER_BYTES,
808
- };
809
- }
810
- finally {
811
- await handle.close();
812
- }
813
- }
814
- async function runLinkedTestCommand(command, timeoutMs, env, progressContext, progressMode, cwd = process.cwd()) {
815
- const startedAt = Date.now();
816
- const captureRoot = await mkdtemp(path.join(tmpdir(), "pm-linked-test-output-"));
817
- try {
818
- const stdoutPath = path.join(captureRoot, "stdout.log");
819
- const stderrPath = path.join(captureRoot, "stderr.log");
820
- const [stdoutHandle, stderrHandle] = await Promise.all([
821
- open(stdoutPath, "w"),
822
- open(stderrPath, "w"),
823
- ]);
824
- let child;
825
- try {
826
- child = createLinkedTestChild(command, env, stdoutHandle.fd, stderrHandle.fd, cwd);
827
- }
828
- catch (error) {
829
- await Promise.all([stdoutHandle.close(), stderrHandle.close()]);
830
- throw error;
831
- }
832
- const childClose = waitForLinkedTestChildClose(child);
833
- let spawnError;
834
- /* c8 ignore next 5 -- shell spawn error callbacks are non-deterministic across platforms. */
835
- child.on("error", (error) => {
836
- spawnError = error.message;
837
- });
838
- await Promise.all([stdoutHandle.close(), stderrHandle.close()]);
839
- closeLinkedTestStdin(child);
840
- const timers = {
841
- heartbeat: beginLinkedTestProgress(progressContext, progressMode),
842
- forceKillTimer: null,
843
- timedOutTimer: null,
844
- };
845
- const requestTermination = createLinkedTestTerminationRequester(child, timers);
846
- let timedOut = false;
847
- /* c8 ignore next 4 -- callback scheduling timing is non-deterministic under coverage instrumentation. */
848
- timers.timedOutTimer = setTimeout(() => {
849
- timedOut = true;
850
- void requestTermination();
851
- }, timeoutMs);
852
- timers.timedOutTimer.unref?.();
853
- const { code, signal } = await childClose;
854
- clearLinkedTestTimers(timers);
855
- const [stdout, stderr] = await Promise.all([
856
- readLinkedTestCapture(stdoutPath),
857
- readLinkedTestCapture(stderrPath),
858
- ]);
859
- const outputTruncated = stdout.truncated || stderr.truncated;
860
- const truncationNotice = outputTruncated
861
- ? `\n[pm linked-test output truncated at ${TEST_OUTPUT_MAX_BUFFER_BYTES} bytes per stream; the child process continued to completion]\n`
862
- : "";
863
- const executionResult = {
864
- stdout: `${stdout.text}${truncationNotice}`,
865
- stderr: stderr.text,
866
- exitCode: code,
867
- signal,
868
- timedOut,
869
- maxBufferExceeded: false,
870
- outputTruncated,
871
- spawnError,
872
- };
873
- endLinkedTestProgress(progressContext, executionResult, startedAt, progressMode);
874
- return executionResult;
875
- }
876
- finally {
877
- await rm(captureRoot, { recursive: true, force: true });
878
- }
879
- }
880
- /* c8 ignore stop */
881
- function formatLinkedTestExecutionError(result, timeoutMs) {
882
- const details = [];
883
- if (result.maxBufferExceeded) {
884
- details.push(`Linked test output exceeded maxBuffer=${TEST_OUTPUT_MAX_BUFFER_BYTES} bytes. Reduce output volume or split the command.`);
885
- }
886
- if (result.timedOut && timeoutMs > 0) {
887
- details.push(`Linked test timed out after ${timeoutMs}ms.`);
888
- }
889
- const signalMessage = result.signal
890
- ? `Linked test command terminated by signal ${result.signal}.`
891
- : undefined;
892
- const baseMessage = result.spawnError?.trim() || signalMessage || "Linked test command failed.";
893
- if (details.length === 0) {
894
- return baseMessage;
895
- }
896
- return `${baseMessage} ${details.join(" ")}`;
897
- }
898
- function hasInfraCollisionSignal(result) {
899
- const combined = [result.spawnError ?? "", result.stderr, result.stdout].join("\n");
900
- return (LINKED_TEST_INFRA_COLLISION_PATTERNS.some((pattern) => pattern.test(combined)) ||
901
- combined
902
- .split(/\r?\n/u)
903
- .some((line) => LINKED_TEST_LOCK_ACQUISITION_PATTERN.test(line) &&
904
- LINKED_TEST_LOCK_CONTENTION_CONTEXT_PATTERN.test(line)));
905
- }
906
- /** Implements classify linked test failure for the public runtime surface of this module. */
907
- export function classifyLinkedTestFailure(result) {
908
- if (hasInfraCollisionSignal(result)) {
909
- return "infra_collision";
910
- }
911
- if (result.timedOut) {
912
- return "timeout";
913
- }
914
- if (result.maxBufferExceeded) {
915
- return "max_buffer";
916
- }
917
- if (result.spawnError) {
918
- return "spawn_error";
919
- }
920
- if (result.signal) {
921
- return "signal";
922
- }
923
- return "assertion_failure";
924
- }
925
- function createEmptyFailureCategoryCounts() {
926
- return {
927
- infra_collision: 0,
928
- trust_refusal: 0,
929
- assertion_failure: 0,
930
- empty_run: 0,
931
- timeout: 0,
932
- max_buffer: 0,
933
- spawn_error: 0,
934
- signal: 0,
935
- };
936
- }
937
- /** Implements count failure categories for the public runtime surface of this module. */
938
- export function countFailureCategories(runResults) {
939
- const counts = createEmptyFailureCategoryCounts();
940
- for (const result of runResults) {
941
- if (result.status !== "failed" || !result.failure_category) {
942
- continue;
943
- }
944
- counts[result.failure_category] += 1;
945
- }
946
- return counts;
947
- }
948
- function applyEnvDirectiveStage(env, directives) {
949
- for (const [key, value] of Object.entries(directives.env_set)) {
950
- if (LINKED_TEST_PROTECTED_ENV_KEYS.has(key.toUpperCase())) {
951
- continue;
952
- }
953
- env[key] = value;
954
- }
955
- for (const key of directives.env_clear) {
956
- if (LINKED_TEST_PROTECTED_ENV_KEYS.has(key.toUpperCase())) {
957
- continue;
958
- }
959
- delete env[key];
960
- }
961
- }
962
- function applySharedHostSafeDefaults(env) {
963
- if (env.PORT === undefined) {
964
- env.PORT = "0";
965
- }
966
- if (env.HOST === undefined) {
967
- env.HOST = "127.0.0.1";
968
- }
969
- if (env.PM_SHARED_HOST_SAFE === undefined) {
970
- env.PM_SHARED_HOST_SAFE = "1";
971
- }
972
- if (env.PLAYWRIGHT_HTML_OPEN === undefined) {
973
- env.PLAYWRIGHT_HTML_OPEN = "never";
974
- }
975
- if (env.PW_TEST_HTML_REPORT_OPEN === undefined) {
976
- env.PW_TEST_HTML_REPORT_OPEN = "never";
977
- }
978
- }
979
- function resolveEffectiveLinkedTestDirectives(runtimeDirectives, linkedTest) {
980
- const envSet = { ...runtimeDirectives.env_set, ...linkedTest.env_set };
981
- const envClear = [
982
- ...new Set([
983
- ...runtimeDirectives.env_clear,
984
- ...(linkedTest.env_clear ?? []),
985
- ]),
986
- ];
987
- const sharedHostSafe = linkedTest.shared_host_safe ?? runtimeDirectives.shared_host_safe;
988
- return {
989
- env_set: envSet,
990
- env_clear: envClear,
991
- shared_host_safe: sharedHostSafe,
992
- };
993
- }
994
- function resolveRuntimeDirectives(envSetEntries, envClearEntries, sharedHostSafe) {
995
- return {
996
- env_set: mergeEnvSetDirectives(envSetEntries, "--env-set"),
997
- env_clear: mergeEnvClearDirectives(envClearEntries, "--env-clear"),
998
- shared_host_safe: sharedHostSafe === true,
999
- };
1000
- }
1001
- /* c8 ignore start -- sandbox copy race/path permutations are covered by filesystem integration suites */
1002
- async function copyIntoSandboxIfPresent(sourcePath, targetPath, recursive = false, makeDirectory = mkdir) {
1003
- if (!(await pathExists(sourcePath))) {
1004
- return;
1005
- }
1006
- try {
1007
- await withHostEnvironmentBoundary("seed_linked_test", async () => {
1008
- await makeDirectory(path.dirname(targetPath), { recursive: true });
1009
- if (recursive) {
1010
- await cp(sourcePath, targetPath, { recursive: true, force: true });
1011
- return;
1012
- }
1013
- await cp(sourcePath, targetPath, { force: true });
1014
- }, {
1015
- why: "Linked tests need an isolated PM context without mutating the source tracker.",
1016
- nextSteps: [
1017
- "Reclaim capacity on the filesystem that hosts the linked-test temporary directory.",
1018
- ],
1019
- });
1020
- }
1021
- catch (error) {
1022
- if (typeof error === "object" &&
1023
- error !== null &&
1024
- "code" in error &&
1025
- isFileMissingError(error)) {
1026
- return;
1027
- }
1028
- throw error;
1029
- }
1030
- }
1031
- /* c8 ignore stop */
1032
- async function seedLinkedTestSandbox(sandboxPmPath, sandboxGlobalPath, sourceRoots) {
1033
- await copyIntoSandboxIfPresent(getSettingsPath(sourceRoots.projectPmRoot), getSettingsPath(sandboxPmPath));
1034
- await copyIntoSandboxIfPresent(path.join(sourceRoots.projectPmRoot, "extensions"), path.join(sandboxPmPath, "extensions"), true);
1035
- await copyIntoSandboxIfPresent(getSettingsPath(sourceRoots.globalPmRoot), getSettingsPath(sandboxGlobalPath));
1036
- await copyIntoSandboxIfPresent(path.join(sourceRoots.globalPmRoot, "extensions"), path.join(sandboxGlobalPath, "extensions"), true);
1037
- }
1038
- async function seedLinkedTestTrackerData(sourceRoot, sandboxRoot) {
1039
- if (!(await pathExists(sourceRoot))) {
1040
- return;
1041
- }
1042
- const entries = await readdir(sourceRoot, { withFileTypes: true });
1043
- for (const entry of entries) {
1044
- if (LINKED_TEST_TRACKER_DIRS_TO_SKIP.has(entry.name)) {
1045
- continue;
1046
- }
1047
- const sourcePath = path.join(sourceRoot, entry.name);
1048
- const targetPath = path.join(sandboxRoot, entry.name);
1049
- if (entry.isDirectory()) {
1050
- await copyIntoSandboxIfPresent(sourcePath, targetPath, true);
1051
- continue;
1052
- }
1053
- if (entry.isFile()) {
1054
- await copyIntoSandboxIfPresent(sourcePath, targetPath);
1055
- }
1056
- }
1057
- }
1058
- /**
1059
- * Create an audited settings snapshot only for source trackers that predate
1060
- * workspace history, while preserving any existing source history verbatim.
1061
- */
1062
- async function seedMissingLinkedTestSettingsHistory(sourceRoot, sandboxRoot) {
1063
- const sourceSettingsRaw = await readFileIfExists(getSettingsPath(sourceRoot));
1064
- if (sourceSettingsRaw === null ||
1065
- (await pathExists(getWorkspaceHistoryPath(sourceRoot)))) {
1066
- return;
1067
- }
1068
- const sandboxSettings = await readSettings(sandboxRoot);
1069
- await rm(getWorkspaceHistoryPath(sandboxRoot), { force: true });
1070
- await rm(getSettingsPath(sandboxRoot), { force: true });
1071
- await writeWorkspaceJsonWithHistory({
1072
- pmRoot: sandboxRoot,
1073
- filePath: getSettingsPath(sandboxRoot),
1074
- raw: sourceSettingsRaw,
1075
- op: "settings:write",
1076
- author: resolveAuthor(undefined, sandboxSettings.author_default),
1077
- lockTtlSeconds: sandboxSettings.locks.ttl_seconds,
1078
- lockWaitMs: sandboxSettings.locks.wait_ms,
1079
- recordCreation: true,
1080
- message: "Seed linked-test tracker settings history",
1081
- });
1082
- }
1083
- async function countLinkedTestItemFiles(pmRoot) {
1084
- if (!(await pathExists(pmRoot))) {
1085
- return 0;
1086
- }
1087
- let total = 0;
1088
- const entries = await readdir(pmRoot, { withFileTypes: true });
1089
- for (const entry of entries) {
1090
- if (!entry.isDirectory() ||
1091
- LINKED_TEST_ITEM_COUNT_DIRS_TO_SKIP.has(entry.name)) {
1092
- continue;
1093
- }
1094
- const folderPath = path.join(pmRoot, entry.name);
1095
- let files;
1096
- try {
1097
- files = await readdir(folderPath, { withFileTypes: true });
1098
- }
1099
- catch {
1100
- continue;
1101
- }
1102
- for (const file of files) {
1103
- if (!file.isFile()) {
1104
- continue;
1105
- }
1106
- if (ITEM_FILE_EXTENSIONS.some((extension) => file.name.toLowerCase().endsWith(extension))) {
1107
- total += 1;
1108
- }
1109
- }
1110
- }
1111
- return total;
1112
- }
1113
- /** Implements resolve linked test failure exit code for the public runtime surface of this module. */
1114
- export function resolveLinkedTestFailureExitCode(execution) {
1115
- const rawExitCode = typeof execution.exitCode === "number" ? execution.exitCode : 1;
1116
- if ((execution.timedOut || execution.maxBufferExceeded) &&
1117
- rawExitCode === 0) {
1118
- return 1;
1119
- }
1120
- return rawExitCode;
1121
- }
1122
- function splitJsonPathSegments(fieldPath) {
1123
- const segments = [];
1124
- const tokens = fieldPath.match(/[^.[\]]+|\[\d+\]/g) ?? [];
1125
- for (const token of tokens) {
1126
- if (token.startsWith("[") && token.endsWith("]")) {
1127
- const parsedIndex = Number.parseInt(token.slice(1, -1), 10);
1128
- if (!Number.isInteger(parsedIndex) || parsedIndex < 0) {
1129
- return [];
1130
- }
1131
- segments.push(parsedIndex);
1132
- continue;
1133
- }
1134
- segments.push(token);
1135
- }
1136
- return segments;
1137
- }
1138
- function readJsonPathValue(root, fieldPath) {
1139
- const normalizedPath = fieldPath.trim();
1140
- if (normalizedPath.length === 0) {
1141
- return { found: false, value: undefined };
1142
- }
1143
- const segments = splitJsonPathSegments(normalizedPath);
1144
- if (segments.length === 0) {
1145
- return { found: false, value: undefined };
1146
- }
1147
- let current = root;
1148
- for (const segment of segments) {
1149
- if (typeof segment === "number") {
1150
- if (!Array.isArray(current) || segment >= current.length) {
1151
- return { found: false, value: undefined };
1152
- }
1153
- current = current[segment];
1154
- continue;
1155
- }
1156
- if (typeof current !== "object" ||
1157
- current === null ||
1158
- !Object.prototype.hasOwnProperty.call(current, segment)) {
1159
- return { found: false, value: undefined };
1160
- }
1161
- current = current[segment];
1162
- }
1163
- return { found: true, value: current };
1164
- }
1165
- /* c8 ignore start -- assertion-literal parsing edge cases are validated by assertion integration suites */
1166
- function parseAssertionLiteral(raw) {
1167
- const trimmed = raw.trim();
1168
- if (trimmed === "true") {
1169
- return true;
1170
- }
1171
- if (trimmed === "false") {
1172
- return false;
1173
- }
1174
- if (trimmed === "null") {
1175
- return null;
1176
- }
1177
- const numeric = Number(trimmed);
1178
- if (trimmed.length > 0 && Number.isFinite(numeric)) {
1179
- return numeric;
1180
- }
1181
- if ((trimmed.startsWith("{") && trimmed.endsWith("}")) ||
1182
- (trimmed.startsWith("[") && trimmed.endsWith("]"))) {
1183
- try {
1184
- return JSON.parse(trimmed);
1185
- }
1186
- catch {
1187
- // Fall back to string comparison for malformed JSON literals.
1188
- }
1189
- }
1190
- return trimmed;
1191
- }
1192
- /* c8 ignore stop */
1193
- function compareAssertionValues(actual, expected) {
1194
- if (typeof actual === "object" &&
1195
- actual !== null &&
1196
- typeof expected === "object" &&
1197
- expected !== null) {
1198
- return stableValueEquals(actual, expected);
1199
- }
1200
- return Object.is(actual, expected);
1201
- }
1202
- /* c8 ignore start -- assertion failure-path permutations are covered by linked-test integration fixtures */
1203
- function evaluateLinkedTestAssertions(linkedTest, stdout, stderr) {
1204
- const failures = [];
1205
- failures.push(...evaluateContainsAssertions("stdout", stdout, linkedTest.assert_stdout_contains));
1206
- failures.push(...evaluateRegexAssertions("stdout", stdout, linkedTest.assert_stdout_regex));
1207
- failures.push(...evaluateContainsAssertions("stderr", stderr, linkedTest.assert_stderr_contains));
1208
- failures.push(...evaluateRegexAssertions("stderr", stderr, linkedTest.assert_stderr_regex));
1209
- failures.push(...evaluateStdoutLineCountAssertion(stdout, linkedTest.assert_stdout_min_lines));
1210
- const jsonEqualsAssertions = linkedTest.assert_json_field_equals ?? {};
1211
- const jsonGteAssertions = linkedTest.assert_json_field_gte ?? {};
1212
- const needsJsonAssertions = Object.keys(jsonEqualsAssertions).length > 0 ||
1213
- Object.keys(jsonGteAssertions).length > 0;
1214
- if (!needsJsonAssertions) {
1215
- return failures;
1216
- }
1217
- let parsedJson;
1218
- try {
1219
- parsedJson = JSON.parse(stdout);
1220
- }
1221
- catch (error) {
1222
- failures.push(`stdout is not valid JSON for assert_json_field_* checks: ${error instanceof Error ? error.message : String(error)}`);
1223
- return failures;
1224
- }
1225
- for (const [fieldPath, expectedRaw] of Object.entries(jsonEqualsAssertions)) {
1226
- const resolved = readJsonPathValue(parsedJson, fieldPath);
1227
- if (!resolved.found) {
1228
- failures.push(`assert_json_field_equals missing path "${fieldPath}"`);
1229
- continue;
1230
- }
1231
- const expected = parseAssertionLiteral(expectedRaw);
1232
- if (!compareAssertionValues(resolved.value, expected)) {
1233
- failures.push(`assert_json_field_equals mismatch at "${fieldPath}" (expected=${JSON.stringify(expected)} actual=${JSON.stringify(resolved.value)})`);
1234
- }
1235
- }
1236
- for (const [fieldPath, expectedMinimum] of Object.entries(jsonGteAssertions)) {
1237
- const resolved = readJsonPathValue(parsedJson, fieldPath);
1238
- if (!resolved.found) {
1239
- failures.push(`assert_json_field_gte missing path "${fieldPath}"`);
1240
- continue;
1241
- }
1242
- if (typeof resolved.value !== "number" ||
1243
- !Number.isFinite(resolved.value)) {
1244
- failures.push(`assert_json_field_gte path "${fieldPath}" resolved to non-numeric value`);
1245
- continue;
1246
- }
1247
- if (resolved.value < expectedMinimum) {
1248
- failures.push(`assert_json_field_gte failed at "${fieldPath}" (expected >= ${expectedMinimum}, actual ${resolved.value})`);
1249
- }
1250
- }
1251
- return failures;
1252
- }
1253
- /* c8 ignore stop */
1254
- function evaluateContainsAssertions(streamName, output, expectedValues) {
1255
- const failures = [];
1256
- for (const expected of expectedValues ?? []) {
1257
- if (!output.includes(expected)) {
1258
- failures.push(`${streamName} missing required text: "${expected}"`);
1259
- }
1260
- }
1261
- return failures;
1262
- }
1263
- function evaluateRegexAssertions(streamName, output, patterns) {
1264
- const failures = [];
1265
- for (const pattern of patterns ?? []) {
1266
- try {
1267
- const regex = new RegExp(pattern, "m");
1268
- if (!regex.test(output)) {
1269
- failures.push(`${streamName} failed regex assertion: /${pattern}/m`);
1270
- }
1271
- }
1272
- catch (error) {
1273
- failures.push(`${streamName} regex assertion is invalid: /${pattern}/ (${error instanceof Error ? error.message : String(error)})`);
1274
- }
1275
- }
1276
- return failures;
1277
- }
1278
- function evaluateStdoutLineCountAssertion(stdout, minimum) {
1279
- if (typeof minimum !== "number") {
1280
- return [];
1281
- }
1282
- const lineCount = stdout
1283
- .split(/\r?\n/)
1284
- .map((line) => line.trim())
1285
- .filter((line) => line.length > 0).length;
1286
- return lineCount < minimum
1287
- ? [`stdout line count ${lineCount} is below required minimum ${minimum}`]
1288
- : [];
1289
- }
1290
- const EMPTY_LINKED_TEST_RUN_PATTERNS = [
1291
- {
1292
- code: "no_projects_matched_filters",
1293
- regex: /\bNo projects matched the filters\b/i,
1294
- },
1295
- { code: "no_test_files_found", regex: /\bNo test files found\b/i },
1296
- { code: "no_tests_found", regex: /\bNo tests found\b/i },
1297
- { code: "no_matching_tests", regex: /\bNo matching tests?\b/i },
1298
- { code: "collected_zero_items", regex: /\bcollected 0 items?\b/i },
1299
- {
1300
- code: "reported_zero_passes",
1301
- 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,
1302
- },
1303
- {
1304
- code: "reported_zero_tests",
1305
- regex: /^\s*(?:#|ℹ)?\s*tests\s+0\s*$/imu,
1306
- },
1307
- ];
1308
- const POSITIVE_LINKED_TEST_RUN_PATTERNS = [
1309
- /^\s*(?:#|ℹ)?\s*tests\s+[1-9]\d*\s*$/imu,
1310
- /^\s*(?:#|ℹ)?\s*pass\s+[1-9]\d*\s*$/imu,
1311
- /\bTests?\s+[1-9]\d*\s+passed\b/iu,
1312
- /\b[1-9]\d*\s+passed\b/iu,
1313
- /\bTests:\s+(?:.*\b)?[1-9]\d*\s+passed\b/iu,
1314
- ];
1315
- function detectEmptyLinkedTestRun(stdout, stderr) {
1316
- const combined = `${stdout}\n${stderr}`;
1317
- for (const pattern of EMPTY_LINKED_TEST_RUN_PATTERNS) {
1318
- if (pattern.regex.test(combined)) {
1319
- return { code: pattern.code };
1320
- }
1321
- }
1322
- return null;
1323
- }
1324
- /** Return whether runner output contains a recognized positive executed-test receipt. */
1325
- function hasPositiveLinkedTestRunReceipt(stdout, stderr) {
1326
- const combined = `${stdout}\n${stderr}`;
1327
- return POSITIVE_LINKED_TEST_RUN_PATTERNS.some((pattern) => pattern.test(combined));
1328
- }
1329
- /** Return whether a linked command applies a runner-level test-name filter that must match at least one test. */
1330
- function commandUsesTestNameFilter(command) {
1331
- const normalized = normalizeCommandForValidation(command);
1332
- if (/(?:^|\s)(?:--test-name-pattern|--testnamepattern)(?:=|\s)/u.test(normalized)) {
1333
- return true;
1334
- }
1335
- return splitNormalizedCommandSegments(normalized).some((segment) => /(?:^|\s)-t(?:=|\s)/u.test(segment) &&
1336
- segmentInvokesDirectTestRunner(segment));
1337
- }
1338
- function createLinkedTestSandboxLayout(sandboxRoot) {
1339
- return {
1340
- root: sandboxRoot,
1341
- schemaProjectPmPath: path.join(sandboxRoot, "schema", "project", ".agents", "pm"),
1342
- schemaGlobalPmPath: path.join(sandboxRoot, "schema", "global"),
1343
- trackerProjectPmPath: path.join(sandboxRoot, "tracker", "project", ".agents", "pm"),
1344
- trackerGlobalPmPath: path.join(sandboxRoot, "tracker", "global"),
1345
- workspaceSnapshotRoot: path.join(sandboxRoot, "workspace", "project"),
1346
- workspaceIsolatedRoot: path.join(sandboxRoot, "workspace", "isolated"),
1347
- };
1348
- }
1349
- function linkedTestsRequireWorkspaceSnapshot(tests, runLevelMode, overrideLinkedMode) {
1350
- return tests.some((linkedTest) => resolveLinkedTestWorkspaceContextMode(linkedTest, runLevelMode, overrideLinkedMode) === "snapshot");
1351
- }
1352
- async function seedLinkedTestWorkspaceSnapshot(layout, sourceWorkspaceRoot) {
1353
- const excludedSegments = new Set([
1354
- ".agents",
1355
- ".git",
1356
- ".nyc_output",
1357
- ".turbo",
1358
- "coverage",
1359
- "node_modules",
1360
- ]);
1361
- await mkdir(path.dirname(layout.workspaceSnapshotRoot), { recursive: true });
1362
- await cp(sourceWorkspaceRoot, layout.workspaceSnapshotRoot, {
1363
- recursive: true,
1364
- force: true,
1365
- filter(source) {
1366
- const relative = path.relative(sourceWorkspaceRoot, source);
1367
- if (!relative) {
1368
- return true;
1369
- }
1370
- return !relative
1371
- .split(path.sep)
1372
- .some((segment) => excludedSegments.has(segment));
1373
- },
1374
- });
1375
- const sourceNodeModules = path.join(sourceWorkspaceRoot, "node_modules");
1376
- if (await pathExists(sourceNodeModules)) {
1377
- await symlink(sourceNodeModules, path.join(layout.workspaceSnapshotRoot, "node_modules"), process.platform === "win32" ? "junction" : "dir");
1378
- }
1379
- }
1380
- async function bindSnapshotTracker(snapshotRoot, sandboxPmPath) {
1381
- const agentsRoot = path.join(snapshotRoot, ".agents");
1382
- const target = path.join(agentsRoot, "pm");
1383
- await mkdir(agentsRoot, { recursive: true });
1384
- await rm(target, { recursive: true, force: true });
1385
- await symlink(sandboxPmPath, target, process.platform === "win32" ? "junction" : "dir");
1386
- }
1387
- async function initializeLinkedTestSandboxes(layout, initialize = runInit, includeTrackerData = true) {
1388
- const initOptions = { defaults: true, agentGuidance: "skip" };
1389
- await initialize(undefined, { path: layout.schemaProjectPmPath }, initOptions);
1390
- await initialize(undefined, { path: layout.schemaGlobalPmPath }, initOptions);
1391
- if (!includeTrackerData) {
1392
- return;
1393
- }
1394
- await initialize(undefined, { path: layout.trackerProjectPmPath }, initOptions);
1395
- await initialize(undefined, { path: layout.trackerGlobalPmPath }, initOptions);
1396
- }
1397
- async function seedLinkedTestSandboxesFromSource(layout, sourceRoots, includeTrackerData) {
1398
- if (!sourceRoots) {
1399
- return;
1400
- }
1401
- await seedLinkedTestSandbox(layout.schemaProjectPmPath, layout.schemaGlobalPmPath, sourceRoots);
1402
- if (!includeTrackerData) {
1403
- return;
1404
- }
1405
- await seedLinkedTestSandbox(layout.trackerProjectPmPath, layout.trackerGlobalPmPath, sourceRoots);
1406
- await seedLinkedTestTrackerData(sourceRoots.projectPmRoot, layout.trackerProjectPmPath);
1407
- await seedLinkedTestTrackerData(sourceRoots.globalPmRoot, layout.trackerGlobalPmPath);
1408
- await seedMissingLinkedTestSettingsHistory(sourceRoots.projectPmRoot, layout.trackerProjectPmPath);
1409
- await seedMissingLinkedTestSettingsHistory(sourceRoots.globalPmRoot, layout.trackerGlobalPmPath);
1410
- }
1411
- function linkedTestsRequireTrackerData(tests, runLevelPmContextMode, options) {
1412
- return tests.some((linkedTest) => {
1413
- const command = linkedTest.command ?? "";
1414
- const isPmTrackerReadCommand = command.length > 0 && commandInvokesPmTrackerReadCommand(command);
1415
- const requestedPmContextMode = options?.autoPmContext === true && isPmTrackerReadCommand
1416
- ? "auto"
1417
- : resolveLinkedTestRequestedContextMode(linkedTest, runLevelPmContextMode, options?.overrideLinkedPmContext === true);
1418
- return (resolveLinkedTestEffectiveContextMode(requestedPmContextMode, isPmTrackerReadCommand) === "tracker");
1419
- });
1420
- }
1421
- async function countLinkedTestSandboxItems(layout, sourceRoots) {
1422
- return {
1423
- sourceProjectItemCount: sourceRoots
1424
- ? await countLinkedTestItemFiles(sourceRoots.projectPmRoot)
1425
- : 0,
1426
- sourceGlobalItemCount: sourceRoots
1427
- ? await countLinkedTestItemFiles(sourceRoots.globalPmRoot)
1428
- : 0,
1429
- schemaProjectItemCount: await countLinkedTestItemFiles(layout.schemaProjectPmPath),
1430
- schemaGlobalItemCount: await countLinkedTestItemFiles(layout.schemaGlobalPmPath),
1431
- trackerProjectItemCount: await countLinkedTestItemFiles(layout.trackerProjectPmPath),
1432
- trackerGlobalItemCount: await countLinkedTestItemFiles(layout.trackerGlobalPmPath),
1433
- };
1434
- }
1435
- function buildLinkedTestExecutionContext(params) {
1436
- const trackerMode = params.effectivePmContextMode === "tracker";
1437
- const selectedSandboxProjectPmPath = trackerMode
1438
- ? params.layout.trackerProjectPmPath
1439
- : params.layout.schemaProjectPmPath;
1440
- const selectedSandboxGlobalPmPath = trackerMode
1441
- ? params.layout.trackerGlobalPmPath
1442
- : params.layout.schemaGlobalPmPath;
1443
- const selectedSandboxProjectItemCount = trackerMode
1444
- ? params.counts.trackerProjectItemCount
1445
- : params.counts.schemaProjectItemCount;
1446
- const selectedSandboxGlobalItemCount = trackerMode
1447
- ? params.counts.trackerGlobalItemCount
1448
- : params.counts.schemaGlobalItemCount;
1449
- const workingDirectory = params.workspaceContextMode === "snapshot"
1450
- ? params.layout.workspaceSnapshotRoot
1451
- : params.workspaceContextMode === "isolated"
1452
- ? params.layout.workspaceIsolatedRoot
1453
- : params.sourceWorkspaceRoot;
1454
- const exposedSourceWorkspaceRoot = params.workspaceContextMode === "isolated" ? "" : workingDirectory;
1455
- return {
1456
- requested_pm_context_mode: params.requestedPmContextMode,
1457
- pm_context_mode: params.effectivePmContextMode,
1458
- auto_pm_context_applied: params.autoPmContextApplied,
1459
- is_pm_command: params.isPmCommand,
1460
- is_pm_tracker_read_command: params.isPmTrackerReadCommand,
1461
- source_project_pm_path: params.sourceRoots?.projectPmRoot ?? "",
1462
- sandbox_project_pm_path: selectedSandboxProjectPmPath,
1463
- source_global_pm_path: params.sourceRoots?.globalPmRoot ?? "",
1464
- sandbox_global_pm_path: selectedSandboxGlobalPmPath,
1465
- source_project_item_count: params.counts.sourceProjectItemCount,
1466
- sandbox_project_item_count: selectedSandboxProjectItemCount,
1467
- source_global_item_count: params.counts.sourceGlobalItemCount,
1468
- sandbox_global_item_count: selectedSandboxGlobalItemCount,
1469
- mismatch_detected: params.isPmCommand &&
1470
- (params.counts.sourceProjectItemCount !==
1471
- selectedSandboxProjectItemCount ||
1472
- params.counts.sourceGlobalItemCount !== selectedSandboxGlobalItemCount),
1473
- project_extensions_seeded: Boolean(params.sourceRoots),
1474
- global_extensions_seeded: Boolean(params.sourceRoots),
1475
- requested_workspace_context_mode: params.requestedWorkspaceContextMode,
1476
- workspace_context_mode: params.workspaceContextMode,
1477
- working_directory: workingDirectory,
1478
- source_workspace_root: exposedSourceWorkspaceRoot,
1479
- trust: params.trust,
1480
- };
1481
- }
1482
- async function resolveLinkedTestCommandContext(params) {
1483
- const linkedOverridePmContextMode = typeof params.linkedTest.pm_context_mode === "string" &&
1484
- params.linkedTest.pm_context_mode.trim().length > 0
1485
- ? parsePmContextMode(params.linkedTest.pm_context_mode)
1486
- : undefined;
1487
- const command = typeof params.linkedTest.command === "string" &&
1488
- params.linkedTest.command.length > 0
1489
- ? params.linkedTest.command
1490
- : undefined;
1491
- const isPmCommand = command ? commandInvokesPmCli(command) : false;
1492
- const isPmTrackerReadCommand = isPmCommand && command
1493
- ? commandInvokesPmTrackerReadCommand(command)
1494
- : false;
1495
- const autoPmContextApplied = params.options?.autoPmContext === true && isPmTrackerReadCommand;
1496
- const requestedPmContextMode = autoPmContextApplied
1497
- ? "auto"
1498
- : resolveLinkedTestRequestedContextMode(params.linkedTest, params.runLevelPmContextMode, params.options?.overrideLinkedPmContext === true);
1499
- const effectivePmContextMode = resolveLinkedTestEffectiveContextMode(requestedPmContextMode, isPmTrackerReadCommand);
1500
- const selectedSandboxProjectPmPath = effectivePmContextMode === "tracker"
1501
- ? params.layout.trackerProjectPmPath
1502
- : params.layout.schemaProjectPmPath;
1503
- if (params.workspaceContextMode === "snapshot") {
1504
- await bindSnapshotTracker(params.layout.workspaceSnapshotRoot, selectedSandboxProjectPmPath);
1505
- }
1506
- const trust = params.options?.pmRoot
1507
- ? await resolveLinkedTestTrust(params.options.pmRoot, params.linkedTest, params.currentSourceRef)
1508
- : await resolveLinkedTestTrust(params.layout.root, params.linkedTest, params.currentSourceRef);
1509
- return {
1510
- linkedOverridePmContextMode,
1511
- executionContext: buildLinkedTestExecutionContext({
1512
- layout: params.layout,
1513
- counts: params.counts,
1514
- sourceRoots: params.sourceRoots,
1515
- isPmCommand,
1516
- isPmTrackerReadCommand,
1517
- requestedPmContextMode,
1518
- effectivePmContextMode,
1519
- autoPmContextApplied,
1520
- requestedWorkspaceContextMode: params.requestedWorkspaceContextMode,
1521
- workspaceContextMode: params.workspaceContextMode,
1522
- sourceWorkspaceRoot: params.sourceWorkspaceRoot,
1523
- trust,
1524
- }),
1525
- };
1526
- }
1527
- function buildLinkedTestSkippedResult(linkedTest, executionContext, error) {
1528
- return {
1529
- command: linkedTest.command,
1530
- path: linkedTest.path,
1531
- status: "skipped",
1532
- execution_context: executionContext,
1533
- error,
1534
- };
1535
- }
1536
- function buildLinkedTestAssertionFailureResult(linkedTest, executionContext, error, output) {
1537
- return {
1538
- command: linkedTest.command,
1539
- path: linkedTest.path,
1540
- status: "failed",
1541
- exit_code: 1,
1542
- failure_category: "assertion_failure",
1543
- execution_context: executionContext,
1544
- ...(output ? { stdout: output.stdout, stderr: output.stderr } : {}),
1545
- error,
1546
- };
1547
- }
1548
- function buildLinkedTestTrustRefusalResult(linkedTest, executionContext, error) {
1549
- return {
1550
- command: linkedTest.command,
1551
- path: linkedTest.path,
1552
- status: "failed",
1553
- exit_code: 1,
1554
- failure_category: "trust_refusal",
1555
- execution_context: executionContext,
1556
- error,
1557
- };
1558
- }
1559
- /** Build the fail-closed result for a command this clone has not trusted. */
1560
- function resolveLinkedTestTrustPreflightResult(params) {
1561
- if (params.executionContext.trust.trusted)
1562
- return null;
1563
- const overrideRequested = params.options?.allowUntrustedLinkedTests === true;
1564
- const policyEnabled = params.options?.untrustedLinkedTestsPolicyEnabled === true;
1565
- if (overrideRequested && policyEnabled)
1566
- return null;
1567
- const recovery = policyEnabled
1568
- ? "Retry with --allow-untrusted-linked-tests for this invocation, or acknowledge the current commands with --acknowledge-linked-tests."
1569
- : "Set testing.allow_untrusted_linked_tests=true, then retry with --allow-untrusted-linked-tests; alternatively acknowledge the current commands with --acknowledge-linked-tests.";
1570
- 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}`);
1571
- }
1572
- /** Resolve missing-command and runtime-safety skips before policy checks. */
1573
- function resolveLinkedTestSafetyPreflightResult(params) {
1574
- if (!params.linkedTest.command) {
1575
- return buildLinkedTestSkippedResult(params.linkedTest, params.executionContext, "No command configured for this linked test.");
1576
- }
1577
- const runtimeSafetySkipReason = getRuntimeSafetySkipReason(params.linkedTest.command);
1578
- return runtimeSafetySkipReason
1579
- ? buildLinkedTestSkippedResult(params.linkedTest, params.executionContext, runtimeSafetySkipReason)
1580
- : null;
1581
- }
1582
- function resolveLinkedTestPreflightResult(params) {
1583
- const safetyFailure = resolveLinkedTestSafetyPreflightResult(params);
1584
- if (safetyFailure)
1585
- return safetyFailure;
1586
- const trustFailure = resolveLinkedTestTrustPreflightResult(params);
1587
- if (trustFailure)
1588
- return trustFailure;
1589
- const failOnMismatchByDefault = params.executionContext.pm_context_mode === "schema" &&
1590
- params.executionContext.is_pm_tracker_read_command &&
1591
- params.executionContext.mismatch_detected;
1592
- const failOnMismatchByFlag = params.options?.failOnContextMismatch === true &&
1593
- params.executionContext.is_pm_command &&
1594
- params.executionContext.mismatch_detected;
1595
- if (failOnMismatchByDefault || failOnMismatchByFlag) {
1596
- const mismatchHint = buildPmContextMismatchHint({
1597
- executionContext: params.executionContext,
1598
- runLevelPmContextMode: params.runLevelPmContextMode,
1599
- linkedOverridePmContextMode: params.linkedOverridePmContextMode,
1600
- });
1601
- const mismatchPrefix = params.options?.checkContext === true
1602
- ? "Linked test preflight PM context mismatch detected"
1603
- : "Linked test PM context mismatch detected";
1604
- return buildLinkedTestAssertionFailureResult(params.linkedTest, params.executionContext, `${mismatchPrefix} (source_project_items=${params.executionContext.source_project_item_count}, ` +
1605
- `sandbox_project_items=${params.executionContext.sandbox_project_item_count}).${mismatchHint}`);
1606
- }
1607
- if (params.options?.requireAssertionsForPm === true &&
1608
- params.executionContext.is_pm_command &&
1609
- !hasLinkedTestAssertions(params.linkedTest)) {
1610
- return buildLinkedTestAssertionFailureResult(params.linkedTest, params.executionContext, "Linked PM command requires assertions when --require-assertions-for-pm is enabled.");
1611
- }
1612
- return null;
1613
- }
1614
- function buildLinkedTestExecutionEnv(params) {
1615
- const effectiveDirectives = resolveEffectiveLinkedTestDirectives(params.runtimeDirectives, params.linkedTest);
1616
- const executionEnv = {
1617
- ...process.env,
1618
- SENTRY_ENVIRONMENT: "test",
1619
- };
1620
- applyEnvDirectiveStage(executionEnv, params.runtimeDirectives);
1621
- applyEnvDirectiveStage(executionEnv, {
1622
- env_set: params.linkedTest.env_set ?? {},
1623
- env_clear: params.linkedTest.env_clear ?? [],
1624
- });
1625
- if (effectiveDirectives.shared_host_safe) {
1626
- applySharedHostSafeDefaults(executionEnv);
1627
- }
1628
- executionEnv.FORCE_COLOR = "0";
1629
- executionEnv.PM_PATH = params.executionContext.sandbox_project_pm_path;
1630
- executionEnv.PM_GLOBAL_PATH = params.executionContext.sandbox_global_pm_path;
1631
- if (params.executionContext.workspace_context_mode === "isolated") {
1632
- delete executionEnv.PM_SOURCE_WORKSPACE_ROOT;
1633
- delete executionEnv.PM_SOURCE_PM_PATH;
1634
- delete executionEnv[SOURCE_CONTEXT_ACCESS_ENV];
1635
- }
1636
- else {
1637
- executionEnv.PM_SOURCE_WORKSPACE_ROOT =
1638
- params.executionContext.source_workspace_root;
1639
- executionEnv.PM_SOURCE_PM_PATH =
1640
- params.executionContext.workspace_context_mode === "snapshot"
1641
- ? params.executionContext.sandbox_project_pm_path
1642
- : params.executionContext.source_project_pm_path;
1643
- executionEnv[SOURCE_CONTEXT_ACCESS_ENV] = "read_only";
1644
- }
1645
- return executionEnv;
1646
- }
1647
- function buildLinkedTestPassedResult(linkedTest, executionContext, execution) {
1648
- return {
1649
- command: linkedTest.command,
1650
- path: linkedTest.path,
1651
- status: "passed",
1652
- exit_code: 0,
1653
- execution_context: executionContext,
1654
- stdout: execution.stdout,
1655
- stderr: execution.stderr,
1656
- };
1657
- }
1658
- function buildLinkedTestEmptyRunResult(params) {
1659
- return {
1660
- command: params.linkedTest.command,
1661
- path: params.linkedTest.path,
1662
- status: "failed",
1663
- exit_code: 1,
1664
- failure_category: "empty_run",
1665
- execution_context: params.executionContext,
1666
- stdout: params.execution.stdout,
1667
- stderr: params.execution.stderr,
1668
- error: `Linked test reported an empty test run (${params.emptyRunSignal.code}). ` +
1669
- "Update the test selection so at least one test executes; unfiltered commands may opt out by omitting --fail-on-empty-test-run.",
1670
- };
1671
- }
1672
- function buildLinkedTestCommandFailureResult(params) {
1673
- return {
1674
- command: params.linkedTest.command,
1675
- path: params.linkedTest.path,
1676
- status: "failed",
1677
- exit_code: resolveLinkedTestFailureExitCode(params.execution),
1678
- failure_category: classifyLinkedTestFailure(params.execution),
1679
- execution_context: params.executionContext,
1680
- stdout: params.execution.stdout,
1681
- stderr: params.execution.stderr,
1682
- error: formatLinkedTestExecutionError(params.execution, params.timeoutMs),
1683
- };
1684
- }
1685
- function buildLinkedTestPassedExecutionResult(params) {
1686
- if (params.options?.failOnEmptyTestRun === true ||
1687
- commandUsesTestNameFilter(params.linkedTest.command ?? "")) {
1688
- const emptyRunSignal = detectEmptyLinkedTestRun(params.execution.stdout, params.execution.stderr);
1689
- if (emptyRunSignal) {
1690
- return buildLinkedTestEmptyRunResult({ ...params, emptyRunSignal });
1691
- }
1692
- if (commandUsesTestNameFilter(params.linkedTest.command ?? "") &&
1693
- !hasPositiveLinkedTestRunReceipt(params.execution.stdout, params.execution.stderr)) {
1694
- return buildLinkedTestEmptyRunResult({
1695
- ...params,
1696
- emptyRunSignal: { code: "missing_positive_execution_receipt" },
1697
- });
1698
- }
1699
- }
1700
- const assertionFailures = evaluateLinkedTestAssertions(params.linkedTest, params.execution.stdout, params.execution.stderr);
1701
- if (assertionFailures.length > 0) {
1702
- return buildLinkedTestAssertionFailureResult(params.linkedTest, params.executionContext, `Linked test assertion(s) failed: ${assertionFailures.join("; ")}`, params.execution);
1703
- }
1704
- return buildLinkedTestPassedResult(params.linkedTest, params.executionContext, params.execution);
1705
- }
1706
- async function runSingleLinkedTest(params) {
1707
- const command = params.linkedTest.command ?? "";
1708
- const timeoutMs = (params.linkedTest.timeout_seconds ?? params.defaultTimeoutSeconds ?? 120) *
1709
- 1000;
1710
- const execution = await runLinkedTestCommand(command, timeoutMs, buildLinkedTestExecutionEnv(params), {
1711
- index: params.index + 1,
1712
- total: params.total,
1713
- timeoutMs,
1714
- command,
1715
- }, params.progressMode, params.executionContext.working_directory);
1716
- const passed = execution.exitCode === 0 &&
1717
- !execution.timedOut &&
1718
- !execution.maxBufferExceeded;
1719
- return passed
1720
- ? buildLinkedTestPassedExecutionResult({ ...params, execution })
1721
- : buildLinkedTestCommandFailureResult({ ...params, execution, timeoutMs });
1722
- }
1723
- /** Implements run linked tests for the public runtime surface of this module. */
1724
- export async function runLinkedTests(tests, defaultTimeoutSeconds, options) {
1725
- const results = [];
1726
- const sandboxRoot = await mkdtemp(path.join(tmpdir(), "pm-linked-test-"));
1727
- const layout = createLinkedTestSandboxLayout(sandboxRoot);
1728
- const runLevelPmContextMode = parsePmContextMode(options?.pmContext);
1729
- const runLevelWorkspaceContextMode = parseWorkspaceContextMode(options?.workspaceContext);
1730
- const progressMode = options?.progress === true ? "always" : "auto";
1731
- const runtimeDirectives = resolveRuntimeDirectives(options?.envSet, options?.envClear, options?.sharedHostSafe);
1732
- const sourceRoots = options?.sourceRoots;
1733
- const sourceWorkspaceRoot = resolveLinkedTestSourceWorkspaceRoot();
1734
- const includeTrackerData = linkedTestsRequireTrackerData(tests, runLevelPmContextMode, options);
1735
- const includeWorkspaceSnapshot = linkedTestsRequireWorkspaceSnapshot(tests, runLevelWorkspaceContextMode, options?.overrideLinkedWorkspaceContext === true);
1736
- const currentSourceRef = await resolveLinkedTestSourceRef(sourceWorkspaceRoot);
1737
- try {
1738
- await initializeLinkedTestSandboxes(layout, runInit, includeTrackerData);
1739
- await mkdir(layout.workspaceIsolatedRoot, { recursive: true });
1740
- await seedLinkedTestSandboxesFromSource(layout, sourceRoots, includeTrackerData);
1741
- if (includeWorkspaceSnapshot) {
1742
- await seedLinkedTestWorkspaceSnapshot(layout, sourceWorkspaceRoot);
1743
- }
1744
- const counts = await countLinkedTestSandboxItems(layout, sourceRoots);
1745
- for (let index = 0; index < tests.length; index += 1) {
1746
- const linkedTest = tests[index];
1747
- const workspaceContextMode = resolveLinkedTestWorkspaceContextMode(linkedTest, runLevelWorkspaceContextMode, options?.overrideLinkedWorkspaceContext === true);
1748
- const commandContext = await resolveLinkedTestCommandContext({
1749
- linkedTest,
1750
- layout,
1751
- counts,
1752
- sourceRoots,
1753
- runLevelPmContextMode,
1754
- options,
1755
- requestedWorkspaceContextMode: runLevelWorkspaceContextMode,
1756
- workspaceContextMode,
1757
- currentSourceRef,
1758
- sourceWorkspaceRoot,
1759
- });
1760
- const preflightResult = resolveLinkedTestPreflightResult({
1761
- linkedTest,
1762
- executionContext: commandContext.executionContext,
1763
- runLevelPmContextMode,
1764
- linkedOverridePmContextMode: commandContext.linkedOverridePmContextMode,
1765
- options,
1766
- });
1767
- if (preflightResult) {
1768
- results.push(preflightResult);
1769
- continue;
1770
- }
1771
- results.push(await runSingleLinkedTest({
1772
- linkedTest,
1773
- index,
1774
- total: tests.length,
1775
- defaultTimeoutSeconds,
1776
- runtimeDirectives,
1777
- progressMode,
1778
- executionContext: commandContext.executionContext,
1779
- options,
1780
- }));
1781
- }
1782
- }
1783
- finally {
1784
- await rm(layout.root, {
1785
- recursive: true,
1786
- force: true,
1787
- maxRetries: 3,
1788
- retryDelay: 100,
1789
- });
1790
- }
1791
- return results;
1792
- }
1793
- async function readLinkedTestItem(params) {
1794
- const { id, pmRoot, settings, typeToFolder } = params;
1795
- const located = await locateItem(pmRoot, id, settings.id_prefix, settings.item_format, typeToFolder);
1796
- if (!located) {
1797
- throw new PmCliError(`Item ${id} not found`, EXIT_CODE.NOT_FOUND);
1798
- }
1799
- const loaded = await readLocatedItem(located, { schema: settings.schema });
1800
- return {
1801
- itemId: located.id,
1802
- tests: loaded.document.metadata.tests ?? [],
1803
- testRuns: loaded.document.metadata.test_runs ?? [],
1804
- changed: false,
1805
- removed: 0,
1806
- };
1807
- }
1808
- function linkedTestsHaveSameIdentity(left, right) {
1809
- return (left.command === right.command &&
1810
- left.path === right.path &&
1811
- left.scope === right.scope &&
1812
- left.pm_context_mode === right.pm_context_mode &&
1813
- left.workspace_context_mode === right.workspace_context_mode);
1814
- }
1815
- function appendMissingLinkedTests(current, additions) {
1816
- const next = [...current];
1817
- for (const addition of additions) {
1818
- if (!next.some((entry) => linkedTestsHaveSameIdentity(entry, addition))) {
1819
- next.push(addition);
1820
- }
1821
- }
1822
- return next;
1823
- }
1824
- function removeLinkedTestsBySelector(current, removals, removeIndexes) {
1825
- if (removals.length === 0 && removeIndexes.length === 0) {
1826
- return { tests: current, removed: 0 };
1827
- }
1828
- const indexSet = new Set(removeIndexes);
1829
- const tests = current.filter((entry, index) => {
1830
- if (indexSet.has(index + 1))
1831
- return false;
1832
- return !removals.some((selector) => {
1833
- if (selector.kind === "command")
1834
- return entry.command === selector.value;
1835
- if (selector.kind === "path")
1836
- return entry.path === selector.value;
1837
- return entry.command === selector.value || entry.path === selector.value;
1838
- });
1839
- });
1840
- return { tests, removed: current.length - tests.length };
1841
- }
1842
- function applyLinkedTestMutations(previous, adds, removes, removeIndexes) {
1843
- return removeLinkedTestsBySelector(appendMissingLinkedTests(previous, adds), removes, removeIndexes);
1844
- }
1845
- function hasTestRuntimeDirectiveFlags(options) {
1846
- return [
1847
- (options.envSet?.length ?? 0) > 0,
1848
- (options.envClear?.length ?? 0) > 0,
1849
- options.sharedHostSafe === true,
1850
- options.pmContext !== undefined,
1851
- options.overrideLinkedPmContext === true,
1852
- options.failOnContextMismatch === true,
1853
- options.failOnSkipped === true,
1854
- options.failOnEmptyTestRun === true,
1855
- options.requireAssertionsForPm === true,
1856
- options.checkContext === true,
1857
- options.autoPmContext === true,
1858
- options.workspaceContext !== undefined,
1859
- options.overrideLinkedWorkspaceContext === true,
1860
- options.allowUntrustedLinkedTests === true,
1861
- ].some(Boolean);
1862
- }
1863
- function assertTestRunFlagUsage(options) {
1864
- if (hasTestRuntimeDirectiveFlags(options) && options.run !== true) {
1865
- 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);
1866
- }
1867
- if (options.acknowledgeLinkedTests === true && options.run === true) {
1868
- throw new PmCliError("--acknowledge-linked-tests records trust without executing commands; run it separately before --run", EXIT_CODE.USAGE);
1869
- }
1870
- const hasSelectorFlags = options.match !== undefined ||
1871
- options.onlyIndex !== undefined ||
1872
- options.onlyLast === true;
1873
- if (hasSelectorFlags && options.run !== true) {
1874
- throw new PmCliError("--match, --only-index, and --only-last require --run", EXIT_CODE.USAGE);
1875
- }
1876
- }
1877
- async function resolveLinkedTestItem(params) {
1878
- const { id, options, pmRoot, settings, typeToFolder, adds, removes, removeIndexes, } = params;
1879
- if (adds.length === 0 && removes.length === 0 && removeIndexes.length === 0) {
1880
- return readLinkedTestItem({ id, pmRoot, settings, typeToFolder });
1881
- }
1882
- let removed = 0;
1883
- const result = await mutateItem({
1884
- pmRoot,
1885
- settings,
1886
- id,
1887
- op: adds.length > 0 ? "tests_add" : "tests_remove",
1888
- author: resolveAuthor(options.author, settings.author_default),
1889
- message: options.message,
1890
- force: options.force,
1891
- skipNoop: true,
1892
- mutate(document) {
1893
- const previous = document.metadata.tests ?? [];
1894
- const mutation = applyLinkedTestMutations(previous, adds, removes, removeIndexes);
1895
- removed = mutation.removed;
1896
- if ((removes.length > 0 || removeIndexes.length > 0) && removed === 0) {
1897
- throw new PmCliError("No linked tests matched the requested removal selector", EXIT_CODE.NOT_FOUND, {
1898
- code: "linked_test_remove_no_match",
1899
- unmatched: [
1900
- ...removes.map((entry) => `${entry.kind}=${entry.value}`),
1901
- ...removeIndexes.map((index) => `index=${index}`),
1902
- ],
1903
- required: "List linked tests and retry with an exact command/path or a current 1-based --remove-index.",
1904
- examples: [
1905
- `pm test ${id} --list`,
1906
- `pm test ${id} --remove-index 1`,
1907
- ],
1908
- });
1909
- }
1910
- document.metadata.tests = mutation.tests;
1911
- return {
1912
- changedFields: stableValueEquals(previous, mutation.tests)
1913
- ? []
1914
- : ["tests"],
1915
- };
1916
- },
1917
- });
1918
- return {
1919
- itemId: result.item.id,
1920
- tests: result.item.tests ?? [],
1921
- testRuns: result.item.test_runs ?? [],
1922
- changed: result.changedFields.length > 0,
1923
- removed,
1924
- };
1925
- }
1926
- function resolveTestRunOptions(options, tests) {
1927
- assertTestRunFlagUsage(options);
1928
- const selectorInput = {
1929
- match: options.match,
1930
- onlyIndex: options.onlyIndex === undefined
1931
- ? undefined
1932
- : parseOnlyIndexValue(options.onlyIndex),
1933
- onlyLast: options.onlyLast,
1934
- };
1935
- const runSelection = options.run === true
1936
- ? resolveLinkedTestRunSelection(tests, selectorInput)
1937
- : undefined;
1938
- const defaultTimeoutSeconds = options.timeout === undefined
1939
- ? undefined
1940
- : parseOptionalNumber(options.timeout, "timeout");
1941
- if (defaultTimeoutSeconds !== undefined && defaultTimeoutSeconds <= 0) {
1942
- throw new PmCliError("Test timeout must be a positive number", EXIT_CODE.USAGE);
1943
- }
1944
- return {
1945
- defaultTimeoutSeconds,
1946
- pmContextMode: parsePmContextMode(options.pmContext),
1947
- runSelection,
1948
- testsToRun: runSelection?.selected ?? tests,
1949
- };
1950
- }
1951
- async function executeSelectedLinkedTests(params) {
1952
- const { options, runOptions, pmRoot } = params;
1953
- if (options.run !== true) {
1954
- return [];
1955
- }
1956
- return await runLinkedTests(runOptions.testsToRun, runOptions.defaultTimeoutSeconds, {
1957
- progress: options.progress,
1958
- envSet: options.envSet,
1959
- envClear: options.envClear,
1960
- sharedHostSafe: options.sharedHostSafe,
1961
- pmContext: runOptions.pmContextMode,
1962
- overrideLinkedPmContext: options.overrideLinkedPmContext,
1963
- failOnContextMismatch: options.failOnContextMismatch,
1964
- failOnEmptyTestRun: options.failOnEmptyTestRun,
1965
- requireAssertionsForPm: options.requireAssertionsForPm,
1966
- checkContext: options.checkContext,
1967
- autoPmContext: options.autoPmContext,
1968
- workspaceContext: options.workspaceContext,
1969
- overrideLinkedWorkspaceContext: options.overrideLinkedWorkspaceContext,
1970
- pmRoot,
1971
- allowUntrustedLinkedTests: options.allowUntrustedLinkedTests,
1972
- untrustedLinkedTestsPolicyEnabled: params.untrustedLinkedTestsPolicyEnabled,
1973
- sourceRoots: {
1974
- projectPmRoot: pmRoot,
1975
- globalPmRoot: resolveGlobalPmRoot(process.cwd()),
1976
- },
1977
- });
1978
- }
1979
- function buildTestWarnings(options, runSelection, runResults) {
1980
- const warnings = [];
1981
- if (runSelection && runSelection.selector !== null) {
1982
- warnings.push(`linked_test_selection:${runSelection.selector}=${runSelection.requested};selected=${runSelection.selected_count};skipped=${runSelection.skipped_count};indexes=${runSelection.selected_indexes.join(",")}`);
1983
- }
1984
- if (options.run === true && options.checkContext === true) {
1985
- const preflight = summarizeContextPreflight(runResults);
1986
- warnings.push(`context_preflight:checked_pm_commands=${preflight.checked_pm_commands};` +
1987
- `tracker_read_commands=${preflight.tracker_read_commands};` +
1988
- `mismatches=${preflight.mismatches};` +
1989
- `auto_remediated=${preflight.auto_remediated}`);
1990
- }
1991
- return warnings;
1992
- }
1993
- function buildTrackedTestRunSummary(params) {
1994
- const summary = summarizeRunResultStatuses(params.runResults);
1995
- const parsedAttempt = Number.parseInt(process.env.PM_BACKGROUND_TEST_RUN_ATTEMPT?.trim() ?? "", 10);
1996
- const resumedFrom = process.env.PM_BACKGROUND_TEST_RUN_RESUMED_FROM?.trim();
1997
- const recordedAt = nowIso();
1998
- const measurements = parseTestRunMeasurements(params.options.measure, recordedAt);
1999
- return {
2000
- run_id: resolveTrackedRunId("test"),
2001
- kind: "test",
2002
- status: summary.failed > 0 || params.failOnSkippedTriggered ? "failed" : "passed",
2003
- started_at: params.runStartedAt,
2004
- finished_at: recordedAt,
2005
- recorded_at: recordedAt,
2006
- attempt: Number.isFinite(parsedAttempt) && parsedAttempt >= 1
2007
- ? parsedAttempt
2008
- : undefined,
2009
- resumed_from: resumedFrom && resumedFrom.length > 0 ? resumedFrom : undefined,
2010
- passed: summary.passed,
2011
- failed: summary.failed,
2012
- skipped: summary.skipped,
2013
- fail_on_skipped_triggered: params.failOnSkippedTriggered ? true : undefined,
2014
- measurements: measurements.length > 0 ? measurements : undefined,
2015
- executions: params.runResults
2016
- .filter((result) => result.command?.trim())
2017
- .slice(0, 32)
2018
- .map((result) => ({
2019
- command: result.command.trim(),
2020
- requested_pm_context_mode: result.execution_context?.requested_pm_context_mode,
2021
- pm_context_mode: result.execution_context?.pm_context_mode,
2022
- workspace_context_mode: result.execution_context?.workspace_context_mode,
2023
- trust_reason: result.execution_context?.trust.reason,
2024
- })),
2025
- };
2026
- }
2027
- async function recordTestRunSummary(params) {
2028
- const { options, pmRoot, settings, itemId, runStartedAt, runResults, failOnSkippedTriggered, warnings, } = params;
2029
- if (options.run !== true || !runStartedAt) {
2030
- return undefined;
2031
- }
2032
- const entry = buildTrackedTestRunSummary({
2033
- options,
2034
- runStartedAt,
2035
- runResults,
2036
- failOnSkippedTriggered,
2037
- });
2038
- if (settings.testing.record_results_to_items !== true) {
2039
- return entry;
2040
- }
2041
- try {
2042
- await appendTrackedTestRunSummary({
2043
- pmRoot,
2044
- settings,
2045
- itemId,
2046
- author: resolveAuthor(options.author, settings.author_default),
2047
- message: `Track test run summary (${entry.run_id})`,
2048
- entry,
2049
- });
2050
- return entry;
2051
- }
2052
- catch (error) {
2053
- warnings.push(`test_result_tracking_failed:${itemId}:${error instanceof Error ? error.message : String(error)}`);
2054
- return entry;
2055
- }
2056
- }
2057
- function buildTestMeasurementProjection(options, runs, recordedRun) {
2058
- let metricBelow;
2059
- if (options.metricBelow !== undefined) {
2060
- const separator = options.metricBelow.indexOf("=");
2061
- const name = options.metricBelow.slice(0, separator).trim();
2062
- const thresholdText = options.metricBelow.slice(separator + 1).trim();
2063
- const threshold = Number(thresholdText);
2064
- if (separator <= 0 ||
2065
- name.length === 0 ||
2066
- thresholdText.length === 0 ||
2067
- !Number.isFinite(threshold)) {
2068
- throw new PmCliError("--metric-below must use name=value", EXIT_CODE.USAGE);
2069
- }
2070
- metricBelow = queryTestRunMeasurementsBelow(runs, name, threshold);
2071
- }
2072
- return {
2073
- measurements: recordedRun?.measurements,
2074
- metric_below: metricBelow,
2075
- metric_diff: options.metricDiff === undefined
2076
- ? undefined
2077
- : diffTestRunMeasurements(runs, options.metricDiff),
2078
- };
2079
- }
2080
- /** Acknowledge current linked commands only when the dedicated mode is active. */
2081
- async function maybeAcknowledgeLinkedTests(options, pmRoot, tests) {
2082
- if (options.acknowledgeLinkedTests !== true)
2083
- return undefined;
2084
- return acknowledgeLinkedTests(pmRoot, tests, nowIso());
2085
- }
2086
- /** Implements run test for the public runtime surface of this module. */
2087
- export async function runTest(id, options, global) {
2088
- const stdinResolver = createStdinTokenResolver();
2089
- const pmRoot = resolvePmRoot(process.cwd(), global.path);
2090
- await assertInitializedTracker(pmRoot);
2091
- const settings = await readSettings(pmRoot);
2092
- if ((options.measure?.length ?? 0) > 0 && options.run !== true) {
2093
- throw new PmCliError("--measure requires --run", EXIT_CODE.USAGE);
2094
- }
2095
- parseTestRunMeasurements(options.measure, "validation");
2096
- buildTestMeasurementProjection(options, [], undefined);
2097
- const typeRegistry = resolveItemTypeRegistry(settings, getActiveExtensionRegistrations());
2098
- const resolvedAdds = await stdinResolver.resolveList(options.add, "--add");
2099
- const resolvedAddJsons = await stdinResolver.resolveList(options.addJson, "--add-json");
2100
- const resolvedRemoves = await stdinResolver.resolveList(options.remove, "--remove");
2101
- const parsedAdds = [
2102
- ...parseAddEntries(resolvedAdds),
2103
- ...parseAddJsonEntries(resolvedAddJsons),
2104
- ];
2105
- const adds = (await attachLinkedTestMutationProvenance(parsedAdds, resolveAuthor(options.author, settings.author_default), nowIso())) ?? [];
2106
- const removes = parseRemoveEntries(resolvedRemoves);
2107
- const removeIndexes = parseRemoveIndexes(options.removeIndex);
2108
- const item = await resolveLinkedTestItem({
2109
- id,
2110
- options,
2111
- pmRoot,
2112
- settings,
2113
- typeToFolder: typeRegistry.type_to_folder,
2114
- adds,
2115
- removes,
2116
- removeIndexes,
2117
- });
2118
- const runOptions = resolveTestRunOptions(options, item.tests);
2119
- const trustAcknowledgement = await maybeAcknowledgeLinkedTests(options, pmRoot, item.tests);
2120
- const runStartedAt = options.run === true ? nowIso() : undefined;
2121
- const runResults = await executeSelectedLinkedTests({
2122
- options,
2123
- runOptions,
2124
- pmRoot,
2125
- untrustedLinkedTestsPolicyEnabled: settings.testing.allow_untrusted_linked_tests,
2126
- });
2127
- const failureCategories = countFailureCategories(runResults);
2128
- const failOnSkippedTriggered = options.run === true &&
2129
- options.failOnSkipped === true &&
2130
- runResults.some((entry) => entry.status === "skipped");
2131
- const warnings = buildTestWarnings(options, runOptions.runSelection, runResults);
2132
- if (trustAcknowledgement) {
2133
- warnings.push(`linked_test_trust_acknowledged:${trustAcknowledgement.acknowledged}`);
2134
- }
2135
- const recordedRun = await recordTestRunSummary({
2136
- options,
2137
- pmRoot,
2138
- settings,
2139
- itemId: item.itemId,
2140
- runStartedAt,
2141
- runResults,
2142
- failOnSkippedTriggered,
2143
- warnings,
2144
- });
2145
- const runs = recordedRun
2146
- ? buildTrackedTestRunHistory([...item.testRuns, recordedRun])
2147
- : item.testRuns;
2148
- const measurementProjection = buildTestMeasurementProjection(options, runs, recordedRun);
2149
- return {
2150
- ok: runResults.every((entry) => entry.status !== "failed") &&
2151
- failOnSkippedTriggered !== true,
2152
- id: item.itemId,
2153
- tests: item.tests,
2154
- run_results: runResults,
2155
- failure_categories: failureCategories,
2156
- selection: runOptions.runSelection
2157
- ? {
2158
- selector: runOptions.runSelection.selector,
2159
- requested: runOptions.runSelection.requested,
2160
- selected_indexes: runOptions.runSelection.selected_indexes,
2161
- selected_count: runOptions.runSelection.selected_count,
2162
- skipped_count: runOptions.runSelection.skipped_count,
2163
- }
2164
- : undefined,
2165
- fail_on_skipped_triggered: failOnSkippedTriggered ? true : undefined,
2166
- warnings: warnings.length > 0 ? warnings : undefined,
2167
- changed: item.changed,
2168
- count: item.tests.length,
2169
- ...(item.removed > 0 ? { removed: item.removed } : {}),
2170
- ...measurementProjection,
2171
- };
2172
- }
2173
- /* c8 ignore stop */
2174
- /** Public contract for test only test command, shared by SDK and presentation-layer consumers. */
2175
- export const _testOnlyTestCommand = {
2176
- buildLinkedTestExecutionContext,
2177
- buildPmContextMismatchHint,
2178
- commandInvokesPmCli,
2179
- commandInvokesPmTrackerReadCommand,
2180
- commandUsesTestNameFilter,
2181
- copyIntoSandboxIfPresent,
2182
- countLinkedTestItemFiles,
2183
- ensureScope,
2184
- evaluateLinkedTestAssertions,
2185
- endLinkedTestProgress,
2186
- extractPmInvocationArgsFromSegment,
2187
- firstDirectTestRunnerSubcommand,
2188
- formatLinkedTestExecutionError,
2189
- hasLinkedTestAssertions,
2190
- initializeLinkedTestSandboxes,
2191
- linkedTestsRequireTrackerData,
2192
- parseAddJsonEntries,
2193
- parseLinkedTestTimeoutSeconds,
2194
- parsePmContextMode,
2195
- readJsonPathValue,
2196
- resolveAuthor,
2197
- resolveDirectRunnerSubcommand,
2198
- resolveLinkedTestEffectiveContextMode,
2199
- resolveLinkedTestFailureExitCode,
2200
- resolveLinkedTestRequestedContextMode,
2201
- resolveTestRunOptions,
2202
- resolveTrackedRunId,
2203
- runLinkedTestCommand,
2204
- seedMissingLinkedTestSettingsHistory,
2205
- seedLinkedTestSandbox,
2206
- seedLinkedTestTrackerData,
2207
- segmentInvokesRecursiveTestAll,
2208
- splitJsonPathSegments,
2209
- summarizeRunResultStatuses,
2210
- };
1
+ !(function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=new e.Error().stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="2833cfc3-db98-5f7f-82e0-388e320ef740")}catch(e2){}})();import{assertInitializedTracker}from"../environment/tracker-preflight.js";import{spawn}from"node:child_process";import{cp,mkdir,mkdtemp,open,readdir,rm,symlink}from"node:fs/promises";import{tmpdir}from"node:os";import path from"node:path";import{getActiveExtensionRegistrations}from"../../core/extensions/index.js";import{isFileMissingError,pathExists,readFileIfExists}from"../../core/fs/fs-utils.js";import{getWorkspaceHistoryPath,writeWorkspaceJsonWithHistory}from"../../core/history/workspace-history.js";import{resolveItemTypeRegistry}from"../../core/item/type-registry.js";import{createStdinTokenResolver,parseCsvKv,parseOptionalNumber}from"../../core/item/parse.js";import{EXIT_CODE}from"../../core/shared/constants.js";import{resolveAuthor}from"../../core/shared/author.js";import{PmCliError}from"../../core/shared/errors.js";import{stableValueEquals}from"../../core/shared/serialization.js";import{nowIso}from"../../core/shared/time.js";import{locateItem,mutateItem,readLocatedItem}from"../../core/store/item-store.js";import{getSettingsPath,ITEM_FILE_EXTENSIONS,resolveGlobalPmRoot,resolvePmRoot}from"../../core/store/paths.js";import{readSettings}from"../../core/store/settings.js";import{appendTrackedTestRunSummary,buildTrackedTestRunHistory}from"../../core/test/item-test-run-tracking.js";import{runInit}from"../init.js";import{looksLikeStructuredLinkedTestEntry,normalizeStructuredLinkedTestEntry}from"./entry.js";import{LINKED_TEST_PM_CONTEXT_MODE_VALUES as PM_CONTEXT_MODE_VALUES,LINKED_TEST_PROTECTED_ENV_KEYS,parseLinkedTestAssertionEqualsMap,parseLinkedTestAssertionGteMap,parseLinkedTestBoolean as parseLinkedTestBooleanValue,parseLinkedTestContextMode as parseLinkedTestContextModeValue,parseLinkedTestEnvClear as parseLinkedTestEnvClearValue,parseLinkedTestEnvSet as parseLinkedTestEnvSetValue,parseLinkedTestJsonEntries,parseLinkedTestMinLines,parseLinkedTestRegexList,parseLinkedTestStringList,parseLinkedTestWorkspaceContextMode}from"./parsers.js";import{parseOnlyIndexValue,resolveLinkedTestRunSelection}from"../../core/test/run-selectors.js";import{SCOPE_VALUES}from"../../types/index.js";import{diffTestRunMeasurements,parseTestRunMeasurements,queryTestRunMeasurementsBelow}from"./measurements.js";import{withHostEnvironmentBoundary}from"../environment/host-environment-errors.js";import{SOURCE_CONTEXT_ACCESS_ENV}from"../environment/source-context.js";import{acknowledgeLinkedTests,attachLinkedTestMutationProvenance,resolveLinkedTestSourceRef,resolveLinkedTestSourceWorkspaceRoot,resolveLinkedTestTrust}from"./trust.js";const TEST_OUTPUT_MAX_BUFFER_BYTES=20*1024*1024;const DEFAULT_LINKED_TEST_TIMEOUT_FORCE_KILL_DELAY_MS=3e3;const DEFAULT_LINKED_TEST_HEARTBEAT_INTERVAL_MS=1e4;const DEFAULT_LINKED_TEST_PIPE_CLOSE_GRACE_MS=5e3;const MAX_LINKED_TEST_COMMAND_LABEL_LENGTH=120;const LINKED_TEST_TRACKER_DIRS_TO_SKIP=new Set(["locks","extensions","runtime"]);const LINKED_TEST_ITEM_COUNT_DIRS_TO_SKIP=new Set(["history","index","search","extensions","locks","runtime"]);const LINKED_TEST_INFRA_COLLISION_PATTERNS=[/eaddrinuse/i,/address already in use/i,/port\s+\d+\s+is already in use/i,/web server[^.\n]*already running/i,/failed to listen on/i];const LINKED_TEST_LOCK_ACQUISITION_PATTERN=/\b(?:could not|failed to|unable to)\s+acquire\b[^\n.]*\block\b/i;const LINKED_TEST_LOCK_CONTENTION_CONTEXT_PATTERN=/\b(?:already running|timed?\s*out|timeout|held by another|lock\s+is\s+held)\b/i;const PM_TRACKER_READ_SUBCOMMANDS=new Set(["activity","calendar","context","ctx","deps","get","health","history","list","list-all","list-blocked","list-canceled","list-closed","list-draft","list-in-progress","list-open","search","stats","test-all","validate"]);function readPositiveIntegerEnv(name,fallback){const raw=process.env[name];if(typeof raw!=="string"||raw.trim().length===0){return fallback}const parsed=Number.parseInt(raw,10);if(!Number.isFinite(parsed)||parsed<=0){return fallback}return parsed}function linkedTestTimeoutForceKillDelayMs(){return readPositiveIntegerEnv("PM_LINKED_TEST_TIMEOUT_FORCE_KILL_DELAY_MS",DEFAULT_LINKED_TEST_TIMEOUT_FORCE_KILL_DELAY_MS)}function linkedTestHeartbeatIntervalMs(){return readPositiveIntegerEnv("PM_LINKED_TEST_HEARTBEAT_INTERVAL_MS",DEFAULT_LINKED_TEST_HEARTBEAT_INTERVAL_MS)}function linkedTestPipeCloseGraceMs(){return readPositiveIntegerEnv("PM_LINKED_TEST_PIPE_CLOSE_GRACE_MS",DEFAULT_LINKED_TEST_PIPE_CLOSE_GRACE_MS)}function resolveTrackedRunId(kind){const fromEnv=process.env.PM_BACKGROUND_TEST_RUN_ID?.trim();if(fromEnv&&fromEnv.length>0){return fromEnv}return`${kind}-local-${Date.now().toString(36)}-${Math.random().toString(36).slice(2,8)}`}function summarizeRunResultStatuses(results){let passed=0;let failed=0;let skipped=0;for(const entry of results){if(entry.status==="passed"){passed+=1;continue}if(entry.status==="failed"){failed+=1;continue}skipped+=1}return{passed,failed,skipped}}function summarizeContextPreflight(runResults){let checkedPmCommands=0;let trackerReadCommands=0;let mismatches=0;let autoRemediated=0;for(const result of runResults){const context=result.execution_context;if(!context||context.is_pm_command!==true){continue}checkedPmCommands+=1;if(context.is_pm_tracker_read_command===true){trackerReadCommands+=1}if(context.mismatch_detected===true){mismatches+=1}if(context.auto_pm_context_applied===true){autoRemediated+=1}}return{checked_pm_commands:checkedPmCommands,tracker_read_commands:trackerReadCommands,mismatches,auto_remediated:autoRemediated}}function ensureScope(raw){const value=raw??"project";if(!SCOPE_VALUES.includes(value)){throw new PmCliError(`Invalid scope "${raw}"`,EXIT_CODE.USAGE)}return value}function parsePmContextMode(raw){if(!raw){return"schema"}const normalized=raw.trim().toLowerCase();if(PM_CONTEXT_MODE_VALUES.includes(normalized)){return normalized}throw new PmCliError(`Invalid --pm-context value "${raw}". Expected one of: ${PM_CONTEXT_MODE_VALUES.join(", ")}`,EXIT_CODE.USAGE)}function parseWorkspaceContextMode(raw){return parseLinkedTestWorkspaceContextMode(raw??"source","--workspace-context")}function resolveLinkedTestWorkspaceContextMode(linkedTest,runLevelMode,overrideLinkedMode){if(overrideLinkedMode){return runLevelMode}return parseLinkedTestWorkspaceContextMode(linkedTest.workspace_context_mode,"linked test")??runLevelMode}function resolveLinkedTestRequestedContextMode(linkedTest,runLevelMode,overrideLinkedPmContext){if(overrideLinkedPmContext){return runLevelMode}if(typeof linkedTest.pm_context_mode!=="string"||linkedTest.pm_context_mode.trim().length===0){return runLevelMode}return parsePmContextMode(linkedTest.pm_context_mode)}function resolveLinkedTestEffectiveContextMode(requestedMode,isPmTrackerReadCommand){if(requestedMode==="auto"){return isPmTrackerReadCommand?"tracker":"schema"}return requestedMode}function hasLinkedTestAssertions(linkedTest){return Object.entries(linkedTest).filter(([key])=>key.startsWith("assert_")).some(([,value])=>{if(value===void 0||value===null)return false;if(Array.isArray(value))return value.length>0;if(typeof value==="object")return Object.keys(value).length>0;if(typeof value==="string")return value.trim().length>0;return true})}function buildPmContextMismatchHint(params){const{executionContext,runLevelPmContextMode,linkedOverridePmContextMode}=params;if(!executionContext.is_pm_tracker_read_command||!executionContext.mismatch_detected){return""}if(runLevelPmContextMode==="tracker"&&linkedOverridePmContextMode==="schema"){return" Linked test metadata pm_context_mode=schema overrides run-level --pm-context tracker. Set pm_context_mode=tracker (or auto) on the linked test, or remove the override, to run against seeded tracker data."}if(executionContext.pm_context_mode==="schema"){return" Use --auto-pm-context to route PM tracker-read linked commands through seeded tracker data automatically. Alternatively, use --pm-context tracker for the whole run."}return""}function mergeEnvSetDirectives(entries,optionName){const merged={};if(!entries){return merged}for(const entry of entries){const parsed=parseLinkedTestEnvSetValue(entry,optionName);if(!parsed){continue}for(const[key,value]of Object.entries(parsed)){merged[key]=value}}return merged}function mergeEnvClearDirectives(entries,optionName){if(!entries){return[]}const values=[];for(const entry of entries){const parsed=parseLinkedTestEnvClearValue(entry,optionName);if(parsed){values.push(...parsed)}}return[...new Set(values)]}import{BUN_GLOBAL_FLAGS_WITH_VALUE,NPM_GLOBAL_FLAGS_WITH_VALUE,PNPM_GLOBAL_FLAGS_WITH_VALUE,SCRIPT_RUN_FLAGS_WITH_VALUE,SCRIPT_RUN_SUBCOMMANDS,YARN_GLOBAL_FLAGS_WITH_VALUE,firstPmSubcommand,extractPmInvocationArgsFromSegment,isPmCliPackageToken,isPmCliScriptToken,isPmExecutableToken,parseLauncherSubcommand,parseNpmExecCommand,parseNpxCommand,parsePnpmDlxCommand,resolvePmSubcommandContext,splitNormalizedCommandSegments,stripLeadingEnvAssignments}from"./linked-command-detection.js";import{extractReferencedPmItemIdsFromCommand}from"./linked-command-detection.js";function commandInvokesPmCli(command){const normalizedCommand=normalizeCommandForValidation(command);return splitNormalizedCommandSegments(normalizedCommand).some(segment=>extractPmInvocationArgsFromSegment(segment)!==null)}function commandInvokesPmTrackerReadCommand(command){const normalizedCommand=normalizeCommandForValidation(command);return splitNormalizedCommandSegments(normalizedCommand).some(segment=>{const invocationArgs=extractPmInvocationArgsFromSegment(segment);if(!invocationArgs){return false}const context=resolvePmSubcommandContext(invocationArgs);if(!context){return false}return PM_TRACKER_READ_SUBCOMMANDS.has(context.subcommand)})}function resolveDirectRunnerSubcommand(parsed){if(!parsed){return void 0}if(!SCRIPT_RUN_SUBCOMMANDS.has(parsed.subcommand)){return parsed.subcommand}return parseLauncherSubcommand(parsed.args,SCRIPT_RUN_FLAGS_WITH_VALUE)?.subcommand}function firstDirectTestRunnerSubcommand(executable,args){if(executable==="npx"||executable==="bunx"){return parseNpxCommand(args)?.command}if(executable==="pnpm"){return resolveDirectRunnerSubcommand(parseLauncherSubcommand(args,PNPM_GLOBAL_FLAGS_WITH_VALUE))}if(executable==="npm"){return resolveDirectRunnerSubcommand(parseLauncherSubcommand(args,NPM_GLOBAL_FLAGS_WITH_VALUE))}if(executable==="yarn"){return resolveDirectRunnerSubcommand(parseLauncherSubcommand(args,YARN_GLOBAL_FLAGS_WITH_VALUE))}if(executable==="bun"){return resolveDirectRunnerSubcommand(parseLauncherSubcommand(args,BUN_GLOBAL_FLAGS_WITH_VALUE))}return void 0}function isDirectTestRunnerSubcommand(token){if(!token){return false}return token==="vitest"||token==="test"||token.startsWith("test:")}function parsedLauncherInvokesRecursiveTestAll(parsed){if(!parsed){return false}if(!isPmExecutableToken(parsed.command)&&!isPmCliPackageToken(parsed.command)){return false}return firstPmSubcommand(parsed.args)==="test-all"}const SHELL_COMMAND_FLAG_PATTERNS=new Map([...["sh","bash","dash","zsh"].map(executable=>[executable,/^(?:-c|-[a-z]*c[a-z]*)$/u]),...["cmd","cmd.exe"].map(executable=>[executable,/^\/[ck]$/u]),...["powershell","powershell.exe","pwsh","pwsh.exe"].map(executable=>[executable,/^-(?:c|command)$/u])]);const PACKAGE_EXEC_EXECUTABLES=new Set(["npx","bunx"]);function segmentInvokesRecursiveTestAll(segment){const rawTokens=segment.split(" ").filter(token=>token.length>0);const tokens=stripLeadingEnvAssignments(rawTokens);if(tokens.length===0){return false}const[executable,...args]=tokens;const shellCommandFlagPattern=SHELL_COMMAND_FLAG_PATTERNS.get(executable);if(shellCommandFlagPattern){const commandFlagIndex=args.findIndex(arg=>shellCommandFlagPattern.test(arg));return commandFlagIndex>=0?segmentInvokesRecursiveTestAll(args.slice(commandFlagIndex+1).join(" ")):false}if(isPmExecutableToken(executable)||isPmCliScriptToken(executable)){return firstPmSubcommand(args)==="test-all"}if(executable==="node"&&args.length>0&&isPmCliScriptToken(args[0])){return firstPmSubcommand(args.slice(1))==="test-all"}if(PACKAGE_EXEC_EXECUTABLES.has(executable)){return parsedLauncherInvokesRecursiveTestAll(parseNpxCommand(args))}if(executable==="pnpm"){return parsedLauncherInvokesRecursiveTestAll(parsePnpmDlxCommand(args))}if(executable==="npm"){return parsedLauncherInvokesRecursiveTestAll(parseNpmExecCommand(args))}return false}function invokesRecursiveTestAllCommand(command){const normalized=normalizeCommandForValidation(command);return splitNormalizedCommandSegments(normalized).some(segment=>segmentInvokesRecursiveTestAll(segment))}function assertNoRecursiveTestAllCommand(command){if(!invokesRecursiveTestAllCommand(command))return;throw new PmCliError('Linked test commands must not invoke "pm test-all"; this creates recursive orchestration.',EXIT_CODE.USAGE)}function normalizeCommandForValidation(command){return command.trim().replaceAll("\\","/").replaceAll('"',"").replaceAll("'","").replaceAll(/\s+/g," ").toLowerCase()}function commandUsesSandboxRunner(normalizedCommand){return normalizedCommand.includes("node scripts/run-tests.mjs ")||normalizedCommand.endsWith("node scripts/run-tests.mjs")||normalizedCommand.includes("node ./scripts/run-tests.mjs ")||normalizedCommand.endsWith("node ./scripts/run-tests.mjs")}function segmentInvokesDirectTestRunner(normalizedSegment){const rawTokens=normalizedSegment.split(" ").filter(token=>token.length>0);const tokens=stripLeadingEnvAssignments(rawTokens);if(tokens.length===0){return false}const[executable,...args]=tokens;const shellCommandFlagPattern=SHELL_COMMAND_FLAG_PATTERNS.get(executable);if(shellCommandFlagPattern){const commandFlagIndex=args.findIndex(argument=>shellCommandFlagPattern.test(argument));return commandFlagIndex>=0&&segmentInvokesDirectTestRunner(args.slice(commandFlagIndex+1).join(" "))}if(executable==="vitest"||executable.endsWith("/vitest")||executable.endsWith("/vitest.mjs")){return true}if(executable==="node"){return nodeArgsInvokeDirectTestRunner(args)}return packageManagerInvokesDirectTestRunner(executable,args)}function nodeArgsInvokeDirectTestRunner(args){return args.includes("--test")||args.some(arg=>arg==="vitest"||arg==="vitest.mjs"||arg.endsWith("/vitest")||arg.endsWith("/vitest.mjs"))}function packageManagerInvokesDirectTestRunner(executable,args){if(executable==="npx"||executable==="bunx"){return isDirectTestRunnerSubcommand(parseNpxCommand(args)?.command)}if(executable==="pnpm"||executable==="npm"){return isDirectTestRunnerSubcommand((executable==="pnpm"?parsePnpmDlxCommand(args):parseNpmExecCommand(args))?.command)||isDirectTestRunnerSubcommand(firstDirectTestRunnerSubcommand(executable,args))}return(executable==="yarn"||executable==="bun")&&isDirectTestRunnerSubcommand(firstDirectTestRunnerSubcommand(executable,args))}function classifyLinkedTestCommandSafety(command){const normalized=normalizeCommandForValidation(command);const segments=splitNormalizedCommandSegments(normalized);const directRunner=segments.some(segment=>!commandUsesSandboxRunner(segment)&&segmentInvokesDirectTestRunner(segment));const sandboxRunner=segments.some(commandUsesSandboxRunner);return{accepted:true,command_kind:directRunner?"direct_runner":sandboxRunner?"sandbox_runner":"other",tracker_isolation:"runtime_injected",trust_boundary:"provenance",advisory:directRunner?"Accepted with runtime-injected tracker isolation. Prefer the repository sandbox runner when available for reproducible build and coverage setup.":"Accepted with runtime-injected tracker isolation and provenance checks before execution."}}function getRuntimeSafetySkipReason(command){if(!invokesRecursiveTestAllCommand(command))return void 0;return'Linked test command skipped: Linked test commands must not invoke "pm test-all"; this creates recursive orchestration.'}function parseAddEntries(raw){if(!raw)return[];return raw.map(parseAddEntry)}function parseAddEntry(entry){const trimmed=entry.trim();const kv=looksLikeStructuredLinkedTestEntry(trimmed)?normalizeStructuredLinkedTestEntry(parseCsvKv(entry,"--add"),"--add"):{command:trimmed};const command=trimLinkedTestEntryField(kv.command);if(!command){throw new PmCliError("--add requires command=<value> or a bare command (path=<value> is optional metadata)",EXIT_CODE.USAGE)}assertNoRecursiveTestAllCommand(command);return{command,path:trimLinkedTestEntryField(kv.path),scope:ensureScope(kv.scope),timeout_seconds:parseLinkedTestTimeoutSeconds(trimLinkedTestEntryField(kv.timeout_seconds),trimLinkedTestEntryField(kv.timeout)),pm_context_mode:parseLinkedTestContextModeValue(trimLinkedTestEntryField(kv.pm_context_mode),"--add"),workspace_context_mode:parseLinkedTestWorkspaceContextMode(trimLinkedTestEntryField(kv.workspace_context_mode),"--add"),env_set:parseLinkedTestEnvSetValue(trimLinkedTestEntryField(kv.env_set),"--add"),env_clear:parseLinkedTestEnvClearValue(trimLinkedTestEntryField(kv.env_clear),"--add"),shared_host_safe:parseLinkedTestBooleanValue(trimLinkedTestEntryField(kv.shared_host_safe),"--add","shared_host_safe"),assert_stdout_contains:parseLinkedTestStringList(trimLinkedTestEntryField(kv.assert_stdout_contains)),assert_stdout_regex:parseLinkedTestRegexList(trimLinkedTestEntryField(kv.assert_stdout_regex),"--add","assert_stdout_regex"),assert_stderr_contains:parseLinkedTestStringList(trimLinkedTestEntryField(kv.assert_stderr_contains)),assert_stderr_regex:parseLinkedTestRegexList(trimLinkedTestEntryField(kv.assert_stderr_regex),"--add","assert_stderr_regex"),assert_stdout_min_lines:parseLinkedTestMinLines(trimLinkedTestEntryField(kv.assert_stdout_min_lines),"--add"),assert_json_field_equals:parseLinkedTestAssertionEqualsMap(trimLinkedTestEntryField(kv.assert_json_field_equals),"--add"),assert_json_field_gte:parseLinkedTestAssertionGteMap(trimLinkedTestEntryField(kv.assert_json_field_gte),"--add"),note:trimLinkedTestEntryField(kv.note)}}function trimLinkedTestEntryField(value){const trimmed=value?.trim();return trimmed&&trimmed.length>0?trimmed:void 0}function parseLinkedTestTimeoutSeconds(timeoutSecondsRaw,timeoutAliasRaw){if(timeoutSecondsRaw&&timeoutAliasRaw&&timeoutSecondsRaw!==timeoutAliasRaw){throw new PmCliError("--add timeout and timeout_seconds must match when both are provided",EXIT_CODE.USAGE)}const timeoutRaw=timeoutSecondsRaw??timeoutAliasRaw;if(timeoutRaw===void 0)return void 0;const timeout=Math.floor(parseOptionalNumber(timeoutRaw,"timeout_seconds"));if(timeout<=0){throw new PmCliError("--add timeout_seconds must be a positive integer",EXIT_CODE.USAGE)}return timeout}function parseAddJsonEntries(raw){if(!raw)return[];return raw.flatMap(entry=>{const parsed=parseLinkedTestJsonEntries(entry,"--add-json");for(const linkedTest of parsed){const command=linkedTest.command;if(!command){throw new PmCliError("--add-json requires a non-empty command string",EXIT_CODE.USAGE)}assertNoRecursiveTestAllCommand(command)}return parsed})}function parseRemoveEntries(raw){if(!raw)return[];return raw.map(entry=>{const trimmed=entry.trim();if(!trimmed){throw new PmCliError("--remove requires command or path value",EXIT_CODE.USAGE)}const identityPrefix=/^(command|path)\s*=/i.exec(trimmed);if(identityPrefix){const value=trimmed.slice(identityPrefix[0].length).trim();if(!value){throw new PmCliError("--remove requires a non-empty command=<value> or path=<value>",EXIT_CODE.USAGE)}return{kind:identityPrefix[1].toLowerCase(),value}}if(/^[A-Za-z_][\w-]*=\S+$/u.test(trimmed)){throw new PmCliError("--remove structured selectors require command=<value> or path=<value>",EXIT_CODE.USAGE)}if(/^(?:[-*+]\s+)?(?:path|command)\s*:/i.test(trimmed)||trimmed.startsWith("```")){const kv=parseCsvKv(trimmed,"--remove");const kind=kv.path!==void 0?"path":"command";const value=kv.path??kv.command;if(!value?.trim()){throw new PmCliError("--remove requires command=<value> and/or path=<value>",EXIT_CODE.USAGE)}return{kind,value:value.trim()}}return{kind:"either",value:trimmed}})}function parseRemoveIndexes(raw){if(!raw)return[];return[...new Set(raw.map(value=>{const parsed=typeof value==="number"?value:Number.parseInt(value,10);if(!Number.isInteger(parsed)||parsed<1||String(parsed)!==String(value).trim()){throw new PmCliError("--remove-index must be a positive integer",EXIT_CODE.USAGE)}return parsed}))]}function closeLinkedTestStdin(child){try{child.stdin?.end()}catch{}}function summarizeLinkedTestCommand(command){const normalized=command.trim().replaceAll(/\s+/g," ");if(normalized.length<=MAX_LINKED_TEST_COMMAND_LABEL_LENGTH){return normalized}return`${normalized.slice(0,MAX_LINKED_TEST_COMMAND_LABEL_LENGTH-3)}...`}function shouldEmitLinkedTestProgress(mode){if(mode==="off"){return false}if(mode==="always"){return true}return process.stderr.isTTY===true}function emitLinkedTestProgress(message){try{process.stderr.write(`${message}
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
+ [pm linked-test output truncated at ${TEST_OUTPUT_MAX_BUFFER_BYTES} bytes per stream; the child process continued to completion]
4
+ `:"";const executionResult={stdout:`${stdout.text}${truncationNotice}`,stderr:stderr.text,exitCode:code,signal,timedOut,maxBufferExceeded:false,outputTruncated,spawnError};endLinkedTestProgress(progressContext,executionResult,startedAt,progressMode);return executionResult}finally{await rm(captureRoot,{recursive:true,force:true})}}function formatLinkedTestExecutionError(result,timeoutMs){const details=[];if(result.maxBufferExceeded){details.push(`Linked test output exceeded maxBuffer=${TEST_OUTPUT_MAX_BUFFER_BYTES} bytes. Reduce output volume or split the command.`)}if(result.timedOut&&timeoutMs>0){details.push(`Linked test timed out after ${timeoutMs}ms.`)}const signalMessage=result.signal?`Linked test command terminated by signal ${result.signal}.`:void 0;const baseMessage=result.spawnError?.trim()||signalMessage||"Linked test command failed.";if(details.length===0){return baseMessage}return`${baseMessage} ${details.join(" ")}`}function hasInfraCollisionSignal(result){const combined=[result.spawnError??"",result.stderr,result.stdout].join("\n");return LINKED_TEST_INFRA_COLLISION_PATTERNS.some(pattern=>pattern.test(combined))||combined.split(/\r?\n/u).some(line=>LINKED_TEST_LOCK_ACQUISITION_PATTERN.test(line)&&LINKED_TEST_LOCK_CONTENTION_CONTEXT_PATTERN.test(line))}function classifyLinkedTestFailure(result){if(hasInfraCollisionSignal(result)){return"infra_collision"}if(result.timedOut){return"timeout"}if(result.maxBufferExceeded){return"max_buffer"}if(result.spawnError){return"spawn_error"}if(result.signal){return"signal"}return"assertion_failure"}function createEmptyFailureCategoryCounts(){return{infra_collision:0,trust_refusal:0,assertion_failure:0,empty_run:0,timeout:0,max_buffer:0,spawn_error:0,signal:0}}function countFailureCategories(runResults){const counts=createEmptyFailureCategoryCounts();for(const result of runResults){if(result.status!=="failed"||!result.failure_category){continue}counts[result.failure_category]+=1}return counts}function applyEnvDirectiveStage(env,directives){for(const[key,value]of Object.entries(directives.env_set)){if(LINKED_TEST_PROTECTED_ENV_KEYS.has(key.toUpperCase())){continue}env[key]=value}for(const key of directives.env_clear){if(LINKED_TEST_PROTECTED_ENV_KEYS.has(key.toUpperCase())){continue}delete env[key]}}function applySharedHostSafeDefaults(env){if(env.PORT===void 0){env.PORT="0"}if(env.HOST===void 0){env.HOST="127.0.0.1"}if(env.PM_SHARED_HOST_SAFE===void 0){env.PM_SHARED_HOST_SAFE="1"}if(env.PLAYWRIGHT_HTML_OPEN===void 0){env.PLAYWRIGHT_HTML_OPEN="never"}if(env.PW_TEST_HTML_REPORT_OPEN===void 0){env.PW_TEST_HTML_REPORT_OPEN="never"}}function resolveEffectiveLinkedTestDirectives(runtimeDirectives,linkedTest){const envSet={...runtimeDirectives.env_set,...linkedTest.env_set};const envClear=[...new Set([...runtimeDirectives.env_clear,...linkedTest.env_clear??[]])];const sharedHostSafe=linkedTest.shared_host_safe??runtimeDirectives.shared_host_safe;return{env_set:envSet,env_clear:envClear,shared_host_safe:sharedHostSafe}}function resolveRuntimeDirectives(envSetEntries,envClearEntries,sharedHostSafe){return{env_set:mergeEnvSetDirectives(envSetEntries,"--env-set"),env_clear:mergeEnvClearDirectives(envClearEntries,"--env-clear"),shared_host_safe:sharedHostSafe===true}}async function copyIntoSandboxIfPresent(sourcePath,targetPath,recursive=false,makeDirectory=mkdir){if(!await pathExists(sourcePath)){return}try{await withHostEnvironmentBoundary("seed_linked_test",async()=>{await makeDirectory(path.dirname(targetPath),{recursive:true});if(recursive){await cp(sourcePath,targetPath,{recursive:true,force:true});return}await cp(sourcePath,targetPath,{force:true})},{why:"Linked tests need an isolated PM context without mutating the source tracker.",nextSteps:["Reclaim capacity on the filesystem that hosts the linked-test temporary directory."]})}catch(error){if(typeof error==="object"&&error!==null&&"code"in error&&isFileMissingError(error)){return}throw error}}async function seedLinkedTestSandbox(sandboxPmPath,sandboxGlobalPath,sourceRoots){await copyIntoSandboxIfPresent(getSettingsPath(sourceRoots.projectPmRoot),getSettingsPath(sandboxPmPath));await copyIntoSandboxIfPresent(path.join(sourceRoots.projectPmRoot,"extensions"),path.join(sandboxPmPath,"extensions"),true);await copyIntoSandboxIfPresent(getSettingsPath(sourceRoots.globalPmRoot),getSettingsPath(sandboxGlobalPath));await copyIntoSandboxIfPresent(path.join(sourceRoots.globalPmRoot,"extensions"),path.join(sandboxGlobalPath,"extensions"),true)}async function seedLinkedTestTrackerData(sourceRoot,sandboxRoot){if(!await pathExists(sourceRoot)){return}const entries=await readdir(sourceRoot,{withFileTypes:true});for(const entry of entries){if(LINKED_TEST_TRACKER_DIRS_TO_SKIP.has(entry.name)){continue}const sourcePath=path.join(sourceRoot,entry.name);const targetPath=path.join(sandboxRoot,entry.name);if(entry.isDirectory()){await copyIntoSandboxIfPresent(sourcePath,targetPath,true);continue}if(entry.isFile()){await copyIntoSandboxIfPresent(sourcePath,targetPath)}}}async function seedMissingLinkedTestSettingsHistory(sourceRoot,sandboxRoot){const sourceSettingsRaw=await readFileIfExists(getSettingsPath(sourceRoot));if(sourceSettingsRaw===null||await pathExists(getWorkspaceHistoryPath(sourceRoot))){return}const sandboxSettings=await readSettings(sandboxRoot);await rm(getWorkspaceHistoryPath(sandboxRoot),{force:true});await rm(getSettingsPath(sandboxRoot),{force:true});await writeWorkspaceJsonWithHistory({pmRoot:sandboxRoot,filePath:getSettingsPath(sandboxRoot),raw:sourceSettingsRaw,op:"settings:write",author:resolveAuthor(void 0,sandboxSettings.author_default),lockTtlSeconds:sandboxSettings.locks.ttl_seconds,lockWaitMs:sandboxSettings.locks.wait_ms,recordCreation:true,message:"Seed linked-test tracker settings history"})}async function countLinkedTestItemFiles(pmRoot){if(!await pathExists(pmRoot)){return 0}let total=0;const entries=await readdir(pmRoot,{withFileTypes:true});for(const entry of entries){if(!entry.isDirectory()||LINKED_TEST_ITEM_COUNT_DIRS_TO_SKIP.has(entry.name)){continue}const folderPath=path.join(pmRoot,entry.name);let files;try{files=await readdir(folderPath,{withFileTypes:true})}catch{continue}for(const file of files){if(!file.isFile()){continue}if(ITEM_FILE_EXTENSIONS.some(extension=>file.name.toLowerCase().endsWith(extension))){total+=1}}}return total}function resolveLinkedTestFailureExitCode(execution){const rawExitCode=typeof execution.exitCode==="number"?execution.exitCode:1;if((execution.timedOut||execution.maxBufferExceeded)&&rawExitCode===0){return 1}return rawExitCode}function splitJsonPathSegments(fieldPath){const segments=[];const tokens=fieldPath.match(/[^.[\]]+|\[\d+\]/g)??[];for(const token of tokens){if(token.startsWith("[")&&token.endsWith("]")){const parsedIndex=Number.parseInt(token.slice(1,-1),10);if(!Number.isInteger(parsedIndex)||parsedIndex<0){return[]}segments.push(parsedIndex);continue}segments.push(token)}return segments}function readJsonPathValue(root,fieldPath){const normalizedPath=fieldPath.trim();if(normalizedPath.length===0){return{found:false,value:void 0}}const segments=splitJsonPathSegments(normalizedPath);if(segments.length===0){return{found:false,value:void 0}}let current=root;for(const segment of segments){if(typeof segment==="number"){if(!Array.isArray(current)||segment>=current.length){return{found:false,value:void 0}}current=current[segment];continue}if(typeof current!=="object"||current===null||!Object.prototype.hasOwnProperty.call(current,segment)){return{found:false,value:void 0}}current=current[segment]}return{found:true,value:current}}function parseAssertionLiteral(raw){const trimmed=raw.trim();if(trimmed==="true"){return true}if(trimmed==="false"){return false}if(trimmed==="null"){return null}const numeric=Number(trimmed);if(trimmed.length>0&&Number.isFinite(numeric)){return numeric}if(trimmed.startsWith("{")&&trimmed.endsWith("}")||trimmed.startsWith("[")&&trimmed.endsWith("]")){try{return JSON.parse(trimmed)}catch{}}return trimmed}function compareAssertionValues(actual,expected){if(typeof actual==="object"&&actual!==null&&typeof expected==="object"&&expected!==null){return stableValueEquals(actual,expected)}return Object.is(actual,expected)}function evaluateLinkedTestAssertions(linkedTest,stdout,stderr){const failures=[];failures.push(...evaluateContainsAssertions("stdout",stdout,linkedTest.assert_stdout_contains));failures.push(...evaluateRegexAssertions("stdout",stdout,linkedTest.assert_stdout_regex));failures.push(...evaluateContainsAssertions("stderr",stderr,linkedTest.assert_stderr_contains));failures.push(...evaluateRegexAssertions("stderr",stderr,linkedTest.assert_stderr_regex));failures.push(...evaluateStdoutLineCountAssertion(stdout,linkedTest.assert_stdout_min_lines));const jsonEqualsAssertions=linkedTest.assert_json_field_equals??{};const jsonGteAssertions=linkedTest.assert_json_field_gte??{};const needsJsonAssertions=Object.keys(jsonEqualsAssertions).length>0||Object.keys(jsonGteAssertions).length>0;if(!needsJsonAssertions){return failures}let parsedJson;try{parsedJson=JSON.parse(stdout)}catch(error){failures.push(`stdout is not valid JSON for assert_json_field_* checks: ${error instanceof Error?error.message:String(error)}`);return failures}for(const[fieldPath,expectedRaw]of Object.entries(jsonEqualsAssertions)){const resolved=readJsonPathValue(parsedJson,fieldPath);if(!resolved.found){failures.push(`assert_json_field_equals missing path "${fieldPath}"`);continue}const expected=parseAssertionLiteral(expectedRaw);if(!compareAssertionValues(resolved.value,expected)){failures.push(`assert_json_field_equals mismatch at "${fieldPath}" (expected=${JSON.stringify(expected)} actual=${JSON.stringify(resolved.value)})`)}}for(const[fieldPath,expectedMinimum]of Object.entries(jsonGteAssertions)){const resolved=readJsonPathValue(parsedJson,fieldPath);if(!resolved.found){failures.push(`assert_json_field_gte missing path "${fieldPath}"`);continue}if(typeof resolved.value!=="number"||!Number.isFinite(resolved.value)){failures.push(`assert_json_field_gte path "${fieldPath}" resolved to non-numeric value`);continue}if(resolved.value<expectedMinimum){failures.push(`assert_json_field_gte failed at "${fieldPath}" (expected >= ${expectedMinimum}, actual ${resolved.value})`)}}return failures}function evaluateContainsAssertions(streamName,output,expectedValues){const failures=[];for(const expected of expectedValues??[]){if(!output.includes(expected)){failures.push(`${streamName} missing required text: "${expected}"`)}}return failures}function evaluateRegexAssertions(streamName,output,patterns){const failures=[];for(const pattern of patterns??[]){try{const regex=new RegExp(pattern,"m");if(!regex.test(output)){failures.push(`${streamName} failed regex assertion: /${pattern}/m`)}}catch(error){failures.push(`${streamName} regex assertion is invalid: /${pattern}/ (${error instanceof Error?error.message:String(error)})`)}}return failures}function evaluateStdoutLineCountAssertion(stdout,minimum){if(typeof minimum!=="number"){return[]}const lineCount=stdout.split(/\r?\n/).map(line=>line.trim()).filter(line=>line.length>0).length;return lineCount<minimum?[`stdout line count ${lineCount} is below required minimum ${minimum}`]:[]}const EMPTY_LINKED_TEST_RUN_PATTERNS=[{code:"no_projects_matched_filters",regex:/\bNo projects matched the filters\b/i},{code:"no_test_files_found",regex:/\bNo test files found\b/i},{code:"no_tests_found",regex:/\bNo tests found\b/i},{code:"no_matching_tests",regex:/\bNo matching tests?\b/i},{code:"collected_zero_items",regex:/\bcollected 0 items?\b/i},{code:"reported_zero_passes",regex:/(?:^\s*(?:#|ℹ)?\s*tests\s+0\s*$[\s\S]*^\s*(?:#|ℹ)?\s*pass\s+0\s*$|^\s*(?:#|ℹ)?\s*pass\s+0\s*$[\s\S]*^\s*(?:#|ℹ)?\s*tests\s+0\s*$)/imu},{code:"reported_zero_tests",regex:/^\s*(?:#|ℹ)?\s*tests\s+0\s*$/imu}];const POSITIVE_LINKED_TEST_RUN_PATTERNS=[/^\s*(?:#|ℹ)?\s*tests\s+[1-9]\d*\s*$/imu,/^\s*(?:#|ℹ)?\s*pass\s+[1-9]\d*\s*$/imu,/\bTests?\s+[1-9]\d*\s+passed\b/iu,/\b[1-9]\d*\s+passed\b/iu,/\bTests:\s+(?:.*\b)?[1-9]\d*\s+passed\b/iu];function detectEmptyLinkedTestRun(stdout,stderr){const combined=`${stdout}
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"&&params.linkedTest.pm_context_mode.trim().length>0?parsePmContextMode(params.linkedTest.pm_context_mode):void 0;const command=typeof params.linkedTest.command==="string"&&params.linkedTest.command.length>0?params.linkedTest.command:void 0;const isPmCommand=command?commandInvokesPmCli(command):false;const isPmTrackerReadCommand=isPmCommand&&command?commandInvokesPmTrackerReadCommand(command):false;const autoPmContextApplied=params.options?.autoPmContext===true&&isPmTrackerReadCommand;const requestedPmContextMode=autoPmContextApplied?"auto":resolveLinkedTestRequestedContextMode(params.linkedTest,params.runLevelPmContextMode,params.options?.overrideLinkedPmContext===true);const effectivePmContextMode=resolveLinkedTestEffectiveContextMode(requestedPmContextMode,isPmTrackerReadCommand);const selectedSandboxProjectPmPath=effectivePmContextMode==="tracker"?params.layout.trackerProjectPmPath:params.layout.schemaProjectPmPath;if(params.workspaceContextMode==="snapshot"){await bindSnapshotTracker(params.layout.workspaceSnapshotRoot,selectedSandboxProjectPmPath)}const trust=params.options?.pmRoot?await resolveLinkedTestTrust(params.options.pmRoot,params.linkedTest,params.currentSourceRef):await resolveLinkedTestTrust(params.layout.root,params.linkedTest,params.currentSourceRef);return{linkedOverridePmContextMode,executionContext:buildLinkedTestExecutionContext({layout:params.layout,counts:params.counts,sourceRoots:params.sourceRoots,isPmCommand,isPmTrackerReadCommand,requestedPmContextMode,effectivePmContextMode,autoPmContextApplied,requestedWorkspaceContextMode:params.requestedWorkspaceContextMode,workspaceContextMode:params.workspaceContextMode,sourceWorkspaceRoot:params.sourceWorkspaceRoot,trust})}}function buildLinkedTestSkippedResult(linkedTest,executionContext,error){return{command:linkedTest.command,path:linkedTest.path,status:"skipped",execution_context:executionContext,error}}function buildLinkedTestAssertionFailureResult(linkedTest,executionContext,error,output){return{command:linkedTest.command,path:linkedTest.path,status:"failed",exit_code:1,failure_category:"assertion_failure",execution_context:executionContext,...output?{stdout:output.stdout,stderr:output.stderr}:{},error}}function buildLinkedTestTrustRefusalResult(linkedTest,executionContext,error){return{command:linkedTest.command,path:linkedTest.path,status:"failed",exit_code:1,failure_category:"trust_refusal",execution_context:executionContext,error}}function resolveLinkedTestTrustPreflightResult(params){if(params.executionContext.trust.trusted)return null;const overrideRequested=params.options?.allowUntrustedLinkedTests===true;const policyEnabled=params.options?.untrustedLinkedTestsPolicyEnabled===true;if(overrideRequested&&policyEnabled)return null;const recovery=policyEnabled?"Retry with --allow-untrusted-linked-tests for this invocation, or acknowledge the current commands with --acknowledge-linked-tests.":"Set testing.allow_untrusted_linked_tests=true, then retry with --allow-untrusted-linked-tests; alternatively acknowledge the current commands with --acknowledge-linked-tests.";return buildLinkedTestTrustRefusalResult(params.linkedTest,params.executionContext,`Linked test command is not trusted by this clone (source_ref=${params.executionContext.trust.source_ref??"unknown"}, current_ref=${params.executionContext.trust.current_source_ref??"unknown"}). ${recovery}`)}function resolveLinkedTestSafetyPreflightResult(params){if(!params.linkedTest.command){return buildLinkedTestSkippedResult(params.linkedTest,params.executionContext,"No command configured for this linked test.")}const runtimeSafetySkipReason=getRuntimeSafetySkipReason(params.linkedTest.command);return runtimeSafetySkipReason?buildLinkedTestSkippedResult(params.linkedTest,params.executionContext,runtimeSafetySkipReason):null}function resolveLinkedTestPreflightResult(params){const safetyFailure=resolveLinkedTestSafetyPreflightResult(params);if(safetyFailure)return safetyFailure;const trustFailure=resolveLinkedTestTrustPreflightResult(params);if(trustFailure)return trustFailure;const failOnMismatchByDefault=params.executionContext.pm_context_mode==="schema"&&params.executionContext.is_pm_tracker_read_command&&params.executionContext.mismatch_detected;const failOnMismatchByFlag=params.options?.failOnContextMismatch===true&&params.executionContext.is_pm_command&&params.executionContext.mismatch_detected;if(failOnMismatchByDefault||failOnMismatchByFlag){const mismatchHint=buildPmContextMismatchHint({executionContext:params.executionContext,runLevelPmContextMode:params.runLevelPmContextMode,linkedOverridePmContextMode:params.linkedOverridePmContextMode});const mismatchPrefix=params.options?.checkContext===true?"Linked test preflight PM context mismatch detected":"Linked test PM context mismatch detected";return buildLinkedTestAssertionFailureResult(params.linkedTest,params.executionContext,`${mismatchPrefix} (source_project_items=${params.executionContext.source_project_item_count}, sandbox_project_items=${params.executionContext.sandbox_project_item_count}).${mismatchHint}`)}if(params.options?.requireAssertionsForPm===true&&params.executionContext.is_pm_command&&!hasLinkedTestAssertions(params.linkedTest)){return buildLinkedTestAssertionFailureResult(params.linkedTest,params.executionContext,"Linked PM command requires assertions when --require-assertions-for-pm is enabled.")}return null}function buildLinkedTestExecutionEnv(params){const effectiveDirectives=resolveEffectiveLinkedTestDirectives(params.runtimeDirectives,params.linkedTest);const executionEnv={...process.env,SENTRY_ENVIRONMENT:"test"};applyEnvDirectiveStage(executionEnv,params.runtimeDirectives);applyEnvDirectiveStage(executionEnv,{env_set:params.linkedTest.env_set??{},env_clear:params.linkedTest.env_clear??[]});if(effectiveDirectives.shared_host_safe){applySharedHostSafeDefaults(executionEnv)}executionEnv.FORCE_COLOR="0";executionEnv.PM_PATH=params.executionContext.sandbox_project_pm_path;executionEnv.PM_GLOBAL_PATH=params.executionContext.sandbox_global_pm_path;if(params.executionContext.workspace_context_mode==="isolated"){delete executionEnv.PM_SOURCE_WORKSPACE_ROOT;delete executionEnv.PM_SOURCE_PM_PATH;delete executionEnv[SOURCE_CONTEXT_ACCESS_ENV]}else{executionEnv.PM_SOURCE_WORKSPACE_ROOT=params.executionContext.source_workspace_root;executionEnv.PM_SOURCE_PM_PATH=params.executionContext.workspace_context_mode==="snapshot"?params.executionContext.sandbox_project_pm_path:params.executionContext.source_project_pm_path;executionEnv[SOURCE_CONTEXT_ACCESS_ENV]="read_only"}return executionEnv}function buildLinkedTestPassedResult(linkedTest,executionContext,execution){return{command:linkedTest.command,path:linkedTest.path,status:"passed",exit_code:0,execution_context:executionContext,stdout:execution.stdout,stderr:execution.stderr}}function buildLinkedTestEmptyRunResult(params){return{command:params.linkedTest.command,path:params.linkedTest.path,status:"failed",exit_code:1,failure_category:"empty_run",execution_context:params.executionContext,stdout:params.execution.stdout,stderr:params.execution.stderr,error:`Linked test reported an empty test run (${params.emptyRunSignal.code}). Update the test selection so at least one test executes; unfiltered commands may opt out by omitting --fail-on-empty-test-run.`}}function buildLinkedTestCommandFailureResult(params){return{command:params.linkedTest.command,path:params.linkedTest.path,status:"failed",exit_code:resolveLinkedTestFailureExitCode(params.execution),failure_category:classifyLinkedTestFailure(params.execution),execution_context:params.executionContext,stdout:params.execution.stdout,stderr:params.execution.stderr,error:formatLinkedTestExecutionError(params.execution,params.timeoutMs)}}function buildLinkedTestPassedExecutionResult(params){if(params.options?.failOnEmptyTestRun===true||commandUsesTestNameFilter(params.linkedTest.command??"")){const emptyRunSignal=detectEmptyLinkedTestRun(params.execution.stdout,params.execution.stderr);if(emptyRunSignal){return buildLinkedTestEmptyRunResult({...params,emptyRunSignal})}if(commandUsesTestNameFilter(params.linkedTest.command??"")&&!hasPositiveLinkedTestRunReceipt(params.execution.stdout,params.execution.stderr)){return buildLinkedTestEmptyRunResult({...params,emptyRunSignal:{code:"missing_positive_execution_receipt"}})}}const assertionFailures=evaluateLinkedTestAssertions(params.linkedTest,params.execution.stdout,params.execution.stderr);if(assertionFailures.length>0){return buildLinkedTestAssertionFailureResult(params.linkedTest,params.executionContext,`Linked test assertion(s) failed: ${assertionFailures.join("; ")}`,params.execution)}return buildLinkedTestPassedResult(params.linkedTest,params.executionContext,params.execution)}async function runSingleLinkedTest(params){const command=params.linkedTest.command??"";const timeoutMs=(params.linkedTest.timeout_seconds??params.defaultTimeoutSeconds??120)*1e3;const execution=await runLinkedTestCommand(command,timeoutMs,buildLinkedTestExecutionEnv(params),{index:params.index+1,total:params.total,timeoutMs,command},params.progressMode,params.executionContext.working_directory);const passed=execution.exitCode===0&&!execution.timedOut&&!execution.maxBufferExceeded;return passed?buildLinkedTestPassedExecutionResult({...params,execution}):buildLinkedTestCommandFailureResult({...params,execution,timeoutMs})}async function runLinkedTests(tests,defaultTimeoutSeconds,options){const results=[];const sandboxRoot=await mkdtemp(path.join(tmpdir(),"pm-linked-test-"));const layout=createLinkedTestSandboxLayout(sandboxRoot);const runLevelPmContextMode=parsePmContextMode(options?.pmContext);const runLevelWorkspaceContextMode=parseWorkspaceContextMode(options?.workspaceContext);const progressMode=options?.progress===true?"always":"auto";const runtimeDirectives=resolveRuntimeDirectives(options?.envSet,options?.envClear,options?.sharedHostSafe);const sourceRoots=options?.sourceRoots;const sourceWorkspaceRoot=resolveLinkedTestSourceWorkspaceRoot();const includeTrackerData=linkedTestsRequireTrackerData(tests,runLevelPmContextMode,options);const includeWorkspaceSnapshot=linkedTestsRequireWorkspaceSnapshot(tests,runLevelWorkspaceContextMode,options?.overrideLinkedWorkspaceContext===true);const currentSourceRef=await resolveLinkedTestSourceRef(sourceWorkspaceRoot);try{await initializeLinkedTestSandboxes(layout,runInit,includeTrackerData);await mkdir(layout.workspaceIsolatedRoot,{recursive:true});await seedLinkedTestSandboxesFromSource(layout,sourceRoots,includeTrackerData);if(includeWorkspaceSnapshot){await seedLinkedTestWorkspaceSnapshot(layout,sourceWorkspaceRoot)}const counts=await countLinkedTestSandboxItems(layout,sourceRoots);for(let index=0;index<tests.length;index+=1){const linkedTest=tests[index];const workspaceContextMode=resolveLinkedTestWorkspaceContextMode(linkedTest,runLevelWorkspaceContextMode,options?.overrideLinkedWorkspaceContext===true);const commandContext=await resolveLinkedTestCommandContext({linkedTest,layout,counts,sourceRoots,runLevelPmContextMode,options,requestedWorkspaceContextMode:runLevelWorkspaceContextMode,workspaceContextMode,currentSourceRef,sourceWorkspaceRoot});const preflightResult=resolveLinkedTestPreflightResult({linkedTest,executionContext:commandContext.executionContext,runLevelPmContextMode,linkedOverridePmContextMode:commandContext.linkedOverridePmContextMode,options});if(preflightResult){results.push(preflightResult);continue}results.push(await runSingleLinkedTest({linkedTest,index,total:tests.length,defaultTimeoutSeconds,runtimeDirectives,progressMode,executionContext:commandContext.executionContext,options}))}}finally{await rm(layout.root,{recursive:true,force:true,maxRetries:3,retryDelay:100})}return results}async function readLinkedTestItem(params){const{id,pmRoot,settings,typeToFolder}=params;const located=await locateItem(pmRoot,id,settings.id_prefix,settings.item_format,typeToFolder);if(!located){throw new PmCliError(`Item ${id} not found`,EXIT_CODE.NOT_FOUND)}const loaded=await readLocatedItem(located,{schema:settings.schema});return{itemId:located.id,tests:loaded.document.metadata.tests??[],testRuns:loaded.document.metadata.test_runs??[],changed:false,removed:0}}function linkedTestsHaveSameIdentity(left,right){return left.command===right.command&&left.path===right.path&&left.scope===right.scope&&left.pm_context_mode===right.pm_context_mode&&left.workspace_context_mode===right.workspace_context_mode}function appendMissingLinkedTests(current,additions){const next=[...current];for(const addition of additions){if(!next.some(entry=>linkedTestsHaveSameIdentity(entry,addition))){next.push(addition)}}return next}function removeLinkedTestsBySelector(current,removals,removeIndexes){if(removals.length===0&&removeIndexes.length===0){return{tests:current,removed:0}}const indexSet=new Set(removeIndexes);const tests=current.filter((entry,index)=>{if(indexSet.has(index+1))return false;return!removals.some(selector=>{if(selector.kind==="command")return entry.command===selector.value;if(selector.kind==="path")return entry.path===selector.value;return entry.command===selector.value||entry.path===selector.value})});return{tests,removed:current.length-tests.length}}function applyLinkedTestMutations(previous,adds,removes,removeIndexes){return removeLinkedTestsBySelector(appendMissingLinkedTests(previous,adds),removes,removeIndexes)}function hasTestRuntimeDirectiveFlags(options){return[(options.envSet?.length??0)>0,(options.envClear?.length??0)>0,options.sharedHostSafe===true,options.pmContext!==void 0,options.overrideLinkedPmContext===true,options.failOnContextMismatch===true,options.failOnSkipped===true,options.failOnEmptyTestRun===true,options.requireAssertionsForPm===true,options.checkContext===true,options.autoPmContext===true,options.workspaceContext!==void 0,options.overrideLinkedWorkspaceContext===true,options.allowUntrustedLinkedTests===true].some(Boolean)}function assertTestRunFlagUsage(options){if(hasTestRuntimeDirectiveFlags(options)&&options.run!==true){throw new PmCliError("--env-set, --env-clear, --shared-host-safe, --pm-context, --override-linked-pm-context, --workspace-context, --override-linked-workspace-context, --allow-untrusted-linked-tests, --fail-on-context-mismatch, --fail-on-skipped, --fail-on-empty-test-run, --require-assertions-for-pm, --check-context, and --auto-pm-context require --run",EXIT_CODE.USAGE)}if(options.acknowledgeLinkedTests===true&&options.run===true){throw new PmCliError("--acknowledge-linked-tests records trust without executing commands; run it separately before --run",EXIT_CODE.USAGE)}const hasSelectorFlags=options.match!==void 0||options.onlyIndex!==void 0||options.onlyLast===true;if(hasSelectorFlags&&options.run!==true){throw new PmCliError("--match, --only-index, and --only-last require --run",EXIT_CODE.USAGE)}}async function resolveLinkedTestItem(params){const{id,options,pmRoot,settings,typeToFolder,adds,removes,removeIndexes}=params;if(adds.length===0&&removes.length===0&&removeIndexes.length===0){return readLinkedTestItem({id,pmRoot,settings,typeToFolder})}let removed=0;const result=await mutateItem({pmRoot,settings,id,op:adds.length>0?"tests_add":"tests_remove",author:resolveAuthor(options.author,settings.author_default),message:options.message,force:options.force,skipNoop:true,mutate(document){const previous=document.metadata.tests??[];const mutation=applyLinkedTestMutations(previous,adds,removes,removeIndexes);removed=mutation.removed;if((removes.length>0||removeIndexes.length>0)&&removed===0){throw new PmCliError("No linked tests matched the requested removal selector",EXIT_CODE.NOT_FOUND,{code:"linked_test_remove_no_match",unmatched:[...removes.map(entry=>`${entry.kind}=${entry.value}`),...removeIndexes.map(index=>`index=${index}`)],required:"List linked tests and retry with an exact command/path or a current 1-based --remove-index.",examples:[`pm test ${id} --list`,`pm test ${id} --remove-index 1`]})}document.metadata.tests=mutation.tests;return{changedFields:stableValueEquals(previous,mutation.tests)?[]:["tests"]}}});return{itemId:result.item.id,tests:result.item.tests??[],testRuns:result.item.test_runs??[],changed:result.changedFields.length>0,removed}}function resolveTestRunOptions(options,tests){assertTestRunFlagUsage(options);const selectorInput={match:options.match,onlyIndex:options.onlyIndex===void 0?void 0:parseOnlyIndexValue(options.onlyIndex),onlyLast:options.onlyLast};const runSelection=options.run===true?resolveLinkedTestRunSelection(tests,selectorInput):void 0;const defaultTimeoutSeconds=options.timeout===void 0?void 0:parseOptionalNumber(options.timeout,"timeout");if(defaultTimeoutSeconds!==void 0&&defaultTimeoutSeconds<=0){throw new PmCliError("Test timeout must be a positive number",EXIT_CODE.USAGE)}return{defaultTimeoutSeconds,pmContextMode:parsePmContextMode(options.pmContext),runSelection,testsToRun:runSelection?.selected??tests}}async function executeSelectedLinkedTests(params){const{options,runOptions,pmRoot}=params;if(options.run!==true){return[]}return await runLinkedTests(runOptions.testsToRun,runOptions.defaultTimeoutSeconds,{progress:options.progress,envSet:options.envSet,envClear:options.envClear,sharedHostSafe:options.sharedHostSafe,pmContext:runOptions.pmContextMode,overrideLinkedPmContext:options.overrideLinkedPmContext,failOnContextMismatch:options.failOnContextMismatch,failOnEmptyTestRun:options.failOnEmptyTestRun,requireAssertionsForPm:options.requireAssertionsForPm,checkContext:options.checkContext,autoPmContext:options.autoPmContext,workspaceContext:options.workspaceContext,overrideLinkedWorkspaceContext:options.overrideLinkedWorkspaceContext,pmRoot,allowUntrustedLinkedTests:options.allowUntrustedLinkedTests,untrustedLinkedTestsPolicyEnabled:params.untrustedLinkedTestsPolicyEnabled,sourceRoots:{projectPmRoot:pmRoot,globalPmRoot:resolveGlobalPmRoot(process.cwd())}})}function buildTestWarnings(options,runSelection,runResults){const warnings=[];if(runSelection&&runSelection.selector!==null){warnings.push(`linked_test_selection:${runSelection.selector}=${runSelection.requested};selected=${runSelection.selected_count};skipped=${runSelection.skipped_count};indexes=${runSelection.selected_indexes.join(",")}`)}if(options.run===true&&options.checkContext===true){const preflight=summarizeContextPreflight(runResults);warnings.push(`context_preflight:checked_pm_commands=${preflight.checked_pm_commands};tracker_read_commands=${preflight.tracker_read_commands};mismatches=${preflight.mismatches};auto_remediated=${preflight.auto_remediated}`)}return warnings}function buildTrackedTestRunSummary(params){const summary=summarizeRunResultStatuses(params.runResults);const parsedAttempt=Number.parseInt(process.env.PM_BACKGROUND_TEST_RUN_ATTEMPT?.trim()??"",10);const resumedFrom=process.env.PM_BACKGROUND_TEST_RUN_RESUMED_FROM?.trim();const recordedAt=nowIso();const measurements=parseTestRunMeasurements(params.options.measure,recordedAt);return{run_id:resolveTrackedRunId("test"),kind:"test",status:summary.failed>0||params.failOnSkippedTriggered?"failed":"passed",started_at:params.runStartedAt,finished_at:recordedAt,recorded_at:recordedAt,attempt:Number.isFinite(parsedAttempt)&&parsedAttempt>=1?parsedAttempt:void 0,resumed_from:resumedFrom&&resumedFrom.length>0?resumedFrom:void 0,passed:summary.passed,failed:summary.failed,skipped:summary.skipped,fail_on_skipped_triggered:params.failOnSkippedTriggered?true:void 0,measurements:measurements.length>0?measurements:void 0,executions:params.runResults.filter(result=>result.command?.trim()).slice(0,32).map(result=>({command:result.command.trim(),requested_pm_context_mode:result.execution_context?.requested_pm_context_mode,pm_context_mode:result.execution_context?.pm_context_mode,workspace_context_mode:result.execution_context?.workspace_context_mode,trust_reason:result.execution_context?.trust.reason}))}}async function recordTestRunSummary(params){const{options,pmRoot,settings,itemId,runStartedAt,runResults,failOnSkippedTriggered,warnings}=params;if(options.run!==true||!runStartedAt){return void 0}const entry=buildTrackedTestRunSummary({options,runStartedAt,runResults,failOnSkippedTriggered});if(settings.testing.record_results_to_items!==true){return entry}try{await appendTrackedTestRunSummary({pmRoot,settings,itemId,author:resolveAuthor(options.author,settings.author_default),message:`Track test run summary (${entry.run_id})`,entry});return entry}catch(error){warnings.push(`test_result_tracking_failed:${itemId}:${error instanceof Error?error.message:String(error)}`);return entry}}function buildTestMeasurementProjection(options,runs,recordedRun){let metricBelow;if(options.metricBelow!==void 0){const separator=options.metricBelow.indexOf("=");const name=options.metricBelow.slice(0,separator).trim();const thresholdText=options.metricBelow.slice(separator+1).trim();const threshold=Number(thresholdText);if(separator<=0||name.length===0||thresholdText.length===0||!Number.isFinite(threshold)){throw new PmCliError("--metric-below must use name=value",EXIT_CODE.USAGE)}metricBelow=queryTestRunMeasurementsBelow(runs,name,threshold)}return{measurements:recordedRun?.measurements,metric_below:metricBelow,metric_diff:options.metricDiff===void 0?void 0:diffTestRunMeasurements(runs,options.metricDiff)}}async function maybeAcknowledgeLinkedTests(options,pmRoot,tests){if(options.acknowledgeLinkedTests!==true)return void 0;return acknowledgeLinkedTests(pmRoot,tests,nowIso())}async function runTest(id,options,global2){const stdinResolver=createStdinTokenResolver();const pmRoot=resolvePmRoot(process.cwd(),global2.path);await assertInitializedTracker(pmRoot);const settings=await readSettings(pmRoot);if((options.measure?.length??0)>0&&options.run!==true){throw new PmCliError("--measure requires --run",EXIT_CODE.USAGE)}parseTestRunMeasurements(options.measure,"validation");buildTestMeasurementProjection(options,[],void 0);const typeRegistry=resolveItemTypeRegistry(settings,getActiveExtensionRegistrations());const resolvedAdds=await stdinResolver.resolveList(options.add,"--add");const resolvedAddJsons=await stdinResolver.resolveList(options.addJson,"--add-json");const resolvedRemoves=await stdinResolver.resolveList(options.remove,"--remove");const parsedAdds=[...parseAddEntries(resolvedAdds),...parseAddJsonEntries(resolvedAddJsons)];const adds=await attachLinkedTestMutationProvenance(parsedAdds,resolveAuthor(options.author,settings.author_default),nowIso())??[];const removes=parseRemoveEntries(resolvedRemoves);const removeIndexes=parseRemoveIndexes(options.removeIndex);const item=await resolveLinkedTestItem({id,options,pmRoot,settings,typeToFolder:typeRegistry.type_to_folder,adds,removes,removeIndexes});const runOptions=resolveTestRunOptions(options,item.tests);const trustAcknowledgement=await maybeAcknowledgeLinkedTests(options,pmRoot,item.tests);const runStartedAt=options.run===true?nowIso():void 0;const runResults=await executeSelectedLinkedTests({options,runOptions,pmRoot,untrustedLinkedTestsPolicyEnabled:settings.testing.allow_untrusted_linked_tests});const failureCategories=countFailureCategories(runResults);const failOnSkippedTriggered=options.run===true&&options.failOnSkipped===true&&runResults.some(entry=>entry.status==="skipped");const warnings=buildTestWarnings(options,runOptions.runSelection,runResults);if(trustAcknowledgement){warnings.push(`linked_test_trust_acknowledged:${trustAcknowledgement.acknowledged}`)}const recordedRun=await recordTestRunSummary({options,pmRoot,settings,itemId:item.itemId,runStartedAt,runResults,failOnSkippedTriggered,warnings});const runs=recordedRun?buildTrackedTestRunHistory([...item.testRuns,recordedRun]):item.testRuns;const measurementProjection=buildTestMeasurementProjection(options,runs,recordedRun);return{ok:runResults.every(entry=>entry.status!=="failed")&&failOnSkippedTriggered!==true,id:item.itemId,tests:item.tests,run_results:runResults,failure_categories:failureCategories,selection:runOptions.runSelection?{selector:runOptions.runSelection.selector,requested:runOptions.runSelection.requested,selected_indexes:runOptions.runSelection.selected_indexes,selected_count:runOptions.runSelection.selected_count,skipped_count:runOptions.runSelection.skipped_count}:void 0,fail_on_skipped_triggered:failOnSkippedTriggered?true:void 0,warnings:warnings.length>0?warnings:void 0,changed:item.changed,count:item.tests.length,...item.removed>0?{removed:item.removed}:{},...measurementProjection}}const _testOnlyTestCommand={buildLinkedTestExecutionContext,buildPmContextMismatchHint,commandInvokesPmCli,commandInvokesPmTrackerReadCommand,commandUsesTestNameFilter,copyIntoSandboxIfPresent,countLinkedTestItemFiles,ensureScope,evaluateLinkedTestAssertions,endLinkedTestProgress,extractPmInvocationArgsFromSegment,firstDirectTestRunnerSubcommand,formatLinkedTestExecutionError,hasLinkedTestAssertions,initializeLinkedTestSandboxes,linkedTestsRequireTrackerData,parseAddJsonEntries,parseLinkedTestTimeoutSeconds,parsePmContextMode,readJsonPathValue,resolveAuthor,resolveDirectRunnerSubcommand,resolveLinkedTestEffectiveContextMode,resolveLinkedTestFailureExitCode,resolveLinkedTestRequestedContextMode,resolveTestRunOptions,resolveTrackedRunId,runLinkedTestCommand,seedMissingLinkedTestSettingsHistory,seedLinkedTestSandbox,seedLinkedTestTrackerData,segmentInvokesRecursiveTestAll,splitJsonPathSegments,summarizeRunResultStatuses};export{_testOnlyTestCommand,classifyLinkedTestCommandSafety,classifyLinkedTestFailure,countFailureCategories,extractReferencedPmItemIdsFromCommand,resolveLinkedTestFailureExitCode,runLinkedTests,runTest,summarizeContextPreflight};
2211
7
  //# sourceMappingURL=execution.js.map
2212
- //# debugId=03f2f710-3b96-5671-85fa-d8ea4726ee86