@unbrained/pm-cli 2026.7.14 → 2026.7.16

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 (708) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/CHANGELOG.md +54 -0
  3. package/README.md +14 -11
  4. package/dist/cli/argv-utils.d.ts +1 -4
  5. package/dist/cli/argv-utils.js +3 -13
  6. package/dist/cli/argv-utils.js.map +1 -1
  7. package/dist/cli/bootstrap-args.d.ts +6 -4
  8. package/dist/cli/bootstrap-args.js +51 -16
  9. package/dist/cli/bootstrap-args.js.map +1 -1
  10. package/dist/cli/bundle-integrity.d.ts +49 -0
  11. package/dist/cli/bundle-integrity.js +111 -0
  12. package/dist/cli/bundle-integrity.js.map +1 -0
  13. package/dist/cli/commands/completion.js +7 -2
  14. package/dist/cli/commands/completion.js.map +1 -1
  15. package/dist/cli/commands/config.d.ts +6 -163
  16. package/dist/cli/commands/config.js +4 -1133
  17. package/dist/cli/commands/config.js.map +1 -1
  18. package/dist/cli/commands/context.d.ts +2 -4
  19. package/dist/cli/commands/context.js +10 -85
  20. package/dist/cli/commands/context.js.map +1 -1
  21. package/dist/cli/commands/create.d.ts +1 -0
  22. package/dist/cli/commands/create.js +3 -2
  23. package/dist/cli/commands/create.js.map +1 -1
  24. package/dist/cli/commands/extension/describe.d.ts +2 -91
  25. package/dist/cli/commands/extension/describe.js +4 -142
  26. package/dist/cli/commands/extension/describe.js.map +1 -1
  27. package/dist/cli/commands/extension/scaffold.d.ts +2 -36
  28. package/dist/cli/commands/extension/scaffold.js +4 -2435
  29. package/dist/cli/commands/extension/scaffold.js.map +1 -1
  30. package/dist/cli/commands/extension/shared.d.ts +2 -28
  31. package/dist/cli/commands/extension/shared.js +4 -130
  32. package/dist/cli/commands/extension/shared.js.map +1 -1
  33. package/dist/cli/commands/extension.d.ts +3 -400
  34. package/dist/cli/commands/extension.js +4 -2183
  35. package/dist/cli/commands/extension.js.map +1 -1
  36. package/dist/cli/commands/gc.d.ts +6 -56
  37. package/dist/cli/commands/gc.js +4 -270
  38. package/dist/cli/commands/gc.js.map +1 -1
  39. package/dist/cli/commands/get.d.ts +18 -5
  40. package/dist/cli/commands/get.js +117 -58
  41. package/dist/cli/commands/get.js.map +1 -1
  42. package/dist/cli/commands/health.d.ts +6 -184
  43. package/dist/cli/commands/health.js +4 -1747
  44. package/dist/cli/commands/health.js.map +1 -1
  45. package/dist/cli/commands/history-compact.d.ts +4 -139
  46. package/dist/cli/commands/history-compact.js +5 -510
  47. package/dist/cli/commands/history-compact.js.map +1 -1
  48. package/dist/cli/commands/history-redact.d.ts +8 -120
  49. package/dist/cli/commands/history-redact.js +6 -581
  50. package/dist/cli/commands/history-redact.js.map +1 -1
  51. package/dist/cli/commands/history-repair.d.ts +7 -89
  52. package/dist/cli/commands/history-repair.js +5 -257
  53. package/dist/cli/commands/history-repair.js.map +1 -1
  54. package/dist/cli/commands/history.d.ts +1 -2
  55. package/dist/cli/commands/history.js +6 -48
  56. package/dist/cli/commands/history.js.map +1 -1
  57. package/dist/cli/commands/index.d.ts +1 -0
  58. package/dist/cli/commands/index.js +3 -2
  59. package/dist/cli/commands/index.js.map +1 -1
  60. package/dist/cli/commands/init-agent-guidance.d.ts +6 -113
  61. package/dist/cli/commands/init-agent-guidance.js +4 -431
  62. package/dist/cli/commands/init-agent-guidance.js.map +1 -1
  63. package/dist/cli/commands/init.d.ts +6 -181
  64. package/dist/cli/commands/init.js +4 -690
  65. package/dist/cli/commands/init.js.map +1 -1
  66. package/dist/cli/commands/next.js +3 -3
  67. package/dist/cli/commands/next.js.map +1 -1
  68. package/dist/cli/commands/profile.d.ts +4 -174
  69. package/dist/cli/commands/profile.js +4 -425
  70. package/dist/cli/commands/profile.js.map +1 -1
  71. package/dist/cli/commands/restore.d.ts +4 -19
  72. package/dist/cli/commands/restore.js +9 -158
  73. package/dist/cli/commands/restore.js.map +1 -1
  74. package/dist/cli/commands/schema.d.ts +7 -507
  75. package/dist/cli/commands/schema.js +5 -1254
  76. package/dist/cli/commands/schema.js.map +1 -1
  77. package/dist/cli/commands/test.d.ts +2 -4
  78. package/dist/cli/commands/test.js +7 -97
  79. package/dist/cli/commands/test.js.map +1 -1
  80. package/dist/cli/commands/validate.d.ts +9 -249
  81. package/dist/cli/commands/validate.js +9 -2150
  82. package/dist/cli/commands/validate.js.map +1 -1
  83. package/dist/cli/extension-command-help.js +18 -4
  84. package/dist/cli/extension-command-help.js.map +1 -1
  85. package/dist/cli/extension-command-options.js +3 -3
  86. package/dist/cli/extension-command-options.js.map +1 -1
  87. package/dist/cli/help-content.js +7 -3
  88. package/dist/cli/help-content.js.map +1 -1
  89. package/dist/cli/main.js +20 -30
  90. package/dist/cli/main.js.map +1 -1
  91. package/dist/cli/register-list-query.js +4 -2
  92. package/dist/cli/register-list-query.js.map +1 -1
  93. package/dist/cli/register-mutation.js +11 -3
  94. package/dist/cli/register-mutation.js.map +1 -1
  95. package/dist/cli/register-setup.js +4 -2
  96. package/dist/cli/register-setup.js.map +1 -1
  97. package/dist/cli/registration-helpers.js +3 -2
  98. package/dist/cli/registration-helpers.js.map +1 -1
  99. package/dist/cli-bundle/bundle-manifest.json +782 -0
  100. package/dist/cli-bundle/chunks/{append-PVZJDM6J.js → append-OJQFUQ4T.js} +5 -5
  101. package/dist/cli-bundle/chunks/append-UZVHUO74.js +11 -0
  102. package/dist/cli-bundle/chunks/{chunk-MQZ3PLCF.js → chunk-2IFFUZLC.js} +2 -2
  103. package/dist/cli-bundle/chunks/{chunk-P6MYFH6K.js → chunk-2JKGO6QX.js} +4 -4
  104. package/dist/cli-bundle/chunks/{chunk-X3GYR4P2.js → chunk-2NCIPMQP.js} +2 -2
  105. package/dist/cli-bundle/chunks/{chunk-Q7E6OYZK.js → chunk-2VPAVL7O.js} +2 -2
  106. package/dist/cli-bundle/chunks/{chunk-DRNJPEPQ.js → chunk-2WSE6PO2.js} +4 -4
  107. package/dist/cli-bundle/chunks/{chunk-FDSBDNYE.js → chunk-34L5OT3Y.js} +2 -2
  108. package/dist/cli-bundle/chunks/{chunk-TH7U4KLY.js → chunk-3AUGG2DP.js} +3 -3
  109. package/dist/cli-bundle/chunks/{chunk-NKW36AMT.js → chunk-3CLU2XZO.js} +4 -4
  110. package/dist/cli-bundle/chunks/{chunk-N3CMP4NI.js → chunk-3S5QVM5H.js} +43 -7
  111. package/dist/cli-bundle/chunks/chunk-3S5QVM5H.js.map +1 -0
  112. package/dist/cli-bundle/chunks/chunk-3U7ZR6M4.js +64 -0
  113. package/dist/cli-bundle/chunks/chunk-3U7ZR6M4.js.map +7 -0
  114. package/dist/cli-bundle/chunks/{chunk-ABRCOSDC.js → chunk-3WM75PWC.js} +3 -3
  115. package/dist/cli-bundle/chunks/chunk-4223PVZT.js +121 -0
  116. package/dist/cli-bundle/chunks/chunk-4223PVZT.js.map +7 -0
  117. package/dist/cli-bundle/chunks/{chunk-5URI7TPJ.js → chunk-4XQ3BDV4.js} +281 -164
  118. package/dist/cli-bundle/chunks/chunk-4XQ3BDV4.js.map +7 -0
  119. package/dist/cli-bundle/chunks/{chunk-X6BVXQ52.js → chunk-5JLUII3O.js} +13 -13
  120. package/dist/cli-bundle/chunks/{chunk-MKL4CP2I.js → chunk-5ZSLZLDN.js} +631 -246
  121. package/dist/cli-bundle/chunks/chunk-5ZSLZLDN.js.map +1 -0
  122. package/dist/cli-bundle/chunks/{chunk-Q37E63AM.js → chunk-6CFNAVTF.js} +21 -141
  123. package/dist/cli-bundle/chunks/chunk-6CFNAVTF.js.map +7 -0
  124. package/dist/cli-bundle/chunks/{chunk-3TL7WOEF.js → chunk-6N2VXVTQ.js} +2 -2
  125. package/dist/cli-bundle/chunks/{chunk-ERL2YFJ2.js → chunk-6S3YSUYG.js} +3 -3
  126. package/dist/cli-bundle/chunks/{chunk-ZUKSZVBW.js → chunk-7B67ZL4N.js} +8 -7
  127. package/dist/cli-bundle/chunks/chunk-7B67ZL4N.js.map +7 -0
  128. package/dist/cli-bundle/chunks/{chunk-IA3YFTXT.js → chunk-7PIWCHTW.js} +11 -13
  129. package/dist/cli-bundle/chunks/chunk-7PIWCHTW.js.map +1 -0
  130. package/dist/cli-bundle/chunks/{chunk-BPD26E3P.js → chunk-7Z4LKRS4.js} +291 -81
  131. package/dist/cli-bundle/chunks/chunk-7Z4LKRS4.js.map +1 -0
  132. package/dist/cli-bundle/chunks/{chunk-IFZA4VB5.js → chunk-AHVTB5I5.js} +4 -4
  133. package/dist/cli-bundle/chunks/{chunk-DO5RGJXT.js → chunk-APRVSW2O.js} +3 -3
  134. package/dist/cli-bundle/chunks/{chunk-NAQYVWSQ.js → chunk-B7PE22W6.js} +4 -4
  135. package/dist/cli-bundle/chunks/{chunk-MD6ULIPJ.js → chunk-BCOFWYW4.js} +3 -3
  136. package/dist/cli-bundle/chunks/{chunk-KADLSPIU.js → chunk-BEP7WJA2.js} +4 -4
  137. package/dist/cli-bundle/chunks/chunk-BKVG6XOA.js +1114 -0
  138. package/dist/cli-bundle/chunks/chunk-BKVG6XOA.js.map +7 -0
  139. package/dist/cli-bundle/chunks/{chunk-AFWZRL7M.js → chunk-BSEQUTQP.js} +2 -2
  140. package/dist/cli-bundle/chunks/{chunk-AFWZRL7M.js.map → chunk-BSEQUTQP.js.map} +2 -2
  141. package/dist/cli-bundle/chunks/{chunk-OBDLEQG5.js → chunk-CRVF2JDM.js} +10 -10
  142. package/dist/cli-bundle/chunks/{chunk-FJZDA6MS.js → chunk-DM4H7FP4.js} +3 -3
  143. package/dist/cli-bundle/chunks/chunk-DUT67M43.js +5 -0
  144. package/dist/cli-bundle/chunks/{chunk-44LNIULG.js → chunk-DYH63FX7.js} +4 -4
  145. package/dist/cli-bundle/chunks/{chunk-LWTIRS3Y.js → chunk-E2OJMKML.js} +306 -82
  146. package/dist/cli-bundle/chunks/chunk-E2OJMKML.js.map +7 -0
  147. package/dist/cli-bundle/chunks/{chunk-HAMLIWAF.js → chunk-EAB64KLB.js} +4 -4
  148. package/dist/cli-bundle/chunks/{chunk-CIOZ376P.js → chunk-EACOH7AU.js} +10 -10
  149. package/dist/cli-bundle/chunks/{chunk-HL7V2PHX.js → chunk-EH2OPHBI.js} +10 -10
  150. package/dist/cli-bundle/chunks/{chunk-HL7V2PHX.js.map → chunk-EH2OPHBI.js.map} +1 -1
  151. package/dist/cli-bundle/chunks/{chunk-QK2ZN6UL.js → chunk-EJEXHTY5.js} +2 -2
  152. package/dist/cli-bundle/chunks/{chunk-WQUEHPZB.js → chunk-EYLCSJAI.js} +2 -2
  153. package/dist/cli-bundle/chunks/{chunk-655STMGA.js → chunk-FMBNM2EN.js} +2 -2
  154. package/dist/cli-bundle/chunks/{chunk-RMF3IOYQ.js → chunk-FNSPG7MU.js} +12 -12
  155. package/dist/cli-bundle/chunks/{chunk-5CT5BW6E.js → chunk-GM5QHQU6.js} +46 -40
  156. package/dist/cli-bundle/chunks/chunk-GM5QHQU6.js.map +7 -0
  157. package/dist/cli-bundle/chunks/{chunk-FTQLW4JD.js → chunk-GOSLKLFE.js} +418 -24
  158. package/dist/cli-bundle/chunks/chunk-GOSLKLFE.js.map +1 -0
  159. package/dist/cli-bundle/chunks/{chunk-KB726FMH.js → chunk-H2Z3FYXQ.js} +4 -4
  160. package/dist/cli-bundle/chunks/chunk-H3EIEWGR.js +162 -0
  161. package/dist/cli-bundle/chunks/chunk-H3EIEWGR.js.map +1 -0
  162. package/dist/cli-bundle/chunks/{chunk-2QIQGYPW.js → chunk-H5APFTHZ.js} +53 -10
  163. package/dist/cli-bundle/chunks/chunk-H5APFTHZ.js.map +7 -0
  164. package/dist/cli-bundle/chunks/chunk-HDH3JUGG.js +118 -0
  165. package/dist/cli-bundle/chunks/chunk-HDH3JUGG.js.map +1 -0
  166. package/dist/cli-bundle/chunks/{chunk-ZAYYDNB3.js → chunk-HO4KXD6I.js} +22 -142
  167. package/dist/cli-bundle/chunks/chunk-HO4KXD6I.js.map +1 -0
  168. package/dist/cli-bundle/chunks/{chunk-ILLUE5DI.js → chunk-HYB5AZ63.js} +203 -62
  169. package/dist/cli-bundle/chunks/{chunk-ILLUE5DI.js.map → chunk-HYB5AZ63.js.map} +3 -3
  170. package/dist/cli-bundle/chunks/{chunk-BN62TO2S.js → chunk-IA5FKNII.js} +2 -2
  171. package/dist/cli-bundle/chunks/{chunk-JL7WZ4O2.js → chunk-IG3SWA4A.js} +33 -9
  172. package/dist/cli-bundle/chunks/chunk-IG3SWA4A.js.map +7 -0
  173. package/dist/cli-bundle/chunks/{chunk-W6HZ55JO.js → chunk-IJIICKVW.js} +2 -2
  174. package/dist/cli-bundle/chunks/{chunk-IGED7NH2.js → chunk-ILX33QXL.js} +668 -251
  175. package/dist/cli-bundle/chunks/chunk-ILX33QXL.js.map +7 -0
  176. package/dist/cli-bundle/chunks/{chunk-UGTVWL3E.js → chunk-INU6DKT5.js} +47 -41
  177. package/dist/cli-bundle/chunks/chunk-INU6DKT5.js.map +1 -0
  178. package/dist/cli-bundle/chunks/{chunk-UB2QYSTO.js → chunk-J5AOEGDC.js} +26 -18
  179. package/dist/cli-bundle/chunks/chunk-J5AOEGDC.js.map +1 -0
  180. package/dist/cli-bundle/chunks/{chunk-4DY4KPQI.js → chunk-J6364LFM.js} +1330 -937
  181. package/dist/cli-bundle/chunks/chunk-J6364LFM.js.map +7 -0
  182. package/dist/cli-bundle/chunks/{chunk-DVOLZJ3C.js → chunk-J6EY5V4N.js} +1295 -939
  183. package/dist/cli-bundle/chunks/chunk-J6EY5V4N.js.map +1 -0
  184. package/dist/cli-bundle/chunks/chunk-JFTKLTEF.js +1097 -0
  185. package/dist/cli-bundle/chunks/chunk-JFTKLTEF.js.map +1 -0
  186. package/dist/cli-bundle/chunks/{chunk-M7EWN4TF.js → chunk-JOUQZENX.js} +3 -3
  187. package/dist/cli-bundle/chunks/{chunk-J4NZOP3I.js → chunk-JXQUXVMI.js} +9 -8
  188. package/dist/cli-bundle/chunks/chunk-JXQUXVMI.js.map +1 -0
  189. package/dist/cli-bundle/chunks/{chunk-2FNWQXZ2.js → chunk-JYFOV7FC.js} +4 -4
  190. package/dist/cli-bundle/chunks/{chunk-HLHSQ5CA.js → chunk-K2X6U4UA.js} +4 -4
  191. package/dist/cli-bundle/chunks/{chunk-6OTZMORS.js → chunk-K3WII2ME.js} +27 -27
  192. package/dist/cli-bundle/chunks/chunk-K3WII2ME.js.map +7 -0
  193. package/dist/cli-bundle/chunks/{chunk-M6ASXYCU.js → chunk-KG44YSKD.js} +4 -4
  194. package/dist/cli-bundle/chunks/{chunk-YVKE5ZDM.js → chunk-KPMKHGEA.js} +4 -4
  195. package/dist/cli-bundle/chunks/{chunk-QASRVKUQ.js → chunk-LPYWLWDS.js} +257 -163
  196. package/dist/cli-bundle/chunks/chunk-LPYWLWDS.js.map +1 -0
  197. package/dist/cli-bundle/chunks/{chunk-RGXD5JJR.js → chunk-LSG2U345.js} +19 -12
  198. package/dist/cli-bundle/chunks/chunk-LSG2U345.js.map +7 -0
  199. package/dist/cli-bundle/chunks/chunk-MIQDDLB5.js +5 -0
  200. package/dist/cli-bundle/chunks/{chunk-UJ4HJYWL.js → chunk-NLDFBC6J.js} +2 -2
  201. package/dist/cli-bundle/chunks/{chunk-WRKB56FC.js → chunk-NP22ERDF.js} +3 -3
  202. package/dist/cli-bundle/chunks/{chunk-YQYG2V23.js → chunk-NU3IMIEA.js} +2 -2
  203. package/dist/cli-bundle/chunks/{chunk-3I3LRT2G.js → chunk-NVPZIJVR.js} +2 -2
  204. package/dist/cli-bundle/chunks/{chunk-JAF4TUQ7.js → chunk-ODALILG3.js} +2 -2
  205. package/dist/cli-bundle/chunks/{chunk-CDJYP7OQ.js → chunk-ONG5V5CI.js} +190 -63
  206. package/dist/cli-bundle/chunks/chunk-ONG5V5CI.js.map +1 -0
  207. package/dist/cli-bundle/chunks/{chunk-QZ7LT3XE.js → chunk-ONREO4ST.js} +3 -3
  208. package/dist/cli-bundle/chunks/chunk-OW4JWD5M.js +61 -0
  209. package/dist/cli-bundle/chunks/chunk-OW4JWD5M.js.map +1 -0
  210. package/dist/cli-bundle/chunks/{chunk-3CMRNVTO.js → chunk-P74IZQ7A.js} +12 -12
  211. package/dist/cli-bundle/chunks/chunk-PENXZX3U.js +172 -0
  212. package/dist/cli-bundle/chunks/chunk-PENXZX3U.js.map +7 -0
  213. package/dist/cli-bundle/chunks/{chunk-ZTRBSABY.js → chunk-PGY23YTC.js} +3 -3
  214. package/dist/cli-bundle/chunks/{chunk-H2QBAKLQ.js → chunk-PMOSXADO.js} +3 -3
  215. package/dist/cli-bundle/chunks/chunk-PNTULOGU.js +9 -0
  216. package/dist/cli-bundle/chunks/{chunk-GSEADLRH.js → chunk-Q7A43DS4.js} +2 -2
  217. package/dist/cli-bundle/chunks/{chunk-QSPARJWF.js → chunk-QEHQ2GVG.js} +2 -2
  218. package/dist/cli-bundle/chunks/{chunk-VJTAEIPC.js → chunk-QIPLHNP7.js} +7 -7
  219. package/dist/cli-bundle/chunks/{chunk-GXXC7QSK.js → chunk-QKECXBBG.js} +3 -3
  220. package/dist/cli-bundle/chunks/{chunk-GXXC7QSK.js.map → chunk-QKECXBBG.js.map} +1 -1
  221. package/dist/cli-bundle/chunks/{chunk-K7WNZ7KD.js → chunk-QKJGPFAF.js} +3 -3
  222. package/dist/cli-bundle/chunks/{chunk-F7PSHRNO.js → chunk-QKXHFPYM.js} +27 -10
  223. package/dist/cli-bundle/chunks/chunk-QKXHFPYM.js.map +1 -0
  224. package/dist/cli-bundle/chunks/{chunk-ZNP64HOW.js → chunk-RLCHOOHC.js} +2 -2
  225. package/dist/cli-bundle/chunks/{chunk-IAPHYPRL.js → chunk-RMLWNPH2.js} +2 -2
  226. package/dist/cli-bundle/chunks/{chunk-UVBA6F5A.js → chunk-RRM5JYJC.js} +2 -2
  227. package/dist/cli-bundle/chunks/{chunk-6IIVBC56.js → chunk-S6MQESFR.js} +4 -4
  228. package/dist/cli-bundle/chunks/{chunk-QABYZYLU.js → chunk-SDQLJWBO.js} +4 -4
  229. package/dist/cli-bundle/chunks/{chunk-ANSJHDOZ.js → chunk-SELVXEZK.js} +2 -2
  230. package/dist/cli-bundle/chunks/{chunk-DYEX6NFH.js → chunk-TD3LVC7D.js} +438 -23
  231. package/dist/cli-bundle/chunks/chunk-TD3LVC7D.js.map +7 -0
  232. package/dist/cli-bundle/chunks/{chunk-WD6MSYMP.js → chunk-TKOVBP6C.js} +2 -2
  233. package/dist/cli-bundle/chunks/{chunk-TZ2PSLLT.js → chunk-TQSZFPIS.js} +4 -4
  234. package/dist/cli-bundle/chunks/{chunk-WPAWZW7S.js → chunk-TSMHDIW4.js} +2 -2
  235. package/dist/cli-bundle/chunks/{chunk-TZRTLMWM.js → chunk-UFXU44HV.js} +4 -4
  236. package/dist/cli-bundle/chunks/{chunk-TWLDBKQU.js → chunk-ULTHRDBP.js} +3 -3
  237. package/dist/cli-bundle/chunks/{chunk-OI4S4KCI.js → chunk-UMOPHQSU.js} +9 -9
  238. package/dist/cli-bundle/chunks/{chunk-OI4S4KCI.js.map → chunk-UMOPHQSU.js.map} +2 -2
  239. package/dist/cli-bundle/chunks/{chunk-RA5AIAMP.js → chunk-UOHFKBUV.js} +3 -3
  240. package/dist/cli-bundle/chunks/{chunk-PHKP3AGR.js → chunk-UWQHLMEB.js} +35 -18
  241. package/dist/cli-bundle/chunks/chunk-UWQHLMEB.js.map +7 -0
  242. package/dist/cli-bundle/chunks/{chunk-6TTOHMTB.js → chunk-UXONS674.js} +45 -112
  243. package/dist/cli-bundle/chunks/chunk-UXONS674.js.map +1 -0
  244. package/dist/cli-bundle/chunks/{chunk-4RXTMU57.js → chunk-UY2ACEMF.js} +7 -7
  245. package/dist/cli-bundle/chunks/{chunk-6PKA3SJQ.js → chunk-WQPUU7XD.js} +19 -28
  246. package/dist/cli-bundle/chunks/chunk-WQPUU7XD.js.map +1 -0
  247. package/dist/cli-bundle/chunks/{chunk-7TQMR6LK.js → chunk-WT3ZEOGH.js} +70 -20
  248. package/dist/cli-bundle/chunks/chunk-WT3ZEOGH.js.map +7 -0
  249. package/dist/cli-bundle/chunks/{chunk-FIGXOCEQ.js → chunk-WUJQK4YV.js} +4 -4
  250. package/dist/cli-bundle/chunks/{chunk-T4BRCB6Z.js → chunk-XSJUSQAE.js} +2 -2
  251. package/dist/cli-bundle/chunks/{chunk-6MHSBDGD.js → chunk-XXXF6PAQ.js} +50 -117
  252. package/dist/cli-bundle/chunks/chunk-XXXF6PAQ.js.map +7 -0
  253. package/dist/cli-bundle/chunks/{chunk-UKUIIBDW.js → chunk-YAQEBNY7.js} +4 -4
  254. package/dist/cli-bundle/chunks/{chunk-7QDSYUMF.js → chunk-YAROI3PD.js} +44 -21
  255. package/dist/cli-bundle/chunks/chunk-YAROI3PD.js.map +1 -0
  256. package/dist/cli-bundle/chunks/{chunk-E2WC6S4P.js → chunk-YBLZ2VE7.js} +13 -13
  257. package/dist/cli-bundle/chunks/{chunk-Y72FV7RM.js → chunk-YCQET3OT.js} +2 -2
  258. package/dist/cli-bundle/chunks/chunk-YUSOJDWM.js +9 -0
  259. package/dist/cli-bundle/chunks/chunk-YXSHIAUJ.js +58 -0
  260. package/dist/cli-bundle/chunks/chunk-YXSHIAUJ.js.map +1 -0
  261. package/dist/cli-bundle/chunks/{chunk-FS6UM5WH.js → chunk-ZI6VQWQ7.js} +4 -4
  262. package/dist/cli-bundle/chunks/chunk-ZKRZY773.js +68 -0
  263. package/dist/cli-bundle/chunks/chunk-ZKRZY773.js.map +7 -0
  264. package/dist/cli-bundle/chunks/{close-LZECOMHO.js → close-CWODTPKG.js} +5 -5
  265. package/dist/cli-bundle/chunks/close-W6LI2PKM.js +12 -0
  266. package/dist/cli-bundle/chunks/{close-many-ZHI6A5TN.js → close-many-CQFUY7MR.js} +15 -13
  267. package/dist/cli-bundle/chunks/close-many-DLDDVSH5.js +23 -0
  268. package/dist/cli-bundle/chunks/comments-E3TNTK24.js +13 -0
  269. package/dist/cli-bundle/chunks/{comments-AJWRTFZ5.js → comments-WGLHR7LB.js} +7 -7
  270. package/dist/cli-bundle/chunks/{copy-GMXYKBXT.js → copy-LXVDXH7B.js} +4 -4
  271. package/dist/cli-bundle/chunks/copy-Q5ZJPUKR.js +9 -0
  272. package/dist/cli-bundle/chunks/{create-F53OBTOU.js → create-J5U6ND4K.js} +9 -9
  273. package/dist/cli-bundle/chunks/{create-HU4Q7RJX.js → create-LBVCYMEG.js} +9 -9
  274. package/dist/cli-bundle/chunks/{delete-GSDZLTRC.js → delete-PMJBNY5G.js} +4 -4
  275. package/dist/cli-bundle/chunks/delete-WNHB3LYW.js +10 -0
  276. package/dist/cli-bundle/chunks/{deps-WWB7P2GT.js → deps-JZ7TMVCB.js} +7 -4
  277. package/dist/cli-bundle/chunks/{deps-U57I7EAK.js → deps-OA26IS25.js} +7 -4
  278. package/dist/cli-bundle/chunks/{docs-MQIY2ZQS.js → docs-FEWB2DQB.js} +6 -6
  279. package/dist/cli-bundle/chunks/docs-HA2AIDBB.js +14 -0
  280. package/dist/cli-bundle/chunks/{files-TBZD7V6H.js → files-6YFBJRBG.js} +6 -6
  281. package/dist/cli-bundle/chunks/{files-TTY6N2AV.js → files-EFIWKFTM.js} +6 -6
  282. package/dist/cli-bundle/chunks/{focus-XT6YVFFX.js → focus-4QJR7FOQ.js} +5 -5
  283. package/dist/cli-bundle/chunks/focus-POIW5LTJ.js +10 -0
  284. package/dist/cli-bundle/chunks/{history-compact-KKVUWB4V.js → history-compact-CKRPDTJN.js} +6 -10
  285. package/dist/cli-bundle/chunks/history-compact-FWG2C7FZ.js +16 -0
  286. package/dist/cli-bundle/chunks/history-redact-BB3TGDAE.js +15 -0
  287. package/dist/cli-bundle/chunks/{history-redact-3DVZOILD.js → history-redact-D225BHOZ.js} +5 -6
  288. package/dist/cli-bundle/chunks/history-repair-6GEAM7HE.js +16 -0
  289. package/dist/cli-bundle/chunks/{history-repair-ITDUMK4N.js → history-repair-RQXKQHL4.js} +6 -8
  290. package/dist/cli-bundle/chunks/{learnings-FGO3NSFW.js → learnings-JLNLCOL5.js} +7 -7
  291. package/dist/cli-bundle/chunks/learnings-RSNLSWDC.js +13 -0
  292. package/dist/cli-bundle/chunks/notes-2WDH6DXU.js +13 -0
  293. package/dist/cli-bundle/chunks/{notes-NQ6RXVW6.js → notes-GB47XXQQ.js} +7 -7
  294. package/dist/cli-bundle/chunks/plan-DOVI2VMW.js +24 -0
  295. package/dist/cli-bundle/chunks/{plan-2NNEO46V.js → plan-VDCJ2PCN.js} +10 -10
  296. package/dist/cli-bundle/chunks/{profile-AAQUU6T3.js → profile-FAU7NZML.js} +10 -10
  297. package/dist/cli-bundle/chunks/{profile-U725ATWZ.js → profile-IVXQEL3D.js} +10 -10
  298. package/dist/cli-bundle/chunks/{register-list-query-BUWCYHBT.js → register-list-query-DKA3EH5D.js} +22 -19
  299. package/dist/cli-bundle/chunks/register-list-query-DKA3EH5D.js.map +7 -0
  300. package/dist/cli-bundle/chunks/{register-list-query-KTZ6AGE7.js → register-list-query-UL4UKBBN.js} +23 -20
  301. package/dist/cli-bundle/chunks/register-list-query-UL4UKBBN.js.map +1 -0
  302. package/dist/cli-bundle/chunks/{register-mutation-UELYYOR7.js → register-mutation-ARFHRW55.js} +36 -32
  303. package/dist/cli-bundle/chunks/{register-mutation-UELYYOR7.js.map → register-mutation-ARFHRW55.js.map} +2 -2
  304. package/dist/cli-bundle/chunks/{register-mutation-4FMGJJF7.js → register-mutation-RBBJ52DK.js} +37 -33
  305. package/dist/cli-bundle/chunks/register-mutation-RBBJ52DK.js.map +1 -0
  306. package/dist/cli-bundle/chunks/{register-operations-JVW24J2Y.js → register-operations-ZIBBRSED.js} +41 -39
  307. package/dist/cli-bundle/chunks/{register-operations-JVW24J2Y.js.map → register-operations-ZIBBRSED.js.map} +1 -1
  308. package/dist/cli-bundle/chunks/{register-operations-ISUX6LY5.js → register-operations-ZUJ5JWPE.js} +39 -37
  309. package/dist/cli-bundle/chunks/{register-operations-ISUX6LY5.js.map → register-operations-ZUJ5JWPE.js.map} +1 -1
  310. package/dist/cli-bundle/chunks/{register-setup-5XNGKJ22.js → register-setup-BCVLFEYS.js} +29 -17
  311. package/dist/cli-bundle/chunks/{register-setup-5XNGKJ22.js.map → register-setup-BCVLFEYS.js.map} +2 -2
  312. package/dist/cli-bundle/chunks/{register-setup-ZTNY6S7R.js → register-setup-TDCGWIOS.js} +19 -18
  313. package/dist/cli-bundle/chunks/{register-setup-ZTNY6S7R.js.map → register-setup-TDCGWIOS.js.map} +1 -1
  314. package/dist/cli-bundle/chunks/{restore-B3BAB6X2.js → restore-25DLT2Q2.js} +6 -6
  315. package/dist/cli-bundle/chunks/restore-YPMVUUEX.js +14 -0
  316. package/dist/cli-bundle/chunks/{schema-U7IM2DXV.js → schema-FNU3WE4O.js} +7 -6
  317. package/dist/cli-bundle/chunks/{schema-LZNUYHDB.js → schema-NBCM3RAD.js} +7 -6
  318. package/dist/cli-bundle/chunks/update-WWBYOPXO.js +21 -0
  319. package/dist/cli-bundle/chunks/update-WWBYOPXO.js.map +7 -0
  320. package/dist/cli-bundle/chunks/{update-Q3UCILNR.js → update-XHSIAIXE.js} +12 -12
  321. package/dist/cli-bundle/chunks/update-XHSIAIXE.js.map +1 -0
  322. package/dist/cli-bundle/chunks/update-many-OUGIU5SF.js +32 -0
  323. package/dist/cli-bundle/chunks/update-many-OUGIU5SF.js.map +7 -0
  324. package/dist/cli-bundle/chunks/update-many-TA7NGQBO.js +36 -0
  325. package/dist/cli-bundle/chunks/update-many-TA7NGQBO.js.map +1 -0
  326. package/dist/cli-bundle/main.js +826 -43
  327. package/dist/cli-bundle/main.js.map +4 -4
  328. package/dist/cli-bundle/sdk-runtime.js +70 -57
  329. package/dist/cli-bundle/sdk-testing.js +6 -6
  330. package/dist/cli-bundle/sdk.js +876 -74
  331. package/dist/cli-bundle/sdk.js.map +4 -4
  332. package/dist/cli.js +5 -4
  333. package/dist/cli.js.map +1 -1
  334. package/dist/core/diagnostics/remediation.js +12 -2
  335. package/dist/core/diagnostics/remediation.js.map +1 -1
  336. package/dist/core/history/projection.d.ts +39 -0
  337. package/dist/core/history/projection.js +164 -0
  338. package/dist/core/history/projection.js.map +1 -0
  339. package/dist/core/history/read.d.ts +3 -0
  340. package/dist/core/history/read.js +55 -0
  341. package/dist/core/history/read.js.map +1 -0
  342. package/dist/core/output/output-control.d.ts +18 -0
  343. package/dist/core/output/output-control.js +26 -0
  344. package/dist/core/output/output-control.js.map +1 -0
  345. package/dist/core/output/output.js +20 -8
  346. package/dist/core/output/output.js.map +1 -1
  347. package/dist/core/sentry/helpers.js +10 -2
  348. package/dist/core/sentry/helpers.js.map +1 -1
  349. package/dist/core/shared/command-types.d.ts +2 -0
  350. package/dist/core/shared/command-types.js +2 -2
  351. package/dist/core/shared/command-types.js.map +1 -1
  352. package/dist/core/shared/errors.d.ts +11 -0
  353. package/dist/core/shared/errors.js +2 -2
  354. package/dist/core/shared/errors.js.map +1 -1
  355. package/dist/mcp/server.d.ts +2 -2
  356. package/dist/mcp/tool-definitions.js +5 -5
  357. package/dist/mcp/tool-definitions.js.map +1 -1
  358. package/dist/sdk/author-attribution.d.ts +35 -0
  359. package/dist/sdk/author-attribution.js +118 -0
  360. package/dist/sdk/author-attribution.js.map +1 -0
  361. package/dist/sdk/cli-contracts/command-aliases.d.ts +7 -0
  362. package/dist/sdk/cli-contracts/command-aliases.js +17 -0
  363. package/dist/sdk/cli-contracts/command-aliases.js.map +1 -0
  364. package/dist/sdk/cli-contracts/flag-contracts.js +5 -2
  365. package/dist/sdk/cli-contracts/flag-contracts.js.map +1 -1
  366. package/dist/sdk/cli-contracts/tool-option-contracts.js +6 -2
  367. package/dist/sdk/cli-contracts/tool-option-contracts.js.map +1 -1
  368. package/dist/sdk/cli-contracts/tool-parameter-tables.js +21 -2
  369. package/dist/sdk/cli-contracts/tool-parameter-tables.js.map +1 -1
  370. package/dist/sdk/cli-contracts/tool-schema.js +3 -3
  371. package/dist/sdk/cli-contracts/tool-schema.js.map +1 -1
  372. package/dist/sdk/cli-contracts.d.ts +1 -0
  373. package/dist/sdk/cli-contracts.js +3 -2
  374. package/dist/sdk/cli-contracts.js.map +1 -1
  375. package/dist/sdk/cli-program.d.ts +8 -0
  376. package/dist/sdk/cli-program.js +39 -0
  377. package/dist/sdk/cli-program.js.map +1 -0
  378. package/dist/sdk/command-line.d.ts +11 -0
  379. package/dist/sdk/command-line.js +40 -0
  380. package/dist/sdk/command-line.js.map +1 -0
  381. package/dist/sdk/config.d.ts +163 -0
  382. package/dist/sdk/config.js +1087 -0
  383. package/dist/sdk/config.js.map +1 -0
  384. package/dist/sdk/context-relevance.d.ts +21 -0
  385. package/dist/sdk/context-relevance.js +70 -2
  386. package/dist/sdk/context-relevance.js.map +1 -1
  387. package/dist/sdk/dependencies.d.ts +15 -2
  388. package/dist/sdk/dependencies.js +97 -8
  389. package/dist/sdk/dependencies.js.map +1 -1
  390. package/dist/{cli/commands → sdk}/extension/bundled-catalog.d.ts +1 -1
  391. package/dist/{cli/commands → sdk}/extension/bundled-catalog.js +12 -12
  392. package/dist/sdk/extension/bundled-catalog.js.map +1 -0
  393. package/dist/sdk/extension/concurrency.d.ts +7 -0
  394. package/dist/sdk/extension/concurrency.js +26 -0
  395. package/dist/sdk/extension/concurrency.js.map +1 -0
  396. package/dist/sdk/extension/describe.d.ts +91 -0
  397. package/dist/sdk/extension/describe.js +144 -0
  398. package/dist/sdk/extension/describe.js.map +1 -0
  399. package/dist/{cli/commands → sdk}/extension/doctor.d.ts +3 -3
  400. package/dist/{cli/commands → sdk}/extension/doctor.js +7 -7
  401. package/dist/sdk/extension/doctor.js.map +1 -0
  402. package/dist/sdk/extension/install-runtime.d.ts +26 -0
  403. package/dist/sdk/extension/install-runtime.js +269 -0
  404. package/dist/sdk/extension/install-runtime.js.map +1 -0
  405. package/dist/sdk/extension/install-snapshot.d.ts +23 -0
  406. package/dist/sdk/extension/install-snapshot.js +66 -0
  407. package/dist/sdk/extension/install-snapshot.js.map +1 -0
  408. package/dist/{cli/commands → sdk}/extension/install-sources.d.ts +3 -3
  409. package/dist/{cli/commands → sdk}/extension/install-sources.js +15 -12
  410. package/dist/sdk/extension/install-sources.js.map +1 -0
  411. package/dist/{cli/commands → sdk}/extension/managed-state.js +6 -6
  412. package/dist/sdk/extension/managed-state.js.map +1 -0
  413. package/dist/sdk/extension/scaffold.d.ts +36 -0
  414. package/dist/sdk/extension/scaffold.js +2436 -0
  415. package/dist/sdk/extension/scaffold.js.map +1 -0
  416. package/dist/sdk/extension/shared.d.ts +28 -0
  417. package/dist/sdk/extension/shared.js +140 -0
  418. package/dist/sdk/extension/shared.js.map +1 -0
  419. package/dist/sdk/extension/update-check.d.ts +15 -0
  420. package/dist/sdk/extension/update-check.js +68 -0
  421. package/dist/sdk/extension/update-check.js.map +1 -0
  422. package/dist/sdk/extension.d.ts +367 -0
  423. package/dist/sdk/extension.js +2174 -0
  424. package/dist/sdk/extension.js.map +1 -0
  425. package/dist/sdk/governance/gc.d.ts +56 -0
  426. package/dist/sdk/governance/gc.js +276 -0
  427. package/dist/sdk/governance/gc.js.map +1 -0
  428. package/dist/sdk/governance/health.d.ts +187 -0
  429. package/dist/sdk/governance/health.js +1796 -0
  430. package/dist/sdk/governance/health.js.map +1 -0
  431. package/dist/sdk/governance/validate.d.ts +255 -0
  432. package/dist/sdk/governance/validate.js +2175 -0
  433. package/dist/sdk/governance/validate.js.map +1 -0
  434. package/dist/sdk/history-compact.d.ts +142 -0
  435. package/dist/sdk/history-compact.js +508 -0
  436. package/dist/sdk/history-compact.js.map +1 -0
  437. package/dist/sdk/history-mcp.d.ts +17 -0
  438. package/dist/sdk/history-mcp.js +68 -0
  439. package/dist/sdk/history-mcp.js.map +1 -0
  440. package/dist/sdk/history-read.d.ts +31 -0
  441. package/dist/sdk/history-read.js +58 -0
  442. package/dist/sdk/history-read.js.map +1 -0
  443. package/dist/sdk/history-redact.d.ts +120 -0
  444. package/dist/sdk/history-redact.js +577 -0
  445. package/dist/sdk/history-redact.js.map +1 -0
  446. package/dist/sdk/history-repair.d.ts +89 -0
  447. package/dist/sdk/history-repair.js +263 -0
  448. package/dist/sdk/history-repair.js.map +1 -0
  449. package/dist/sdk/index.d.ts +25 -1
  450. package/dist/sdk/index.js +27 -3
  451. package/dist/sdk/index.js.map +1 -1
  452. package/dist/sdk/init-agent-guidance.d.ts +113 -0
  453. package/dist/sdk/init-agent-guidance.js +437 -0
  454. package/dist/sdk/init-agent-guidance.js.map +1 -0
  455. package/dist/sdk/init.d.ts +186 -0
  456. package/dist/sdk/init.js +733 -0
  457. package/dist/sdk/init.js.map +1 -0
  458. package/dist/sdk/invocation-author.d.ts +2 -0
  459. package/dist/sdk/invocation-author.js +39 -0
  460. package/dist/sdk/invocation-author.js.map +1 -0
  461. package/dist/sdk/item-children.d.ts +45 -0
  462. package/dist/sdk/item-children.js +75 -0
  463. package/dist/sdk/item-children.js.map +1 -0
  464. package/dist/sdk/item-schedule.d.ts +26 -0
  465. package/dist/sdk/item-schedule.js +28 -0
  466. package/dist/sdk/item-schedule.js.map +1 -0
  467. package/dist/sdk/output.d.ts +7 -0
  468. package/dist/sdk/output.js +11 -0
  469. package/dist/sdk/output.js.map +1 -0
  470. package/dist/sdk/pagination.d.ts +4 -1
  471. package/dist/sdk/pagination.js +13 -5
  472. package/dist/sdk/pagination.js.map +1 -1
  473. package/dist/sdk/profile.d.ts +176 -0
  474. package/dist/sdk/profile.js +431 -0
  475. package/dist/sdk/profile.js.map +1 -0
  476. package/dist/sdk/relationship-analytics.d.ts +108 -0
  477. package/dist/sdk/relationship-analytics.js +292 -0
  478. package/dist/sdk/relationship-analytics.js.map +1 -0
  479. package/dist/sdk/relationship-context.d.ts +67 -0
  480. package/dist/sdk/relationship-context.js +207 -0
  481. package/dist/sdk/relationship-context.js.map +1 -0
  482. package/dist/sdk/relationship-history.d.ts +109 -0
  483. package/dist/sdk/relationship-history.js +401 -0
  484. package/dist/sdk/relationship-history.js.map +1 -0
  485. package/dist/sdk/relationships.d.ts +145 -0
  486. package/dist/sdk/relationships.js +551 -0
  487. package/dist/sdk/relationships.js.map +1 -0
  488. package/dist/sdk/runtime-input.d.ts +10 -0
  489. package/dist/sdk/runtime-input.js +60 -0
  490. package/dist/sdk/runtime-input.js.map +1 -0
  491. package/dist/sdk/runtime.d.ts +31 -8
  492. package/dist/sdk/runtime.js +61 -57
  493. package/dist/sdk/runtime.js.map +1 -1
  494. package/dist/sdk/schema.d.ts +507 -0
  495. package/dist/sdk/schema.js +1269 -0
  496. package/dist/sdk/schema.js.map +1 -0
  497. package/dist/{cli/commands → sdk}/templates.d.ts +1 -1
  498. package/dist/{cli/commands → sdk}/templates.js +9 -9
  499. package/dist/sdk/templates.js.map +1 -0
  500. package/dist/{cli/commands → sdk}/test/linked-command-detection.d.ts +10 -2
  501. package/dist/sdk/test/linked-command-detection.js +525 -0
  502. package/dist/sdk/test/linked-command-detection.js.map +1 -0
  503. package/docs/COMMANDS.md +58 -40
  504. package/docs/EXTENSIONS.md +10 -1
  505. package/docs/RELATIONSHIP_GRAPH.md +113 -0
  506. package/docs/RELEASING.md +2 -0
  507. package/docs/SDK.md +156 -9
  508. package/docs/SDK_CUSTOMIZATION_PRIMITIVES.md +79 -0
  509. package/marketplace.json +2 -2
  510. package/package.json +3 -2
  511. package/packages/pm-beads/package.json +1 -1
  512. package/packages/pm-calendar/package.json +1 -1
  513. package/packages/pm-command-kit/package.json +1 -1
  514. package/packages/pm-governance-audit/extensions/governance-audit/runtime-utils.ts +76 -16
  515. package/packages/pm-governance-audit/extensions/governance-audit/sdk.ts +8 -0
  516. package/packages/pm-governance-audit/package.json +1 -1
  517. package/packages/pm-guide-shell/package.json +1 -1
  518. package/packages/pm-kanban/package.json +1 -1
  519. package/packages/pm-lifecycle-hooks/package.json +1 -1
  520. package/packages/pm-linked-test-adapters/package.json +1 -1
  521. package/packages/pm-search-advanced/package.json +1 -1
  522. package/packages/pm-templates/package.json +1 -1
  523. package/packages/pm-todos/package.json +1 -1
  524. package/plugins/pm-claude/.claude-plugin/plugin.json +1 -1
  525. package/plugins/pm-codex/.codex-plugin/plugin.json +1 -1
  526. package/scripts/bundle-cli.mjs +66 -23
  527. package/dist/cli/commands/extension/bundled-catalog.js.map +0 -1
  528. package/dist/cli/commands/extension/doctor.js.map +0 -1
  529. package/dist/cli/commands/extension/install-sources.js.map +0 -1
  530. package/dist/cli/commands/extension/managed-state.js.map +0 -1
  531. package/dist/cli/commands/templates.js.map +0 -1
  532. package/dist/cli/commands/test/linked-command-detection.js +0 -248
  533. package/dist/cli/commands/test/linked-command-detection.js.map +0 -1
  534. package/dist/cli-bundle/chunks/append-DYQWQK2J.js +0 -11
  535. package/dist/cli-bundle/chunks/chunk-2GA3NK35.js +0 -530
  536. package/dist/cli-bundle/chunks/chunk-2GA3NK35.js.map +0 -1
  537. package/dist/cli-bundle/chunks/chunk-2QIQGYPW.js.map +0 -7
  538. package/dist/cli-bundle/chunks/chunk-4DY4KPQI.js.map +0 -7
  539. package/dist/cli-bundle/chunks/chunk-4WREH7RT.js +0 -554
  540. package/dist/cli-bundle/chunks/chunk-4WREH7RT.js.map +0 -7
  541. package/dist/cli-bundle/chunks/chunk-5CT5BW6E.js.map +0 -7
  542. package/dist/cli-bundle/chunks/chunk-5URI7TPJ.js.map +0 -7
  543. package/dist/cli-bundle/chunks/chunk-6MHSBDGD.js.map +0 -7
  544. package/dist/cli-bundle/chunks/chunk-6OTZMORS.js.map +0 -7
  545. package/dist/cli-bundle/chunks/chunk-6PKA3SJQ.js.map +0 -1
  546. package/dist/cli-bundle/chunks/chunk-6TTOHMTB.js.map +0 -1
  547. package/dist/cli-bundle/chunks/chunk-7QDSYUMF.js.map +0 -1
  548. package/dist/cli-bundle/chunks/chunk-7TQMR6LK.js.map +0 -7
  549. package/dist/cli-bundle/chunks/chunk-B5DZYTT3.js +0 -312
  550. package/dist/cli-bundle/chunks/chunk-B5DZYTT3.js.map +0 -7
  551. package/dist/cli-bundle/chunks/chunk-BPD26E3P.js.map +0 -1
  552. package/dist/cli-bundle/chunks/chunk-CDJYP7OQ.js.map +0 -1
  553. package/dist/cli-bundle/chunks/chunk-DBZVUJTK.js +0 -858
  554. package/dist/cli-bundle/chunks/chunk-DBZVUJTK.js.map +0 -1
  555. package/dist/cli-bundle/chunks/chunk-DVOLZJ3C.js.map +0 -1
  556. package/dist/cli-bundle/chunks/chunk-DYEX6NFH.js.map +0 -7
  557. package/dist/cli-bundle/chunks/chunk-F7PSHRNO.js.map +0 -1
  558. package/dist/cli-bundle/chunks/chunk-FO7GMJKG.js +0 -875
  559. package/dist/cli-bundle/chunks/chunk-FO7GMJKG.js.map +0 -7
  560. package/dist/cli-bundle/chunks/chunk-FTQLW4JD.js.map +0 -1
  561. package/dist/cli-bundle/chunks/chunk-IA3YFTXT.js.map +0 -1
  562. package/dist/cli-bundle/chunks/chunk-IGED7NH2.js.map +0 -7
  563. package/dist/cli-bundle/chunks/chunk-J4NZOP3I.js.map +0 -1
  564. package/dist/cli-bundle/chunks/chunk-JL7WZ4O2.js.map +0 -7
  565. package/dist/cli-bundle/chunks/chunk-L24HCJFC.js +0 -309
  566. package/dist/cli-bundle/chunks/chunk-L24HCJFC.js.map +0 -1
  567. package/dist/cli-bundle/chunks/chunk-LWTIRS3Y.js.map +0 -7
  568. package/dist/cli-bundle/chunks/chunk-MKL4CP2I.js.map +0 -1
  569. package/dist/cli-bundle/chunks/chunk-N3CMP4NI.js.map +0 -1
  570. package/dist/cli-bundle/chunks/chunk-PHKP3AGR.js.map +0 -7
  571. package/dist/cli-bundle/chunks/chunk-Q37E63AM.js.map +0 -7
  572. package/dist/cli-bundle/chunks/chunk-QASRVKUQ.js.map +0 -1
  573. package/dist/cli-bundle/chunks/chunk-RGXD5JJR.js.map +0 -7
  574. package/dist/cli-bundle/chunks/chunk-UB2QYSTO.js.map +0 -1
  575. package/dist/cli-bundle/chunks/chunk-UGTVWL3E.js.map +0 -1
  576. package/dist/cli-bundle/chunks/chunk-ZAYYDNB3.js.map +0 -1
  577. package/dist/cli-bundle/chunks/chunk-ZUKSZVBW.js.map +0 -7
  578. package/dist/cli-bundle/chunks/close-RI4JQQHU.js +0 -12
  579. package/dist/cli-bundle/chunks/close-many-LY2XJ52M.js +0 -21
  580. package/dist/cli-bundle/chunks/comments-35M273DN.js +0 -13
  581. package/dist/cli-bundle/chunks/copy-NPYM7M7U.js +0 -9
  582. package/dist/cli-bundle/chunks/delete-ODRWJWAM.js +0 -10
  583. package/dist/cli-bundle/chunks/docs-G7N4E2JO.js +0 -14
  584. package/dist/cli-bundle/chunks/focus-U5TRVCRS.js +0 -10
  585. package/dist/cli-bundle/chunks/history-compact-M7GHYVFN.js +0 -20
  586. package/dist/cli-bundle/chunks/history-redact-JIHZLHDP.js +0 -16
  587. package/dist/cli-bundle/chunks/history-repair-NAFCHLH7.js +0 -18
  588. package/dist/cli-bundle/chunks/learnings-SONGPSHI.js +0 -13
  589. package/dist/cli-bundle/chunks/notes-BSD53H2W.js +0 -13
  590. package/dist/cli-bundle/chunks/plan-AWLYGBNV.js +0 -24
  591. package/dist/cli-bundle/chunks/register-list-query-BUWCYHBT.js.map +0 -7
  592. package/dist/cli-bundle/chunks/register-list-query-KTZ6AGE7.js.map +0 -1
  593. package/dist/cli-bundle/chunks/register-mutation-4FMGJJF7.js.map +0 -1
  594. package/dist/cli-bundle/chunks/restore-66GLMHIX.js +0 -14
  595. package/dist/cli-bundle/chunks/update-2XGHIVQ2.js +0 -21
  596. package/dist/cli-bundle/chunks/update-many-75K57FS3.js +0 -34
  597. package/dist/cli-bundle/chunks/update-many-U42YA445.js +0 -30
  598. /package/dist/cli-bundle/chunks/{append-PVZJDM6J.js.map → append-OJQFUQ4T.js.map} +0 -0
  599. /package/dist/cli-bundle/chunks/{append-DYQWQK2J.js.map → append-UZVHUO74.js.map} +0 -0
  600. /package/dist/cli-bundle/chunks/{chunk-MQZ3PLCF.js.map → chunk-2IFFUZLC.js.map} +0 -0
  601. /package/dist/cli-bundle/chunks/{chunk-P6MYFH6K.js.map → chunk-2JKGO6QX.js.map} +0 -0
  602. /package/dist/cli-bundle/chunks/{chunk-X3GYR4P2.js.map → chunk-2NCIPMQP.js.map} +0 -0
  603. /package/dist/cli-bundle/chunks/{chunk-Q7E6OYZK.js.map → chunk-2VPAVL7O.js.map} +0 -0
  604. /package/dist/cli-bundle/chunks/{chunk-DRNJPEPQ.js.map → chunk-2WSE6PO2.js.map} +0 -0
  605. /package/dist/cli-bundle/chunks/{chunk-FDSBDNYE.js.map → chunk-34L5OT3Y.js.map} +0 -0
  606. /package/dist/cli-bundle/chunks/{chunk-TH7U4KLY.js.map → chunk-3AUGG2DP.js.map} +0 -0
  607. /package/dist/cli-bundle/chunks/{chunk-NKW36AMT.js.map → chunk-3CLU2XZO.js.map} +0 -0
  608. /package/dist/cli-bundle/chunks/{chunk-ABRCOSDC.js.map → chunk-3WM75PWC.js.map} +0 -0
  609. /package/dist/cli-bundle/chunks/{chunk-X6BVXQ52.js.map → chunk-5JLUII3O.js.map} +0 -0
  610. /package/dist/cli-bundle/chunks/{chunk-3TL7WOEF.js.map → chunk-6N2VXVTQ.js.map} +0 -0
  611. /package/dist/cli-bundle/chunks/{chunk-ERL2YFJ2.js.map → chunk-6S3YSUYG.js.map} +0 -0
  612. /package/dist/cli-bundle/chunks/{chunk-IFZA4VB5.js.map → chunk-AHVTB5I5.js.map} +0 -0
  613. /package/dist/cli-bundle/chunks/{chunk-DO5RGJXT.js.map → chunk-APRVSW2O.js.map} +0 -0
  614. /package/dist/cli-bundle/chunks/{chunk-NAQYVWSQ.js.map → chunk-B7PE22W6.js.map} +0 -0
  615. /package/dist/cli-bundle/chunks/{chunk-MD6ULIPJ.js.map → chunk-BCOFWYW4.js.map} +0 -0
  616. /package/dist/cli-bundle/chunks/{chunk-KADLSPIU.js.map → chunk-BEP7WJA2.js.map} +0 -0
  617. /package/dist/cli-bundle/chunks/{chunk-OBDLEQG5.js.map → chunk-CRVF2JDM.js.map} +0 -0
  618. /package/dist/cli-bundle/chunks/{chunk-FJZDA6MS.js.map → chunk-DM4H7FP4.js.map} +0 -0
  619. /package/dist/cli-bundle/chunks/{close-LZECOMHO.js.map → chunk-DUT67M43.js.map} +0 -0
  620. /package/dist/cli-bundle/chunks/{chunk-44LNIULG.js.map → chunk-DYH63FX7.js.map} +0 -0
  621. /package/dist/cli-bundle/chunks/{chunk-HAMLIWAF.js.map → chunk-EAB64KLB.js.map} +0 -0
  622. /package/dist/cli-bundle/chunks/{chunk-CIOZ376P.js.map → chunk-EACOH7AU.js.map} +0 -0
  623. /package/dist/cli-bundle/chunks/{chunk-QK2ZN6UL.js.map → chunk-EJEXHTY5.js.map} +0 -0
  624. /package/dist/cli-bundle/chunks/{chunk-WQUEHPZB.js.map → chunk-EYLCSJAI.js.map} +0 -0
  625. /package/dist/cli-bundle/chunks/{chunk-655STMGA.js.map → chunk-FMBNM2EN.js.map} +0 -0
  626. /package/dist/cli-bundle/chunks/{chunk-RMF3IOYQ.js.map → chunk-FNSPG7MU.js.map} +0 -0
  627. /package/dist/cli-bundle/chunks/{chunk-KB726FMH.js.map → chunk-H2Z3FYXQ.js.map} +0 -0
  628. /package/dist/cli-bundle/chunks/{chunk-BN62TO2S.js.map → chunk-IA5FKNII.js.map} +0 -0
  629. /package/dist/cli-bundle/chunks/{chunk-W6HZ55JO.js.map → chunk-IJIICKVW.js.map} +0 -0
  630. /package/dist/cli-bundle/chunks/{chunk-M7EWN4TF.js.map → chunk-JOUQZENX.js.map} +0 -0
  631. /package/dist/cli-bundle/chunks/{chunk-2FNWQXZ2.js.map → chunk-JYFOV7FC.js.map} +0 -0
  632. /package/dist/cli-bundle/chunks/{chunk-HLHSQ5CA.js.map → chunk-K2X6U4UA.js.map} +0 -0
  633. /package/dist/cli-bundle/chunks/{chunk-M6ASXYCU.js.map → chunk-KG44YSKD.js.map} +0 -0
  634. /package/dist/cli-bundle/chunks/{chunk-YVKE5ZDM.js.map → chunk-KPMKHGEA.js.map} +0 -0
  635. /package/dist/cli-bundle/chunks/{close-many-ZHI6A5TN.js.map → chunk-MIQDDLB5.js.map} +0 -0
  636. /package/dist/cli-bundle/chunks/{chunk-UJ4HJYWL.js.map → chunk-NLDFBC6J.js.map} +0 -0
  637. /package/dist/cli-bundle/chunks/{chunk-WRKB56FC.js.map → chunk-NP22ERDF.js.map} +0 -0
  638. /package/dist/cli-bundle/chunks/{chunk-YQYG2V23.js.map → chunk-NU3IMIEA.js.map} +0 -0
  639. /package/dist/cli-bundle/chunks/{chunk-3I3LRT2G.js.map → chunk-NVPZIJVR.js.map} +0 -0
  640. /package/dist/cli-bundle/chunks/{chunk-JAF4TUQ7.js.map → chunk-ODALILG3.js.map} +0 -0
  641. /package/dist/cli-bundle/chunks/{chunk-QZ7LT3XE.js.map → chunk-ONREO4ST.js.map} +0 -0
  642. /package/dist/cli-bundle/chunks/{chunk-3CMRNVTO.js.map → chunk-P74IZQ7A.js.map} +0 -0
  643. /package/dist/cli-bundle/chunks/{chunk-ZTRBSABY.js.map → chunk-PGY23YTC.js.map} +0 -0
  644. /package/dist/cli-bundle/chunks/{chunk-H2QBAKLQ.js.map → chunk-PMOSXADO.js.map} +0 -0
  645. /package/dist/cli-bundle/chunks/{close-RI4JQQHU.js.map → chunk-PNTULOGU.js.map} +0 -0
  646. /package/dist/cli-bundle/chunks/{chunk-GSEADLRH.js.map → chunk-Q7A43DS4.js.map} +0 -0
  647. /package/dist/cli-bundle/chunks/{chunk-QSPARJWF.js.map → chunk-QEHQ2GVG.js.map} +0 -0
  648. /package/dist/cli-bundle/chunks/{chunk-VJTAEIPC.js.map → chunk-QIPLHNP7.js.map} +0 -0
  649. /package/dist/cli-bundle/chunks/{chunk-K7WNZ7KD.js.map → chunk-QKJGPFAF.js.map} +0 -0
  650. /package/dist/cli-bundle/chunks/{chunk-ZNP64HOW.js.map → chunk-RLCHOOHC.js.map} +0 -0
  651. /package/dist/cli-bundle/chunks/{chunk-IAPHYPRL.js.map → chunk-RMLWNPH2.js.map} +0 -0
  652. /package/dist/cli-bundle/chunks/{chunk-UVBA6F5A.js.map → chunk-RRM5JYJC.js.map} +0 -0
  653. /package/dist/cli-bundle/chunks/{chunk-6IIVBC56.js.map → chunk-S6MQESFR.js.map} +0 -0
  654. /package/dist/cli-bundle/chunks/{chunk-QABYZYLU.js.map → chunk-SDQLJWBO.js.map} +0 -0
  655. /package/dist/cli-bundle/chunks/{chunk-ANSJHDOZ.js.map → chunk-SELVXEZK.js.map} +0 -0
  656. /package/dist/cli-bundle/chunks/{chunk-WD6MSYMP.js.map → chunk-TKOVBP6C.js.map} +0 -0
  657. /package/dist/cli-bundle/chunks/{chunk-TZ2PSLLT.js.map → chunk-TQSZFPIS.js.map} +0 -0
  658. /package/dist/cli-bundle/chunks/{chunk-WPAWZW7S.js.map → chunk-TSMHDIW4.js.map} +0 -0
  659. /package/dist/cli-bundle/chunks/{chunk-TZRTLMWM.js.map → chunk-UFXU44HV.js.map} +0 -0
  660. /package/dist/cli-bundle/chunks/{chunk-TWLDBKQU.js.map → chunk-ULTHRDBP.js.map} +0 -0
  661. /package/dist/cli-bundle/chunks/{chunk-RA5AIAMP.js.map → chunk-UOHFKBUV.js.map} +0 -0
  662. /package/dist/cli-bundle/chunks/{chunk-4RXTMU57.js.map → chunk-UY2ACEMF.js.map} +0 -0
  663. /package/dist/cli-bundle/chunks/{chunk-FIGXOCEQ.js.map → chunk-WUJQK4YV.js.map} +0 -0
  664. /package/dist/cli-bundle/chunks/{chunk-T4BRCB6Z.js.map → chunk-XSJUSQAE.js.map} +0 -0
  665. /package/dist/cli-bundle/chunks/{chunk-UKUIIBDW.js.map → chunk-YAQEBNY7.js.map} +0 -0
  666. /package/dist/cli-bundle/chunks/{chunk-E2WC6S4P.js.map → chunk-YBLZ2VE7.js.map} +0 -0
  667. /package/dist/cli-bundle/chunks/{chunk-Y72FV7RM.js.map → chunk-YCQET3OT.js.map} +0 -0
  668. /package/dist/cli-bundle/chunks/{close-many-LY2XJ52M.js.map → chunk-YUSOJDWM.js.map} +0 -0
  669. /package/dist/cli-bundle/chunks/{chunk-FS6UM5WH.js.map → chunk-ZI6VQWQ7.js.map} +0 -0
  670. /package/dist/cli-bundle/chunks/{comments-AJWRTFZ5.js.map → close-CWODTPKG.js.map} +0 -0
  671. /package/dist/cli-bundle/chunks/{comments-35M273DN.js.map → close-W6LI2PKM.js.map} +0 -0
  672. /package/dist/cli-bundle/chunks/{copy-GMXYKBXT.js.map → close-many-CQFUY7MR.js.map} +0 -0
  673. /package/dist/cli-bundle/chunks/{copy-NPYM7M7U.js.map → close-many-DLDDVSH5.js.map} +0 -0
  674. /package/dist/cli-bundle/chunks/{create-F53OBTOU.js.map → comments-E3TNTK24.js.map} +0 -0
  675. /package/dist/cli-bundle/chunks/{create-HU4Q7RJX.js.map → comments-WGLHR7LB.js.map} +0 -0
  676. /package/dist/cli-bundle/chunks/{delete-GSDZLTRC.js.map → copy-LXVDXH7B.js.map} +0 -0
  677. /package/dist/cli-bundle/chunks/{delete-ODRWJWAM.js.map → copy-Q5ZJPUKR.js.map} +0 -0
  678. /package/dist/cli-bundle/chunks/{deps-U57I7EAK.js.map → create-J5U6ND4K.js.map} +0 -0
  679. /package/dist/cli-bundle/chunks/{deps-WWB7P2GT.js.map → create-LBVCYMEG.js.map} +0 -0
  680. /package/dist/cli-bundle/chunks/{docs-MQIY2ZQS.js.map → delete-PMJBNY5G.js.map} +0 -0
  681. /package/dist/cli-bundle/chunks/{docs-G7N4E2JO.js.map → delete-WNHB3LYW.js.map} +0 -0
  682. /package/dist/cli-bundle/chunks/{files-TTY6N2AV.js.map → deps-JZ7TMVCB.js.map} +0 -0
  683. /package/dist/cli-bundle/chunks/{files-TBZD7V6H.js.map → deps-OA26IS25.js.map} +0 -0
  684. /package/dist/cli-bundle/chunks/{focus-XT6YVFFX.js.map → docs-FEWB2DQB.js.map} +0 -0
  685. /package/dist/cli-bundle/chunks/{focus-U5TRVCRS.js.map → docs-HA2AIDBB.js.map} +0 -0
  686. /package/dist/cli-bundle/chunks/{history-compact-M7GHYVFN.js.map → files-6YFBJRBG.js.map} +0 -0
  687. /package/dist/cli-bundle/chunks/{history-compact-KKVUWB4V.js.map → files-EFIWKFTM.js.map} +0 -0
  688. /package/dist/cli-bundle/chunks/{history-redact-3DVZOILD.js.map → focus-4QJR7FOQ.js.map} +0 -0
  689. /package/dist/cli-bundle/chunks/{history-redact-JIHZLHDP.js.map → focus-POIW5LTJ.js.map} +0 -0
  690. /package/dist/cli-bundle/chunks/{history-repair-ITDUMK4N.js.map → history-compact-CKRPDTJN.js.map} +0 -0
  691. /package/dist/cli-bundle/chunks/{history-repair-NAFCHLH7.js.map → history-compact-FWG2C7FZ.js.map} +0 -0
  692. /package/dist/cli-bundle/chunks/{learnings-SONGPSHI.js.map → history-redact-BB3TGDAE.js.map} +0 -0
  693. /package/dist/cli-bundle/chunks/{learnings-FGO3NSFW.js.map → history-redact-D225BHOZ.js.map} +0 -0
  694. /package/dist/cli-bundle/chunks/{notes-BSD53H2W.js.map → history-repair-6GEAM7HE.js.map} +0 -0
  695. /package/dist/cli-bundle/chunks/{notes-NQ6RXVW6.js.map → history-repair-RQXKQHL4.js.map} +0 -0
  696. /package/dist/cli-bundle/chunks/{plan-2NNEO46V.js.map → learnings-JLNLCOL5.js.map} +0 -0
  697. /package/dist/cli-bundle/chunks/{plan-AWLYGBNV.js.map → learnings-RSNLSWDC.js.map} +0 -0
  698. /package/dist/cli-bundle/chunks/{profile-U725ATWZ.js.map → notes-2WDH6DXU.js.map} +0 -0
  699. /package/dist/cli-bundle/chunks/{profile-AAQUU6T3.js.map → notes-GB47XXQQ.js.map} +0 -0
  700. /package/dist/cli-bundle/chunks/{restore-66GLMHIX.js.map → plan-DOVI2VMW.js.map} +0 -0
  701. /package/dist/cli-bundle/chunks/{restore-B3BAB6X2.js.map → plan-VDCJ2PCN.js.map} +0 -0
  702. /package/dist/cli-bundle/chunks/{schema-U7IM2DXV.js.map → profile-FAU7NZML.js.map} +0 -0
  703. /package/dist/cli-bundle/chunks/{schema-LZNUYHDB.js.map → profile-IVXQEL3D.js.map} +0 -0
  704. /package/dist/cli-bundle/chunks/{update-Q3UCILNR.js.map → restore-25DLT2Q2.js.map} +0 -0
  705. /package/dist/cli-bundle/chunks/{update-2XGHIVQ2.js.map → restore-YPMVUUEX.js.map} +0 -0
  706. /package/dist/cli-bundle/chunks/{update-many-75K57FS3.js.map → schema-FNU3WE4O.js.map} +0 -0
  707. /package/dist/cli-bundle/chunks/{update-many-U42YA445.js.map → schema-NBCM3RAD.js.map} +0 -0
  708. /package/dist/{cli/commands → sdk}/extension/managed-state.d.ts +0 -0
@@ -0,0 +1,2175 @@
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]="362d2563-06b2-58f4-8c37-62e0de1eb95b")}catch(e){}}();
8
+ import fs from "node:fs/promises";
9
+ import { realpathSync } from "node:fs";
10
+ import { execFile } from "node:child_process";
11
+ import path from "node:path";
12
+ import { promisify } from "node:util";
13
+ import { buildRemediationCommands } from "../../core/diagnostics/remediation.js";
14
+ import { getActiveExtensionRegistrations } from "../../core/extensions/index.js";
15
+ import { pathExists } from "../../core/fs/fs-utils.js";
16
+ import { scanHistoryDrift } from "../../core/history/drift-scan.js";
17
+ import { normalizeStatusInput } from "../../core/item/status.js";
18
+ import { resolveItemTypeRegistry } from "../../core/item/type-registry.js";
19
+ import { resolveRuntimeStatusRegistry, } from "../../core/schema/runtime-schema.js";
20
+ import { DEFAULT_VALIDATE_CLOSURE_LIKE_METADATA_FIELD_PATTERNS, DEFAULT_VALIDATE_STALE_BLOCKER_REASON_PATTERNS, EXIT_CODE, PM_DIRNAME, } from "../../core/shared/constants.js";
21
+ import { PmCliError } from "../../core/shared/errors.js";
22
+ import { toNonEmptyStringOrUndefined } from "../../core/shared/primitives.js";
23
+ import { nowIso } from "../../core/shared/time.js";
24
+ import { CURRENT_ITEM_FORMAT_VERSION, effectiveItemFormatVersion, scanItemFormatVersions, } from "../../core/item/item-format-version.js";
25
+ import { listAllItemMetadataWithBody } from "../../core/store/item-store.js";
26
+ import { getSettingsPath, 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 { buildMissingLinkedPathRows, summarizeMissingLinkedPathRows, } from "../../core/validate/missing-link-owners.js";
34
+ import { collectDanglingDependencyReferences } from "../dependencies.js";
35
+ import { scanHistoryAuthorAttribution } from "../author-attribution.js";
36
+ import { createRelationshipKindRegistry, } from "../relationships.js";
37
+ import { runDocs } from "../docs.js";
38
+ import { runFiles } from "../files.js";
39
+ import { extractReferencedPmItemIdsFromCommand } from "../test/linked-command-detection.js";
40
+ const FILE_SCAN_DIRECTORIES = ["src", "tests", "docs"];
41
+ const FILE_SCAN_ROOT_FILES = [
42
+ "README.md",
43
+ "PRD.md",
44
+ "CHANGELOG.md",
45
+ "AGENTS.md",
46
+ "CONTRIBUTING.md",
47
+ "SECURITY.md",
48
+ "CODE_OF_CONDUCT.md",
49
+ "LICENSE",
50
+ ];
51
+ const DIRECTORY_IGNORE_SET = new Set([
52
+ "node_modules",
53
+ ".git",
54
+ ".cursor",
55
+ ".agents",
56
+ "dist",
57
+ "coverage",
58
+ ]);
59
+ const RESOLUTION_FIELD_KEYS = [
60
+ "resolution",
61
+ "expected_result",
62
+ "actual_result",
63
+ ];
64
+ const VALIDATE_FILE_SCAN_MODES = [
65
+ "default",
66
+ "tracked-all",
67
+ "tracked-all-strict",
68
+ ];
69
+ const VALIDATE_METADATA_PROFILE_VALUES = ["core", "strict", "custom"];
70
+ const VALIDATE_DEPENDENCY_CYCLE_SEVERITY_VALUES = [
71
+ "off",
72
+ "warn",
73
+ "error",
74
+ ];
75
+ const LIFECYCLE_PATTERN_FIELD_KEYS = [
76
+ "blocked_reason",
77
+ "resolution",
78
+ "actual_result",
79
+ ];
80
+ const CORE_METADATA_REQUIRED_FIELDS = [
81
+ "author",
82
+ "acceptance_criteria",
83
+ "estimated_minutes",
84
+ "close_reason",
85
+ ];
86
+ const STRICT_METADATA_REQUIRED_FIELDS = [
87
+ ...CORE_METADATA_REQUIRED_FIELDS,
88
+ "reviewer",
89
+ "risk",
90
+ "confidence",
91
+ "sprint",
92
+ "release",
93
+ ];
94
+ // Keep this deduplicated so future list edits cannot double-count metadata diagnostics.
95
+ const SUPPORTED_METADATA_REQUIRED_FIELDS = Array.from(new Set(STRICT_METADATA_REQUIRED_FIELDS));
96
+ const METADATA_REQUIRED_FIELD_ALIASES = {
97
+ author: "author",
98
+ acceptance_criteria: "acceptance_criteria",
99
+ "acceptance-criteria": "acceptance_criteria",
100
+ estimated_minutes: "estimated_minutes",
101
+ "estimated-minutes": "estimated_minutes",
102
+ estimate: "estimated_minutes",
103
+ close_reason: "close_reason",
104
+ "close-reason": "close_reason",
105
+ reviewer: "reviewer",
106
+ risk: "risk",
107
+ confidence: "confidence",
108
+ sprint: "sprint",
109
+ release: "release",
110
+ };
111
+ const METADATA_WARNING_TOKEN_BY_FIELD = {
112
+ author: "validate_metadata_missing_author",
113
+ acceptance_criteria: "validate_metadata_missing_acceptance_criteria",
114
+ estimated_minutes: "validate_metadata_missing_estimate",
115
+ close_reason: "validate_metadata_missing_close_reason",
116
+ reviewer: "validate_metadata_missing_reviewer",
117
+ risk: "validate_metadata_missing_risk",
118
+ confidence: "validate_metadata_missing_confidence",
119
+ sprint: "validate_metadata_missing_sprint",
120
+ release: "validate_metadata_missing_release",
121
+ };
122
+ const METADATA_COUNT_KEY_BY_FIELD = {
123
+ author: "missing_author",
124
+ acceptance_criteria: "missing_acceptance_criteria",
125
+ estimated_minutes: "missing_estimated_minutes",
126
+ close_reason: "closed_missing_close_reason",
127
+ reviewer: "missing_reviewer",
128
+ risk: "missing_risk",
129
+ confidence: "missing_confidence",
130
+ sprint: "missing_sprint",
131
+ release: "missing_release",
132
+ };
133
+ const METADATA_ITEM_IDS_KEY_BY_FIELD = {
134
+ author: "missing_author_item_ids",
135
+ acceptance_criteria: "missing_acceptance_criteria_item_ids",
136
+ estimated_minutes: "missing_estimated_minutes_item_ids",
137
+ close_reason: "closed_missing_close_reason_item_ids",
138
+ reviewer: "missing_reviewer_item_ids",
139
+ risk: "missing_risk_item_ids",
140
+ confidence: "missing_confidence_item_ids",
141
+ sprint: "missing_sprint_item_ids",
142
+ release: "missing_release_item_ids",
143
+ };
144
+ const METADATA_TRUNCATED_KEY_BY_FIELD = {
145
+ author: "missing_author_truncated",
146
+ acceptance_criteria: "missing_acceptance_criteria_truncated",
147
+ estimated_minutes: "missing_estimated_minutes_truncated",
148
+ close_reason: "closed_missing_close_reason_truncated",
149
+ reviewer: "missing_reviewer_truncated",
150
+ risk: "missing_risk_truncated",
151
+ confidence: "missing_confidence_truncated",
152
+ sprint: "missing_sprint_truncated",
153
+ release: "missing_release_truncated",
154
+ };
155
+ const GIT_LS_FILES_MAX_BUFFER = 32 * 1024 * 1024;
156
+ const GIT_LS_FILES_TIMEOUT_MS = 10_000;
157
+ const FILE_LIST_SUMMARY_LIMIT = 40;
158
+ const DIAGNOSTIC_LIST_SUMMARY_LIMIT = 5;
159
+ // Conservative pre-stat guard for common filesystem limits; over-limit links are unreadable, not prune-safe.
160
+ const LINKED_ARTIFACT_MAX_PATH_LENGTH = 4096;
161
+ const LINKED_ARTIFACT_MAX_SEGMENT_LENGTH = 255;
162
+ const execFileAsync = promisify(execFile);
163
+ function normalizeRelativePath(value) {
164
+ return value
165
+ .replaceAll("\\", "/")
166
+ .replace(/^\.\/+/, "")
167
+ .replace(/^\/+/, "");
168
+ }
169
+ function normalizeRelativeDirectoryPath(value) {
170
+ const normalized = normalizeRelativePath(value);
171
+ return normalized.replace(/\/+$/, "");
172
+ }
173
+ function toMeaningfulString(value) {
174
+ const normalized = toNonEmptyStringOrUndefined(value);
175
+ if (!normalized) {
176
+ return undefined;
177
+ }
178
+ const lowered = normalized.toLowerCase();
179
+ if (lowered === "none" ||
180
+ lowered === "null" ||
181
+ lowered === "n/a" ||
182
+ lowered === "na") {
183
+ return undefined;
184
+ }
185
+ return normalized;
186
+ }
187
+ /* c8 ignore start -- runtime-status alias normalization is covered by status-registry integration tests */
188
+ function normalizeStatusForRegistry(status, statusRegistry) {
189
+ return normalizeStatusInput(status, statusRegistry) ?? status;
190
+ }
191
+ /* c8 ignore stop */
192
+ function isTerminalStatus(status, statusRegistry) {
193
+ return statusRegistry.terminal_statuses.has(normalizeStatusForRegistry(status, statusRegistry));
194
+ }
195
+ /* c8 ignore start -- lifecycle pattern normalization/default-vs-settings matrix is covered by lifecycle integration tests */
196
+ function normalizeLifecyclePatternList(values) {
197
+ return [
198
+ ...new Set((values ?? [])
199
+ .map((value) => value.trim().toLowerCase())
200
+ .filter((value) => value.length > 0)),
201
+ ].sort((left, right) => left.localeCompare(right));
202
+ }
203
+ function areSortedStringListsEqual(left, right) {
204
+ if (left.length !== right.length) {
205
+ return false;
206
+ }
207
+ return left.every((value, index) => value === right[index]);
208
+ }
209
+ function resolveLifecyclePatternPolicy(settings) {
210
+ const defaultStalePatterns = normalizeLifecyclePatternList(DEFAULT_VALIDATE_STALE_BLOCKER_REASON_PATTERNS);
211
+ const defaultClosureLikePatterns = {
212
+ blocked_reason: normalizeLifecyclePatternList(DEFAULT_VALIDATE_CLOSURE_LIKE_METADATA_FIELD_PATTERNS.blocked_reason),
213
+ resolution: normalizeLifecyclePatternList(DEFAULT_VALIDATE_CLOSURE_LIKE_METADATA_FIELD_PATTERNS.resolution),
214
+ actual_result: normalizeLifecyclePatternList(DEFAULT_VALIDATE_CLOSURE_LIKE_METADATA_FIELD_PATTERNS.actual_result),
215
+ };
216
+ const staleBlockerReasonPatterns = normalizeLifecyclePatternList(settings.validation.lifecycle_stale_blocker_reason_patterns);
217
+ const closureLikePatterns = {
218
+ blocked_reason: normalizeLifecyclePatternList(settings.validation.lifecycle_closure_like_blocked_reason_patterns),
219
+ resolution: normalizeLifecyclePatternList(settings.validation.lifecycle_closure_like_resolution_patterns),
220
+ actual_result: normalizeLifecyclePatternList(settings.validation.lifecycle_closure_like_actual_result_patterns),
221
+ };
222
+ return {
223
+ stale_blocker_reason_patterns: staleBlockerReasonPatterns,
224
+ stale_blocker_reason_pattern_source: areSortedStringListsEqual(staleBlockerReasonPatterns, defaultStalePatterns)
225
+ ? "default"
226
+ : "settings",
227
+ closure_like_metadata_field_patterns: closureLikePatterns,
228
+ closure_like_metadata_field_pattern_sources: {
229
+ blocked_reason: areSortedStringListsEqual(closureLikePatterns.blocked_reason, defaultClosureLikePatterns.blocked_reason)
230
+ ? "default"
231
+ : "settings",
232
+ resolution: areSortedStringListsEqual(closureLikePatterns.resolution, defaultClosureLikePatterns.resolution)
233
+ ? "default"
234
+ : "settings",
235
+ actual_result: areSortedStringListsEqual(closureLikePatterns.actual_result, defaultClosureLikePatterns.actual_result)
236
+ ? "default"
237
+ : "settings",
238
+ },
239
+ };
240
+ }
241
+ /* c8 ignore stop */
242
+ function resolveValidateMetadataProfile(value) {
243
+ const normalized = value?.trim().toLowerCase();
244
+ if (!normalized || normalized.length === 0) {
245
+ return "core";
246
+ }
247
+ if (VALIDATE_METADATA_PROFILE_VALUES.includes(normalized)) {
248
+ return normalized;
249
+ }
250
+ throw new PmCliError(`Unknown --metadata-profile value "${value}". Supported values: ${VALIDATE_METADATA_PROFILE_VALUES.join(", ")}.`, EXIT_CODE.USAGE);
251
+ }
252
+ function resolveDependencyCycleSeverity(value) {
253
+ const normalized = value?.trim().toLowerCase();
254
+ if (!normalized || normalized.length === 0) {
255
+ return "warn";
256
+ }
257
+ if (VALIDATE_DEPENDENCY_CYCLE_SEVERITY_VALUES.includes(normalized)) {
258
+ return normalized;
259
+ }
260
+ throw new PmCliError(`Unknown --dependency-cycle-severity value "${value}". Supported values: ${VALIDATE_DEPENDENCY_CYCLE_SEVERITY_VALUES.join(", ")}.`, EXIT_CODE.USAGE);
261
+ }
262
+ function resolveParentCycleSeverity(value) {
263
+ const normalized = value?.trim().toLowerCase();
264
+ if (!normalized || normalized.length === 0) {
265
+ return "warn";
266
+ }
267
+ if (VALIDATE_DEPENDENCY_CYCLE_SEVERITY_VALUES.includes(normalized)) {
268
+ return normalized;
269
+ }
270
+ throw new PmCliError(`Unknown --parent-cycle-severity value "${value}". Supported values: ${VALIDATE_DEPENDENCY_CYCLE_SEVERITY_VALUES.join(", ")}.`, EXIT_CODE.USAGE);
271
+ }
272
+ /* c8 ignore start -- metadata required-field alias normalization is covered by metadata-policy integration tests */
273
+ function normalizeMetadataRequiredFieldsFromSettings(values) {
274
+ const normalized = [
275
+ ...new Set((values ?? []).map((value) => value.trim().toLowerCase().replaceAll("-", "_"))),
276
+ ];
277
+ return normalized
278
+ .map((value) => METADATA_REQUIRED_FIELD_ALIASES[value])
279
+ .filter((value) => value !== undefined)
280
+ .sort((left, right) => left.localeCompare(right));
281
+ }
282
+ /* c8 ignore stop */
283
+ function resolveValidateMetadataPolicy(profile, profileSource, configuredCustomFields) {
284
+ const normalizedCustomFields = normalizeMetadataRequiredFieldsFromSettings(configuredCustomFields);
285
+ if (profile === "core") {
286
+ return {
287
+ profile,
288
+ profile_source: profileSource,
289
+ required_fields: [...CORE_METADATA_REQUIRED_FIELDS],
290
+ configured_custom_fields: normalizedCustomFields,
291
+ fallback_to_core: false,
292
+ warnings: [],
293
+ };
294
+ }
295
+ if (profile === "strict") {
296
+ return {
297
+ profile,
298
+ profile_source: profileSource,
299
+ required_fields: [...STRICT_METADATA_REQUIRED_FIELDS],
300
+ configured_custom_fields: normalizedCustomFields,
301
+ fallback_to_core: false,
302
+ warnings: [],
303
+ };
304
+ }
305
+ if (normalizedCustomFields.length > 0) {
306
+ return {
307
+ profile,
308
+ profile_source: profileSource,
309
+ required_fields: normalizedCustomFields,
310
+ configured_custom_fields: normalizedCustomFields,
311
+ fallback_to_core: false,
312
+ warnings: [],
313
+ };
314
+ }
315
+ return {
316
+ profile,
317
+ profile_source: profileSource,
318
+ required_fields: [...CORE_METADATA_REQUIRED_FIELDS],
319
+ configured_custom_fields: normalizedCustomFields,
320
+ fallback_to_core: true,
321
+ warnings: ["validate_metadata_custom_profile_missing_required_fields:0"],
322
+ };
323
+ }
324
+ /** 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. */
325
+ const TERMINAL_EXEMPT_PLANNING_FIELDS = new Set(["acceptance_criteria", "estimated_minutes"]);
326
+ function isMetadataFieldMissing(item, field, statusRegistry, enforcePlanningFieldsOnTerminal) {
327
+ // GH-276: skip planning-field gaps on retired (closed/canceled) items unless
328
+ // the resolved profile explicitly demands strict historical coverage.
329
+ if (!enforcePlanningFieldsOnTerminal &&
330
+ TERMINAL_EXEMPT_PLANNING_FIELDS.has(field) &&
331
+ isTerminalStatus(item.status, statusRegistry)) {
332
+ return false;
333
+ }
334
+ if (field === "author") {
335
+ return !toNonEmptyStringOrUndefined(item.author);
336
+ }
337
+ if (field === "acceptance_criteria") {
338
+ return !toNonEmptyStringOrUndefined(item.acceptance_criteria);
339
+ }
340
+ if (field === "estimated_minutes") {
341
+ return !Number.isFinite(item.estimated_minutes);
342
+ }
343
+ if (field === "close_reason") {
344
+ return (normalizeStatusForRegistry(item.status, statusRegistry) ===
345
+ statusRegistry.close_status &&
346
+ !toNonEmptyStringOrUndefined(item.close_reason));
347
+ }
348
+ if (field === "reviewer") {
349
+ return !toNonEmptyStringOrUndefined(item.reviewer);
350
+ }
351
+ if (field === "risk") {
352
+ return !toNonEmptyStringOrUndefined(item.risk);
353
+ }
354
+ if (field === "confidence") {
355
+ if (typeof item.confidence === "number") {
356
+ return !Number.isFinite(item.confidence);
357
+ }
358
+ return !toNonEmptyStringOrUndefined(item.confidence);
359
+ }
360
+ if (field === "sprint") {
361
+ return !toNonEmptyStringOrUndefined(item.sprint);
362
+ }
363
+ return !toNonEmptyStringOrUndefined(item.release);
364
+ }
365
+ function resolveFileScanMode(scanMode) {
366
+ if (scanMode === undefined) {
367
+ return "default";
368
+ }
369
+ const normalized = scanMode.trim().toLowerCase();
370
+ if (normalized.length === 0) {
371
+ return "default";
372
+ }
373
+ if (normalized === "default") {
374
+ return "default";
375
+ }
376
+ if (normalized === "tracked-all" || normalized === "tracked_all") {
377
+ return "tracked-all";
378
+ }
379
+ if (normalized === "tracked-all-strict" ||
380
+ normalized === "tracked_all_strict") {
381
+ return "tracked-all-strict";
382
+ }
383
+ throw new PmCliError(`Unknown --scan-mode value "${scanMode}". Supported values: ${VALIDATE_FILE_SCAN_MODES.join(", ")}.`, EXIT_CODE.USAGE);
384
+ }
385
+ function resolveWorkspaceRoot(pmRoot) {
386
+ const resolvedPmRoot = path.resolve(pmRoot);
387
+ const normalizedPmRoot = resolvedPmRoot.replaceAll("\\", "/");
388
+ if (normalizedPmRoot.endsWith("/.agents/pm")) {
389
+ return path.dirname(path.dirname(resolvedPmRoot));
390
+ }
391
+ const resolvedCwd = path.resolve(process.cwd());
392
+ const canonicalPmRoot = realpathForWorkspaceRoot(resolvedPmRoot);
393
+ const canonicalCwd = realpathForWorkspaceRoot(resolvedCwd);
394
+ const relativeFromPmRoot = path.relative(canonicalPmRoot, canonicalCwd);
395
+ const cwdInsidePmRoot = relativeFromPmRoot.length === 0 ||
396
+ (!relativeFromPmRoot.startsWith("..") &&
397
+ !path.isAbsolute(relativeFromPmRoot));
398
+ if (cwdInsidePmRoot) {
399
+ return resolvedPmRoot;
400
+ }
401
+ /* c8 ignore next 2 -- non-standard PM root layouts are integration-only edge cases. */
402
+ return resolvedCwd;
403
+ }
404
+ function realpathForWorkspaceRoot(inputPath) {
405
+ try {
406
+ return realpathSync.native(inputPath);
407
+ }
408
+ catch {
409
+ return path.resolve(inputPath);
410
+ }
411
+ }
412
+ /* c8 ignore start -- recursive file-walk dirent/permission edge cases are covered by filesystem integration suites */
413
+ async function listFilesRecursive(basePath, relativePath, output) {
414
+ const targetDirectory = relativePath.length > 0 ? path.join(basePath, relativePath) : basePath;
415
+ let entries;
416
+ try {
417
+ entries = await fs.readdir(targetDirectory, { withFileTypes: true });
418
+ }
419
+ catch (error) {
420
+ if (typeof error === "object" &&
421
+ error !== null &&
422
+ "code" in error &&
423
+ error.code === "ENOENT") {
424
+ return;
425
+ }
426
+ throw error;
427
+ }
428
+ for (const entry of entries) {
429
+ if (entry.name.startsWith(".")) {
430
+ continue;
431
+ }
432
+ const childRelative = relativePath.length > 0
433
+ ? path.join(relativePath, entry.name)
434
+ : entry.name;
435
+ if (entry.isDirectory()) {
436
+ if (DIRECTORY_IGNORE_SET.has(entry.name)) {
437
+ continue;
438
+ }
439
+ await listFilesRecursive(basePath, childRelative, output);
440
+ continue;
441
+ }
442
+ /* c8 ignore next 3 -- non-file dirent variants (symlink/socket) are filesystem-specific. */
443
+ if (!entry.isFile()) {
444
+ continue;
445
+ }
446
+ output.push(normalizeRelativePath(childRelative));
447
+ }
448
+ }
449
+ /* c8 ignore stop */
450
+ async function collectDefaultProjectFileCandidates(workspaceRoot) {
451
+ const discovered = [];
452
+ for (const directory of FILE_SCAN_DIRECTORIES) {
453
+ await listFilesRecursive(workspaceRoot, directory, discovered);
454
+ }
455
+ for (const candidate of FILE_SCAN_ROOT_FILES) {
456
+ const absolute = path.join(workspaceRoot, candidate);
457
+ try {
458
+ const stats = await fs.stat(absolute);
459
+ /* c8 ignore start -- root-candidate presence depends on fixture workspace composition. */
460
+ if (stats.isFile()) {
461
+ discovered.push(normalizeRelativePath(candidate));
462
+ }
463
+ /* c8 ignore stop */
464
+ }
465
+ catch {
466
+ // Ignore root-file candidates that are not present in this workspace.
467
+ }
468
+ }
469
+ return [...new Set(discovered)].sort((left, right) => left.localeCompare(right));
470
+ }
471
+ async function collectTrackedGitFileCandidates(workspaceRoot) {
472
+ try {
473
+ const { stdout } = await execFileAsync("git", ["ls-files", "-z"], {
474
+ cwd: workspaceRoot,
475
+ encoding: "utf8",
476
+ maxBuffer: GIT_LS_FILES_MAX_BUFFER,
477
+ windowsHide: true,
478
+ timeout: GIT_LS_FILES_TIMEOUT_MS,
479
+ });
480
+ const discovered = stdout
481
+ .split("\0")
482
+ .map((value) => normalizeRelativePath(value))
483
+ .filter((value) => value.length > 0);
484
+ return [...new Set(discovered)].sort((left, right) => left.localeCompare(right));
485
+ }
486
+ catch {
487
+ /* c8 ignore start -- fallback path exercised only when git metadata is unavailable. */
488
+ return null;
489
+ /* c8 ignore stop */
490
+ }
491
+ }
492
+ /* c8 ignore start -- PM-internal prefix derivation permutations are covered by file-scan integration tests */
493
+ function resolvePmInternalCandidatePrefixes(pmRoot, workspaceRoot) {
494
+ const prefixes = new Set();
495
+ const configuredDefault = normalizeRelativeDirectoryPath(PM_DIRNAME);
496
+ if (configuredDefault.length > 0) {
497
+ prefixes.add(configuredDefault);
498
+ }
499
+ const relativePmRoot = normalizeRelativeDirectoryPath(path.relative(workspaceRoot, pmRoot));
500
+ if (relativePmRoot.length > 0 && !relativePmRoot.startsWith("..")) {
501
+ prefixes.add(relativePmRoot);
502
+ }
503
+ return [...prefixes].sort((left, right) => left.localeCompare(right));
504
+ }
505
+ /* c8 ignore stop */
506
+ function hasPathPrefix(candidate, prefixes) {
507
+ for (const prefix of prefixes) {
508
+ if (candidate === prefix || candidate.startsWith(`${prefix}/`)) {
509
+ return true;
510
+ }
511
+ }
512
+ return false;
513
+ }
514
+ async function collectProjectFileCandidates(workspaceRoot, scanMode) {
515
+ if (scanMode === "tracked-all" || scanMode === "tracked-all-strict") {
516
+ const trackedCandidates = await collectTrackedGitFileCandidates(workspaceRoot);
517
+ /* c8 ignore start -- tracked-git availability fallback is covered by git/non-git integration fixtures */
518
+ if (trackedCandidates) {
519
+ return {
520
+ requestedMode: scanMode,
521
+ appliedMode: scanMode,
522
+ source: "tracked-git",
523
+ candidateFiles: trackedCandidates,
524
+ candidateTotal: trackedCandidates.length,
525
+ candidateScanned: trackedCandidates.length,
526
+ };
527
+ }
528
+ /* c8 ignore stop */
529
+ /* c8 ignore start -- deterministic fallback retained for non-git workspaces. */
530
+ const fallbackCandidates = await collectDefaultProjectFileCandidates(workspaceRoot);
531
+ return {
532
+ requestedMode: scanMode,
533
+ appliedMode: "default",
534
+ source: "tracked-all-fallback-default",
535
+ candidateFiles: fallbackCandidates,
536
+ candidateTotal: fallbackCandidates.length,
537
+ candidateScanned: fallbackCandidates.length,
538
+ };
539
+ /* c8 ignore stop */
540
+ }
541
+ const defaultCandidates = await collectDefaultProjectFileCandidates(workspaceRoot);
542
+ return {
543
+ requestedMode: scanMode,
544
+ appliedMode: "default",
545
+ source: "default-curated",
546
+ candidateFiles: defaultCandidates,
547
+ candidateTotal: defaultCandidates.length,
548
+ candidateScanned: defaultCandidates.length,
549
+ };
550
+ }
551
+ function summarizeList(values, limit = DIAGNOSTIC_LIST_SUMMARY_LIMIT) {
552
+ /* c8 ignore start -- truncation behavior only surfaces with very large synthetic datasets. */
553
+ if (values.length <= limit) {
554
+ return { values, truncated: false };
555
+ }
556
+ return {
557
+ values: values.slice(0, limit),
558
+ truncated: true,
559
+ };
560
+ /* c8 ignore stop */
561
+ }
562
+ function summarizeFileList(values, verboseFileLists) {
563
+ if (verboseFileLists) {
564
+ return {
565
+ values,
566
+ truncated: false,
567
+ total: values.length,
568
+ };
569
+ }
570
+ const summary = summarizeList(values, FILE_LIST_SUMMARY_LIMIT);
571
+ return {
572
+ values: summary.values,
573
+ truncated: summary.truncated,
574
+ total: values.length,
575
+ };
576
+ }
577
+ const RESOLUTION_REMEDIATION_FLAG_BY_FIELD = {
578
+ resolution: "--resolution",
579
+ expected_result: "--expected-result",
580
+ actual_result: "--actual-result",
581
+ };
582
+ const RESOLUTION_REMEDIATION_PLACEHOLDER_BY_FIELD = {
583
+ resolution: "Describe how this item was resolved",
584
+ expected_result: "Describe the expected result",
585
+ actual_result: "Describe the actual result",
586
+ };
587
+ function buildResolutionRemediationCommand(row) {
588
+ const fieldArguments = row.missing_fields
589
+ .map((field) => `${RESOLUTION_REMEDIATION_FLAG_BY_FIELD[field]} "${RESOLUTION_REMEDIATION_PLACEHOLDER_BY_FIELD[field]}"`)
590
+ .join(" ");
591
+ return `pm update ${row.id} ${fieldArguments} --message "Backfill resolution metadata"`;
592
+ }
593
+ /** 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. */
594
+ /* c8 ignore start -- fix-hint projection/truncation combinations are covered by validate output integration tests */
595
+ function attachValidateFixHints(check, checkWarnings) {
596
+ const existingResolutionHints = check.details?.missing_resolution_remediation_hints;
597
+ const aliasedResolution = Array.isArray(existingResolutionHints) &&
598
+ existingResolutionHints.length > 0;
599
+ const fixHints = aliasedResolution
600
+ ? existingResolutionHints.filter((hint) => typeof hint === "string")
601
+ : buildRemediationCommands(checkWarnings);
602
+ if (fixHints.length === 0) {
603
+ return;
604
+ }
605
+ // The resolution check truncates its per-row hint list for low-token output;
606
+ // carry that marker onto fix_hints so an agent knows the list is partial and
607
+ // there are more items to repair beyond the ones shown.
608
+ const truncated = aliasedResolution &&
609
+ check.details?.missing_resolution_remediation_hints_truncated === true;
610
+ check.details = {
611
+ ...check.details,
612
+ fix_hints: fixHints,
613
+ ...(truncated ? { fix_hints_truncated: true } : {}),
614
+ };
615
+ }
616
+ /* c8 ignore stop */
617
+ function resolveRequestedChecks(options) {
618
+ const requested = new Set();
619
+ if (options.checkMetadata) {
620
+ requested.add("metadata");
621
+ }
622
+ if (options.checkResolution) {
623
+ requested.add("resolution");
624
+ }
625
+ if (options.checkLifecycle || options.checkStaleBlockers) {
626
+ requested.add("lifecycle");
627
+ }
628
+ if (options.checkFiles) {
629
+ requested.add("files");
630
+ }
631
+ if (options.checkHistoryDrift) {
632
+ requested.add("history_drift");
633
+ }
634
+ if (options.checkCommandReferences) {
635
+ requested.add("command_references");
636
+ }
637
+ if (requested.size === 0) {
638
+ // Remediation flags without explicit --check-* flags scope the run to the
639
+ // checks that can produce fixes: --auto-fix plans metadata/resolution
640
+ // backfills and gated lifecycle fixes; --prune-missing needs the files
641
+ // scan. With neither, the historical run-everything default applies.
642
+ if (options.autoFix) {
643
+ requested.add("metadata");
644
+ requested.add("resolution");
645
+ requested.add("lifecycle");
646
+ }
647
+ if (options.pruneMissing) {
648
+ requested.add("files");
649
+ }
650
+ if (requested.size === 0) {
651
+ requested.add("metadata");
652
+ requested.add("resolution");
653
+ requested.add("lifecycle");
654
+ requested.add("files");
655
+ requested.add("command_references");
656
+ requested.add("history_drift");
657
+ requested.add("format_version");
658
+ }
659
+ return requested;
660
+ }
661
+ // Explicit check flags are respected as-is, except --prune-missing always
662
+ // needs the files scan it acts on.
663
+ if (options.pruneMissing) {
664
+ requested.add("files");
665
+ }
666
+ return requested;
667
+ }
668
+ const DUPLICATE_ISSUE_CODE_WARNING_TOKEN = "validate_metadata_duplicate_issue_codes";
669
+ /** 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. */
670
+ function summarizeDuplicateIssueCodes(duplicates, verboseDiagnostics) {
671
+ if (duplicates.length === 0) {
672
+ return { rows: [], truncated: false, warnings: [] };
673
+ }
674
+ const limit = verboseDiagnostics
675
+ ? duplicates.length
676
+ : DIAGNOSTIC_LIST_SUMMARY_LIMIT;
677
+ const shown = duplicates.slice(0, limit);
678
+ const rows = shown.map((duplicate) => ({
679
+ code: duplicate.code,
680
+ count: duplicate.count,
681
+ ids: duplicate.ids,
682
+ titles: duplicate.titles,
683
+ remediation_hint: `Items ${duplicate.ids.join(", ")} share issue code "${duplicate.code}"; rename or merge so each logical code maps to one item.`,
684
+ }));
685
+ return {
686
+ rows,
687
+ truncated: shown.length < duplicates.length,
688
+ warnings: [`${DUPLICATE_ISSUE_CODE_WARNING_TOKEN}:${duplicates.length}`],
689
+ };
690
+ }
691
+ function initializeMissingMetadataByField() {
692
+ return Object.fromEntries(SUPPORTED_METADATA_REQUIRED_FIELDS.map((field) => [field, []]));
693
+ }
694
+ function collectMissingMetadataByField(items, statusRegistry, enforcePlanningFieldsOnTerminal) {
695
+ const missingByField = initializeMissingMetadataByField();
696
+ for (const item of items) {
697
+ for (const field of SUPPORTED_METADATA_REQUIRED_FIELDS) {
698
+ if (isMetadataFieldMissing(item, field, statusRegistry, enforcePlanningFieldsOnTerminal)) {
699
+ missingByField[field].push(item.id);
700
+ }
701
+ }
702
+ }
703
+ return missingByField;
704
+ }
705
+ function buildMetadataWarningTokens(metadataPolicy, missingByField, duplicateWarnings) {
706
+ const warningTokens = [...metadataPolicy.warnings];
707
+ for (const field of metadataPolicy.required_fields) {
708
+ const missingItems = missingByField[field];
709
+ if (missingItems.length > 0) {
710
+ warningTokens.push(`${METADATA_WARNING_TOKEN_BY_FIELD[field]}:${missingItems.length}`);
711
+ }
712
+ }
713
+ warningTokens.push(...duplicateWarnings);
714
+ return warningTokens;
715
+ }
716
+ function buildMetadataCounts(metadataPolicy, missingByField) {
717
+ const counts = {};
718
+ for (const field of metadataPolicy.required_fields) {
719
+ const missing = missingByField[field];
720
+ const value = missing ? missing.length : 0;
721
+ const countKey = METADATA_COUNT_KEY_BY_FIELD[field];
722
+ if (value > 0 && countKey) {
723
+ counts[countKey] = value;
724
+ }
725
+ }
726
+ return counts;
727
+ }
728
+ function buildMissingFieldOccurrences(metadataPolicy, missingByField, itemsById) {
729
+ const occurrences = [];
730
+ for (const field of metadataPolicy.required_fields) {
731
+ const missing = missingByField[field];
732
+ if (!missing) {
733
+ continue;
734
+ }
735
+ for (const itemId of missing) {
736
+ const itemType = itemsById.get(itemId)?.type;
737
+ const normalizedItemType = typeof itemType === "string" && itemType.length > 0
738
+ ? itemType
739
+ : "Unknown";
740
+ occurrences.push({ item_type: normalizedItemType, field });
741
+ }
742
+ }
743
+ return occurrences;
744
+ }
745
+ /* v8 ignore start -- metadata policy keys are validated before this summarizer; fallback branches are defensive */
746
+ function attachMissingMetadataItemIds(details, metadataPolicy, missingByField, verboseDiagnostics) {
747
+ for (const field of metadataPolicy.required_fields) {
748
+ const missing = missingByField[field];
749
+ if (!missing || missing.length === 0) {
750
+ continue;
751
+ }
752
+ const idsKey = METADATA_ITEM_IDS_KEY_BY_FIELD[field];
753
+ const truncatedKey = METADATA_TRUNCATED_KEY_BY_FIELD[field];
754
+ if (!idsKey || !truncatedKey) {
755
+ continue;
756
+ }
757
+ const summarized = summarizeList(missing, verboseDiagnostics ? missing.length : DIAGNOSTIC_LIST_SUMMARY_LIMIT);
758
+ details[idsKey] = summarized.values;
759
+ details[truncatedKey] = summarized.truncated;
760
+ }
761
+ }
762
+ /* v8 ignore stop */
763
+ function buildCloseReasonBackfillRows(metadataPolicy, missingByField, itemsById) {
764
+ if (!metadataPolicy.required_fields.includes("close_reason")) {
765
+ return [];
766
+ }
767
+ const missing = missingByField.close_reason;
768
+ if (!missing) {
769
+ return [];
770
+ }
771
+ return missing.map((itemId) => ({
772
+ id: itemId,
773
+ resolution: toNonEmptyStringOrUndefined(itemsById.get(itemId)?.resolution),
774
+ }));
775
+ }
776
+ function buildEstimateBackfillRows(metadataPolicy, missingByField, itemsById) {
777
+ if (!metadataPolicy.required_fields.includes("estimated_minutes")) {
778
+ return [];
779
+ }
780
+ const missing = missingByField.estimated_minutes;
781
+ if (!missing) {
782
+ return [];
783
+ }
784
+ return missing.map((itemId) => ({
785
+ id: itemId,
786
+ type: toNonEmptyStringOrUndefined(itemsById.get(itemId)?.type),
787
+ }));
788
+ }
789
+ /* c8 ignore start -- metadata diagnostics/backfill planning permutations are covered by validate integration suites */
790
+ function buildMetadataCheck(items, metadataPolicy, statusRegistry, verboseDiagnostics) {
791
+ const itemsById = new Map(items.map((item) => [item.id, item]));
792
+ // GH-276: only the `strict` profile enforces planning fields on terminal
793
+ // (closed/canceled) historical items; core/minimal/custom profiles treat a
794
+ // retired item's missing estimate or acceptance criteria as resolved.
795
+ const enforcePlanningFieldsOnTerminal = metadataPolicy.profile === "strict";
796
+ const missingByField = collectMissingMetadataByField(items, statusRegistry, enforcePlanningFieldsOnTerminal);
797
+ // Duplicate logical issue-code detection (GH-235): advisory warning when two
798
+ // or more items share a leading title issue code (e.g. `ISSUE-004`).
799
+ const duplicateIssueCodes = findDuplicateIssueCodes(items);
800
+ const duplicateIssueCodeSummary = summarizeDuplicateIssueCodes(duplicateIssueCodes, verboseDiagnostics);
801
+ const warningTokens = buildMetadataWarningTokens(metadataPolicy, missingByField, duplicateIssueCodeSummary.warnings);
802
+ // Zero-suppress counts to reduce agent token cost (telemetry pm-tylj).
803
+ // Only emit counts for the ACTIVE required fields of the resolved profile so a
804
+ // looser profile (e.g. core) never reports missing reviewer/risk/sprint/etc.
805
+ // Defensive guards (Gemini high #1, PR #78 follow-up): a future settings
806
+ // shape could include an unsupported field in required_fields — fall back
807
+ // to 0 instead of throwing TypeError, and skip writing when the count-key
808
+ // mapping is undefined.
809
+ const counts = buildMetadataCounts(metadataPolicy, missingByField);
810
+ // Per-item-type grouping of missing required-field counts (pm-pmyq /
811
+ // GH-172): counts only — never row dumps — and only for the ACTIVE required
812
+ // fields, so the grouping mirrors `counts` at type granularity (e.g.
813
+ // `{ Task: { close_reason: 3 } }`). Zero-suppressed at both levels.
814
+ const missingFieldOccurrences = buildMissingFieldOccurrences(metadataPolicy, missingByField, itemsById);
815
+ const missingByType = buildMissingByTypeCounts(missingFieldOccurrences);
816
+ const details = {
817
+ checked_items: items.length,
818
+ metadata_profile: metadataPolicy.profile,
819
+ metadata_profile_source: metadataPolicy.profile_source,
820
+ metadata_profile_fallback_to_core: metadataPolicy.fallback_to_core,
821
+ required_fields: [...metadataPolicy.required_fields],
822
+ supported_required_fields: [...SUPPORTED_METADATA_REQUIRED_FIELDS],
823
+ counts,
824
+ missing_by_type: missingByType,
825
+ duplicate_issue_codes_count: duplicateIssueCodes.length,
826
+ duplicate_issue_codes: duplicateIssueCodeSummary.rows,
827
+ duplicate_issue_codes_truncated: duplicateIssueCodeSummary.truncated,
828
+ };
829
+ if (metadataPolicy.configured_custom_fields.length > 0) {
830
+ details.configured_custom_required_fields = [
831
+ ...metadataPolicy.configured_custom_fields,
832
+ ];
833
+ }
834
+ // Only emit per-field item_ids/truncated keys for the ACTIVE required fields of
835
+ // the resolved profile (and only when there are missing items). This stops a
836
+ // looser profile (e.g. core) from emitting the identical full ID array for
837
+ // reviewer/risk/confidence/sprint/release that it does not even require
838
+ // (pm-edge #2 — ~150 redundant lines per validate run on minimal/core).
839
+ // Defensive guard (Gemini high #2, PR #78 follow-up): same optional-chain
840
+ // safety as the counts loop above — never throw if a future settings shape
841
+ // includes an unsupported field.
842
+ attachMissingMetadataItemIds(details, metadataPolicy, missingByField, verboseDiagnostics);
843
+ // Auto-fix planning input (pm-c3sz): closed items flagged for a missing
844
+ // close_reason whose resolution can serve as the derivable source value.
845
+ // Only collected when close_reason is an active required field, so fixes
846
+ // always trace back to an actual finding of this run.
847
+ const closeReasonBackfillRows = buildCloseReasonBackfillRows(metadataPolicy, missingByField, itemsById);
848
+ // Estimate auto-fix planning input (GH-212): items flagged for a missing
849
+ // estimated_minutes whose type drives the config-driven default backfill.
850
+ // Only collected when estimated_minutes is an active required field, so fixes
851
+ // always trace back to an actual finding of this run.
852
+ const estimateBackfillRows = buildEstimateBackfillRows(metadataPolicy, missingByField, itemsById);
853
+ return {
854
+ check: {
855
+ name: "metadata",
856
+ status: warningTokens.length === 0 ? "ok" : "warn",
857
+ details,
858
+ },
859
+ warnings: warningTokens,
860
+ closeReasonBackfillRows,
861
+ estimateBackfillRows,
862
+ };
863
+ }
864
+ /* c8 ignore stop */
865
+ function buildResolutionCheck(items, statusRegistry, verboseDiagnostics) {
866
+ const terminalDoneStatuses = new Set(statusRegistry.terminal_done_statuses);
867
+ terminalDoneStatuses.add(statusRegistry.close_status);
868
+ const closedItems = items.filter((item) => terminalDoneStatuses.has(normalizeStatusForRegistry(item.status, statusRegistry)));
869
+ const missingResolutionRows = [];
870
+ const resolutionBackfillRows = [];
871
+ for (const item of closedItems) {
872
+ const missingFields = RESOLUTION_FIELD_KEYS.filter((field) => !toNonEmptyStringOrUndefined(item[field]));
873
+ if (missingFields.length === 0) {
874
+ continue;
875
+ }
876
+ missingResolutionRows.push({
877
+ id: item.id,
878
+ missing_fields: missingFields,
879
+ });
880
+ // Auto-fix planning input (pm-c3sz): the planner backfills only the
881
+ // `resolution` field, deriving from the item's close_reason when present.
882
+ resolutionBackfillRows.push({
883
+ id: item.id,
884
+ missing_fields: missingFields,
885
+ close_reason: toNonEmptyStringOrUndefined(item.close_reason),
886
+ });
887
+ }
888
+ const warnings = missingResolutionRows.length > 0
889
+ ? [`validate_resolution_missing_fields:${missingResolutionRows.length}`]
890
+ : [];
891
+ const diagnosticLimit = verboseDiagnostics
892
+ ? Number.POSITIVE_INFINITY
893
+ : DIAGNOSTIC_LIST_SUMMARY_LIMIT;
894
+ const summarizedRows = summarizeList(missingResolutionRows.map((row) => `${row.id}:${row.missing_fields.join(",")}`), diagnosticLimit);
895
+ const remediationHints = missingResolutionRows.map((row) => buildResolutionRemediationCommand(row));
896
+ const summarizedHints = summarizeList(remediationHints, diagnosticLimit);
897
+ return {
898
+ check: {
899
+ name: "resolution",
900
+ status: warnings.length === 0 ? "ok" : "warn",
901
+ details: {
902
+ checked_closed_items: closedItems.length,
903
+ missing_resolution_items: missingResolutionRows.length,
904
+ missing_resolution_rows: summarizedRows.values,
905
+ missing_resolution_rows_truncated: summarizedRows.truncated,
906
+ missing_resolution_remediation_hints: summarizedHints.values,
907
+ missing_resolution_remediation_hints_truncated: summarizedHints.truncated,
908
+ },
909
+ },
910
+ warnings,
911
+ resolutionBackfillRows,
912
+ };
913
+ }
914
+ /* c8 ignore start -- lifecycle dependency-graph cycle analysis is covered by lifecycle integration fixtures */
915
+ function escapeRegExp(value) {
916
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
917
+ }
918
+ function buildLifecycleDependencyGraph(activeItems, idPrefix = "pm", relationshipRegistry = createRelationshipKindRegistry()) {
919
+ const activeItemIds = new Set(activeItems.map((item) => item.id));
920
+ const graph = new Map();
921
+ const sortedItems = [...activeItems].sort((left, right) => left.id.localeCompare(right.id));
922
+ for (const item of sortedItems) {
923
+ const edges = new Set();
924
+ const blockedBy = toMeaningfulString(item.blocked_by);
925
+ if (blockedBy && activeItemIds.has(blockedBy)) {
926
+ edges.add(blockedBy);
927
+ }
928
+ for (const dependency of item.dependencies ?? []) {
929
+ const dependencyKind = toMeaningfulString(dependency.kind);
930
+ if (!dependencyKind)
931
+ continue;
932
+ const relationshipDefinition = relationshipRegistry.resolve(dependencyKind);
933
+ if (!relationshipDefinition?.ordering)
934
+ continue;
935
+ const dependencyId = toMeaningfulString(dependency.id);
936
+ if (!dependencyId || !activeItemIds.has(dependencyId)) {
937
+ continue;
938
+ }
939
+ edges.add(dependencyId);
940
+ }
941
+ const definitionOfReady = toMeaningfulString(item.definition_of_ready);
942
+ if (definitionOfReady) {
943
+ for (const referencedId of extractItemIds(definitionOfReady, idPrefix)) {
944
+ if (activeItemIds.has(referencedId)) {
945
+ edges.add(referencedId);
946
+ }
947
+ }
948
+ }
949
+ graph.set(item.id, [...edges].sort((left, right) => left.localeCompare(right)));
950
+ }
951
+ return graph;
952
+ }
953
+ /* c8 ignore stop */
954
+ function buildDependencyReferencesCheck(items, verboseDiagnostics, statusRegistry) {
955
+ const classified = collectDanglingDependencyReferences(items, statusRegistry
956
+ ? (status) => isTerminalStatus(status, statusRegistry)
957
+ : undefined);
958
+ const activeRows = classified.active.map((row) => `${row.holder_id}:${row.target_id}:${row.kind}`);
959
+ const legacyRows = classified.legacy_terminal.map((row) => `${row.holder_id}:${row.target_id}:${row.kind}:${row.holder_status}`);
960
+ const diagnosticLimit = verboseDiagnostics
961
+ ? Number.POSITIVE_INFINITY
962
+ : DIAGNOSTIC_LIST_SUMMARY_LIMIT;
963
+ const summarizedRows = summarizeList(activeRows, diagnosticLimit);
964
+ const summarizedLegacyRows = summarizeList(legacyRows, diagnosticLimit);
965
+ const hints = summarizeList(classified.active.map((row) => {
966
+ if (row.source === "parent") {
967
+ return `pm update ${row.holder_id} --unset parent`;
968
+ }
969
+ return row.source === "blocked_by"
970
+ ? `pm update ${row.holder_id} --unset blocked_by`
971
+ : `pm update ${row.holder_id} --replace-deps '<correct dependency edges>'`;
972
+ }), diagnosticLimit);
973
+ return {
974
+ check: {
975
+ name: "dependency_references",
976
+ status: activeRows.length === 0 ? "ok" : "warn",
977
+ details: {
978
+ checked_items: items.length,
979
+ dangling_reference_count: activeRows.length + legacyRows.length,
980
+ active_dangling_reference_count: activeRows.length,
981
+ dangling_reference_rows: summarizedRows.values,
982
+ dangling_reference_rows_truncated: summarizedRows.truncated,
983
+ legacy_terminal_dangling_reference_count: legacyRows.length,
984
+ legacy_closed_dangling_reference_count: classified.legacy_terminal.filter((row) => row.holder_status === "closed").length,
985
+ legacy_terminal_dangling_reference_rows: summarizedLegacyRows.values,
986
+ legacy_terminal_dangling_reference_rows_truncated: summarizedLegacyRows.truncated,
987
+ no_active_blocker_sentinel_count: classified.no_active_blocker_sentinels.length,
988
+ remediation_hints: hints.values,
989
+ remediation_hints_truncated: hints.truncated,
990
+ },
991
+ },
992
+ warnings: activeRows.length > 0
993
+ ? [`validate_dangling_dependency_references:${activeRows.length}`]
994
+ : [],
995
+ };
996
+ }
997
+ function extractItemIds(value, idPrefix = "pm") {
998
+ const normalizedPrefix = (idPrefix.trim().toLowerCase() || "pm").replace(/-+$/g, "");
999
+ const pattern = new RegExp(`(?:^|[^a-z0-9-])(${escapeRegExp(normalizedPrefix)}-[a-z0-9][a-z0-9-]*)`, "gi");
1000
+ return [
1001
+ ...new Set([...value.matchAll(pattern)].map((match) => match[1].toLowerCase())),
1002
+ ].sort((left, right) => left.localeCompare(right));
1003
+ }
1004
+ /* c8 ignore start -- Tarjan SCC traversal branch matrix is covered by lifecycle cycle integration fixtures */
1005
+ function findLifecycleDependencyCycleComponents(graph) {
1006
+ let nextIndex = 0;
1007
+ const indexById = new Map();
1008
+ const lowLinkById = new Map();
1009
+ const stack = [];
1010
+ const inStack = new Set();
1011
+ const components = [];
1012
+ const visit = (id) => {
1013
+ indexById.set(id, nextIndex);
1014
+ lowLinkById.set(id, nextIndex);
1015
+ nextIndex += 1;
1016
+ stack.push(id);
1017
+ inStack.add(id);
1018
+ for (const dependencyId of graph.get(id) ?? []) {
1019
+ if (!indexById.has(dependencyId)) {
1020
+ visit(dependencyId);
1021
+ lowLinkById.set(id, Math.min(lowLinkById.get(id), lowLinkById.get(dependencyId)));
1022
+ }
1023
+ else if (inStack.has(dependencyId)) {
1024
+ lowLinkById.set(id, Math.min(lowLinkById.get(id), indexById.get(dependencyId)));
1025
+ }
1026
+ }
1027
+ if (lowLinkById.get(id) !== indexById.get(id)) {
1028
+ return;
1029
+ }
1030
+ const component = [];
1031
+ while (stack.length > 0) {
1032
+ const member = stack.pop();
1033
+ inStack.delete(member);
1034
+ component.push(member);
1035
+ if (member === id) {
1036
+ break;
1037
+ }
1038
+ }
1039
+ component.sort((left, right) => left.localeCompare(right));
1040
+ components.push(component);
1041
+ };
1042
+ const sortedNodeIds = [...graph.keys()].sort((left, right) => left.localeCompare(right));
1043
+ for (const id of sortedNodeIds) {
1044
+ if (!indexById.has(id)) {
1045
+ visit(id);
1046
+ }
1047
+ }
1048
+ const cycleComponents = components.filter((component) => {
1049
+ if (component.length > 1) {
1050
+ return true;
1051
+ }
1052
+ const selfId = component[0];
1053
+ return (graph.get(selfId) ?? []).includes(selfId);
1054
+ });
1055
+ return cycleComponents.sort((left, right) => left[0].localeCompare(right[0]) ||
1056
+ left.length - right.length ||
1057
+ left.join(",").localeCompare(right.join(",")));
1058
+ }
1059
+ /* c8 ignore stop */
1060
+ /* c8 ignore start -- cycle sample-path fallback branches are covered by lifecycle graph integration tests */
1061
+ function resolveLifecycleDependencyCycleSamplePath(component, graph) {
1062
+ const start = component[0];
1063
+ if (component.length === 1) {
1064
+ return [start, start];
1065
+ }
1066
+ const componentSet = new Set(component);
1067
+ const path = [start];
1068
+ const visited = new Set([start]);
1069
+ const search = (current) => {
1070
+ const neighbors = (graph.get(current) ?? []).filter((candidate) => componentSet.has(candidate));
1071
+ for (const next of neighbors) {
1072
+ if (next === start && path.length > 1) {
1073
+ path.push(start);
1074
+ return true;
1075
+ }
1076
+ if (visited.has(next)) {
1077
+ continue;
1078
+ }
1079
+ visited.add(next);
1080
+ path.push(next);
1081
+ if (search(next)) {
1082
+ return true;
1083
+ }
1084
+ path.pop();
1085
+ visited.delete(next);
1086
+ }
1087
+ return false;
1088
+ };
1089
+ if (search(start)) {
1090
+ return [...path];
1091
+ }
1092
+ return [...component, start];
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
+ // Parent (composition) cycle detection (pm-8vul / GH-280). The dependency-cycle
1107
+ // path above only walks blocked_by/definition_of_ready edges across ACTIVE items;
1108
+ // it never traverses item.parent, so a parent cycle (A.parent=B, B.parent=A, or
1109
+ // any longer ring) goes undetected while it silently breaks `pm list --tree`,
1110
+ // orphan detection, and progress rollups. We reuse the generic Tarjan SCC helper
1111
+ // on a child->[parent] adjacency map. Unlike dependency cycles we scan ALL items
1112
+ // (not just active ones) because a parent cycle among closed items is still
1113
+ // structural corruption of the hierarchy.
1114
+ function buildLifecycleParentGraph(items) {
1115
+ // PR #279 made parent matching case-insensitive (e.g. `parent: PM-FK49`
1116
+ // resolves to `id: pm-fk49`). Resolve parent references to their canonical
1117
+ // item id the same way so a casing mismatch can never silently drop a cycle
1118
+ // edge and hide a parent cycle (false negative).
1119
+ const canonicalIdByLowercase = new Map(items.map((item) => [item.id.toLowerCase(), item.id]));
1120
+ const graph = new Map();
1121
+ const sortedItems = [...items].sort((left, right) => left.id.localeCompare(right.id));
1122
+ for (const item of sortedItems) {
1123
+ const edges = [];
1124
+ const parentId = toMeaningfulString(item.parent);
1125
+ const canonicalParentId = parentId
1126
+ ? canonicalIdByLowercase.get(parentId.toLowerCase())
1127
+ : undefined;
1128
+ if (canonicalParentId) {
1129
+ edges.push(canonicalParentId);
1130
+ }
1131
+ graph.set(item.id, edges);
1132
+ }
1133
+ return graph;
1134
+ }
1135
+ function detectLifecycleParentCycles(items) {
1136
+ const graph = buildLifecycleParentGraph(items);
1137
+ const cycleComponents = findLifecycleDependencyCycleComponents(graph);
1138
+ const cycleItemIds = [...new Set(cycleComponents.flat())].sort((left, right) => left.localeCompare(right));
1139
+ const cycleSamplePaths = cycleComponents.map((component) => resolveLifecycleDependencyCycleSamplePath(component, graph).join("->"));
1140
+ return {
1141
+ cycle_count: cycleComponents.length,
1142
+ cycle_item_ids: cycleItemIds,
1143
+ cycle_sample_paths: cycleSamplePaths,
1144
+ };
1145
+ }
1146
+ function classifyOrphanedPath(pathValue) {
1147
+ if (pathValue.startsWith("docs/")) {
1148
+ return "docs_unowned";
1149
+ }
1150
+ if (pathValue.startsWith("tests/")) {
1151
+ return "tests_unowned";
1152
+ }
1153
+ if (pathValue.startsWith("src/")) {
1154
+ return "source_unowned";
1155
+ }
1156
+ if (pathValue.endsWith(".md")) {
1157
+ return "docs_unowned";
1158
+ }
1159
+ return "unlinked_existing";
1160
+ }
1161
+ function directoryOf(relativePath) {
1162
+ const slash = relativePath.lastIndexOf("/");
1163
+ return slash === -1 ? "" : relativePath.slice(0, slash);
1164
+ }
1165
+ function sharedDirectoryPrefixLength(left, right) {
1166
+ const leftParts = directoryOf(left).split("/").filter(Boolean);
1167
+ const rightParts = directoryOf(right).split("/").filter(Boolean);
1168
+ let count = 0;
1169
+ while (count < leftParts.length &&
1170
+ count < rightParts.length &&
1171
+ leftParts[count] === rightParts[count]) {
1172
+ count += 1;
1173
+ }
1174
+ return count;
1175
+ }
1176
+ function scoreOrphanOwnerCandidate(pathValue, linkedPath) {
1177
+ const linkedDir = directoryOf(linkedPath);
1178
+ const orphanDir = directoryOf(pathValue);
1179
+ const directoryPrefix = linkedPath.endsWith("/")
1180
+ ? linkedPath
1181
+ : `${linkedPath}/`;
1182
+ const isDirectoryPrefix = pathValue.startsWith(directoryPrefix);
1183
+ const sameDirectory = linkedDir.length > 0 && linkedDir === orphanDir;
1184
+ const sharedPrefixLength = sharedDirectoryPrefixLength(pathValue, linkedPath);
1185
+ if (!isDirectoryPrefix && !sameDirectory && sharedPrefixLength === 0) {
1186
+ return null;
1187
+ }
1188
+ if (isDirectoryPrefix) {
1189
+ return { score: linkedPath.length + 1000, confidence: "path_prefix" };
1190
+ }
1191
+ return sameDirectory
1192
+ ? { score: sharedPrefixLength + 500, confidence: "same_directory" }
1193
+ : { score: sharedPrefixLength, confidence: "shared_directory" };
1194
+ }
1195
+ function shouldReplaceOrphanOwnerCandidate(best, item, score) {
1196
+ return (best === undefined ||
1197
+ score > best.score ||
1198
+ (score === best.score && item.id.localeCompare(best.item.id) < 0));
1199
+ }
1200
+ function findOrphanOwnerCandidate(pathValue, classification, items) {
1201
+ const linkKind = classification === "docs_unowned" ? "docs" : "files";
1202
+ let best;
1203
+ for (const item of items) {
1204
+ const links = linkKind === "docs" ? (item.docs ?? []) : (item.files ?? []);
1205
+ for (const link of links) {
1206
+ if (link.scope !== "project") {
1207
+ continue;
1208
+ }
1209
+ const linkedPath = normalizeRelativePath(link.path);
1210
+ if (linkedPath.length === 0 || linkedPath === pathValue) {
1211
+ continue;
1212
+ }
1213
+ const scored = scoreOrphanOwnerCandidate(pathValue, linkedPath);
1214
+ if (scored === null) {
1215
+ continue;
1216
+ }
1217
+ if (shouldReplaceOrphanOwnerCandidate(best, item, scored.score)) {
1218
+ best = {
1219
+ item,
1220
+ score: scored.score,
1221
+ confidence: scored.confidence,
1222
+ };
1223
+ }
1224
+ }
1225
+ }
1226
+ if (!best) {
1227
+ return null;
1228
+ }
1229
+ return {
1230
+ id: best.item.id,
1231
+ type: best.item.type,
1232
+ title: best.item.title,
1233
+ status: best.item.status,
1234
+ confidence: best.confidence,
1235
+ };
1236
+ }
1237
+ function buildOrphanedPathRows(orphanedFiles, items) {
1238
+ return orphanedFiles.map((pathValue) => {
1239
+ const classification = classifyOrphanedPath(pathValue);
1240
+ const linkCommand = classification === "docs_unowned" ? "docs" : "files";
1241
+ const ownerCandidate = findOrphanOwnerCandidate(pathValue, classification, items);
1242
+ const target = ownerCandidate?.id ?? "<id>";
1243
+ return {
1244
+ path: pathValue,
1245
+ classification,
1246
+ owner_candidate: ownerCandidate,
1247
+ remediation_hint: `pm ${linkCommand} ${target} --add path=${pathValue},scope=project,note="<why this artifact belongs to the item>"`,
1248
+ };
1249
+ });
1250
+ }
1251
+ function summarizeOrphanedPathRows(rows) {
1252
+ return rows.map((row) => `${row.path}:${row.classification} owner_candidate=${row.owner_candidate?.id ?? "unowned"} hint=${JSON.stringify(row.remediation_hint)}`);
1253
+ }
1254
+ function closureLikeFieldsForItem(item, lifecyclePatternPolicy) {
1255
+ return Object.entries(lifecyclePatternPolicy.closure_like_metadata_field_patterns)
1256
+ .filter(([field, patterns]) => {
1257
+ const value = toMeaningfulString(item[field]);
1258
+ return value
1259
+ ? patterns.some((pattern) => value.toLowerCase().includes(pattern))
1260
+ : false;
1261
+ })
1262
+ .map(([field]) => field)
1263
+ .sort((left, right) => left.localeCompare(right));
1264
+ }
1265
+ function buildTerminalParentFixRow(item, parent, itemsById, canonicalIdByLowercase, statusRegistry) {
1266
+ const grandparentId = toMeaningfulString(parent.parent);
1267
+ const grandparent = grandparentId
1268
+ ? itemsById.get(canonicalIdByLowercase.get(grandparentId.toLowerCase()) ??
1269
+ grandparentId)
1270
+ : undefined;
1271
+ return {
1272
+ id: item.id,
1273
+ parent_id: parent.id,
1274
+ grandparent_id: grandparent?.id,
1275
+ grandparent_active: grandparent !== undefined &&
1276
+ !isTerminalStatus(grandparent.status, statusRegistry),
1277
+ };
1278
+ }
1279
+ /* v8 ignore start -- stale-blocker reason variants are covered through command-level diagnostics; helper branch counters are defensive */
1280
+ function staleBlockerReasonsForItem(item, blockedStatuses, statusRegistry, lifecyclePatternPolicy) {
1281
+ const blockedBy = toMeaningfulString(item.blocked_by);
1282
+ const blockedReason = toMeaningfulString(item.blocked_reason);
1283
+ const blockedReasonNormalized = blockedReason?.toLowerCase();
1284
+ const normalizedStatus = normalizeStatusForRegistry(item.status, statusRegistry);
1285
+ const reasons = [];
1286
+ if (!blockedStatuses.has(normalizedStatus)) {
1287
+ if (blockedBy) {
1288
+ reasons.push("non_blocked_status_has_blocked_by");
1289
+ }
1290
+ if (blockedReason) {
1291
+ reasons.push("non_blocked_status_has_blocked_reason");
1292
+ }
1293
+ return reasons;
1294
+ }
1295
+ if (!blockedBy && !blockedReason) {
1296
+ /* v8 ignore start -- blocked-status creation paths seed either blocked_by or blocked_reason before lifecycle validation */
1297
+ reasons.push("blocked_status_missing_blocker_context");
1298
+ /* v8 ignore stop */
1299
+ }
1300
+ if (blockedReasonNormalized?.includes("no active blocker")) {
1301
+ reasons.push("blocked_status_reason_reports_no_active_blocker");
1302
+ }
1303
+ if (blockedReasonNormalized &&
1304
+ lifecyclePatternPolicy.stale_blocker_reason_patterns.some((pattern) => blockedReasonNormalized.includes(pattern))) {
1305
+ reasons.push("blocked_status_reason_matches_stale_pattern");
1306
+ }
1307
+ return reasons;
1308
+ }
1309
+ /* v8 ignore stop */
1310
+ function collectLifecycleScanRows(items, includeStaleBlockers, statusRegistry, lifecyclePatternPolicy) {
1311
+ const itemsById = new Map(items.map((item) => [item.id, item]));
1312
+ const canonicalIdByLowercase = new Map(items.map((item) => [item.id.toLowerCase(), item.id]));
1313
+ /* v8 ignore start -- runtime status registry normally supplies blocked statuses; fallback preserves legacy settings safety */
1314
+ const blockedStatuses = statusRegistry.blocked_statuses.size > 0
1315
+ ? statusRegistry.blocked_statuses
1316
+ : new Set(["blocked"]);
1317
+ /* v8 ignore stop */
1318
+ const rows = {
1319
+ activeItems: items.filter((item) => !isTerminalStatus(item.status, statusRegistry)),
1320
+ closureLikeRows: [],
1321
+ terminalParentRows: [],
1322
+ terminalParentFixRows: [],
1323
+ staleBlockerRows: [],
1324
+ };
1325
+ for (const item of rows.activeItems) {
1326
+ const closureLikeFields = closureLikeFieldsForItem(item, lifecyclePatternPolicy);
1327
+ if (closureLikeFields.length > 0) {
1328
+ rows.closureLikeRows.push({ id: item.id, fields: closureLikeFields });
1329
+ }
1330
+ const parentId = toMeaningfulString(item.parent);
1331
+ const parent = parentId
1332
+ ? itemsById.get(canonicalIdByLowercase.get(parentId.toLowerCase()) ?? parentId)
1333
+ : undefined;
1334
+ if (parent && isTerminalStatus(parent.status, statusRegistry)) {
1335
+ rows.terminalParentRows.push({
1336
+ id: item.id,
1337
+ parent_id: parent.id,
1338
+ parent_status: parent.status,
1339
+ });
1340
+ rows.terminalParentFixRows.push(buildTerminalParentFixRow(item, parent, itemsById, canonicalIdByLowercase, statusRegistry));
1341
+ }
1342
+ /* v8 ignore start -- stale-blocker reason presence is covered by command diagnostics; branch accounting here is defensive */
1343
+ if (includeStaleBlockers) {
1344
+ const reasons = staleBlockerReasonsForItem(item, blockedStatuses, statusRegistry, lifecyclePatternPolicy);
1345
+ if (reasons.length > 0) {
1346
+ rows.staleBlockerRows.push({
1347
+ id: item.id,
1348
+ status: item.status,
1349
+ reasons: [...new Set(reasons)].sort((left, right) => left.localeCompare(right)),
1350
+ });
1351
+ }
1352
+ }
1353
+ /* v8 ignore stop */
1354
+ }
1355
+ return rows;
1356
+ }
1357
+ function sortLifecycleScanRows(rows) {
1358
+ rows.closureLikeRows.sort((left, right) => left.id.localeCompare(right.id));
1359
+ /* v8 ignore start -- parent-id tie breakers only matter for duplicate legacy ids, which the tracker writer prevents */
1360
+ rows.terminalParentRows.sort((left, right) => left.id.localeCompare(right.id) ||
1361
+ left.parent_id.localeCompare(right.parent_id));
1362
+ rows.terminalParentFixRows.sort((left, right) => left.id.localeCompare(right.id) ||
1363
+ left.parent_id.localeCompare(right.parent_id));
1364
+ /* v8 ignore stop */
1365
+ rows.staleBlockerRows.sort((left, right) => left.id.localeCompare(right.id));
1366
+ }
1367
+ function lifecycleCycleWarningToken(prefix, severity, count) {
1368
+ if (count <= 0 || severity === "off") {
1369
+ return null;
1370
+ }
1371
+ return `${severity === "error" ? `${prefix}_error` : prefix}:${count}`;
1372
+ }
1373
+ function buildLifecycleWarnings(rows, includeStaleBlockers, dependencyCycleSeverity, parentCycleSeverity, dependencyCycleCount, parentCycleCount) {
1374
+ const warnings = [];
1375
+ if (rows.closureLikeRows.length > 0) {
1376
+ warnings.push(`validate_lifecycle_active_closure_like_metadata:${rows.closureLikeRows.length}`);
1377
+ }
1378
+ if (rows.terminalParentRows.length > 0) {
1379
+ warnings.push(`validate_lifecycle_active_terminal_parent:${rows.terminalParentRows.length}`);
1380
+ }
1381
+ if (includeStaleBlockers && rows.staleBlockerRows.length > 0) {
1382
+ warnings.push(`validate_lifecycle_stale_blockers:${rows.staleBlockerRows.length}`);
1383
+ }
1384
+ const dependencyWarning = lifecycleCycleWarningToken("validate_lifecycle_dependency_cycles", dependencyCycleSeverity, dependencyCycleCount);
1385
+ const parentWarning = lifecycleCycleWarningToken("validate_hierarchy_parent_cycle", parentCycleSeverity, parentCycleCount);
1386
+ return [
1387
+ ...warnings,
1388
+ ...(dependencyWarning ? [dependencyWarning] : []),
1389
+ ...(parentWarning ? [parentWarning] : []),
1390
+ ];
1391
+ }
1392
+ function linkedArtifactPathExceedsFilesystemLimits(artifactPath) {
1393
+ const normalized = normalizeRelativePath(artifactPath);
1394
+ return (normalized.length > LINKED_ARTIFACT_MAX_PATH_LENGTH ||
1395
+ normalized
1396
+ .split(/[\\/]/)
1397
+ .some((segment) => segment.length > LINKED_ARTIFACT_MAX_SEGMENT_LENGTH));
1398
+ }
1399
+ async function linkedArtifactIsMissing(workspaceRoot, artifactPath) {
1400
+ const absolutePath = path.isAbsolute(artifactPath)
1401
+ ? artifactPath
1402
+ : path.resolve(workspaceRoot, artifactPath);
1403
+ if (linkedArtifactPathExceedsFilesystemLimits(absolutePath)) {
1404
+ return false;
1405
+ }
1406
+ try {
1407
+ const stats = await fs.stat(absolutePath);
1408
+ return !stats.isFile() && !stats.isDirectory();
1409
+ }
1410
+ catch (error) {
1411
+ const code = typeof error === "object" && error !== null && "code" in error
1412
+ ? error.code
1413
+ : undefined;
1414
+ return code === "ENOENT" || code === "ENOTDIR";
1415
+ }
1416
+ }
1417
+ async function collectLinkedPathScanState(items, workspaceRoot) {
1418
+ const state = {
1419
+ linkedProjectPaths: new Set(),
1420
+ remoteLinkedPaths: new Set(),
1421
+ missingLinkedPaths: [],
1422
+ staleLinkRows: [],
1423
+ };
1424
+ for (const item of items) {
1425
+ const linkedArtifactGroups = [
1426
+ { link_kind: "files", artifacts: item.files ?? [] },
1427
+ { link_kind: "docs", artifacts: item.docs ?? [] },
1428
+ ];
1429
+ for (const group of linkedArtifactGroups) {
1430
+ for (const artifact of group.artifacts) {
1431
+ if (artifact.scope !== "project") {
1432
+ continue;
1433
+ }
1434
+ if (isRemoteLinkedArtifactReference(artifact.path)) {
1435
+ state.remoteLinkedPaths.add(artifact.path.trim());
1436
+ continue;
1437
+ }
1438
+ const normalizedPath = normalizeRelativePath(artifact.path);
1439
+ if (normalizedPath.length === 0) {
1440
+ continue;
1441
+ }
1442
+ state.linkedProjectPaths.add(normalizedPath);
1443
+ if (await linkedArtifactIsMissing(workspaceRoot, artifact.path)) {
1444
+ state.missingLinkedPaths.push(normalizedPath);
1445
+ state.staleLinkRows.push({
1446
+ item_id: item.id,
1447
+ path: normalizedPath,
1448
+ link_kind: group.link_kind,
1449
+ });
1450
+ }
1451
+ }
1452
+ }
1453
+ }
1454
+ return state;
1455
+ }
1456
+ function partitionFileCandidates(fileCandidates, pmRoot, workspaceRoot, fileScanMode, includePmInternals, verboseFileLists) {
1457
+ const strictTrackedAllMode = fileScanMode === "tracked-all-strict";
1458
+ const strictModeForcesPmInternals = strictTrackedAllMode && !includePmInternals;
1459
+ const includePmInternalsEffective = includePmInternals || strictTrackedAllMode;
1460
+ const pmInternalCandidatePrefixes = includePmInternalsEffective
1461
+ ? []
1462
+ : resolvePmInternalCandidatePrefixes(pmRoot, workspaceRoot);
1463
+ const excludedPmInternalPaths = pmInternalCandidatePrefixes.length === 0
1464
+ ? []
1465
+ : fileCandidates.candidateFiles.filter((candidate) => hasPathPrefix(candidate, pmInternalCandidatePrefixes));
1466
+ const candidateFiles = pmInternalCandidatePrefixes.length === 0
1467
+ ? fileCandidates.candidateFiles
1468
+ : fileCandidates.candidateFiles.filter((candidate) => !hasPathPrefix(candidate, pmInternalCandidatePrefixes));
1469
+ const excludedByReason = {};
1470
+ if (excludedPmInternalPaths.length > 0) {
1471
+ const summarizedPmInternalPaths = summarizeFileList(excludedPmInternalPaths, verboseFileLists);
1472
+ excludedByReason.pm_internals = {
1473
+ count: excludedPmInternalPaths.length,
1474
+ paths: summarizedPmInternalPaths.values,
1475
+ paths_truncated: summarizedPmInternalPaths.truncated,
1476
+ paths_total: summarizedPmInternalPaths.total,
1477
+ };
1478
+ }
1479
+ return {
1480
+ strictTrackedAllMode,
1481
+ strictModeForcesPmInternals,
1482
+ includePmInternalsEffective,
1483
+ pmInternalCandidatePrefixes,
1484
+ excludedPmInternalPaths,
1485
+ candidateFiles,
1486
+ excludedByReason,
1487
+ };
1488
+ }
1489
+ /* c8 ignore start -- lifecycle stale/terminal/dependency diagnostics matrix is covered by end-to-end validate integration runs */
1490
+ function buildLifecycleCheck(items, includeStaleBlockers, dependencyCycleSeverity, parentCycleSeverity, statusRegistry, lifecyclePatternPolicy, verboseDiagnostics, idPrefix = "pm") {
1491
+ const rows = collectLifecycleScanRows(items, includeStaleBlockers, statusRegistry, lifecyclePatternPolicy);
1492
+ sortLifecycleScanRows(rows);
1493
+ const dependencyCycleDiagnostics = detectLifecycleDependencyCycles(rows.activeItems, idPrefix);
1494
+ const parentCycleDiagnostics = detectLifecycleParentCycles(items);
1495
+ const warnings = buildLifecycleWarnings(rows, includeStaleBlockers, dependencyCycleSeverity, parentCycleSeverity, dependencyCycleDiagnostics.cycle_count, parentCycleDiagnostics.cycle_count);
1496
+ const diagnosticLimit = verboseDiagnostics
1497
+ ? Number.POSITIVE_INFINITY
1498
+ : DIAGNOSTIC_LIST_SUMMARY_LIMIT;
1499
+ const summarizedClosureLikeRows = summarizeList(rows.closureLikeRows.map((row) => `${row.id}:${row.fields.join(",")}`), diagnosticLimit);
1500
+ const summarizedTerminalParentRows = summarizeList(rows.terminalParentRows.map((row) => `${row.id}:${row.parent_id}:${row.parent_status}`), diagnosticLimit);
1501
+ const summarizedStaleBlockerRows = summarizeList(rows.staleBlockerRows.map((row) => `${row.id}:${row.status}:${row.reasons.join(",")}`), diagnosticLimit);
1502
+ const summarizedDependencyCycleItemIds = summarizeList(dependencyCycleDiagnostics.cycle_item_ids, diagnosticLimit);
1503
+ const summarizedDependencyCycleSamplePaths = summarizeList(dependencyCycleDiagnostics.cycle_sample_paths, diagnosticLimit);
1504
+ const summarizedParentCycleItemIds = summarizeList(parentCycleDiagnostics.cycle_item_ids, diagnosticLimit);
1505
+ const summarizedParentCycleSamplePaths = summarizeList(parentCycleDiagnostics.cycle_sample_paths, diagnosticLimit);
1506
+ const hasErrorSeverityCycle = (dependencyCycleDiagnostics.cycle_count > 0 &&
1507
+ dependencyCycleSeverity === "error") ||
1508
+ (parentCycleDiagnostics.cycle_count > 0 && parentCycleSeverity === "error");
1509
+ return {
1510
+ check: {
1511
+ name: "lifecycle",
1512
+ status: hasErrorSeverityCycle
1513
+ ? "error"
1514
+ : warnings.length === 0
1515
+ ? "ok"
1516
+ : "warn",
1517
+ details: {
1518
+ checked_active_items: rows.activeItems.length,
1519
+ active_closure_like_metadata_items: rows.closureLikeRows.length,
1520
+ active_closure_like_metadata_rows: summarizedClosureLikeRows.values,
1521
+ active_closure_like_metadata_rows_truncated: summarizedClosureLikeRows.truncated,
1522
+ active_terminal_parent_items: rows.terminalParentRows.length,
1523
+ active_terminal_parent_rows: summarizedTerminalParentRows.values,
1524
+ active_terminal_parent_rows_truncated: summarizedTerminalParentRows.truncated,
1525
+ stale_blocker_checks_enabled: includeStaleBlockers,
1526
+ stale_blocker_items: rows.staleBlockerRows.length,
1527
+ stale_blocker_rows: summarizedStaleBlockerRows.values,
1528
+ stale_blocker_rows_truncated: summarizedStaleBlockerRows.truncated,
1529
+ dependency_cycle_severity_policy: dependencyCycleSeverity,
1530
+ dependency_cycle_count: dependencyCycleDiagnostics.cycle_count,
1531
+ dependency_cycle_item_count: dependencyCycleDiagnostics.cycle_item_ids.length,
1532
+ dependency_cycle_item_ids: summarizedDependencyCycleItemIds.values,
1533
+ dependency_cycle_item_ids_truncated: summarizedDependencyCycleItemIds.truncated,
1534
+ dependency_cycle_sample_paths: summarizedDependencyCycleSamplePaths.values,
1535
+ dependency_cycle_sample_paths_truncated: summarizedDependencyCycleSamplePaths.truncated,
1536
+ parent_cycle_severity_policy: parentCycleSeverity,
1537
+ parent_cycle_count: parentCycleDiagnostics.cycle_count,
1538
+ parent_cycle_item_count: parentCycleDiagnostics.cycle_item_ids.length,
1539
+ parent_cycle_item_ids: summarizedParentCycleItemIds.values,
1540
+ parent_cycle_item_ids_truncated: summarizedParentCycleItemIds.truncated,
1541
+ parent_cycle_sample_paths: summarizedParentCycleSamplePaths.values,
1542
+ parent_cycle_sample_paths_truncated: summarizedParentCycleSamplePaths.truncated,
1543
+ stale_blocker_reason_patterns: [
1544
+ ...lifecyclePatternPolicy.stale_blocker_reason_patterns,
1545
+ ],
1546
+ stale_blocker_reason_pattern_source: lifecyclePatternPolicy.stale_blocker_reason_pattern_source,
1547
+ closure_like_blocked_reason_patterns: [
1548
+ ...lifecyclePatternPolicy.closure_like_metadata_field_patterns
1549
+ .blocked_reason,
1550
+ ],
1551
+ closure_like_blocked_reason_pattern_source: lifecyclePatternPolicy.closure_like_metadata_field_pattern_sources
1552
+ .blocked_reason,
1553
+ closure_like_resolution_patterns: [
1554
+ ...lifecyclePatternPolicy.closure_like_metadata_field_patterns
1555
+ .resolution,
1556
+ ],
1557
+ closure_like_resolution_pattern_source: lifecyclePatternPolicy.closure_like_metadata_field_pattern_sources
1558
+ .resolution,
1559
+ closure_like_actual_result_patterns: [
1560
+ ...lifecyclePatternPolicy.closure_like_metadata_field_patterns
1561
+ .actual_result,
1562
+ ],
1563
+ closure_like_actual_result_pattern_source: lifecyclePatternPolicy.closure_like_metadata_field_pattern_sources
1564
+ .actual_result,
1565
+ },
1566
+ },
1567
+ warnings,
1568
+ terminalParentFixRows: rows.terminalParentFixRows,
1569
+ };
1570
+ }
1571
+ /* c8 ignore stop */
1572
+ /* c8 ignore start -- files-check candidate filtering/classification permutations are covered by file-audit integration suites */
1573
+ async function buildFilesCheck(items, workspaceRoot, pmRoot, fileScanMode, includePmInternals, verboseFileLists) {
1574
+ const linkedPathState = await collectLinkedPathScanState(items, workspaceRoot);
1575
+ const itemsById = new Map(items.map((item) => [item.id, item]));
1576
+ const uniqueMissingLinkedPaths = [
1577
+ ...new Set(linkedPathState.missingLinkedPaths),
1578
+ ].sort((left, right) => left.localeCompare(right));
1579
+ const fileCandidates = await collectProjectFileCandidates(workspaceRoot, fileScanMode);
1580
+ const partition = partitionFileCandidates(fileCandidates, pmRoot, workspaceRoot, fileScanMode, includePmInternals, verboseFileLists);
1581
+ const orphanedFiles = partition.candidateFiles.filter((candidate) => !linkedPathState.linkedProjectPaths.has(candidate));
1582
+ const orphanedPathRows = buildOrphanedPathRows(orphanedFiles, items);
1583
+ // Stale-path classification (pm-0v2m / GH-184): a missing linked path whose
1584
+ // basename still exists in the candidate scan is reported as `moved` (with
1585
+ // relink candidates); otherwise it is `deleted` and safe to prune.
1586
+ const classifiedStalePaths = classifyStaleLinkedPaths(uniqueMissingLinkedPaths, partition.candidateFiles);
1587
+ const classificationByPath = new Map(classifiedStalePaths.map((entry) => [entry.path, entry.classification]));
1588
+ const movedStalePathCount = classifiedStalePaths.filter((entry) => entry.classification === "moved").length;
1589
+ const staleLinkPruneRows = linkedPathState.staleLinkRows
1590
+ .map((row) => ({
1591
+ ...row,
1592
+ classification: classificationByPath.get(row.path) ?? "deleted",
1593
+ }))
1594
+ .sort((left, right) => left.item_id.localeCompare(right.item_id) ||
1595
+ left.path.localeCompare(right.path) ||
1596
+ left.link_kind.localeCompare(right.link_kind));
1597
+ const warnings = [];
1598
+ if (partition.strictModeForcesPmInternals) {
1599
+ warnings.push("validate_files_tracked_all_strict_forces_pm_internals");
1600
+ }
1601
+ if (uniqueMissingLinkedPaths.length > 0) {
1602
+ warnings.push(`validate_files_missing_linked_paths:${uniqueMissingLinkedPaths.length}`);
1603
+ }
1604
+ if (orphanedFiles.length > 0) {
1605
+ warnings.push(`validate_files_orphaned_paths:${orphanedFiles.length}`);
1606
+ }
1607
+ const uniqueRemoteLinkedPaths = [...linkedPathState.remoteLinkedPaths].sort((left, right) => left.localeCompare(right));
1608
+ const summarizedRemote = summarizeFileList(uniqueRemoteLinkedPaths, verboseFileLists);
1609
+ const summarizedMissing = summarizeFileList(uniqueMissingLinkedPaths, verboseFileLists);
1610
+ const summarizedOrphaned = summarizeFileList(orphanedFiles, verboseFileLists);
1611
+ const summarizedOrphanedClassifications = summarizeFileList(orphanedPathRows.map((row) => `${row.path}:${row.classification}:owner_candidate=${row.owner_candidate?.id ?? "unowned"}`), verboseFileLists);
1612
+ const orphanedPathRowDetail = verboseFileLists
1613
+ ? orphanedPathRows
1614
+ : summarizeFileList(summarizeOrphanedPathRows(orphanedPathRows), false)
1615
+ .values;
1616
+ const summarizedClassifications = summarizeFileList(summarizeStaleLinkedPathClassifications(classifiedStalePaths), verboseFileLists);
1617
+ // Owner attribution for missing linked paths (GH-210): per-path rows naming
1618
+ // the owning item(s) so cleanup is evidence-based instead of requiring a
1619
+ // reverse lookup. Full structured objects under --verbose-file-lists; compact
1620
+ // `path:classification owner=… field=…` one-liners (capped) otherwise — same
1621
+ // full/summary split as the other file-check lists (file_list_detail_mode).
1622
+ const missingLinkedPathRows = staleLinkPruneRows.map((row) => ({
1623
+ item_id: row.item_id,
1624
+ path: row.path,
1625
+ link_kind: row.link_kind,
1626
+ classification: row.classification,
1627
+ }));
1628
+ const ownerRows = buildMissingLinkedPathRows(missingLinkedPathRows, (id) => {
1629
+ const owner = itemsById.get(id);
1630
+ return owner
1631
+ ? { type: owner.type, title: owner.title, status: owner.status }
1632
+ : undefined;
1633
+ });
1634
+ // Default to token-efficient compact one-liners; expose the full structured
1635
+ // rows (the GH-210 JSON shape) under --verbose-file-lists.
1636
+ const ownerRowDetail = verboseFileLists
1637
+ ? ownerRows
1638
+ : summarizeFileList(summarizeMissingLinkedPathRows(ownerRows), false)
1639
+ .values;
1640
+ return {
1641
+ check: {
1642
+ name: "files",
1643
+ status: warnings.length === 0 ? "ok" : "warn",
1644
+ details: {
1645
+ workspace_root: workspaceRoot,
1646
+ scan_mode_requested: fileCandidates.requestedMode,
1647
+ scan_mode_applied: fileCandidates.appliedMode,
1648
+ strict_tracked_all_mode: partition.strictTrackedAllMode,
1649
+ strict_mode_forces_pm_internals: partition.strictModeForcesPmInternals,
1650
+ strict_mode_forces_pm_internals_notice: partition.strictModeForcesPmInternals
1651
+ ? "tracked-all-strict force-enables PM internals; pass --include-pm-internals to make inclusion explicit."
1652
+ : null,
1653
+ file_list_detail_mode: verboseFileLists ? "full" : "summary",
1654
+ file_list_summary_limit: FILE_LIST_SUMMARY_LIMIT,
1655
+ candidate_scan_source: fileCandidates.source,
1656
+ include_pm_internals: partition.includePmInternalsEffective,
1657
+ include_pm_internals_requested: includePmInternals,
1658
+ pm_internal_candidate_prefixes: partition.pmInternalCandidatePrefixes,
1659
+ pm_internal_excluded_count: partition.excludedPmInternalPaths.length,
1660
+ excluded_total: partition.excludedPmInternalPaths.length,
1661
+ excluded_by_reason: partition.excludedByReason,
1662
+ linked_project_paths: linkedPathState.linkedProjectPaths.size,
1663
+ remote_linked_paths_count: uniqueRemoteLinkedPaths.length,
1664
+ remote_linked_paths_total: summarizedRemote.total,
1665
+ remote_linked_paths: summarizedRemote.values,
1666
+ remote_linked_paths_truncated: summarizedRemote.truncated,
1667
+ candidate_total_raw: fileCandidates.candidateTotal,
1668
+ candidate_scanned_raw: fileCandidates.candidateScanned,
1669
+ candidate_total: partition.candidateFiles.length,
1670
+ candidate_scanned: partition.candidateFiles.length,
1671
+ scanned_candidate_files: partition.candidateFiles.length,
1672
+ missing_linked_paths_count: uniqueMissingLinkedPaths.length,
1673
+ missing_linked_paths_total: summarizedMissing.total,
1674
+ missing_linked_paths: summarizedMissing.values,
1675
+ missing_linked_paths_truncated: summarizedMissing.truncated,
1676
+ missing_linked_paths_moved_count: movedStalePathCount,
1677
+ missing_linked_paths_deleted_count: uniqueMissingLinkedPaths.length - movedStalePathCount,
1678
+ missing_linked_path_classifications: summarizedClassifications.values,
1679
+ missing_linked_path_classifications_truncated: summarizedClassifications.truncated,
1680
+ missing_linked_path_rows_count: ownerRows.length,
1681
+ missing_linked_path_rows: ownerRowDetail,
1682
+ orphaned_paths_count: orphanedFiles.length,
1683
+ orphaned_paths_total: summarizedOrphaned.total,
1684
+ orphaned_paths: summarizedOrphaned.values,
1685
+ orphaned_paths_truncated: summarizedOrphaned.truncated,
1686
+ orphaned_path_classifications: summarizedOrphanedClassifications.values,
1687
+ orphaned_path_classifications_truncated: summarizedOrphanedClassifications.truncated,
1688
+ orphaned_path_rows_count: orphanedPathRows.length,
1689
+ orphaned_path_rows: orphanedPathRowDetail,
1690
+ },
1691
+ },
1692
+ warnings,
1693
+ staleLinkPruneRows,
1694
+ };
1695
+ }
1696
+ /* c8 ignore stop */
1697
+ /* c8 ignore start -- history-drift warning/count projection permutations are covered by drift integration tests */
1698
+ async function buildHistoryDriftCheck(pmRoot, items, verboseDiagnostics) {
1699
+ const { missingStreams, unreadableStreams, hashMismatches, chainMismatches, driftedItems, } = await scanHistoryDrift(pmRoot, items);
1700
+ const warnings = [];
1701
+ if (missingStreams.length > 0) {
1702
+ warnings.push(`validate_history_drift_missing_streams:${missingStreams.length}`);
1703
+ }
1704
+ if (unreadableStreams.length > 0) {
1705
+ warnings.push(`validate_history_drift_unreadable_streams:${unreadableStreams.length}`);
1706
+ }
1707
+ if (hashMismatches.length > 0) {
1708
+ warnings.push(`validate_history_drift_hash_mismatches:${hashMismatches.length}`);
1709
+ }
1710
+ if (chainMismatches.length > 0) {
1711
+ warnings.push(`validate_history_drift_chain_mismatches:${chainMismatches.length}`);
1712
+ }
1713
+ const diagnosticLimit = verboseDiagnostics
1714
+ ? Number.POSITIVE_INFINITY
1715
+ : DIAGNOSTIC_LIST_SUMMARY_LIMIT;
1716
+ const summarizedDrifted = summarizeList(driftedItems, diagnosticLimit);
1717
+ return {
1718
+ check: {
1719
+ name: "history_drift",
1720
+ status: warnings.length === 0 ? "ok" : "warn",
1721
+ details: {
1722
+ checked_items: items.length,
1723
+ drifted_items_count: driftedItems.length,
1724
+ drifted_items: summarizedDrifted.values,
1725
+ drifted_items_truncated: summarizedDrifted.truncated,
1726
+ counts: {
1727
+ missing_streams: missingStreams.length,
1728
+ unreadable_streams: unreadableStreams.length,
1729
+ hash_mismatches: hashMismatches.length,
1730
+ chain_mismatches: chainMismatches.length,
1731
+ },
1732
+ },
1733
+ },
1734
+ warnings,
1735
+ };
1736
+ }
1737
+ /* c8 ignore stop */
1738
+ /* c8 ignore start -- command preview truncation formatting is covered by command-reference integration fixtures */
1739
+ function summarizeCommandReferenceRow(ownerId, referencedId, command) {
1740
+ const normalizedCommand = command.trim().replaceAll(/\s+/g, " ");
1741
+ const commandPreview = normalizedCommand.length > 120
1742
+ ? `${normalizedCommand.slice(0, 117)}...`
1743
+ : normalizedCommand;
1744
+ return `${ownerId}:${referencedId}:${commandPreview}`;
1745
+ }
1746
+ /* c8 ignore stop */
1747
+ /* c8 ignore start -- command-reference discovery/stale-id permutations are covered by linked-test integration suites */
1748
+ function buildCommandReferencesCheck(items, idPrefix, verboseDiagnostics) {
1749
+ const knownIds = new Set(items.map((item) => item.id.toLowerCase()));
1750
+ let linkedCommandsScanned = 0;
1751
+ let referencedPmIdCount = 0;
1752
+ const referencedPmIds = new Set();
1753
+ const staleReferenceRows = [];
1754
+ for (const item of items) {
1755
+ for (const linkedTest of item.tests ?? []) {
1756
+ if (typeof linkedTest.command !== "string" ||
1757
+ linkedTest.command.trim().length === 0) {
1758
+ continue;
1759
+ }
1760
+ linkedCommandsScanned += 1;
1761
+ const referencedIds = extractReferencedPmItemIdsFromCommand(linkedTest.command, idPrefix);
1762
+ if (referencedIds.length === 0) {
1763
+ continue;
1764
+ }
1765
+ referencedPmIdCount += referencedIds.length;
1766
+ for (const referencedId of referencedIds) {
1767
+ referencedPmIds.add(referencedId);
1768
+ if (!knownIds.has(referencedId.toLowerCase())) {
1769
+ staleReferenceRows.push(summarizeCommandReferenceRow(item.id, referencedId, linkedTest.command));
1770
+ }
1771
+ }
1772
+ }
1773
+ }
1774
+ const uniqueStaleReferenceRows = [...new Set(staleReferenceRows)].sort((left, right) => left.localeCompare(right));
1775
+ const stalePmIds = [
1776
+ ...new Set(uniqueStaleReferenceRows.map((row) => row.split(":")[1] ?? "")),
1777
+ ]
1778
+ .filter((value) => value.length > 0)
1779
+ .sort((left, right) => left.localeCompare(right));
1780
+ const warnings = uniqueStaleReferenceRows.length > 0
1781
+ ? [
1782
+ `validate_command_references_stale_pm_ids:${uniqueStaleReferenceRows.length}`,
1783
+ ]
1784
+ : [];
1785
+ const diagnosticLimit = verboseDiagnostics
1786
+ ? Number.POSITIVE_INFINITY
1787
+ : DIAGNOSTIC_LIST_SUMMARY_LIMIT;
1788
+ const summarizedRows = summarizeList(uniqueStaleReferenceRows, diagnosticLimit);
1789
+ const summarizedStalePmIds = summarizeList(stalePmIds, diagnosticLimit);
1790
+ const summarizedReferencedPmIds = summarizeList([...referencedPmIds].sort((left, right) => left.localeCompare(right)), diagnosticLimit);
1791
+ return {
1792
+ check: {
1793
+ name: "command_references",
1794
+ status: warnings.length === 0 ? "ok" : "warn",
1795
+ details: {
1796
+ checked_items: items.length,
1797
+ linked_commands_scanned: linkedCommandsScanned,
1798
+ referenced_pm_ids_count: referencedPmIdCount,
1799
+ unique_referenced_pm_ids_count: referencedPmIds.size,
1800
+ unique_referenced_pm_ids: summarizedReferencedPmIds.values,
1801
+ unique_referenced_pm_ids_truncated: summarizedReferencedPmIds.truncated,
1802
+ stale_pm_id_references_count: uniqueStaleReferenceRows.length,
1803
+ stale_pm_ids_count: stalePmIds.length,
1804
+ stale_pm_ids: summarizedStalePmIds.values,
1805
+ stale_pm_ids_truncated: summarizedStalePmIds.truncated,
1806
+ stale_pm_id_reference_rows: summarizedRows.values,
1807
+ stale_pm_id_reference_rows_truncated: summarizedRows.truncated,
1808
+ },
1809
+ },
1810
+ warnings,
1811
+ };
1812
+ }
1813
+ /* c8 ignore stop */
1814
+ /* c8 ignore start -- format-version diagnostics projection is covered by validate format-version integration fixtures */
1815
+ function buildFormatVersionCheck(items, verboseDiagnostics) {
1816
+ const scan = scanItemFormatVersions(items.map((item) => ({
1817
+ ref: item.id,
1818
+ version: effectiveItemFormatVersion(item),
1819
+ })));
1820
+ const warnings = [];
1821
+ if (scan.outdated.length > 0) {
1822
+ warnings.push(`validate_format_version_outdated_items:${scan.outdated.length}`);
1823
+ }
1824
+ if (scan.ahead.length > 0) {
1825
+ warnings.push(`validate_format_version_ahead_items:${scan.ahead.length}`);
1826
+ }
1827
+ const diagnosticLimit = verboseDiagnostics
1828
+ ? Number.POSITIVE_INFINITY
1829
+ : DIAGNOSTIC_LIST_SUMMARY_LIMIT;
1830
+ const summarizedOutdated = summarizeList(scan.outdated, diagnosticLimit);
1831
+ const summarizedAhead = summarizeList(scan.ahead, diagnosticLimit);
1832
+ // `ahead` items were written by a newer pm than this runtime: validation
1833
+ // cannot vouch for fields it does not understand, so it is an error (upgrade
1834
+ // pm). `outdated` items are a non-fatal warning (a migration would rewrite
1835
+ // them). At the current baseline version neither list is populated.
1836
+ const status = scan.ahead.length > 0 ? "error" : scan.outdated.length > 0 ? "warn" : "ok";
1837
+ return {
1838
+ check: {
1839
+ name: "format_version",
1840
+ status,
1841
+ details: {
1842
+ checked_items: items.length,
1843
+ current_format_version: CURRENT_ITEM_FORMAT_VERSION,
1844
+ outdated_items_count: scan.outdated.length,
1845
+ outdated_items: summarizedOutdated.values,
1846
+ outdated_items_truncated: summarizedOutdated.truncated,
1847
+ ahead_items_count: scan.ahead.length,
1848
+ ahead_items: summarizedAhead.values,
1849
+ ahead_items_truncated: summarizedAhead.truncated,
1850
+ },
1851
+ },
1852
+ warnings,
1853
+ };
1854
+ }
1855
+ /* c8 ignore stop */
1856
+ const VALIDATE_AUTO_FIX_MESSAGE = "pm validate auto-fix";
1857
+ /** 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. */
1858
+ /* c8 ignore start -- lazy mutation-command dispatch branches are covered by validate auto-fix integration tests */
1859
+ async function applyValidateFix(fix, global, services) {
1860
+ switch (fix.kind) {
1861
+ case "set_resolution":
1862
+ case "set_close_reason":
1863
+ case "set_estimate":
1864
+ case "reparent":
1865
+ case "unset_parent": {
1866
+ const updateOptions = {
1867
+ message: VALIDATE_AUTO_FIX_MESSAGE,
1868
+ };
1869
+ if (fix.kind === "set_resolution") {
1870
+ updateOptions.resolution = fix.value;
1871
+ }
1872
+ else if (fix.kind === "set_close_reason") {
1873
+ updateOptions.closeReason = fix.value;
1874
+ }
1875
+ else if (fix.kind === "set_estimate") {
1876
+ updateOptions.estimatedMinutes = fix.value;
1877
+ }
1878
+ else if (fix.kind === "reparent") {
1879
+ updateOptions.parent = fix.parent_id;
1880
+ }
1881
+ else {
1882
+ updateOptions.unset = ["parent"];
1883
+ }
1884
+ if (!services.runUpdate) {
1885
+ throw new PmCliError("Applying validate metadata/lifecycle fixes requires a runUpdate mutation service.", EXIT_CODE.USAGE);
1886
+ }
1887
+ await services.runUpdate(fix.item_id, updateOptions, global);
1888
+ return;
1889
+ }
1890
+ case "prune_file_link": {
1891
+ throw new Error(`Unsupported non-batched fix kind: ${fix.kind}`);
1892
+ }
1893
+ case "prune_doc_link": {
1894
+ throw new Error(`Unsupported non-batched fix kind: ${fix.kind}`);
1895
+ }
1896
+ }
1897
+ }
1898
+ /* c8 ignore stop */
1899
+ function pruneBatchKey(fix) {
1900
+ if (fix.kind !== "prune_file_link" && fix.kind !== "prune_doc_link") {
1901
+ return null;
1902
+ }
1903
+ return `${fix.kind}:${fix.item_id}`;
1904
+ }
1905
+ /* c8 ignore start -- batched prune/apply failure fan-out permutations are covered by auto-fix integration suites */
1906
+ async function applyValidateFixes(applicable, global, services) {
1907
+ const applied = [];
1908
+ const failed = [];
1909
+ const pruneBatches = new Map();
1910
+ for (const fix of applicable) {
1911
+ const batchKey = pruneBatchKey(fix);
1912
+ if (batchKey === null) {
1913
+ try {
1914
+ await applyValidateFix(fix, global, services);
1915
+ applied.push(fix);
1916
+ }
1917
+ catch (error) {
1918
+ failed.push({ fix, error });
1919
+ }
1920
+ continue;
1921
+ }
1922
+ const existing = pruneBatches.get(batchKey);
1923
+ if (existing) {
1924
+ existing.push(fix);
1925
+ }
1926
+ else {
1927
+ pruneBatches.set(batchKey, [fix]);
1928
+ }
1929
+ }
1930
+ for (const batch of pruneBatches.values()) {
1931
+ const first = batch[0];
1932
+ /* c8 ignore next 2 -- pruneBatches values are only created with at least one fix */
1933
+ if (!first) {
1934
+ continue;
1935
+ }
1936
+ const remove = batch
1937
+ .map((fix) => fix.path)
1938
+ .filter((value) => typeof value === "string" && value.length > 0);
1939
+ try {
1940
+ if (first.kind === "prune_file_link") {
1941
+ await runFiles(first.item_id, { remove, message: VALIDATE_AUTO_FIX_MESSAGE }, global);
1942
+ }
1943
+ else {
1944
+ await runDocs(first.item_id, { remove, message: VALIDATE_AUTO_FIX_MESSAGE }, global);
1945
+ }
1946
+ applied.push(...batch);
1947
+ }
1948
+ catch (error) {
1949
+ failed.push(...batch.map((fix) => ({ fix, error })));
1950
+ }
1951
+ }
1952
+ return { applied, failed };
1953
+ }
1954
+ /* c8 ignore stop */
1955
+ /** Public contract for test only validate command, shared by SDK and presentation-layer consumers. */
1956
+ export const _testOnlyValidateCommand = {
1957
+ applyValidateFix,
1958
+ applyValidateFixes,
1959
+ attachValidateFixHints,
1960
+ buildCommandReferencesCheck,
1961
+ buildDependencyReferencesCheck,
1962
+ buildCloseReasonBackfillRows,
1963
+ buildFilesCheck,
1964
+ buildLifecycleCheck,
1965
+ buildLifecycleDependencyGraph,
1966
+ buildLifecycleParentGraph,
1967
+ buildEstimateBackfillRows,
1968
+ buildMetadataCounts,
1969
+ buildMissingFieldOccurrences,
1970
+ buildOrphanedPathRows,
1971
+ classifyOrphanedPath,
1972
+ collectDefaultProjectFileCandidates,
1973
+ collectTrackedGitFileCandidates,
1974
+ detectLifecycleDependencyCycles,
1975
+ detectLifecycleParentCycles,
1976
+ escapeRegExp,
1977
+ extractItemIds,
1978
+ findLifecycleDependencyCycleComponents,
1979
+ isMetadataFieldMissing,
1980
+ linkedArtifactPathExceedsFilesystemLimits,
1981
+ listFilesRecursive,
1982
+ resolveDependencyCycleSeverity,
1983
+ resolveParentCycleSeverity,
1984
+ resolveFileScanMode,
1985
+ resolveLifecycleDependencyCycleSamplePath,
1986
+ resolveRequestedChecks,
1987
+ resolveValidateMetadataProfile,
1988
+ resolveWorkspaceRoot,
1989
+ sharedDirectoryPrefixLength,
1990
+ summarizeOrphanedPathRows,
1991
+ summarizeDuplicateIssueCodes,
1992
+ toMeaningfulString,
1993
+ };
1994
+ function recordValidateCheck(state, built, fixHintsEnabled) {
1995
+ if (fixHintsEnabled) {
1996
+ attachValidateFixHints(built.check, built.warnings);
1997
+ }
1998
+ state.checks.push(built.check);
1999
+ state.warnings.push(...built.warnings);
2000
+ }
2001
+ async function executeRequestedValidateChecks(params) {
2002
+ const state = {
2003
+ checks: [],
2004
+ warnings: [...params.initialWarnings],
2005
+ closeReasonBackfillRows: [],
2006
+ estimateBackfillRows: [],
2007
+ resolutionBackfillRows: [],
2008
+ terminalParentFixRows: [],
2009
+ staleLinkPruneRows: [],
2010
+ };
2011
+ const fixHintsEnabled = params.options.fixHints === true;
2012
+ const fullDiagnostics = params.options.verboseDiagnostics === true ||
2013
+ params.options.allAffectedIds === true ||
2014
+ params.global.json === true;
2015
+ if (params.requestedChecks.has("metadata")) {
2016
+ const built = buildMetadataCheck(params.items, params.metadataPolicy, params.statusRegistry, fullDiagnostics);
2017
+ state.closeReasonBackfillRows = built.closeReasonBackfillRows;
2018
+ state.estimateBackfillRows = built.estimateBackfillRows;
2019
+ recordValidateCheck(state, built, fixHintsEnabled);
2020
+ }
2021
+ if (params.requestedChecks.has("resolution")) {
2022
+ const built = buildResolutionCheck(params.items, params.statusRegistry, fullDiagnostics);
2023
+ state.resolutionBackfillRows = built.resolutionBackfillRows;
2024
+ recordValidateCheck(state, built, fixHintsEnabled);
2025
+ }
2026
+ if (params.requestedChecks.has("lifecycle")) {
2027
+ const built = buildLifecycleCheck(params.items, Boolean(params.options.checkStaleBlockers), params.dependencyCycleSeverity, params.parentCycleSeverity, params.statusRegistry, params.lifecyclePatternPolicy, fullDiagnostics, params.settings.id_prefix);
2028
+ state.terminalParentFixRows = built.terminalParentFixRows;
2029
+ recordValidateCheck(state, built, fixHintsEnabled);
2030
+ recordValidateCheck(state, buildDependencyReferencesCheck(params.items, fullDiagnostics, params.statusRegistry), fixHintsEnabled);
2031
+ }
2032
+ if (params.requestedChecks.has("files")) {
2033
+ const built = await buildFilesCheck(params.items, params.workspaceRoot, params.pmRoot, params.fileScanMode, Boolean(params.options.includePmInternals), Boolean(params.options.verboseFileLists));
2034
+ state.staleLinkPruneRows = built.staleLinkPruneRows;
2035
+ recordValidateCheck(state, built, fixHintsEnabled);
2036
+ }
2037
+ if (params.requestedChecks.has("command_references")) {
2038
+ recordValidateCheck(state, buildCommandReferencesCheck(params.items, params.settings.id_prefix, fullDiagnostics), fixHintsEnabled);
2039
+ }
2040
+ if (params.requestedChecks.has("history_drift")) {
2041
+ recordValidateCheck(state, await buildHistoryDriftCheck(params.pmRoot, params.items, fullDiagnostics), fixHintsEnabled);
2042
+ }
2043
+ if (params.requestedChecks.has("format_version")) {
2044
+ recordValidateCheck(state, buildFormatVersionCheck(params.items, fullDiagnostics), fixHintsEnabled);
2045
+ }
2046
+ return state;
2047
+ }
2048
+ function planValidateFixes(options, state, settings) {
2049
+ const planned = [];
2050
+ if (options.autoFix === true) {
2051
+ planned.push(...planCloseReasonBackfillFixes(state.closeReasonBackfillRows));
2052
+ planned.push(...planResolutionBackfillFixes(state.resolutionBackfillRows));
2053
+ planned.push(...planEstimateBackfillFixes(state.estimateBackfillRows, settings.validation.estimate_defaults_by_type));
2054
+ planned.push(...planTerminalParentFixes(state.terminalParentFixRows));
2055
+ }
2056
+ if (options.pruneMissing === true) {
2057
+ planned.push(...planStaleLinkPruneFixes(state.staleLinkPruneRows));
2058
+ }
2059
+ return planned;
2060
+ }
2061
+ /* v8 ignore start -- validate fix-summary matrix is covered end-to-end; direct helper fallback branches are defensive */
2062
+ async function buildValidateFixesSummary(options, state, settings, grantedFixScopes, global, services) {
2063
+ if (options.autoFix !== true && options.pruneMissing !== true) {
2064
+ return undefined;
2065
+ }
2066
+ const planned = planValidateFixes(options, state, settings);
2067
+ const { applicable, gated } = partitionFixesByGrant(planned, grantedFixScopes);
2068
+ const dryRun = options.dryRun === true;
2069
+ const appliedFixRows = [];
2070
+ const failedFixRows = [];
2071
+ if (!dryRun) {
2072
+ const applied = await applyValidateFixes(applicable, global, services);
2073
+ appliedFixRows.push(...applied.applied.map(toFixOutputRow));
2074
+ failedFixRows.push(...applied.failed.map(({ fix, error }) => ({
2075
+ ...toFixOutputRow(fix),
2076
+ error: error instanceof Error ? error.message : String(error),
2077
+ })));
2078
+ }
2079
+ return {
2080
+ mode: dryRun ? "dry_run" : "apply",
2081
+ auto_fix: options.autoFix === true,
2082
+ prune_missing: options.pruneMissing === true,
2083
+ granted_fix_scopes: [...grantedFixScopes].sort((left, right) => left.localeCompare(right)),
2084
+ planned_count: planned.length,
2085
+ applied_count: appliedFixRows.length,
2086
+ gated_count: gated.length,
2087
+ failed_count: failedFixRows.length,
2088
+ planned_fixes: planned.map(toFixOutputRow),
2089
+ applied_fixes: appliedFixRows,
2090
+ gated_fixes: gated.map((fix) => ({
2091
+ ...toFixOutputRow(fix),
2092
+ gate_hint: `Withheld: re-run with --fix-scope ${fix.gate} to apply.`,
2093
+ })),
2094
+ failed_fixes: failedFixRows,
2095
+ };
2096
+ }
2097
+ /* v8 ignore stop */
2098
+ /* c8 ignore start -- validate orchestration + fix-application matrices are covered by end-to-end command integration runs */
2099
+ /** Implements run validate for the public runtime surface of this module. */
2100
+ export async function runValidate(options, global, services = {}) {
2101
+ const fixesRequested = options.autoFix === true || options.pruneMissing === true;
2102
+ if (options.dryRun === true && !fixesRequested) {
2103
+ throw new PmCliError("--dry-run requires --auto-fix or --prune-missing (there is nothing to preview otherwise).", EXIT_CODE.USAGE);
2104
+ }
2105
+ if (options.fixScope !== undefined &&
2106
+ options.fixScope.length > 0 &&
2107
+ options.autoFix !== true) {
2108
+ throw new PmCliError("--fix-scope requires --auto-fix.", EXIT_CODE.USAGE);
2109
+ }
2110
+ // Resolved up-front so unknown --fix-scope values fail fast before any scan.
2111
+ const grantedFixScopes = resolveGrantedFixScopes(options.fixScope);
2112
+ const pmRoot = resolvePmRoot(process.cwd(), global.path);
2113
+ if (!(await pathExists(getSettingsPath(pmRoot)))) {
2114
+ throw new PmCliError(`Tracker is not initialized at ${pmRoot}. Run pm init first.`, EXIT_CODE.NOT_FOUND);
2115
+ }
2116
+ const settings = await readSettings(pmRoot);
2117
+ const statusRegistry = resolveRuntimeStatusRegistry(settings.schema);
2118
+ const typeRegistry = resolveItemTypeRegistry(settings, getActiveExtensionRegistrations());
2119
+ const itemReadWarnings = [];
2120
+ const items = await listAllItemMetadataWithBody(pmRoot, settings.item_format, typeRegistry.type_to_folder, itemReadWarnings, settings.schema);
2121
+ const requestedChecks = resolveRequestedChecks(options);
2122
+ if (requestedChecks.has("history_drift")) {
2123
+ const authorAttribution = await scanHistoryAuthorAttribution(pmRoot);
2124
+ if (authorAttribution.actionable_unknown_event_count > 0) {
2125
+ itemReadWarnings.push(`validate_history_unknown_author_events:${authorAttribution.actionable_unknown_event_count}`);
2126
+ }
2127
+ }
2128
+ const metadataProfileSource = typeof options.metadataProfile === "string" ? "option" : "settings";
2129
+ const metadataProfile = resolveValidateMetadataProfile(typeof options.metadataProfile === "string"
2130
+ ? options.metadataProfile
2131
+ : settings.validation.metadata_profile);
2132
+ const metadataPolicy = resolveValidateMetadataPolicy(metadataProfile, metadataProfileSource, settings.validation.metadata_required_fields);
2133
+ const lifecyclePatternPolicy = resolveLifecyclePatternPolicy(settings);
2134
+ const dependencyCycleSeverity = resolveDependencyCycleSeverity(options.dependencyCycleSeverity);
2135
+ const parentCycleSeverity = resolveParentCycleSeverity(options.parentCycleSeverity);
2136
+ const fileScanMode = resolveFileScanMode(options.scanMode);
2137
+ const workspaceRoot = resolveWorkspaceRoot(pmRoot);
2138
+ const state = await executeRequestedValidateChecks({
2139
+ requestedChecks,
2140
+ options,
2141
+ global,
2142
+ pmRoot,
2143
+ workspaceRoot,
2144
+ settings,
2145
+ items,
2146
+ statusRegistry,
2147
+ metadataPolicy,
2148
+ lifecyclePatternPolicy,
2149
+ dependencyCycleSeverity,
2150
+ parentCycleSeverity,
2151
+ fileScanMode,
2152
+ initialWarnings: [...new Set(itemReadWarnings)],
2153
+ });
2154
+ // Remediation phase (pm-c3sz / pm-8jss / pm-0v2m). Plans are derived from
2155
+ // the findings of THIS run; checks above always report the pre-fix state.
2156
+ // Safe field backfills apply by default under --auto-fix; gated lifecycle
2157
+ // fixes are planned but withheld unless --fix-scope lifecycle grants them;
2158
+ // --dry-run previews without mutating; failures never abort the run.
2159
+ const fixes = fixesRequested
2160
+ ? await buildValidateFixesSummary(options, state, settings, grantedFixScopes, global, services)
2161
+ : undefined;
2162
+ const normalizedWarnings = [...new Set(state.warnings)].sort((left, right) => left.localeCompare(right));
2163
+ const hasErrors = state.checks.some((check) => check.status === "error");
2164
+ return {
2165
+ ok: !hasErrors,
2166
+ has_warnings: normalizedWarnings.length > 0,
2167
+ checks: state.checks,
2168
+ warnings: normalizedWarnings,
2169
+ ...(fixes !== undefined ? { fixes } : {}),
2170
+ generated_at: nowIso(),
2171
+ };
2172
+ }
2173
+ /* c8 ignore stop */
2174
+ //# sourceMappingURL=validate.js.map
2175
+ //# debugId=362d2563-06b2-58f4-8c37-62e0de1eb95b