@unbrained/pm-cli 2026.3.12 → 2026.5.1

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 (285) hide show
  1. package/.agents/pm/extensions/.managed-extensions.json +42 -0
  2. package/.agents/pm/extensions/beads/index.js +109 -0
  3. package/.agents/pm/extensions/beads/manifest.json +7 -0
  4. package/{dist/cli/commands/beads.js → .agents/pm/extensions/beads/runtime.js} +31 -21
  5. package/.agents/pm/extensions/beads/runtime.ts +702 -0
  6. package/.agents/pm/extensions/todos/index.js +126 -0
  7. package/.agents/pm/extensions/todos/manifest.json +7 -0
  8. package/{dist/extensions/builtins/todos/import-export.js → .agents/pm/extensions/todos/runtime.js} +39 -29
  9. package/.agents/pm/extensions/todos/runtime.ts +568 -0
  10. package/AGENTS.md +196 -92
  11. package/CHANGELOG.md +399 -0
  12. package/CODE_OF_CONDUCT.md +42 -0
  13. package/CONTRIBUTING.md +144 -0
  14. package/PRD.md +512 -164
  15. package/README.md +1053 -2
  16. package/SECURITY.md +51 -0
  17. package/dist/cli/commands/activity.d.ts +5 -0
  18. package/dist/cli/commands/activity.js +66 -3
  19. package/dist/cli/commands/activity.js.map +1 -1
  20. package/dist/cli/commands/aggregate.d.ts +54 -0
  21. package/dist/cli/commands/aggregate.js +181 -0
  22. package/dist/cli/commands/aggregate.js.map +1 -0
  23. package/dist/cli/commands/append.js +4 -1
  24. package/dist/cli/commands/append.js.map +1 -1
  25. package/dist/cli/commands/calendar.d.ts +109 -0
  26. package/dist/cli/commands/calendar.js +797 -0
  27. package/dist/cli/commands/calendar.js.map +1 -0
  28. package/dist/cli/commands/claim.d.ts +5 -1
  29. package/dist/cli/commands/claim.js +42 -21
  30. package/dist/cli/commands/claim.js.map +1 -1
  31. package/dist/cli/commands/close.d.ts +1 -0
  32. package/dist/cli/commands/close.js +54 -5
  33. package/dist/cli/commands/close.js.map +1 -1
  34. package/dist/cli/commands/comments-audit.d.ts +91 -0
  35. package/dist/cli/commands/comments-audit.js +195 -0
  36. package/dist/cli/commands/comments-audit.js.map +1 -0
  37. package/dist/cli/commands/comments.d.ts +1 -0
  38. package/dist/cli/commands/comments.js +70 -21
  39. package/dist/cli/commands/comments.js.map +1 -1
  40. package/dist/cli/commands/completion.d.ts +10 -4
  41. package/dist/cli/commands/completion.js +1184 -137
  42. package/dist/cli/commands/completion.js.map +1 -1
  43. package/dist/cli/commands/config.d.ts +35 -3
  44. package/dist/cli/commands/config.js +968 -13
  45. package/dist/cli/commands/config.js.map +1 -1
  46. package/dist/cli/commands/context.d.ts +86 -0
  47. package/dist/cli/commands/context.js +299 -0
  48. package/dist/cli/commands/context.js.map +1 -0
  49. package/dist/cli/commands/contracts.d.ts +78 -0
  50. package/dist/cli/commands/contracts.js +920 -0
  51. package/dist/cli/commands/contracts.js.map +1 -0
  52. package/dist/cli/commands/create.d.ts +48 -14
  53. package/dist/cli/commands/create.js +1331 -160
  54. package/dist/cli/commands/create.js.map +1 -1
  55. package/dist/cli/commands/dedupe-audit.d.ts +81 -0
  56. package/dist/cli/commands/dedupe-audit.js +330 -0
  57. package/dist/cli/commands/dedupe-audit.js.map +1 -0
  58. package/dist/cli/commands/deps.d.ts +52 -0
  59. package/dist/cli/commands/deps.js +204 -0
  60. package/dist/cli/commands/deps.js.map +1 -0
  61. package/dist/cli/commands/docs.d.ts +19 -0
  62. package/dist/cli/commands/docs.js +212 -13
  63. package/dist/cli/commands/docs.js.map +1 -1
  64. package/dist/cli/commands/extension.d.ts +122 -0
  65. package/dist/cli/commands/extension.js +1850 -0
  66. package/dist/cli/commands/extension.js.map +1 -0
  67. package/dist/cli/commands/files.d.ts +52 -1
  68. package/dist/cli/commands/files.js +443 -13
  69. package/dist/cli/commands/files.js.map +1 -1
  70. package/dist/cli/commands/gc.d.ts +11 -1
  71. package/dist/cli/commands/gc.js +89 -11
  72. package/dist/cli/commands/gc.js.map +1 -1
  73. package/dist/cli/commands/get.d.ts +13 -0
  74. package/dist/cli/commands/get.js +35 -3
  75. package/dist/cli/commands/get.js.map +1 -1
  76. package/dist/cli/commands/health.d.ts +10 -2
  77. package/dist/cli/commands/health.js +774 -23
  78. package/dist/cli/commands/health.js.map +1 -1
  79. package/dist/cli/commands/history.d.ts +20 -0
  80. package/dist/cli/commands/history.js +152 -6
  81. package/dist/cli/commands/history.js.map +1 -1
  82. package/dist/cli/commands/index.d.ts +16 -3
  83. package/dist/cli/commands/index.js +16 -3
  84. package/dist/cli/commands/index.js.map +1 -1
  85. package/dist/cli/commands/init.d.ts +7 -2
  86. package/dist/cli/commands/init.js +137 -5
  87. package/dist/cli/commands/init.js.map +1 -1
  88. package/dist/cli/commands/learnings.d.ts +17 -0
  89. package/dist/cli/commands/learnings.js +129 -0
  90. package/dist/cli/commands/learnings.js.map +1 -0
  91. package/dist/cli/commands/list.d.ts +29 -1
  92. package/dist/cli/commands/list.js +289 -53
  93. package/dist/cli/commands/list.js.map +1 -1
  94. package/dist/cli/commands/normalize.d.ts +51 -0
  95. package/dist/cli/commands/normalize.js +298 -0
  96. package/dist/cli/commands/normalize.js.map +1 -0
  97. package/dist/cli/commands/notes.d.ts +17 -0
  98. package/dist/cli/commands/notes.js +129 -0
  99. package/dist/cli/commands/notes.js.map +1 -0
  100. package/dist/cli/commands/reindex.d.ts +1 -0
  101. package/dist/cli/commands/reindex.js +208 -32
  102. package/dist/cli/commands/reindex.js.map +1 -1
  103. package/dist/cli/commands/restore.js +164 -30
  104. package/dist/cli/commands/restore.js.map +1 -1
  105. package/dist/cli/commands/search.d.ts +14 -1
  106. package/dist/cli/commands/search.js +475 -81
  107. package/dist/cli/commands/search.js.map +1 -1
  108. package/dist/cli/commands/stats.js +26 -10
  109. package/dist/cli/commands/stats.js.map +1 -1
  110. package/dist/cli/commands/templates.d.ts +26 -0
  111. package/dist/cli/commands/templates.js +179 -0
  112. package/dist/cli/commands/templates.js.map +1 -0
  113. package/dist/cli/commands/test-all.d.ts +19 -1
  114. package/dist/cli/commands/test-all.js +161 -13
  115. package/dist/cli/commands/test-all.js.map +1 -1
  116. package/dist/cli/commands/test-runs.d.ts +63 -0
  117. package/dist/cli/commands/test-runs.js +179 -0
  118. package/dist/cli/commands/test-runs.js.map +1 -0
  119. package/dist/cli/commands/test.d.ts +75 -1
  120. package/dist/cli/commands/test.js +1360 -41
  121. package/dist/cli/commands/test.js.map +1 -1
  122. package/dist/cli/commands/update-many.d.ts +57 -0
  123. package/dist/cli/commands/update-many.js +631 -0
  124. package/dist/cli/commands/update-many.js.map +1 -0
  125. package/dist/cli/commands/update.d.ts +30 -0
  126. package/dist/cli/commands/update.js +1393 -84
  127. package/dist/cli/commands/update.js.map +1 -1
  128. package/dist/cli/commands/validate.d.ts +30 -0
  129. package/dist/cli/commands/validate.js +1140 -0
  130. package/dist/cli/commands/validate.js.map +1 -0
  131. package/dist/cli/error-guidance.d.ts +33 -0
  132. package/dist/cli/error-guidance.js +337 -0
  133. package/dist/cli/error-guidance.js.map +1 -0
  134. package/dist/cli/extension-command-options.d.ts +1 -0
  135. package/dist/cli/extension-command-options.js +92 -0
  136. package/dist/cli/extension-command-options.js.map +1 -1
  137. package/dist/cli/help-content.d.ts +20 -0
  138. package/dist/cli/help-content.js +543 -0
  139. package/dist/cli/help-content.js.map +1 -0
  140. package/dist/cli/main.js +3625 -445
  141. package/dist/cli/main.js.map +1 -1
  142. package/dist/core/extensions/index.d.ts +13 -1
  143. package/dist/core/extensions/index.js +108 -1
  144. package/dist/core/extensions/index.js.map +1 -1
  145. package/dist/core/extensions/item-fields.d.ts +2 -0
  146. package/dist/core/extensions/item-fields.js +79 -0
  147. package/dist/core/extensions/item-fields.js.map +1 -0
  148. package/dist/core/extensions/loader.d.ts +322 -9
  149. package/dist/core/extensions/loader.js +911 -20
  150. package/dist/core/extensions/loader.js.map +1 -1
  151. package/dist/core/extensions/runtime-registrations.d.ts +5 -0
  152. package/dist/core/extensions/runtime-registrations.js +51 -0
  153. package/dist/core/extensions/runtime-registrations.js.map +1 -0
  154. package/dist/core/history/history-stream-policy.d.ts +20 -0
  155. package/dist/core/history/history-stream-policy.js +53 -0
  156. package/dist/core/history/history-stream-policy.js.map +1 -0
  157. package/dist/core/history/history.js +90 -1
  158. package/dist/core/history/history.js.map +1 -1
  159. package/dist/core/item/id.js +4 -1
  160. package/dist/core/item/id.js.map +1 -1
  161. package/dist/core/item/index.d.ts +1 -0
  162. package/dist/core/item/index.js +1 -0
  163. package/dist/core/item/index.js.map +1 -1
  164. package/dist/core/item/item-format.d.ts +11 -5
  165. package/dist/core/item/item-format.js +507 -24
  166. package/dist/core/item/item-format.js.map +1 -1
  167. package/dist/core/item/parent-reference-policy.d.ts +6 -0
  168. package/dist/core/item/parent-reference-policy.js +32 -0
  169. package/dist/core/item/parent-reference-policy.js.map +1 -0
  170. package/dist/core/item/parse.d.ts +5 -0
  171. package/dist/core/item/parse.js +216 -19
  172. package/dist/core/item/parse.js.map +1 -1
  173. package/dist/core/item/sprint-release-format.d.ts +6 -0
  174. package/dist/core/item/sprint-release-format.js +33 -0
  175. package/dist/core/item/sprint-release-format.js.map +1 -0
  176. package/dist/core/item/status.d.ts +3 -0
  177. package/dist/core/item/status.js +24 -0
  178. package/dist/core/item/status.js.map +1 -0
  179. package/dist/core/item/type-registry.d.ts +37 -0
  180. package/dist/core/item/type-registry.js +706 -0
  181. package/dist/core/item/type-registry.js.map +1 -0
  182. package/dist/core/lock/lock.d.ts +1 -1
  183. package/dist/core/lock/lock.js +101 -12
  184. package/dist/core/lock/lock.js.map +1 -1
  185. package/dist/core/output/command-aware.d.ts +1 -0
  186. package/dist/core/output/command-aware.js +394 -0
  187. package/dist/core/output/command-aware.js.map +1 -0
  188. package/dist/core/output/output.d.ts +3 -0
  189. package/dist/core/output/output.js +124 -6
  190. package/dist/core/output/output.js.map +1 -1
  191. package/dist/core/schema/runtime-field-filters.d.ts +3 -0
  192. package/dist/core/schema/runtime-field-filters.js +39 -0
  193. package/dist/core/schema/runtime-field-filters.js.map +1 -0
  194. package/dist/core/schema/runtime-field-values.d.ts +8 -0
  195. package/dist/core/schema/runtime-field-values.js +154 -0
  196. package/dist/core/schema/runtime-field-values.js.map +1 -0
  197. package/dist/core/schema/runtime-schema.d.ts +68 -0
  198. package/dist/core/schema/runtime-schema.js +554 -0
  199. package/dist/core/schema/runtime-schema.js.map +1 -0
  200. package/dist/core/search/cache.d.ts +13 -1
  201. package/dist/core/search/cache.js +123 -14
  202. package/dist/core/search/cache.js.map +1 -1
  203. package/dist/core/search/semantic-defaults.d.ts +6 -0
  204. package/dist/core/search/semantic-defaults.js +120 -0
  205. package/dist/core/search/semantic-defaults.js.map +1 -0
  206. package/dist/core/search/vector-stores.js +3 -1
  207. package/dist/core/search/vector-stores.js.map +1 -1
  208. package/dist/core/shared/command-types.d.ts +2 -0
  209. package/dist/core/shared/conflict-markers.d.ts +7 -0
  210. package/dist/core/shared/conflict-markers.js +27 -0
  211. package/dist/core/shared/conflict-markers.js.map +1 -0
  212. package/dist/core/shared/constants.d.ts +15 -4
  213. package/dist/core/shared/constants.js +141 -1
  214. package/dist/core/shared/constants.js.map +1 -1
  215. package/dist/core/shared/errors.d.ts +10 -1
  216. package/dist/core/shared/errors.js +3 -1
  217. package/dist/core/shared/errors.js.map +1 -1
  218. package/dist/core/shared/text-normalization.d.ts +4 -0
  219. package/dist/core/shared/text-normalization.js +33 -0
  220. package/dist/core/shared/text-normalization.js.map +1 -0
  221. package/dist/core/shared/time.d.ts +1 -2
  222. package/dist/core/shared/time.js +98 -11
  223. package/dist/core/shared/time.js.map +1 -1
  224. package/dist/core/store/index.d.ts +1 -0
  225. package/dist/core/store/index.js +1 -0
  226. package/dist/core/store/index.js.map +1 -1
  227. package/dist/core/store/item-format-migration.d.ts +9 -0
  228. package/dist/core/store/item-format-migration.js +87 -0
  229. package/dist/core/store/item-format-migration.js.map +1 -0
  230. package/dist/core/store/item-store.d.ts +13 -4
  231. package/dist/core/store/item-store.js +238 -51
  232. package/dist/core/store/item-store.js.map +1 -1
  233. package/dist/core/store/paths.d.ts +21 -3
  234. package/dist/core/store/paths.js +59 -4
  235. package/dist/core/store/paths.js.map +1 -1
  236. package/dist/core/store/settings.d.ts +14 -1
  237. package/dist/core/store/settings.js +463 -7
  238. package/dist/core/store/settings.js.map +1 -1
  239. package/dist/core/telemetry/consent.d.ts +2 -0
  240. package/dist/core/telemetry/consent.js +79 -0
  241. package/dist/core/telemetry/consent.js.map +1 -0
  242. package/dist/core/telemetry/runtime.d.ts +38 -0
  243. package/dist/core/telemetry/runtime.js +733 -0
  244. package/dist/core/telemetry/runtime.js.map +1 -0
  245. package/dist/core/test/background-runs.d.ts +117 -0
  246. package/dist/core/test/background-runs.js +760 -0
  247. package/dist/core/test/background-runs.js.map +1 -0
  248. package/dist/core/test/item-test-run-tracking.d.ts +9 -0
  249. package/dist/core/test/item-test-run-tracking.js +50 -0
  250. package/dist/core/test/item-test-run-tracking.js.map +1 -0
  251. package/dist/sdk/cli-contracts.d.ts +92 -0
  252. package/dist/sdk/cli-contracts.js +2357 -0
  253. package/dist/sdk/cli-contracts.js.map +1 -0
  254. package/dist/sdk/index.d.ts +34 -0
  255. package/dist/sdk/index.js +23 -0
  256. package/dist/sdk/index.js.map +1 -0
  257. package/dist/types.d.ts +197 -3
  258. package/dist/types.js +48 -1
  259. package/dist/types.js.map +1 -1
  260. package/docs/ARCHITECTURE.md +368 -39
  261. package/docs/EXTENSIONS.md +454 -49
  262. package/docs/RELEASING.md +68 -19
  263. package/docs/SDK.md +123 -0
  264. package/docs/examples/starter-extension/README.md +48 -0
  265. package/docs/examples/starter-extension/index.js +191 -0
  266. package/docs/examples/starter-extension/manifest.json +17 -0
  267. package/docs/examples/starter-extension/package.json +10 -0
  268. package/package.json +33 -6
  269. package/.pi/extensions/pm-cli/index.ts +0 -778
  270. package/dist/cli/commands/beads.d.ts +0 -16
  271. package/dist/cli/commands/beads.js.map +0 -1
  272. package/dist/cli/commands/install.d.ts +0 -18
  273. package/dist/cli/commands/install.js +0 -87
  274. package/dist/cli/commands/install.js.map +0 -1
  275. package/dist/core/extensions/builtins.d.ts +0 -3
  276. package/dist/core/extensions/builtins.js +0 -47
  277. package/dist/core/extensions/builtins.js.map +0 -1
  278. package/dist/extensions/builtins/beads/index.d.ts +0 -8
  279. package/dist/extensions/builtins/beads/index.js +0 -33
  280. package/dist/extensions/builtins/beads/index.js.map +0 -1
  281. package/dist/extensions/builtins/todos/import-export.d.ts +0 -26
  282. package/dist/extensions/builtins/todos/import-export.js.map +0 -1
  283. package/dist/extensions/builtins/todos/index.d.ts +0 -8
  284. package/dist/extensions/builtins/todos/index.js +0 -38
  285. package/dist/extensions/builtins/todos/index.js.map +0 -1
@@ -0,0 +1,1140 @@
1
+ import fs from "node:fs/promises";
2
+ import { execFile } from "node:child_process";
3
+ import path from "node:path";
4
+ import { promisify } from "node:util";
5
+ import { getActiveExtensionRegistrations } from "../../core/extensions/index.js";
6
+ import { pathExists } from "../../core/fs/fs-utils.js";
7
+ import { hashDocument } from "../../core/history/history.js";
8
+ import { normalizeStatusInput } from "../../core/item/status.js";
9
+ import { resolveItemTypeRegistry } from "../../core/item/type-registry.js";
10
+ import { resolveRuntimeStatusRegistry } from "../../core/schema/runtime-schema.js";
11
+ import { DEFAULT_VALIDATE_CLOSURE_LIKE_METADATA_FIELD_PATTERNS, DEFAULT_VALIDATE_STALE_BLOCKER_REASON_PATTERNS, EXIT_CODE, PM_DIRNAME, } from "../../core/shared/constants.js";
12
+ import { PmCliError } from "../../core/shared/errors.js";
13
+ import { nowIso } from "../../core/shared/time.js";
14
+ import { listAllFrontMatterWithBody } from "../../core/store/item-store.js";
15
+ import { getHistoryPath, getSettingsPath, resolvePmRoot } from "../../core/store/paths.js";
16
+ import { readSettings } from "../../core/store/settings.js";
17
+ import { extractReferencedPmItemIdsFromCommand } from "./test.js";
18
+ const FILE_SCAN_DIRECTORIES = ["src", "tests", "docs"];
19
+ const FILE_SCAN_ROOT_FILES = [
20
+ "README.md",
21
+ "PRD.md",
22
+ "CHANGELOG.md",
23
+ "AGENTS.md",
24
+ "CONTRIBUTING.md",
25
+ "SECURITY.md",
26
+ "CODE_OF_CONDUCT.md",
27
+ "LICENSE",
28
+ ];
29
+ const DIRECTORY_IGNORE_SET = new Set(["node_modules", ".git", ".cursor", ".agents", "dist", "coverage"]);
30
+ const RESOLUTION_FIELD_KEYS = ["resolution", "expected_result", "actual_result"];
31
+ const VALIDATE_FILE_SCAN_MODES = ["default", "tracked-all", "tracked-all-strict"];
32
+ const VALIDATE_METADATA_PROFILE_VALUES = ["core", "strict", "custom"];
33
+ const VALIDATE_DEPENDENCY_CYCLE_SEVERITY_VALUES = ["off", "warn", "error"];
34
+ const LIFECYCLE_PATTERN_FIELD_KEYS = ["blocked_reason", "resolution", "actual_result"];
35
+ const CORE_METADATA_REQUIRED_FIELDS = ["author", "acceptance_criteria", "estimated_minutes", "close_reason"];
36
+ const STRICT_METADATA_REQUIRED_FIELDS = [
37
+ ...CORE_METADATA_REQUIRED_FIELDS,
38
+ "reviewer",
39
+ "risk",
40
+ "confidence",
41
+ "sprint",
42
+ "release",
43
+ ];
44
+ const SUPPORTED_METADATA_REQUIRED_FIELDS = [
45
+ ...new Set([...STRICT_METADATA_REQUIRED_FIELDS]),
46
+ ];
47
+ const METADATA_REQUIRED_FIELD_ALIASES = {
48
+ author: "author",
49
+ acceptance_criteria: "acceptance_criteria",
50
+ "acceptance-criteria": "acceptance_criteria",
51
+ estimated_minutes: "estimated_minutes",
52
+ "estimated-minutes": "estimated_minutes",
53
+ estimate: "estimated_minutes",
54
+ close_reason: "close_reason",
55
+ "close-reason": "close_reason",
56
+ reviewer: "reviewer",
57
+ risk: "risk",
58
+ confidence: "confidence",
59
+ sprint: "sprint",
60
+ release: "release",
61
+ };
62
+ const METADATA_WARNING_TOKEN_BY_FIELD = {
63
+ author: "validate_metadata_missing_author",
64
+ acceptance_criteria: "validate_metadata_missing_acceptance_criteria",
65
+ estimated_minutes: "validate_metadata_missing_estimate",
66
+ close_reason: "validate_metadata_missing_close_reason",
67
+ reviewer: "validate_metadata_missing_reviewer",
68
+ risk: "validate_metadata_missing_risk",
69
+ confidence: "validate_metadata_missing_confidence",
70
+ sprint: "validate_metadata_missing_sprint",
71
+ release: "validate_metadata_missing_release",
72
+ };
73
+ const METADATA_COUNT_KEY_BY_FIELD = {
74
+ author: "missing_author",
75
+ acceptance_criteria: "missing_acceptance_criteria",
76
+ estimated_minutes: "missing_estimated_minutes",
77
+ close_reason: "closed_missing_close_reason",
78
+ reviewer: "missing_reviewer",
79
+ risk: "missing_risk",
80
+ confidence: "missing_confidence",
81
+ sprint: "missing_sprint",
82
+ release: "missing_release",
83
+ };
84
+ const METADATA_ITEM_IDS_KEY_BY_FIELD = {
85
+ author: "missing_author_item_ids",
86
+ acceptance_criteria: "missing_acceptance_criteria_item_ids",
87
+ estimated_minutes: "missing_estimated_minutes_item_ids",
88
+ close_reason: "closed_missing_close_reason_item_ids",
89
+ reviewer: "missing_reviewer_item_ids",
90
+ risk: "missing_risk_item_ids",
91
+ confidence: "missing_confidence_item_ids",
92
+ sprint: "missing_sprint_item_ids",
93
+ release: "missing_release_item_ids",
94
+ };
95
+ const METADATA_TRUNCATED_KEY_BY_FIELD = {
96
+ author: "missing_author_truncated",
97
+ acceptance_criteria: "missing_acceptance_criteria_truncated",
98
+ estimated_minutes: "missing_estimated_minutes_truncated",
99
+ close_reason: "closed_missing_close_reason_truncated",
100
+ reviewer: "missing_reviewer_truncated",
101
+ risk: "missing_risk_truncated",
102
+ confidence: "missing_confidence_truncated",
103
+ sprint: "missing_sprint_truncated",
104
+ release: "missing_release_truncated",
105
+ };
106
+ const GIT_LS_FILES_MAX_BUFFER = 32 * 1024 * 1024;
107
+ const FILE_LIST_SUMMARY_LIMIT = 40;
108
+ const execFileAsync = promisify(execFile);
109
+ function normalizeRelativePath(value) {
110
+ return value.replaceAll("\\", "/").replace(/^\.\/+/, "").replace(/^\/+/, "");
111
+ }
112
+ function normalizeRelativeDirectoryPath(value) {
113
+ const normalized = normalizeRelativePath(value);
114
+ return normalized.replace(/\/+$/, "");
115
+ }
116
+ function toNonEmptyString(value) {
117
+ if (typeof value !== "string") {
118
+ return undefined;
119
+ }
120
+ const trimmed = value.trim();
121
+ return trimmed.length > 0 ? trimmed : undefined;
122
+ }
123
+ function toMeaningfulString(value) {
124
+ const normalized = toNonEmptyString(value);
125
+ if (!normalized) {
126
+ return undefined;
127
+ }
128
+ const lowered = normalized.toLowerCase();
129
+ if (lowered === "none" || lowered === "null" || lowered === "n/a" || lowered === "na") {
130
+ return undefined;
131
+ }
132
+ return normalized;
133
+ }
134
+ function normalizeStatusForRegistry(status, statusRegistry) {
135
+ return normalizeStatusInput(status, statusRegistry) ?? status;
136
+ }
137
+ function isTerminalStatus(status, statusRegistry) {
138
+ return statusRegistry.terminal_statuses.has(normalizeStatusForRegistry(status, statusRegistry));
139
+ }
140
+ function normalizeLifecyclePatternList(values) {
141
+ return [...new Set((values ?? []).map((value) => value.trim().toLowerCase()).filter((value) => value.length > 0))].sort((left, right) => left.localeCompare(right));
142
+ }
143
+ function areSortedStringListsEqual(left, right) {
144
+ if (left.length !== right.length) {
145
+ return false;
146
+ }
147
+ return left.every((value, index) => value === right[index]);
148
+ }
149
+ function resolveLifecyclePatternPolicy(settings) {
150
+ const defaultStalePatterns = normalizeLifecyclePatternList(DEFAULT_VALIDATE_STALE_BLOCKER_REASON_PATTERNS);
151
+ const defaultClosureLikePatterns = {
152
+ blocked_reason: normalizeLifecyclePatternList(DEFAULT_VALIDATE_CLOSURE_LIKE_METADATA_FIELD_PATTERNS.blocked_reason),
153
+ resolution: normalizeLifecyclePatternList(DEFAULT_VALIDATE_CLOSURE_LIKE_METADATA_FIELD_PATTERNS.resolution),
154
+ actual_result: normalizeLifecyclePatternList(DEFAULT_VALIDATE_CLOSURE_LIKE_METADATA_FIELD_PATTERNS.actual_result),
155
+ };
156
+ const staleBlockerReasonPatterns = normalizeLifecyclePatternList(settings.validation.lifecycle_stale_blocker_reason_patterns);
157
+ const closureLikePatterns = {
158
+ blocked_reason: normalizeLifecyclePatternList(settings.validation.lifecycle_closure_like_blocked_reason_patterns),
159
+ resolution: normalizeLifecyclePatternList(settings.validation.lifecycle_closure_like_resolution_patterns),
160
+ actual_result: normalizeLifecyclePatternList(settings.validation.lifecycle_closure_like_actual_result_patterns),
161
+ };
162
+ return {
163
+ stale_blocker_reason_patterns: staleBlockerReasonPatterns,
164
+ stale_blocker_reason_pattern_source: areSortedStringListsEqual(staleBlockerReasonPatterns, defaultStalePatterns)
165
+ ? "default"
166
+ : "settings",
167
+ closure_like_metadata_field_patterns: closureLikePatterns,
168
+ closure_like_metadata_field_pattern_sources: {
169
+ blocked_reason: areSortedStringListsEqual(closureLikePatterns.blocked_reason, defaultClosureLikePatterns.blocked_reason)
170
+ ? "default"
171
+ : "settings",
172
+ resolution: areSortedStringListsEqual(closureLikePatterns.resolution, defaultClosureLikePatterns.resolution)
173
+ ? "default"
174
+ : "settings",
175
+ actual_result: areSortedStringListsEqual(closureLikePatterns.actual_result, defaultClosureLikePatterns.actual_result)
176
+ ? "default"
177
+ : "settings",
178
+ },
179
+ };
180
+ }
181
+ function resolveValidateMetadataProfile(value) {
182
+ const normalized = value?.trim().toLowerCase();
183
+ if (!normalized || normalized.length === 0) {
184
+ return "core";
185
+ }
186
+ if (VALIDATE_METADATA_PROFILE_VALUES.includes(normalized)) {
187
+ return normalized;
188
+ }
189
+ throw new PmCliError(`Unknown --metadata-profile value "${value}". Supported values: ${VALIDATE_METADATA_PROFILE_VALUES.join(", ")}.`, EXIT_CODE.USAGE);
190
+ }
191
+ function resolveDependencyCycleSeverity(value) {
192
+ const normalized = value?.trim().toLowerCase();
193
+ if (!normalized || normalized.length === 0) {
194
+ return "warn";
195
+ }
196
+ if (VALIDATE_DEPENDENCY_CYCLE_SEVERITY_VALUES.includes(normalized)) {
197
+ return normalized;
198
+ }
199
+ throw new PmCliError(`Unknown --dependency-cycle-severity value "${value}". Supported values: ${VALIDATE_DEPENDENCY_CYCLE_SEVERITY_VALUES.join(", ")}.`, EXIT_CODE.USAGE);
200
+ }
201
+ function normalizeMetadataRequiredFieldsFromSettings(values) {
202
+ const normalized = [...new Set((values ?? []).map((value) => value.trim().toLowerCase().replaceAll("-", "_")))];
203
+ return normalized
204
+ .map((value) => METADATA_REQUIRED_FIELD_ALIASES[value])
205
+ .filter((value) => value !== undefined)
206
+ .sort((left, right) => left.localeCompare(right));
207
+ }
208
+ function resolveValidateMetadataPolicy(profile, profileSource, configuredCustomFields) {
209
+ const normalizedCustomFields = normalizeMetadataRequiredFieldsFromSettings(configuredCustomFields);
210
+ if (profile === "core") {
211
+ return {
212
+ profile,
213
+ profile_source: profileSource,
214
+ required_fields: [...CORE_METADATA_REQUIRED_FIELDS],
215
+ configured_custom_fields: normalizedCustomFields,
216
+ fallback_to_core: false,
217
+ warnings: [],
218
+ };
219
+ }
220
+ if (profile === "strict") {
221
+ return {
222
+ profile,
223
+ profile_source: profileSource,
224
+ required_fields: [...STRICT_METADATA_REQUIRED_FIELDS],
225
+ configured_custom_fields: normalizedCustomFields,
226
+ fallback_to_core: false,
227
+ warnings: [],
228
+ };
229
+ }
230
+ if (normalizedCustomFields.length > 0) {
231
+ return {
232
+ profile,
233
+ profile_source: profileSource,
234
+ required_fields: normalizedCustomFields,
235
+ configured_custom_fields: normalizedCustomFields,
236
+ fallback_to_core: false,
237
+ warnings: [],
238
+ };
239
+ }
240
+ return {
241
+ profile,
242
+ profile_source: profileSource,
243
+ required_fields: [...CORE_METADATA_REQUIRED_FIELDS],
244
+ configured_custom_fields: normalizedCustomFields,
245
+ fallback_to_core: true,
246
+ warnings: ["validate_metadata_custom_profile_missing_required_fields:0"],
247
+ };
248
+ }
249
+ function isMetadataFieldMissing(item, field, statusRegistry) {
250
+ if (field === "author") {
251
+ return !toNonEmptyString(item.author);
252
+ }
253
+ if (field === "acceptance_criteria") {
254
+ return !toNonEmptyString(item.acceptance_criteria);
255
+ }
256
+ if (field === "estimated_minutes") {
257
+ return !Number.isFinite(item.estimated_minutes);
258
+ }
259
+ if (field === "close_reason") {
260
+ return normalizeStatusForRegistry(item.status, statusRegistry) === statusRegistry.close_status && !toNonEmptyString(item.close_reason);
261
+ }
262
+ if (field === "reviewer") {
263
+ return !toNonEmptyString(item.reviewer);
264
+ }
265
+ if (field === "risk") {
266
+ return !toNonEmptyString(item.risk);
267
+ }
268
+ if (field === "confidence") {
269
+ if (typeof item.confidence === "number") {
270
+ return !Number.isFinite(item.confidence);
271
+ }
272
+ return !toNonEmptyString(item.confidence);
273
+ }
274
+ if (field === "sprint") {
275
+ return !toNonEmptyString(item.sprint);
276
+ }
277
+ return !toNonEmptyString(item.release);
278
+ }
279
+ function resolveFileScanMode(scanMode) {
280
+ if (scanMode === undefined) {
281
+ return "default";
282
+ }
283
+ const normalized = scanMode.trim().toLowerCase();
284
+ if (normalized.length === 0) {
285
+ return "default";
286
+ }
287
+ if (normalized === "default") {
288
+ return "default";
289
+ }
290
+ if (normalized === "tracked-all" || normalized === "tracked_all") {
291
+ return "tracked-all";
292
+ }
293
+ if (normalized === "tracked-all-strict" || normalized === "tracked_all_strict") {
294
+ return "tracked-all-strict";
295
+ }
296
+ throw new PmCliError(`Unknown --scan-mode value "${scanMode}". Supported values: ${VALIDATE_FILE_SCAN_MODES.join(", ")}.`, EXIT_CODE.USAGE);
297
+ }
298
+ function resolveWorkspaceRoot(pmRoot) {
299
+ const resolvedPmRoot = path.resolve(pmRoot);
300
+ const normalizedPmRoot = resolvedPmRoot.replaceAll("\\", "/");
301
+ if (normalizedPmRoot.endsWith("/.agents/pm")) {
302
+ return path.dirname(path.dirname(resolvedPmRoot));
303
+ }
304
+ const resolvedCwd = path.resolve(process.cwd());
305
+ const relativeFromPmRoot = path.relative(resolvedPmRoot, resolvedCwd);
306
+ const cwdInsidePmRoot = relativeFromPmRoot.length === 0 ||
307
+ (!relativeFromPmRoot.startsWith("..") && !path.isAbsolute(relativeFromPmRoot));
308
+ if (cwdInsidePmRoot) {
309
+ return resolvedPmRoot;
310
+ }
311
+ /* c8 ignore next 2 -- non-standard PM root layouts are integration-only edge cases. */
312
+ return resolvedCwd;
313
+ }
314
+ async function listFilesRecursive(basePath, relativePath, output) {
315
+ const targetDirectory = relativePath.length > 0 ? path.join(basePath, relativePath) : basePath;
316
+ let entries;
317
+ try {
318
+ entries = await fs.readdir(targetDirectory, { withFileTypes: true });
319
+ }
320
+ catch (error) {
321
+ if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") {
322
+ return;
323
+ }
324
+ throw error;
325
+ }
326
+ for (const entry of entries) {
327
+ if (entry.name.startsWith(".")) {
328
+ continue;
329
+ }
330
+ const childRelative = relativePath.length > 0 ? path.join(relativePath, entry.name) : entry.name;
331
+ if (entry.isDirectory()) {
332
+ if (DIRECTORY_IGNORE_SET.has(entry.name)) {
333
+ continue;
334
+ }
335
+ await listFilesRecursive(basePath, childRelative, output);
336
+ continue;
337
+ }
338
+ /* c8 ignore next 3 -- non-file dirent variants (symlink/socket) are filesystem-specific. */
339
+ if (!entry.isFile()) {
340
+ continue;
341
+ }
342
+ output.push(normalizeRelativePath(childRelative));
343
+ }
344
+ }
345
+ async function collectDefaultProjectFileCandidates(workspaceRoot) {
346
+ const discovered = [];
347
+ for (const directory of FILE_SCAN_DIRECTORIES) {
348
+ await listFilesRecursive(workspaceRoot, directory, discovered);
349
+ }
350
+ for (const candidate of FILE_SCAN_ROOT_FILES) {
351
+ const absolute = path.join(workspaceRoot, candidate);
352
+ try {
353
+ const stats = await fs.stat(absolute);
354
+ /* c8 ignore start -- root-candidate presence depends on fixture workspace composition. */
355
+ if (stats.isFile()) {
356
+ discovered.push(normalizeRelativePath(candidate));
357
+ }
358
+ /* c8 ignore stop */
359
+ }
360
+ catch {
361
+ // Ignore root-file candidates that are not present in this workspace.
362
+ }
363
+ }
364
+ return [...new Set(discovered)].sort((left, right) => left.localeCompare(right));
365
+ }
366
+ async function collectTrackedGitFileCandidates(workspaceRoot) {
367
+ try {
368
+ const { stdout } = await execFileAsync("git", ["ls-files", "-z"], {
369
+ cwd: workspaceRoot,
370
+ encoding: "utf8",
371
+ maxBuffer: GIT_LS_FILES_MAX_BUFFER,
372
+ windowsHide: true,
373
+ });
374
+ const discovered = stdout
375
+ .split("\0")
376
+ .map((value) => normalizeRelativePath(value))
377
+ .filter((value) => value.length > 0);
378
+ return [...new Set(discovered)].sort((left, right) => left.localeCompare(right));
379
+ }
380
+ catch {
381
+ /* c8 ignore start -- fallback path exercised only when git metadata is unavailable. */
382
+ return null;
383
+ /* c8 ignore stop */
384
+ }
385
+ }
386
+ function resolvePmInternalCandidatePrefixes(pmRoot, workspaceRoot) {
387
+ const prefixes = new Set();
388
+ const configuredDefault = normalizeRelativeDirectoryPath(PM_DIRNAME);
389
+ if (configuredDefault.length > 0) {
390
+ prefixes.add(configuredDefault);
391
+ }
392
+ const relativePmRoot = normalizeRelativeDirectoryPath(path.relative(workspaceRoot, pmRoot));
393
+ if (relativePmRoot.length > 0 && !relativePmRoot.startsWith("..")) {
394
+ prefixes.add(relativePmRoot);
395
+ }
396
+ return [...prefixes].sort((left, right) => left.localeCompare(right));
397
+ }
398
+ function hasPathPrefix(candidate, prefixes) {
399
+ for (const prefix of prefixes) {
400
+ if (candidate === prefix || candidate.startsWith(`${prefix}/`)) {
401
+ return true;
402
+ }
403
+ }
404
+ return false;
405
+ }
406
+ async function collectProjectFileCandidates(workspaceRoot, scanMode) {
407
+ if (scanMode === "tracked-all" || scanMode === "tracked-all-strict") {
408
+ const trackedCandidates = await collectTrackedGitFileCandidates(workspaceRoot);
409
+ if (trackedCandidates) {
410
+ return {
411
+ requestedMode: scanMode,
412
+ appliedMode: scanMode,
413
+ source: "tracked-git",
414
+ candidateFiles: trackedCandidates,
415
+ candidateTotal: trackedCandidates.length,
416
+ candidateScanned: trackedCandidates.length,
417
+ };
418
+ }
419
+ /* c8 ignore start -- deterministic fallback retained for non-git workspaces. */
420
+ const fallbackCandidates = await collectDefaultProjectFileCandidates(workspaceRoot);
421
+ return {
422
+ requestedMode: scanMode,
423
+ appliedMode: "default",
424
+ source: "tracked-all-fallback-default",
425
+ candidateFiles: fallbackCandidates,
426
+ candidateTotal: fallbackCandidates.length,
427
+ candidateScanned: fallbackCandidates.length,
428
+ };
429
+ /* c8 ignore stop */
430
+ }
431
+ const defaultCandidates = await collectDefaultProjectFileCandidates(workspaceRoot);
432
+ return {
433
+ requestedMode: scanMode,
434
+ appliedMode: "default",
435
+ source: "default-curated",
436
+ candidateFiles: defaultCandidates,
437
+ candidateTotal: defaultCandidates.length,
438
+ candidateScanned: defaultCandidates.length,
439
+ };
440
+ }
441
+ function summarizeList(values, limit = 200) {
442
+ /* c8 ignore start -- truncation behavior only surfaces with very large synthetic datasets. */
443
+ if (values.length <= limit) {
444
+ return { values, truncated: false };
445
+ }
446
+ return {
447
+ values: values.slice(0, limit),
448
+ truncated: true,
449
+ };
450
+ /* c8 ignore stop */
451
+ }
452
+ function summarizeFileList(values, verboseFileLists) {
453
+ if (verboseFileLists) {
454
+ return {
455
+ values,
456
+ truncated: false,
457
+ total: values.length,
458
+ };
459
+ }
460
+ const summary = summarizeList(values, FILE_LIST_SUMMARY_LIMIT);
461
+ return {
462
+ values: summary.values,
463
+ truncated: summary.truncated,
464
+ total: values.length,
465
+ };
466
+ }
467
+ const RESOLUTION_REMEDIATION_FLAG_BY_FIELD = {
468
+ resolution: "--resolution",
469
+ expected_result: "--expected-result",
470
+ actual_result: "--actual-result",
471
+ };
472
+ const RESOLUTION_REMEDIATION_PLACEHOLDER_BY_FIELD = {
473
+ resolution: "Describe how this item was resolved",
474
+ expected_result: "Describe the expected result",
475
+ actual_result: "Describe the actual result",
476
+ };
477
+ function buildResolutionRemediationCommand(row) {
478
+ const fieldArguments = row.missing_fields
479
+ .map((field) => `${RESOLUTION_REMEDIATION_FLAG_BY_FIELD[field]} \"${RESOLUTION_REMEDIATION_PLACEHOLDER_BY_FIELD[field]}\"`)
480
+ .join(" ");
481
+ return `pm update ${row.id} ${fieldArguments} --message \"Backfill resolution metadata\"`;
482
+ }
483
+ function resolveRequestedChecks(options) {
484
+ const requested = new Set();
485
+ if (options.checkMetadata) {
486
+ requested.add("metadata");
487
+ }
488
+ if (options.checkResolution) {
489
+ requested.add("resolution");
490
+ }
491
+ if (options.checkLifecycle || options.checkStaleBlockers) {
492
+ requested.add("lifecycle");
493
+ }
494
+ if (options.checkFiles) {
495
+ requested.add("files");
496
+ }
497
+ if (options.checkHistoryDrift) {
498
+ requested.add("history_drift");
499
+ }
500
+ if (options.checkCommandReferences) {
501
+ requested.add("command_references");
502
+ }
503
+ if (requested.size === 0) {
504
+ requested.add("metadata");
505
+ requested.add("resolution");
506
+ requested.add("lifecycle");
507
+ requested.add("files");
508
+ requested.add("command_references");
509
+ requested.add("history_drift");
510
+ }
511
+ return requested;
512
+ }
513
+ function buildMetadataCheck(items, metadataPolicy, statusRegistry) {
514
+ const missingByField = Object.fromEntries(SUPPORTED_METADATA_REQUIRED_FIELDS.map((field) => [field, []]));
515
+ for (const item of items) {
516
+ for (const field of SUPPORTED_METADATA_REQUIRED_FIELDS) {
517
+ if (!isMetadataFieldMissing(item, field, statusRegistry)) {
518
+ continue;
519
+ }
520
+ missingByField[field].push(item.id);
521
+ }
522
+ }
523
+ const warningTokens = [...metadataPolicy.warnings];
524
+ for (const field of metadataPolicy.required_fields) {
525
+ const missingItems = missingByField[field];
526
+ if (missingItems.length === 0) {
527
+ continue;
528
+ }
529
+ warningTokens.push(`${METADATA_WARNING_TOKEN_BY_FIELD[field]}:${missingItems.length}`);
530
+ }
531
+ const counts = Object.fromEntries(SUPPORTED_METADATA_REQUIRED_FIELDS.map((field) => [METADATA_COUNT_KEY_BY_FIELD[field], missingByField[field].length]));
532
+ const details = {
533
+ checked_items: items.length,
534
+ metadata_profile: metadataPolicy.profile,
535
+ metadata_profile_source: metadataPolicy.profile_source,
536
+ metadata_profile_fallback_to_core: metadataPolicy.fallback_to_core,
537
+ required_fields: [...metadataPolicy.required_fields],
538
+ configured_custom_required_fields: [...metadataPolicy.configured_custom_fields],
539
+ supported_required_fields: [...SUPPORTED_METADATA_REQUIRED_FIELDS],
540
+ counts,
541
+ };
542
+ for (const field of SUPPORTED_METADATA_REQUIRED_FIELDS) {
543
+ const summarized = summarizeList(missingByField[field]);
544
+ details[METADATA_ITEM_IDS_KEY_BY_FIELD[field]] = summarized.values;
545
+ details[METADATA_TRUNCATED_KEY_BY_FIELD[field]] = summarized.truncated;
546
+ }
547
+ return {
548
+ check: {
549
+ name: "metadata",
550
+ status: warningTokens.length === 0 ? "ok" : "warn",
551
+ details,
552
+ },
553
+ warnings: warningTokens,
554
+ };
555
+ }
556
+ function buildResolutionCheck(items, statusRegistry) {
557
+ const terminalDoneStatuses = new Set(statusRegistry.terminal_done_statuses);
558
+ terminalDoneStatuses.add(statusRegistry.close_status);
559
+ const closedItems = items.filter((item) => terminalDoneStatuses.has(normalizeStatusForRegistry(item.status, statusRegistry)));
560
+ const missingResolutionRows = [];
561
+ for (const item of closedItems) {
562
+ const missingFields = RESOLUTION_FIELD_KEYS.filter((field) => !toNonEmptyString(item[field]));
563
+ if (missingFields.length === 0) {
564
+ continue;
565
+ }
566
+ missingResolutionRows.push({
567
+ id: item.id,
568
+ missing_fields: missingFields,
569
+ });
570
+ }
571
+ const warnings = missingResolutionRows.length > 0 ? [`validate_resolution_missing_fields:${missingResolutionRows.length}`] : [];
572
+ const summarizedRows = summarizeList(missingResolutionRows.map((row) => `${row.id}:${row.missing_fields.join(",")}`));
573
+ const remediationHints = missingResolutionRows.map((row) => buildResolutionRemediationCommand(row));
574
+ const summarizedHints = summarizeList(remediationHints);
575
+ return {
576
+ check: {
577
+ name: "resolution",
578
+ status: warnings.length === 0 ? "ok" : "warn",
579
+ details: {
580
+ checked_closed_items: closedItems.length,
581
+ missing_resolution_items: missingResolutionRows.length,
582
+ missing_resolution_rows: summarizedRows.values,
583
+ missing_resolution_rows_truncated: summarizedRows.truncated,
584
+ missing_resolution_remediation_hints: summarizedHints.values,
585
+ missing_resolution_remediation_hints_truncated: summarizedHints.truncated,
586
+ },
587
+ },
588
+ warnings,
589
+ };
590
+ }
591
+ function buildLifecycleDependencyGraph(activeItems) {
592
+ const activeItemIds = new Set(activeItems.map((item) => item.id));
593
+ const graph = new Map();
594
+ const sortedItems = [...activeItems].sort((left, right) => left.id.localeCompare(right.id));
595
+ for (const item of sortedItems) {
596
+ const edges = new Set();
597
+ for (const dependency of item.dependencies ?? []) {
598
+ const dependencyId = toMeaningfulString(dependency.id);
599
+ if (!dependencyId || !activeItemIds.has(dependencyId)) {
600
+ continue;
601
+ }
602
+ edges.add(dependencyId);
603
+ }
604
+ graph.set(item.id, [...edges].sort((left, right) => left.localeCompare(right)));
605
+ }
606
+ return graph;
607
+ }
608
+ function findLifecycleDependencyCycleComponents(graph) {
609
+ let nextIndex = 0;
610
+ const indexById = new Map();
611
+ const lowLinkById = new Map();
612
+ const stack = [];
613
+ const inStack = new Set();
614
+ const components = [];
615
+ const visit = (id) => {
616
+ indexById.set(id, nextIndex);
617
+ lowLinkById.set(id, nextIndex);
618
+ nextIndex += 1;
619
+ stack.push(id);
620
+ inStack.add(id);
621
+ for (const dependencyId of graph.get(id) ?? []) {
622
+ if (!indexById.has(dependencyId)) {
623
+ visit(dependencyId);
624
+ lowLinkById.set(id, Math.min(lowLinkById.get(id), lowLinkById.get(dependencyId)));
625
+ }
626
+ else if (inStack.has(dependencyId)) {
627
+ lowLinkById.set(id, Math.min(lowLinkById.get(id), indexById.get(dependencyId)));
628
+ }
629
+ }
630
+ if (lowLinkById.get(id) !== indexById.get(id)) {
631
+ return;
632
+ }
633
+ const component = [];
634
+ while (stack.length > 0) {
635
+ const member = stack.pop();
636
+ inStack.delete(member);
637
+ component.push(member);
638
+ if (member === id) {
639
+ break;
640
+ }
641
+ }
642
+ component.sort((left, right) => left.localeCompare(right));
643
+ components.push(component);
644
+ };
645
+ const sortedNodeIds = [...graph.keys()].sort((left, right) => left.localeCompare(right));
646
+ for (const id of sortedNodeIds) {
647
+ if (!indexById.has(id)) {
648
+ visit(id);
649
+ }
650
+ }
651
+ const cycleComponents = components.filter((component) => {
652
+ if (component.length > 1) {
653
+ return true;
654
+ }
655
+ const selfId = component[0];
656
+ return (graph.get(selfId) ?? []).includes(selfId);
657
+ });
658
+ return cycleComponents.sort((left, right) => left[0].localeCompare(right[0]) ||
659
+ left.length - right.length ||
660
+ left.join(",").localeCompare(right.join(",")));
661
+ }
662
+ function resolveLifecycleDependencyCycleSamplePath(component, graph) {
663
+ const start = component[0];
664
+ if (component.length === 1) {
665
+ return [start, start];
666
+ }
667
+ const componentSet = new Set(component);
668
+ const path = [start];
669
+ const visited = new Set([start]);
670
+ const search = (current) => {
671
+ const neighbors = (graph.get(current) ?? []).filter((candidate) => componentSet.has(candidate));
672
+ for (const next of neighbors) {
673
+ if (next === start && path.length > 1) {
674
+ path.push(start);
675
+ return true;
676
+ }
677
+ if (visited.has(next)) {
678
+ continue;
679
+ }
680
+ visited.add(next);
681
+ path.push(next);
682
+ if (search(next)) {
683
+ return true;
684
+ }
685
+ path.pop();
686
+ visited.delete(next);
687
+ }
688
+ return false;
689
+ };
690
+ if (search(start)) {
691
+ return [...path];
692
+ }
693
+ return [...component, start];
694
+ }
695
+ function detectLifecycleDependencyCycles(activeItems) {
696
+ const graph = buildLifecycleDependencyGraph(activeItems);
697
+ const cycleComponents = findLifecycleDependencyCycleComponents(graph);
698
+ const cycleItemIds = [...new Set(cycleComponents.flat())].sort((left, right) => left.localeCompare(right));
699
+ const cycleSamplePaths = cycleComponents.map((component) => resolveLifecycleDependencyCycleSamplePath(component, graph).join("->"));
700
+ return {
701
+ cycle_count: cycleComponents.length,
702
+ cycle_item_ids: cycleItemIds,
703
+ cycle_sample_paths: cycleSamplePaths,
704
+ };
705
+ }
706
+ function buildLifecycleCheck(items, includeStaleBlockers, dependencyCycleSeverity, statusRegistry, lifecyclePatternPolicy) {
707
+ const itemsById = new Map(items.map((item) => [item.id, item]));
708
+ const blockedStatuses = statusRegistry.blocked_statuses.size > 0 ? statusRegistry.blocked_statuses : new Set(["blocked"]);
709
+ const activeItems = items.filter((item) => !isTerminalStatus(item.status, statusRegistry));
710
+ const closureLikeRows = [];
711
+ const terminalParentRows = [];
712
+ const staleBlockerRows = [];
713
+ for (const item of activeItems) {
714
+ const closureLikeFields = Object.entries(lifecyclePatternPolicy.closure_like_metadata_field_patterns)
715
+ .filter(([field, patterns]) => {
716
+ const value = toMeaningfulString(item[field]);
717
+ if (!value) {
718
+ return false;
719
+ }
720
+ const normalized = value.toLowerCase();
721
+ return patterns.some((pattern) => normalized.includes(pattern));
722
+ })
723
+ .map(([field]) => field)
724
+ .sort((left, right) => left.localeCompare(right));
725
+ if (closureLikeFields.length > 0) {
726
+ closureLikeRows.push({
727
+ id: item.id,
728
+ fields: closureLikeFields,
729
+ });
730
+ }
731
+ const parentId = toMeaningfulString(item.parent);
732
+ if (parentId) {
733
+ const parent = itemsById.get(parentId);
734
+ if (parent && isTerminalStatus(parent.status, statusRegistry)) {
735
+ terminalParentRows.push({
736
+ id: item.id,
737
+ parent_id: parent.id,
738
+ parent_status: parent.status,
739
+ });
740
+ }
741
+ }
742
+ if (includeStaleBlockers) {
743
+ const blockedBy = toMeaningfulString(item.blocked_by);
744
+ const blockedReason = toMeaningfulString(item.blocked_reason);
745
+ const blockedReasonNormalized = blockedReason?.toLowerCase();
746
+ const reasons = [];
747
+ const normalizedStatus = normalizeStatusForRegistry(item.status, statusRegistry);
748
+ if (!blockedStatuses.has(normalizedStatus)) {
749
+ if (blockedBy) {
750
+ reasons.push("non_blocked_status_has_blocked_by");
751
+ }
752
+ if (blockedReason) {
753
+ reasons.push("non_blocked_status_has_blocked_reason");
754
+ }
755
+ }
756
+ else {
757
+ if (!blockedBy && !blockedReason) {
758
+ reasons.push("blocked_status_missing_blocker_context");
759
+ }
760
+ if (blockedReasonNormalized?.includes("no active blocker")) {
761
+ reasons.push("blocked_status_reason_reports_no_active_blocker");
762
+ }
763
+ if (blockedReasonNormalized &&
764
+ lifecyclePatternPolicy.stale_blocker_reason_patterns.some((pattern) => blockedReasonNormalized.includes(pattern))) {
765
+ reasons.push("blocked_status_reason_matches_stale_pattern");
766
+ }
767
+ }
768
+ if (reasons.length > 0) {
769
+ staleBlockerRows.push({
770
+ id: item.id,
771
+ status: item.status,
772
+ reasons: [...new Set(reasons)].sort((left, right) => left.localeCompare(right)),
773
+ });
774
+ }
775
+ }
776
+ }
777
+ closureLikeRows.sort((left, right) => left.id.localeCompare(right.id));
778
+ terminalParentRows.sort((left, right) => left.id.localeCompare(right.id) || left.parent_id.localeCompare(right.parent_id));
779
+ staleBlockerRows.sort((left, right) => left.id.localeCompare(right.id));
780
+ const dependencyCycleDiagnostics = detectLifecycleDependencyCycles(activeItems);
781
+ const warnings = [];
782
+ if (closureLikeRows.length > 0) {
783
+ warnings.push(`validate_lifecycle_active_closure_like_metadata:${closureLikeRows.length}`);
784
+ }
785
+ if (terminalParentRows.length > 0) {
786
+ warnings.push(`validate_lifecycle_active_terminal_parent:${terminalParentRows.length}`);
787
+ }
788
+ if (includeStaleBlockers && staleBlockerRows.length > 0) {
789
+ warnings.push(`validate_lifecycle_stale_blockers:${staleBlockerRows.length}`);
790
+ }
791
+ if (dependencyCycleDiagnostics.cycle_count > 0 && dependencyCycleSeverity !== "off") {
792
+ warnings.push(`${dependencyCycleSeverity === "error"
793
+ ? "validate_lifecycle_dependency_cycles_error"
794
+ : "validate_lifecycle_dependency_cycles"}:${dependencyCycleDiagnostics.cycle_count}`);
795
+ }
796
+ const summarizedClosureLikeRows = summarizeList(closureLikeRows.map((row) => `${row.id}:${row.fields.join(",")}`));
797
+ const summarizedTerminalParentRows = summarizeList(terminalParentRows.map((row) => `${row.id}:${row.parent_id}:${row.parent_status}`));
798
+ const summarizedStaleBlockerRows = summarizeList(staleBlockerRows.map((row) => `${row.id}:${row.status}:${row.reasons.join(",")}`));
799
+ const summarizedDependencyCycleItemIds = summarizeList(dependencyCycleDiagnostics.cycle_item_ids);
800
+ const summarizedDependencyCycleSamplePaths = summarizeList(dependencyCycleDiagnostics.cycle_sample_paths);
801
+ return {
802
+ check: {
803
+ name: "lifecycle",
804
+ status: warnings.length === 0 ? "ok" : "warn",
805
+ details: {
806
+ checked_active_items: activeItems.length,
807
+ active_closure_like_metadata_items: closureLikeRows.length,
808
+ active_closure_like_metadata_rows: summarizedClosureLikeRows.values,
809
+ active_closure_like_metadata_rows_truncated: summarizedClosureLikeRows.truncated,
810
+ active_terminal_parent_items: terminalParentRows.length,
811
+ active_terminal_parent_rows: summarizedTerminalParentRows.values,
812
+ active_terminal_parent_rows_truncated: summarizedTerminalParentRows.truncated,
813
+ stale_blocker_checks_enabled: includeStaleBlockers,
814
+ stale_blocker_items: staleBlockerRows.length,
815
+ stale_blocker_rows: summarizedStaleBlockerRows.values,
816
+ stale_blocker_rows_truncated: summarizedStaleBlockerRows.truncated,
817
+ dependency_cycle_severity_policy: dependencyCycleSeverity,
818
+ dependency_cycle_count: dependencyCycleDiagnostics.cycle_count,
819
+ dependency_cycle_item_count: dependencyCycleDiagnostics.cycle_item_ids.length,
820
+ dependency_cycle_item_ids: summarizedDependencyCycleItemIds.values,
821
+ dependency_cycle_item_ids_truncated: summarizedDependencyCycleItemIds.truncated,
822
+ dependency_cycle_sample_paths: summarizedDependencyCycleSamplePaths.values,
823
+ dependency_cycle_sample_paths_truncated: summarizedDependencyCycleSamplePaths.truncated,
824
+ stale_blocker_reason_patterns: [...lifecyclePatternPolicy.stale_blocker_reason_patterns],
825
+ stale_blocker_reason_pattern_source: lifecyclePatternPolicy.stale_blocker_reason_pattern_source,
826
+ closure_like_blocked_reason_patterns: [
827
+ ...lifecyclePatternPolicy.closure_like_metadata_field_patterns.blocked_reason,
828
+ ],
829
+ closure_like_blocked_reason_pattern_source: lifecyclePatternPolicy.closure_like_metadata_field_pattern_sources.blocked_reason,
830
+ closure_like_resolution_patterns: [...lifecyclePatternPolicy.closure_like_metadata_field_patterns.resolution],
831
+ closure_like_resolution_pattern_source: lifecyclePatternPolicy.closure_like_metadata_field_pattern_sources.resolution,
832
+ closure_like_actual_result_patterns: [...lifecyclePatternPolicy.closure_like_metadata_field_patterns.actual_result],
833
+ closure_like_actual_result_pattern_source: lifecyclePatternPolicy.closure_like_metadata_field_pattern_sources.actual_result,
834
+ },
835
+ },
836
+ warnings,
837
+ };
838
+ }
839
+ async function buildFilesCheck(items, workspaceRoot, pmRoot, fileScanMode, includePmInternals, verboseFileLists) {
840
+ const linkedProjectPaths = new Set();
841
+ const missingLinkedPaths = [];
842
+ for (const item of items) {
843
+ const linkedArtifacts = [...(item.files ?? []), ...(item.docs ?? [])];
844
+ for (const artifact of linkedArtifacts) {
845
+ if (artifact.scope !== "project") {
846
+ continue;
847
+ }
848
+ const normalizedPath = normalizeRelativePath(artifact.path);
849
+ if (normalizedPath.length === 0) {
850
+ continue;
851
+ }
852
+ linkedProjectPaths.add(normalizedPath);
853
+ const absolutePath = path.isAbsolute(artifact.path) ? artifact.path : path.resolve(workspaceRoot, artifact.path);
854
+ try {
855
+ const stats = await fs.stat(absolutePath);
856
+ if (!stats.isFile()) {
857
+ missingLinkedPaths.push(normalizedPath);
858
+ }
859
+ }
860
+ catch {
861
+ missingLinkedPaths.push(normalizedPath);
862
+ }
863
+ }
864
+ }
865
+ const uniqueMissingLinkedPaths = [...new Set(missingLinkedPaths)].sort((left, right) => left.localeCompare(right));
866
+ const fileCandidates = await collectProjectFileCandidates(workspaceRoot, fileScanMode);
867
+ const strictTrackedAllMode = fileScanMode === "tracked-all-strict";
868
+ const strictModeForcesPmInternals = strictTrackedAllMode && !includePmInternals;
869
+ const includePmInternalsEffective = includePmInternals || strictTrackedAllMode;
870
+ const pmInternalCandidatePrefixes = includePmInternalsEffective ? [] : resolvePmInternalCandidatePrefixes(pmRoot, workspaceRoot);
871
+ const excludedPmInternalPaths = pmInternalCandidatePrefixes.length === 0
872
+ ? []
873
+ : fileCandidates.candidateFiles.filter((candidate) => hasPathPrefix(candidate, pmInternalCandidatePrefixes));
874
+ const candidateFiles = pmInternalCandidatePrefixes.length === 0
875
+ ? fileCandidates.candidateFiles
876
+ : fileCandidates.candidateFiles.filter((candidate) => !hasPathPrefix(candidate, pmInternalCandidatePrefixes));
877
+ const excludedPmInternalCount = excludedPmInternalPaths.length;
878
+ const excludedByReason = {};
879
+ if (excludedPmInternalCount > 0) {
880
+ const summarizedPmInternalPaths = summarizeFileList(excludedPmInternalPaths, verboseFileLists);
881
+ excludedByReason.pm_internals = {
882
+ count: excludedPmInternalCount,
883
+ paths: summarizedPmInternalPaths.values,
884
+ paths_truncated: summarizedPmInternalPaths.truncated,
885
+ paths_total: summarizedPmInternalPaths.total,
886
+ };
887
+ }
888
+ const orphanedFiles = candidateFiles.filter((candidate) => !linkedProjectPaths.has(candidate));
889
+ const warnings = [];
890
+ if (strictModeForcesPmInternals) {
891
+ warnings.push("validate_files_tracked_all_strict_forces_pm_internals");
892
+ }
893
+ if (uniqueMissingLinkedPaths.length > 0) {
894
+ warnings.push(`validate_files_missing_linked_paths:${uniqueMissingLinkedPaths.length}`);
895
+ }
896
+ if (orphanedFiles.length > 0) {
897
+ warnings.push(`validate_files_orphaned_paths:${orphanedFiles.length}`);
898
+ }
899
+ const summarizedMissing = summarizeFileList(uniqueMissingLinkedPaths, verboseFileLists);
900
+ const summarizedOrphaned = summarizeFileList(orphanedFiles, verboseFileLists);
901
+ return {
902
+ check: {
903
+ name: "files",
904
+ status: warnings.length === 0 ? "ok" : "warn",
905
+ details: {
906
+ workspace_root: workspaceRoot,
907
+ scan_mode_requested: fileCandidates.requestedMode,
908
+ scan_mode_applied: fileCandidates.appliedMode,
909
+ strict_tracked_all_mode: strictTrackedAllMode,
910
+ strict_mode_forces_pm_internals: strictModeForcesPmInternals,
911
+ strict_mode_forces_pm_internals_notice: strictModeForcesPmInternals
912
+ ? "tracked-all-strict force-enables PM internals; pass --include-pm-internals to make inclusion explicit."
913
+ : null,
914
+ file_list_detail_mode: verboseFileLists ? "full" : "summary",
915
+ file_list_summary_limit: FILE_LIST_SUMMARY_LIMIT,
916
+ candidate_scan_source: fileCandidates.source,
917
+ include_pm_internals: includePmInternalsEffective,
918
+ include_pm_internals_requested: includePmInternals,
919
+ pm_internal_candidate_prefixes: pmInternalCandidatePrefixes,
920
+ pm_internal_excluded_count: excludedPmInternalCount,
921
+ excluded_total: excludedPmInternalCount,
922
+ excluded_by_reason: excludedByReason,
923
+ linked_project_paths: linkedProjectPaths.size,
924
+ candidate_total_raw: fileCandidates.candidateTotal,
925
+ candidate_scanned_raw: fileCandidates.candidateScanned,
926
+ candidate_total: candidateFiles.length,
927
+ candidate_scanned: candidateFiles.length,
928
+ scanned_candidate_files: candidateFiles.length,
929
+ missing_linked_paths_count: uniqueMissingLinkedPaths.length,
930
+ missing_linked_paths_total: summarizedMissing.total,
931
+ missing_linked_paths: summarizedMissing.values,
932
+ missing_linked_paths_truncated: summarizedMissing.truncated,
933
+ orphaned_paths_count: orphanedFiles.length,
934
+ orphaned_paths_total: summarizedOrphaned.total,
935
+ orphaned_paths: summarizedOrphaned.values,
936
+ orphaned_paths_truncated: summarizedOrphaned.truncated,
937
+ },
938
+ },
939
+ warnings,
940
+ };
941
+ }
942
+ async function buildHistoryDriftCheck(pmRoot, items) {
943
+ const missingStreams = [];
944
+ const unreadableStreams = [];
945
+ const hashMismatches = [];
946
+ for (const item of items) {
947
+ const historyPath = getHistoryPath(pmRoot, item.id);
948
+ let latestAfterHash = null;
949
+ try {
950
+ const raw = await fs.readFile(historyPath, "utf8");
951
+ if (raw.trim().length === 0) {
952
+ missingStreams.push(item.id);
953
+ continue;
954
+ }
955
+ for (const line of raw.split(/\r?\n/)) {
956
+ const trimmed = line.trim();
957
+ if (trimmed.length === 0) {
958
+ continue;
959
+ }
960
+ const parsed = JSON.parse(trimmed);
961
+ if (typeof parsed.after_hash !== "string" || parsed.after_hash.trim().length === 0) {
962
+ throw new Error("missing after_hash");
963
+ }
964
+ latestAfterHash = parsed.after_hash;
965
+ }
966
+ }
967
+ catch (error) {
968
+ if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") {
969
+ missingStreams.push(item.id);
970
+ }
971
+ else {
972
+ unreadableStreams.push(item.id);
973
+ }
974
+ continue;
975
+ }
976
+ /* c8 ignore start -- defensive guard for future history schema changes. */
977
+ if (!latestAfterHash) {
978
+ missingStreams.push(item.id);
979
+ continue;
980
+ }
981
+ /* c8 ignore stop */
982
+ const { body, ...frontMatter } = item;
983
+ const currentHash = hashDocument({
984
+ front_matter: frontMatter,
985
+ body,
986
+ });
987
+ if (currentHash !== latestAfterHash) {
988
+ hashMismatches.push(item.id);
989
+ }
990
+ }
991
+ const driftedItems = [...new Set([...missingStreams, ...unreadableStreams, ...hashMismatches])].sort((a, b) => a.localeCompare(b));
992
+ const warnings = [];
993
+ if (missingStreams.length > 0) {
994
+ warnings.push(`validate_history_drift_missing_streams:${missingStreams.length}`);
995
+ }
996
+ if (unreadableStreams.length > 0) {
997
+ warnings.push(`validate_history_drift_unreadable_streams:${unreadableStreams.length}`);
998
+ }
999
+ if (hashMismatches.length > 0) {
1000
+ warnings.push(`validate_history_drift_hash_mismatches:${hashMismatches.length}`);
1001
+ }
1002
+ const summarizedDrifted = summarizeList(driftedItems);
1003
+ return {
1004
+ check: {
1005
+ name: "history_drift",
1006
+ status: warnings.length === 0 ? "ok" : "warn",
1007
+ details: {
1008
+ checked_items: items.length,
1009
+ drifted_items_count: driftedItems.length,
1010
+ drifted_items: summarizedDrifted.values,
1011
+ drifted_items_truncated: summarizedDrifted.truncated,
1012
+ counts: {
1013
+ missing_streams: missingStreams.length,
1014
+ unreadable_streams: unreadableStreams.length,
1015
+ hash_mismatches: hashMismatches.length,
1016
+ },
1017
+ },
1018
+ },
1019
+ warnings,
1020
+ };
1021
+ }
1022
+ function summarizeCommandReferenceRow(ownerId, referencedId, command) {
1023
+ const normalizedCommand = command.trim().replaceAll(/\s+/g, " ");
1024
+ const commandPreview = normalizedCommand.length > 120 ? `${normalizedCommand.slice(0, 117)}...` : normalizedCommand;
1025
+ return `${ownerId}:${referencedId}:${commandPreview}`;
1026
+ }
1027
+ function buildCommandReferencesCheck(items, idPrefix) {
1028
+ const knownIds = new Set(items.map((item) => item.id.toLowerCase()));
1029
+ let linkedCommandsScanned = 0;
1030
+ let referencedPmIdCount = 0;
1031
+ const referencedPmIds = new Set();
1032
+ const staleReferenceRows = [];
1033
+ for (const item of items) {
1034
+ for (const linkedTest of item.tests ?? []) {
1035
+ if (typeof linkedTest.command !== "string" || linkedTest.command.trim().length === 0) {
1036
+ continue;
1037
+ }
1038
+ linkedCommandsScanned += 1;
1039
+ const referencedIds = extractReferencedPmItemIdsFromCommand(linkedTest.command, idPrefix);
1040
+ if (referencedIds.length === 0) {
1041
+ continue;
1042
+ }
1043
+ referencedPmIdCount += referencedIds.length;
1044
+ for (const referencedId of referencedIds) {
1045
+ referencedPmIds.add(referencedId);
1046
+ if (!knownIds.has(referencedId.toLowerCase())) {
1047
+ staleReferenceRows.push(summarizeCommandReferenceRow(item.id, referencedId, linkedTest.command));
1048
+ }
1049
+ }
1050
+ }
1051
+ }
1052
+ const uniqueStaleReferenceRows = [...new Set(staleReferenceRows)].sort((left, right) => left.localeCompare(right));
1053
+ const stalePmIds = [...new Set(uniqueStaleReferenceRows.map((row) => row.split(":")[1] ?? ""))]
1054
+ .filter((value) => value.length > 0)
1055
+ .sort((left, right) => left.localeCompare(right));
1056
+ const warnings = uniqueStaleReferenceRows.length > 0 ? [`validate_command_references_stale_pm_ids:${uniqueStaleReferenceRows.length}`] : [];
1057
+ const summarizedRows = summarizeList(uniqueStaleReferenceRows);
1058
+ const summarizedStalePmIds = summarizeList(stalePmIds);
1059
+ const summarizedReferencedPmIds = summarizeList([...referencedPmIds].sort((left, right) => left.localeCompare(right)));
1060
+ return {
1061
+ check: {
1062
+ name: "command_references",
1063
+ status: warnings.length === 0 ? "ok" : "warn",
1064
+ details: {
1065
+ checked_items: items.length,
1066
+ linked_commands_scanned: linkedCommandsScanned,
1067
+ referenced_pm_ids_count: referencedPmIdCount,
1068
+ unique_referenced_pm_ids_count: referencedPmIds.size,
1069
+ unique_referenced_pm_ids: summarizedReferencedPmIds.values,
1070
+ unique_referenced_pm_ids_truncated: summarizedReferencedPmIds.truncated,
1071
+ stale_pm_id_references_count: uniqueStaleReferenceRows.length,
1072
+ stale_pm_ids_count: stalePmIds.length,
1073
+ stale_pm_ids: summarizedStalePmIds.values,
1074
+ stale_pm_ids_truncated: summarizedStalePmIds.truncated,
1075
+ stale_pm_id_reference_rows: summarizedRows.values,
1076
+ stale_pm_id_reference_rows_truncated: summarizedRows.truncated,
1077
+ },
1078
+ },
1079
+ warnings,
1080
+ };
1081
+ }
1082
+ export async function runValidate(options, global) {
1083
+ const pmRoot = resolvePmRoot(process.cwd(), global.path);
1084
+ if (!(await pathExists(getSettingsPath(pmRoot)))) {
1085
+ throw new PmCliError(`Tracker is not initialized at ${pmRoot}. Run pm init first.`, EXIT_CODE.NOT_FOUND);
1086
+ }
1087
+ const settings = await readSettings(pmRoot);
1088
+ const statusRegistry = resolveRuntimeStatusRegistry(settings.schema);
1089
+ const typeRegistry = resolveItemTypeRegistry(settings, getActiveExtensionRegistrations());
1090
+ const itemReadWarnings = [];
1091
+ const items = await listAllFrontMatterWithBody(pmRoot, settings.item_format, typeRegistry.type_to_folder, itemReadWarnings, settings.schema);
1092
+ const requestedChecks = resolveRequestedChecks(options);
1093
+ const metadataProfileSource = typeof options.metadataProfile === "string" ? "option" : "settings";
1094
+ const metadataProfile = resolveValidateMetadataProfile(typeof options.metadataProfile === "string" ? options.metadataProfile : settings.validation.metadata_profile);
1095
+ const metadataPolicy = resolveValidateMetadataPolicy(metadataProfile, metadataProfileSource, settings.validation.metadata_required_fields);
1096
+ const lifecyclePatternPolicy = resolveLifecyclePatternPolicy(settings);
1097
+ const dependencyCycleSeverity = resolveDependencyCycleSeverity(options.dependencyCycleSeverity);
1098
+ const fileScanMode = resolveFileScanMode(options.scanMode);
1099
+ const workspaceRoot = resolveWorkspaceRoot(pmRoot);
1100
+ const checks = [];
1101
+ const warnings = [...new Set(itemReadWarnings)];
1102
+ if (requestedChecks.has("metadata")) {
1103
+ const metadataCheck = buildMetadataCheck(items, metadataPolicy, statusRegistry);
1104
+ checks.push(metadataCheck.check);
1105
+ warnings.push(...metadataCheck.warnings);
1106
+ }
1107
+ if (requestedChecks.has("resolution")) {
1108
+ const resolutionCheck = buildResolutionCheck(items, statusRegistry);
1109
+ checks.push(resolutionCheck.check);
1110
+ warnings.push(...resolutionCheck.warnings);
1111
+ }
1112
+ if (requestedChecks.has("lifecycle")) {
1113
+ const lifecycleCheck = buildLifecycleCheck(items, Boolean(options.checkStaleBlockers), dependencyCycleSeverity, statusRegistry, lifecyclePatternPolicy);
1114
+ checks.push(lifecycleCheck.check);
1115
+ warnings.push(...lifecycleCheck.warnings);
1116
+ }
1117
+ if (requestedChecks.has("files")) {
1118
+ const filesCheck = await buildFilesCheck(items, workspaceRoot, pmRoot, fileScanMode, Boolean(options.includePmInternals), Boolean(options.verboseFileLists));
1119
+ checks.push(filesCheck.check);
1120
+ warnings.push(...filesCheck.warnings);
1121
+ }
1122
+ if (requestedChecks.has("command_references")) {
1123
+ const commandReferencesCheck = buildCommandReferencesCheck(items, settings.id_prefix);
1124
+ checks.push(commandReferencesCheck.check);
1125
+ warnings.push(...commandReferencesCheck.warnings);
1126
+ }
1127
+ if (requestedChecks.has("history_drift")) {
1128
+ const historyDriftCheck = await buildHistoryDriftCheck(pmRoot, items);
1129
+ checks.push(historyDriftCheck.check);
1130
+ warnings.push(...historyDriftCheck.warnings);
1131
+ }
1132
+ const normalizedWarnings = [...new Set(warnings)].sort((left, right) => left.localeCompare(right));
1133
+ return {
1134
+ ok: normalizedWarnings.length === 0,
1135
+ checks,
1136
+ warnings: normalizedWarnings,
1137
+ generated_at: nowIso(),
1138
+ };
1139
+ }
1140
+ //# sourceMappingURL=validate.js.map