@unbrained/pm-cli 2026.9.7 → 2026.9.9

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 (746) 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 +7 -2
  6. package/.agents/skills/pm-user/references/ECOSYSTEM_PLANNING.md +95 -0
  7. package/.claude-plugin/marketplace.json +2 -2
  8. package/CHANGELOG.md +231 -182
  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.d.ts +0 -5
  92. package/dist/cli/register-mutation.js +19 -1893
  93. package/dist/cli/register-operations.js +1 -920
  94. package/dist/cli/register-setup.js +1 -716
  95. package/dist/cli/register-structured-mutation.js +1 -196
  96. package/dist/cli/registration-helpers.js +1 -5
  97. package/dist/cli/runtime-compatibility-boundary.js +4 -42
  98. package/dist/cli/schema-registration-helpers.js +1 -73
  99. package/dist/cli/search-refresh.js +1 -13
  100. package/dist/cli/stats-analytics-json.js +1 -68
  101. package/dist/cli/telemetry-flush.js +1 -10
  102. package/dist/cli-bundle/bundle-manifest.json +164 -156
  103. package/dist/cli-bundle/chunks/{chunk-NPN5I67J.js → chunk-3IPRM75M.js} +3 -3
  104. package/dist/cli-bundle/chunks/chunk-4VL5P22L.js +2 -0
  105. package/dist/cli-bundle/chunks/chunk-AAFBV3U3.js +29 -0
  106. package/dist/cli-bundle/chunks/chunk-D3YVILJJ.js +20 -0
  107. package/dist/cli-bundle/chunks/{chunk-WYRZZ5ZE.js → chunk-DEZPZOQ7.js} +2 -2
  108. package/dist/cli-bundle/chunks/chunk-ECBLPN7B.js +3 -0
  109. package/dist/cli-bundle/chunks/chunk-J6ERHBV2.js +235 -0
  110. package/dist/cli-bundle/chunks/chunk-JUNJ5EKI.js +2 -0
  111. package/dist/cli-bundle/chunks/chunk-L5RCDKQ2.js +2 -0
  112. package/dist/cli-bundle/chunks/chunk-NSAPNEYH.js +2 -0
  113. package/dist/cli-bundle/chunks/chunk-OHNLSGGV.js +79 -0
  114. package/dist/cli-bundle/chunks/chunk-WQKMSFBV.js +5 -0
  115. package/dist/cli-bundle/chunks/{chunk-BAX2BYUW.js → chunk-XZ6T7YPP.js} +256 -150
  116. package/dist/cli-bundle/chunks/{chunk-JT6XF6GE.js → chunk-Z3XVXUVF.js} +4 -4
  117. package/dist/cli-bundle/chunks/register-list-query-UIHRCS6D.js +11 -0
  118. package/dist/cli-bundle/chunks/register-mutation-WHWMGRVX.js +20 -0
  119. package/dist/cli-bundle/chunks/register-operations-64YZYTC5.js +2 -0
  120. package/dist/cli-bundle/chunks/register-setup-PERI45EM.js +2 -0
  121. package/dist/cli-bundle/focused-chunks/chunk-2W4XIYOB.js +2 -0
  122. package/dist/cli-bundle/focused-chunks/chunk-2WSBXIGF.js +12 -0
  123. package/dist/cli-bundle/focused-chunks/chunk-5FHTCOHS.js +3 -0
  124. package/dist/cli-bundle/focused-chunks/chunk-7N6DOREF.js +26 -0
  125. package/dist/cli-bundle/focused-chunks/{chunk-3BZEOXLT.js → chunk-7PC4R5QB.js} +2 -2
  126. package/dist/cli-bundle/focused-chunks/chunk-BUCJUZVG.js +6 -0
  127. package/dist/cli-bundle/focused-chunks/chunk-FE3BABLJ.js +2 -0
  128. package/dist/cli-bundle/focused-chunks/chunk-IWITF53W.js +2 -0
  129. package/dist/cli-bundle/focused-chunks/chunk-OQUGRHKV.js +10 -0
  130. package/dist/cli-bundle/focused-chunks/{chunk-M6PBRFSH.js → chunk-PNKIANFZ.js} +2 -2
  131. package/dist/cli-bundle/focused-chunks/{chunk-LIJIA4TY.js → chunk-QQQ4SAUO.js} +3 -3
  132. package/dist/cli-bundle/focused-chunks/chunk-TTYXF5YD.js +177 -0
  133. package/dist/cli-bundle/focused-chunks/chunk-XNR3DM6J.js +2 -0
  134. package/dist/cli-bundle/focused-chunks/chunk-XSS3APUF.js +9 -0
  135. package/dist/cli-bundle/focused-chunks/chunk-YCDQ2N23.js +8 -0
  136. package/dist/cli-bundle/focused-chunks/chunk-YJUXGS57.js +20 -0
  137. package/dist/cli-bundle/focused-chunks/chunk-ZTCE4UO6.js +58 -0
  138. package/dist/cli-bundle/main.js +20 -20
  139. package/dist/cli-bundle/sdk-authoring.js +1 -1
  140. package/dist/cli-bundle/sdk-contracts.js +2 -2
  141. package/dist/cli-bundle/sdk-core.js +37 -37
  142. package/dist/cli-bundle/sdk-governance.js +1 -1
  143. package/dist/cli-bundle/sdk-graph.js +1 -1
  144. package/dist/cli-bundle/sdk-merge.js +42 -42
  145. package/dist/cli-bundle/sdk-query.js +1 -1
  146. package/dist/cli-bundle/sdk-runtime.js +1 -1
  147. package/dist/cli-bundle/sdk-testing.js +1 -1
  148. package/dist/cli-bundle/sdk.js +11 -11
  149. package/dist/cli.js +1 -104
  150. package/dist/core/checkpoint/checkpoint-gc.js +1 -169
  151. package/dist/core/checkpoint/mutation-checkpoint.js +2 -133
  152. package/dist/core/config/nested-settings.js +1 -534
  153. package/dist/core/config/positional-value.js +1 -109
  154. package/dist/core/diagnostics/remediation.js +1 -506
  155. package/dist/core/extensions/activation-summary-markdown.js +2 -145
  156. package/dist/core/extensions/activation-summary.js +1 -92
  157. package/dist/core/extensions/activation-transaction.js +1 -46
  158. package/dist/core/extensions/capability-usage.js +1 -173
  159. package/dist/core/extensions/command-hook-context.js +1 -13
  160. package/dist/core/extensions/command-metadata-contract.js +1 -22
  161. package/dist/core/extensions/command-visibility-tier.js +1 -29
  162. package/dist/core/extensions/contribution-inventory.js +1 -173
  163. package/dist/core/extensions/exporter-output-contract.js +1 -66
  164. package/dist/core/extensions/extension-candidate.js +1 -18
  165. package/dist/core/extensions/extension-capability-aliases.js +1 -158
  166. package/dist/core/extensions/extension-hook-runtime.js +1 -699
  167. package/dist/core/extensions/extension-policy.js +1 -599
  168. package/dist/core/extensions/extension-registries.js +1 -62
  169. package/dist/core/extensions/extension-runtime-helpers.js +1 -33
  170. package/dist/core/extensions/extension-types.js +1 -102
  171. package/dist/core/extensions/flag-definition-validation.js +1 -52
  172. package/dist/core/extensions/flag-value-types.js +1 -64
  173. package/dist/core/extensions/index.js +1 -526
  174. package/dist/core/extensions/item-field-types.js +1 -39
  175. package/dist/core/extensions/item-fields.js +1 -261
  176. package/dist/core/extensions/loader.js +1 -2223
  177. package/dist/core/extensions/manifest-schema.js +1 -69
  178. package/dist/core/extensions/module-graph-snapshot.js +1 -33
  179. package/dist/core/extensions/preflight-ownership.js +1 -41
  180. package/dist/core/extensions/registration-validation.js +1 -82
  181. package/dist/core/extensions/registration-values.js +1 -209
  182. package/dist/core/extensions/reserved-host-flags.js +1 -36
  183. package/dist/core/extensions/runtime-registrations.js +1 -67
  184. package/dist/core/extensions/service-ownership.js +1 -19
  185. package/dist/core/extensions/version-compat.js +1 -216
  186. package/dist/core/fs/fs-utils.js +2 -158
  187. package/dist/core/fs/host-environment-errors.js +1 -141
  188. package/dist/core/fs/index.js +1 -9
  189. package/dist/core/fs/path-utils.js +1 -17
  190. package/dist/core/governance/content-fields.js +1 -160
  191. package/dist/core/governance/issue-codes.js +1 -151
  192. package/dist/core/governance/metadata-coverage.js +1 -358
  193. package/dist/core/history/drift-cache.js +3 -72
  194. package/dist/core/history/drift-scan.js +1 -342
  195. package/dist/core/history/event-classification.js +1 -109
  196. package/dist/core/history/event-index.js +8 -784
  197. package/dist/core/history/history-compact-bulk.js +1 -104
  198. package/dist/core/history/history-diff.js +1 -158
  199. package/dist/core/history/history-rewrite.d.ts +6 -1
  200. package/dist/core/history/history-rewrite.js +1 -107
  201. package/dist/core/history/history-storage-stats.js +1 -102
  202. package/dist/core/history/history-stream-policy.js +1 -62
  203. package/dist/core/history/history.js +2 -617
  204. package/dist/core/history/identity.js +1 -30
  205. package/dist/core/history/index.js +1 -9
  206. package/dist/core/history/projection.js +1 -196
  207. package/dist/core/history/read.d.ts +9 -0
  208. package/dist/core/history/read.js +1 -57
  209. package/dist/core/history/replay.js +2 -439
  210. package/dist/core/history/version-address.js +1 -32
  211. package/dist/core/history/workspace-history.js +2 -433
  212. package/dist/core/io/body-file.js +1 -62
  213. package/dist/core/io/bulk-ids-input.js +1 -112
  214. package/dist/core/item/actionability.js +1 -327
  215. package/dist/core/item/dependency-reference.js +1 -20
  216. package/dist/core/item/id.js +1 -112
  217. package/dist/core/item/index.js +1 -12
  218. package/dist/core/item/item-format-version.js +1 -78
  219. package/dist/core/item/item-format.js +5 -1473
  220. package/dist/core/item/item-record.js +1 -23
  221. package/dist/core/item/item-type-definition.js +1 -133
  222. package/dist/core/item/parent-reference-policy.js +1 -57
  223. package/dist/core/item/parse.js +2 -603
  224. package/dist/core/item/plan-lifecycle.js +1 -19
  225. package/dist/core/item/priority.js +1 -61
  226. package/dist/core/item/sprint-release-format.js +1 -38
  227. package/dist/core/item/status-filter.js +1 -115
  228. package/dist/core/item/status.js +1 -43
  229. package/dist/core/item/toon-decode.js +1 -29
  230. package/dist/core/item/type-registry.js +1 -715
  231. package/dist/core/item/type-synonyms.js +1 -38
  232. package/dist/core/lock/index.js +1 -9
  233. package/dist/core/lock/lock-gc.js +1 -240
  234. package/dist/core/lock/lock.js +3 -590
  235. package/dist/core/output/mutation-projection.js +1 -217
  236. package/dist/core/output/output-control.js +1 -25
  237. package/dist/core/output/output.js +11 -494
  238. package/dist/core/output/query-summary.js +1 -58
  239. package/dist/core/output/tabular.js +1 -77
  240. package/dist/core/packages/manifest.js +1 -244
  241. package/dist/core/packages/root.js +1 -84
  242. package/dist/core/policy/workflow-policy-schema.d.ts +312 -0
  243. package/dist/core/policy/workflow-policy-schema.js +2 -0
  244. package/dist/core/policy/workflow-policy-store.d.ts +24 -0
  245. package/dist/core/policy/workflow-policy-store.js +2 -0
  246. package/dist/core/policy/workflow-policy.d.ts +139 -0
  247. package/dist/core/policy/workflow-policy.js +2 -0
  248. package/dist/core/profile/profile-describe.js +1 -83
  249. package/dist/core/profile/profile-lint.js +1 -415
  250. package/dist/core/profile/profile-plan.js +0 -0
  251. package/dist/core/profile/profile-presets.js +1 -370
  252. package/dist/core/reproducibility/context.js +1 -82
  253. package/dist/core/schema/fields-file.js +2 -288
  254. package/dist/core/schema/item-types-file.js +2 -296
  255. package/dist/core/schema/runtime-field-filters.js +1 -49
  256. package/dist/core/schema/runtime-field-values.js +1 -313
  257. package/dist/core/schema/runtime-schema.js +2 -826
  258. package/dist/core/schema/status-defs-file.js +2 -227
  259. package/dist/core/schema/status-token.js +1 -31
  260. package/dist/core/schema/type-inference.js +1 -95
  261. package/dist/core/schema/type-presets.js +1 -87
  262. package/dist/core/schema/type-workflows.js +1 -104
  263. package/dist/core/search/background-refresh.js +2 -286
  264. package/dist/core/search/bm25.js +1 -146
  265. package/dist/core/search/cache.js +2 -534
  266. package/dist/core/search/corpus.js +1 -311
  267. package/dist/core/search/embedding-batches.js +1 -163
  268. package/dist/core/search/eval.js +1 -225
  269. package/dist/core/search/http-client.js +1 -75
  270. package/dist/core/search/providers.js +1 -242
  271. package/dist/core/search/relevance.js +1 -244
  272. package/dist/core/search/semantic-defaults.js +1 -199
  273. package/dist/core/search/staleness.js +1 -30
  274. package/dist/core/search/vector-stores.js +2 -664
  275. package/dist/core/search/vectorization-metadata.js +1 -81
  276. package/dist/core/sentry/helpers.js +1 -272
  277. package/dist/core/sentry/instrument.js +1 -387
  278. package/dist/core/session/session-state.js +1 -260
  279. package/dist/core/shared/agent-session-context.js +1 -133
  280. package/dist/core/shared/author.js +1 -1172
  281. package/dist/core/shared/command-types.js +1 -4
  282. package/dist/core/shared/conflict-markers.js +1 -31
  283. package/dist/core/shared/constants.js +1 -405
  284. package/dist/core/shared/errors.js +1 -20
  285. package/dist/core/shared/html-entity-decode.js +1 -131
  286. package/dist/core/shared/index.js +1 -12
  287. package/dist/core/shared/item-metadata-contract.js +1 -89
  288. package/dist/core/shared/lazy-module.js +1 -16
  289. package/dist/core/shared/levenshtein.js +1 -61
  290. package/dist/core/shared/memo.js +1 -19
  291. package/dist/core/shared/numeric-parsers.js +1 -20
  292. package/dist/core/shared/option-alias-visibility.js +1 -67
  293. package/dist/core/shared/primitives.js +1 -127
  294. package/dist/core/shared/serial-queue.js +1 -79
  295. package/dist/core/shared/serialization.js +1 -210
  296. package/dist/core/shared/split-comma-list.js +1 -30
  297. package/dist/core/shared/text-normalization.js +1 -38
  298. package/dist/core/shared/time.js +1 -306
  299. package/dist/core/store/index.js +1 -12
  300. package/dist/core/store/item-format-migration.js +1 -125
  301. package/dist/core/store/item-metadata-cache.js +1 -1116
  302. package/dist/core/store/item-metadata-query-index.js +9 -394
  303. package/dist/core/store/item-metadata-read-work.js +1 -43
  304. package/dist/core/store/item-store.js +1 -693
  305. package/dist/core/store/paths.d.ts +1 -1
  306. package/dist/core/store/paths.js +1 -214
  307. package/dist/core/store/settings-read-cache.js +1 -85
  308. package/dist/core/store/settings-validator.d.ts +1 -0
  309. package/dist/core/store/settings-validator.js +1 -421
  310. package/dist/core/store/settings.js +3 -1309
  311. package/dist/core/store/tracker-preflight.js +1 -141
  312. package/dist/core/telemetry/consent.js +5 -108
  313. package/dist/core/telemetry/observability.js +1 -246
  314. package/dist/core/telemetry/runtime.js +4 -1988
  315. package/dist/core/test/background-runs.js +4 -945
  316. package/dist/core/test/item-test-run-tracking.js +1 -63
  317. package/dist/core/test/run-selectors.js +1 -120
  318. package/dist/core/validate/estimate-defaults.js +1 -89
  319. package/dist/core/validate/fix-planning.js +1 -189
  320. package/dist/core/validate/linked-artifact-reference.js +1 -36
  321. package/dist/core/validate/missing-by-type.js +1 -34
  322. package/dist/core/validate/missing-link-owners.js +1 -95
  323. package/dist/core/validate/stale-file-classification.js +1 -82
  324. package/dist/mcp/http-server.js +5 -448
  325. package/dist/mcp/legacy-adapter.js +1 -77
  326. package/dist/mcp/runtime-capabilities.js +1 -200
  327. package/dist/mcp/server.js +4 -1233
  328. package/dist/mcp/tool-definitions.js +1 -804
  329. package/dist/sdk/actionability.js +1 -13
  330. package/dist/sdk/agent/closed-domain-contracts.js +1 -161
  331. package/dist/sdk/agent/command-recovery.js +1 -59
  332. package/dist/sdk/agent/command-suggestions.js +1 -66
  333. package/dist/sdk/agent/create-option-policy.js +1 -31
  334. package/dist/sdk/agent/identity-config.js +1 -106
  335. package/dist/sdk/agent/item-addressing.js +1 -207
  336. package/dist/sdk/agent/refusal-closure-census.js +1 -151
  337. package/dist/sdk/agent/refusal-closure.js +1 -133
  338. package/dist/sdk/agent/refusal-corpus-contracts.js +1 -193
  339. package/dist/sdk/agent/refusal-reachability.js +1 -593
  340. package/dist/sdk/agent/subcommand-recovery.js +1 -83
  341. package/dist/sdk/agent/task-transcript-contracts.js +1 -197
  342. package/dist/sdk/agent/tracker-preflight-contracts.js +1 -126
  343. package/dist/sdk/agent-capability-contracts.js +1 -428
  344. package/dist/sdk/agent-session-context.js +1 -9
  345. package/dist/sdk/annotations.js +1 -448
  346. package/dist/sdk/author-attribution.js +1 -468
  347. package/dist/sdk/authoring.js +1 -20
  348. package/dist/sdk/cli-bootstrap.js +1 -838
  349. package/dist/sdk/cli-contracts/agent-output-contracts.d.ts +1 -1
  350. package/dist/sdk/cli-contracts/agent-output-contracts.js +4 -476
  351. package/dist/sdk/cli-contracts/bootstrap-command-scanner.js +1 -79
  352. package/dist/sdk/cli-contracts/command-aliases.d.ts +4 -0
  353. package/dist/sdk/cli-contracts/command-aliases.js +1 -107
  354. package/dist/sdk/cli-contracts/command-exit-contracts.js +1 -151
  355. package/dist/sdk/cli-contracts/commander-mutation-options.js +1 -1081
  356. package/dist/sdk/cli-contracts/commander-types.js +1 -159
  357. package/dist/sdk/cli-contracts/completeness.js +1 -393
  358. package/dist/sdk/cli-contracts/enum-contracts.d.ts +1 -1
  359. package/dist/sdk/cli-contracts/enum-contracts.js +1 -222
  360. package/dist/sdk/cli-contracts/flag-contracts.js +1 -1947
  361. package/dist/sdk/cli-contracts/flag-lexicon-contracts.js +1 -347
  362. package/dist/sdk/cli-contracts/grammar-contracts.js +1 -986
  363. package/dist/sdk/cli-contracts/registration-helpers.js +7 -990
  364. package/dist/sdk/cli-contracts/runtime-contracts.d.ts +5 -0
  365. package/dist/sdk/cli-contracts/runtime-contracts.js +1 -2090
  366. package/dist/sdk/cli-contracts/schemas/assurance-definition.js +1 -324
  367. package/dist/sdk/cli-contracts/string-lists.js +1 -16
  368. package/dist/sdk/cli-contracts/tool-option-contracts.js +1 -461
  369. package/dist/sdk/cli-contracts/tool-parameter-tables.js +1 -2167
  370. package/dist/sdk/cli-contracts/tool-schema.d.ts +3 -1
  371. package/dist/sdk/cli-contracts/tool-schema.js +1 -1664
  372. package/dist/sdk/cli-contracts/typed-action-inputs.js +1 -4
  373. package/dist/sdk/cli-contracts.d.ts +1 -1
  374. package/dist/sdk/cli-contracts.js +1 -17
  375. package/dist/sdk/cli-program.js +1 -49
  376. package/dist/sdk/command-line.d.ts +2 -5
  377. package/dist/sdk/command-line.js +1 -78
  378. package/dist/sdk/comments.js +1 -133
  379. package/dist/sdk/compile-cache.js +1 -73
  380. package/dist/sdk/completion.js +174 -839
  381. package/dist/sdk/compose.js +1 -1090
  382. package/dist/sdk/config.js +1 -1204
  383. package/dist/sdk/context/recency.js +1 -33
  384. package/dist/sdk/context/semantic-session-attribution.js +1 -82
  385. package/dist/sdk/context/usage-egress.js +1 -29
  386. package/dist/sdk/context/usage-ledger.js +3 -185
  387. package/dist/sdk/context-intent-contracts.js +1 -709
  388. package/dist/sdk/context-intent-runtime.js +1 -115
  389. package/dist/sdk/context-packing.js +1 -218
  390. package/dist/sdk/context-relevance.js +1 -503
  391. package/dist/sdk/context-signal-store.js +2 -426
  392. package/dist/sdk/context-usage.js +1 -367
  393. package/dist/sdk/contracts.js +1 -23
  394. package/dist/sdk/core-governance.js +1 -14
  395. package/dist/sdk/core.js +1 -14
  396. package/dist/sdk/corpus-shape.js +1 -401
  397. package/dist/sdk/define.js +1 -220
  398. package/dist/sdk/dependencies.js +1 -718
  399. package/dist/sdk/dependency-flag-validation.js +1 -156
  400. package/dist/sdk/dependency-provenance.js +1 -135
  401. package/dist/sdk/docs.js +1 -14
  402. package/dist/sdk/duplicates.js +1 -40
  403. package/dist/sdk/environment/agent-environment.js +1 -466
  404. package/dist/sdk/environment/host-environment-errors.js +1 -8
  405. package/dist/sdk/environment/project-runtime-compatibility.js +1 -481
  406. package/dist/sdk/environment/source-context.js +1 -28
  407. package/dist/sdk/environment/tracker-preflight.js +1 -9
  408. package/dist/sdk/error-code-catalog.js +1 -153
  409. package/dist/sdk/error-runtime.js +1 -58
  410. package/dist/sdk/errors.js +1 -58
  411. package/dist/sdk/eval.js +1 -190
  412. package/dist/sdk/extension/author-manifest.js +1 -92
  413. package/dist/sdk/extension/bundled-catalog.js +1 -332
  414. package/dist/sdk/extension/concurrency.js +1 -25
  415. package/dist/sdk/extension/copy-scope.d.ts +2 -0
  416. package/dist/sdk/extension/copy-scope.js +2 -0
  417. package/dist/sdk/extension/custom-field-collisions.js +1 -30
  418. package/dist/sdk/extension/describe.js +3 -189
  419. package/dist/sdk/extension/doctor.js +1 -606
  420. package/dist/sdk/extension/install-plan.d.ts +69 -0
  421. package/dist/sdk/extension/install-plan.js +2 -0
  422. package/dist/sdk/extension/install-receipts.d.ts +7 -0
  423. package/dist/sdk/extension/install-receipts.js +2 -0
  424. package/dist/sdk/extension/install-runtime.js +3 -313
  425. package/dist/sdk/extension/install-snapshot.js +1 -66
  426. package/dist/sdk/extension/install-sources.js +5 -1057
  427. package/dist/sdk/extension/managed-state.js +2 -252
  428. package/dist/sdk/extension/migrations.js +2 -174
  429. package/dist/sdk/extension/output-ownership.js +1 -25
  430. package/dist/sdk/extension/runtime-summary.js +1 -18
  431. package/dist/sdk/extension/scaffold.js +4 -2308
  432. package/dist/sdk/extension/shared.js +1 -147
  433. package/dist/sdk/extension/source-resolution.js +1 -65
  434. package/dist/sdk/extension/update-check.js +1 -67
  435. package/dist/sdk/extension-command-context.js +1 -44
  436. package/dist/sdk/extension-contracts.js +1 -73
  437. package/dist/sdk/extension.d.ts +7 -2
  438. package/dist/sdk/extension.js +1 -2196
  439. package/dist/sdk/files.js +1 -535
  440. package/dist/sdk/flag-invocation-contracts.d.ts +0 -5
  441. package/dist/sdk/flag-invocation-contracts.js +1 -344
  442. package/dist/sdk/generated/generated-error-code-catalog-part-1.js +1 -2224
  443. package/dist/sdk/generated/generated-error-code-catalog-part-2.js +1 -2236
  444. package/dist/sdk/generated-error-code-catalog.js +1 -16
  445. package/dist/sdk/governance/assurance-action-contracts.js +1 -4
  446. package/dist/sdk/governance/assurance-action.js +1 -411
  447. package/dist/sdk/governance/assurance-limits.js +1 -10
  448. package/dist/sdk/governance/assurance-mutation-error.js +1 -81
  449. package/dist/sdk/governance/assurance-presets.js +1 -135
  450. package/dist/sdk/governance/assurance-runtime.js +1 -238
  451. package/dist/sdk/governance/assurance.js +4 -1182
  452. package/dist/sdk/governance/boundary-fixtures.js +1 -200
  453. package/dist/sdk/governance/coverage-assurance.js +1 -60
  454. package/dist/sdk/governance/defect-recurrence-coverage.js +1 -188
  455. package/dist/sdk/governance/defect-recurrence-graph.js +1 -120
  456. package/dist/sdk/governance/defect-recurrence-signals.js +1 -24
  457. package/dist/sdk/governance/defect-recurrence.js +1 -623
  458. package/dist/sdk/governance/extension-host-version.js +1 -115
  459. package/dist/sdk/governance/gc.js +1 -368
  460. package/dist/sdk/governance/health.js +1 -2367
  461. package/dist/sdk/governance/hierarchy-validation.js +1 -108
  462. package/dist/sdk/governance/linked-file-report.js +1 -71
  463. package/dist/sdk/governance/merge-receipt-history.js +1 -206
  464. package/dist/sdk/governance/provenance-health.js +1 -282
  465. package/dist/sdk/governance/reindex.js +4 -761
  466. package/dist/sdk/governance/stale-work.js +1 -82
  467. package/dist/sdk/governance/status-role-diagnostics.js +1 -35
  468. package/dist/sdk/governance/storage-integrity.js +1 -280
  469. package/dist/sdk/governance/tracked-runtime-cache.js +1 -106
  470. package/dist/sdk/governance/upgrade.js +2 -327
  471. package/dist/sdk/governance/validate-history-drift.js +1 -81
  472. package/dist/sdk/governance/validate-item-reader.js +1 -17
  473. package/dist/sdk/governance/validate-normalization.js +1 -36
  474. package/dist/sdk/governance/validate.d.ts +3 -1
  475. package/dist/sdk/governance/validate.js +1 -2261
  476. package/dist/sdk/governance/workflow-completeness.d.ts +32 -0
  477. package/dist/sdk/governance/workflow-completeness.js +2 -0
  478. package/dist/sdk/governance/workflow-policy.d.ts +47 -0
  479. package/dist/sdk/governance/workflow-policy.js +4 -0
  480. package/dist/sdk/governance/workspace-position.js +1 -163
  481. package/dist/sdk/governance.d.ts +2 -0
  482. package/dist/sdk/governance.js +1 -31
  483. package/dist/sdk/graph/adapter.js +1 -181
  484. package/dist/sdk/graph/analytics.js +1 -474
  485. package/dist/sdk/graph/assembly.js +1 -377
  486. package/dist/sdk/graph/cache.js +1 -160
  487. package/dist/sdk/graph/centrality.js +1 -258
  488. package/dist/sdk/graph/conformance.js +1 -130
  489. package/dist/sdk/graph/deadline-scheduling.d.ts +90 -0
  490. package/dist/sdk/graph/deadline-scheduling.js +2 -0
  491. package/dist/sdk/graph/durable-cache.d.ts +1 -1
  492. package/dist/sdk/graph/durable-cache.js +1 -361
  493. package/dist/sdk/graph/governance-contracts.js +1 -34
  494. package/dist/sdk/graph/governance.d.ts +13 -1
  495. package/dist/sdk/graph/governance.js +1 -735
  496. package/dist/sdk/graph/hierarchy-integrity.js +1 -310
  497. package/dist/sdk/graph/index.d.ts +3 -2
  498. package/dist/sdk/graph/index.js +1 -26
  499. package/dist/sdk/graph/lifecycle-coverage.d.ts +32 -0
  500. package/dist/sdk/graph/lifecycle-coverage.js +2 -0
  501. package/dist/sdk/graph/mutation-advisory.js +1 -176
  502. package/dist/sdk/graph/remediation.js +1 -197
  503. package/dist/sdk/graph/run.d.ts +15 -1
  504. package/dist/sdk/graph/run.js +1 -768
  505. package/dist/sdk/graph/scheduling.js +1 -124
  506. package/dist/sdk/graph/traversal.js +1 -284
  507. package/dist/sdk/graph/workspace-relationship-kind-registry.js +1 -27
  508. package/dist/sdk/graph.js +1 -15
  509. package/dist/sdk/guide-topics.js +1 -707
  510. package/dist/sdk/guide.js +1 -279
  511. package/dist/sdk/history/maintenance.d.ts +71 -0
  512. package/dist/sdk/history/maintenance.js +2 -0
  513. package/dist/sdk/history/salvage.js +2 -247
  514. package/dist/sdk/history/subject.d.ts +19 -0
  515. package/dist/sdk/history/subject.js +2 -0
  516. package/dist/sdk/history-analytics.js +1 -282
  517. package/dist/sdk/history-compact.js +1 -527
  518. package/dist/sdk/history-mcp.js +1 -68
  519. package/dist/sdk/history-provenance.js +1 -182
  520. package/dist/sdk/history-read.js +1 -96
  521. package/dist/sdk/history-redact.d.ts +1 -14
  522. package/dist/sdk/history-redact.js +1 -643
  523. package/dist/sdk/history-repair.d.ts +5 -0
  524. package/dist/sdk/history-repair.js +1 -657
  525. package/dist/sdk/improvement-ledger-validation.js +1 -62
  526. package/dist/sdk/improvement-ledger.js +2 -257
  527. package/dist/sdk/index.d.ts +4 -1
  528. package/dist/sdk/index.js +1 -183
  529. package/dist/sdk/init-agent-guidance.js +2 -496
  530. package/dist/sdk/init.js +2 -820
  531. package/dist/sdk/invocation-author.js +1 -39
  532. package/dist/sdk/item-children.js +1 -90
  533. package/dist/sdk/item-metadata-index.js +1 -15
  534. package/dist/sdk/item-schedule.js +1 -27
  535. package/dist/sdk/item-transaction.js +1 -396
  536. package/dist/sdk/learnings.js +1 -29
  537. package/dist/sdk/lifecycle/append.js +2 -53
  538. package/dist/sdk/lifecycle/claim.d.ts +2 -0
  539. package/dist/sdk/lifecycle/claim.js +1 -242
  540. package/dist/sdk/lifecycle/close-many.js +1 -438
  541. package/dist/sdk/lifecycle/close.js +1 -468
  542. package/dist/sdk/lifecycle/copy.js +1 -212
  543. package/dist/sdk/lifecycle/create.js +1 -1949
  544. package/dist/sdk/lifecycle/delete.js +1 -45
  545. package/dist/sdk/lifecycle/event-validation-messages.js +1 -91
  546. package/dist/sdk/lifecycle/focus.js +1 -91
  547. package/dist/sdk/lifecycle/hierarchy-mutation-lock.js +1 -24
  548. package/dist/sdk/lifecycle/legacy-none-tokens.js +1 -149
  549. package/dist/sdk/lifecycle/lifecycle-transitions.js +1 -40
  550. package/dist/sdk/lifecycle/mcp-actions.d.ts +2 -0
  551. package/dist/sdk/lifecycle/mcp-actions.js +1 -51
  552. package/dist/sdk/lifecycle/metadata-normalizers.js +1 -46
  553. package/dist/sdk/lifecycle/mutation-command-options.js +1 -9
  554. package/dist/sdk/lifecycle/plan.js +1 -1606
  555. package/dist/sdk/lifecycle/recurrence-parsers.d.ts +1 -1
  556. package/dist/sdk/lifecycle/recurrence-parsers.js +1 -147
  557. package/dist/sdk/lifecycle/reopen.js +1 -68
  558. package/dist/sdk/lifecycle/repeatable-metadata-parsers.js +1 -143
  559. package/dist/sdk/lifecycle/restore.d.ts +1 -0
  560. package/dist/sdk/lifecycle/restore.js +1 -330
  561. package/dist/sdk/lifecycle/shared-unset-fields.js +1 -295
  562. package/dist/sdk/lifecycle/update-many.js +1 -861
  563. package/dist/sdk/lifecycle/update.js +1 -2136
  564. package/dist/sdk/lifecycle-completion.js +1 -92
  565. package/dist/sdk/lifecycle-policy.js +1 -120
  566. package/dist/sdk/linked-artifacts.js +1 -530
  567. package/dist/sdk/mcp/apps.js +4 -133
  568. package/dist/sdk/mcp/authorization.js +1 -404
  569. package/dist/sdk/mcp/discovery.js +1 -494
  570. package/dist/sdk/mcp/interactions.js +1 -336
  571. package/dist/sdk/mcp/protocol.d.ts +7 -0
  572. package/dist/sdk/mcp/protocol.js +1 -184
  573. package/dist/sdk/mcp/skills.js +1 -389
  574. package/dist/sdk/mcp/subscriptions.js +1 -211
  575. package/dist/sdk/mcp/tasks.js +2 -430
  576. package/dist/sdk/mcp/transport.js +1 -260
  577. package/dist/sdk/merge/abandoned-receipts.js +1 -46
  578. package/dist/sdk/merge/driver.js +1 -212
  579. package/dist/sdk/merge/index.js +1 -16
  580. package/dist/sdk/merge/install.js +3 -561
  581. package/dist/sdk/merge/receipt-file-boundary.js +1 -46
  582. package/dist/sdk/merge/receipt-operation.js +1 -96
  583. package/dist/sdk/merge/receipt-schema.js +1 -397
  584. package/dist/sdk/merge/receipts.js +4 -525
  585. package/dist/sdk/merge/reconcile.js +1 -352
  586. package/dist/sdk/merge/three-way.js +3 -775
  587. package/dist/sdk/merge.js +1 -10
  588. package/dist/sdk/mutation-events.js +1 -292
  589. package/dist/sdk/mutation-guard.js +1 -216
  590. package/dist/sdk/notes.js +1 -139
  591. package/dist/sdk/output-contracts.js +1 -172
  592. package/dist/sdk/output-projection.js +1 -430
  593. package/dist/sdk/output-token-accounting.js +1 -140
  594. package/dist/sdk/output.js +1 -64
  595. package/dist/sdk/ownership-source.js +1 -21
  596. package/dist/sdk/package-import-adapters.js +1 -442
  597. package/dist/sdk/package-migrations.js +1 -20
  598. package/dist/sdk/package-runtime-options.js +1 -80
  599. package/dist/sdk/pagination.js +1 -162
  600. package/dist/sdk/profile.js +1 -454
  601. package/dist/sdk/provenance.js +1 -258
  602. package/dist/sdk/public-surface.json +1 -1
  603. package/dist/sdk/query/activity.js +1 -401
  604. package/dist/sdk/query/aggregate.d.ts +16 -0
  605. package/dist/sdk/query/aggregate.js +1 -390
  606. package/dist/sdk/query/calendar.js +1 -964
  607. package/dist/sdk/query/complete-list.js +1 -215
  608. package/dist/sdk/query/context.js +1 -1702
  609. package/dist/sdk/query/duplicate-candidates.js +1 -70
  610. package/dist/sdk/query/get.js +1 -450
  611. package/dist/sdk/query/history.js +1 -186
  612. package/dist/sdk/query/item-filter-options.js +1 -9
  613. package/dist/sdk/query/light-metadata.js +1 -18
  614. package/dist/sdk/query/list-filter-shared.js +1 -81
  615. package/dist/sdk/query/list.js +1 -1304
  616. package/dist/sdk/query/multi-value-filters.js +1 -78
  617. package/dist/sdk/query/next.js +1 -513
  618. package/dist/sdk/query/parsers.js +1 -44
  619. package/dist/sdk/query/projection-contracts.js +1 -184
  620. package/dist/sdk/query/search-contracts.d.ts +27 -0
  621. package/dist/sdk/query/search-contracts.js +1 -230
  622. package/dist/sdk/query/search-pagination.js +1 -35
  623. package/dist/sdk/query/search-rendering.js +1 -105
  624. package/dist/sdk/query/search.d.ts +6 -4
  625. package/dist/sdk/query/search.js +2 -1987
  626. package/dist/sdk/query.js +1 -35
  627. package/dist/sdk/read-output/continuation.js +1 -194
  628. package/dist/sdk/read-output-budget.js +1 -135
  629. package/dist/sdk/read-output-contracts.d.ts +1 -1
  630. package/dist/sdk/read-output-contracts.js +1 -1225
  631. package/dist/sdk/read-output-rows.js +1 -186
  632. package/dist/sdk/read-output-session.js +1 -208
  633. package/dist/sdk/relationship-analytics.js +1 -410
  634. package/dist/sdk/relationship-context.js +1 -284
  635. package/dist/sdk/relationship-history.js +4 -519
  636. package/dist/sdk/relationship-kinds/contract.js +1 -22
  637. package/dist/sdk/relationship-migration.js +1 -77
  638. package/dist/sdk/relationships.js +1 -579
  639. package/dist/sdk/reproducibility/process.js +1 -87
  640. package/dist/sdk/runtime-action-aliases.js +1 -59
  641. package/dist/sdk/runtime-extended-actions.js +1 -177
  642. package/dist/sdk/runtime-input.js +1 -614
  643. package/dist/sdk/runtime-primitives.js +1 -84
  644. package/dist/sdk/runtime-public-contracts.d.ts +2 -1
  645. package/dist/sdk/runtime-public-contracts.js +1 -4
  646. package/dist/sdk/runtime-stats-options.js +1 -38
  647. package/dist/sdk/runtime.d.ts +4 -0
  648. package/dist/sdk/runtime.js +1 -1928
  649. package/dist/sdk/scheduling-shortcuts.js +1 -63
  650. package/dist/sdk/schema-migration.js +3 -856
  651. package/dist/sdk/schema.d.ts +2 -1
  652. package/dist/sdk/schema.js +1 -1376
  653. package/dist/sdk/search-refresh-worker.js +1 -18
  654. package/dist/sdk/similarity-scoring.js +1 -53
  655. package/dist/sdk/similarity.js +1 -326
  656. package/dist/sdk/start-task-status.js +1 -14
  657. package/dist/sdk/stats.js +1 -270
  658. package/dist/sdk/structured-mutations.js +1 -580
  659. package/dist/sdk/telemetry-flush.js +1 -13
  660. package/dist/sdk/telemetry.js +1 -405
  661. package/dist/sdk/templates.js +2 -292
  662. package/dist/sdk/test/batch.js +2 -509
  663. package/dist/sdk/test/entry.js +1 -70
  664. package/dist/sdk/test/execution.js +6 -2211
  665. package/dist/sdk/test/linked-command-detection.js +1 -546
  666. package/dist/sdk/test/measurements.js +1 -91
  667. package/dist/sdk/test/parsers.js +1 -545
  668. package/dist/sdk/test/runs.js +1 -193
  669. package/dist/sdk/test/trust.js +2 -206
  670. package/dist/sdk/testing.js +4 -1391
  671. package/dist/sdk/traceability/runtime-files-lookup.js +1 -32
  672. package/dist/sdk/traceability/source-traceability.js +1 -292
  673. package/dist/sdk/tracker-root-discovery.js +1 -9
  674. package/dist/sdk/workspace-contracts-cache.js +1 -28
  675. package/dist/sdk/workspace-contracts.js +1 -33
  676. package/dist/sdk/workspace-memory.js +2 -318
  677. package/dist/sdk/workspace-recipe.js +1 -71
  678. package/dist/sdk/workspace-snapshot.js +4 -613
  679. package/dist/sdk/workspace-transaction-gc.js +1 -149
  680. package/dist/sdk/workspace-transaction.js +2 -363
  681. package/dist/sdk/workspace.js +3 -127
  682. package/dist/types/index.js +1 -9
  683. package/dist/types.d.ts +4 -0
  684. package/dist/types.js +1 -245
  685. package/docs/AGENT_GUIDE.md +1 -1
  686. package/docs/COMMANDS.md +10 -0
  687. package/docs/CONFIGURATION.md +12 -1
  688. package/docs/CONTEXT_ALGORITHM_PORTFOLIO.md +167 -0
  689. package/docs/DIAGNOSTIC_OUTPUT_CONTRACTS.md +9 -3
  690. package/docs/EXTENSIONS.md +1 -0
  691. package/docs/HISTORY_MAINTENANCE.md +125 -0
  692. package/docs/MCP_2026_07_28_CONFORMANCE.md +20 -10
  693. package/docs/PACKAGE_EVIDENCE.md +92 -0
  694. package/docs/PLANNING_MEASUREMENTS.md +183 -0
  695. package/docs/README.md +4 -0
  696. package/docs/READ_OUTPUT_CONTRACTS.md +5 -0
  697. package/docs/SCRIPTING.md +21 -0
  698. package/docs/TESTING.md +33 -0
  699. package/docs/WORKFLOW_POLICIES.md +129 -0
  700. package/docs/generated/FLAG_LEXICON_BUDGETS.md +7 -7
  701. package/docs/generated/REFUSAL_CLOSURE_CENSUS.md +11 -5
  702. package/marketplace.json +2 -2
  703. package/package.json +5 -5
  704. package/packages/pm-beads/package.json +1 -1
  705. package/packages/pm-calendar/package.json +1 -1
  706. package/packages/pm-command-kit/package.json +1 -1
  707. package/packages/pm-digital-twin/package.json +1 -1
  708. package/packages/pm-governance-audit/package.json +1 -1
  709. package/packages/pm-guide-shell/package.json +1 -1
  710. package/packages/pm-kanban/package.json +1 -1
  711. package/packages/pm-lifecycle-hooks/package.json +1 -1
  712. package/packages/pm-linked-test-adapters/package.json +1 -1
  713. package/packages/pm-search-advanced/package.json +1 -1
  714. package/packages/pm-templates/package.json +1 -1
  715. package/packages/pm-todos/package.json +1 -1
  716. package/packages/pm-vcs/package.json +1 -1
  717. package/plugins/pm-claude/.claude-plugin/plugin.json +1 -1
  718. package/plugins/pm-codex/.codex-plugin/plugin.json +1 -1
  719. package/scripts/finalize-build.mjs +45 -3
  720. package/dist/cli-bundle/chunks/chunk-AZDFJGH6.js +0 -9
  721. package/dist/cli-bundle/chunks/chunk-CKOL5U7J.js +0 -77
  722. package/dist/cli-bundle/chunks/chunk-JX4DH4WC.js +0 -3
  723. package/dist/cli-bundle/chunks/chunk-OF76PSDQ.js +0 -5
  724. package/dist/cli-bundle/chunks/chunk-QUWFABDM.js +0 -232
  725. package/dist/cli-bundle/chunks/chunk-QWRRFYK3.js +0 -2
  726. package/dist/cli-bundle/chunks/chunk-RWWBOWJ5.js +0 -2
  727. package/dist/cli-bundle/chunks/chunk-WGAOYSYH.js +0 -40
  728. package/dist/cli-bundle/chunks/chunk-WI2AYPA7.js +0 -2
  729. package/dist/cli-bundle/chunks/register-list-query-5JL52D33.js +0 -11
  730. package/dist/cli-bundle/chunks/register-mutation-ZPG7KMVP.js +0 -20
  731. package/dist/cli-bundle/chunks/register-operations-I5W3KMGB.js +0 -2
  732. package/dist/cli-bundle/chunks/register-setup-H4SVAVX7.js +0 -2
  733. package/dist/cli-bundle/focused-chunks/chunk-AL5ON6K6.js +0 -2
  734. package/dist/cli-bundle/focused-chunks/chunk-CHC2IDXA.js +0 -6
  735. package/dist/cli-bundle/focused-chunks/chunk-CK6QFAPZ.js +0 -20
  736. package/dist/cli-bundle/focused-chunks/chunk-EZR3GHFE.js +0 -2
  737. package/dist/cli-bundle/focused-chunks/chunk-H2WJWK7L.js +0 -56
  738. package/dist/cli-bundle/focused-chunks/chunk-NTPY5GSO.js +0 -3
  739. package/dist/cli-bundle/focused-chunks/chunk-P5O7MP5Y.js +0 -2
  740. package/dist/cli-bundle/focused-chunks/chunk-PDMW2E36.js +0 -177
  741. package/dist/cli-bundle/focused-chunks/chunk-PEFABF7E.js +0 -26
  742. package/dist/cli-bundle/focused-chunks/chunk-QTVCLMSZ.js +0 -6
  743. package/dist/cli-bundle/focused-chunks/chunk-QTZRBUYA.js +0 -8
  744. package/dist/cli-bundle/focused-chunks/chunk-SGQST3PQ.js +0 -12
  745. package/dist/cli-bundle/focused-chunks/chunk-UKHASMMP.js +0 -10
  746. package/dist/cli-bundle/focused-chunks/chunk-ZPEKQHOC.js +0 -2
@@ -1,2262 +1,2 @@
1
- /**
2
- * @module sdk/governance/validate
3
- *
4
- * Implements the pm validate 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]="898c4c05-70ab-5ea4-851e-9e224a85afc4")}catch(e){}}();
8
- import { assertInitializedTracker } from "../environment/tracker-preflight.js";
9
- import fs from "node:fs/promises";
10
- import { realpathSync } from "node:fs";
11
- import { execFile } from "node:child_process";
12
- import path from "node:path";
13
- import { promisify } from "node:util";
14
- import { buildRemediationCommands } from "../../core/diagnostics/remediation.js";
15
- import { getActiveExtensionRegistrations } from "../../core/extensions/index.js";
16
- import { collectRegisteredItemFieldNames } from "../../core/extensions/item-fields.js";
17
- import { isFileMissingError } from "../../core/fs/fs-utils.js";
18
- import { normalizeStatusInput } from "../../core/item/status.js";
19
- import { resolveItemTypeRegistry } from "../../core/item/type-registry.js";
20
- import { resolveRuntimeStatusRegistry, } from "../../core/schema/runtime-schema.js";
21
- import { DEFAULT_VALIDATE_CLOSURE_LIKE_METADATA_FIELD_PATTERNS, DEFAULT_VALIDATE_STALE_BLOCKER_REASON_PATTERNS, EXIT_CODE, PM_DIRNAME, SETTINGS_DEFAULTS, } from "../../core/shared/constants.js";
22
- import { PmCliError } from "../../core/shared/errors.js";
23
- import { toNonEmptyStringOrUndefined } from "../../core/shared/primitives.js";
24
- import { nowIso } from "../../core/shared/time.js";
25
- import { CURRENT_ITEM_FORMAT_VERSION, effectiveItemFormatVersion, scanItemFormatVersions, } from "../../core/item/item-format-version.js";
26
- import { resolvePmRoot } from "../../core/store/paths.js";
27
- import { readSettings } from "../../core/store/settings.js";
28
- import { partitionFixesByGrant, planCloseReasonBackfillFixes, planEstimateBackfillFixes, planResolutionBackfillFixes, planStaleLinkPruneFixes, planTerminalParentFixes, resolveGrantedFixScopes, toFixOutputRow, } from "../../core/validate/fix-planning.js";
29
- import { findDuplicateIssueCodes, } from "../../core/governance/issue-codes.js";
30
- import { buildMissingByTypeCounts, } from "../../core/validate/missing-by-type.js";
31
- import { classifyStaleLinkedPaths, summarizeStaleLinkedPathClassifications, } from "../../core/validate/stale-file-classification.js";
32
- import { isRemoteLinkedArtifactReference } from "../../core/validate/linked-artifact-reference.js";
33
- import { assertProjectionModeChoice, } from "../output-projection.js";
34
- import { collectDanglingDependencyReferences } from "../graph/assembly.js";
35
- import { auditMergeAttributeFence, auditMergeDriverConfiguration, findGitWorkspaceRoot, resolveProjectMergeTypeFolders, } from "../merge/install.js";
36
- import { inspectMergeReceiptEvidence, partitionMergeReceipts, } from "../merge/receipts.js";
37
- import { buildLinkedFileRepairReport } from "./linked-file-report.js";
38
- import { scanStorageIntegrity } from "./storage-integrity.js";
39
- import { scanTrackedRuntimeCache } from "./tracked-runtime-cache.js";
40
- import { scanHistoryAuthorAttribution } from "../author-attribution.js";
41
- import { createRelationshipKindRegistry, } from "../relationships.js";
42
- import { runDocs } from "../docs.js";
43
- import { runFiles } from "../files.js";
44
- import { extractReferencedPmItemIdsFromCommand } from "../test/linked-command-detection.js";
45
- import { resolveLinkedTestSourceRef, resolveLinkedTestSourceWorkspaceRoot, resolveLinkedTestTrustBatch, } from "../test/trust.js";
46
- import { readValidateItems, } from "./validate-item-reader.js";
47
- import { buildValidateHistoryDriftCheck } from "./validate-history-drift.js";
48
- import { buildLifecycleParentGraph, detectLifecycleParentCycles, resolveLifecycleDependencyCycleSamplePath, } from "./hierarchy-validation.js";
49
- import { buildStatusRoleWarnings, inspectStatusRoleAssignments, } from "./status-role-diagnostics.js";
50
- import { normalizeRelativeDirectoryPath, normalizeRelativePath, toMeaningfulString, } from "./validate-normalization.js";
51
- const FILE_SCAN_DIRECTORIES = ["src", "tests", "docs"];
52
- const FILE_SCAN_ROOT_FILES = [
53
- "README.md",
54
- "PRD.md",
55
- "CHANGELOG.md",
56
- "AGENTS.md",
57
- "CONTRIBUTING.md",
58
- "SECURITY.md",
59
- "CODE_OF_CONDUCT.md",
60
- "LICENSE",
61
- ];
62
- const DIRECTORY_IGNORE_SET = new Set([
63
- "node_modules",
64
- ".git",
65
- ".cursor",
66
- ".agents",
67
- "dist",
68
- "coverage",
69
- ]);
70
- const RESOLUTION_FIELD_KEYS = [
71
- "resolution",
72
- "expected_result",
73
- "actual_result",
74
- ];
75
- const VALIDATE_FILE_SCAN_MODES = [
76
- "default",
77
- "tracked-all",
78
- "tracked-all-strict",
79
- ];
80
- const VALIDATE_METADATA_PROFILE_VALUES = ["core", "strict", "custom"];
81
- const VALIDATE_DEPENDENCY_CYCLE_SEVERITY_VALUES = [
82
- "off",
83
- "warn",
84
- "error",
85
- ];
86
- const CORE_METADATA_REQUIRED_FIELDS = [
87
- "author",
88
- "acceptance_criteria",
89
- "estimated_minutes",
90
- "close_reason",
91
- ];
92
- const STRICT_METADATA_REQUIRED_FIELDS = [
93
- ...CORE_METADATA_REQUIRED_FIELDS,
94
- "reviewer",
95
- "risk",
96
- "confidence",
97
- "sprint",
98
- "release",
99
- ];
100
- // Keep this deduplicated so future list edits cannot double-count metadata diagnostics.
101
- const SUPPORTED_METADATA_REQUIRED_FIELDS = Array.from(new Set(STRICT_METADATA_REQUIRED_FIELDS));
102
- const METADATA_REQUIRED_FIELD_ALIASES = {
103
- author: "author",
104
- acceptance_criteria: "acceptance_criteria",
105
- "acceptance-criteria": "acceptance_criteria",
106
- estimated_minutes: "estimated_minutes",
107
- "estimated-minutes": "estimated_minutes",
108
- estimate: "estimated_minutes",
109
- close_reason: "close_reason",
110
- "close-reason": "close_reason",
111
- reviewer: "reviewer",
112
- risk: "risk",
113
- confidence: "confidence",
114
- sprint: "sprint",
115
- release: "release",
116
- };
117
- const METADATA_WARNING_TOKEN_BY_FIELD = {
118
- author: "validate_metadata_missing_author",
119
- acceptance_criteria: "validate_metadata_missing_acceptance_criteria",
120
- estimated_minutes: "validate_metadata_missing_estimate",
121
- close_reason: "validate_metadata_missing_close_reason",
122
- reviewer: "validate_metadata_missing_reviewer",
123
- risk: "validate_metadata_missing_risk",
124
- confidence: "validate_metadata_missing_confidence",
125
- sprint: "validate_metadata_missing_sprint",
126
- release: "validate_metadata_missing_release",
127
- };
128
- const METADATA_COUNT_KEY_BY_FIELD = {
129
- author: "missing_author",
130
- acceptance_criteria: "missing_acceptance_criteria",
131
- estimated_minutes: "missing_estimated_minutes",
132
- close_reason: "closed_missing_close_reason",
133
- reviewer: "missing_reviewer",
134
- risk: "missing_risk",
135
- confidence: "missing_confidence",
136
- sprint: "missing_sprint",
137
- release: "missing_release",
138
- };
139
- const METADATA_ITEM_IDS_KEY_BY_FIELD = {
140
- author: "missing_author_item_ids",
141
- acceptance_criteria: "missing_acceptance_criteria_item_ids",
142
- estimated_minutes: "missing_estimated_minutes_item_ids",
143
- close_reason: "closed_missing_close_reason_item_ids",
144
- reviewer: "missing_reviewer_item_ids",
145
- risk: "missing_risk_item_ids",
146
- confidence: "missing_confidence_item_ids",
147
- sprint: "missing_sprint_item_ids",
148
- release: "missing_release_item_ids",
149
- };
150
- const METADATA_TRUNCATED_KEY_BY_FIELD = {
151
- author: "missing_author_truncated",
152
- acceptance_criteria: "missing_acceptance_criteria_truncated",
153
- estimated_minutes: "missing_estimated_minutes_truncated",
154
- close_reason: "closed_missing_close_reason_truncated",
155
- reviewer: "missing_reviewer_truncated",
156
- risk: "missing_risk_truncated",
157
- confidence: "missing_confidence_truncated",
158
- sprint: "missing_sprint_truncated",
159
- release: "missing_release_truncated",
160
- };
161
- const GIT_LS_FILES_MAX_BUFFER = 32 * 1024 * 1024;
162
- const GIT_LS_FILES_TIMEOUT_MS = 10_000;
163
- const FILE_LIST_SUMMARY_LIMIT = 40;
164
- const DIAGNOSTIC_LIST_SUMMARY_LIMIT = 5;
165
- // Conservative pre-stat guard for common filesystem limits; over-limit links are unreadable, not prune-safe.
166
- const LINKED_ARTIFACT_MAX_PATH_LENGTH = 4096;
167
- const LINKED_ARTIFACT_MAX_SEGMENT_LENGTH = 255;
168
- const execFileAsync = promisify(execFile);
169
- /** Recursively omit diagnostic arrays and false truncation markers while preserving zero counts and ordinary false values. */
170
- function projectValidateCountsRecord(record) {
171
- const projected = {};
172
- for (const [key, value] of Object.entries(record)) {
173
- if (Array.isArray(value) || (value === false && key.endsWith("_truncated"))) {
174
- continue;
175
- }
176
- projected[key] =
177
- typeof value === "object" && value !== null
178
- ? projectValidateCountsRecord(value)
179
- : value;
180
- }
181
- return projected;
182
- }
183
- /**
184
- * Build the counts-only validate projection without mutating the full result.
185
- * The check envelope and warning codes remain available for gates, while every
186
- * per-item diagnostic/fix array is removed recursively and scalar counts,
187
- * totals, statuses, and true truncation markers are preserved. False truncation
188
- * markers are omitted; this sparse convention does not suppress other false or
189
- * zero values, and the projection receipt still declares omitted diagnostic rows.
190
- */
191
- export function projectValidateCounts(result) {
192
- return {
193
- ...result,
194
- checks: result.checks.map((check) => ({
195
- ...check,
196
- details: projectValidateCountsRecord(check.details),
197
- })),
198
- ...(result.fixes
199
- ? {
200
- fixes: {
201
- mode: result.fixes.mode,
202
- auto_fix: result.fixes.auto_fix,
203
- prune_missing: result.fixes.prune_missing,
204
- planned_count: result.fixes.planned_count,
205
- applied_count: result.fixes.applied_count,
206
- gated_count: result.fixes.gated_count,
207
- failed_count: result.fixes.failed_count,
208
- },
209
- }
210
- : {}),
211
- };
212
- }
213
- /* c8 ignore start -- runtime-status alias normalization is covered by status-registry integration tests */
214
- function normalizeStatusForRegistry(status, statusRegistry) {
215
- return normalizeStatusInput(status, statusRegistry) ?? status;
216
- }
217
- /* c8 ignore stop */
218
- function isTerminalStatus(status, statusRegistry) {
219
- return statusRegistry.terminal_statuses.has(normalizeStatusForRegistry(status, statusRegistry));
220
- }
221
- /* c8 ignore start -- lifecycle pattern normalization/default-vs-settings matrix is covered by lifecycle integration tests */
222
- function normalizeLifecyclePatternList(values) {
223
- return [
224
- ...new Set((values ?? [])
225
- .map((value) => value.trim().toLowerCase())
226
- .filter((value) => value.length > 0)),
227
- ].sort((left, right) => left.localeCompare(right));
228
- }
229
- function areSortedStringListsEqual(left, right) {
230
- if (left.length !== right.length) {
231
- return false;
232
- }
233
- return left.every((value, index) => value === right[index]);
234
- }
235
- function resolveLifecyclePatternPolicy(settings) {
236
- const defaultStalePatterns = normalizeLifecyclePatternList(DEFAULT_VALIDATE_STALE_BLOCKER_REASON_PATTERNS);
237
- const defaultClosureLikePatterns = {
238
- blocked_reason: normalizeLifecyclePatternList(DEFAULT_VALIDATE_CLOSURE_LIKE_METADATA_FIELD_PATTERNS.blocked_reason),
239
- resolution: normalizeLifecyclePatternList(DEFAULT_VALIDATE_CLOSURE_LIKE_METADATA_FIELD_PATTERNS.resolution),
240
- actual_result: normalizeLifecyclePatternList(DEFAULT_VALIDATE_CLOSURE_LIKE_METADATA_FIELD_PATTERNS.actual_result),
241
- };
242
- const staleBlockerReasonPatterns = normalizeLifecyclePatternList(settings.validation.lifecycle_stale_blocker_reason_patterns);
243
- const closureLikePatterns = {
244
- blocked_reason: normalizeLifecyclePatternList(settings.validation.lifecycle_closure_like_blocked_reason_patterns),
245
- resolution: normalizeLifecyclePatternList(settings.validation.lifecycle_closure_like_resolution_patterns),
246
- actual_result: normalizeLifecyclePatternList(settings.validation.lifecycle_closure_like_actual_result_patterns),
247
- };
248
- return {
249
- stale_blocker_reason_patterns: staleBlockerReasonPatterns,
250
- stale_blocker_reason_pattern_source: areSortedStringListsEqual(staleBlockerReasonPatterns, defaultStalePatterns)
251
- ? "default"
252
- : "settings",
253
- closure_like_metadata_field_patterns: closureLikePatterns,
254
- closure_like_metadata_field_pattern_sources: {
255
- blocked_reason: areSortedStringListsEqual(closureLikePatterns.blocked_reason, defaultClosureLikePatterns.blocked_reason)
256
- ? "default"
257
- : "settings",
258
- resolution: areSortedStringListsEqual(closureLikePatterns.resolution, defaultClosureLikePatterns.resolution)
259
- ? "default"
260
- : "settings",
261
- actual_result: areSortedStringListsEqual(closureLikePatterns.actual_result, defaultClosureLikePatterns.actual_result)
262
- ? "default"
263
- : "settings",
264
- },
265
- };
266
- }
267
- /* c8 ignore stop */
268
- function resolveValidateMetadataProfile(value) {
269
- const normalized = value?.trim().toLowerCase();
270
- if (!normalized || normalized.length === 0) {
271
- return "core";
272
- }
273
- if (VALIDATE_METADATA_PROFILE_VALUES.includes(normalized)) {
274
- return normalized;
275
- }
276
- throw new PmCliError(`Unknown --metadata-profile value "${value}". Supported values: ${VALIDATE_METADATA_PROFILE_VALUES.join(", ")}.`, EXIT_CODE.USAGE);
277
- }
278
- function resolveDependencyCycleSeverity(value) {
279
- const normalized = value?.trim().toLowerCase();
280
- if (!normalized || normalized.length === 0) {
281
- return "warn";
282
- }
283
- if (VALIDATE_DEPENDENCY_CYCLE_SEVERITY_VALUES.includes(normalized)) {
284
- return normalized;
285
- }
286
- throw new PmCliError(`Unknown --dependency-cycle-severity value "${value}". Supported values: ${VALIDATE_DEPENDENCY_CYCLE_SEVERITY_VALUES.join(", ")}.`, EXIT_CODE.USAGE);
287
- }
288
- function resolveParentCycleSeverity(value) {
289
- const normalized = value?.trim().toLowerCase();
290
- if (!normalized || normalized.length === 0) {
291
- return "warn";
292
- }
293
- if (VALIDATE_DEPENDENCY_CYCLE_SEVERITY_VALUES.includes(normalized)) {
294
- return normalized;
295
- }
296
- throw new PmCliError(`Unknown --parent-cycle-severity value "${value}". Supported values: ${VALIDATE_DEPENDENCY_CYCLE_SEVERITY_VALUES.join(", ")}.`, EXIT_CODE.USAGE);
297
- }
298
- /* c8 ignore start -- metadata required-field alias normalization is covered by metadata-policy integration tests */
299
- function normalizeMetadataRequiredFieldsFromSettings(values) {
300
- const normalized = [
301
- ...new Set((values ?? []).map((value) => value.trim().toLowerCase().replaceAll("-", "_"))),
302
- ];
303
- return normalized
304
- .map((value) => METADATA_REQUIRED_FIELD_ALIASES[value])
305
- .filter((value) => value !== undefined)
306
- .sort((left, right) => left.localeCompare(right));
307
- }
308
- /* c8 ignore stop */
309
- function resolveValidateMetadataPolicy(profile, profileSource, configuredCustomFields) {
310
- const normalizedCustomFields = normalizeMetadataRequiredFieldsFromSettings(configuredCustomFields);
311
- if (profile === "core") {
312
- return {
313
- profile,
314
- profile_source: profileSource,
315
- required_fields: [...CORE_METADATA_REQUIRED_FIELDS],
316
- configured_custom_fields: normalizedCustomFields,
317
- fallback_to_core: false,
318
- warnings: [],
319
- };
320
- }
321
- if (profile === "strict") {
322
- return {
323
- profile,
324
- profile_source: profileSource,
325
- required_fields: [...STRICT_METADATA_REQUIRED_FIELDS],
326
- configured_custom_fields: normalizedCustomFields,
327
- fallback_to_core: false,
328
- warnings: [],
329
- };
330
- }
331
- if (normalizedCustomFields.length > 0) {
332
- return {
333
- profile,
334
- profile_source: profileSource,
335
- required_fields: normalizedCustomFields,
336
- configured_custom_fields: normalizedCustomFields,
337
- fallback_to_core: false,
338
- warnings: [],
339
- };
340
- }
341
- return {
342
- profile,
343
- profile_source: profileSource,
344
- required_fields: [...CORE_METADATA_REQUIRED_FIELDS],
345
- configured_custom_fields: normalizedCustomFields,
346
- fallback_to_core: true,
347
- warnings: ["validate_metadata_custom_profile_missing_required_fields:0"],
348
- };
349
- }
350
- /** Planning fields whose absence is only actionable on live work (GH-276): an agent backfills an estimate or acceptance criteria to plan/execute an item, so flagging them on a terminal (closed/canceled) historical item is pure noise. Under the `strict` profile these are still enforced everywhere for projects that want full historical coverage. */
351
- const TERMINAL_EXEMPT_PLANNING_FIELDS = new Set(["acceptance_criteria", "estimated_minutes"]);
352
- function isMetadataFieldMissing(item, field, statusRegistry, enforcePlanningFieldsOnTerminal) {
353
- // GH-276: skip planning-field gaps on retired (closed/canceled) items unless
354
- // the resolved profile explicitly demands strict historical coverage.
355
- if (!enforcePlanningFieldsOnTerminal &&
356
- TERMINAL_EXEMPT_PLANNING_FIELDS.has(field) &&
357
- isTerminalStatus(item.status, statusRegistry)) {
358
- return false;
359
- }
360
- if (field === "author") {
361
- return !toNonEmptyStringOrUndefined(item.author);
362
- }
363
- if (field === "acceptance_criteria") {
364
- return !toNonEmptyStringOrUndefined(item.acceptance_criteria);
365
- }
366
- if (field === "estimated_minutes") {
367
- return !Number.isFinite(item.estimated_minutes);
368
- }
369
- if (field === "close_reason") {
370
- return (normalizeStatusForRegistry(item.status, statusRegistry) ===
371
- statusRegistry.close_status &&
372
- !toNonEmptyStringOrUndefined(item.close_reason));
373
- }
374
- if (field === "reviewer") {
375
- return !toNonEmptyStringOrUndefined(item.reviewer);
376
- }
377
- if (field === "risk") {
378
- return !toNonEmptyStringOrUndefined(item.risk);
379
- }
380
- if (field === "confidence") {
381
- if (typeof item.confidence === "number") {
382
- return !Number.isFinite(item.confidence);
383
- }
384
- return !toNonEmptyStringOrUndefined(item.confidence);
385
- }
386
- if (field === "sprint") {
387
- return !toNonEmptyStringOrUndefined(item.sprint);
388
- }
389
- return !toNonEmptyStringOrUndefined(item.release);
390
- }
391
- function resolveFileScanMode(scanMode) {
392
- if (scanMode === undefined) {
393
- return "default";
394
- }
395
- const normalized = scanMode.trim().toLowerCase();
396
- if (normalized.length === 0) {
397
- return "default";
398
- }
399
- if (normalized === "default") {
400
- return "default";
401
- }
402
- if (normalized === "tracked-all" || normalized === "tracked_all") {
403
- return "tracked-all";
404
- }
405
- if (normalized === "tracked-all-strict" ||
406
- normalized === "tracked_all_strict") {
407
- return "tracked-all-strict";
408
- }
409
- throw new PmCliError(`Unknown --scan-mode value "${scanMode}". Supported values: ${VALIDATE_FILE_SCAN_MODES.join(", ")}.`, EXIT_CODE.USAGE);
410
- }
411
- function resolveWorkspaceRoot(pmRoot) {
412
- const sourceWorkspaceRoot = process.env.PM_SOURCE_WORKSPACE_ROOT?.trim();
413
- if (sourceWorkspaceRoot) {
414
- return path.resolve(sourceWorkspaceRoot);
415
- }
416
- const resolvedPmRoot = path.resolve(pmRoot);
417
- const normalizedPmRoot = resolvedPmRoot.replaceAll("\\", "/");
418
- if (normalizedPmRoot.endsWith("/.agents/pm")) {
419
- return path.dirname(path.dirname(resolvedPmRoot));
420
- }
421
- const resolvedCwd = path.resolve(process.cwd());
422
- const canonicalPmRoot = realpathForWorkspaceRoot(resolvedPmRoot);
423
- const canonicalCwd = realpathForWorkspaceRoot(resolvedCwd);
424
- const relativeFromPmRoot = path.relative(canonicalPmRoot, canonicalCwd);
425
- const cwdInsidePmRoot = relativeFromPmRoot.length === 0 ||
426
- (!relativeFromPmRoot.startsWith("..") &&
427
- !path.isAbsolute(relativeFromPmRoot));
428
- if (cwdInsidePmRoot) {
429
- return resolvedPmRoot;
430
- }
431
- /* c8 ignore next 2 -- non-standard PM root layouts are integration-only edge cases. */
432
- return resolvedCwd;
433
- }
434
- function realpathForWorkspaceRoot(inputPath) {
435
- try {
436
- return realpathSync.native(inputPath);
437
- }
438
- catch {
439
- return path.resolve(inputPath);
440
- }
441
- }
442
- function hasFilesystemErrorCode(error, acceptedCodes) {
443
- return (typeof error === "object" &&
444
- error !== null &&
445
- "code" in error &&
446
- typeof error.code === "string" &&
447
- acceptedCodes.includes(error.code));
448
- }
449
- /* c8 ignore start -- recursive file-walk dirent/permission edge cases are covered by filesystem integration suites */
450
- async function listFilesRecursive(basePath, relativePath, output) {
451
- const targetDirectory = relativePath.length > 0 ? path.join(basePath, relativePath) : basePath;
452
- let entries;
453
- try {
454
- entries = await fs.readdir(targetDirectory, { withFileTypes: true });
455
- }
456
- catch (error) {
457
- if (isFileMissingError(error)) {
458
- return;
459
- }
460
- throw error;
461
- }
462
- for (const entry of entries) {
463
- if (entry.name.startsWith(".")) {
464
- continue;
465
- }
466
- const childRelative = relativePath.length > 0
467
- ? path.join(relativePath, entry.name)
468
- : entry.name;
469
- if (entry.isDirectory()) {
470
- if (DIRECTORY_IGNORE_SET.has(entry.name)) {
471
- continue;
472
- }
473
- await listFilesRecursive(basePath, childRelative, output);
474
- continue;
475
- }
476
- /* c8 ignore next 3 -- non-file dirent variants (symlink/socket) are filesystem-specific. */
477
- if (!entry.isFile()) {
478
- continue;
479
- }
480
- output.push(normalizeRelativePath(childRelative));
481
- }
482
- }
483
- /* c8 ignore stop */
484
- async function collectDefaultProjectFileCandidates(workspaceRoot) {
485
- const discovered = [];
486
- for (const directory of FILE_SCAN_DIRECTORIES) {
487
- await listFilesRecursive(workspaceRoot, directory, discovered);
488
- }
489
- for (const candidate of FILE_SCAN_ROOT_FILES) {
490
- const absolute = path.join(workspaceRoot, candidate);
491
- try {
492
- const stats = await fs.stat(absolute);
493
- /* c8 ignore start -- root-candidate presence depends on fixture workspace composition. */
494
- if (stats.isFile()) {
495
- discovered.push(normalizeRelativePath(candidate));
496
- }
497
- /* c8 ignore stop */
498
- }
499
- catch {
500
- // Ignore root-file candidates that are not present in this workspace.
501
- }
502
- }
503
- return [...new Set(discovered)].sort((left, right) => left.localeCompare(right));
504
- }
505
- async function collectTrackedGitFileCandidates(workspaceRoot) {
506
- try {
507
- const { stdout } = await execFileAsync("git", ["ls-files", "-z"], {
508
- cwd: workspaceRoot,
509
- encoding: "utf8",
510
- maxBuffer: GIT_LS_FILES_MAX_BUFFER,
511
- windowsHide: true,
512
- timeout: GIT_LS_FILES_TIMEOUT_MS,
513
- });
514
- const discovered = stdout
515
- .split("\0")
516
- .map((value) => normalizeRelativePath(value))
517
- .filter((value) => value.length > 0);
518
- return [...new Set(discovered)].sort((left, right) => left.localeCompare(right));
519
- }
520
- catch {
521
- /* c8 ignore start -- fallback path exercised only when git metadata is unavailable. */
522
- return null;
523
- /* c8 ignore stop */
524
- }
525
- }
526
- /* c8 ignore start -- PM-internal prefix derivation permutations are covered by file-scan integration tests */
527
- function resolvePmInternalCandidatePrefixes(pmRoot, workspaceRoot) {
528
- const prefixes = new Set();
529
- const configuredDefault = normalizeRelativeDirectoryPath(PM_DIRNAME);
530
- if (configuredDefault.length > 0) {
531
- prefixes.add(configuredDefault);
532
- }
533
- const relativePmRoot = normalizeRelativeDirectoryPath(path.relative(workspaceRoot, pmRoot));
534
- if (relativePmRoot.length > 0 && !relativePmRoot.startsWith("..")) {
535
- prefixes.add(relativePmRoot);
536
- }
537
- return [...prefixes].sort((left, right) => left.localeCompare(right));
538
- }
539
- /* c8 ignore stop */
540
- function hasPathPrefix(candidate, prefixes) {
541
- for (const prefix of prefixes) {
542
- if (candidate === prefix || candidate.startsWith(`${prefix}/`)) {
543
- return true;
544
- }
545
- }
546
- return false;
547
- }
548
- async function collectProjectFileCandidates(workspaceRoot, scanMode) {
549
- if (scanMode === "tracked-all" || scanMode === "tracked-all-strict") {
550
- const trackedCandidates = await collectTrackedGitFileCandidates(workspaceRoot);
551
- /* c8 ignore start -- tracked-git availability fallback is covered by git/non-git integration fixtures */
552
- if (trackedCandidates) {
553
- return {
554
- requestedMode: scanMode,
555
- appliedMode: scanMode,
556
- source: "tracked-git",
557
- candidateFiles: trackedCandidates,
558
- candidateTotal: trackedCandidates.length,
559
- candidateScanned: trackedCandidates.length,
560
- };
561
- }
562
- /* c8 ignore stop */
563
- /* c8 ignore start -- deterministic fallback retained for non-git workspaces. */
564
- const fallbackCandidates = await collectDefaultProjectFileCandidates(workspaceRoot);
565
- return {
566
- requestedMode: scanMode,
567
- appliedMode: "default",
568
- source: "tracked-all-fallback-default",
569
- candidateFiles: fallbackCandidates,
570
- candidateTotal: fallbackCandidates.length,
571
- candidateScanned: fallbackCandidates.length,
572
- };
573
- /* c8 ignore stop */
574
- }
575
- const defaultCandidates = await collectDefaultProjectFileCandidates(workspaceRoot);
576
- return {
577
- requestedMode: scanMode,
578
- appliedMode: "default",
579
- source: "default-curated",
580
- candidateFiles: defaultCandidates,
581
- candidateTotal: defaultCandidates.length,
582
- candidateScanned: defaultCandidates.length,
583
- };
584
- }
585
- function summarizeList(values, limit = DIAGNOSTIC_LIST_SUMMARY_LIMIT) {
586
- /* c8 ignore start -- truncation behavior only surfaces with very large synthetic datasets. */
587
- if (values.length <= limit) {
588
- return { values, truncated: false };
589
- }
590
- return {
591
- values: values.slice(0, limit),
592
- truncated: true,
593
- };
594
- /* c8 ignore stop */
595
- }
596
- function summarizeFileList(values, verboseFileLists) {
597
- if (verboseFileLists) {
598
- return {
599
- values,
600
- truncated: false,
601
- total: values.length,
602
- };
603
- }
604
- const summary = summarizeList(values, FILE_LIST_SUMMARY_LIMIT);
605
- return {
606
- values: summary.values,
607
- truncated: summary.truncated,
608
- total: values.length,
609
- };
610
- }
611
- const RESOLUTION_REMEDIATION_FLAG_BY_FIELD = {
612
- resolution: "--resolution",
613
- expected_result: "--expected-result",
614
- actual_result: "--actual-result",
615
- };
616
- const RESOLUTION_REMEDIATION_PLACEHOLDER_BY_FIELD = {
617
- resolution: "Describe how this item was resolved",
618
- expected_result: "Describe the expected result",
619
- actual_result: "Describe the actual result",
620
- };
621
- function buildResolutionRemediationCommand(row) {
622
- const fieldArguments = row.missing_fields
623
- .map((field) => `${RESOLUTION_REMEDIATION_FLAG_BY_FIELD[field]} "${RESOLUTION_REMEDIATION_PLACEHOLDER_BY_FIELD[field]}"`)
624
- .join(" ");
625
- return `pm update ${row.id} ${fieldArguments} --message "Backfill resolution metadata"`;
626
- }
627
- /** Attach a uniform, machine-executable `fix_hints` array to a validate check's details when `--fix-hints` is requested. The resolution check's existing per-row remediation commands (which already carry concrete item ids) are aliased in so agents read one uniform field across every check; all other checks derive one generic command per distinct warning code from the shared remediation registry. Generic hints may contain `<id>`/`<field>`/`<path>` placeholders the caller substitutes before running — they are templates, not always directly executable as-is. Read-only: this only enriches the diagnostic output, never mutates any item. */
628
- /* c8 ignore start -- fix-hint projection/truncation combinations are covered by validate output integration tests */
629
- function attachValidateFixHints(check, checkWarnings) {
630
- const existingResolutionHints = check.details?.missing_resolution_remediation_hints;
631
- const aliasedResolution = Array.isArray(existingResolutionHints) &&
632
- existingResolutionHints.length > 0;
633
- const fixHints = aliasedResolution
634
- ? existingResolutionHints.filter((hint) => typeof hint === "string")
635
- : buildRemediationCommands(checkWarnings);
636
- if (fixHints.length === 0) {
637
- return;
638
- }
639
- // The resolution check truncates its per-row hint list for low-token output;
640
- // carry that marker onto fix_hints so an agent knows the list is partial and
641
- // there are more items to repair beyond the ones shown.
642
- const truncated = aliasedResolution &&
643
- check.details?.missing_resolution_remediation_hints_truncated === true;
644
- check.details = {
645
- ...check.details,
646
- fix_hints: fixHints,
647
- ...(truncated ? { fix_hints_truncated: true } : {}),
648
- };
649
- }
650
- /* c8 ignore stop */
651
- function resolveRequestedChecks(options) {
652
- const requested = new Set();
653
- if (options.checkMetadata) {
654
- requested.add("metadata");
655
- }
656
- if (options.checkResolution) {
657
- requested.add("resolution");
658
- }
659
- if (options.checkLifecycle || options.checkStaleBlockers) {
660
- requested.add("lifecycle");
661
- }
662
- if (options.checkFiles) {
663
- requested.add("files");
664
- }
665
- if (options.checkHistoryDrift) {
666
- requested.add("history_drift");
667
- }
668
- if (options.checkCommandReferences) {
669
- requested.add("command_references");
670
- }
671
- if (options.checkStorageIntegrity) {
672
- requested.add("storage_integrity");
673
- }
674
- if (requested.size === 0) {
675
- // Remediation flags without explicit --check-* flags scope the run to the
676
- // checks that can produce fixes: --auto-fix plans metadata/resolution
677
- // backfills and gated lifecycle fixes; --prune-missing needs the files
678
- // scan. With neither, the historical run-everything default applies.
679
- if (options.autoFix) {
680
- requested.add("metadata");
681
- requested.add("resolution");
682
- requested.add("lifecycle");
683
- }
684
- if (options.pruneMissing) {
685
- requested.add("files");
686
- }
687
- if (requested.size === 0) {
688
- requested.add("metadata");
689
- requested.add("resolution");
690
- requested.add("lifecycle");
691
- requested.add("files");
692
- requested.add("command_references");
693
- requested.add("history_drift");
694
- requested.add("format_version");
695
- requested.add("storage_integrity");
696
- }
697
- return requested;
698
- }
699
- // Explicit check flags are respected as-is, except --prune-missing always
700
- // needs the files scan it acts on.
701
- if (options.pruneMissing) {
702
- requested.add("files");
703
- }
704
- return requested;
705
- }
706
- const DUPLICATE_ISSUE_CODE_WARNING_TOKEN = "validate_metadata_duplicate_issue_codes";
707
- /** Project the duplicate logical issue-code findings (GH-235) into the metadata-check `details` shape plus an advisory warning token. Duplicate codes are advisory (warn), never an error — matching every other metadata finding — so an otherwise clean tracker that simply reuses a title prefix is not failed by `pm validate`. Kept outside the c8-ignored builder block so the projection/remediation logic is fully covered by unit tests. */
708
- function summarizeDuplicateIssueCodes(duplicates, verboseDiagnostics) {
709
- if (duplicates.length === 0) {
710
- return { rows: [], truncated: false, warnings: [] };
711
- }
712
- const limit = verboseDiagnostics
713
- ? duplicates.length
714
- : DIAGNOSTIC_LIST_SUMMARY_LIMIT;
715
- const shown = duplicates.slice(0, limit);
716
- const rows = shown.map((duplicate) => ({
717
- code: duplicate.code,
718
- count: duplicate.count,
719
- ids: duplicate.ids,
720
- titles: duplicate.titles,
721
- remediation_hint: `Items ${duplicate.ids.join(", ")} share issue code "${duplicate.code}"; rename or merge so each logical code maps to one item.`,
722
- }));
723
- return {
724
- rows,
725
- truncated: shown.length < duplicates.length,
726
- warnings: [`${DUPLICATE_ISSUE_CODE_WARNING_TOKEN}:${duplicates.length}`],
727
- };
728
- }
729
- function initializeMissingMetadataByField() {
730
- return Object.fromEntries(SUPPORTED_METADATA_REQUIRED_FIELDS.map((field) => [field, []]));
731
- }
732
- function collectMissingMetadataByField(items, statusRegistry, enforcePlanningFieldsOnTerminal) {
733
- const missingByField = initializeMissingMetadataByField();
734
- for (const item of items) {
735
- for (const field of SUPPORTED_METADATA_REQUIRED_FIELDS) {
736
- if (isMetadataFieldMissing(item, field, statusRegistry, enforcePlanningFieldsOnTerminal)) {
737
- missingByField[field].push(item.id);
738
- }
739
- }
740
- }
741
- return missingByField;
742
- }
743
- function buildMetadataWarningTokens(metadataPolicy, missingByField, duplicateWarnings) {
744
- const warningTokens = [...metadataPolicy.warnings];
745
- for (const field of metadataPolicy.required_fields) {
746
- const missingItems = missingByField[field];
747
- if (missingItems.length > 0) {
748
- warningTokens.push(`${METADATA_WARNING_TOKEN_BY_FIELD[field]}:${missingItems.length}`);
749
- }
750
- }
751
- warningTokens.push(...duplicateWarnings);
752
- return warningTokens;
753
- }
754
- function buildMetadataCounts(metadataPolicy, missingByField) {
755
- const counts = {};
756
- for (const field of metadataPolicy.required_fields) {
757
- const missing = missingByField[field];
758
- const value = missing ? missing.length : 0;
759
- const countKey = METADATA_COUNT_KEY_BY_FIELD[field];
760
- counts[countKey] = value;
761
- }
762
- return counts;
763
- }
764
- function buildMissingFieldOccurrences(metadataPolicy, missingByField, itemsById) {
765
- const occurrences = [];
766
- for (const field of metadataPolicy.required_fields) {
767
- const missing = missingByField[field];
768
- if (!missing) {
769
- continue;
770
- }
771
- for (const itemId of missing) {
772
- const itemType = itemsById.get(itemId)?.type;
773
- const normalizedItemType = typeof itemType === "string" && itemType.length > 0
774
- ? itemType
775
- : "Unknown";
776
- occurrences.push({ item_type: normalizedItemType, field });
777
- }
778
- }
779
- return occurrences;
780
- }
781
- /* v8 ignore start -- metadata policy keys are validated before this summarizer; fallback branches are defensive */
782
- function attachMissingMetadataItemIds(details, metadataPolicy, missingByField, verboseDiagnostics) {
783
- for (const field of metadataPolicy.required_fields) {
784
- const missing = missingByField[field];
785
- if (!missing || missing.length === 0) {
786
- continue;
787
- }
788
- const idsKey = METADATA_ITEM_IDS_KEY_BY_FIELD[field];
789
- const truncatedKey = METADATA_TRUNCATED_KEY_BY_FIELD[field];
790
- if (!idsKey || !truncatedKey) {
791
- continue;
792
- }
793
- const summarized = summarizeList(missing, verboseDiagnostics ? missing.length : DIAGNOSTIC_LIST_SUMMARY_LIMIT);
794
- details[idsKey] = summarized.values;
795
- details[truncatedKey] = summarized.truncated;
796
- }
797
- }
798
- /* v8 ignore stop */
799
- function buildCloseReasonBackfillRows(metadataPolicy, missingByField, itemsById) {
800
- if (!metadataPolicy.required_fields.includes("close_reason")) {
801
- return [];
802
- }
803
- const missing = missingByField.close_reason;
804
- if (!missing) {
805
- return [];
806
- }
807
- return missing.map((itemId) => ({
808
- id: itemId,
809
- resolution: toNonEmptyStringOrUndefined(itemsById.get(itemId)?.resolution),
810
- }));
811
- }
812
- function buildEstimateBackfillRows(metadataPolicy, missingByField, itemsById) {
813
- if (!metadataPolicy.required_fields.includes("estimated_minutes")) {
814
- return [];
815
- }
816
- const missing = missingByField.estimated_minutes;
817
- if (!missing) {
818
- return [];
819
- }
820
- return missing.map((itemId) => ({
821
- id: itemId,
822
- type: toNonEmptyStringOrUndefined(itemsById.get(itemId)?.type),
823
- }));
824
- }
825
- /* c8 ignore start -- metadata diagnostics/backfill planning permutations are covered by validate integration suites */
826
- function buildMetadataCheck(items, metadataPolicy, statusRegistry, verboseDiagnostics, configuredIssueCodePrefixes) {
827
- const itemsById = new Map(items.map((item) => [item.id, item]));
828
- // GH-276: only the `strict` profile enforces planning fields on terminal
829
- // (closed/canceled) historical items; core/minimal/custom profiles treat a
830
- // retired item's missing estimate or acceptance criteria as resolved.
831
- const enforcePlanningFieldsOnTerminal = metadataPolicy.profile === "strict";
832
- const missingByField = collectMissingMetadataByField(items, statusRegistry, enforcePlanningFieldsOnTerminal);
833
- // Duplicate logical issue-code detection (GH-235): advisory warning when two
834
- // or more items share a leading title issue code (e.g. `ISSUE-004`).
835
- const duplicateIssueCodes = findDuplicateIssueCodes(items, {
836
- configuredPrefixes: configuredIssueCodePrefixes,
837
- });
838
- const duplicateIssueCodeSummary = summarizeDuplicateIssueCodes(duplicateIssueCodes, verboseDiagnostics);
839
- const warningTokens = buildMetadataWarningTokens(metadataPolicy, missingByField, duplicateIssueCodeSummary.warnings);
840
- // Emit an explicit numeric predicate for every ACTIVE required field. Stable
841
- // zeroes let assurance consumers distinguish a passing metric from an absent
842
- // contract key without exposing any per-item diagnostic rows.
843
- // A looser profile (e.g. core) still never reports reviewer/risk/sprint/etc.
844
- // Defensive guards (Gemini high #1, PR #78 follow-up): a future settings
845
- // shape could include an unsupported field in required_fields — fall back
846
- // to 0 instead of throwing TypeError, and skip writing when the count-key
847
- // mapping is undefined.
848
- const counts = buildMetadataCounts(metadataPolicy, missingByField);
849
- // Per-item-type grouping of missing required-field counts (pm-pmyq /
850
- // GH-172): counts only — never row dumps — and only for the ACTIVE required
851
- // fields, so the grouping mirrors `counts` at type granularity (e.g.
852
- // `{ Task: { close_reason: 3 } }`). Zero-suppressed at both levels.
853
- const missingFieldOccurrences = buildMissingFieldOccurrences(metadataPolicy, missingByField, itemsById);
854
- const missingByType = buildMissingByTypeCounts(missingFieldOccurrences);
855
- const details = {
856
- checked_items: items.length,
857
- metadata_profile: metadataPolicy.profile,
858
- metadata_profile_source: metadataPolicy.profile_source,
859
- metadata_profile_fallback_to_core: metadataPolicy.fallback_to_core,
860
- required_fields: [...metadataPolicy.required_fields],
861
- supported_required_fields: [...SUPPORTED_METADATA_REQUIRED_FIELDS],
862
- counts,
863
- missing_by_type: missingByType,
864
- duplicate_issue_codes_count: duplicateIssueCodes.length,
865
- duplicate_issue_codes: duplicateIssueCodeSummary.rows,
866
- duplicate_issue_codes_truncated: duplicateIssueCodeSummary.truncated,
867
- };
868
- if (metadataPolicy.configured_custom_fields.length > 0) {
869
- details.configured_custom_required_fields = [
870
- ...metadataPolicy.configured_custom_fields,
871
- ];
872
- }
873
- // Only emit per-field item_ids/truncated keys for the ACTIVE required fields of
874
- // the resolved profile (and only when there are missing items). This stops a
875
- // looser profile (e.g. core) from emitting the identical full ID array for
876
- // reviewer/risk/confidence/sprint/release that it does not even require
877
- // (pm-edge #2 — ~150 redundant lines per validate run on minimal/core).
878
- // Defensive guard (Gemini high #2, PR #78 follow-up): same optional-chain
879
- // safety as the counts loop above — never throw if a future settings shape
880
- // includes an unsupported field.
881
- attachMissingMetadataItemIds(details, metadataPolicy, missingByField, verboseDiagnostics);
882
- // Auto-fix planning input (pm-c3sz): closed items flagged for a missing
883
- // close_reason whose resolution can serve as the derivable source value.
884
- // Only collected when close_reason is an active required field, so fixes
885
- // always trace back to an actual finding of this run.
886
- const closeReasonBackfillRows = buildCloseReasonBackfillRows(metadataPolicy, missingByField, itemsById);
887
- // Estimate auto-fix planning input (GH-212): items flagged for a missing
888
- // estimated_minutes whose type drives the config-driven default backfill.
889
- // Only collected when estimated_minutes is an active required field, so fixes
890
- // always trace back to an actual finding of this run.
891
- const estimateBackfillRows = buildEstimateBackfillRows(metadataPolicy, missingByField, itemsById);
892
- return {
893
- check: {
894
- name: "metadata",
895
- status: warningTokens.length === 0 ? "ok" : "warn",
896
- ok: warningTokens.length === 0,
897
- details,
898
- },
899
- warnings: warningTokens,
900
- closeReasonBackfillRows,
901
- estimateBackfillRows,
902
- };
903
- }
904
- /* c8 ignore stop */
905
- function buildResolutionCheck(items, statusRegistry, verboseDiagnostics) {
906
- const terminalDoneStatuses = new Set(statusRegistry.terminal_done_statuses);
907
- terminalDoneStatuses.add(statusRegistry.close_status);
908
- const closedItems = items.filter((item) => terminalDoneStatuses.has(normalizeStatusForRegistry(item.status, statusRegistry)));
909
- const missingResolutionRows = [];
910
- const resolutionBackfillRows = [];
911
- for (const item of closedItems) {
912
- const missingFields = RESOLUTION_FIELD_KEYS.filter((field) => !toNonEmptyStringOrUndefined(item[field]));
913
- if (missingFields.length === 0) {
914
- continue;
915
- }
916
- missingResolutionRows.push({
917
- id: item.id,
918
- missing_fields: missingFields,
919
- });
920
- // Auto-fix planning input (pm-c3sz): the planner backfills only the
921
- // `resolution` field, deriving from the item's close_reason when present.
922
- resolutionBackfillRows.push({
923
- id: item.id,
924
- missing_fields: missingFields,
925
- close_reason: toNonEmptyStringOrUndefined(item.close_reason),
926
- });
927
- }
928
- const warnings = missingResolutionRows.length > 0
929
- ? [`validate_resolution_missing_fields:${missingResolutionRows.length}`]
930
- : [];
931
- const diagnosticLimit = verboseDiagnostics
932
- ? Number.POSITIVE_INFINITY
933
- : DIAGNOSTIC_LIST_SUMMARY_LIMIT;
934
- const summarizedRows = summarizeList(missingResolutionRows.map((row) => `${row.id}:${row.missing_fields.join(",")}`), diagnosticLimit);
935
- const remediationHints = missingResolutionRows.map((row) => buildResolutionRemediationCommand(row));
936
- const summarizedHints = summarizeList(remediationHints, diagnosticLimit);
937
- return {
938
- check: {
939
- name: "resolution",
940
- status: warnings.length === 0 ? "ok" : "warn",
941
- ok: warnings.length === 0,
942
- details: {
943
- checked_closed_items: closedItems.length,
944
- missing_resolution_items: missingResolutionRows.length,
945
- missing_resolution_rows: summarizedRows.values,
946
- missing_resolution_rows_truncated: summarizedRows.truncated,
947
- missing_resolution_remediation_hints: summarizedHints.values,
948
- missing_resolution_remediation_hints_truncated: summarizedHints.truncated,
949
- },
950
- },
951
- warnings,
952
- resolutionBackfillRows,
953
- };
954
- }
955
- /* c8 ignore start -- lifecycle dependency-graph cycle analysis is covered by lifecycle integration fixtures */
956
- function escapeRegExp(value) {
957
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
958
- }
959
- function buildLifecycleDependencyGraph(activeItems, idPrefix = "pm", relationshipRegistry = createRelationshipKindRegistry()) {
960
- const activeItemIds = new Set(activeItems.map((item) => item.id));
961
- const sortedItems = [...activeItems].sort((left, right) => left.id.localeCompare(right.id));
962
- return new Map(sortedItems.map((item) => {
963
- const edges = new Set();
964
- const blockedBy = toMeaningfulString(item.blocked_by);
965
- if (blockedBy && activeItemIds.has(blockedBy)) {
966
- edges.add(blockedBy);
967
- }
968
- (item.dependencies ?? [])
969
- .filter((dependency) => {
970
- const dependencyKind = toMeaningfulString(dependency.kind);
971
- return (dependencyKind !== undefined &&
972
- relationshipRegistry.resolve(dependencyKind)?.ordering === true);
973
- })
974
- .map((dependency) => toMeaningfulString(dependency.id))
975
- .filter((dependencyId) => dependencyId !== undefined && activeItemIds.has(dependencyId))
976
- .forEach((dependencyId) => edges.add(dependencyId));
977
- const definitionOfReady = toMeaningfulString(item.definition_of_ready);
978
- extractItemIds(definitionOfReady ?? "", idPrefix)
979
- .filter((referencedId) => activeItemIds.has(referencedId))
980
- .forEach((referencedId) => edges.add(referencedId));
981
- return [
982
- item.id,
983
- [...edges].sort((left, right) => left.localeCompare(right)),
984
- ];
985
- }));
986
- }
987
- /* c8 ignore stop */
988
- function buildDependencyReferencesCheck(items, verboseDiagnostics, statusRegistry) {
989
- const classified = collectDanglingDependencyReferences(items, statusRegistry
990
- ? (status) => isTerminalStatus(status, statusRegistry)
991
- : undefined);
992
- const activeRows = classified.active.map((row) => `${row.holder_id}:${row.target_id}:${row.kind}`);
993
- const legacyRows = classified.legacy_terminal.map((row) => `${row.holder_id}:${row.target_id}:${row.kind}:${row.holder_status}`);
994
- const diagnosticLimit = verboseDiagnostics
995
- ? Number.POSITIVE_INFINITY
996
- : DIAGNOSTIC_LIST_SUMMARY_LIMIT;
997
- const summarizedRows = summarizeList(activeRows, diagnosticLimit);
998
- const summarizedLegacyRows = summarizeList(legacyRows, diagnosticLimit);
999
- const hints = summarizeList(classified.active.map((row) => {
1000
- if (row.source === "parent") {
1001
- return `pm update ${row.holder_id} --unset parent`;
1002
- }
1003
- return row.source === "blocked_by"
1004
- ? `pm update ${row.holder_id} --unset blocked_by`
1005
- : `pm update ${row.holder_id} --replace-deps '<correct dependency edges>'`;
1006
- }), diagnosticLimit);
1007
- return {
1008
- check: {
1009
- name: "dependency_references",
1010
- status: activeRows.length === 0 ? "ok" : "warn",
1011
- ok: activeRows.length === 0,
1012
- details: {
1013
- checked_items: items.length,
1014
- dangling_reference_count: activeRows.length + legacyRows.length,
1015
- active_dangling_reference_count: activeRows.length,
1016
- dangling_reference_rows: summarizedRows.values,
1017
- dangling_reference_rows_truncated: summarizedRows.truncated,
1018
- legacy_terminal_dangling_reference_count: legacyRows.length,
1019
- legacy_closed_dangling_reference_count: classified.legacy_terminal.filter((row) => row.holder_status === "closed").length,
1020
- legacy_terminal_dangling_reference_rows: summarizedLegacyRows.values,
1021
- legacy_terminal_dangling_reference_rows_truncated: summarizedLegacyRows.truncated,
1022
- no_active_blocker_sentinel_count: classified.no_active_blocker_sentinels.length,
1023
- remediation_hints: hints.values,
1024
- remediation_hints_truncated: hints.truncated,
1025
- },
1026
- },
1027
- warnings: activeRows.length > 0
1028
- ? [`validate_dangling_dependency_references:${activeRows.length}`]
1029
- : [],
1030
- };
1031
- }
1032
- function extractItemIds(value, idPrefix = "pm") {
1033
- const normalizedPrefix = (idPrefix.trim().toLowerCase() || "pm").replace(/-+$/g, "");
1034
- const pattern = new RegExp(`(?:^|[^a-z0-9-])(${escapeRegExp(normalizedPrefix)}-[a-z0-9][a-z0-9-]*)`, "gi");
1035
- return [
1036
- ...new Set([...value.matchAll(pattern)].map((match) => match[1].toLowerCase())),
1037
- ].sort((left, right) => left.localeCompare(right));
1038
- }
1039
- /* c8 ignore start -- Tarjan SCC traversal branch matrix is covered by lifecycle cycle integration fixtures */
1040
- function findLifecycleDependencyCycleComponents(graph) {
1041
- let nextIndex = 0;
1042
- const indexById = new Map();
1043
- const lowLinkById = new Map();
1044
- const stack = [];
1045
- const inStack = new Set();
1046
- const components = [];
1047
- const visit = (id) => {
1048
- indexById.set(id, nextIndex);
1049
- lowLinkById.set(id, nextIndex);
1050
- nextIndex += 1;
1051
- stack.push(id);
1052
- inStack.add(id);
1053
- for (const dependencyId of graph.get(id) ?? []) {
1054
- if (!indexById.has(dependencyId)) {
1055
- visit(dependencyId);
1056
- lowLinkById.set(id, Math.min(lowLinkById.get(id), lowLinkById.get(dependencyId)));
1057
- }
1058
- else if (inStack.has(dependencyId)) {
1059
- lowLinkById.set(id, Math.min(lowLinkById.get(id), indexById.get(dependencyId)));
1060
- }
1061
- }
1062
- if (lowLinkById.get(id) !== indexById.get(id)) {
1063
- return;
1064
- }
1065
- const component = [];
1066
- while (stack.length > 0) {
1067
- const member = stack.pop();
1068
- inStack.delete(member);
1069
- component.push(member);
1070
- if (member === id) {
1071
- break;
1072
- }
1073
- }
1074
- component.sort((left, right) => left.localeCompare(right));
1075
- components.push(component);
1076
- };
1077
- const sortedNodeIds = [...graph.keys()].sort((left, right) => left.localeCompare(right));
1078
- for (const id of sortedNodeIds) {
1079
- if (!indexById.has(id)) {
1080
- visit(id);
1081
- }
1082
- }
1083
- const cycleComponents = components.filter((component) => {
1084
- if (component.length > 1) {
1085
- return true;
1086
- }
1087
- const selfId = component[0];
1088
- return (graph.get(selfId) ?? []).includes(selfId);
1089
- });
1090
- return cycleComponents.sort((left, right) => left[0].localeCompare(right[0]) ||
1091
- left.length - right.length ||
1092
- left.join(",").localeCompare(right.join(",")));
1093
- }
1094
- /* c8 ignore stop */
1095
- function detectLifecycleDependencyCycles(activeItems, idPrefix = "pm") {
1096
- const graph = buildLifecycleDependencyGraph(activeItems, idPrefix);
1097
- const cycleComponents = findLifecycleDependencyCycleComponents(graph);
1098
- const cycleItemIds = [...new Set(cycleComponents.flat())].sort((left, right) => left.localeCompare(right));
1099
- const cycleSamplePaths = cycleComponents.map((component) => resolveLifecycleDependencyCycleSamplePath(component, graph).join("->"));
1100
- return {
1101
- cycle_count: cycleComponents.length,
1102
- cycle_item_ids: cycleItemIds,
1103
- cycle_sample_paths: cycleSamplePaths,
1104
- };
1105
- }
1106
- function classifyOrphanedPath(pathValue) {
1107
- if (pathValue.startsWith("docs/")) {
1108
- return "docs_unowned";
1109
- }
1110
- if (pathValue.startsWith("tests/")) {
1111
- return "tests_unowned";
1112
- }
1113
- if (pathValue.startsWith("src/")) {
1114
- return "source_unowned";
1115
- }
1116
- if (pathValue.endsWith(".md")) {
1117
- return "docs_unowned";
1118
- }
1119
- return "unlinked_existing";
1120
- }
1121
- function directoryOf(relativePath) {
1122
- const slash = relativePath.lastIndexOf("/");
1123
- return slash === -1 ? "" : relativePath.slice(0, slash);
1124
- }
1125
- function sharedDirectoryPrefixLength(left, right) {
1126
- const leftParts = directoryOf(left).split("/").filter(Boolean);
1127
- const rightParts = directoryOf(right).split("/").filter(Boolean);
1128
- let count = 0;
1129
- while (count < leftParts.length &&
1130
- count < rightParts.length &&
1131
- leftParts[count] === rightParts[count]) {
1132
- count += 1;
1133
- }
1134
- return count;
1135
- }
1136
- function scoreOrphanOwnerCandidate(pathValue, linkedPath) {
1137
- const linkedDir = directoryOf(linkedPath);
1138
- const orphanDir = directoryOf(pathValue);
1139
- const directoryPrefix = linkedPath.endsWith("/")
1140
- ? linkedPath
1141
- : `${linkedPath}/`;
1142
- const isDirectoryPrefix = pathValue.startsWith(directoryPrefix);
1143
- const sameDirectory = linkedDir.length > 0 && linkedDir === orphanDir;
1144
- const sharedPrefixLength = sharedDirectoryPrefixLength(pathValue, linkedPath);
1145
- if (!isDirectoryPrefix && !sameDirectory && sharedPrefixLength === 0) {
1146
- return null;
1147
- }
1148
- if (isDirectoryPrefix) {
1149
- return { score: linkedPath.length + 1000, confidence: "path_prefix" };
1150
- }
1151
- return sameDirectory
1152
- ? { score: sharedPrefixLength + 500, confidence: "same_directory" }
1153
- : { score: sharedPrefixLength, confidence: "shared_directory" };
1154
- }
1155
- function shouldReplaceOrphanOwnerCandidate(best, item, score) {
1156
- return (best === undefined ||
1157
- score > best.score ||
1158
- (score === best.score && item.id.localeCompare(best.item.id) < 0));
1159
- }
1160
- function findOrphanOwnerCandidate(pathValue, classification, items) {
1161
- const linkKind = classification === "docs_unowned" ? "docs" : "files";
1162
- let best;
1163
- items
1164
- .flatMap((item) => (linkKind === "docs" ? (item.docs ?? []) : (item.files ?? [])).map((link) => ({ item, link })))
1165
- .forEach(({ item, link }) => {
1166
- if (link.scope !== "project") {
1167
- return;
1168
- }
1169
- const linkedPath = normalizeRelativePath(link.path);
1170
- if (linkedPath.length === 0 || linkedPath === pathValue) {
1171
- return;
1172
- }
1173
- const scored = scoreOrphanOwnerCandidate(pathValue, linkedPath);
1174
- if (scored === null) {
1175
- return;
1176
- }
1177
- if (shouldReplaceOrphanOwnerCandidate(best, item, scored.score)) {
1178
- best = {
1179
- item,
1180
- score: scored.score,
1181
- confidence: scored.confidence,
1182
- };
1183
- }
1184
- });
1185
- if (!best) {
1186
- return null;
1187
- }
1188
- return {
1189
- id: best.item.id,
1190
- type: best.item.type,
1191
- title: best.item.title,
1192
- status: best.item.status,
1193
- confidence: best.confidence,
1194
- };
1195
- }
1196
- function buildOrphanedPathRows(orphanedFiles, items) {
1197
- return orphanedFiles.map((pathValue) => {
1198
- const classification = classifyOrphanedPath(pathValue);
1199
- const linkCommand = classification === "docs_unowned" ? "docs" : "files";
1200
- const ownerCandidate = findOrphanOwnerCandidate(pathValue, classification, items);
1201
- const target = ownerCandidate?.id ?? "<id>";
1202
- return {
1203
- path: pathValue,
1204
- classification,
1205
- owner_candidate: ownerCandidate,
1206
- remediation_hint: `pm ${linkCommand} ${target} --add path=${pathValue},scope=project,note="<why this artifact belongs to the item>"`,
1207
- };
1208
- });
1209
- }
1210
- function summarizeOrphanedPathRows(rows) {
1211
- return rows.map((row) => `${row.path}:${row.classification} owner_candidate=${row.owner_candidate?.id ?? "unowned"} hint=${JSON.stringify(row.remediation_hint)}`);
1212
- }
1213
- function closureLikeFieldsForItem(item, lifecyclePatternPolicy) {
1214
- return Object.entries(lifecyclePatternPolicy.closure_like_metadata_field_patterns)
1215
- .filter(([field, patterns]) => {
1216
- const value = toMeaningfulString(item[field]);
1217
- return value
1218
- ? patterns.some((pattern) => value.toLowerCase().includes(pattern))
1219
- : false;
1220
- })
1221
- .map(([field]) => field)
1222
- .sort((left, right) => left.localeCompare(right));
1223
- }
1224
- function buildTerminalParentFixRow(item, parent, itemsById, canonicalIdByLowercase, statusRegistry) {
1225
- const grandparentId = toMeaningfulString(parent.parent);
1226
- const grandparent = grandparentId
1227
- ? itemsById.get(canonicalIdByLowercase.get(grandparentId.toLowerCase()) ??
1228
- grandparentId)
1229
- : undefined;
1230
- return {
1231
- id: item.id,
1232
- parent_id: parent.id,
1233
- grandparent_id: grandparent?.id,
1234
- grandparent_active: grandparent !== undefined &&
1235
- !isTerminalStatus(grandparent.status, statusRegistry),
1236
- };
1237
- }
1238
- /* v8 ignore start -- stale-blocker reason variants are covered through command-level diagnostics; helper branch counters are defensive */
1239
- function staleBlockerReasonsForItem(item, blockedStatuses, statusRegistry, lifecyclePatternPolicy) {
1240
- const blockedBy = toMeaningfulString(item.blocked_by);
1241
- const blockedReason = toMeaningfulString(item.blocked_reason);
1242
- const blockedReasonNormalized = blockedReason?.toLowerCase();
1243
- const normalizedStatus = normalizeStatusForRegistry(item.status, statusRegistry);
1244
- const reasons = [];
1245
- if (!blockedStatuses.has(normalizedStatus)) {
1246
- if (blockedBy) {
1247
- reasons.push("non_blocked_status_has_blocked_by");
1248
- }
1249
- if (blockedReason) {
1250
- reasons.push("non_blocked_status_has_blocked_reason");
1251
- }
1252
- return reasons;
1253
- }
1254
- if (!blockedBy && !blockedReason) {
1255
- /* v8 ignore start -- blocked-status creation paths seed either blocked_by or blocked_reason before lifecycle validation */
1256
- reasons.push("blocked_status_missing_blocker_context");
1257
- /* v8 ignore stop */
1258
- }
1259
- if (blockedReasonNormalized?.includes("no active blocker")) {
1260
- reasons.push("blocked_status_reason_reports_no_active_blocker");
1261
- }
1262
- if (blockedReasonNormalized &&
1263
- lifecyclePatternPolicy.stale_blocker_reason_patterns.some((pattern) => blockedReasonNormalized.includes(pattern))) {
1264
- reasons.push("blocked_status_reason_matches_stale_pattern");
1265
- }
1266
- return reasons;
1267
- }
1268
- /* v8 ignore stop */
1269
- function collectLifecycleScanRows(items, includeStaleBlockers, statusRegistry, lifecyclePatternPolicy) {
1270
- const itemsById = new Map(items.map((item) => [item.id, item]));
1271
- const canonicalIdByLowercase = new Map(items.map((item) => [item.id.toLowerCase(), item.id]));
1272
- /* v8 ignore start -- runtime status registry normally supplies blocked statuses; fallback preserves legacy settings safety */
1273
- const blockedStatuses = statusRegistry.blocked_statuses.size > 0
1274
- ? statusRegistry.blocked_statuses
1275
- : new Set(["blocked"]);
1276
- /* v8 ignore stop */
1277
- const rows = {
1278
- activeItems: items.filter((item) => !isTerminalStatus(item.status, statusRegistry)),
1279
- closureLikeRows: [],
1280
- terminalParentRows: [],
1281
- terminalParentFixRows: [],
1282
- staleBlockerRows: [],
1283
- };
1284
- for (const item of rows.activeItems) {
1285
- const closureLikeFields = closureLikeFieldsForItem(item, lifecyclePatternPolicy);
1286
- if (closureLikeFields.length > 0) {
1287
- rows.closureLikeRows.push({ id: item.id, fields: closureLikeFields });
1288
- }
1289
- const parentId = toMeaningfulString(item.parent);
1290
- const parent = parentId
1291
- ? itemsById.get(canonicalIdByLowercase.get(parentId.toLowerCase()) ?? parentId)
1292
- : undefined;
1293
- if (parent && isTerminalStatus(parent.status, statusRegistry)) {
1294
- rows.terminalParentRows.push({
1295
- id: item.id,
1296
- parent_id: parent.id,
1297
- parent_status: parent.status,
1298
- });
1299
- rows.terminalParentFixRows.push(buildTerminalParentFixRow(item, parent, itemsById, canonicalIdByLowercase, statusRegistry));
1300
- }
1301
- /* v8 ignore start -- stale-blocker reason presence is covered by command diagnostics; branch accounting here is defensive */
1302
- if (includeStaleBlockers) {
1303
- const reasons = staleBlockerReasonsForItem(item, blockedStatuses, statusRegistry, lifecyclePatternPolicy);
1304
- if (reasons.length > 0) {
1305
- rows.staleBlockerRows.push({
1306
- id: item.id,
1307
- status: item.status,
1308
- reasons: [...new Set(reasons)].sort((left, right) => left.localeCompare(right)),
1309
- });
1310
- }
1311
- }
1312
- /* v8 ignore stop */
1313
- }
1314
- return rows;
1315
- }
1316
- function sortLifecycleScanRows(rows) {
1317
- rows.closureLikeRows.sort((left, right) => left.id.localeCompare(right.id));
1318
- /* v8 ignore start -- parent-id tie breakers only matter for duplicate legacy ids, which the tracker writer prevents */
1319
- rows.terminalParentRows.sort((left, right) => left.id.localeCompare(right.id) ||
1320
- left.parent_id.localeCompare(right.parent_id));
1321
- rows.terminalParentFixRows.sort((left, right) => left.id.localeCompare(right.id) ||
1322
- left.parent_id.localeCompare(right.parent_id));
1323
- /* v8 ignore stop */
1324
- rows.staleBlockerRows.sort((left, right) => left.id.localeCompare(right.id));
1325
- }
1326
- function lifecycleCycleWarningToken(prefix, severity, count) {
1327
- if (count <= 0 || severity === "off") {
1328
- return null;
1329
- }
1330
- return `${severity === "error" ? `${prefix}_error` : prefix}:${count}`;
1331
- }
1332
- function lifecycleAdvisoryWarningToken(prefix, severity, count) {
1333
- return count > 0 && severity !== "off" ? `${prefix}:${count}` : null;
1334
- }
1335
- function buildLifecycleWarnings(rows, includeStaleBlockers, dependencyCycleSeverity, parentCycleSeverity, counts) {
1336
- const warnings = [];
1337
- if (rows.closureLikeRows.length > 0) {
1338
- warnings.push(`validate_lifecycle_active_closure_like_metadata:${rows.closureLikeRows.length}`);
1339
- }
1340
- if (rows.terminalParentRows.length > 0) {
1341
- warnings.push(`validate_lifecycle_active_terminal_parent:${rows.terminalParentRows.length}`);
1342
- }
1343
- if (includeStaleBlockers && rows.staleBlockerRows.length > 0) {
1344
- warnings.push(`validate_lifecycle_stale_blockers:${rows.staleBlockerRows.length}`);
1345
- }
1346
- const dependencyWarning = lifecycleCycleWarningToken("validate_lifecycle_dependency_cycles", dependencyCycleSeverity, counts.dependencyCycles);
1347
- const parentWarning = lifecycleCycleWarningToken("validate_hierarchy_parent_cycle", parentCycleSeverity, counts.activeHierarchyCycles);
1348
- const cardinalityWarning = lifecycleCycleWarningToken("validate_hierarchy_cardinality_violation", parentCycleSeverity, counts.activeHierarchyCardinality);
1349
- const divergenceWarning = lifecycleCycleWarningToken("validate_hierarchy_parent_divergence", parentCycleSeverity, counts.activeHierarchyDivergence);
1350
- const legacyCycleWarning = lifecycleAdvisoryWarningToken("validate_legacy_hierarchy_parent_cycle", parentCycleSeverity, counts.legacyHierarchyCycles);
1351
- const legacyCardinalityWarning = lifecycleAdvisoryWarningToken("validate_legacy_hierarchy_cardinality_violation", parentCycleSeverity, counts.legacyHierarchyCardinality);
1352
- const legacyDivergenceWarning = lifecycleAdvisoryWarningToken("validate_legacy_hierarchy_parent_divergence", parentCycleSeverity, counts.legacyHierarchyDivergence);
1353
- return [
1354
- ...warnings,
1355
- ...(dependencyWarning ? [dependencyWarning] : []),
1356
- ...(parentWarning ? [parentWarning] : []),
1357
- ...(cardinalityWarning ? [cardinalityWarning] : []),
1358
- ...(divergenceWarning ? [divergenceWarning] : []),
1359
- ...(legacyCycleWarning ? [legacyCycleWarning] : []),
1360
- ...(legacyCardinalityWarning ? [legacyCardinalityWarning] : []),
1361
- ...(legacyDivergenceWarning ? [legacyDivergenceWarning] : []),
1362
- ];
1363
- }
1364
- function linkedArtifactPathExceedsFilesystemLimits(artifactPath) {
1365
- const normalized = normalizeRelativePath(artifactPath);
1366
- return (normalized.length > LINKED_ARTIFACT_MAX_PATH_LENGTH ||
1367
- normalized
1368
- .split(/[\\/]/)
1369
- .some((segment) => segment.length > LINKED_ARTIFACT_MAX_SEGMENT_LENGTH));
1370
- }
1371
- async function linkedArtifactIsMissing(workspaceRoot, artifactPath) {
1372
- const absolutePath = path.isAbsolute(artifactPath)
1373
- ? artifactPath
1374
- : path.resolve(workspaceRoot, artifactPath);
1375
- if (linkedArtifactPathExceedsFilesystemLimits(absolutePath)) {
1376
- return false;
1377
- }
1378
- try {
1379
- const stats = await fs.stat(absolutePath);
1380
- return !stats.isFile() && !stats.isDirectory();
1381
- }
1382
- catch (error) {
1383
- return hasFilesystemErrorCode(error, ["ENOENT", "ENOTDIR"]);
1384
- }
1385
- }
1386
- async function collectLinkedPathScanState(items, workspaceRoot) {
1387
- const state = {
1388
- linkedProjectPaths: new Set(),
1389
- remoteLinkedPaths: new Set(),
1390
- missingLinkedPaths: [],
1391
- staleLinkRows: [],
1392
- };
1393
- const linkedArtifacts = items.flatMap((item) => [
1394
- ...(item.files ?? []).map((artifact) => ({
1395
- item,
1396
- artifact,
1397
- link_kind: "files",
1398
- })),
1399
- ...(item.docs ?? []).map((artifact) => ({
1400
- item,
1401
- artifact,
1402
- link_kind: "docs",
1403
- })),
1404
- ]);
1405
- for (const { item, artifact, link_kind } of linkedArtifacts) {
1406
- if (artifact.scope !== "project") {
1407
- continue;
1408
- }
1409
- if (isRemoteLinkedArtifactReference(artifact.path)) {
1410
- state.remoteLinkedPaths.add(artifact.path.trim());
1411
- continue;
1412
- }
1413
- const normalizedPath = normalizeRelativePath(artifact.path);
1414
- if (normalizedPath.length === 0) {
1415
- continue;
1416
- }
1417
- state.linkedProjectPaths.add(normalizedPath);
1418
- if (await linkedArtifactIsMissing(workspaceRoot, artifact.path)) {
1419
- state.missingLinkedPaths.push(normalizedPath);
1420
- state.staleLinkRows.push({
1421
- item_id: item.id,
1422
- path: normalizedPath,
1423
- link_kind,
1424
- });
1425
- }
1426
- }
1427
- return state;
1428
- }
1429
- function partitionFileCandidates(fileCandidates, pmRoot, workspaceRoot, fileScanMode, includePmInternals, verboseFileLists) {
1430
- const strictTrackedAllMode = fileScanMode === "tracked-all-strict";
1431
- const strictModeForcesPmInternals = strictTrackedAllMode && !includePmInternals;
1432
- const includePmInternalsEffective = includePmInternals || strictTrackedAllMode;
1433
- const pmInternalCandidatePrefixes = includePmInternalsEffective
1434
- ? []
1435
- : resolvePmInternalCandidatePrefixes(pmRoot, workspaceRoot);
1436
- const excludedPmInternalPaths = pmInternalCandidatePrefixes.length === 0
1437
- ? []
1438
- : fileCandidates.candidateFiles.filter((candidate) => hasPathPrefix(candidate, pmInternalCandidatePrefixes));
1439
- const candidateFiles = pmInternalCandidatePrefixes.length === 0
1440
- ? fileCandidates.candidateFiles
1441
- : fileCandidates.candidateFiles.filter((candidate) => !hasPathPrefix(candidate, pmInternalCandidatePrefixes));
1442
- const excludedByReason = {};
1443
- if (excludedPmInternalPaths.length > 0) {
1444
- const summarizedPmInternalPaths = summarizeFileList(excludedPmInternalPaths, verboseFileLists);
1445
- excludedByReason.pm_internals = {
1446
- count: excludedPmInternalPaths.length,
1447
- paths: summarizedPmInternalPaths.values,
1448
- paths_truncated: summarizedPmInternalPaths.truncated,
1449
- paths_total: summarizedPmInternalPaths.total,
1450
- };
1451
- }
1452
- return {
1453
- strictTrackedAllMode,
1454
- strictModeForcesPmInternals,
1455
- includePmInternalsEffective,
1456
- pmInternalCandidatePrefixes,
1457
- excludedPmInternalPaths,
1458
- candidateFiles,
1459
- excludedByReason,
1460
- };
1461
- }
1462
- /* c8 ignore start -- lifecycle stale/terminal/dependency diagnostics matrix is covered by end-to-end validate integration runs */
1463
- function buildLifecycleCheck(items, includeStaleBlockers, dependencyCycleSeverity, parentCycleSeverity, statusRegistry, lifecyclePatternPolicy, verboseDiagnostics, idPrefix = "pm") {
1464
- const rows = collectLifecycleScanRows(items, includeStaleBlockers, statusRegistry, lifecyclePatternPolicy);
1465
- sortLifecycleScanRows(rows);
1466
- const dependencyCycleDiagnostics = detectLifecycleDependencyCycles(rows.activeItems, idPrefix);
1467
- const parentCycleDiagnostics = detectLifecycleParentCycles(items, statusRegistry);
1468
- const statusRoleDiagnostics = inspectStatusRoleAssignments(statusRegistry, items, verboseDiagnostics
1469
- ? Number.POSITIVE_INFINITY
1470
- : DIAGNOSTIC_LIST_SUMMARY_LIMIT);
1471
- const warnings = [
1472
- ...buildLifecycleWarnings(rows, includeStaleBlockers, dependencyCycleSeverity, parentCycleSeverity, {
1473
- dependencyCycles: dependencyCycleDiagnostics.cycle_count,
1474
- activeHierarchyCycles: parentCycleDiagnostics.active_cycle_count,
1475
- activeHierarchyCardinality: parentCycleDiagnostics.active_cardinality_violation_count,
1476
- activeHierarchyDivergence: parentCycleDiagnostics.active_parent_divergence_count,
1477
- legacyHierarchyCycles: parentCycleDiagnostics.legacy_cycle_count,
1478
- legacyHierarchyCardinality: parentCycleDiagnostics.legacy_cardinality_violation_count,
1479
- legacyHierarchyDivergence: parentCycleDiagnostics.legacy_parent_divergence_count,
1480
- }),
1481
- ...buildStatusRoleWarnings(statusRoleDiagnostics),
1482
- ];
1483
- const diagnosticLimit = verboseDiagnostics
1484
- ? Number.POSITIVE_INFINITY
1485
- : DIAGNOSTIC_LIST_SUMMARY_LIMIT;
1486
- const summarizedClosureLikeRows = summarizeList(rows.closureLikeRows.map((row) => `${row.id}:${row.fields.join(",")}`), diagnosticLimit);
1487
- const summarizedTerminalParentRows = summarizeList(rows.terminalParentRows.map((row) => `${row.id}:${row.parent_id}:${row.parent_status}`), diagnosticLimit);
1488
- const summarizedStaleBlockerRows = summarizeList(rows.staleBlockerRows.map((row) => `${row.id}:${row.status}:${row.reasons.join(",")}`), diagnosticLimit);
1489
- const summarizedDependencyCycleItemIds = summarizeList(dependencyCycleDiagnostics.cycle_item_ids, diagnosticLimit);
1490
- const summarizedDependencyCycleSamplePaths = summarizeList(dependencyCycleDiagnostics.cycle_sample_paths, diagnosticLimit);
1491
- const summarizedParentCycleItemIds = summarizeList(parentCycleDiagnostics.cycle_item_ids, diagnosticLimit);
1492
- const summarizedParentCycleSamplePaths = summarizeList(parentCycleDiagnostics.cycle_sample_paths, diagnosticLimit);
1493
- const summarizedHierarchyCardinalityRows = summarizeList(parentCycleDiagnostics.cardinality_violation_rows, diagnosticLimit);
1494
- const summarizedHierarchyDivergenceRows = summarizeList(parentCycleDiagnostics.parent_divergence_rows, diagnosticLimit);
1495
- const hasErrorSeverityCycle = (dependencyCycleDiagnostics.cycle_count > 0 &&
1496
- dependencyCycleSeverity === "error") ||
1497
- ((parentCycleDiagnostics.active_cycle_count > 0 ||
1498
- parentCycleDiagnostics.active_cardinality_violation_count > 0 ||
1499
- parentCycleDiagnostics.active_parent_divergence_count > 0) &&
1500
- parentCycleSeverity === "error");
1501
- return {
1502
- check: {
1503
- name: "lifecycle",
1504
- status: hasErrorSeverityCycle
1505
- ? "error"
1506
- : warnings.length === 0
1507
- ? "ok"
1508
- : "warn",
1509
- ok: !hasErrorSeverityCycle && warnings.length === 0,
1510
- details: {
1511
- checked_active_items: rows.activeItems.length,
1512
- lifecycle_status_roles: statusRoleDiagnostics,
1513
- active_closure_like_metadata_items: rows.closureLikeRows.length,
1514
- active_closure_like_metadata_rows: summarizedClosureLikeRows.values,
1515
- active_closure_like_metadata_rows_truncated: summarizedClosureLikeRows.truncated,
1516
- active_terminal_parent_items: rows.terminalParentRows.length,
1517
- active_terminal_parent_rows: summarizedTerminalParentRows.values,
1518
- active_terminal_parent_rows_truncated: summarizedTerminalParentRows.truncated,
1519
- stale_blocker_checks_enabled: includeStaleBlockers,
1520
- stale_blocker_items: rows.staleBlockerRows.length,
1521
- stale_blocker_rows: summarizedStaleBlockerRows.values,
1522
- stale_blocker_rows_truncated: summarizedStaleBlockerRows.truncated,
1523
- dependency_cycle_severity_policy: dependencyCycleSeverity,
1524
- dependency_cycle_count: dependencyCycleDiagnostics.cycle_count,
1525
- dependency_cycle_item_count: dependencyCycleDiagnostics.cycle_item_ids.length,
1526
- dependency_cycle_item_ids: summarizedDependencyCycleItemIds.values,
1527
- dependency_cycle_item_ids_truncated: summarizedDependencyCycleItemIds.truncated,
1528
- dependency_cycle_sample_paths: summarizedDependencyCycleSamplePaths.values,
1529
- dependency_cycle_sample_paths_truncated: summarizedDependencyCycleSamplePaths.truncated,
1530
- parent_cycle_severity_policy: parentCycleSeverity,
1531
- parent_cycle_count: parentCycleDiagnostics.cycle_count,
1532
- active_parent_cycle_count: parentCycleDiagnostics.active_cycle_count,
1533
- legacy_parent_cycle_count: parentCycleDiagnostics.legacy_cycle_count,
1534
- parent_cycle_item_count: parentCycleDiagnostics.cycle_item_ids.length,
1535
- parent_cycle_item_ids: summarizedParentCycleItemIds.values,
1536
- parent_cycle_item_ids_truncated: summarizedParentCycleItemIds.truncated,
1537
- parent_cycle_sample_paths: summarizedParentCycleSamplePaths.values,
1538
- parent_cycle_sample_paths_truncated: summarizedParentCycleSamplePaths.truncated,
1539
- hierarchy_cardinality_violation_count: parentCycleDiagnostics.cardinality_violation_count,
1540
- active_hierarchy_cardinality_violation_count: parentCycleDiagnostics.active_cardinality_violation_count,
1541
- legacy_hierarchy_cardinality_violation_count: parentCycleDiagnostics.legacy_cardinality_violation_count,
1542
- hierarchy_cardinality_violation_rows: summarizedHierarchyCardinalityRows.values,
1543
- hierarchy_cardinality_violation_rows_truncated: summarizedHierarchyCardinalityRows.truncated,
1544
- hierarchy_parent_divergence_count: parentCycleDiagnostics.parent_divergence_count,
1545
- active_hierarchy_parent_divergence_count: parentCycleDiagnostics.active_parent_divergence_count,
1546
- legacy_hierarchy_parent_divergence_count: parentCycleDiagnostics.legacy_parent_divergence_count,
1547
- hierarchy_parent_divergence_rows: summarizedHierarchyDivergenceRows.values,
1548
- hierarchy_parent_divergence_rows_truncated: summarizedHierarchyDivergenceRows.truncated,
1549
- stale_blocker_reason_patterns: [
1550
- ...lifecyclePatternPolicy.stale_blocker_reason_patterns,
1551
- ],
1552
- stale_blocker_reason_pattern_source: lifecyclePatternPolicy.stale_blocker_reason_pattern_source,
1553
- closure_like_blocked_reason_patterns: [
1554
- ...lifecyclePatternPolicy.closure_like_metadata_field_patterns
1555
- .blocked_reason,
1556
- ],
1557
- closure_like_blocked_reason_pattern_source: lifecyclePatternPolicy.closure_like_metadata_field_pattern_sources
1558
- .blocked_reason,
1559
- closure_like_resolution_patterns: [
1560
- ...lifecyclePatternPolicy.closure_like_metadata_field_patterns
1561
- .resolution,
1562
- ],
1563
- closure_like_resolution_pattern_source: lifecyclePatternPolicy.closure_like_metadata_field_pattern_sources
1564
- .resolution,
1565
- closure_like_actual_result_patterns: [
1566
- ...lifecyclePatternPolicy.closure_like_metadata_field_patterns
1567
- .actual_result,
1568
- ],
1569
- closure_like_actual_result_pattern_source: lifecyclePatternPolicy.closure_like_metadata_field_pattern_sources
1570
- .actual_result,
1571
- },
1572
- },
1573
- warnings,
1574
- terminalParentFixRows: rows.terminalParentFixRows,
1575
- };
1576
- }
1577
- /* c8 ignore stop */
1578
- /* c8 ignore start -- files-check candidate filtering/classification permutations are covered by file-audit integration suites */
1579
- /** Join live filesystem evidence with holder lifecycle, retaining historical rows while warning only on active missing links. */
1580
- async function buildFilesCheck(items, workspaceRoot, pmRoot, fileScanMode, includePmInternals, verboseFileLists, statusRegistry = resolveRuntimeStatusRegistry(SETTINGS_DEFAULTS.schema)) {
1581
- const linkedPathState = await collectLinkedPathScanState(items, workspaceRoot);
1582
- const itemsById = new Map(items.map((item) => [item.id, item]));
1583
- const uniqueMissingLinkedPaths = [
1584
- ...new Set(linkedPathState.missingLinkedPaths),
1585
- ].sort((left, right) => left.localeCompare(right));
1586
- const fileCandidates = await collectProjectFileCandidates(workspaceRoot, fileScanMode);
1587
- const partition = partitionFileCandidates(fileCandidates, pmRoot, workspaceRoot, fileScanMode, includePmInternals, verboseFileLists);
1588
- const orphanedFiles = partition.candidateFiles.filter((candidate) => !linkedPathState.linkedProjectPaths.has(candidate));
1589
- const orphanedPathRows = buildOrphanedPathRows(orphanedFiles, items);
1590
- // Stale-path classification (pm-0v2m / GH-184): a missing linked path whose
1591
- // basename still exists in the candidate scan is reported as `moved` (with
1592
- // relink candidates); otherwise it is `deleted` and safe to prune.
1593
- const classifiedStalePaths = classifyStaleLinkedPaths(uniqueMissingLinkedPaths, partition.candidateFiles);
1594
- const classificationByPath = new Map(classifiedStalePaths.map((entry) => [entry.path, entry.classification]));
1595
- const movedStalePathCount = classifiedStalePaths.filter((entry) => entry.classification === "moved").length;
1596
- const staleLinkPruneRows = linkedPathState.staleLinkRows
1597
- .map((row) => ({
1598
- ...row,
1599
- classification: classificationByPath.get(row.path) ?? "deleted",
1600
- }))
1601
- .sort((left, right) => left.item_id.localeCompare(right.item_id) ||
1602
- left.path.localeCompare(right.path) ||
1603
- left.link_kind.localeCompare(right.link_kind));
1604
- const warnings = [];
1605
- if (partition.strictModeForcesPmInternals) {
1606
- warnings.push("validate_files_tracked_all_strict_forces_pm_internals");
1607
- }
1608
- if (orphanedFiles.length > 0) {
1609
- warnings.push(`validate_files_orphaned_paths:${orphanedFiles.length}`);
1610
- }
1611
- const uniqueRemoteLinkedPaths = [...linkedPathState.remoteLinkedPaths].sort((left, right) => left.localeCompare(right));
1612
- const summarizedRemote = summarizeFileList(uniqueRemoteLinkedPaths, verboseFileLists);
1613
- const summarizedMissing = summarizeFileList(uniqueMissingLinkedPaths, verboseFileLists);
1614
- const summarizedOrphaned = summarizeFileList(orphanedFiles, verboseFileLists);
1615
- const summarizedOrphanedClassifications = summarizeFileList(orphanedPathRows.map((row) => `${row.path}:${row.classification}:owner_candidate=${row.owner_candidate?.id ?? "unowned"}`), verboseFileLists);
1616
- const orphanedPathRowDetail = verboseFileLists
1617
- ? orphanedPathRows
1618
- : summarizeFileList(summarizeOrphanedPathRows(orphanedPathRows), false)
1619
- .values;
1620
- const summarizedClassifications = summarizeFileList(summarizeStaleLinkedPathClassifications(classifiedStalePaths), verboseFileLists);
1621
- const missingLinkedPathRows = staleLinkPruneRows;
1622
- const repairReport = buildLinkedFileRepairReport(missingLinkedPathRows, classifiedStalePaths, (id) => itemsById.get(id), statusRegistry, verboseFileLists ? Infinity : FILE_LIST_SUMMARY_LIMIT);
1623
- if (repairReport.active_missing_linked_paths_count > 0) {
1624
- warnings.push(`validate_files_missing_linked_paths:${repairReport.active_missing_linked_paths_count}`);
1625
- }
1626
- return {
1627
- check: {
1628
- name: "files",
1629
- status: warnings.length === 0 ? "ok" : "warn",
1630
- ok: warnings.length === 0,
1631
- details: {
1632
- workspace_root: workspaceRoot,
1633
- scan_mode_requested: fileCandidates.requestedMode,
1634
- scan_mode_applied: fileCandidates.appliedMode,
1635
- strict_tracked_all_mode: partition.strictTrackedAllMode,
1636
- strict_mode_forces_pm_internals: partition.strictModeForcesPmInternals,
1637
- strict_mode_forces_pm_internals_notice: partition.strictModeForcesPmInternals
1638
- ? "tracked-all-strict force-enables PM internals; pass --include-pm-internals to make inclusion explicit."
1639
- : null,
1640
- file_list_detail_mode: verboseFileLists ? "full" : "summary",
1641
- file_list_summary_limit: FILE_LIST_SUMMARY_LIMIT,
1642
- candidate_scan_source: fileCandidates.source,
1643
- include_pm_internals: partition.includePmInternalsEffective,
1644
- include_pm_internals_requested: includePmInternals,
1645
- pm_internal_candidate_prefixes: partition.pmInternalCandidatePrefixes,
1646
- pm_internal_excluded_count: partition.excludedPmInternalPaths.length,
1647
- excluded_total: partition.excludedPmInternalPaths.length,
1648
- excluded_by_reason: partition.excludedByReason,
1649
- linked_project_paths: linkedPathState.linkedProjectPaths.size,
1650
- remote_linked_paths_count: uniqueRemoteLinkedPaths.length,
1651
- remote_linked_paths_total: summarizedRemote.total,
1652
- remote_linked_paths: summarizedRemote.values,
1653
- remote_linked_paths_truncated: summarizedRemote.truncated,
1654
- candidate_total_raw: fileCandidates.candidateTotal,
1655
- candidate_scanned_raw: fileCandidates.candidateScanned,
1656
- candidate_total: partition.candidateFiles.length,
1657
- candidate_scanned: partition.candidateFiles.length,
1658
- scanned_candidate_files: partition.candidateFiles.length,
1659
- missing_linked_paths_count: uniqueMissingLinkedPaths.length,
1660
- missing_linked_paths_total: summarizedMissing.total,
1661
- missing_linked_paths: summarizedMissing.values,
1662
- missing_linked_paths_truncated: summarizedMissing.truncated,
1663
- missing_linked_paths_moved_count: movedStalePathCount,
1664
- missing_linked_paths_deleted_count: classifiedStalePaths.filter((entry) => entry.classification === "deleted").length,
1665
- missing_linked_paths_malformed_count: classifiedStalePaths.filter((entry) => entry.classification === "malformed").length,
1666
- missing_linked_path_classifications: summarizedClassifications.values,
1667
- missing_linked_path_classifications_truncated: summarizedClassifications.truncated,
1668
- ...repairReport,
1669
- orphaned_paths_count: orphanedFiles.length,
1670
- orphaned_paths_total: summarizedOrphaned.total,
1671
- orphaned_paths: summarizedOrphaned.values,
1672
- orphaned_paths_truncated: summarizedOrphaned.truncated,
1673
- orphaned_path_classifications: summarizedOrphanedClassifications.values,
1674
- orphaned_path_classifications_truncated: summarizedOrphanedClassifications.truncated,
1675
- orphaned_path_rows_count: orphanedPathRows.length,
1676
- orphaned_path_rows: orphanedPathRowDetail,
1677
- },
1678
- },
1679
- warnings,
1680
- staleLinkPruneRows,
1681
- };
1682
- }
1683
- /* c8 ignore stop */
1684
- /** Collapse a linked command to the bounded preview used in validation rows. */
1685
- function summarizeCommandPreview(command) {
1686
- const normalizedCommand = command.trim().replaceAll(/\s+/g, " ");
1687
- return normalizedCommand.length > 120
1688
- ? `${normalizedCommand.slice(0, 117)}...`
1689
- : normalizedCommand;
1690
- }
1691
- /** Join command-reference identity and its bounded preview for diagnostics. */
1692
- function summarizeCommandReferenceRow(ownerId, referencedId, command) {
1693
- return `${ownerId}:${referencedId}:${summarizeCommandPreview(command)}`;
1694
- }
1695
- /** Render portable diagnostics for linked tests that this clone has not trusted. */
1696
- function buildUntrustedLinkedTestRows(linkedTests, trustDecisions) {
1697
- const rows = [];
1698
- linkedTests.forEach(({ item, linkedTest }, index) => {
1699
- const trust = trustDecisions[index];
1700
- if (trust.trusted)
1701
- return;
1702
- const command = summarizeCommandPreview(linkedTest.command ?? "");
1703
- rows.push(`${item.id}:${trust.source_ref ?? "unknown"}->${trust.current_source_ref ?? "unknown"}:${command}`);
1704
- });
1705
- return rows;
1706
- }
1707
- /* c8 ignore start -- command-reference discovery/stale-id permutations are covered by linked-test integration suites */
1708
- async function buildCommandReferencesCheck(items, idPrefix, verboseDiagnostics, pmRoot) {
1709
- const knownIds = new Set(items.map((item) => item.id.toLowerCase()));
1710
- let linkedCommandsScanned = 0;
1711
- let referencedPmIdCount = 0;
1712
- const referencedPmIds = new Set();
1713
- const staleReferenceRows = [];
1714
- const linkedTests = items.flatMap((item) => (item.tests ?? []).map((linkedTest) => ({ item, linkedTest })));
1715
- const currentSourceRef = await resolveLinkedTestSourceRef(resolveLinkedTestSourceWorkspaceRoot());
1716
- const trustDecisions = await resolveLinkedTestTrustBatch(pmRoot, linkedTests.map(({ linkedTest }) => linkedTest), currentSourceRef);
1717
- const untrustedRows = buildUntrustedLinkedTestRows(linkedTests, trustDecisions);
1718
- for (const { item, linkedTest } of linkedTests) {
1719
- if (typeof linkedTest.command !== "string" ||
1720
- linkedTest.command.trim().length === 0) {
1721
- continue;
1722
- }
1723
- linkedCommandsScanned += 1;
1724
- const referencedIds = extractReferencedPmItemIdsFromCommand(linkedTest.command, idPrefix);
1725
- if (referencedIds.length === 0) {
1726
- continue;
1727
- }
1728
- referencedPmIdCount += referencedIds.length;
1729
- for (const referencedId of referencedIds) {
1730
- referencedPmIds.add(referencedId);
1731
- if (!knownIds.has(referencedId.toLowerCase())) {
1732
- staleReferenceRows.push(summarizeCommandReferenceRow(item.id, referencedId, linkedTest.command));
1733
- }
1734
- }
1735
- }
1736
- const uniqueStaleReferenceRows = [...new Set(staleReferenceRows)].sort((left, right) => left.localeCompare(right));
1737
- const stalePmIds = [
1738
- ...new Set(uniqueStaleReferenceRows.map((row) => row.split(":")[1] ?? "")),
1739
- ]
1740
- .filter((value) => value.length > 0)
1741
- .sort((left, right) => left.localeCompare(right));
1742
- const warnings = [];
1743
- if (uniqueStaleReferenceRows.length > 0) {
1744
- warnings.push(`validate_command_references_stale_pm_ids:${uniqueStaleReferenceRows.length}`);
1745
- }
1746
- if (untrustedRows.length > 0) {
1747
- warnings.push(`validate_linked_test_trust_unacknowledged:${untrustedRows.length}`);
1748
- }
1749
- const diagnosticLimit = verboseDiagnostics
1750
- ? Number.POSITIVE_INFINITY
1751
- : DIAGNOSTIC_LIST_SUMMARY_LIMIT;
1752
- const summarizedRows = summarizeList(uniqueStaleReferenceRows, diagnosticLimit);
1753
- const summarizedStalePmIds = summarizeList(stalePmIds, diagnosticLimit);
1754
- const summarizedReferencedPmIds = summarizeList([...referencedPmIds].sort((left, right) => left.localeCompare(right)), diagnosticLimit);
1755
- const summarizedUntrustedRows = summarizeList(untrustedRows, diagnosticLimit);
1756
- return {
1757
- check: {
1758
- name: "command_references",
1759
- status: warnings.length === 0 ? "ok" : "warn",
1760
- ok: warnings.length === 0,
1761
- details: {
1762
- checked_items: items.length,
1763
- linked_commands_scanned: linkedCommandsScanned,
1764
- referenced_pm_ids_count: referencedPmIdCount,
1765
- unique_referenced_pm_ids_count: referencedPmIds.size,
1766
- unique_referenced_pm_ids: summarizedReferencedPmIds.values,
1767
- unique_referenced_pm_ids_truncated: summarizedReferencedPmIds.truncated,
1768
- linked_test_trust_current_source_ref: currentSourceRef ?? null,
1769
- untrusted_linked_tests_count: untrustedRows.length,
1770
- untrusted_linked_tests: summarizedUntrustedRows.values,
1771
- untrusted_linked_tests_truncated: summarizedUntrustedRows.truncated,
1772
- stale_pm_id_references_count: uniqueStaleReferenceRows.length,
1773
- stale_pm_ids_count: stalePmIds.length,
1774
- stale_pm_ids: summarizedStalePmIds.values,
1775
- stale_pm_ids_truncated: summarizedStalePmIds.truncated,
1776
- stale_pm_id_reference_rows: summarizedRows.values,
1777
- stale_pm_id_reference_rows_truncated: summarizedRows.truncated,
1778
- },
1779
- },
1780
- warnings,
1781
- };
1782
- }
1783
- /* c8 ignore stop */
1784
- /* c8 ignore start -- format-version diagnostics projection is covered by validate format-version integration fixtures */
1785
- function buildFormatVersionCheck(items, verboseDiagnostics) {
1786
- const scan = scanItemFormatVersions(items.map((item) => ({
1787
- ref: item.id,
1788
- version: effectiveItemFormatVersion(item),
1789
- })));
1790
- const warnings = [];
1791
- if (scan.outdated.length > 0) {
1792
- warnings.push(`validate_format_version_outdated_items:${scan.outdated.length}`);
1793
- }
1794
- if (scan.ahead.length > 0) {
1795
- warnings.push(`validate_format_version_ahead_items:${scan.ahead.length}`);
1796
- }
1797
- const diagnosticLimit = verboseDiagnostics
1798
- ? Number.POSITIVE_INFINITY
1799
- : DIAGNOSTIC_LIST_SUMMARY_LIMIT;
1800
- const summarizedOutdated = summarizeList(scan.outdated, diagnosticLimit);
1801
- const summarizedAhead = summarizeList(scan.ahead, diagnosticLimit);
1802
- // `ahead` items were written by a newer pm than this runtime: validation
1803
- // cannot vouch for fields it does not understand, so it is an error (upgrade
1804
- // pm). `outdated` items are a non-fatal warning (a migration would rewrite
1805
- // them). At the current baseline version neither list is populated.
1806
- const status = scan.ahead.length > 0 ? "error" : scan.outdated.length > 0 ? "warn" : "ok";
1807
- return {
1808
- check: {
1809
- name: "format_version",
1810
- status,
1811
- ok: status === "ok",
1812
- details: {
1813
- checked_items: items.length,
1814
- current_format_version: CURRENT_ITEM_FORMAT_VERSION,
1815
- outdated_items_count: scan.outdated.length,
1816
- outdated_items: summarizedOutdated.values,
1817
- outdated_items_truncated: summarizedOutdated.truncated,
1818
- ahead_items_count: scan.ahead.length,
1819
- ahead_items: summarizedAhead.values,
1820
- ahead_items_truncated: summarizedAhead.truncated,
1821
- },
1822
- },
1823
- warnings,
1824
- };
1825
- }
1826
- /* c8 ignore stop */
1827
- const VALIDATE_AUTO_FIX_MESSAGE = "pm validate auto-fix";
1828
- /** Apply one planned fix through the SAME audited command paths an operator would use by hand (`pm update` / `pm files --remove` / `pm docs --remove`), so every applied fix carries normal history, locking, and hook behavior. Command modules are imported lazily: plain validate runs stay read-only and never pay the mutation-stack import cost. */
1829
- /* c8 ignore start -- lazy mutation-command dispatch branches are covered by validate auto-fix integration tests */
1830
- async function applyValidateFix(fix, global, services) {
1831
- switch (fix.kind) {
1832
- case "set_resolution":
1833
- case "set_close_reason":
1834
- case "set_estimate":
1835
- case "reparent":
1836
- case "unset_parent": {
1837
- const updateOptions = {
1838
- message: VALIDATE_AUTO_FIX_MESSAGE,
1839
- };
1840
- if (fix.kind === "set_resolution") {
1841
- updateOptions.resolution = fix.value;
1842
- }
1843
- else if (fix.kind === "set_close_reason") {
1844
- updateOptions.closeReason = fix.value;
1845
- }
1846
- else if (fix.kind === "set_estimate") {
1847
- updateOptions.estimatedMinutes = fix.value;
1848
- }
1849
- else if (fix.kind === "reparent") {
1850
- updateOptions.parent = fix.parent_id;
1851
- }
1852
- else {
1853
- updateOptions.unset = ["parent"];
1854
- }
1855
- if (!services.runUpdate) {
1856
- throw new PmCliError("Applying validate metadata/lifecycle fixes requires a runUpdate mutation service.", EXIT_CODE.USAGE);
1857
- }
1858
- await services.runUpdate(fix.item_id, updateOptions, global);
1859
- return;
1860
- }
1861
- case "prune_file_link": {
1862
- throw new Error(`Unsupported non-batched fix kind: ${fix.kind}`);
1863
- }
1864
- case "prune_doc_link": {
1865
- throw new Error(`Unsupported non-batched fix kind: ${fix.kind}`);
1866
- }
1867
- }
1868
- }
1869
- /* c8 ignore stop */
1870
- function pruneBatchKey(fix) {
1871
- if (fix.kind !== "prune_file_link" && fix.kind !== "prune_doc_link") {
1872
- return null;
1873
- }
1874
- return `${fix.kind}:${fix.item_id}`;
1875
- }
1876
- /* c8 ignore start -- batched prune/apply failure fan-out permutations are covered by auto-fix integration suites */
1877
- async function applyValidateFixes(applicable, global, services) {
1878
- const applied = [];
1879
- const failed = [];
1880
- const pruneBatches = new Map();
1881
- for (const fix of applicable.filter((candidate) => pruneBatchKey(candidate) === null)) {
1882
- try {
1883
- await applyValidateFix(fix, global, services);
1884
- applied.push(fix);
1885
- }
1886
- catch (error) {
1887
- failed.push({ fix, error });
1888
- }
1889
- }
1890
- for (const fix of applicable.filter((candidate) => pruneBatchKey(candidate) !== null)) {
1891
- const batchKey = pruneBatchKey(fix);
1892
- const existing = pruneBatches.get(batchKey);
1893
- if (existing) {
1894
- existing.push(fix);
1895
- }
1896
- else {
1897
- pruneBatches.set(batchKey, [fix]);
1898
- }
1899
- }
1900
- for (const batch of pruneBatches.values()) {
1901
- const first = batch[0];
1902
- /* c8 ignore next 2 -- pruneBatches values are only created with at least one fix */
1903
- if (!first) {
1904
- continue;
1905
- }
1906
- const remove = batch
1907
- .map((fix) => fix.path)
1908
- .filter((value) => typeof value === "string" && value.length > 0);
1909
- try {
1910
- if (first.kind === "prune_file_link") {
1911
- await runFiles(first.item_id, { remove, message: VALIDATE_AUTO_FIX_MESSAGE }, global);
1912
- }
1913
- else {
1914
- await runDocs(first.item_id, { remove, message: VALIDATE_AUTO_FIX_MESSAGE }, global);
1915
- }
1916
- applied.push(...batch);
1917
- }
1918
- catch (error) {
1919
- failed.push(...batch.map((fix) => ({ fix, error })));
1920
- }
1921
- }
1922
- return { applied, failed };
1923
- }
1924
- /* c8 ignore stop */
1925
- /** Public contract for test only validate command, shared by SDK and presentation-layer consumers. */
1926
- export const _testOnlyValidateCommand = {
1927
- applyValidateFix,
1928
- applyValidateFixes,
1929
- attachValidateFixHints,
1930
- buildCommandReferencesCheck,
1931
- buildDependencyReferencesCheck,
1932
- buildCloseReasonBackfillRows,
1933
- buildFilesCheck,
1934
- buildLifecycleCheck,
1935
- buildLifecycleDependencyGraph,
1936
- buildLifecycleParentGraph,
1937
- buildEstimateBackfillRows,
1938
- buildMetadataCounts,
1939
- buildMissingFieldOccurrences,
1940
- buildOrphanedPathRows,
1941
- classifyOrphanedPath,
1942
- collectDefaultProjectFileCandidates,
1943
- collectTrackedGitFileCandidates,
1944
- detectLifecycleDependencyCycles,
1945
- detectLifecycleParentCycles,
1946
- escapeRegExp,
1947
- extractItemIds,
1948
- findLifecycleDependencyCycleComponents,
1949
- isMetadataFieldMissing,
1950
- linkedArtifactPathExceedsFilesystemLimits,
1951
- listFilesRecursive,
1952
- resolveDependencyCycleSeverity,
1953
- resolveParentCycleSeverity,
1954
- resolveFileScanMode,
1955
- resolveLifecycleDependencyCycleSamplePath,
1956
- resolveRequestedChecks,
1957
- resolveValidateMetadataProfile,
1958
- resolveWorkspaceRoot,
1959
- sharedDirectoryPrefixLength,
1960
- summarizeOrphanedPathRows,
1961
- summarizeDuplicateIssueCodes,
1962
- toMeaningfulString,
1963
- };
1964
- function appendInvalidMergeReceiptEvidenceWarning(warnings, invalidEvidenceCount) {
1965
- if (invalidEvidenceCount > 0) {
1966
- warnings.push(`merge_receipt_evidence_invalid:${invalidEvidenceCount}`);
1967
- }
1968
- }
1969
- /** Build the post-merge storage-integrity check (GH-603/GH-607/GH-609/GH-611): provably corrupted storage — unreadable item documents, on-disk-vs-parsed census mismatches, history conflict markers, resurrected deletes, and unparseable config/schema files — fails validate as an error instead of hiding behind ok:true. The `history_repair_reconciliations` count is informational: it surfaces post-repair divergence without permanently failing repaired workspaces. */
1970
- async function buildStorageIntegrityCheck(pmRoot, items, typeToFolder, settings) {
1971
- const scan = await scanStorageIntegrity(pmRoot, new Set(items.map((item) => item.id)), typeToFolder, {
1972
- schema: settings.schema,
1973
- extensionFieldNames: collectRegisteredItemFieldNames(getActiveExtensionRegistrations()),
1974
- });
1975
- const warnings = [];
1976
- if (scan.unreadable_item_files.length > 0) {
1977
- warnings.push(`validate_storage_unreadable_items:${scan.unreadable_item_files.length}`);
1978
- }
1979
- if (scan.duplicate_item_ids.length > 0) {
1980
- warnings.push(`validate_storage_duplicate_item_ids:${scan.duplicate_item_ids.length}`);
1981
- }
1982
- if (scan.history_conflict_marker_streams.length > 0) {
1983
- warnings.push(`validate_storage_history_conflict_markers:${scan.history_conflict_marker_streams.length}`);
1984
- }
1985
- if (scan.history_unparseable_streams.length > 0) {
1986
- warnings.push(`validate_storage_unparseable_history:${scan.history_unparseable_streams.length}`);
1987
- }
1988
- if (scan.resurrected_items.length > 0) {
1989
- warnings.push(`validate_storage_resurrected_items:${scan.resurrected_items.length}`);
1990
- }
1991
- if (scan.unparseable_config_files.length > 0) {
1992
- warnings.push(`validate_storage_unparseable_config:${scan.unparseable_config_files.length}`);
1993
- }
1994
- const errorCount = warnings.length;
1995
- const trackedRuntimeCache = await scanTrackedRuntimeCache(pmRoot);
1996
- if (trackedRuntimeCache.tracked_path_count > 0) {
1997
- warnings.push(`validate_storage_tracked_runtime_cache_files:${trackedRuntimeCache.tracked_path_count}`);
1998
- }
1999
- const fenceAudit = await auditMergeAttributeFence(pmRoot, resolveProjectMergeTypeFolders(settings));
2000
- if (fenceAudit.status === "drift") {
2001
- warnings.push(`validate_merge_fence_drift:${fenceAudit.missing_patterns.length + fenceAudit.stale_patterns.length}`);
2002
- }
2003
- const gitWorkspaceRoot = await findGitWorkspaceRoot(pmRoot);
2004
- const mergeDriverAudit = gitWorkspaceRoot === null
2005
- ? null
2006
- : await auditMergeDriverConfiguration(gitWorkspaceRoot);
2007
- if (mergeDriverAudit !== null && mergeDriverAudit.status !== "ok") {
2008
- warnings.push(`validate_merge_driver_configuration:${mergeDriverAudit.missing_keys.length + mergeDriverAudit.drifted_keys.length}`);
2009
- }
2010
- const mergeReceiptEvidence = await inspectMergeReceiptEvidence(gitWorkspaceRoot ?? pmRoot, {
2011
- includeLossless: true,
2012
- pmRoot,
2013
- });
2014
- const pendingMergeReceipts = mergeReceiptEvidence.receipts;
2015
- const { pendingDecisions: pendingMergeDecisions, lossless: losslessMergeReceipts, } = partitionMergeReceipts(pendingMergeReceipts);
2016
- if (pendingMergeDecisions.length > 0) {
2017
- warnings.push(`validate_merge_decisions_unreviewed:${pendingMergeDecisions.length}`);
2018
- }
2019
- appendInvalidMergeReceiptEvidenceWarning(warnings, mergeReceiptEvidence.invalid_evidence_count);
2020
- const status = errorCount > 0 ? "error" : warnings.length > 0 ? "warn" : "ok";
2021
- return {
2022
- check: {
2023
- name: "storage_integrity",
2024
- // Corruption findings fail the check; fence drift alone is a warning —
2025
- // the workspace data is intact, but future merges of uncovered paths
2026
- // would fall back to git's default text driver.
2027
- status,
2028
- ok: status === "ok",
2029
- details: {
2030
- ...scan,
2031
- merge_fence: fenceAudit,
2032
- merge_driver_configuration: mergeDriverAudit,
2033
- pending_merge_decision_count: pendingMergeDecisions.length,
2034
- pending_merge_decision_items: [
2035
- ...new Set(pendingMergeDecisions.map((receipt) => receipt.item_id)),
2036
- ].sort((left, right) => left.localeCompare(right)),
2037
- lossless_merge_receipt_count: losslessMergeReceipts.length,
2038
- invalid_merge_receipt_evidence_count: mergeReceiptEvidence.invalid_evidence_count,
2039
- invalid_merge_receipt_evidence_details: mergeReceiptEvidence.invalid_evidence,
2040
- invalid_merge_receipt_evidence_details_truncated: mergeReceiptEvidence.invalid_evidence_truncated,
2041
- lossless_merge_receipt_items: [
2042
- ...new Set(losslessMergeReceipts.map((receipt) => receipt.item_id)),
2043
- ].sort((left, right) => left.localeCompare(right)),
2044
- tracked_runtime_cache: {
2045
- tracker_relative_root: trackedRuntimeCache.tracker_relative_root,
2046
- tracked_paths: trackedRuntimeCache.tracked_paths,
2047
- tracked_path_count: trackedRuntimeCache.tracked_path_count,
2048
- remediation_command: trackedRuntimeCache.remediation_command,
2049
- },
2050
- },
2051
- },
2052
- warnings,
2053
- };
2054
- }
2055
- function recordValidateCheck(state, built, fixHintsEnabled) {
2056
- if (fixHintsEnabled) {
2057
- attachValidateFixHints(built.check, built.warnings);
2058
- }
2059
- state.checks.push(built.check);
2060
- state.warnings.push(...built.warnings);
2061
- }
2062
- /** Run only selected checks against one resolved workspace context and collect their typed remediation evidence. */
2063
- async function executeRequestedValidateChecks(params) {
2064
- const state = {
2065
- checks: [],
2066
- warnings: [...params.initialWarnings],
2067
- closeReasonBackfillRows: [],
2068
- estimateBackfillRows: [],
2069
- resolutionBackfillRows: [],
2070
- terminalParentFixRows: [],
2071
- staleLinkPruneRows: [],
2072
- };
2073
- const fixHintsEnabled = params.options.fixHints === true;
2074
- const fullDiagnostics = params.options.verboseDiagnostics === true ||
2075
- params.options.allAffectedIds === true ||
2076
- params.global.json === true;
2077
- if (params.requestedChecks.has("metadata")) {
2078
- const built = buildMetadataCheck(params.items, params.metadataPolicy, params.statusRegistry, fullDiagnostics, [params.settings.id_prefix]);
2079
- state.closeReasonBackfillRows = built.closeReasonBackfillRows;
2080
- state.estimateBackfillRows = built.estimateBackfillRows;
2081
- recordValidateCheck(state, built, fixHintsEnabled);
2082
- }
2083
- if (params.requestedChecks.has("resolution")) {
2084
- const built = buildResolutionCheck(params.items, params.statusRegistry, fullDiagnostics);
2085
- state.resolutionBackfillRows = built.resolutionBackfillRows;
2086
- recordValidateCheck(state, built, fixHintsEnabled);
2087
- }
2088
- if (params.requestedChecks.has("lifecycle")) {
2089
- const built = buildLifecycleCheck(params.items, Boolean(params.options.checkStaleBlockers), params.dependencyCycleSeverity, params.parentCycleSeverity, params.statusRegistry, params.lifecyclePatternPolicy, fullDiagnostics, params.settings.id_prefix);
2090
- state.terminalParentFixRows = built.terminalParentFixRows;
2091
- recordValidateCheck(state, built, fixHintsEnabled);
2092
- recordValidateCheck(state, buildDependencyReferencesCheck(params.items, fullDiagnostics, params.statusRegistry), fixHintsEnabled);
2093
- }
2094
- if (params.requestedChecks.has("files")) {
2095
- const built = await buildFilesCheck(params.items, params.workspaceRoot, params.pmRoot, params.fileScanMode, Boolean(params.options.includePmInternals), Boolean(params.options.verboseFileLists), params.statusRegistry);
2096
- state.staleLinkPruneRows = built.staleLinkPruneRows;
2097
- recordValidateCheck(state, built, fixHintsEnabled);
2098
- }
2099
- if (params.requestedChecks.has("command_references")) {
2100
- recordValidateCheck(state, await buildCommandReferencesCheck(params.items, params.settings.id_prefix, fullDiagnostics, params.pmRoot), fixHintsEnabled);
2101
- }
2102
- if (params.requestedChecks.has("history_drift")) {
2103
- recordValidateCheck(state, await buildValidateHistoryDriftCheck(params.pmRoot, params.items, fullDiagnostics), fixHintsEnabled);
2104
- }
2105
- if (params.requestedChecks.has("format_version")) {
2106
- recordValidateCheck(state, buildFormatVersionCheck(params.items, fullDiagnostics), fixHintsEnabled);
2107
- }
2108
- if (params.requestedChecks.has("storage_integrity")) {
2109
- recordValidateCheck(state, await buildStorageIntegrityCheck(params.pmRoot, params.items, resolveItemTypeRegistry(params.settings, getActiveExtensionRegistrations()).type_to_folder, params.settings), fixHintsEnabled);
2110
- }
2111
- return state;
2112
- }
2113
- function planValidateFixes(options, state, settings) {
2114
- const planned = [];
2115
- if (options.autoFix === true) {
2116
- planned.push(...planCloseReasonBackfillFixes(state.closeReasonBackfillRows));
2117
- planned.push(...planResolutionBackfillFixes(state.resolutionBackfillRows));
2118
- planned.push(...planEstimateBackfillFixes(state.estimateBackfillRows, settings.validation.estimate_defaults_by_type));
2119
- planned.push(...planTerminalParentFixes(state.terminalParentFixRows));
2120
- }
2121
- if (options.pruneMissing === true) {
2122
- planned.push(...planStaleLinkPruneFixes(state.staleLinkPruneRows));
2123
- }
2124
- return planned;
2125
- }
2126
- /* v8 ignore start -- validate fix-summary matrix is covered end-to-end; direct helper fallback branches are defensive */
2127
- async function buildValidateFixesSummary(options, state, settings, grantedFixScopes, global, services) {
2128
- if (options.autoFix !== true && options.pruneMissing !== true) {
2129
- return undefined;
2130
- }
2131
- const planned = planValidateFixes(options, state, settings);
2132
- const { applicable, gated } = partitionFixesByGrant(planned, grantedFixScopes);
2133
- const dryRun = options.dryRun === true;
2134
- const appliedFixRows = [];
2135
- const failedFixRows = [];
2136
- if (!dryRun) {
2137
- const applied = await applyValidateFixes(applicable, global, services);
2138
- appliedFixRows.push(...applied.applied.map(toFixOutputRow));
2139
- failedFixRows.push(...applied.failed.map(({ fix, error }) => ({
2140
- ...toFixOutputRow(fix),
2141
- error: error instanceof Error ? error.message : String(error),
2142
- })));
2143
- }
2144
- return {
2145
- mode: dryRun ? "dry_run" : "apply",
2146
- auto_fix: options.autoFix === true,
2147
- prune_missing: options.pruneMissing === true,
2148
- granted_fix_scopes: [...grantedFixScopes].sort((left, right) => left.localeCompare(right)),
2149
- planned_count: planned.length,
2150
- applied_count: appliedFixRows.length,
2151
- gated_count: gated.length,
2152
- failed_count: failedFixRows.length,
2153
- planned_fixes: planned.map(toFixOutputRow),
2154
- applied_fixes: appliedFixRows,
2155
- gated_fixes: gated.map((fix) => ({
2156
- ...toFixOutputRow(fix),
2157
- gate_hint: `Withheld: re-run with --fix-scope ${fix.gate} to apply.`,
2158
- })),
2159
- failed_fixes: failedFixRows,
2160
- };
2161
- }
2162
- /** Implements run validate for the public runtime surface of this module. */
2163
- export async function runValidate(options, global, services = {}) {
2164
- assertProjectionModeChoice(options.counts === true, options.full === true, "--counts");
2165
- const fixesRequested = options.autoFix === true || options.pruneMissing === true;
2166
- if (options.dryRun === true && !fixesRequested) {
2167
- throw new PmCliError("--dry-run requires --auto-fix or --prune-missing (there is nothing to preview otherwise).", EXIT_CODE.USAGE);
2168
- }
2169
- if (options.fixScope !== undefined &&
2170
- options.fixScope.length > 0 &&
2171
- options.autoFix !== true) {
2172
- throw new PmCliError("--fix-scope requires --auto-fix.", EXIT_CODE.USAGE);
2173
- }
2174
- // Resolved up-front so unknown --fix-scope values fail fast before any scan.
2175
- const grantedFixScopes = resolveGrantedFixScopes(options.fixScope);
2176
- const pmRoot = resolvePmRoot(process.cwd(), global.path);
2177
- await assertInitializedTracker(pmRoot);
2178
- const settings = await readSettings(pmRoot);
2179
- const statusRegistry = resolveRuntimeStatusRegistry(settings.schema);
2180
- const typeRegistry = resolveItemTypeRegistry(settings, getActiveExtensionRegistrations());
2181
- const requestedChecks = resolveRequestedChecks(options);
2182
- const itemReadWarnings = [];
2183
- const items = await readValidateItems({
2184
- includeBody: requestedChecks.has("history_drift") || requestedChecks.has("metadata"),
2185
- pmRoot,
2186
- settings,
2187
- typeToFolder: typeRegistry.type_to_folder,
2188
- warnings: itemReadWarnings,
2189
- });
2190
- if (requestedChecks.has("history_drift")) {
2191
- const authorAttribution = await scanHistoryAuthorAttribution(pmRoot);
2192
- if (authorAttribution.actionable_unknown_event_count > 0) {
2193
- itemReadWarnings.push(`validate_history_unknown_author_events:${authorAttribution.actionable_unknown_event_count}`);
2194
- }
2195
- }
2196
- const metadataProfileSource = typeof options.metadataProfile === "string" ? "option" : "settings";
2197
- const metadataProfile = resolveValidateMetadataProfile(typeof options.metadataProfile === "string"
2198
- ? options.metadataProfile
2199
- : settings.validation.metadata_profile);
2200
- const metadataPolicy = resolveValidateMetadataPolicy(metadataProfile, metadataProfileSource, settings.validation.metadata_required_fields);
2201
- const lifecyclePatternPolicy = resolveLifecyclePatternPolicy(settings);
2202
- const dependencyCycleSeverity = resolveDependencyCycleSeverity(options.dependencyCycleSeverity);
2203
- const parentCycleSeverity = resolveParentCycleSeverity(options.parentCycleSeverity);
2204
- const fileScanMode = resolveFileScanMode(options.scanMode);
2205
- const workspaceRoot = resolveWorkspaceRoot(pmRoot);
2206
- const state = await executeRequestedValidateChecks({
2207
- requestedChecks,
2208
- options,
2209
- global,
2210
- pmRoot,
2211
- workspaceRoot,
2212
- settings,
2213
- items,
2214
- statusRegistry,
2215
- metadataPolicy,
2216
- lifecyclePatternPolicy,
2217
- dependencyCycleSeverity,
2218
- parentCycleSeverity,
2219
- fileScanMode,
2220
- initialWarnings: [...new Set(itemReadWarnings)],
2221
- });
2222
- // Remediation phase (pm-c3sz / pm-8jss / pm-0v2m). Plans are derived from
2223
- // the findings of THIS run; checks above always report the pre-fix state.
2224
- // Safe field backfills apply by default under --auto-fix; gated lifecycle
2225
- // fixes are planned but withheld unless --fix-scope lifecycle grants them;
2226
- // --dry-run previews without mutating; failures never abort the run.
2227
- const fixes = fixesRequested
2228
- ? await buildValidateFixesSummary(options, state, settings, grantedFixScopes, global, services)
2229
- : undefined;
2230
- const normalizedWarnings = [...new Set(state.warnings)].sort((left, right) => left.localeCompare(right));
2231
- const hasErrors = state.checks.some((check) => check.status === "error");
2232
- const result = {
2233
- ok: !hasErrors,
2234
- has_warnings: normalizedWarnings.length > 0,
2235
- checks: state.checks,
2236
- warnings: normalizedWarnings,
2237
- ...(fixes !== undefined ? { fixes } : {}),
2238
- generated_at: nowIso(),
2239
- projection: {
2240
- mode: "full",
2241
- declared_field_groups: [
2242
- { name: "diagnostic_rows", restore_with: "--full" },
2243
- ],
2244
- included_field_groups: ["diagnostic_rows"],
2245
- },
2246
- };
2247
- if (options.counts !== true)
2248
- return result;
2249
- return {
2250
- ...projectValidateCounts(result),
2251
- projection: {
2252
- mode: "counts",
2253
- declared_field_groups: [
2254
- { name: "diagnostic_rows", restore_with: "--full" },
2255
- ],
2256
- included_field_groups: [],
2257
- },
2258
- };
2259
- }
2260
- /* c8 ignore stop */
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]="16aa1db1-b6f2-504b-aa4e-0e384ad1d7a5")}catch(e2){}})();import{assertInitializedTracker}from"../environment/tracker-preflight.js";import{readWorkflowCompletenessCheck}from"./workflow-completeness.js";import fs from"node:fs/promises";import{realpathSync}from"node:fs";import{execFile}from"node:child_process";import path from"node:path";import{promisify}from"node:util";import{buildRemediationCommands}from"../../core/diagnostics/remediation.js";import{getActiveExtensionRegistrations}from"../../core/extensions/index.js";import{collectRegisteredItemFieldNames}from"../../core/extensions/item-fields.js";import{isFileMissingError}from"../../core/fs/fs-utils.js";import{normalizeStatusInput}from"../../core/item/status.js";import{resolveItemTypeRegistry}from"../../core/item/type-registry.js";import{resolveRuntimeStatusRegistry}from"../../core/schema/runtime-schema.js";import{DEFAULT_VALIDATE_CLOSURE_LIKE_METADATA_FIELD_PATTERNS,DEFAULT_VALIDATE_STALE_BLOCKER_REASON_PATTERNS,EXIT_CODE,PM_DIRNAME,SETTINGS_DEFAULTS}from"../../core/shared/constants.js";import{PmCliError}from"../../core/shared/errors.js";import{toNonEmptyStringOrUndefined}from"../../core/shared/primitives.js";import{nowIso}from"../../core/shared/time.js";import{CURRENT_ITEM_FORMAT_VERSION,effectiveItemFormatVersion,scanItemFormatVersions}from"../../core/item/item-format-version.js";import{resolvePmRoot}from"../../core/store/paths.js";import{readSettings}from"../../core/store/settings.js";import{partitionFixesByGrant,planCloseReasonBackfillFixes,planEstimateBackfillFixes,planResolutionBackfillFixes,planStaleLinkPruneFixes,planTerminalParentFixes,resolveGrantedFixScopes,toFixOutputRow}from"../../core/validate/fix-planning.js";import{findDuplicateIssueCodes}from"../../core/governance/issue-codes.js";import{buildMissingByTypeCounts}from"../../core/validate/missing-by-type.js";import{classifyStaleLinkedPaths,summarizeStaleLinkedPathClassifications}from"../../core/validate/stale-file-classification.js";import{isRemoteLinkedArtifactReference}from"../../core/validate/linked-artifact-reference.js";import{assertProjectionModeChoice}from"../output-projection.js";import{collectDanglingDependencyReferences}from"../graph/assembly.js";import{auditMergeAttributeFence,auditMergeDriverConfiguration,findGitWorkspaceRoot,resolveProjectMergeTypeFolders}from"../merge/install.js";import{inspectMergeReceiptEvidence,partitionMergeReceipts}from"../merge/receipts.js";import{buildLinkedFileRepairReport}from"./linked-file-report.js";import{scanStorageIntegrity}from"./storage-integrity.js";import{scanTrackedRuntimeCache}from"./tracked-runtime-cache.js";import{scanHistoryAuthorAttribution}from"../author-attribution.js";import{createRelationshipKindRegistry}from"../relationships.js";import{runDocs}from"../docs.js";import{runFiles}from"../files.js";import{extractReferencedPmItemIdsFromCommand}from"../test/linked-command-detection.js";import{resolveLinkedTestSourceRef,resolveLinkedTestSourceWorkspaceRoot,resolveLinkedTestTrustBatch}from"../test/trust.js";import{readValidateItems}from"./validate-item-reader.js";import{buildValidateHistoryDriftCheck}from"./validate-history-drift.js";import{buildLifecycleParentGraph,detectLifecycleParentCycles,resolveLifecycleDependencyCycleSamplePath}from"./hierarchy-validation.js";import{buildStatusRoleWarnings,inspectStatusRoleAssignments}from"./status-role-diagnostics.js";import{normalizeRelativeDirectoryPath,normalizeRelativePath,toMeaningfulString}from"./validate-normalization.js";const FILE_SCAN_DIRECTORIES=["src","tests","docs"];const FILE_SCAN_ROOT_FILES=["README.md","PRD.md","CHANGELOG.md","AGENTS.md","CONTRIBUTING.md","SECURITY.md","CODE_OF_CONDUCT.md","LICENSE"];const DIRECTORY_IGNORE_SET=new Set(["node_modules",".git",".cursor",".agents","dist","coverage"]);const RESOLUTION_FIELD_KEYS=["resolution","expected_result","actual_result"];const VALIDATE_FILE_SCAN_MODES=["default","tracked-all","tracked-all-strict"];const VALIDATE_METADATA_PROFILE_VALUES=["core","strict","custom"];const VALIDATE_DEPENDENCY_CYCLE_SEVERITY_VALUES=["off","warn","error"];const CORE_METADATA_REQUIRED_FIELDS=["author","acceptance_criteria","estimated_minutes","close_reason"];const STRICT_METADATA_REQUIRED_FIELDS=[...CORE_METADATA_REQUIRED_FIELDS,"reviewer","risk","confidence","sprint","release"];const SUPPORTED_METADATA_REQUIRED_FIELDS=Array.from(new Set(STRICT_METADATA_REQUIRED_FIELDS));const METADATA_REQUIRED_FIELD_ALIASES={author:"author",acceptance_criteria:"acceptance_criteria","acceptance-criteria":"acceptance_criteria",estimated_minutes:"estimated_minutes","estimated-minutes":"estimated_minutes",estimate:"estimated_minutes",close_reason:"close_reason","close-reason":"close_reason",reviewer:"reviewer",risk:"risk",confidence:"confidence",sprint:"sprint",release:"release"};const METADATA_WARNING_TOKEN_BY_FIELD={author:"validate_metadata_missing_author",acceptance_criteria:"validate_metadata_missing_acceptance_criteria",estimated_minutes:"validate_metadata_missing_estimate",close_reason:"validate_metadata_missing_close_reason",reviewer:"validate_metadata_missing_reviewer",risk:"validate_metadata_missing_risk",confidence:"validate_metadata_missing_confidence",sprint:"validate_metadata_missing_sprint",release:"validate_metadata_missing_release"};const METADATA_COUNT_KEY_BY_FIELD={author:"missing_author",acceptance_criteria:"missing_acceptance_criteria",estimated_minutes:"missing_estimated_minutes",close_reason:"closed_missing_close_reason",reviewer:"missing_reviewer",risk:"missing_risk",confidence:"missing_confidence",sprint:"missing_sprint",release:"missing_release"};const METADATA_ITEM_IDS_KEY_BY_FIELD={author:"missing_author_item_ids",acceptance_criteria:"missing_acceptance_criteria_item_ids",estimated_minutes:"missing_estimated_minutes_item_ids",close_reason:"closed_missing_close_reason_item_ids",reviewer:"missing_reviewer_item_ids",risk:"missing_risk_item_ids",confidence:"missing_confidence_item_ids",sprint:"missing_sprint_item_ids",release:"missing_release_item_ids"};const METADATA_TRUNCATED_KEY_BY_FIELD={author:"missing_author_truncated",acceptance_criteria:"missing_acceptance_criteria_truncated",estimated_minutes:"missing_estimated_minutes_truncated",close_reason:"closed_missing_close_reason_truncated",reviewer:"missing_reviewer_truncated",risk:"missing_risk_truncated",confidence:"missing_confidence_truncated",sprint:"missing_sprint_truncated",release:"missing_release_truncated"};const GIT_LS_FILES_MAX_BUFFER=32*1024*1024;const GIT_LS_FILES_TIMEOUT_MS=1e4;const FILE_LIST_SUMMARY_LIMIT=40;const DIAGNOSTIC_LIST_SUMMARY_LIMIT=5;const LINKED_ARTIFACT_MAX_PATH_LENGTH=4096;const LINKED_ARTIFACT_MAX_SEGMENT_LENGTH=255;const execFileAsync=promisify(execFile);function projectValidateCountsRecord(record){const projected={};for(const[key,value]of Object.entries(record)){if(Array.isArray(value)||value===false&&key.endsWith("_truncated")){continue}projected[key]=typeof value==="object"&&value!==null?projectValidateCountsRecord(value):value}return projected}function projectValidateCounts(result){return{...result,checks:result.checks.map(check=>({...check,details:projectValidateCountsRecord(check.details)})),...result.fixes?{fixes:{mode:result.fixes.mode,auto_fix:result.fixes.auto_fix,prune_missing:result.fixes.prune_missing,planned_count:result.fixes.planned_count,applied_count:result.fixes.applied_count,gated_count:result.fixes.gated_count,failed_count:result.fixes.failed_count}}:{}}}function normalizeStatusForRegistry(status,statusRegistry){return normalizeStatusInput(status,statusRegistry)??status}function isTerminalStatus(status,statusRegistry){return statusRegistry.terminal_statuses.has(normalizeStatusForRegistry(status,statusRegistry))}function normalizeLifecyclePatternList(values){return[...new Set((values??[]).map(value=>value.trim().toLowerCase()).filter(value=>value.length>0))].sort((left,right)=>left.localeCompare(right))}function areSortedStringListsEqual(left,right){if(left.length!==right.length){return false}return left.every((value,index)=>value===right[index])}function resolveLifecyclePatternPolicy(settings){const defaultStalePatterns=normalizeLifecyclePatternList(DEFAULT_VALIDATE_STALE_BLOCKER_REASON_PATTERNS);const defaultClosureLikePatterns={blocked_reason:normalizeLifecyclePatternList(DEFAULT_VALIDATE_CLOSURE_LIKE_METADATA_FIELD_PATTERNS.blocked_reason),resolution:normalizeLifecyclePatternList(DEFAULT_VALIDATE_CLOSURE_LIKE_METADATA_FIELD_PATTERNS.resolution),actual_result:normalizeLifecyclePatternList(DEFAULT_VALIDATE_CLOSURE_LIKE_METADATA_FIELD_PATTERNS.actual_result)};const staleBlockerReasonPatterns=normalizeLifecyclePatternList(settings.validation.lifecycle_stale_blocker_reason_patterns);const closureLikePatterns={blocked_reason:normalizeLifecyclePatternList(settings.validation.lifecycle_closure_like_blocked_reason_patterns),resolution:normalizeLifecyclePatternList(settings.validation.lifecycle_closure_like_resolution_patterns),actual_result:normalizeLifecyclePatternList(settings.validation.lifecycle_closure_like_actual_result_patterns)};return{stale_blocker_reason_patterns:staleBlockerReasonPatterns,stale_blocker_reason_pattern_source:areSortedStringListsEqual(staleBlockerReasonPatterns,defaultStalePatterns)?"default":"settings",closure_like_metadata_field_patterns:closureLikePatterns,closure_like_metadata_field_pattern_sources:{blocked_reason:areSortedStringListsEqual(closureLikePatterns.blocked_reason,defaultClosureLikePatterns.blocked_reason)?"default":"settings",resolution:areSortedStringListsEqual(closureLikePatterns.resolution,defaultClosureLikePatterns.resolution)?"default":"settings",actual_result:areSortedStringListsEqual(closureLikePatterns.actual_result,defaultClosureLikePatterns.actual_result)?"default":"settings"}}}function resolveValidateMetadataProfile(value){const normalized=value?.trim().toLowerCase();if(!normalized||normalized.length===0){return"core"}if(VALIDATE_METADATA_PROFILE_VALUES.includes(normalized)){return normalized}throw new PmCliError(`Unknown --metadata-profile value "${value}". Supported values: ${VALIDATE_METADATA_PROFILE_VALUES.join(", ")}.`,EXIT_CODE.USAGE)}function resolveDependencyCycleSeverity(value){const normalized=value?.trim().toLowerCase();if(!normalized||normalized.length===0){return"warn"}if(VALIDATE_DEPENDENCY_CYCLE_SEVERITY_VALUES.includes(normalized)){return normalized}throw new PmCliError(`Unknown --dependency-cycle-severity value "${value}". Supported values: ${VALIDATE_DEPENDENCY_CYCLE_SEVERITY_VALUES.join(", ")}.`,EXIT_CODE.USAGE)}function resolveParentCycleSeverity(value){const normalized=value?.trim().toLowerCase();if(!normalized||normalized.length===0){return"warn"}if(VALIDATE_DEPENDENCY_CYCLE_SEVERITY_VALUES.includes(normalized)){return normalized}throw new PmCliError(`Unknown --parent-cycle-severity value "${value}". Supported values: ${VALIDATE_DEPENDENCY_CYCLE_SEVERITY_VALUES.join(", ")}.`,EXIT_CODE.USAGE)}function normalizeMetadataRequiredFieldsFromSettings(values){const normalized=[...new Set((values??[]).map(value=>value.trim().toLowerCase().replaceAll("-","_")))];return normalized.map(value=>METADATA_REQUIRED_FIELD_ALIASES[value]).filter(value=>value!==void 0).sort((left,right)=>left.localeCompare(right))}function resolveValidateMetadataPolicy(profile,profileSource,configuredCustomFields){const normalizedCustomFields=normalizeMetadataRequiredFieldsFromSettings(configuredCustomFields);if(profile==="core"){return{profile,profile_source:profileSource,required_fields:[...CORE_METADATA_REQUIRED_FIELDS],configured_custom_fields:normalizedCustomFields,fallback_to_core:false,warnings:[]}}if(profile==="strict"){return{profile,profile_source:profileSource,required_fields:[...STRICT_METADATA_REQUIRED_FIELDS],configured_custom_fields:normalizedCustomFields,fallback_to_core:false,warnings:[]}}if(normalizedCustomFields.length>0){return{profile,profile_source:profileSource,required_fields:normalizedCustomFields,configured_custom_fields:normalizedCustomFields,fallback_to_core:false,warnings:[]}}return{profile,profile_source:profileSource,required_fields:[...CORE_METADATA_REQUIRED_FIELDS],configured_custom_fields:normalizedCustomFields,fallback_to_core:true,warnings:["validate_metadata_custom_profile_missing_required_fields:0"]}}const TERMINAL_EXEMPT_PLANNING_FIELDS=new Set(["acceptance_criteria","estimated_minutes"]);function isMetadataFieldMissing(item,field,statusRegistry,enforcePlanningFieldsOnTerminal){if(!enforcePlanningFieldsOnTerminal&&TERMINAL_EXEMPT_PLANNING_FIELDS.has(field)&&isTerminalStatus(item.status,statusRegistry)){return false}if(field==="author"){return!toNonEmptyStringOrUndefined(item.author)}if(field==="acceptance_criteria"){return!toNonEmptyStringOrUndefined(item.acceptance_criteria)}if(field==="estimated_minutes"){return!Number.isFinite(item.estimated_minutes)}if(field==="close_reason"){return normalizeStatusForRegistry(item.status,statusRegistry)===statusRegistry.close_status&&!toNonEmptyStringOrUndefined(item.close_reason)}if(field==="reviewer"){return!toNonEmptyStringOrUndefined(item.reviewer)}if(field==="risk"){return!toNonEmptyStringOrUndefined(item.risk)}if(field==="confidence"){if(typeof item.confidence==="number"){return!Number.isFinite(item.confidence)}return!toNonEmptyStringOrUndefined(item.confidence)}if(field==="sprint"){return!toNonEmptyStringOrUndefined(item.sprint)}return!toNonEmptyStringOrUndefined(item.release)}function resolveFileScanMode(scanMode){if(scanMode===void 0){return"default"}const normalized=scanMode.trim().toLowerCase();if(normalized.length===0){return"default"}if(normalized==="default"){return"default"}if(normalized==="tracked-all"||normalized==="tracked_all"){return"tracked-all"}if(normalized==="tracked-all-strict"||normalized==="tracked_all_strict"){return"tracked-all-strict"}throw new PmCliError(`Unknown --scan-mode value "${scanMode}". Supported values: ${VALIDATE_FILE_SCAN_MODES.join(", ")}.`,EXIT_CODE.USAGE)}function resolveWorkspaceRoot(pmRoot){const sourceWorkspaceRoot=process.env.PM_SOURCE_WORKSPACE_ROOT?.trim();if(sourceWorkspaceRoot){return path.resolve(sourceWorkspaceRoot)}const resolvedPmRoot=path.resolve(pmRoot);const normalizedPmRoot=resolvedPmRoot.replaceAll("\\","/");if(normalizedPmRoot.endsWith("/.agents/pm")){return path.dirname(path.dirname(resolvedPmRoot))}const resolvedCwd=path.resolve(process.cwd());const canonicalPmRoot=realpathForWorkspaceRoot(resolvedPmRoot);const canonicalCwd=realpathForWorkspaceRoot(resolvedCwd);const relativeFromPmRoot=path.relative(canonicalPmRoot,canonicalCwd);const cwdInsidePmRoot=relativeFromPmRoot.length===0||!relativeFromPmRoot.startsWith("..")&&!path.isAbsolute(relativeFromPmRoot);if(cwdInsidePmRoot){return resolvedPmRoot}return resolvedCwd}function realpathForWorkspaceRoot(inputPath){try{return realpathSync.native(inputPath)}catch{return path.resolve(inputPath)}}function hasFilesystemErrorCode(error,acceptedCodes){return typeof error==="object"&&error!==null&&"code"in error&&typeof error.code==="string"&&acceptedCodes.includes(error.code)}async function listFilesRecursive(basePath,relativePath,output){const targetDirectory=relativePath.length>0?path.join(basePath,relativePath):basePath;let entries;try{entries=await fs.readdir(targetDirectory,{withFileTypes:true})}catch(error){if(isFileMissingError(error)){return}throw error}for(const entry of entries){if(entry.name.startsWith(".")){continue}const childRelative=relativePath.length>0?path.join(relativePath,entry.name):entry.name;if(entry.isDirectory()){if(DIRECTORY_IGNORE_SET.has(entry.name)){continue}await listFilesRecursive(basePath,childRelative,output);continue}if(!entry.isFile()){continue}output.push(normalizeRelativePath(childRelative))}}async function collectDefaultProjectFileCandidates(workspaceRoot){const discovered=[];for(const directory of FILE_SCAN_DIRECTORIES){await listFilesRecursive(workspaceRoot,directory,discovered)}for(const candidate of FILE_SCAN_ROOT_FILES){const absolute=path.join(workspaceRoot,candidate);try{const stats=await fs.stat(absolute);if(stats.isFile()){discovered.push(normalizeRelativePath(candidate))}}catch{}}return[...new Set(discovered)].sort((left,right)=>left.localeCompare(right))}async function collectTrackedGitFileCandidates(workspaceRoot){try{const{stdout}=await execFileAsync("git",["ls-files","-z"],{cwd:workspaceRoot,encoding:"utf8",maxBuffer:GIT_LS_FILES_MAX_BUFFER,windowsHide:true,timeout:GIT_LS_FILES_TIMEOUT_MS});const discovered=stdout.split("\0").map(value=>normalizeRelativePath(value)).filter(value=>value.length>0);return[...new Set(discovered)].sort((left,right)=>left.localeCompare(right))}catch{return null}}function resolvePmInternalCandidatePrefixes(pmRoot,workspaceRoot){const prefixes=new Set;const configuredDefault=normalizeRelativeDirectoryPath(PM_DIRNAME);if(configuredDefault.length>0){prefixes.add(configuredDefault)}const relativePmRoot=normalizeRelativeDirectoryPath(path.relative(workspaceRoot,pmRoot));if(relativePmRoot.length>0&&!relativePmRoot.startsWith("..")){prefixes.add(relativePmRoot)}return[...prefixes].sort((left,right)=>left.localeCompare(right))}function hasPathPrefix(candidate,prefixes){for(const prefix of prefixes){if(candidate===prefix||candidate.startsWith(`${prefix}/`)){return true}}return false}async function collectProjectFileCandidates(workspaceRoot,scanMode){if(scanMode==="tracked-all"||scanMode==="tracked-all-strict"){const trackedCandidates=await collectTrackedGitFileCandidates(workspaceRoot);if(trackedCandidates){return{requestedMode:scanMode,appliedMode:scanMode,source:"tracked-git",candidateFiles:trackedCandidates,candidateTotal:trackedCandidates.length,candidateScanned:trackedCandidates.length}}const fallbackCandidates=await collectDefaultProjectFileCandidates(workspaceRoot);return{requestedMode:scanMode,appliedMode:"default",source:"tracked-all-fallback-default",candidateFiles:fallbackCandidates,candidateTotal:fallbackCandidates.length,candidateScanned:fallbackCandidates.length}}const defaultCandidates=await collectDefaultProjectFileCandidates(workspaceRoot);return{requestedMode:scanMode,appliedMode:"default",source:"default-curated",candidateFiles:defaultCandidates,candidateTotal:defaultCandidates.length,candidateScanned:defaultCandidates.length}}function summarizeList(values,limit=DIAGNOSTIC_LIST_SUMMARY_LIMIT){if(values.length<=limit){return{values,truncated:false}}return{values:values.slice(0,limit),truncated:true}}function summarizeFileList(values,verboseFileLists){if(verboseFileLists){return{values,truncated:false,total:values.length}}const summary=summarizeList(values,FILE_LIST_SUMMARY_LIMIT);return{values:summary.values,truncated:summary.truncated,total:values.length}}const RESOLUTION_REMEDIATION_FLAG_BY_FIELD={resolution:"--resolution",expected_result:"--expected-result",actual_result:"--actual-result"};const RESOLUTION_REMEDIATION_PLACEHOLDER_BY_FIELD={resolution:"Describe how this item was resolved",expected_result:"Describe the expected result",actual_result:"Describe the actual result"};function buildResolutionRemediationCommand(row){const fieldArguments=row.missing_fields.map(field=>`${RESOLUTION_REMEDIATION_FLAG_BY_FIELD[field]} "${RESOLUTION_REMEDIATION_PLACEHOLDER_BY_FIELD[field]}"`).join(" ");return`pm update ${row.id} ${fieldArguments} --message "Backfill resolution metadata"`}function attachValidateFixHints(check,checkWarnings){const existingResolutionHints=check.details?.missing_resolution_remediation_hints;const aliasedResolution=Array.isArray(existingResolutionHints)&&existingResolutionHints.length>0;const fixHints=aliasedResolution?existingResolutionHints.filter(hint=>typeof hint==="string"):buildRemediationCommands(checkWarnings);if(fixHints.length===0){return}const truncated=aliasedResolution&&check.details?.missing_resolution_remediation_hints_truncated===true;check.details={...check.details,fix_hints:fixHints,...truncated?{fix_hints_truncated:true}:{}}}function resolveRequestedChecks(options){const requested=new Set;if(options.checkCompleteness)requested.add("completeness");if(options.checkMetadata){requested.add("metadata")}if(options.checkResolution){requested.add("resolution")}if(options.checkLifecycle||options.checkStaleBlockers){requested.add("lifecycle")}if(options.checkFiles){requested.add("files")}if(options.checkHistoryDrift){requested.add("history_drift")}if(options.checkCommandReferences){requested.add("command_references")}if(options.checkStorageIntegrity){requested.add("storage_integrity")}if(requested.size===0){if(options.autoFix){requested.add("metadata");requested.add("resolution");requested.add("lifecycle")}if(options.pruneMissing){requested.add("files")}if(requested.size===0){requested.add("metadata");requested.add("resolution");requested.add("lifecycle");requested.add("files");requested.add("command_references");requested.add("history_drift");requested.add("format_version");requested.add("completeness");requested.add("storage_integrity")}return requested}if(options.pruneMissing){requested.add("files")}return requested}const DUPLICATE_ISSUE_CODE_WARNING_TOKEN="validate_metadata_duplicate_issue_codes";function summarizeDuplicateIssueCodes(duplicates,verboseDiagnostics){if(duplicates.length===0){return{rows:[],truncated:false,warnings:[]}}const limit=verboseDiagnostics?duplicates.length:DIAGNOSTIC_LIST_SUMMARY_LIMIT;const shown=duplicates.slice(0,limit);const rows=shown.map(duplicate=>({code:duplicate.code,count:duplicate.count,ids:duplicate.ids,titles:duplicate.titles,remediation_hint:`Items ${duplicate.ids.join(", ")} share issue code "${duplicate.code}"; rename or merge so each logical code maps to one item.`}));return{rows,truncated:shown.length<duplicates.length,warnings:[`${DUPLICATE_ISSUE_CODE_WARNING_TOKEN}:${duplicates.length}`]}}function collectMissingMetadataByField(items,statusRegistry,enforcePlanningFieldsOnTerminal){const missingByField=Object.fromEntries(SUPPORTED_METADATA_REQUIRED_FIELDS.map(field=>[field,[]]));for(const item of items){for(const field of SUPPORTED_METADATA_REQUIRED_FIELDS){if(isMetadataFieldMissing(item,field,statusRegistry,enforcePlanningFieldsOnTerminal)){missingByField[field].push(item.id)}}}return missingByField}function buildMetadataWarningTokens(metadataPolicy,missingByField,duplicateWarnings){const warningTokens=[...metadataPolicy.warnings];for(const field of metadataPolicy.required_fields){const missingItems=missingByField[field];if(missingItems.length>0){warningTokens.push(`${METADATA_WARNING_TOKEN_BY_FIELD[field]}:${missingItems.length}`)}}warningTokens.push(...duplicateWarnings);return warningTokens}function buildMetadataCounts(metadataPolicy,missingByField){const counts={};for(const field of metadataPolicy.required_fields){const missing=missingByField[field];const value=missing?missing.length:0;const countKey=METADATA_COUNT_KEY_BY_FIELD[field];counts[countKey]=value}return counts}function buildMissingFieldOccurrences(metadataPolicy,missingByField,itemsById){const occurrences=[];for(const field of metadataPolicy.required_fields){const missing=missingByField[field];if(!missing){continue}for(const itemId of missing){const itemType=itemsById.get(itemId)?.type;const normalizedItemType=typeof itemType==="string"&&itemType.length>0?itemType:"Unknown";occurrences.push({item_type:normalizedItemType,field})}}return occurrences}function attachMissingMetadataItemIds(details,metadataPolicy,missingByField,verboseDiagnostics){for(const field of metadataPolicy.required_fields){const missing=missingByField[field];if(!missing||missing.length===0){continue}const idsKey=METADATA_ITEM_IDS_KEY_BY_FIELD[field];const truncatedKey=METADATA_TRUNCATED_KEY_BY_FIELD[field];if(!idsKey||!truncatedKey){continue}const summarized=summarizeList(missing,verboseDiagnostics?missing.length:DIAGNOSTIC_LIST_SUMMARY_LIMIT);details[idsKey]=summarized.values;details[truncatedKey]=summarized.truncated}}function buildCloseReasonBackfillRows(metadataPolicy,missingByField,itemsById){if(!metadataPolicy.required_fields.includes("close_reason")){return[]}const missing=missingByField.close_reason;if(!missing){return[]}return missing.map(itemId=>({id:itemId,resolution:toNonEmptyStringOrUndefined(itemsById.get(itemId)?.resolution)}))}function buildEstimateBackfillRows(metadataPolicy,missingByField,itemsById){if(!metadataPolicy.required_fields.includes("estimated_minutes")){return[]}const missing=missingByField.estimated_minutes;if(!missing){return[]}return missing.map(itemId=>({id:itemId,type:toNonEmptyStringOrUndefined(itemsById.get(itemId)?.type)}))}function buildMetadataCheck(items,metadataPolicy,statusRegistry,verboseDiagnostics,configuredIssueCodePrefixes){const itemsById=new Map(items.map(item=>[item.id,item]));const enforcePlanningFieldsOnTerminal=metadataPolicy.profile==="strict";const missingByField=collectMissingMetadataByField(items,statusRegistry,enforcePlanningFieldsOnTerminal);const duplicateIssueCodes=findDuplicateIssueCodes(items,{configuredPrefixes:configuredIssueCodePrefixes});const duplicateIssueCodeSummary=summarizeDuplicateIssueCodes(duplicateIssueCodes,verboseDiagnostics);const warningTokens=buildMetadataWarningTokens(metadataPolicy,missingByField,duplicateIssueCodeSummary.warnings);const counts=buildMetadataCounts(metadataPolicy,missingByField);const missingFieldOccurrences=buildMissingFieldOccurrences(metadataPolicy,missingByField,itemsById);const missingByType=buildMissingByTypeCounts(missingFieldOccurrences);const details={checked_items:items.length,metadata_profile:metadataPolicy.profile,metadata_profile_source:metadataPolicy.profile_source,metadata_profile_fallback_to_core:metadataPolicy.fallback_to_core,required_fields:[...metadataPolicy.required_fields],supported_required_fields:[...SUPPORTED_METADATA_REQUIRED_FIELDS],counts,missing_by_type:missingByType,duplicate_issue_codes_count:duplicateIssueCodes.length,duplicate_issue_codes:duplicateIssueCodeSummary.rows,duplicate_issue_codes_truncated:duplicateIssueCodeSummary.truncated};if(metadataPolicy.configured_custom_fields.length>0){details.configured_custom_required_fields=[...metadataPolicy.configured_custom_fields]}attachMissingMetadataItemIds(details,metadataPolicy,missingByField,verboseDiagnostics);const closeReasonBackfillRows=buildCloseReasonBackfillRows(metadataPolicy,missingByField,itemsById);const estimateBackfillRows=buildEstimateBackfillRows(metadataPolicy,missingByField,itemsById);return{check:{name:"metadata",status:warningTokens.length===0?"ok":"warn",ok:warningTokens.length===0,details},warnings:warningTokens,closeReasonBackfillRows,estimateBackfillRows}}function buildResolutionCheck(items,statusRegistry,verboseDiagnostics){const terminalDoneStatuses=new Set(statusRegistry.terminal_done_statuses);terminalDoneStatuses.add(statusRegistry.close_status);const closedItems=items.filter(item=>terminalDoneStatuses.has(normalizeStatusForRegistry(item.status,statusRegistry)));const missingResolutionRows=[];const resolutionBackfillRows=[];for(const item of closedItems){const missingFields=RESOLUTION_FIELD_KEYS.filter(field=>!toNonEmptyStringOrUndefined(item[field]));if(missingFields.length===0){continue}missingResolutionRows.push({id:item.id,missing_fields:missingFields});resolutionBackfillRows.push({id:item.id,missing_fields:missingFields,close_reason:toNonEmptyStringOrUndefined(item.close_reason)})}const warnings=missingResolutionRows.length>0?[`validate_resolution_missing_fields:${missingResolutionRows.length}`]:[];const diagnosticLimit=verboseDiagnostics?Number.POSITIVE_INFINITY:DIAGNOSTIC_LIST_SUMMARY_LIMIT;const summarizedRows=summarizeList(missingResolutionRows.map(row=>`${row.id}:${row.missing_fields.join(",")}`),diagnosticLimit);const remediationHints=missingResolutionRows.map(row=>buildResolutionRemediationCommand(row));const summarizedHints=summarizeList(remediationHints,diagnosticLimit);return{check:{name:"resolution",status:warnings.length===0?"ok":"warn",ok:warnings.length===0,details:{checked_closed_items:closedItems.length,missing_resolution_items:missingResolutionRows.length,missing_resolution_rows:summarizedRows.values,missing_resolution_rows_truncated:summarizedRows.truncated,missing_resolution_remediation_hints:summarizedHints.values,missing_resolution_remediation_hints_truncated:summarizedHints.truncated}},warnings,resolutionBackfillRows}}function escapeRegExp(value){return value.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function buildLifecycleDependencyGraph(activeItems,idPrefix="pm",relationshipRegistry=createRelationshipKindRegistry()){const activeItemIds=new Set(activeItems.map(item=>item.id));const sortedItems=[...activeItems].sort((left,right)=>left.id.localeCompare(right.id));return new Map(sortedItems.map(item=>{const edges=new Set;const blockedBy=toMeaningfulString(item.blocked_by);if(blockedBy&&activeItemIds.has(blockedBy)){edges.add(blockedBy)}(item.dependencies??[]).filter(dependency=>{const dependencyKind=toMeaningfulString(dependency.kind);return dependencyKind!==void 0&&relationshipRegistry.resolve(dependencyKind)?.ordering===true}).map(dependency=>toMeaningfulString(dependency.id)).filter(dependencyId=>dependencyId!==void 0&&activeItemIds.has(dependencyId)).forEach(dependencyId=>edges.add(dependencyId));const definitionOfReady=toMeaningfulString(item.definition_of_ready);extractItemIds(definitionOfReady??"",idPrefix).filter(referencedId=>activeItemIds.has(referencedId)).forEach(referencedId=>edges.add(referencedId));return[item.id,[...edges].sort((left,right)=>left.localeCompare(right))]}))}function buildDependencyReferencesCheck(items,verboseDiagnostics,statusRegistry){const classified=collectDanglingDependencyReferences(items,statusRegistry?status=>isTerminalStatus(status,statusRegistry):void 0);const activeRows=classified.active.map(row=>`${row.holder_id}:${row.target_id}:${row.kind}`);const legacyRows=classified.legacy_terminal.map(row=>`${row.holder_id}:${row.target_id}:${row.kind}:${row.holder_status}`);const diagnosticLimit=verboseDiagnostics?Number.POSITIVE_INFINITY:DIAGNOSTIC_LIST_SUMMARY_LIMIT;const summarizedRows=summarizeList(activeRows,diagnosticLimit);const summarizedLegacyRows=summarizeList(legacyRows,diagnosticLimit);const hints=summarizeList(classified.active.map(row=>{if(row.source==="parent"){return`pm update ${row.holder_id} --unset parent`}return row.source==="blocked_by"?`pm update ${row.holder_id} --unset blocked_by`:`pm update ${row.holder_id} --replace-deps '<correct dependency edges>'`}),diagnosticLimit);return{check:{name:"dependency_references",status:activeRows.length===0?"ok":"warn",ok:activeRows.length===0,details:{checked_items:items.length,dangling_reference_count:activeRows.length+legacyRows.length,active_dangling_reference_count:activeRows.length,dangling_reference_rows:summarizedRows.values,dangling_reference_rows_truncated:summarizedRows.truncated,legacy_terminal_dangling_reference_count:legacyRows.length,legacy_closed_dangling_reference_count:classified.legacy_terminal.filter(row=>row.holder_status==="closed").length,legacy_terminal_dangling_reference_rows:summarizedLegacyRows.values,legacy_terminal_dangling_reference_rows_truncated:summarizedLegacyRows.truncated,no_active_blocker_sentinel_count:classified.no_active_blocker_sentinels.length,remediation_hints:hints.values,remediation_hints_truncated:hints.truncated}},warnings:activeRows.length>0?[`validate_dangling_dependency_references:${activeRows.length}`]:[]}}function extractItemIds(value,idPrefix="pm"){const normalizedPrefix=(idPrefix.trim().toLowerCase()||"pm").replace(/-+$/g,"");const pattern=new RegExp(`(?:^|[^a-z0-9-])(${escapeRegExp(normalizedPrefix)}-[a-z0-9][a-z0-9-]*)`,"gi");return[...new Set([...value.matchAll(pattern)].map(match=>match[1].toLowerCase()))].sort((left,right)=>left.localeCompare(right))}function findLifecycleDependencyCycleComponents(graph){let nextIndex=0;const indexById=new Map;const lowLinkById=new Map;const stack=[];const inStack=new Set;const components=[];const visit=id=>{indexById.set(id,nextIndex);lowLinkById.set(id,nextIndex);nextIndex+=1;stack.push(id);inStack.add(id);for(const dependencyId of graph.get(id)??[]){if(!indexById.has(dependencyId)){visit(dependencyId);lowLinkById.set(id,Math.min(lowLinkById.get(id),lowLinkById.get(dependencyId)))}else if(inStack.has(dependencyId)){lowLinkById.set(id,Math.min(lowLinkById.get(id),indexById.get(dependencyId)))}}if(lowLinkById.get(id)!==indexById.get(id)){return}const component=[];while(stack.length>0){const member=stack.pop();inStack.delete(member);component.push(member);if(member===id){break}}component.sort((left,right)=>left.localeCompare(right));components.push(component)};const sortedNodeIds=[...graph.keys()].sort((left,right)=>left.localeCompare(right));for(const id of sortedNodeIds){if(!indexById.has(id)){visit(id)}}const cycleComponents=components.filter(component=>{if(component.length>1){return true}const selfId=component[0];return(graph.get(selfId)??[]).includes(selfId)});return cycleComponents.sort((left,right)=>left[0].localeCompare(right[0])||left.length-right.length||left.join(",").localeCompare(right.join(",")))}function detectLifecycleDependencyCycles(activeItems,idPrefix="pm"){const graph=buildLifecycleDependencyGraph(activeItems,idPrefix);const cycleComponents=findLifecycleDependencyCycleComponents(graph);const cycleItemIds=[...new Set(cycleComponents.flat())].sort((left,right)=>left.localeCompare(right));const cycleSamplePaths=cycleComponents.map(component=>resolveLifecycleDependencyCycleSamplePath(component,graph).join("->"));return{cycle_count:cycleComponents.length,cycle_item_ids:cycleItemIds,cycle_sample_paths:cycleSamplePaths}}function classifyOrphanedPath(pathValue){if(pathValue.startsWith("docs/")){return"docs_unowned"}if(pathValue.startsWith("tests/")){return"tests_unowned"}if(pathValue.startsWith("src/")){return"source_unowned"}if(pathValue.endsWith(".md")){return"docs_unowned"}return"unlinked_existing"}function directoryOf(relativePath){const slash=relativePath.lastIndexOf("/");return slash===-1?"":relativePath.slice(0,slash)}function sharedDirectoryPrefixLength(left,right){const leftParts=directoryOf(left).split("/").filter(Boolean);const rightParts=directoryOf(right).split("/").filter(Boolean);let count=0;while(count<leftParts.length&&count<rightParts.length&&leftParts[count]===rightParts[count]){count+=1}return count}function scoreOrphanOwnerCandidate(pathValue,linkedPath){const linkedDir=directoryOf(linkedPath);const orphanDir=directoryOf(pathValue);const directoryPrefix=linkedPath.endsWith("/")?linkedPath:`${linkedPath}/`;const isDirectoryPrefix=pathValue.startsWith(directoryPrefix);const sameDirectory=linkedDir.length>0&&linkedDir===orphanDir;const sharedPrefixLength=sharedDirectoryPrefixLength(pathValue,linkedPath);if(!isDirectoryPrefix&&!sameDirectory&&sharedPrefixLength===0){return null}if(isDirectoryPrefix){return{score:linkedPath.length+1e3,confidence:"path_prefix"}}return sameDirectory?{score:sharedPrefixLength+500,confidence:"same_directory"}:{score:sharedPrefixLength,confidence:"shared_directory"}}function shouldReplaceOrphanOwnerCandidate(best,item,score){return best===void 0||score>best.score||score===best.score&&item.id.localeCompare(best.item.id)<0}function findOrphanOwnerCandidate(pathValue,classification,items){const linkKind=classification==="docs_unowned"?"docs":"files";let best;items.flatMap(item=>(linkKind==="docs"?item.docs??[]:item.files??[]).map(link=>({item,link}))).forEach(({item,link})=>{if(link.scope!=="project"){return}const linkedPath=normalizeRelativePath(link.path);if(linkedPath.length===0||linkedPath===pathValue){return}const scored=scoreOrphanOwnerCandidate(pathValue,linkedPath);if(scored===null){return}if(shouldReplaceOrphanOwnerCandidate(best,item,scored.score)){best={item,score:scored.score,confidence:scored.confidence}}});if(!best){return null}return{id:best.item.id,type:best.item.type,title:best.item.title,status:best.item.status,confidence:best.confidence}}function buildOrphanedPathRows(orphanedFiles,items){return orphanedFiles.map(pathValue=>{const classification=classifyOrphanedPath(pathValue);const linkCommand=classification==="docs_unowned"?"docs":"files";const ownerCandidate=findOrphanOwnerCandidate(pathValue,classification,items);const target=ownerCandidate?.id??"<id>";return{path:pathValue,classification,owner_candidate:ownerCandidate,remediation_hint:`pm ${linkCommand} ${target} --add path=${pathValue},scope=project,note="<why this artifact belongs to the item>"`}})}function summarizeOrphanedPathRows(rows){return rows.map(row=>`${row.path}:${row.classification} owner_candidate=${row.owner_candidate?.id??"unowned"} hint=${JSON.stringify(row.remediation_hint)}`)}function closureLikeFieldsForItem(item,lifecyclePatternPolicy){return Object.entries(lifecyclePatternPolicy.closure_like_metadata_field_patterns).filter(([field,patterns])=>{const value=toMeaningfulString(item[field]);return value?patterns.some(pattern=>value.toLowerCase().includes(pattern)):false}).map(([field])=>field).sort((left,right)=>left.localeCompare(right))}function buildTerminalParentFixRow(item,parent,itemsById,canonicalIdByLowercase,statusRegistry){const grandparentId=toMeaningfulString(parent.parent);const grandparent=grandparentId?itemsById.get(canonicalIdByLowercase.get(grandparentId.toLowerCase())??grandparentId):void 0;return{id:item.id,parent_id:parent.id,grandparent_id:grandparent?.id,grandparent_active:grandparent!==void 0&&!isTerminalStatus(grandparent.status,statusRegistry)}}function staleBlockerReasonsForItem(item,blockedStatuses,statusRegistry,lifecyclePatternPolicy){const blockedBy=toMeaningfulString(item.blocked_by);const blockedReason=toMeaningfulString(item.blocked_reason);const blockedReasonNormalized=blockedReason?.toLowerCase();const normalizedStatus=normalizeStatusForRegistry(item.status,statusRegistry);const reasons=[];if(!blockedStatuses.has(normalizedStatus)){if(blockedBy){reasons.push("non_blocked_status_has_blocked_by")}if(blockedReason){reasons.push("non_blocked_status_has_blocked_reason")}return reasons}if(!blockedBy&&!blockedReason){reasons.push("blocked_status_missing_blocker_context")}if(blockedReasonNormalized?.includes("no active blocker")){reasons.push("blocked_status_reason_reports_no_active_blocker")}if(blockedReasonNormalized&&lifecyclePatternPolicy.stale_blocker_reason_patterns.some(pattern=>blockedReasonNormalized.includes(pattern))){reasons.push("blocked_status_reason_matches_stale_pattern")}return reasons}function collectLifecycleScanRows(items,includeStaleBlockers,statusRegistry,lifecyclePatternPolicy){const itemsById=new Map(items.map(item=>[item.id,item]));const canonicalIdByLowercase=new Map(items.map(item=>[item.id.toLowerCase(),item.id]));const blockedStatuses=statusRegistry.blocked_statuses.size>0?statusRegistry.blocked_statuses:new Set(["blocked"]);const rows={activeItems:items.filter(item=>!isTerminalStatus(item.status,statusRegistry)),closureLikeRows:[],terminalParentRows:[],terminalParentFixRows:[],staleBlockerRows:[]};for(const item of rows.activeItems){const closureLikeFields=closureLikeFieldsForItem(item,lifecyclePatternPolicy);if(closureLikeFields.length>0){rows.closureLikeRows.push({id:item.id,fields:closureLikeFields})}const parentId=toMeaningfulString(item.parent);const parent=parentId?itemsById.get(canonicalIdByLowercase.get(parentId.toLowerCase())??parentId):void 0;if(parent&&isTerminalStatus(parent.status,statusRegistry)){rows.terminalParentRows.push({id:item.id,parent_id:parent.id,parent_status:parent.status});rows.terminalParentFixRows.push(buildTerminalParentFixRow(item,parent,itemsById,canonicalIdByLowercase,statusRegistry))}if(includeStaleBlockers){const reasons=staleBlockerReasonsForItem(item,blockedStatuses,statusRegistry,lifecyclePatternPolicy);if(reasons.length>0){rows.staleBlockerRows.push({id:item.id,status:item.status,reasons:[...new Set(reasons)].sort((left,right)=>left.localeCompare(right))})}}}return rows}function sortLifecycleScanRows(rows){rows.closureLikeRows.sort((left,right)=>left.id.localeCompare(right.id));rows.terminalParentRows.sort((left,right)=>left.id.localeCompare(right.id)||left.parent_id.localeCompare(right.parent_id));rows.terminalParentFixRows.sort((left,right)=>left.id.localeCompare(right.id)||left.parent_id.localeCompare(right.parent_id));rows.staleBlockerRows.sort((left,right)=>left.id.localeCompare(right.id))}function lifecycleCycleWarningToken(prefix,severity,count){if(count<=0||severity==="off"){return null}return`${severity==="error"?`${prefix}_error`:prefix}:${count}`}function lifecycleAdvisoryWarningToken(prefix,severity,count){return count>0&&severity!=="off"?`${prefix}:${count}`:null}function buildLifecycleWarnings(rows,includeStaleBlockers,dependencyCycleSeverity,parentCycleSeverity,counts){const warnings=[];if(rows.closureLikeRows.length>0){warnings.push(`validate_lifecycle_active_closure_like_metadata:${rows.closureLikeRows.length}`)}if(rows.terminalParentRows.length>0){warnings.push(`validate_lifecycle_active_terminal_parent:${rows.terminalParentRows.length}`)}if(includeStaleBlockers&&rows.staleBlockerRows.length>0){warnings.push(`validate_lifecycle_stale_blockers:${rows.staleBlockerRows.length}`)}const dependencyWarning=lifecycleCycleWarningToken("validate_lifecycle_dependency_cycles",dependencyCycleSeverity,counts.dependencyCycles);const parentWarning=lifecycleCycleWarningToken("validate_hierarchy_parent_cycle",parentCycleSeverity,counts.activeHierarchyCycles);const cardinalityWarning=lifecycleCycleWarningToken("validate_hierarchy_cardinality_violation",parentCycleSeverity,counts.activeHierarchyCardinality);const divergenceWarning=lifecycleCycleWarningToken("validate_hierarchy_parent_divergence",parentCycleSeverity,counts.activeHierarchyDivergence);const legacyCycleWarning=lifecycleAdvisoryWarningToken("validate_legacy_hierarchy_parent_cycle",parentCycleSeverity,counts.legacyHierarchyCycles);const legacyCardinalityWarning=lifecycleAdvisoryWarningToken("validate_legacy_hierarchy_cardinality_violation",parentCycleSeverity,counts.legacyHierarchyCardinality);const legacyDivergenceWarning=lifecycleAdvisoryWarningToken("validate_legacy_hierarchy_parent_divergence",parentCycleSeverity,counts.legacyHierarchyDivergence);return[...warnings,...dependencyWarning?[dependencyWarning]:[],...parentWarning?[parentWarning]:[],...cardinalityWarning?[cardinalityWarning]:[],...divergenceWarning?[divergenceWarning]:[],...legacyCycleWarning?[legacyCycleWarning]:[],...legacyCardinalityWarning?[legacyCardinalityWarning]:[],...legacyDivergenceWarning?[legacyDivergenceWarning]:[]]}function linkedArtifactPathExceedsFilesystemLimits(artifactPath){const normalized=normalizeRelativePath(artifactPath);return normalized.length>LINKED_ARTIFACT_MAX_PATH_LENGTH||normalized.split(/[\\/]/).some(segment=>segment.length>LINKED_ARTIFACT_MAX_SEGMENT_LENGTH)}async function linkedArtifactIsMissing(workspaceRoot,artifactPath){const absolutePath=path.isAbsolute(artifactPath)?artifactPath:path.resolve(workspaceRoot,artifactPath);if(linkedArtifactPathExceedsFilesystemLimits(absolutePath)){return false}try{const stats=await fs.stat(absolutePath);return!stats.isFile()&&!stats.isDirectory()}catch(error){return hasFilesystemErrorCode(error,["ENOENT","ENOTDIR"])}}async function collectLinkedPathScanState(items,workspaceRoot){const state={linkedProjectPaths:new Set,remoteLinkedPaths:new Set,missingLinkedPaths:[],staleLinkRows:[]};const linkedArtifacts=items.flatMap(item=>[...(item.files??[]).map(artifact=>({item,artifact,link_kind:"files"})),...(item.docs??[]).map(artifact=>({item,artifact,link_kind:"docs"}))]);for(const{item,artifact,link_kind}of linkedArtifacts){if(artifact.scope!=="project"){continue}if(isRemoteLinkedArtifactReference(artifact.path)){state.remoteLinkedPaths.add(artifact.path.trim());continue}const normalizedPath=normalizeRelativePath(artifact.path);if(normalizedPath.length===0){continue}state.linkedProjectPaths.add(normalizedPath);if(await linkedArtifactIsMissing(workspaceRoot,artifact.path)){state.missingLinkedPaths.push(normalizedPath);state.staleLinkRows.push({item_id:item.id,path:normalizedPath,link_kind})}}return state}function partitionFileCandidates(fileCandidates,pmRoot,workspaceRoot,fileScanMode,includePmInternals,verboseFileLists){const strictTrackedAllMode=fileScanMode==="tracked-all-strict";const strictModeForcesPmInternals=strictTrackedAllMode&&!includePmInternals;const includePmInternalsEffective=includePmInternals||strictTrackedAllMode;const pmInternalCandidatePrefixes=includePmInternalsEffective?[]:resolvePmInternalCandidatePrefixes(pmRoot,workspaceRoot);const excludedPmInternalPaths=pmInternalCandidatePrefixes.length===0?[]:fileCandidates.candidateFiles.filter(candidate=>hasPathPrefix(candidate,pmInternalCandidatePrefixes));const candidateFiles=pmInternalCandidatePrefixes.length===0?fileCandidates.candidateFiles:fileCandidates.candidateFiles.filter(candidate=>!hasPathPrefix(candidate,pmInternalCandidatePrefixes));const excludedByReason={};if(excludedPmInternalPaths.length>0){const summarizedPmInternalPaths=summarizeFileList(excludedPmInternalPaths,verboseFileLists);excludedByReason.pm_internals={count:excludedPmInternalPaths.length,paths:summarizedPmInternalPaths.values,paths_truncated:summarizedPmInternalPaths.truncated,paths_total:summarizedPmInternalPaths.total}}return{strictTrackedAllMode,strictModeForcesPmInternals,includePmInternalsEffective,pmInternalCandidatePrefixes,excludedPmInternalPaths,candidateFiles,excludedByReason}}function buildLifecycleCheck(items,includeStaleBlockers,dependencyCycleSeverity,parentCycleSeverity,statusRegistry,lifecyclePatternPolicy,verboseDiagnostics,idPrefix="pm"){const rows=collectLifecycleScanRows(items,includeStaleBlockers,statusRegistry,lifecyclePatternPolicy);sortLifecycleScanRows(rows);const dependencyCycleDiagnostics=detectLifecycleDependencyCycles(rows.activeItems,idPrefix);const parentCycleDiagnostics=detectLifecycleParentCycles(items,statusRegistry);const statusRoleDiagnostics=inspectStatusRoleAssignments(statusRegistry,items,verboseDiagnostics?Number.POSITIVE_INFINITY:DIAGNOSTIC_LIST_SUMMARY_LIMIT);const warnings=[...buildLifecycleWarnings(rows,includeStaleBlockers,dependencyCycleSeverity,parentCycleSeverity,{dependencyCycles:dependencyCycleDiagnostics.cycle_count,activeHierarchyCycles:parentCycleDiagnostics.active_cycle_count,activeHierarchyCardinality:parentCycleDiagnostics.active_cardinality_violation_count,activeHierarchyDivergence:parentCycleDiagnostics.active_parent_divergence_count,legacyHierarchyCycles:parentCycleDiagnostics.legacy_cycle_count,legacyHierarchyCardinality:parentCycleDiagnostics.legacy_cardinality_violation_count,legacyHierarchyDivergence:parentCycleDiagnostics.legacy_parent_divergence_count}),...buildStatusRoleWarnings(statusRoleDiagnostics)];const diagnosticLimit=verboseDiagnostics?Number.POSITIVE_INFINITY:DIAGNOSTIC_LIST_SUMMARY_LIMIT;const summarizedClosureLikeRows=summarizeList(rows.closureLikeRows.map(row=>`${row.id}:${row.fields.join(",")}`),diagnosticLimit);const summarizedTerminalParentRows=summarizeList(rows.terminalParentRows.map(row=>`${row.id}:${row.parent_id}:${row.parent_status}`),diagnosticLimit);const summarizedStaleBlockerRows=summarizeList(rows.staleBlockerRows.map(row=>`${row.id}:${row.status}:${row.reasons.join(",")}`),diagnosticLimit);const summarizedDependencyCycleItemIds=summarizeList(dependencyCycleDiagnostics.cycle_item_ids,diagnosticLimit);const summarizedDependencyCycleSamplePaths=summarizeList(dependencyCycleDiagnostics.cycle_sample_paths,diagnosticLimit);const summarizedParentCycleItemIds=summarizeList(parentCycleDiagnostics.cycle_item_ids,diagnosticLimit);const summarizedParentCycleSamplePaths=summarizeList(parentCycleDiagnostics.cycle_sample_paths,diagnosticLimit);const summarizedHierarchyCardinalityRows=summarizeList(parentCycleDiagnostics.cardinality_violation_rows,diagnosticLimit);const summarizedHierarchyDivergenceRows=summarizeList(parentCycleDiagnostics.parent_divergence_rows,diagnosticLimit);const hasErrorSeverityCycle=dependencyCycleDiagnostics.cycle_count>0&&dependencyCycleSeverity==="error"||(parentCycleDiagnostics.active_cycle_count>0||parentCycleDiagnostics.active_cardinality_violation_count>0||parentCycleDiagnostics.active_parent_divergence_count>0)&&parentCycleSeverity==="error";return{check:{name:"lifecycle",status:hasErrorSeverityCycle?"error":warnings.length===0?"ok":"warn",ok:!hasErrorSeverityCycle&&warnings.length===0,details:{checked_active_items:rows.activeItems.length,lifecycle_status_roles:statusRoleDiagnostics,active_closure_like_metadata_items:rows.closureLikeRows.length,active_closure_like_metadata_rows:summarizedClosureLikeRows.values,active_closure_like_metadata_rows_truncated:summarizedClosureLikeRows.truncated,active_terminal_parent_items:rows.terminalParentRows.length,active_terminal_parent_rows:summarizedTerminalParentRows.values,active_terminal_parent_rows_truncated:summarizedTerminalParentRows.truncated,stale_blocker_checks_enabled:includeStaleBlockers,stale_blocker_items:rows.staleBlockerRows.length,stale_blocker_rows:summarizedStaleBlockerRows.values,stale_blocker_rows_truncated:summarizedStaleBlockerRows.truncated,dependency_cycle_severity_policy:dependencyCycleSeverity,dependency_cycle_count:dependencyCycleDiagnostics.cycle_count,dependency_cycle_item_count:dependencyCycleDiagnostics.cycle_item_ids.length,dependency_cycle_item_ids:summarizedDependencyCycleItemIds.values,dependency_cycle_item_ids_truncated:summarizedDependencyCycleItemIds.truncated,dependency_cycle_sample_paths:summarizedDependencyCycleSamplePaths.values,dependency_cycle_sample_paths_truncated:summarizedDependencyCycleSamplePaths.truncated,parent_cycle_severity_policy:parentCycleSeverity,parent_cycle_count:parentCycleDiagnostics.cycle_count,active_parent_cycle_count:parentCycleDiagnostics.active_cycle_count,legacy_parent_cycle_count:parentCycleDiagnostics.legacy_cycle_count,parent_cycle_item_count:parentCycleDiagnostics.cycle_item_ids.length,parent_cycle_item_ids:summarizedParentCycleItemIds.values,parent_cycle_item_ids_truncated:summarizedParentCycleItemIds.truncated,parent_cycle_sample_paths:summarizedParentCycleSamplePaths.values,parent_cycle_sample_paths_truncated:summarizedParentCycleSamplePaths.truncated,hierarchy_cardinality_violation_count:parentCycleDiagnostics.cardinality_violation_count,active_hierarchy_cardinality_violation_count:parentCycleDiagnostics.active_cardinality_violation_count,legacy_hierarchy_cardinality_violation_count:parentCycleDiagnostics.legacy_cardinality_violation_count,hierarchy_cardinality_violation_rows:summarizedHierarchyCardinalityRows.values,hierarchy_cardinality_violation_rows_truncated:summarizedHierarchyCardinalityRows.truncated,hierarchy_parent_divergence_count:parentCycleDiagnostics.parent_divergence_count,active_hierarchy_parent_divergence_count:parentCycleDiagnostics.active_parent_divergence_count,legacy_hierarchy_parent_divergence_count:parentCycleDiagnostics.legacy_parent_divergence_count,hierarchy_parent_divergence_rows:summarizedHierarchyDivergenceRows.values,hierarchy_parent_divergence_rows_truncated:summarizedHierarchyDivergenceRows.truncated,stale_blocker_reason_patterns:[...lifecyclePatternPolicy.stale_blocker_reason_patterns],stale_blocker_reason_pattern_source:lifecyclePatternPolicy.stale_blocker_reason_pattern_source,closure_like_blocked_reason_patterns:[...lifecyclePatternPolicy.closure_like_metadata_field_patterns.blocked_reason],closure_like_blocked_reason_pattern_source:lifecyclePatternPolicy.closure_like_metadata_field_pattern_sources.blocked_reason,closure_like_resolution_patterns:[...lifecyclePatternPolicy.closure_like_metadata_field_patterns.resolution],closure_like_resolution_pattern_source:lifecyclePatternPolicy.closure_like_metadata_field_pattern_sources.resolution,closure_like_actual_result_patterns:[...lifecyclePatternPolicy.closure_like_metadata_field_patterns.actual_result],closure_like_actual_result_pattern_source:lifecyclePatternPolicy.closure_like_metadata_field_pattern_sources.actual_result}},warnings,terminalParentFixRows:rows.terminalParentFixRows}}async function buildFilesCheck(items,workspaceRoot,pmRoot,fileScanMode,includePmInternals,verboseFileLists,statusRegistry=resolveRuntimeStatusRegistry(SETTINGS_DEFAULTS.schema)){const linkedPathState=await collectLinkedPathScanState(items,workspaceRoot);const itemsById=new Map(items.map(item=>[item.id,item]));const uniqueMissingLinkedPaths=[...new Set(linkedPathState.missingLinkedPaths)].sort((left,right)=>left.localeCompare(right));const fileCandidates=await collectProjectFileCandidates(workspaceRoot,fileScanMode);const partition=partitionFileCandidates(fileCandidates,pmRoot,workspaceRoot,fileScanMode,includePmInternals,verboseFileLists);const orphanedFiles=partition.candidateFiles.filter(candidate=>!linkedPathState.linkedProjectPaths.has(candidate));const orphanedPathRows=buildOrphanedPathRows(orphanedFiles,items);const classifiedStalePaths=classifyStaleLinkedPaths(uniqueMissingLinkedPaths,partition.candidateFiles);const classificationByPath=new Map(classifiedStalePaths.map(entry=>[entry.path,entry.classification]));const movedStalePathCount=classifiedStalePaths.filter(entry=>entry.classification==="moved").length;const staleLinkPruneRows=linkedPathState.staleLinkRows.map(row=>({...row,classification:classificationByPath.get(row.path)??"deleted"})).sort((left,right)=>left.item_id.localeCompare(right.item_id)||left.path.localeCompare(right.path)||left.link_kind.localeCompare(right.link_kind));const warnings=[];if(partition.strictModeForcesPmInternals){warnings.push("validate_files_tracked_all_strict_forces_pm_internals")}if(orphanedFiles.length>0){warnings.push(`validate_files_orphaned_paths:${orphanedFiles.length}`)}const uniqueRemoteLinkedPaths=[...linkedPathState.remoteLinkedPaths].sort((left,right)=>left.localeCompare(right));const summarizedRemote=summarizeFileList(uniqueRemoteLinkedPaths,verboseFileLists);const summarizedMissing=summarizeFileList(uniqueMissingLinkedPaths,verboseFileLists);const summarizedOrphaned=summarizeFileList(orphanedFiles,verboseFileLists);const summarizedOrphanedClassifications=summarizeFileList(orphanedPathRows.map(row=>`${row.path}:${row.classification}:owner_candidate=${row.owner_candidate?.id??"unowned"}`),verboseFileLists);const orphanedPathRowDetail=verboseFileLists?orphanedPathRows:summarizeFileList(summarizeOrphanedPathRows(orphanedPathRows),false).values;const summarizedClassifications=summarizeFileList(summarizeStaleLinkedPathClassifications(classifiedStalePaths),verboseFileLists);const missingLinkedPathRows=staleLinkPruneRows;const repairReport=buildLinkedFileRepairReport(missingLinkedPathRows,classifiedStalePaths,id=>itemsById.get(id),statusRegistry,verboseFileLists?Infinity:FILE_LIST_SUMMARY_LIMIT);if(repairReport.active_missing_linked_paths_count>0){warnings.push(`validate_files_missing_linked_paths:${repairReport.active_missing_linked_paths_count}`)}return{check:{name:"files",status:warnings.length===0?"ok":"warn",ok:warnings.length===0,details:{workspace_root:workspaceRoot,scan_mode_requested:fileCandidates.requestedMode,scan_mode_applied:fileCandidates.appliedMode,strict_tracked_all_mode:partition.strictTrackedAllMode,strict_mode_forces_pm_internals:partition.strictModeForcesPmInternals,strict_mode_forces_pm_internals_notice:partition.strictModeForcesPmInternals?"tracked-all-strict force-enables PM internals; pass --include-pm-internals to make inclusion explicit.":null,file_list_detail_mode:verboseFileLists?"full":"summary",file_list_summary_limit:FILE_LIST_SUMMARY_LIMIT,candidate_scan_source:fileCandidates.source,include_pm_internals:partition.includePmInternalsEffective,include_pm_internals_requested:includePmInternals,pm_internal_candidate_prefixes:partition.pmInternalCandidatePrefixes,pm_internal_excluded_count:partition.excludedPmInternalPaths.length,excluded_total:partition.excludedPmInternalPaths.length,excluded_by_reason:partition.excludedByReason,linked_project_paths:linkedPathState.linkedProjectPaths.size,remote_linked_paths_count:uniqueRemoteLinkedPaths.length,remote_linked_paths_total:summarizedRemote.total,remote_linked_paths:summarizedRemote.values,remote_linked_paths_truncated:summarizedRemote.truncated,candidate_total_raw:fileCandidates.candidateTotal,candidate_scanned_raw:fileCandidates.candidateScanned,candidate_total:partition.candidateFiles.length,candidate_scanned:partition.candidateFiles.length,scanned_candidate_files:partition.candidateFiles.length,missing_linked_paths_count:uniqueMissingLinkedPaths.length,missing_linked_paths_total:summarizedMissing.total,missing_linked_paths:summarizedMissing.values,missing_linked_paths_truncated:summarizedMissing.truncated,missing_linked_paths_moved_count:movedStalePathCount,missing_linked_paths_deleted_count:classifiedStalePaths.filter(entry=>entry.classification==="deleted").length,missing_linked_paths_malformed_count:classifiedStalePaths.filter(entry=>entry.classification==="malformed").length,missing_linked_path_classifications:summarizedClassifications.values,missing_linked_path_classifications_truncated:summarizedClassifications.truncated,...repairReport,orphaned_paths_count:orphanedFiles.length,orphaned_paths_total:summarizedOrphaned.total,orphaned_paths:summarizedOrphaned.values,orphaned_paths_truncated:summarizedOrphaned.truncated,orphaned_path_classifications:summarizedOrphanedClassifications.values,orphaned_path_classifications_truncated:summarizedOrphanedClassifications.truncated,orphaned_path_rows_count:orphanedPathRows.length,orphaned_path_rows:orphanedPathRowDetail}},warnings,staleLinkPruneRows}}function summarizeCommandPreview(command){const normalizedCommand=command.trim().replaceAll(/\s+/g," ");return normalizedCommand.length>120?`${normalizedCommand.slice(0,117)}...`:normalizedCommand}function summarizeCommandReferenceRow(ownerId,referencedId,command){return`${ownerId}:${referencedId}:${summarizeCommandPreview(command)}`}function buildUntrustedLinkedTestRows(linkedTests,trustDecisions){const rows=[];linkedTests.forEach(({item,linkedTest},index)=>{const trust=trustDecisions[index];if(trust.trusted)return;const command=summarizeCommandPreview(linkedTest.command??"");rows.push(`${item.id}:${trust.source_ref??"unknown"}->${trust.current_source_ref??"unknown"}:${command}`)});return rows}async function buildCommandReferencesCheck(items,idPrefix,verboseDiagnostics,pmRoot){const knownIds=new Set(items.map(item=>item.id.toLowerCase()));let linkedCommandsScanned=0;let referencedPmIdCount=0;const referencedPmIds=new Set;const staleReferenceRows=[];const linkedTests=items.flatMap(item=>(item.tests??[]).map(linkedTest=>({item,linkedTest})));const currentSourceRef=await resolveLinkedTestSourceRef(resolveLinkedTestSourceWorkspaceRoot());const trustDecisions=await resolveLinkedTestTrustBatch(pmRoot,linkedTests.map(({linkedTest})=>linkedTest),currentSourceRef);const untrustedRows=buildUntrustedLinkedTestRows(linkedTests,trustDecisions);for(const{item,linkedTest}of linkedTests){if(typeof linkedTest.command!=="string"||linkedTest.command.trim().length===0){continue}linkedCommandsScanned+=1;const referencedIds=extractReferencedPmItemIdsFromCommand(linkedTest.command,idPrefix);if(referencedIds.length===0){continue}referencedPmIdCount+=referencedIds.length;for(const referencedId of referencedIds){referencedPmIds.add(referencedId);if(!knownIds.has(referencedId.toLowerCase())){staleReferenceRows.push(summarizeCommandReferenceRow(item.id,referencedId,linkedTest.command))}}}const uniqueStaleReferenceRows=[...new Set(staleReferenceRows)].sort((left,right)=>left.localeCompare(right));const stalePmIds=[...new Set(uniqueStaleReferenceRows.map(row=>row.split(":")[1]??""))].filter(value=>value.length>0).sort((left,right)=>left.localeCompare(right));const warnings=[];if(uniqueStaleReferenceRows.length>0){warnings.push(`validate_command_references_stale_pm_ids:${uniqueStaleReferenceRows.length}`)}if(untrustedRows.length>0){warnings.push(`validate_linked_test_trust_unacknowledged:${untrustedRows.length}`)}const diagnosticLimit=verboseDiagnostics?Number.POSITIVE_INFINITY:DIAGNOSTIC_LIST_SUMMARY_LIMIT;const summarizedRows=summarizeList(uniqueStaleReferenceRows,diagnosticLimit);const summarizedStalePmIds=summarizeList(stalePmIds,diagnosticLimit);const summarizedReferencedPmIds=summarizeList([...referencedPmIds].sort((left,right)=>left.localeCompare(right)),diagnosticLimit);const summarizedUntrustedRows=summarizeList(untrustedRows,diagnosticLimit);return{check:{name:"command_references",status:warnings.length===0?"ok":"warn",ok:warnings.length===0,details:{checked_items:items.length,linked_commands_scanned:linkedCommandsScanned,referenced_pm_ids_count:referencedPmIdCount,unique_referenced_pm_ids_count:referencedPmIds.size,unique_referenced_pm_ids:summarizedReferencedPmIds.values,unique_referenced_pm_ids_truncated:summarizedReferencedPmIds.truncated,linked_test_trust_current_source_ref:currentSourceRef??null,untrusted_linked_tests_count:untrustedRows.length,untrusted_linked_tests:summarizedUntrustedRows.values,untrusted_linked_tests_truncated:summarizedUntrustedRows.truncated,stale_pm_id_references_count:uniqueStaleReferenceRows.length,stale_pm_ids_count:stalePmIds.length,stale_pm_ids:summarizedStalePmIds.values,stale_pm_ids_truncated:summarizedStalePmIds.truncated,stale_pm_id_reference_rows:summarizedRows.values,stale_pm_id_reference_rows_truncated:summarizedRows.truncated}},warnings}}function buildFormatVersionCheck(items,verboseDiagnostics){const scan=scanItemFormatVersions(items.map(item=>({ref:item.id,version:effectiveItemFormatVersion(item)})));const warnings=[];if(scan.outdated.length>0){warnings.push(`validate_format_version_outdated_items:${scan.outdated.length}`)}if(scan.ahead.length>0){warnings.push(`validate_format_version_ahead_items:${scan.ahead.length}`)}const diagnosticLimit=verboseDiagnostics?Number.POSITIVE_INFINITY:DIAGNOSTIC_LIST_SUMMARY_LIMIT;const summarizedOutdated=summarizeList(scan.outdated,diagnosticLimit);const summarizedAhead=summarizeList(scan.ahead,diagnosticLimit);const status=scan.ahead.length>0?"error":scan.outdated.length>0?"warn":"ok";return{check:{name:"format_version",status,ok:status==="ok",details:{checked_items:items.length,current_format_version:CURRENT_ITEM_FORMAT_VERSION,outdated_items_count:scan.outdated.length,outdated_items:summarizedOutdated.values,outdated_items_truncated:summarizedOutdated.truncated,ahead_items_count:scan.ahead.length,ahead_items:summarizedAhead.values,ahead_items_truncated:summarizedAhead.truncated}},warnings}}const VALIDATE_AUTO_FIX_MESSAGE="pm validate auto-fix";async function applyValidateFix(fix,global2,services){switch(fix.kind){case"set_resolution":case"set_close_reason":case"set_estimate":case"reparent":case"unset_parent":{const updateOptions={message:VALIDATE_AUTO_FIX_MESSAGE};if(fix.kind==="set_resolution"){updateOptions.resolution=fix.value}else if(fix.kind==="set_close_reason"){updateOptions.closeReason=fix.value}else if(fix.kind==="set_estimate"){updateOptions.estimatedMinutes=fix.value}else if(fix.kind==="reparent"){updateOptions.parent=fix.parent_id}else{updateOptions.unset=["parent"]}if(!services.runUpdate){throw new PmCliError("Applying validate metadata/lifecycle fixes requires a runUpdate mutation service.",EXIT_CODE.USAGE)}await services.runUpdate(fix.item_id,updateOptions,global2);return}case"prune_file_link":{throw new Error(`Unsupported non-batched fix kind: ${fix.kind}`)}case"prune_doc_link":{throw new Error(`Unsupported non-batched fix kind: ${fix.kind}`)}}}function pruneBatchKey(fix){if(fix.kind!=="prune_file_link"&&fix.kind!=="prune_doc_link"){return null}return`${fix.kind}:${fix.item_id}`}async function applyValidateFixes(applicable,global2,services){const applied=[];const failed=[];const pruneBatches=new Map;for(const fix of applicable.filter(candidate=>pruneBatchKey(candidate)===null)){try{await applyValidateFix(fix,global2,services);applied.push(fix)}catch(error){failed.push({fix,error})}}for(const fix of applicable.filter(candidate=>pruneBatchKey(candidate)!==null)){const batchKey=pruneBatchKey(fix);const existing=pruneBatches.get(batchKey);if(existing){existing.push(fix)}else{pruneBatches.set(batchKey,[fix])}}for(const batch of pruneBatches.values()){const first=batch[0];if(!first){continue}const remove=batch.map(fix=>fix.path).filter(value=>typeof value==="string"&&value.length>0);try{if(first.kind==="prune_file_link"){await runFiles(first.item_id,{remove,message:VALIDATE_AUTO_FIX_MESSAGE},global2)}else{await runDocs(first.item_id,{remove,message:VALIDATE_AUTO_FIX_MESSAGE},global2)}applied.push(...batch)}catch(error){failed.push(...batch.map(fix=>({fix,error})))}}return{applied,failed}}const _testOnlyValidateCommand={applyValidateFix,applyValidateFixes,attachValidateFixHints,buildCommandReferencesCheck,buildDependencyReferencesCheck,buildCloseReasonBackfillRows,buildFilesCheck,buildLifecycleCheck,buildLifecycleDependencyGraph,buildLifecycleParentGraph,buildEstimateBackfillRows,buildMetadataCounts,buildMissingFieldOccurrences,buildOrphanedPathRows,classifyOrphanedPath,collectDefaultProjectFileCandidates,collectTrackedGitFileCandidates,detectLifecycleDependencyCycles,detectLifecycleParentCycles,escapeRegExp,extractItemIds,findLifecycleDependencyCycleComponents,isMetadataFieldMissing,linkedArtifactPathExceedsFilesystemLimits,listFilesRecursive,resolveDependencyCycleSeverity,resolveParentCycleSeverity,resolveFileScanMode,resolveLifecycleDependencyCycleSamplePath,resolveRequestedChecks,resolveValidateMetadataProfile,resolveWorkspaceRoot,sharedDirectoryPrefixLength,summarizeOrphanedPathRows,summarizeDuplicateIssueCodes,toMeaningfulString};function appendInvalidMergeReceiptEvidenceWarning(warnings,invalidEvidenceCount){if(invalidEvidenceCount>0){warnings.push(`merge_receipt_evidence_invalid:${invalidEvidenceCount}`)}}async function buildStorageIntegrityCheck(pmRoot,items,typeToFolder,settings){const scan=await scanStorageIntegrity(pmRoot,new Set(items.map(item=>item.id)),typeToFolder,{schema:settings.schema,extensionFieldNames:collectRegisteredItemFieldNames(getActiveExtensionRegistrations())});const warnings=[];if(scan.unreadable_item_files.length>0){warnings.push(`validate_storage_unreadable_items:${scan.unreadable_item_files.length}`)}if(scan.duplicate_item_ids.length>0){warnings.push(`validate_storage_duplicate_item_ids:${scan.duplicate_item_ids.length}`)}if(scan.history_conflict_marker_streams.length>0){warnings.push(`validate_storage_history_conflict_markers:${scan.history_conflict_marker_streams.length}`)}if(scan.history_unparseable_streams.length>0){warnings.push(`validate_storage_unparseable_history:${scan.history_unparseable_streams.length}`)}if(scan.resurrected_items.length>0){warnings.push(`validate_storage_resurrected_items:${scan.resurrected_items.length}`)}if(scan.unparseable_config_files.length>0){warnings.push(`validate_storage_unparseable_config:${scan.unparseable_config_files.length}`)}const errorCount=warnings.length;const trackedRuntimeCache=await scanTrackedRuntimeCache(pmRoot);if(trackedRuntimeCache.tracked_path_count>0){warnings.push(`validate_storage_tracked_runtime_cache_files:${trackedRuntimeCache.tracked_path_count}`)}const fenceAudit=await auditMergeAttributeFence(pmRoot,resolveProjectMergeTypeFolders(settings));if(fenceAudit.status==="drift"){warnings.push(`validate_merge_fence_drift:${fenceAudit.missing_patterns.length+fenceAudit.stale_patterns.length}`)}const gitWorkspaceRoot=await findGitWorkspaceRoot(pmRoot);const mergeDriverAudit=gitWorkspaceRoot===null?null:await auditMergeDriverConfiguration(gitWorkspaceRoot);if(mergeDriverAudit!==null&&mergeDriverAudit.status!=="ok"){warnings.push(`validate_merge_driver_configuration:${mergeDriverAudit.missing_keys.length+mergeDriverAudit.drifted_keys.length}`)}const mergeReceiptEvidence=await inspectMergeReceiptEvidence(gitWorkspaceRoot??pmRoot,{includeLossless:true,pmRoot});const pendingMergeReceipts=mergeReceiptEvidence.receipts;const{pendingDecisions:pendingMergeDecisions,lossless:losslessMergeReceipts}=partitionMergeReceipts(pendingMergeReceipts);if(pendingMergeDecisions.length>0){warnings.push(`validate_merge_decisions_unreviewed:${pendingMergeDecisions.length}`)}appendInvalidMergeReceiptEvidenceWarning(warnings,mergeReceiptEvidence.invalid_evidence_count);const status=errorCount>0?"error":warnings.length>0?"warn":"ok";return{check:{name:"storage_integrity",status,ok:status==="ok",details:{...scan,merge_fence:fenceAudit,merge_driver_configuration:mergeDriverAudit,pending_merge_decision_count:pendingMergeDecisions.length,pending_merge_decision_items:[...new Set(pendingMergeDecisions.map(receipt=>receipt.item_id))].sort((left,right)=>left.localeCompare(right)),lossless_merge_receipt_count:losslessMergeReceipts.length,invalid_merge_receipt_evidence_count:mergeReceiptEvidence.invalid_evidence_count,invalid_merge_receipt_evidence_details:mergeReceiptEvidence.invalid_evidence,invalid_merge_receipt_evidence_details_truncated:mergeReceiptEvidence.invalid_evidence_truncated,lossless_merge_receipt_items:[...new Set(losslessMergeReceipts.map(receipt=>receipt.item_id))].sort((left,right)=>left.localeCompare(right)),tracked_runtime_cache:{tracker_relative_root:trackedRuntimeCache.tracker_relative_root,tracked_paths:trackedRuntimeCache.tracked_paths,tracked_path_count:trackedRuntimeCache.tracked_path_count,remediation_command:trackedRuntimeCache.remediation_command}}},warnings}}function recordValidateCheck(state,built,fixHintsEnabled){if(fixHintsEnabled){attachValidateFixHints(built.check,built.warnings)}state.checks.push(built.check);state.warnings.push(...built.warnings)}async function executeRequestedValidateChecks(params){const state={checks:[],warnings:[...params.initialWarnings],closeReasonBackfillRows:[],estimateBackfillRows:[],resolutionBackfillRows:[],terminalParentFixRows:[],staleLinkPruneRows:[]};const fixHintsEnabled=params.options.fixHints===true;const fullDiagnostics=params.options.verboseDiagnostics===true||params.options.allAffectedIds===true||params.global.json===true;if(params.requestedChecks.has("completeness")){const built=await readWorkflowCompletenessCheck(params.pmRoot,params.items,fullDiagnostics?Infinity:DIAGNOSTIC_LIST_SUMMARY_LIMIT,params.sourceIncomplete);recordValidateCheck(state,built,fixHintsEnabled)}if(params.requestedChecks.has("metadata")){const built=buildMetadataCheck(params.items,params.metadataPolicy,params.statusRegistry,fullDiagnostics,[params.settings.id_prefix]);state.closeReasonBackfillRows=built.closeReasonBackfillRows;state.estimateBackfillRows=built.estimateBackfillRows;recordValidateCheck(state,built,fixHintsEnabled)}if(params.requestedChecks.has("resolution")){const built=buildResolutionCheck(params.items,params.statusRegistry,fullDiagnostics);state.resolutionBackfillRows=built.resolutionBackfillRows;recordValidateCheck(state,built,fixHintsEnabled)}if(params.requestedChecks.has("lifecycle")){const built=buildLifecycleCheck(params.items,Boolean(params.options.checkStaleBlockers),params.dependencyCycleSeverity,params.parentCycleSeverity,params.statusRegistry,params.lifecyclePatternPolicy,fullDiagnostics,params.settings.id_prefix);state.terminalParentFixRows=built.terminalParentFixRows;recordValidateCheck(state,built,fixHintsEnabled);recordValidateCheck(state,buildDependencyReferencesCheck(params.items,fullDiagnostics,params.statusRegistry),fixHintsEnabled)}if(params.requestedChecks.has("files")){const built=await buildFilesCheck(params.items,params.workspaceRoot,params.pmRoot,params.fileScanMode,Boolean(params.options.includePmInternals),Boolean(params.options.verboseFileLists),params.statusRegistry);state.staleLinkPruneRows=built.staleLinkPruneRows;recordValidateCheck(state,built,fixHintsEnabled)}if(params.requestedChecks.has("command_references")){recordValidateCheck(state,await buildCommandReferencesCheck(params.items,params.settings.id_prefix,fullDiagnostics,params.pmRoot),fixHintsEnabled)}if(params.requestedChecks.has("history_drift")){recordValidateCheck(state,await buildValidateHistoryDriftCheck(params.pmRoot,params.items,fullDiagnostics),fixHintsEnabled)}if(params.requestedChecks.has("format_version")){recordValidateCheck(state,buildFormatVersionCheck(params.items,fullDiagnostics),fixHintsEnabled)}if(params.requestedChecks.has("storage_integrity")){recordValidateCheck(state,await buildStorageIntegrityCheck(params.pmRoot,params.items,resolveItemTypeRegistry(params.settings,getActiveExtensionRegistrations()).type_to_folder,params.settings),fixHintsEnabled)}return state}function planValidateFixes(options,state,settings){const planned=[];if(options.autoFix===true){planned.push(...planCloseReasonBackfillFixes(state.closeReasonBackfillRows));planned.push(...planResolutionBackfillFixes(state.resolutionBackfillRows));planned.push(...planEstimateBackfillFixes(state.estimateBackfillRows,settings.validation.estimate_defaults_by_type));planned.push(...planTerminalParentFixes(state.terminalParentFixRows))}if(options.pruneMissing===true){planned.push(...planStaleLinkPruneFixes(state.staleLinkPruneRows))}return planned}async function buildValidateFixesSummary(options,state,settings,grantedFixScopes,global2,services){if(options.autoFix!==true&&options.pruneMissing!==true){return void 0}const planned=planValidateFixes(options,state,settings);const{applicable,gated}=partitionFixesByGrant(planned,grantedFixScopes);const dryRun=options.dryRun===true;const appliedFixRows=[];const failedFixRows=[];if(!dryRun){const applied=await applyValidateFixes(applicable,global2,services);appliedFixRows.push(...applied.applied.map(toFixOutputRow));failedFixRows.push(...applied.failed.map(({fix,error})=>({...toFixOutputRow(fix),error:error instanceof Error?error.message:String(error)})))}return{mode:dryRun?"dry_run":"apply",auto_fix:options.autoFix===true,prune_missing:options.pruneMissing===true,granted_fix_scopes:[...grantedFixScopes].sort((left,right)=>left.localeCompare(right)),planned_count:planned.length,applied_count:appliedFixRows.length,gated_count:gated.length,failed_count:failedFixRows.length,planned_fixes:planned.map(toFixOutputRow),applied_fixes:appliedFixRows,gated_fixes:gated.map(fix=>({...toFixOutputRow(fix),gate_hint:`Withheld: re-run with --fix-scope ${fix.gate} to apply.`})),failed_fixes:failedFixRows}}async function runValidate(options,global2,services={}){assertProjectionModeChoice(options.counts===true,options.full===true,"--counts");const fixesRequested=options.autoFix===true||options.pruneMissing===true;if(options.dryRun===true&&!fixesRequested){throw new PmCliError("--dry-run requires --auto-fix or --prune-missing (there is nothing to preview otherwise).",EXIT_CODE.USAGE)}if(options.fixScope!==void 0&&options.fixScope.length>0&&options.autoFix!==true){throw new PmCliError("--fix-scope requires --auto-fix.",EXIT_CODE.USAGE)}const grantedFixScopes=resolveGrantedFixScopes(options.fixScope);const pmRoot=resolvePmRoot(process.cwd(),global2.path);await assertInitializedTracker(pmRoot);const settings=await readSettings(pmRoot);const statusRegistry=resolveRuntimeStatusRegistry(settings.schema);const typeRegistry=resolveItemTypeRegistry(settings,getActiveExtensionRegistrations());const requestedChecks=resolveRequestedChecks(options);const itemReadWarnings=[];const items=await readValidateItems({includeBody:["history_drift","metadata","completeness"].some(check=>requestedChecks.has(check)),pmRoot,settings,typeToFolder:typeRegistry.type_to_folder,warnings:itemReadWarnings});const sourceIncomplete=itemReadWarnings.length>0;if(requestedChecks.has("history_drift")){const authorAttribution=await scanHistoryAuthorAttribution(pmRoot);if(authorAttribution.actionable_unknown_event_count>0){itemReadWarnings.push(`validate_history_unknown_author_events:${authorAttribution.actionable_unknown_event_count}`)}}const metadataProfileSource=typeof options.metadataProfile==="string"?"option":"settings";const metadataProfile=resolveValidateMetadataProfile(typeof options.metadataProfile==="string"?options.metadataProfile:settings.validation.metadata_profile);const metadataPolicy=resolveValidateMetadataPolicy(metadataProfile,metadataProfileSource,settings.validation.metadata_required_fields);const lifecyclePatternPolicy=resolveLifecyclePatternPolicy(settings);const dependencyCycleSeverity=resolveDependencyCycleSeverity(options.dependencyCycleSeverity);const parentCycleSeverity=resolveParentCycleSeverity(options.parentCycleSeverity);const fileScanMode=resolveFileScanMode(options.scanMode);const workspaceRoot=resolveWorkspaceRoot(pmRoot);const state=await executeRequestedValidateChecks({requestedChecks,options,global:global2,pmRoot,workspaceRoot,settings,items,statusRegistry,metadataPolicy,lifecyclePatternPolicy,dependencyCycleSeverity,parentCycleSeverity,fileScanMode,initialWarnings:[...new Set(itemReadWarnings)],sourceIncomplete});const fixes=fixesRequested?await buildValidateFixesSummary(options,state,settings,grantedFixScopes,global2,services):void 0;const normalizedWarnings=[...new Set(state.warnings)].sort((left,right)=>left.localeCompare(right));const hasErrors=state.checks.some(check=>check.status==="error");const result={ok:!hasErrors,has_warnings:normalizedWarnings.length>0,checks:state.checks,warnings:normalizedWarnings,...fixes!==void 0?{fixes}:{},generated_at:nowIso(),projection:{mode:"full",declared_field_groups:[{name:"diagnostic_rows",restore_with:"--full"}],included_field_groups:["diagnostic_rows"]}};if(options.counts!==true)return result;return{...projectValidateCounts(result),projection:{mode:"counts",declared_field_groups:[{name:"diagnostic_rows",restore_with:"--full"}],included_field_groups:[]}}}export{_testOnlyValidateCommand,projectValidateCounts,runValidate};
2261
2
  //# sourceMappingURL=validate.js.map
2262
- //# debugId=898c4c05-70ab-5ea4-851e-9e224a85afc4