@lumenflow/packs-software-delivery 4.24.0 → 5.0.0

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 (286) hide show
  1. package/dist/manifest-schema.d.ts +12 -0
  2. package/dist/manifest-schema.d.ts.map +1 -1
  3. package/dist/manifest-schema.js +10 -0
  4. package/dist/manifest-schema.js.map +1 -1
  5. package/dist/manifest.d.ts +21 -0
  6. package/dist/manifest.d.ts.map +1 -1
  7. package/dist/manifest.js +92 -1
  8. package/dist/manifest.js.map +1 -1
  9. package/dist/src/commands/index.d.ts +2 -0
  10. package/dist/src/commands/index.d.ts.map +1 -0
  11. package/dist/src/commands/index.js +5 -0
  12. package/dist/src/commands/index.js.map +1 -0
  13. package/dist/src/config/delivery-review-contract.d.ts +17 -0
  14. package/dist/src/config/delivery-review-contract.d.ts.map +1 -0
  15. package/dist/src/config/delivery-review-contract.js +19 -0
  16. package/dist/src/config/delivery-review-contract.js.map +1 -0
  17. package/dist/src/config/env-accessors.d.ts +16 -0
  18. package/dist/src/config/env-accessors.d.ts.map +1 -0
  19. package/dist/src/config/env-accessors.js +18 -0
  20. package/dist/src/config/env-accessors.js.map +1 -0
  21. package/dist/src/config/index.d.ts +3 -0
  22. package/dist/src/config/index.d.ts.map +1 -0
  23. package/dist/src/config/index.js +8 -0
  24. package/dist/src/config/index.js.map +1 -0
  25. package/dist/src/config/normalize-config-keys.d.ts +16 -0
  26. package/dist/src/config/normalize-config-keys.d.ts.map +1 -0
  27. package/dist/src/config/normalize-config-keys.js +18 -0
  28. package/dist/src/config/normalize-config-keys.js.map +1 -0
  29. package/dist/src/config/schemas/lumenflow-config-schema-types.d.ts +190 -0
  30. package/dist/src/config/schemas/lumenflow-config-schema-types.d.ts.map +1 -0
  31. package/dist/src/config/schemas/lumenflow-config-schema-types.js +182 -0
  32. package/dist/src/config/schemas/lumenflow-config-schema-types.js.map +1 -0
  33. package/dist/src/config/schemas/lumenflow-config-schema.d.ts +190 -0
  34. package/dist/src/config/schemas/lumenflow-config-schema.d.ts.map +1 -0
  35. package/dist/src/config/schemas/lumenflow-config-schema.js +182 -0
  36. package/dist/src/config/schemas/lumenflow-config-schema.js.map +1 -0
  37. package/dist/src/config/workspace-reader.d.ts +56 -0
  38. package/dist/src/config/workspace-reader.d.ts.map +1 -0
  39. package/dist/src/config/workspace-reader.js +209 -0
  40. package/dist/src/config/workspace-reader.js.map +1 -0
  41. package/dist/src/constants/backlog-patterns.d.ts +21 -0
  42. package/dist/src/constants/backlog-patterns.d.ts.map +1 -0
  43. package/dist/src/constants/backlog-patterns.js +26 -0
  44. package/dist/src/constants/backlog-patterns.js.map +1 -0
  45. package/dist/src/constants/client-ids.d.ts +16 -0
  46. package/dist/src/constants/client-ids.d.ts.map +1 -0
  47. package/dist/src/constants/client-ids.js +16 -0
  48. package/dist/src/constants/client-ids.js.map +1 -0
  49. package/dist/src/constants/config-contract.d.ts +2 -0
  50. package/dist/src/constants/config-contract.d.ts.map +1 -0
  51. package/dist/src/constants/config-contract.js +7 -0
  52. package/dist/src/constants/config-contract.js.map +1 -0
  53. package/dist/src/constants/docs-layout-presets.d.ts +31 -0
  54. package/dist/src/constants/docs-layout-presets.d.ts.map +1 -0
  55. package/dist/src/constants/docs-layout-presets.js +41 -0
  56. package/dist/src/constants/docs-layout-presets.js.map +1 -0
  57. package/dist/src/constants/duration-constants.d.ts +11 -0
  58. package/dist/src/constants/duration-constants.d.ts.map +1 -0
  59. package/dist/src/constants/duration-constants.js +13 -0
  60. package/dist/src/constants/duration-constants.js.map +1 -0
  61. package/dist/src/constants/gate-constants.d.ts +24 -0
  62. package/dist/src/constants/gate-constants.d.ts.map +1 -0
  63. package/dist/src/constants/gate-constants.js +26 -0
  64. package/dist/src/constants/gate-constants.js.map +1 -0
  65. package/dist/src/constants/index.d.ts +18 -0
  66. package/dist/src/constants/index.d.ts.map +1 -0
  67. package/dist/src/constants/index.js +29 -0
  68. package/dist/src/constants/index.js.map +1 -0
  69. package/dist/src/constants/lock-constants.d.ts +29 -0
  70. package/dist/src/constants/lock-constants.d.ts.map +1 -0
  71. package/dist/src/constants/lock-constants.js +31 -0
  72. package/dist/src/constants/lock-constants.js.map +1 -0
  73. package/dist/src/constants/object-guards.d.ts +9 -0
  74. package/dist/src/constants/object-guards.d.ts.map +1 -0
  75. package/dist/src/constants/object-guards.js +11 -0
  76. package/dist/src/constants/object-guards.js.map +1 -0
  77. package/dist/src/constants/section-headings.d.ts +35 -0
  78. package/dist/src/constants/section-headings.d.ts.map +1 -0
  79. package/dist/src/constants/section-headings.js +82 -0
  80. package/dist/src/constants/section-headings.js.map +1 -0
  81. package/dist/src/constants/wu-cli-constants.d.ts +434 -0
  82. package/dist/src/constants/wu-cli-constants.d.ts.map +1 -0
  83. package/dist/src/constants/wu-cli-constants.js +439 -0
  84. package/dist/src/constants/wu-cli-constants.js.map +1 -0
  85. package/dist/src/constants/wu-domain-constants.d.ts +296 -0
  86. package/dist/src/constants/wu-domain-constants.d.ts.map +1 -0
  87. package/dist/src/constants/wu-domain-constants.js +400 -0
  88. package/dist/src/constants/wu-domain-constants.js.map +1 -0
  89. package/dist/src/constants/wu-git-constants.d.ts +2 -0
  90. package/dist/src/constants/wu-git-constants.d.ts.map +1 -0
  91. package/dist/src/constants/wu-git-constants.js +7 -0
  92. package/dist/src/constants/wu-git-constants.js.map +1 -0
  93. package/dist/src/constants/wu-id-format.d.ts +138 -0
  94. package/dist/src/constants/wu-id-format.d.ts.map +1 -0
  95. package/dist/src/constants/wu-id-format.js +265 -0
  96. package/dist/src/constants/wu-id-format.js.map +1 -0
  97. package/dist/src/constants/wu-paths-constants.d.ts +254 -0
  98. package/dist/src/constants/wu-paths-constants.d.ts.map +1 -0
  99. package/dist/src/constants/wu-paths-constants.js +276 -0
  100. package/dist/src/constants/wu-paths-constants.js.map +1 -0
  101. package/dist/src/constants/wu-statuses.d.ts +209 -0
  102. package/dist/src/constants/wu-statuses.d.ts.map +1 -0
  103. package/dist/src/constants/wu-statuses.js +245 -0
  104. package/dist/src/constants/wu-statuses.js.map +1 -0
  105. package/dist/src/constants/wu-type-helpers.d.ts +28 -0
  106. package/dist/src/constants/wu-type-helpers.d.ts.map +1 -0
  107. package/dist/src/constants/wu-type-helpers.js +49 -0
  108. package/dist/src/constants/wu-type-helpers.js.map +1 -0
  109. package/dist/src/constants/wu-ui-constants.d.ts +236 -0
  110. package/dist/src/constants/wu-ui-constants.d.ts.map +1 -0
  111. package/dist/src/constants/wu-ui-constants.js +238 -0
  112. package/dist/src/constants/wu-ui-constants.js.map +1 -0
  113. package/dist/src/constants/wu-validation-constants.d.ts +61 -0
  114. package/dist/src/constants/wu-validation-constants.d.ts.map +1 -0
  115. package/dist/src/constants/wu-validation-constants.js +69 -0
  116. package/dist/src/constants/wu-validation-constants.js.map +1 -0
  117. package/dist/src/domain/index.d.ts +4 -0
  118. package/dist/src/domain/index.d.ts.map +1 -0
  119. package/dist/src/domain/index.js +6 -0
  120. package/dist/src/domain/index.js.map +1 -0
  121. package/dist/src/domain/orchestration.constants.d.ts +111 -0
  122. package/dist/src/domain/orchestration.constants.d.ts.map +1 -0
  123. package/dist/src/domain/orchestration.constants.js +130 -0
  124. package/dist/src/domain/orchestration.constants.js.map +1 -0
  125. package/dist/src/domain/orchestration.schemas.d.ts +307 -0
  126. package/dist/src/domain/orchestration.schemas.d.ts.map +1 -0
  127. package/dist/src/domain/orchestration.schemas.js +214 -0
  128. package/dist/src/domain/orchestration.schemas.js.map +1 -0
  129. package/dist/src/domain/orchestration.types.d.ts +134 -0
  130. package/dist/src/domain/orchestration.types.d.ts.map +1 -0
  131. package/dist/src/domain/orchestration.types.js +5 -0
  132. package/dist/src/domain/orchestration.types.js.map +1 -0
  133. package/dist/src/methodology/incremental-test.d.ts +33 -0
  134. package/dist/src/methodology/incremental-test.d.ts.map +1 -0
  135. package/dist/src/methodology/incremental-test.js +73 -0
  136. package/dist/src/methodology/incremental-test.js.map +1 -0
  137. package/dist/src/methodology/index.d.ts +3 -0
  138. package/dist/src/methodology/index.d.ts.map +1 -0
  139. package/dist/src/methodology/index.js +6 -0
  140. package/dist/src/methodology/index.js.map +1 -0
  141. package/dist/src/methodology/manual-test-validator.d.ts +97 -0
  142. package/dist/src/methodology/manual-test-validator.d.ts.map +1 -0
  143. package/dist/src/methodology/manual-test-validator.js +248 -0
  144. package/dist/src/methodology/manual-test-validator.js.map +1 -0
  145. package/dist/src/policy/coverage-gate.d.ts +127 -0
  146. package/dist/src/policy/coverage-gate.d.ts.map +1 -0
  147. package/dist/src/policy/coverage-gate.js +211 -0
  148. package/dist/src/policy/coverage-gate.js.map +1 -0
  149. package/dist/src/policy/gates-agent-mode.d.ts +107 -0
  150. package/dist/src/policy/gates-agent-mode.d.ts.map +1 -0
  151. package/dist/src/policy/gates-agent-mode.js +138 -0
  152. package/dist/src/policy/gates-agent-mode.js.map +1 -0
  153. package/dist/src/policy/gates-config-internal.d.ts +54 -0
  154. package/dist/src/policy/gates-config-internal.d.ts.map +1 -0
  155. package/dist/src/policy/gates-config-internal.js +108 -0
  156. package/dist/src/policy/gates-config-internal.js.map +1 -0
  157. package/dist/src/policy/gates-config.d.ts +67 -0
  158. package/dist/src/policy/gates-config.d.ts.map +1 -0
  159. package/dist/src/policy/gates-config.js +193 -0
  160. package/dist/src/policy/gates-config.js.map +1 -0
  161. package/dist/src/policy/gates-coverage.d.ts +48 -0
  162. package/dist/src/policy/gates-coverage.d.ts.map +1 -0
  163. package/dist/src/policy/gates-coverage.js +182 -0
  164. package/dist/src/policy/gates-coverage.js.map +1 -0
  165. package/dist/src/policy/gates-presets.d.ts +51 -0
  166. package/dist/src/policy/gates-presets.d.ts.map +1 -0
  167. package/dist/src/policy/gates-presets.js +117 -0
  168. package/dist/src/policy/gates-presets.js.map +1 -0
  169. package/dist/src/policy/gates-schemas.d.ts +142 -0
  170. package/dist/src/policy/gates-schemas.d.ts.map +1 -0
  171. package/dist/src/policy/gates-schemas.js +67 -0
  172. package/dist/src/policy/gates-schemas.js.map +1 -0
  173. package/dist/src/policy/index.d.ts +19 -0
  174. package/dist/src/policy/index.d.ts.map +1 -0
  175. package/dist/src/policy/index.js +21 -0
  176. package/dist/src/policy/index.js.map +1 -0
  177. package/dist/src/policy/package-manager-resolver.d.ts +79 -0
  178. package/dist/src/policy/package-manager-resolver.d.ts.map +1 -0
  179. package/dist/src/policy/package-manager-resolver.js +245 -0
  180. package/dist/src/policy/package-manager-resolver.js.map +1 -0
  181. package/dist/src/policy/resolve-policy.d.ts +337 -0
  182. package/dist/src/policy/resolve-policy.d.ts.map +1 -0
  183. package/dist/src/policy/resolve-policy.js +353 -0
  184. package/dist/src/policy/resolve-policy.js.map +1 -0
  185. package/dist/src/ports/config.ports.d.ts +83 -0
  186. package/dist/src/ports/config.ports.d.ts.map +1 -0
  187. package/dist/src/ports/config.ports.js +4 -0
  188. package/dist/src/ports/config.ports.js.map +1 -0
  189. package/dist/src/ports/dashboard-renderer.port.d.ts +113 -0
  190. package/dist/src/ports/dashboard-renderer.port.d.ts.map +1 -0
  191. package/dist/src/ports/dashboard-renderer.port.js +4 -0
  192. package/dist/src/ports/dashboard-renderer.port.js.map +1 -0
  193. package/dist/src/ports/index.d.ts +5 -0
  194. package/dist/src/ports/index.d.ts.map +1 -0
  195. package/dist/src/ports/index.js +10 -0
  196. package/dist/src/ports/index.js.map +1 -0
  197. package/dist/src/ports/sync-validator.ports.d.ts +52 -0
  198. package/dist/src/ports/sync-validator.ports.d.ts.map +1 -0
  199. package/dist/src/ports/sync-validator.ports.js +4 -0
  200. package/dist/src/ports/sync-validator.ports.js.map +1 -0
  201. package/dist/src/ports/wu-helpers.ports.d.ts +157 -0
  202. package/dist/src/ports/wu-helpers.ports.d.ts.map +1 -0
  203. package/dist/src/ports/wu-helpers.ports.js +4 -0
  204. package/dist/src/ports/wu-helpers.ports.js.map +1 -0
  205. package/dist/src/ports/wu-state.ports.d.ts +209 -0
  206. package/dist/src/ports/wu-state.ports.d.ts.map +1 -0
  207. package/dist/src/ports/wu-state.ports.js +4 -0
  208. package/dist/src/ports/wu-state.ports.js.map +1 -0
  209. package/dist/src/primitives/index.d.ts +2 -0
  210. package/dist/src/primitives/index.d.ts.map +1 -0
  211. package/dist/src/primitives/index.js +5 -0
  212. package/dist/src/primitives/index.js.map +1 -0
  213. package/dist/src/runtime/index.d.ts +2 -0
  214. package/dist/src/runtime/index.d.ts.map +1 -0
  215. package/dist/src/runtime/index.js +6 -0
  216. package/dist/src/runtime/index.js.map +1 -0
  217. package/dist/src/runtime/work-classifier.d.ts +103 -0
  218. package/dist/src/runtime/work-classifier.d.ts.map +1 -0
  219. package/dist/src/runtime/work-classifier.js +427 -0
  220. package/dist/src/runtime/work-classifier.js.map +1 -0
  221. package/dist/src/sandbox/index.d.ts +6 -0
  222. package/dist/src/sandbox/index.d.ts.map +1 -0
  223. package/dist/src/sandbox/index.js +10 -0
  224. package/dist/src/sandbox/index.js.map +1 -0
  225. package/dist/src/sandbox/sandbox-allowlist.d.ts +16 -0
  226. package/dist/src/sandbox/sandbox-allowlist.d.ts.map +1 -0
  227. package/dist/src/sandbox/sandbox-allowlist.js +77 -0
  228. package/dist/src/sandbox/sandbox-allowlist.js.map +1 -0
  229. package/dist/src/sandbox/sandbox-backend-linux.d.ts +6 -0
  230. package/dist/src/sandbox/sandbox-backend-linux.d.ts.map +1 -0
  231. package/dist/src/sandbox/sandbox-backend-linux.js +67 -0
  232. package/dist/src/sandbox/sandbox-backend-linux.js.map +1 -0
  233. package/dist/src/sandbox/sandbox-backend-macos.d.ts +6 -0
  234. package/dist/src/sandbox/sandbox-backend-macos.d.ts.map +1 -0
  235. package/dist/src/sandbox/sandbox-backend-macos.js +112 -0
  236. package/dist/src/sandbox/sandbox-backend-macos.js.map +1 -0
  237. package/dist/src/sandbox/sandbox-backend-windows.d.ts +6 -0
  238. package/dist/src/sandbox/sandbox-backend-windows.d.ts.map +1 -0
  239. package/dist/src/sandbox/sandbox-backend-windows.js +30 -0
  240. package/dist/src/sandbox/sandbox-backend-windows.js.map +1 -0
  241. package/dist/src/sandbox/sandbox-profile.d.ts +58 -0
  242. package/dist/src/sandbox/sandbox-profile.d.ts.map +1 -0
  243. package/dist/src/sandbox/sandbox-profile.js +69 -0
  244. package/dist/src/sandbox/sandbox-profile.js.map +1 -0
  245. package/dist/src/schemas/index.d.ts +2 -0
  246. package/dist/src/schemas/index.d.ts.map +1 -0
  247. package/dist/src/schemas/index.js +5 -0
  248. package/dist/src/schemas/index.js.map +1 -0
  249. package/dist/src/state/date-utils.d.ts +66 -0
  250. package/dist/src/state/date-utils.d.ts.map +1 -0
  251. package/dist/src/state/date-utils.js +143 -0
  252. package/dist/src/state/date-utils.js.map +1 -0
  253. package/dist/src/state/index.d.ts +8 -0
  254. package/dist/src/state/index.d.ts.map +1 -0
  255. package/dist/src/state/index.js +15 -0
  256. package/dist/src/state/index.js.map +1 -0
  257. package/dist/src/state/state-machine.d.ts +14 -0
  258. package/dist/src/state/state-machine.d.ts.map +1 -0
  259. package/dist/src/state/state-machine.js +92 -0
  260. package/dist/src/state/state-machine.js.map +1 -0
  261. package/dist/src/state/wu-doc-types.d.ts +48 -0
  262. package/dist/src/state/wu-doc-types.d.ts.map +1 -0
  263. package/dist/src/state/wu-doc-types.js +4 -0
  264. package/dist/src/state/wu-doc-types.js.map +1 -0
  265. package/dist/src/state/wu-paths.d.ts +275 -0
  266. package/dist/src/state/wu-paths.d.ts.map +1 -0
  267. package/dist/src/state/wu-paths.js +335 -0
  268. package/dist/src/state/wu-paths.js.map +1 -0
  269. package/dist/src/state/wu-schema.d.ts +831 -0
  270. package/dist/src/state/wu-schema.d.ts.map +1 -0
  271. package/dist/src/state/wu-schema.js +934 -0
  272. package/dist/src/state/wu-schema.js.map +1 -0
  273. package/dist/src/state/wu-state-schema.d.ts +292 -0
  274. package/dist/src/state/wu-state-schema.d.ts.map +1 -0
  275. package/dist/src/state/wu-state-schema.js +215 -0
  276. package/dist/src/state/wu-state-schema.js.map +1 -0
  277. package/dist/src/state/wu-yaml.d.ts +113 -0
  278. package/dist/src/state/wu-yaml.d.ts.map +1 -0
  279. package/dist/src/state/wu-yaml.js +307 -0
  280. package/dist/src/state/wu-yaml.js.map +1 -0
  281. package/dist/tool-impl/wu-lifecycle-tools.d.ts +11 -0
  282. package/dist/tool-impl/wu-lifecycle-tools.d.ts.map +1 -1
  283. package/dist/tool-impl/wu-lifecycle-tools.js +17 -0
  284. package/dist/tool-impl/wu-lifecycle-tools.js.map +1 -1
  285. package/manifest.yaml +46 -0
  286. package/package.json +88 -3
@@ -0,0 +1,248 @@
1
+ #!/usr/bin/env node
2
+ // Copyright (c) 2026 Hellmai Ltd
3
+ // SPDX-License-Identifier: AGPL-3.0-only
4
+ /**
5
+ * Manual Test Escape Hatch Validator
6
+ *
7
+ * WU-1433: Restricts manual-only tests for WUs touching hex core code.
8
+ * WU-2332: Require automated tests for all code files, remove lane exemptions.
9
+ *
10
+ * Implementation WUs must have at least one automated test (unit/e2e/integration).
11
+ *
12
+ * Exemptions:
13
+ * - type: documentation only
14
+ * - code_paths containing only documentation/config files (no code files)
15
+ * - WU-2273: notes containing tdd-exception: marker (explicit opt-out)
16
+ *
17
+ * @see {@link packages/@lumenflow/cli/src/lib/wu-done-validators.ts} - Integration point
18
+ * @see {@link config.directories.completeGuidePath} - TDD requirements
19
+ */
20
+ import path from 'node:path';
21
+ import { TEST_TYPES, WU_TYPES } from '../constants/wu-statuses.js';
22
+ import { isDocumentationType } from '../constants/wu-type-helpers.js';
23
+ /**
24
+ * Code file extensions that require automated tests.
25
+ * @constant {string[]}
26
+ */
27
+ const CODE_EXTENSIONS = Object.freeze(['.js', '.ts', '.tsx', '.ts']);
28
+ /**
29
+ * Non-code file extensions (documentation, data, config).
30
+ * @constant {string[]}
31
+ */
32
+ const NON_CODE_EXTENSIONS = Object.freeze(['.md', '.yaml', '.yml', '.json']);
33
+ /**
34
+ * Patterns that indicate a config file (even with code extensions).
35
+ * @constant {RegExp[]}
36
+ */
37
+ const CONFIG_PATTERNS = Object.freeze([
38
+ /config\./i, // vitest.config.ts, eslint.config.js
39
+ /\.config\./i, // *.config.ts, *.config.js
40
+ /rc\.[jt]s$/i, // .eslintrc.js, .prettierrc.ts
41
+ /^\.[a-z]+rc\./i, // .eslintrc.*, .prettierrc.*
42
+ ]);
43
+ /**
44
+ * Path prefixes for hex core code requiring automated tests.
45
+ * These are the critical application layer paths.
46
+ *
47
+ * WU-1068: Changed from @exampleapp to @lumenflow for framework reusability.
48
+ * Project-specific patterns should be configured in workspace.yaml.
49
+ *
50
+ * @constant {string[]}
51
+ */
52
+ export const HEX_CORE_CODE_PATTERNS = Object.freeze([
53
+ 'packages/@lumenflow/core/',
54
+ 'packages/@lumenflow/cli/',
55
+ 'packages/@lumenflow/agent/',
56
+ ]);
57
+ /**
58
+ * @deprecated Lane-based exemptions removed in WU-2332.
59
+ * Test requirements are now based on file types, not lanes.
60
+ * Kept for backward compatibility but no longer used.
61
+ * @constant {string[]}
62
+ */
63
+ export const EXEMPT_LANES = Object.freeze([]);
64
+ /**
65
+ * WU types exempt from automated test requirement.
66
+ * Only 'documentation' type is exempt - actual code changes require automated tests.
67
+ *
68
+ * @constant {string[]}
69
+ */
70
+ export const EXEMPT_TYPES = Object.freeze([WU_TYPES.DOCUMENTATION]);
71
+ /**
72
+ * Marker prefix in WU notes that explicitly opts out of automated test requirement.
73
+ * Must match the marker used by wu:prep (packages/@lumenflow/cli/src/wu-prep.ts).
74
+ *
75
+ * WU-2273: Ensures wu:done and wu:prep agree on tdd-exception behavior.
76
+ *
77
+ * @constant {string}
78
+ */
79
+ const TDD_EXCEPTION_MARKER = 'tdd-exception:';
80
+ /**
81
+ * Determine if a file path represents a code file requiring automated tests.
82
+ *
83
+ * Code files are those with extensions like .ts, .ts, .tsx, .js
84
+ * EXCEPT config files (vitest.config.ts, .eslintrc.js, etc.)
85
+ *
86
+ * @param {string} filePath - File path to check
87
+ * @returns {boolean} True if the file is a code file requiring tests
88
+ */
89
+ export function isCodeFile(filePath) {
90
+ if (!filePath || typeof filePath !== 'string') {
91
+ return false;
92
+ }
93
+ // Get the filename from the path
94
+ const fileName = path.basename(filePath);
95
+ const ext = path.extname(filePath).toLowerCase();
96
+ // Check if it's a non-code extension (docs, data)
97
+ if (NON_CODE_EXTENSIONS.includes(ext)) {
98
+ return false;
99
+ }
100
+ // Check if it's a code extension
101
+ if (!CODE_EXTENSIONS.includes(ext)) {
102
+ return false;
103
+ }
104
+ // Check if it's a config file (even with code extension)
105
+ const isConfig = CONFIG_PATTERNS.some((pattern) => pattern.test(fileName));
106
+ if (isConfig) {
107
+ return false;
108
+ }
109
+ return true;
110
+ }
111
+ /**
112
+ * Check if code_paths contains any hex core code.
113
+ *
114
+ * @param {string[]|null|undefined} codePaths - Array of file paths from WU YAML
115
+ * @returns {boolean} True if any path is in hex core layer
116
+ */
117
+ export function containsHexCoreCode(codePaths) {
118
+ if (!codePaths || !Array.isArray(codePaths) || codePaths.length === 0) {
119
+ return false;
120
+ }
121
+ return codePaths.some((path) => {
122
+ if (!path || typeof path !== 'string')
123
+ return false;
124
+ return HEX_CORE_CODE_PATTERNS.some((pattern) => path.startsWith(pattern));
125
+ });
126
+ }
127
+ /**
128
+ * Check if WU notes contain an explicit tdd-exception marker.
129
+ *
130
+ * Supports both string and array-of-strings notes formats.
131
+ * Uses case-insensitive matching consistent with wu:prep behavior.
132
+ *
133
+ * WU-2273: Extracted to share logic with isExemptFromAutomatedTests.
134
+ *
135
+ * @param {unknown} notes - WU notes field (string, string[], or other)
136
+ * @returns {boolean} True if notes contain the tdd-exception marker
137
+ */
138
+ function hasDocumentedTddException(notes) {
139
+ if (typeof notes === 'string') {
140
+ return notes.toLowerCase().includes(TDD_EXCEPTION_MARKER);
141
+ }
142
+ if (Array.isArray(notes)) {
143
+ return notes.some((entry) => typeof entry === 'string' && hasDocumentedTddException(entry));
144
+ }
145
+ return false;
146
+ }
147
+ /**
148
+ * Check if a WU is exempt from automated test requirement.
149
+ *
150
+ * WU-2332: Only type: 'documentation' is exempt.
151
+ * WU-2273: Also exempt if notes contain tdd-exception: marker,
152
+ * aligning wu:done with wu:prep behavior.
153
+ *
154
+ * Lane-based exemptions have been removed - test requirements
155
+ * are now based on file types in code_paths.
156
+ *
157
+ * @param {object} doc - WU YAML document
158
+ * @returns {boolean} True if WU is exempt
159
+ */
160
+ export function isExemptFromAutomatedTests(doc) {
161
+ if (!doc)
162
+ return false;
163
+ // type: documentation is exempt
164
+ const type = doc.type || '';
165
+ if (isDocumentationType(type)) {
166
+ return true;
167
+ }
168
+ // WU-2273: tdd-exception: in notes is an explicit opt-out
169
+ if (hasDocumentedTddException(doc.notes)) {
170
+ return true;
171
+ }
172
+ return false;
173
+ }
174
+ /**
175
+ * Check if WU has at least one automated test.
176
+ *
177
+ * @param {object} tests - Tests object from WU YAML
178
+ * @returns {boolean} True if has at least one automated test
179
+ */
180
+ function hasAutomatedTest(tests) {
181
+ if (!tests || typeof tests !== 'object')
182
+ return false;
183
+ const hasItems = (arr) => Array.isArray(arr) && arr.length > 0;
184
+ const t = tests;
185
+ return (hasItems(t[TEST_TYPES.UNIT]) ||
186
+ hasItems(t[TEST_TYPES.E2E]) ||
187
+ hasItems(t[TEST_TYPES.INTEGRATION]));
188
+ }
189
+ /**
190
+ * Check if any code_paths contain actual code files (not docs/config).
191
+ *
192
+ * @param {string[]} codePaths - Array of file paths from WU YAML
193
+ * @returns {{ hasCodeFiles: boolean, codeFiles: string[] }} Result with list of code files
194
+ */
195
+ function analyzeCodePaths(codePaths) {
196
+ if (!codePaths || !Array.isArray(codePaths) || codePaths.length === 0) {
197
+ return { hasCodeFiles: false, codeFiles: [] };
198
+ }
199
+ const codeFiles = codePaths.filter((p) => isCodeFile(p));
200
+ return {
201
+ hasCodeFiles: codeFiles.length > 0,
202
+ codeFiles,
203
+ };
204
+ }
205
+ /**
206
+ * Validate automated test requirement for WU.
207
+ *
208
+ * WU-2332: Requirements are based on file types, not lanes.
209
+ * - WUs with ANY code files in code_paths must have automated tests
210
+ * - WUs with only docs/config files can use manual-only tests
211
+ * - type: 'documentation' is always exempt
212
+ *
213
+ * @param {object} doc - WU YAML document
214
+ * @returns {{ valid: boolean, errors: string[] }} Validation result
215
+ */
216
+ export function validateAutomatedTestRequirement(doc) {
217
+ const errors = [];
218
+ if (!doc) {
219
+ return { valid: true, errors: [] };
220
+ }
221
+ // Check if WU is exempt by type
222
+ if (isExemptFromAutomatedTests(doc)) {
223
+ return { valid: true, errors: [] };
224
+ }
225
+ // Analyze code_paths for actual code files
226
+ const codePaths = doc.code_paths || [];
227
+ const { hasCodeFiles, codeFiles } = analyzeCodePaths(codePaths);
228
+ // If no code files, manual tests are fine
229
+ if (!hasCodeFiles) {
230
+ return { valid: true, errors: [] };
231
+ }
232
+ // WU has code files - require automated tests
233
+ // Support both tests: (current) and test_paths: (legacy)
234
+ const tests = doc.tests || doc.test_paths || {};
235
+ if (!hasAutomatedTest(tests)) {
236
+ errors.push(`WU modifies code files but has no automated tests.\n` +
237
+ ` Code files: ${codeFiles.join(', ')}\n` +
238
+ ` Required: At least one automated test (unit, e2e, or integration)\n` +
239
+ ` Manual-only tests are not allowed for code changes.\n\n` +
240
+ ` Fix options:\n` +
241
+ ` 1. Add tests to tests.unit, tests.e2e, or tests.integration in WU YAML\n` +
242
+ ` 2. If this WU legitimately does not need automated tests (UI, config, templates),\n` +
243
+ ` add tdd-exception: <reason> to the WU notes field`);
244
+ return { valid: false, errors };
245
+ }
246
+ return { valid: true, errors: [] };
247
+ }
248
+ //# sourceMappingURL=manual-test-validator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manual-test-validator.js","sourceRoot":"","sources":["../../../src/methodology/manual-test-validator.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,127 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Coverage Gate for Quality Gates
4
+ *
5
+ * WU-1433: Adds coverage checking to gates with configurable mode (warn/block).
6
+ * Enforces ≥90% coverage on hex core files (application layer).
7
+ *
8
+ * Mode flag allows gradual rollout:
9
+ * - warn: Log failures but don't block (default)
10
+ * - block: Fail the gate if thresholds not met
11
+ *
12
+ * @see {@link packages/@lumenflow/cli/src/gates.ts} - Integration point
13
+ * @see {@link vitest.config.ts} - Coverage thresholds
14
+ */
15
+ /**
16
+ * Coverage gate modes
17
+ * @constant
18
+ */
19
+ export declare const COVERAGE_GATE_MODES: Readonly<{
20
+ /** Log warnings but don't fail the gate */
21
+ WARN: "warn";
22
+ /** Fail the gate if thresholds not met */
23
+ BLOCK: "block";
24
+ }>;
25
+ /**
26
+ * Glob patterns for hex core files that require ≥90% coverage.
27
+ * These are the critical application layer files.
28
+ *
29
+ * WU-1068: Changed from @exampleapp to @lumenflow for framework reusability.
30
+ * Project-specific patterns should be configured in workspace.yaml.
31
+ *
32
+ * @constant {string[]}
33
+ */
34
+ export declare const HEX_CORE_PATTERNS: readonly string[];
35
+ /**
36
+ * Default coverage threshold for hex core files (percentage)
37
+ * WU-1262: Kept for backwards compatibility.
38
+ * WU-2044: Now delegates to DEFAULT_MIN_COVERAGE from gate-constants.ts (DRY).
39
+ * The actual threshold is determined by resolveCoverageConfig() based on methodology.
40
+ * @constant {number}
41
+ */
42
+ export declare const COVERAGE_THRESHOLD = 90;
43
+ /**
44
+ * Default path to coverage summary JSON
45
+ * @constant {string}
46
+ */
47
+ export declare const DEFAULT_COVERAGE_PATH = "coverage/coverage-summary.json";
48
+ /**
49
+ * Check if a file path is in the hex core layer.
50
+ *
51
+ * WU-2448: Coverage reporters may emit absolute paths (e.g., <repo-root>/packages/...)
52
+ * or file:// URLs; use substring matching so hex-core checks still apply.
53
+ *
54
+ * @param {string|null|undefined} filePath - File path to check
55
+ * @returns {boolean} True if file is in hex core layer
56
+ */
57
+ export declare function isHexCoreFile(filePath: UnsafeAny): boolean;
58
+ /**
59
+ * Parse coverage JSON file.
60
+ *
61
+ * @param {string} coveragePath - Path to coverage-summary.json
62
+ * @returns {object|null} Parsed coverage data or null if invalid
63
+ */
64
+ export declare function parseCoverageJson(coveragePath: UnsafeAny): {
65
+ total: any;
66
+ files: Record<string, unknown>;
67
+ } | null;
68
+ /**
69
+ * Check if coverage meets thresholds for hex core files.
70
+ *
71
+ * @param {object|null} coverageData - Parsed coverage data
72
+ * @param {number} [threshold] - Coverage threshold to use (defaults to COVERAGE_THRESHOLD)
73
+ * @returns {{ pass: boolean, failures: Array<{ file: string, actual: number, threshold: number, metric: string }> }}
74
+ */
75
+ export declare function checkCoverageThresholds(coverageData: UnsafeAny, threshold?: number): {
76
+ pass: boolean;
77
+ failures: {
78
+ file: string;
79
+ actual: number;
80
+ threshold: number;
81
+ metric: string;
82
+ }[];
83
+ };
84
+ /**
85
+ * Format coverage data for display.
86
+ *
87
+ * @param {object|null} coverageData - Parsed coverage data
88
+ * @returns {string} Formatted output string
89
+ */
90
+ export declare function formatCoverageDelta(coverageData: UnsafeAny): string;
91
+ /**
92
+ * Logger interface for coverage gate output
93
+ */
94
+ interface CoverageGateLogger {
95
+ log: (...args: unknown[]) => void;
96
+ }
97
+ /**
98
+ * Options for running coverage gate
99
+ */
100
+ export interface CoverageGateOptions {
101
+ /** Gate mode ('warn' or 'block') */
102
+ mode?: string;
103
+ /** Path to coverage JSON */
104
+ coveragePath?: string;
105
+ /** Logger for output */
106
+ logger?: CoverageGateLogger;
107
+ /**
108
+ * WU-1262: Coverage threshold override (0-100).
109
+ * When provided, overrides the default COVERAGE_THRESHOLD constant.
110
+ * This is typically populated from resolveCoverageConfig().
111
+ */
112
+ threshold?: number;
113
+ }
114
+ /**
115
+ * Run coverage gate.
116
+ *
117
+ * @param {CoverageGateOptions} options - Gate options
118
+ * @returns {Promise<{ ok: boolean, mode: string, duration: number, message: string }>}
119
+ */
120
+ export declare function runCoverageGate(options?: CoverageGateOptions): Promise<{
121
+ ok: boolean;
122
+ mode: string;
123
+ duration: number;
124
+ message: string;
125
+ }>;
126
+ export {};
127
+ //# sourceMappingURL=coverage-gate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"coverage-gate.d.ts","sourceRoot":"","sources":["../../../src/policy/coverage-gate.ts"],"names":[],"mappings":";AAGA;;;;;;;;;;;;GAYG;AAOH;;;GAGG;AACH,eAAO,MAAM,mBAAmB;IAC9B,2CAA2C;;IAE3C,0CAA0C;;EAE1C,CAAC;AAEH;;;;;;;;GAQG;AACH,eAAO,MAAM,iBAAiB,mBAG5B,CAAC;AAEH;;;;;;GAMG;AACH,eAAO,MAAM,kBAAkB,KAAuB,CAAC;AAEvD;;;GAGG;AACH,eAAO,MAAM,qBAAqB,mCAAmC,CAAC;AAEtE;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,SAAS,WAQhD;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,YAAY,EAAE,SAAS;;;SAuBxD;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,YAAY,EAAE,SAAS,EAAE,SAAS,CAAC,EAAE,MAAM;;;;;;;;EA+BlF;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,YAAY,EAAE,SAAS,UA6B1D;AAED;;GAEG;AACH,UAAU,kBAAkB;IAC1B,GAAG,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;CACnC;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,oCAAoC;IACpC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,4BAA4B;IAC5B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,wBAAwB;IACxB,MAAM,CAAC,EAAE,kBAAkB,CAAC;IAC5B;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;GAKG;AACH,wBAAsB,eAAe,CAAC,OAAO,GAAE,mBAAwB;;;;;GAiDtE"}
@@ -0,0 +1,211 @@
1
+ #!/usr/bin/env node
2
+ // Copyright (c) 2026 Hellmai Ltd
3
+ // SPDX-License-Identifier: AGPL-3.0-only
4
+ /**
5
+ * Coverage Gate for Quality Gates
6
+ *
7
+ * WU-1433: Adds coverage checking to gates with configurable mode (warn/block).
8
+ * Enforces ≥90% coverage on hex core files (application layer).
9
+ *
10
+ * Mode flag allows gradual rollout:
11
+ * - warn: Log failures but don't block (default)
12
+ * - block: Fail the gate if thresholds not met
13
+ *
14
+ * @see {@link packages/@lumenflow/cli/src/gates.ts} - Integration point
15
+ * @see {@link vitest.config.ts} - Coverage thresholds
16
+ */
17
+ /* eslint-disable security/detect-non-literal-fs-filename, security/detect-object-injection */
18
+ import { readFileSync, existsSync } from 'node:fs';
19
+ import { EMOJI, STRING_LITERALS } from '../constants/wu-ui-constants.js';
20
+ import { DEFAULT_MIN_COVERAGE } from '../constants/gate-constants.js';
21
+ /**
22
+ * Coverage gate modes
23
+ * @constant
24
+ */
25
+ export const COVERAGE_GATE_MODES = Object.freeze({
26
+ /** Log warnings but don't fail the gate */
27
+ WARN: 'warn',
28
+ /** Fail the gate if thresholds not met */
29
+ BLOCK: 'block',
30
+ });
31
+ /**
32
+ * Glob patterns for hex core files that require ≥90% coverage.
33
+ * These are the critical application layer files.
34
+ *
35
+ * WU-1068: Changed from @exampleapp to @lumenflow for framework reusability.
36
+ * Project-specific patterns should be configured in workspace.yaml.
37
+ *
38
+ * @constant {string[]}
39
+ */
40
+ export const HEX_CORE_PATTERNS = Object.freeze([
41
+ 'packages/@lumenflow/core/',
42
+ 'packages/@lumenflow/cli/',
43
+ ]);
44
+ /**
45
+ * Default coverage threshold for hex core files (percentage)
46
+ * WU-1262: Kept for backwards compatibility.
47
+ * WU-2044: Now delegates to DEFAULT_MIN_COVERAGE from gate-constants.ts (DRY).
48
+ * The actual threshold is determined by resolveCoverageConfig() based on methodology.
49
+ * @constant {number}
50
+ */
51
+ export const COVERAGE_THRESHOLD = DEFAULT_MIN_COVERAGE;
52
+ /**
53
+ * Default path to coverage summary JSON
54
+ * @constant {string}
55
+ */
56
+ export const DEFAULT_COVERAGE_PATH = 'coverage/coverage-summary.json';
57
+ /**
58
+ * Check if a file path is in the hex core layer.
59
+ *
60
+ * WU-2448: Coverage reporters may emit absolute paths (e.g., <repo-root>/packages/...)
61
+ * or file:// URLs; use substring matching so hex-core checks still apply.
62
+ *
63
+ * @param {string|null|undefined} filePath - File path to check
64
+ * @returns {boolean} True if file is in hex core layer
65
+ */
66
+ export function isHexCoreFile(filePath) {
67
+ if (!filePath || typeof filePath !== 'string') {
68
+ return false;
69
+ }
70
+ // Normalize backslashes to forward slashes for cross-platform compatibility
71
+ const normalizedPath = filePath.replace(/\\/g, '/');
72
+ return HEX_CORE_PATTERNS.some((pattern) => normalizedPath.includes(pattern));
73
+ }
74
+ /**
75
+ * Parse coverage JSON file.
76
+ *
77
+ * @param {string} coveragePath - Path to coverage-summary.json
78
+ * @returns {object|null} Parsed coverage data or null if invalid
79
+ */
80
+ export function parseCoverageJson(coveragePath) {
81
+ if (!existsSync(coveragePath)) {
82
+ return null;
83
+ }
84
+ try {
85
+ const content = readFileSync(coveragePath, { encoding: 'utf-8' });
86
+ const data = JSON.parse(content);
87
+ // Transform to consistent format
88
+ const files = {};
89
+ for (const [key, value] of Object.entries(data)) {
90
+ if (key === 'total')
91
+ continue;
92
+ files[key] = value;
93
+ }
94
+ return {
95
+ total: data.total,
96
+ files,
97
+ };
98
+ }
99
+ catch {
100
+ return null;
101
+ }
102
+ }
103
+ /**
104
+ * Check if coverage meets thresholds for hex core files.
105
+ *
106
+ * @param {object|null} coverageData - Parsed coverage data
107
+ * @param {number} [threshold] - Coverage threshold to use (defaults to COVERAGE_THRESHOLD)
108
+ * @returns {{ pass: boolean, failures: Array<{ file: string, actual: number, threshold: number, metric: string }> }}
109
+ */
110
+ export function checkCoverageThresholds(coverageData, threshold) {
111
+ if (!coverageData || !coverageData.files) {
112
+ return { pass: true, failures: [] };
113
+ }
114
+ // WU-1262: Use provided threshold or fall back to constant
115
+ const effectiveThreshold = threshold ?? COVERAGE_THRESHOLD;
116
+ const failures = [];
117
+ for (const [file, metricsValue] of Object.entries(coverageData.files)) {
118
+ if (!isHexCoreFile(file)) {
119
+ continue;
120
+ }
121
+ // Check lines coverage (primary metric)
122
+ const metrics = metricsValue;
123
+ const linesCoverage = metrics.lines?.pct ?? 0;
124
+ if (linesCoverage < effectiveThreshold) {
125
+ failures.push({
126
+ file,
127
+ actual: linesCoverage,
128
+ threshold: effectiveThreshold,
129
+ metric: 'lines',
130
+ });
131
+ }
132
+ }
133
+ return {
134
+ pass: failures.length === 0,
135
+ failures,
136
+ };
137
+ }
138
+ /**
139
+ * Format coverage data for display.
140
+ *
141
+ * @param {object|null} coverageData - Parsed coverage data
142
+ * @returns {string} Formatted output string
143
+ */
144
+ export function formatCoverageDelta(coverageData) {
145
+ if (!coverageData) {
146
+ return '';
147
+ }
148
+ const lines = [];
149
+ const totalPct = coverageData.total?.lines?.pct ?? 0;
150
+ lines.push(`${STRING_LITERALS.NEWLINE}Coverage Summary: ${totalPct.toFixed(1)}% lines${STRING_LITERALS.NEWLINE}`);
151
+ // Show hex core files
152
+ const hexCoreFiles = Object.entries(coverageData.files || {}).filter(([file]) => isHexCoreFile(file));
153
+ if (hexCoreFiles.length > 0) {
154
+ lines.push('Hex Core Files:');
155
+ for (const [file, metricsValue] of hexCoreFiles) {
156
+ const metrics = metricsValue;
157
+ const pct = metrics.lines?.pct ?? 0;
158
+ const status = pct >= COVERAGE_THRESHOLD ? EMOJI.SUCCESS : EMOJI.FAILURE;
159
+ const shortFile = file.replace('packages/@lumenflow/', '');
160
+ lines.push(` ${status} ${shortFile}: ${pct.toFixed(1)}%`);
161
+ }
162
+ }
163
+ return lines.join(STRING_LITERALS.NEWLINE);
164
+ }
165
+ /**
166
+ * Run coverage gate.
167
+ *
168
+ * @param {CoverageGateOptions} options - Gate options
169
+ * @returns {Promise<{ ok: boolean, mode: string, duration: number, message: string }>}
170
+ */
171
+ export async function runCoverageGate(options = {}) {
172
+ const start = Date.now();
173
+ const mode = options.mode || COVERAGE_GATE_MODES.WARN;
174
+ const coveragePath = options.coveragePath || DEFAULT_COVERAGE_PATH;
175
+ const logger = options.logger && typeof options.logger.log === 'function' ? options.logger : console;
176
+ // WU-1262: Use provided threshold or default constant
177
+ const threshold = options.threshold ?? COVERAGE_THRESHOLD;
178
+ // Parse coverage data
179
+ const coverageData = parseCoverageJson(coveragePath);
180
+ if (!coverageData) {
181
+ const duration = Date.now() - start;
182
+ logger.log(`\n${EMOJI.WARNING} Coverage gate: No coverage data found at ${coveragePath}`);
183
+ logger.log(' Run tests with coverage first: pnpm test:coverage\n');
184
+ return { ok: true, mode, duration, message: 'No coverage data' };
185
+ }
186
+ // Check thresholds (WU-1262: pass resolved threshold)
187
+ const { pass, failures } = checkCoverageThresholds(coverageData, threshold);
188
+ // Format and display
189
+ const output = formatCoverageDelta(coverageData);
190
+ logger.log(output);
191
+ const duration = Date.now() - start;
192
+ if (!pass) {
193
+ logger.log(`\n${EMOJI.FAILURE} Coverage below ${threshold}% for hex core files:`);
194
+ for (const failure of failures) {
195
+ const shortFile = failure.file.replace('packages/@lumenflow/', '');
196
+ logger.log(` - ${shortFile}: ${failure.actual.toFixed(1)}% (requires ${failure.threshold}%)`);
197
+ }
198
+ if (mode === COVERAGE_GATE_MODES.BLOCK) {
199
+ logger.log(`\n${EMOJI.FAILURE} Coverage gate FAILED (mode: block)\n`);
200
+ return { ok: false, mode, duration, message: 'Coverage threshold not met' };
201
+ }
202
+ else {
203
+ logger.log(`\n${EMOJI.WARNING} Coverage gate WARNING (mode: warn)\n`);
204
+ logger.log(' Note: This will become blocking in future. Fix coverage now.\n');
205
+ return { ok: true, mode, duration, message: 'Coverage warning' };
206
+ }
207
+ }
208
+ logger.log(`\n${EMOJI.SUCCESS} Coverage gate passed\n`);
209
+ return { ok: true, mode, duration, message: 'Coverage OK' };
210
+ }
211
+ //# sourceMappingURL=coverage-gate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"coverage-gate.js","sourceRoot":"","sources":["../../../src/policy/coverage-gate.ts"],"names":[],"mappings":"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"}