@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.
- package/dist/manifest-schema.d.ts +12 -0
- package/dist/manifest-schema.d.ts.map +1 -1
- package/dist/manifest-schema.js +10 -0
- package/dist/manifest-schema.js.map +1 -1
- package/dist/manifest.d.ts +21 -0
- package/dist/manifest.d.ts.map +1 -1
- package/dist/manifest.js +92 -1
- package/dist/manifest.js.map +1 -1
- package/dist/src/commands/index.d.ts +2 -0
- package/dist/src/commands/index.d.ts.map +1 -0
- package/dist/src/commands/index.js +5 -0
- package/dist/src/commands/index.js.map +1 -0
- package/dist/src/config/delivery-review-contract.d.ts +17 -0
- package/dist/src/config/delivery-review-contract.d.ts.map +1 -0
- package/dist/src/config/delivery-review-contract.js +19 -0
- package/dist/src/config/delivery-review-contract.js.map +1 -0
- package/dist/src/config/env-accessors.d.ts +16 -0
- package/dist/src/config/env-accessors.d.ts.map +1 -0
- package/dist/src/config/env-accessors.js +18 -0
- package/dist/src/config/env-accessors.js.map +1 -0
- package/dist/src/config/index.d.ts +3 -0
- package/dist/src/config/index.d.ts.map +1 -0
- package/dist/src/config/index.js +8 -0
- package/dist/src/config/index.js.map +1 -0
- package/dist/src/config/normalize-config-keys.d.ts +16 -0
- package/dist/src/config/normalize-config-keys.d.ts.map +1 -0
- package/dist/src/config/normalize-config-keys.js +18 -0
- package/dist/src/config/normalize-config-keys.js.map +1 -0
- package/dist/src/config/schemas/lumenflow-config-schema-types.d.ts +190 -0
- package/dist/src/config/schemas/lumenflow-config-schema-types.d.ts.map +1 -0
- package/dist/src/config/schemas/lumenflow-config-schema-types.js +182 -0
- package/dist/src/config/schemas/lumenflow-config-schema-types.js.map +1 -0
- package/dist/src/config/schemas/lumenflow-config-schema.d.ts +190 -0
- package/dist/src/config/schemas/lumenflow-config-schema.d.ts.map +1 -0
- package/dist/src/config/schemas/lumenflow-config-schema.js +182 -0
- package/dist/src/config/schemas/lumenflow-config-schema.js.map +1 -0
- package/dist/src/config/workspace-reader.d.ts +56 -0
- package/dist/src/config/workspace-reader.d.ts.map +1 -0
- package/dist/src/config/workspace-reader.js +209 -0
- package/dist/src/config/workspace-reader.js.map +1 -0
- package/dist/src/constants/backlog-patterns.d.ts +21 -0
- package/dist/src/constants/backlog-patterns.d.ts.map +1 -0
- package/dist/src/constants/backlog-patterns.js +26 -0
- package/dist/src/constants/backlog-patterns.js.map +1 -0
- package/dist/src/constants/client-ids.d.ts +16 -0
- package/dist/src/constants/client-ids.d.ts.map +1 -0
- package/dist/src/constants/client-ids.js +16 -0
- package/dist/src/constants/client-ids.js.map +1 -0
- package/dist/src/constants/config-contract.d.ts +2 -0
- package/dist/src/constants/config-contract.d.ts.map +1 -0
- package/dist/src/constants/config-contract.js +7 -0
- package/dist/src/constants/config-contract.js.map +1 -0
- package/dist/src/constants/docs-layout-presets.d.ts +31 -0
- package/dist/src/constants/docs-layout-presets.d.ts.map +1 -0
- package/dist/src/constants/docs-layout-presets.js +41 -0
- package/dist/src/constants/docs-layout-presets.js.map +1 -0
- package/dist/src/constants/duration-constants.d.ts +11 -0
- package/dist/src/constants/duration-constants.d.ts.map +1 -0
- package/dist/src/constants/duration-constants.js +13 -0
- package/dist/src/constants/duration-constants.js.map +1 -0
- package/dist/src/constants/gate-constants.d.ts +24 -0
- package/dist/src/constants/gate-constants.d.ts.map +1 -0
- package/dist/src/constants/gate-constants.js +26 -0
- package/dist/src/constants/gate-constants.js.map +1 -0
- package/dist/src/constants/index.d.ts +18 -0
- package/dist/src/constants/index.d.ts.map +1 -0
- package/dist/src/constants/index.js +29 -0
- package/dist/src/constants/index.js.map +1 -0
- package/dist/src/constants/lock-constants.d.ts +29 -0
- package/dist/src/constants/lock-constants.d.ts.map +1 -0
- package/dist/src/constants/lock-constants.js +31 -0
- package/dist/src/constants/lock-constants.js.map +1 -0
- package/dist/src/constants/object-guards.d.ts +9 -0
- package/dist/src/constants/object-guards.d.ts.map +1 -0
- package/dist/src/constants/object-guards.js +11 -0
- package/dist/src/constants/object-guards.js.map +1 -0
- package/dist/src/constants/section-headings.d.ts +35 -0
- package/dist/src/constants/section-headings.d.ts.map +1 -0
- package/dist/src/constants/section-headings.js +82 -0
- package/dist/src/constants/section-headings.js.map +1 -0
- package/dist/src/constants/wu-cli-constants.d.ts +434 -0
- package/dist/src/constants/wu-cli-constants.d.ts.map +1 -0
- package/dist/src/constants/wu-cli-constants.js +439 -0
- package/dist/src/constants/wu-cli-constants.js.map +1 -0
- package/dist/src/constants/wu-domain-constants.d.ts +296 -0
- package/dist/src/constants/wu-domain-constants.d.ts.map +1 -0
- package/dist/src/constants/wu-domain-constants.js +400 -0
- package/dist/src/constants/wu-domain-constants.js.map +1 -0
- package/dist/src/constants/wu-git-constants.d.ts +2 -0
- package/dist/src/constants/wu-git-constants.d.ts.map +1 -0
- package/dist/src/constants/wu-git-constants.js +7 -0
- package/dist/src/constants/wu-git-constants.js.map +1 -0
- package/dist/src/constants/wu-id-format.d.ts +138 -0
- package/dist/src/constants/wu-id-format.d.ts.map +1 -0
- package/dist/src/constants/wu-id-format.js +265 -0
- package/dist/src/constants/wu-id-format.js.map +1 -0
- package/dist/src/constants/wu-paths-constants.d.ts +254 -0
- package/dist/src/constants/wu-paths-constants.d.ts.map +1 -0
- package/dist/src/constants/wu-paths-constants.js +276 -0
- package/dist/src/constants/wu-paths-constants.js.map +1 -0
- package/dist/src/constants/wu-statuses.d.ts +209 -0
- package/dist/src/constants/wu-statuses.d.ts.map +1 -0
- package/dist/src/constants/wu-statuses.js +245 -0
- package/dist/src/constants/wu-statuses.js.map +1 -0
- package/dist/src/constants/wu-type-helpers.d.ts +28 -0
- package/dist/src/constants/wu-type-helpers.d.ts.map +1 -0
- package/dist/src/constants/wu-type-helpers.js +49 -0
- package/dist/src/constants/wu-type-helpers.js.map +1 -0
- package/dist/src/constants/wu-ui-constants.d.ts +236 -0
- package/dist/src/constants/wu-ui-constants.d.ts.map +1 -0
- package/dist/src/constants/wu-ui-constants.js +238 -0
- package/dist/src/constants/wu-ui-constants.js.map +1 -0
- package/dist/src/constants/wu-validation-constants.d.ts +61 -0
- package/dist/src/constants/wu-validation-constants.d.ts.map +1 -0
- package/dist/src/constants/wu-validation-constants.js +69 -0
- package/dist/src/constants/wu-validation-constants.js.map +1 -0
- package/dist/src/domain/index.d.ts +4 -0
- package/dist/src/domain/index.d.ts.map +1 -0
- package/dist/src/domain/index.js +6 -0
- package/dist/src/domain/index.js.map +1 -0
- package/dist/src/domain/orchestration.constants.d.ts +111 -0
- package/dist/src/domain/orchestration.constants.d.ts.map +1 -0
- package/dist/src/domain/orchestration.constants.js +130 -0
- package/dist/src/domain/orchestration.constants.js.map +1 -0
- package/dist/src/domain/orchestration.schemas.d.ts +307 -0
- package/dist/src/domain/orchestration.schemas.d.ts.map +1 -0
- package/dist/src/domain/orchestration.schemas.js +214 -0
- package/dist/src/domain/orchestration.schemas.js.map +1 -0
- package/dist/src/domain/orchestration.types.d.ts +134 -0
- package/dist/src/domain/orchestration.types.d.ts.map +1 -0
- package/dist/src/domain/orchestration.types.js +5 -0
- package/dist/src/domain/orchestration.types.js.map +1 -0
- package/dist/src/methodology/incremental-test.d.ts +33 -0
- package/dist/src/methodology/incremental-test.d.ts.map +1 -0
- package/dist/src/methodology/incremental-test.js +73 -0
- package/dist/src/methodology/incremental-test.js.map +1 -0
- package/dist/src/methodology/index.d.ts +3 -0
- package/dist/src/methodology/index.d.ts.map +1 -0
- package/dist/src/methodology/index.js +6 -0
- package/dist/src/methodology/index.js.map +1 -0
- package/dist/src/methodology/manual-test-validator.d.ts +97 -0
- package/dist/src/methodology/manual-test-validator.d.ts.map +1 -0
- package/dist/src/methodology/manual-test-validator.js +248 -0
- package/dist/src/methodology/manual-test-validator.js.map +1 -0
- package/dist/src/policy/coverage-gate.d.ts +127 -0
- package/dist/src/policy/coverage-gate.d.ts.map +1 -0
- package/dist/src/policy/coverage-gate.js +211 -0
- package/dist/src/policy/coverage-gate.js.map +1 -0
- package/dist/src/policy/gates-agent-mode.d.ts +107 -0
- package/dist/src/policy/gates-agent-mode.d.ts.map +1 -0
- package/dist/src/policy/gates-agent-mode.js +138 -0
- package/dist/src/policy/gates-agent-mode.js.map +1 -0
- package/dist/src/policy/gates-config-internal.d.ts +54 -0
- package/dist/src/policy/gates-config-internal.d.ts.map +1 -0
- package/dist/src/policy/gates-config-internal.js +108 -0
- package/dist/src/policy/gates-config-internal.js.map +1 -0
- package/dist/src/policy/gates-config.d.ts +67 -0
- package/dist/src/policy/gates-config.d.ts.map +1 -0
- package/dist/src/policy/gates-config.js +193 -0
- package/dist/src/policy/gates-config.js.map +1 -0
- package/dist/src/policy/gates-coverage.d.ts +48 -0
- package/dist/src/policy/gates-coverage.d.ts.map +1 -0
- package/dist/src/policy/gates-coverage.js +182 -0
- package/dist/src/policy/gates-coverage.js.map +1 -0
- package/dist/src/policy/gates-presets.d.ts +51 -0
- package/dist/src/policy/gates-presets.d.ts.map +1 -0
- package/dist/src/policy/gates-presets.js +117 -0
- package/dist/src/policy/gates-presets.js.map +1 -0
- package/dist/src/policy/gates-schemas.d.ts +142 -0
- package/dist/src/policy/gates-schemas.d.ts.map +1 -0
- package/dist/src/policy/gates-schemas.js +67 -0
- package/dist/src/policy/gates-schemas.js.map +1 -0
- package/dist/src/policy/index.d.ts +19 -0
- package/dist/src/policy/index.d.ts.map +1 -0
- package/dist/src/policy/index.js +21 -0
- package/dist/src/policy/index.js.map +1 -0
- package/dist/src/policy/package-manager-resolver.d.ts +79 -0
- package/dist/src/policy/package-manager-resolver.d.ts.map +1 -0
- package/dist/src/policy/package-manager-resolver.js +245 -0
- package/dist/src/policy/package-manager-resolver.js.map +1 -0
- package/dist/src/policy/resolve-policy.d.ts +337 -0
- package/dist/src/policy/resolve-policy.d.ts.map +1 -0
- package/dist/src/policy/resolve-policy.js +353 -0
- package/dist/src/policy/resolve-policy.js.map +1 -0
- package/dist/src/ports/config.ports.d.ts +83 -0
- package/dist/src/ports/config.ports.d.ts.map +1 -0
- package/dist/src/ports/config.ports.js +4 -0
- package/dist/src/ports/config.ports.js.map +1 -0
- package/dist/src/ports/dashboard-renderer.port.d.ts +113 -0
- package/dist/src/ports/dashboard-renderer.port.d.ts.map +1 -0
- package/dist/src/ports/dashboard-renderer.port.js +4 -0
- package/dist/src/ports/dashboard-renderer.port.js.map +1 -0
- package/dist/src/ports/index.d.ts +5 -0
- package/dist/src/ports/index.d.ts.map +1 -0
- package/dist/src/ports/index.js +10 -0
- package/dist/src/ports/index.js.map +1 -0
- package/dist/src/ports/sync-validator.ports.d.ts +52 -0
- package/dist/src/ports/sync-validator.ports.d.ts.map +1 -0
- package/dist/src/ports/sync-validator.ports.js +4 -0
- package/dist/src/ports/sync-validator.ports.js.map +1 -0
- package/dist/src/ports/wu-helpers.ports.d.ts +157 -0
- package/dist/src/ports/wu-helpers.ports.d.ts.map +1 -0
- package/dist/src/ports/wu-helpers.ports.js +4 -0
- package/dist/src/ports/wu-helpers.ports.js.map +1 -0
- package/dist/src/ports/wu-state.ports.d.ts +209 -0
- package/dist/src/ports/wu-state.ports.d.ts.map +1 -0
- package/dist/src/ports/wu-state.ports.js +4 -0
- package/dist/src/ports/wu-state.ports.js.map +1 -0
- package/dist/src/primitives/index.d.ts +2 -0
- package/dist/src/primitives/index.d.ts.map +1 -0
- package/dist/src/primitives/index.js +5 -0
- package/dist/src/primitives/index.js.map +1 -0
- package/dist/src/runtime/index.d.ts +2 -0
- package/dist/src/runtime/index.d.ts.map +1 -0
- package/dist/src/runtime/index.js +6 -0
- package/dist/src/runtime/index.js.map +1 -0
- package/dist/src/runtime/work-classifier.d.ts +103 -0
- package/dist/src/runtime/work-classifier.d.ts.map +1 -0
- package/dist/src/runtime/work-classifier.js +427 -0
- package/dist/src/runtime/work-classifier.js.map +1 -0
- package/dist/src/sandbox/index.d.ts +6 -0
- package/dist/src/sandbox/index.d.ts.map +1 -0
- package/dist/src/sandbox/index.js +10 -0
- package/dist/src/sandbox/index.js.map +1 -0
- package/dist/src/sandbox/sandbox-allowlist.d.ts +16 -0
- package/dist/src/sandbox/sandbox-allowlist.d.ts.map +1 -0
- package/dist/src/sandbox/sandbox-allowlist.js +77 -0
- package/dist/src/sandbox/sandbox-allowlist.js.map +1 -0
- package/dist/src/sandbox/sandbox-backend-linux.d.ts +6 -0
- package/dist/src/sandbox/sandbox-backend-linux.d.ts.map +1 -0
- package/dist/src/sandbox/sandbox-backend-linux.js +67 -0
- package/dist/src/sandbox/sandbox-backend-linux.js.map +1 -0
- package/dist/src/sandbox/sandbox-backend-macos.d.ts +6 -0
- package/dist/src/sandbox/sandbox-backend-macos.d.ts.map +1 -0
- package/dist/src/sandbox/sandbox-backend-macos.js +112 -0
- package/dist/src/sandbox/sandbox-backend-macos.js.map +1 -0
- package/dist/src/sandbox/sandbox-backend-windows.d.ts +6 -0
- package/dist/src/sandbox/sandbox-backend-windows.d.ts.map +1 -0
- package/dist/src/sandbox/sandbox-backend-windows.js +30 -0
- package/dist/src/sandbox/sandbox-backend-windows.js.map +1 -0
- package/dist/src/sandbox/sandbox-profile.d.ts +58 -0
- package/dist/src/sandbox/sandbox-profile.d.ts.map +1 -0
- package/dist/src/sandbox/sandbox-profile.js +69 -0
- package/dist/src/sandbox/sandbox-profile.js.map +1 -0
- package/dist/src/schemas/index.d.ts +2 -0
- package/dist/src/schemas/index.d.ts.map +1 -0
- package/dist/src/schemas/index.js +5 -0
- package/dist/src/schemas/index.js.map +1 -0
- package/dist/src/state/date-utils.d.ts +66 -0
- package/dist/src/state/date-utils.d.ts.map +1 -0
- package/dist/src/state/date-utils.js +143 -0
- package/dist/src/state/date-utils.js.map +1 -0
- package/dist/src/state/index.d.ts +8 -0
- package/dist/src/state/index.d.ts.map +1 -0
- package/dist/src/state/index.js +15 -0
- package/dist/src/state/index.js.map +1 -0
- package/dist/src/state/state-machine.d.ts +14 -0
- package/dist/src/state/state-machine.d.ts.map +1 -0
- package/dist/src/state/state-machine.js +92 -0
- package/dist/src/state/state-machine.js.map +1 -0
- package/dist/src/state/wu-doc-types.d.ts +48 -0
- package/dist/src/state/wu-doc-types.d.ts.map +1 -0
- package/dist/src/state/wu-doc-types.js +4 -0
- package/dist/src/state/wu-doc-types.js.map +1 -0
- package/dist/src/state/wu-paths.d.ts +275 -0
- package/dist/src/state/wu-paths.d.ts.map +1 -0
- package/dist/src/state/wu-paths.js +335 -0
- package/dist/src/state/wu-paths.js.map +1 -0
- package/dist/src/state/wu-schema.d.ts +831 -0
- package/dist/src/state/wu-schema.d.ts.map +1 -0
- package/dist/src/state/wu-schema.js +934 -0
- package/dist/src/state/wu-schema.js.map +1 -0
- package/dist/src/state/wu-state-schema.d.ts +292 -0
- package/dist/src/state/wu-state-schema.d.ts.map +1 -0
- package/dist/src/state/wu-state-schema.js +215 -0
- package/dist/src/state/wu-state-schema.js.map +1 -0
- package/dist/src/state/wu-yaml.d.ts +113 -0
- package/dist/src/state/wu-yaml.d.ts.map +1 -0
- package/dist/src/state/wu-yaml.js +307 -0
- package/dist/src/state/wu-yaml.js.map +1 -0
- package/dist/tool-impl/wu-lifecycle-tools.d.ts +11 -0
- package/dist/tool-impl/wu-lifecycle-tools.d.ts.map +1 -1
- package/dist/tool-impl/wu-lifecycle-tools.js +17 -0
- package/dist/tool-impl/wu-lifecycle-tools.js.map +1 -1
- package/manifest.yaml +46 -0
- 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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"}
|