@lumenflow/core 1.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/LICENSE +190 -0
- package/README.md +119 -0
- package/dist/active-wu-detector.d.ts +33 -0
- package/dist/active-wu-detector.js +106 -0
- package/dist/adapters/filesystem-metrics.adapter.d.ts +108 -0
- package/dist/adapters/filesystem-metrics.adapter.js +519 -0
- package/dist/adapters/terminal-renderer.adapter.d.ts +106 -0
- package/dist/adapters/terminal-renderer.adapter.js +337 -0
- package/dist/arg-parser.d.ts +63 -0
- package/dist/arg-parser.js +560 -0
- package/dist/backlog-editor.d.ts +98 -0
- package/dist/backlog-editor.js +179 -0
- package/dist/backlog-generator.d.ts +111 -0
- package/dist/backlog-generator.js +381 -0
- package/dist/backlog-parser.d.ts +45 -0
- package/dist/backlog-parser.js +102 -0
- package/dist/backlog-sync-validator.d.ts +78 -0
- package/dist/backlog-sync-validator.js +294 -0
- package/dist/branch-drift.d.ts +34 -0
- package/dist/branch-drift.js +51 -0
- package/dist/cleanup-install-config.d.ts +33 -0
- package/dist/cleanup-install-config.js +37 -0
- package/dist/cleanup-lock.d.ts +139 -0
- package/dist/cleanup-lock.js +313 -0
- package/dist/code-path-validator.d.ts +146 -0
- package/dist/code-path-validator.js +537 -0
- package/dist/code-paths-overlap.d.ts +55 -0
- package/dist/code-paths-overlap.js +245 -0
- package/dist/commands-logger.d.ts +77 -0
- package/dist/commands-logger.js +254 -0
- package/dist/commit-message-utils.d.ts +25 -0
- package/dist/commit-message-utils.js +41 -0
- package/dist/compliance-parser.d.ts +150 -0
- package/dist/compliance-parser.js +507 -0
- package/dist/constants/backlog-patterns.d.ts +20 -0
- package/dist/constants/backlog-patterns.js +23 -0
- package/dist/constants/dora-constants.d.ts +49 -0
- package/dist/constants/dora-constants.js +53 -0
- package/dist/constants/gate-constants.d.ts +15 -0
- package/dist/constants/gate-constants.js +15 -0
- package/dist/constants/linter-constants.d.ts +16 -0
- package/dist/constants/linter-constants.js +16 -0
- package/dist/constants/tokenizer-constants.d.ts +15 -0
- package/dist/constants/tokenizer-constants.js +15 -0
- package/dist/core/scope-checker.d.ts +97 -0
- package/dist/core/scope-checker.js +163 -0
- package/dist/core/tool-runner.d.ts +161 -0
- package/dist/core/tool-runner.js +393 -0
- package/dist/core/tool.constants.d.ts +105 -0
- package/dist/core/tool.constants.js +101 -0
- package/dist/core/tool.schemas.d.ts +226 -0
- package/dist/core/tool.schemas.js +226 -0
- package/dist/core/worktree-guard.d.ts +130 -0
- package/dist/core/worktree-guard.js +242 -0
- package/dist/coverage-gate.d.ts +108 -0
- package/dist/coverage-gate.js +196 -0
- package/dist/date-utils.d.ts +75 -0
- package/dist/date-utils.js +140 -0
- package/dist/dependency-graph.d.ts +142 -0
- package/dist/dependency-graph.js +550 -0
- package/dist/dependency-guard.d.ts +54 -0
- package/dist/dependency-guard.js +142 -0
- package/dist/dependency-validator.d.ts +105 -0
- package/dist/dependency-validator.js +154 -0
- package/dist/docs-path-validator.d.ts +36 -0
- package/dist/docs-path-validator.js +95 -0
- package/dist/domain/orchestration.constants.d.ts +99 -0
- package/dist/domain/orchestration.constants.js +97 -0
- package/dist/domain/orchestration.schemas.d.ts +280 -0
- package/dist/domain/orchestration.schemas.js +211 -0
- package/dist/domain/orchestration.types.d.ts +133 -0
- package/dist/domain/orchestration.types.js +12 -0
- package/dist/error-handler.d.ts +116 -0
- package/dist/error-handler.js +136 -0
- package/dist/file-classifiers.d.ts +62 -0
- package/dist/file-classifiers.js +108 -0
- package/dist/gates-agent-mode.d.ts +81 -0
- package/dist/gates-agent-mode.js +94 -0
- package/dist/generate-traceability.d.ts +107 -0
- package/dist/generate-traceability.js +411 -0
- package/dist/git-adapter.d.ts +395 -0
- package/dist/git-adapter.js +649 -0
- package/dist/git-staged-validator.d.ts +32 -0
- package/dist/git-staged-validator.js +48 -0
- package/dist/hardcoded-strings.d.ts +61 -0
- package/dist/hardcoded-strings.js +270 -0
- package/dist/incremental-lint.d.ts +78 -0
- package/dist/incremental-lint.js +129 -0
- package/dist/incremental-test.d.ts +39 -0
- package/dist/incremental-test.js +61 -0
- package/dist/index.d.ts +42 -0
- package/dist/index.js +61 -0
- package/dist/invariants/check-automated-tests.d.ts +50 -0
- package/dist/invariants/check-automated-tests.js +166 -0
- package/dist/invariants-runner.d.ts +103 -0
- package/dist/invariants-runner.js +527 -0
- package/dist/lane-checker.d.ts +50 -0
- package/dist/lane-checker.js +319 -0
- package/dist/lane-inference.d.ts +39 -0
- package/dist/lane-inference.js +195 -0
- package/dist/lane-lock.d.ts +211 -0
- package/dist/lane-lock.js +474 -0
- package/dist/lane-validator.d.ts +48 -0
- package/dist/lane-validator.js +114 -0
- package/dist/logs-lib.d.ts +104 -0
- package/dist/logs-lib.js +207 -0
- package/dist/lumenflow-config-schema.d.ts +272 -0
- package/dist/lumenflow-config-schema.js +207 -0
- package/dist/lumenflow-config.d.ts +95 -0
- package/dist/lumenflow-config.js +236 -0
- package/dist/manual-test-validator.d.ts +80 -0
- package/dist/manual-test-validator.js +200 -0
- package/dist/merge-lock.d.ts +115 -0
- package/dist/merge-lock.js +251 -0
- package/dist/micro-worktree.d.ts +159 -0
- package/dist/micro-worktree.js +427 -0
- package/dist/migration-deployer.d.ts +69 -0
- package/dist/migration-deployer.js +151 -0
- package/dist/orchestration-advisory-loader.d.ts +28 -0
- package/dist/orchestration-advisory-loader.js +87 -0
- package/dist/orchestration-advisory.d.ts +58 -0
- package/dist/orchestration-advisory.js +94 -0
- package/dist/orchestration-di.d.ts +48 -0
- package/dist/orchestration-di.js +57 -0
- package/dist/orchestration-rules.d.ts +57 -0
- package/dist/orchestration-rules.js +201 -0
- package/dist/orphan-detector.d.ts +131 -0
- package/dist/orphan-detector.js +226 -0
- package/dist/path-classifiers.d.ts +57 -0
- package/dist/path-classifiers.js +93 -0
- package/dist/piped-command-detector.d.ts +34 -0
- package/dist/piped-command-detector.js +64 -0
- package/dist/ports/dashboard-renderer.port.d.ts +112 -0
- package/dist/ports/dashboard-renderer.port.js +25 -0
- package/dist/ports/metrics-collector.port.d.ts +132 -0
- package/dist/ports/metrics-collector.port.js +26 -0
- package/dist/process-detector.d.ts +84 -0
- package/dist/process-detector.js +172 -0
- package/dist/prompt-linter.d.ts +72 -0
- package/dist/prompt-linter.js +312 -0
- package/dist/prompt-monitor.d.ts +15 -0
- package/dist/prompt-monitor.js +205 -0
- package/dist/rebase-artifact-cleanup.d.ts +145 -0
- package/dist/rebase-artifact-cleanup.js +433 -0
- package/dist/retry-strategy.d.ts +189 -0
- package/dist/retry-strategy.js +283 -0
- package/dist/risk-detector.d.ts +108 -0
- package/dist/risk-detector.js +252 -0
- package/dist/rollback-utils.d.ts +76 -0
- package/dist/rollback-utils.js +104 -0
- package/dist/section-headings.d.ts +43 -0
- package/dist/section-headings.js +49 -0
- package/dist/spawn-escalation.d.ts +90 -0
- package/dist/spawn-escalation.js +253 -0
- package/dist/spawn-monitor.d.ts +229 -0
- package/dist/spawn-monitor.js +672 -0
- package/dist/spawn-recovery.d.ts +82 -0
- package/dist/spawn-recovery.js +298 -0
- package/dist/spawn-registry-schema.d.ts +98 -0
- package/dist/spawn-registry-schema.js +108 -0
- package/dist/spawn-registry-store.d.ts +146 -0
- package/dist/spawn-registry-store.js +273 -0
- package/dist/spawn-tree.d.ts +121 -0
- package/dist/spawn-tree.js +285 -0
- package/dist/stamp-status-validator.d.ts +84 -0
- package/dist/stamp-status-validator.js +134 -0
- package/dist/stamp-utils.d.ts +100 -0
- package/dist/stamp-utils.js +229 -0
- package/dist/state-machine.d.ts +26 -0
- package/dist/state-machine.js +83 -0
- package/dist/system-map-validator.d.ts +80 -0
- package/dist/system-map-validator.js +272 -0
- package/dist/telemetry.d.ts +80 -0
- package/dist/telemetry.js +213 -0
- package/dist/token-counter.d.ts +51 -0
- package/dist/token-counter.js +145 -0
- package/dist/usecases/get-dashboard-data.usecase.d.ts +52 -0
- package/dist/usecases/get-dashboard-data.usecase.js +61 -0
- package/dist/usecases/get-suggestions.usecase.d.ts +100 -0
- package/dist/usecases/get-suggestions.usecase.js +153 -0
- package/dist/user-normalizer.d.ts +41 -0
- package/dist/user-normalizer.js +141 -0
- package/dist/validators/phi-constants.d.ts +97 -0
- package/dist/validators/phi-constants.js +152 -0
- package/dist/validators/phi-scanner.d.ts +58 -0
- package/dist/validators/phi-scanner.js +215 -0
- package/dist/worktree-ownership.d.ts +50 -0
- package/dist/worktree-ownership.js +74 -0
- package/dist/worktree-scanner.d.ts +103 -0
- package/dist/worktree-scanner.js +168 -0
- package/dist/worktree-symlink.d.ts +99 -0
- package/dist/worktree-symlink.js +359 -0
- package/dist/wu-backlog-updater.d.ts +17 -0
- package/dist/wu-backlog-updater.js +37 -0
- package/dist/wu-checkpoint.d.ts +124 -0
- package/dist/wu-checkpoint.js +233 -0
- package/dist/wu-claim-helpers.d.ts +26 -0
- package/dist/wu-claim-helpers.js +63 -0
- package/dist/wu-claim-resume.d.ts +106 -0
- package/dist/wu-claim-resume.js +276 -0
- package/dist/wu-consistency-checker.d.ts +95 -0
- package/dist/wu-consistency-checker.js +567 -0
- package/dist/wu-constants.d.ts +1275 -0
- package/dist/wu-constants.js +1382 -0
- package/dist/wu-create-validators.d.ts +42 -0
- package/dist/wu-create-validators.js +93 -0
- package/dist/wu-done-branch-only.d.ts +63 -0
- package/dist/wu-done-branch-only.js +191 -0
- package/dist/wu-done-messages.d.ts +119 -0
- package/dist/wu-done-messages.js +185 -0
- package/dist/wu-done-pr.d.ts +72 -0
- package/dist/wu-done-pr.js +174 -0
- package/dist/wu-done-retry-helpers.d.ts +85 -0
- package/dist/wu-done-retry-helpers.js +172 -0
- package/dist/wu-done-ui.d.ts +37 -0
- package/dist/wu-done-ui.js +69 -0
- package/dist/wu-done-validators.d.ts +411 -0
- package/dist/wu-done-validators.js +1229 -0
- package/dist/wu-done-worktree.d.ts +182 -0
- package/dist/wu-done-worktree.js +1097 -0
- package/dist/wu-helpers.d.ts +128 -0
- package/dist/wu-helpers.js +248 -0
- package/dist/wu-lint.d.ts +70 -0
- package/dist/wu-lint.js +234 -0
- package/dist/wu-paths.d.ts +171 -0
- package/dist/wu-paths.js +178 -0
- package/dist/wu-preflight-validators.d.ts +86 -0
- package/dist/wu-preflight-validators.js +251 -0
- package/dist/wu-recovery.d.ts +138 -0
- package/dist/wu-recovery.js +341 -0
- package/dist/wu-repair-core.d.ts +131 -0
- package/dist/wu-repair-core.js +669 -0
- package/dist/wu-schema-normalization.d.ts +17 -0
- package/dist/wu-schema-normalization.js +82 -0
- package/dist/wu-schema.d.ts +793 -0
- package/dist/wu-schema.js +881 -0
- package/dist/wu-spawn-helpers.d.ts +121 -0
- package/dist/wu-spawn-helpers.js +271 -0
- package/dist/wu-spawn.d.ts +158 -0
- package/dist/wu-spawn.js +1306 -0
- package/dist/wu-state-schema.d.ts +213 -0
- package/dist/wu-state-schema.js +156 -0
- package/dist/wu-state-store.d.ts +264 -0
- package/dist/wu-state-store.js +691 -0
- package/dist/wu-status-transition.d.ts +63 -0
- package/dist/wu-status-transition.js +382 -0
- package/dist/wu-status-updater.d.ts +25 -0
- package/dist/wu-status-updater.js +116 -0
- package/dist/wu-transaction-collectors.d.ts +116 -0
- package/dist/wu-transaction-collectors.js +272 -0
- package/dist/wu-transaction.d.ts +170 -0
- package/dist/wu-transaction.js +273 -0
- package/dist/wu-validation-constants.d.ts +60 -0
- package/dist/wu-validation-constants.js +66 -0
- package/dist/wu-validation.d.ts +118 -0
- package/dist/wu-validation.js +243 -0
- package/dist/wu-validator.d.ts +62 -0
- package/dist/wu-validator.js +325 -0
- package/dist/wu-yaml-fixer.d.ts +97 -0
- package/dist/wu-yaml-fixer.js +264 -0
- package/dist/wu-yaml.d.ts +86 -0
- package/dist/wu-yaml.js +222 -0
- package/package.json +114 -0
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WU-1747: Retry Strategy Module
|
|
3
|
+
*
|
|
4
|
+
* Provides exponential backoff retry mechanism with configurable parameters
|
|
5
|
+
* for wu:done concurrent load resilience.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Configurable max attempts, base delay, multiplier
|
|
9
|
+
* - Exponential backoff with optional jitter
|
|
10
|
+
* - Presets for common scenarios (wu_done, recovery)
|
|
11
|
+
* - Callback hooks for retry events
|
|
12
|
+
* - Conditional retry based on error type
|
|
13
|
+
*
|
|
14
|
+
* @module retry-strategy
|
|
15
|
+
*/
|
|
16
|
+
import { LOG_PREFIX, EMOJI } from './wu-constants.js';
|
|
17
|
+
/**
|
|
18
|
+
* Error message patterns that are considered retryable for wu:done operations
|
|
19
|
+
* Exported for test consistency
|
|
20
|
+
*/
|
|
21
|
+
export const RETRYABLE_ERROR_PATTERNS = Object.freeze({
|
|
22
|
+
FAST_FORWARD: 'fast-forward',
|
|
23
|
+
NOT_POSSIBLE: 'not possible',
|
|
24
|
+
CANNOT_LOCK_REF: 'Cannot lock ref',
|
|
25
|
+
FETCH: 'fetch',
|
|
26
|
+
PUSH: 'push',
|
|
27
|
+
ETIMEDOUT: 'ETIMEDOUT',
|
|
28
|
+
ECONNRESET: 'ECONNRESET',
|
|
29
|
+
});
|
|
30
|
+
/**
|
|
31
|
+
* @typedef {Object} RetryConfig
|
|
32
|
+
* @property {number} maxAttempts - Maximum number of attempts (default: 5)
|
|
33
|
+
* @property {number} baseDelayMs - Base delay in milliseconds (default: 1000)
|
|
34
|
+
* @property {number} maxDelayMs - Maximum delay cap in milliseconds (default: 30000)
|
|
35
|
+
* @property {number} multiplier - Exponential multiplier (default: 2)
|
|
36
|
+
* @property {number} jitter - Jitter factor 0-1 (default: 0.1 for 10%)
|
|
37
|
+
* @property {function} [shouldRetry] - Optional function(error) => boolean to determine if error is retryable
|
|
38
|
+
* @property {function} [onRetry] - Optional callback(attempt, error, delay) before each retry
|
|
39
|
+
*/
|
|
40
|
+
/**
|
|
41
|
+
* Default retry configuration
|
|
42
|
+
* Balanced for typical wu:done operations
|
|
43
|
+
*/
|
|
44
|
+
export const DEFAULT_RETRY_CONFIG = Object.freeze({
|
|
45
|
+
maxAttempts: 5,
|
|
46
|
+
baseDelayMs: 1000,
|
|
47
|
+
maxDelayMs: 30000,
|
|
48
|
+
multiplier: 2,
|
|
49
|
+
jitter: 0.1,
|
|
50
|
+
shouldRetry: (_error) => true,
|
|
51
|
+
onRetry: null,
|
|
52
|
+
});
|
|
53
|
+
/**
|
|
54
|
+
* Pre-configured retry presets for common scenarios
|
|
55
|
+
*/
|
|
56
|
+
export const RETRY_PRESETS = Object.freeze({
|
|
57
|
+
/**
|
|
58
|
+
* Preset for wu:done merge operations
|
|
59
|
+
* Higher attempts and longer delays for handling concurrent load
|
|
60
|
+
*/
|
|
61
|
+
wu_done: Object.freeze({
|
|
62
|
+
maxAttempts: 6,
|
|
63
|
+
baseDelayMs: 2000,
|
|
64
|
+
maxDelayMs: 60000,
|
|
65
|
+
multiplier: 2,
|
|
66
|
+
jitter: 0.15, // 15% jitter to spread concurrent retries
|
|
67
|
+
shouldRetry: (error) => {
|
|
68
|
+
// Retry fast-forward failures and network errors using defined patterns
|
|
69
|
+
const message = error.message || '';
|
|
70
|
+
return Object.values(RETRYABLE_ERROR_PATTERNS).some((pattern) => message.includes(pattern));
|
|
71
|
+
},
|
|
72
|
+
onRetry: null,
|
|
73
|
+
}),
|
|
74
|
+
/**
|
|
75
|
+
* Preset for zombie state recovery
|
|
76
|
+
* More attempts with shorter delays
|
|
77
|
+
*/
|
|
78
|
+
recovery: Object.freeze({
|
|
79
|
+
maxAttempts: 4,
|
|
80
|
+
baseDelayMs: 500,
|
|
81
|
+
maxDelayMs: 10000,
|
|
82
|
+
multiplier: 2,
|
|
83
|
+
jitter: 0.1,
|
|
84
|
+
shouldRetry: () => true,
|
|
85
|
+
onRetry: null,
|
|
86
|
+
}),
|
|
87
|
+
/**
|
|
88
|
+
* Preset for quick operations (file I/O, local git)
|
|
89
|
+
* Fast retries for transient errors
|
|
90
|
+
*/
|
|
91
|
+
quick: Object.freeze({
|
|
92
|
+
maxAttempts: 3,
|
|
93
|
+
baseDelayMs: 100,
|
|
94
|
+
maxDelayMs: 2000,
|
|
95
|
+
multiplier: 2,
|
|
96
|
+
jitter: 0.05,
|
|
97
|
+
shouldRetry: () => true,
|
|
98
|
+
onRetry: null,
|
|
99
|
+
}),
|
|
100
|
+
});
|
|
101
|
+
/**
|
|
102
|
+
* Create a retry configuration by merging defaults with custom options
|
|
103
|
+
*
|
|
104
|
+
* @param {string|Object} [presetOrOptions] - Preset name or custom options
|
|
105
|
+
* @param {Object} [options] - Additional options to merge (when first arg is preset name)
|
|
106
|
+
* @returns {RetryConfig} Complete retry configuration
|
|
107
|
+
*
|
|
108
|
+
* @example
|
|
109
|
+
* // Use defaults
|
|
110
|
+
* const config = createRetryConfig();
|
|
111
|
+
*
|
|
112
|
+
* @example
|
|
113
|
+
* // Customize defaults
|
|
114
|
+
* const config = createRetryConfig({ maxAttempts: 10 });
|
|
115
|
+
*
|
|
116
|
+
* @example
|
|
117
|
+
* // Use preset
|
|
118
|
+
* const config = createRetryConfig('wu_done');
|
|
119
|
+
*
|
|
120
|
+
* @example
|
|
121
|
+
* // Customize preset
|
|
122
|
+
* const config = createRetryConfig('wu_done', { maxAttempts: 10 });
|
|
123
|
+
*/
|
|
124
|
+
export function createRetryConfig(presetOrOptions, options) {
|
|
125
|
+
// Determine base config
|
|
126
|
+
let baseConfig;
|
|
127
|
+
let customOptions;
|
|
128
|
+
if (typeof presetOrOptions === 'string') {
|
|
129
|
+
// First arg is preset name
|
|
130
|
+
if (!(presetOrOptions in RETRY_PRESETS)) {
|
|
131
|
+
throw new Error(`Unknown retry preset: ${presetOrOptions}`);
|
|
132
|
+
}
|
|
133
|
+
baseConfig = RETRY_PRESETS[presetOrOptions];
|
|
134
|
+
customOptions = options || {};
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
// First arg is options (or undefined)
|
|
138
|
+
baseConfig = DEFAULT_RETRY_CONFIG;
|
|
139
|
+
customOptions = presetOrOptions || {};
|
|
140
|
+
}
|
|
141
|
+
// Merge and return
|
|
142
|
+
// Avoid overriding preset/default values with `undefined` (WU-1756):
|
|
143
|
+
// callers often pass option keys conditionally, and spreading `undefined`
|
|
144
|
+
// clobbers required defaults (e.g., maxAttempts) leading to zero-attempt retries.
|
|
145
|
+
const definedOptions = Object.fromEntries(Object.entries(customOptions).filter(([, value]) => value !== undefined));
|
|
146
|
+
return {
|
|
147
|
+
...baseConfig,
|
|
148
|
+
...definedOptions,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Calculate the backoff delay for a given attempt number
|
|
153
|
+
*
|
|
154
|
+
* Uses exponential backoff formula: baseDelay * (multiplier ^ attempt)
|
|
155
|
+
* with optional jitter to spread concurrent retry attempts
|
|
156
|
+
*
|
|
157
|
+
* @param {number} attempt - Zero-based attempt number (0 = first attempt)
|
|
158
|
+
* @param {RetryConfig} config - Retry configuration
|
|
159
|
+
* @returns {number} Delay in milliseconds
|
|
160
|
+
*/
|
|
161
|
+
export function calculateBackoffDelay(attempt, config) {
|
|
162
|
+
const { baseDelayMs, multiplier, maxDelayMs, jitter } = config;
|
|
163
|
+
// Exponential backoff: base * multiplier^attempt
|
|
164
|
+
let delay = baseDelayMs * Math.pow(multiplier, attempt);
|
|
165
|
+
// Cap at max delay
|
|
166
|
+
delay = Math.min(delay, maxDelayMs);
|
|
167
|
+
// Add jitter if enabled (spreads concurrent retries)
|
|
168
|
+
if (jitter > 0) {
|
|
169
|
+
// Jitter adds/subtracts random percentage
|
|
170
|
+
const jitterRange = delay * jitter;
|
|
171
|
+
const jitterOffset = (Math.random() * 2 - 1) * jitterRange;
|
|
172
|
+
delay = delay + jitterOffset;
|
|
173
|
+
}
|
|
174
|
+
return Math.round(delay);
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Sleep for specified milliseconds
|
|
178
|
+
*
|
|
179
|
+
* @param {number} ms - Milliseconds to sleep
|
|
180
|
+
* @returns {Promise<void>}
|
|
181
|
+
*/
|
|
182
|
+
function sleep(ms) {
|
|
183
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Execute a function with retry logic and exponential backoff
|
|
187
|
+
*
|
|
188
|
+
* @template T
|
|
189
|
+
* @param {function(): Promise<T>} fn - Async function to execute
|
|
190
|
+
* @param {RetryConfig} [config] - Retry configuration (uses defaults if not provided)
|
|
191
|
+
* @returns {Promise<T>} Result of successful execution
|
|
192
|
+
* @throws {Error} Last error if all attempts fail
|
|
193
|
+
*
|
|
194
|
+
* @example
|
|
195
|
+
* const result = await withRetry(
|
|
196
|
+
* async () => await mergeBranch(),
|
|
197
|
+
* createRetryConfig('wu_done')
|
|
198
|
+
* );
|
|
199
|
+
*/
|
|
200
|
+
export async function withRetry(fn, config = DEFAULT_RETRY_CONFIG) {
|
|
201
|
+
const { maxAttempts, shouldRetry, onRetry } = config;
|
|
202
|
+
let lastError;
|
|
203
|
+
let attempt = 0;
|
|
204
|
+
while (attempt < maxAttempts) {
|
|
205
|
+
try {
|
|
206
|
+
return await fn();
|
|
207
|
+
}
|
|
208
|
+
catch (error) {
|
|
209
|
+
lastError = error;
|
|
210
|
+
attempt++;
|
|
211
|
+
// Check if we should retry
|
|
212
|
+
if (attempt >= maxAttempts) {
|
|
213
|
+
break; // Max attempts reached
|
|
214
|
+
}
|
|
215
|
+
if (!shouldRetry(error)) {
|
|
216
|
+
break; // Error not retryable
|
|
217
|
+
}
|
|
218
|
+
// Calculate delay for next retry
|
|
219
|
+
const delay = calculateBackoffDelay(attempt - 1, config);
|
|
220
|
+
// Call onRetry callback if provided
|
|
221
|
+
if (typeof onRetry === 'function') {
|
|
222
|
+
onRetry(attempt, error, delay);
|
|
223
|
+
}
|
|
224
|
+
// Log retry info
|
|
225
|
+
console.log(`${LOG_PREFIX.DONE} ${EMOJI.WARNING} Attempt ${attempt}/${maxAttempts} failed: ${error.message}`);
|
|
226
|
+
console.log(`${LOG_PREFIX.DONE} ${EMOJI.INFO} Retrying in ${delay}ms...`);
|
|
227
|
+
// Wait before retry
|
|
228
|
+
await sleep(delay);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
// All attempts failed
|
|
232
|
+
// Defensive: if a caller passes an invalid config, ensure we throw a useful error.
|
|
233
|
+
if (!lastError) {
|
|
234
|
+
throw new Error(`Operation failed: invalid retry configuration (maxAttempts=${maxAttempts})`);
|
|
235
|
+
}
|
|
236
|
+
throw new Error(`Operation failed after ${attempt} attempt(s): ${lastError.message}\n` +
|
|
237
|
+
`Original error: ${lastError.stack || lastError.message}`);
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Higher-order function to wrap a function with retry logic
|
|
241
|
+
*
|
|
242
|
+
* @template T
|
|
243
|
+
* @param {function(...args): Promise<T>} fn - Function to wrap
|
|
244
|
+
* @param {RetryConfig} [config] - Retry configuration
|
|
245
|
+
* @returns {function(...args): Promise<T>} Wrapped function with retry logic
|
|
246
|
+
*
|
|
247
|
+
* @example
|
|
248
|
+
* const retryableMerge = withRetryWrapper(mergeBranch, createRetryConfig('wu_done'));
|
|
249
|
+
* await retryableMerge(branch);
|
|
250
|
+
*/
|
|
251
|
+
export function withRetryWrapper(fn, config = DEFAULT_RETRY_CONFIG) {
|
|
252
|
+
return async (...args) => {
|
|
253
|
+
return withRetry(() => fn(...args), config);
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Determine if an error is a git conflict error (non-retryable)
|
|
258
|
+
*
|
|
259
|
+
* @param {Error} error - Error to check
|
|
260
|
+
* @returns {boolean} True if conflict error
|
|
261
|
+
*/
|
|
262
|
+
export function isConflictError(error) {
|
|
263
|
+
const message = error.message || '';
|
|
264
|
+
return (message.includes('conflict') ||
|
|
265
|
+
message.includes('CONFLICT') ||
|
|
266
|
+
message.includes('<<<<<<<') ||
|
|
267
|
+
message.includes('not possible to fast-forward'));
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Determine if an error is a network/transient error (retryable)
|
|
271
|
+
*
|
|
272
|
+
* @param {Error} error - Error to check
|
|
273
|
+
* @returns {boolean} True if likely transient
|
|
274
|
+
*/
|
|
275
|
+
export function isTransientError(error) {
|
|
276
|
+
const message = error.message || '';
|
|
277
|
+
return (message.includes('ETIMEDOUT') ||
|
|
278
|
+
message.includes('ECONNRESET') ||
|
|
279
|
+
message.includes('ECONNREFUSED') ||
|
|
280
|
+
message.includes('fetch failed') ||
|
|
281
|
+
message.includes('network') ||
|
|
282
|
+
message.includes('timeout'));
|
|
283
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Risk Detector
|
|
4
|
+
*
|
|
5
|
+
* WU-2062: Implement tiered test execution for faster wu:done
|
|
6
|
+
*
|
|
7
|
+
* Detects risk tier based on changed files to enable tiered test execution:
|
|
8
|
+
* - docs-only: Skip tests, run lint/typecheck only
|
|
9
|
+
* - standard: Run changed tests
|
|
10
|
+
* - high-risk: Run integration tests in addition to unit tests
|
|
11
|
+
*
|
|
12
|
+
* Safety-critical tests (red-flag, PHI, escalation) always run regardless of tier.
|
|
13
|
+
*
|
|
14
|
+
* Note: This is project-specific risk detection logic for PatientPath's
|
|
15
|
+
* healthcare domain (PHI, RLS, auth). No standard library exists for this
|
|
16
|
+
* domain-specific classification.
|
|
17
|
+
*
|
|
18
|
+
* @see {@link tools/gates.mjs} - Consumer of risk detection
|
|
19
|
+
* @see {@link tools/lib/file-classifiers.mjs} - File classification utilities
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* Risk tier constants
|
|
23
|
+
* @readonly
|
|
24
|
+
* @enum {string}
|
|
25
|
+
*/
|
|
26
|
+
export declare const RISK_TIERS: Readonly<{
|
|
27
|
+
/** Documentation-only changes - skip tests, run lint/typecheck only */
|
|
28
|
+
DOCS_ONLY: "docs-only";
|
|
29
|
+
/** Standard code changes - run incremental tests */
|
|
30
|
+
STANDARD: "standard";
|
|
31
|
+
/** Safety-critical tests need to run - always include safety test patterns */
|
|
32
|
+
SAFETY_CRITICAL: "safety-critical";
|
|
33
|
+
/** High-risk changes (auth, PHI, RLS, migrations) - run integration tests */
|
|
34
|
+
HIGH_RISK: "high-risk";
|
|
35
|
+
}>;
|
|
36
|
+
/**
|
|
37
|
+
* Test patterns that should ALWAYS run regardless of which files changed.
|
|
38
|
+
* These are safety-critical tests that verify red-flag detection, PHI protection,
|
|
39
|
+
* escalation triggers, and constitutional enforcement.
|
|
40
|
+
*
|
|
41
|
+
* @type {string[]}
|
|
42
|
+
*/
|
|
43
|
+
export declare const SAFETY_CRITICAL_TEST_PATTERNS: readonly string[];
|
|
44
|
+
/**
|
|
45
|
+
* Path patterns that indicate high-risk code changes.
|
|
46
|
+
* Changes to these paths should trigger integration tests.
|
|
47
|
+
*
|
|
48
|
+
* WU-2242: Added authentication/ pattern
|
|
49
|
+
*
|
|
50
|
+
* @type {string[]}
|
|
51
|
+
*/
|
|
52
|
+
export declare const HIGH_RISK_PATH_PATTERNS: readonly string[];
|
|
53
|
+
/**
|
|
54
|
+
* Check if a test file path matches safety-critical patterns.
|
|
55
|
+
*
|
|
56
|
+
* @param {string} testPath - Path to test file
|
|
57
|
+
* @returns {boolean} True if test is safety-critical
|
|
58
|
+
*/
|
|
59
|
+
export declare function isSafetyCriticalTest(testPath: any): boolean;
|
|
60
|
+
/**
|
|
61
|
+
* Check if a migration file contains RLS or policy changes.
|
|
62
|
+
*
|
|
63
|
+
* @param {string} filePath - Migration file path
|
|
64
|
+
* @returns {boolean} True if migration is high-risk
|
|
65
|
+
*/
|
|
66
|
+
export declare function isHighRiskMigration(filePath: any): boolean;
|
|
67
|
+
/**
|
|
68
|
+
* Check if a file path represents a high-risk code change.
|
|
69
|
+
*
|
|
70
|
+
* @param {string} filePath - Path to check
|
|
71
|
+
* @returns {boolean} True if path is high-risk
|
|
72
|
+
*/
|
|
73
|
+
export declare function isHighRiskPath(filePath: any): boolean;
|
|
74
|
+
/**
|
|
75
|
+
* Detect the risk tier for a set of changed files.
|
|
76
|
+
*
|
|
77
|
+
* Returns a result object containing:
|
|
78
|
+
* - tier: The detected risk tier
|
|
79
|
+
* - safetyCriticalPatterns: Patterns to filter safety-critical tests
|
|
80
|
+
* - highRiskPaths: List of high-risk paths found in changes
|
|
81
|
+
* - isDocsOnly: Whether this is a docs-only change
|
|
82
|
+
* - shouldRunIntegration: Whether integration tests should run
|
|
83
|
+
*
|
|
84
|
+
* @param {object} options - Detection options
|
|
85
|
+
* @param {string[]} [options.changedFiles=[]] - List of changed file paths
|
|
86
|
+
* @returns {object} Risk detection result
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* const result = detectRiskTier({ changedFiles: ['src/lib/auth/getUser.ts'] });
|
|
90
|
+
* // result.tier === 'high-risk'
|
|
91
|
+
* // result.shouldRunIntegration === true
|
|
92
|
+
*
|
|
93
|
+
* @example
|
|
94
|
+
* const result = detectRiskTier({ changedFiles: ['docs/guide.md'] });
|
|
95
|
+
* // result.tier === 'docs-only'
|
|
96
|
+
* // result.isDocsOnly === true
|
|
97
|
+
*/
|
|
98
|
+
export interface DetectRiskTierOptions {
|
|
99
|
+
/** Array of changed file paths to analyze */
|
|
100
|
+
changedFiles?: string[];
|
|
101
|
+
}
|
|
102
|
+
export declare function detectRiskTier(options?: DetectRiskTierOptions): {
|
|
103
|
+
tier: any;
|
|
104
|
+
safetyCriticalPatterns: string[];
|
|
105
|
+
highRiskPaths: any;
|
|
106
|
+
isDocsOnly: any;
|
|
107
|
+
shouldRunIntegration: boolean;
|
|
108
|
+
};
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Risk Detector
|
|
4
|
+
*
|
|
5
|
+
* WU-2062: Implement tiered test execution for faster wu:done
|
|
6
|
+
*
|
|
7
|
+
* Detects risk tier based on changed files to enable tiered test execution:
|
|
8
|
+
* - docs-only: Skip tests, run lint/typecheck only
|
|
9
|
+
* - standard: Run changed tests
|
|
10
|
+
* - high-risk: Run integration tests in addition to unit tests
|
|
11
|
+
*
|
|
12
|
+
* Safety-critical tests (red-flag, PHI, escalation) always run regardless of tier.
|
|
13
|
+
*
|
|
14
|
+
* Note: This is project-specific risk detection logic for PatientPath's
|
|
15
|
+
* healthcare domain (PHI, RLS, auth). No standard library exists for this
|
|
16
|
+
* domain-specific classification.
|
|
17
|
+
*
|
|
18
|
+
* @see {@link tools/gates.mjs} - Consumer of risk detection
|
|
19
|
+
* @see {@link tools/lib/file-classifiers.mjs} - File classification utilities
|
|
20
|
+
*/
|
|
21
|
+
import path from 'node:path';
|
|
22
|
+
import { isDocumentationPath } from './file-classifiers.js';
|
|
23
|
+
/**
|
|
24
|
+
* Risk tier constants
|
|
25
|
+
* @readonly
|
|
26
|
+
* @enum {string}
|
|
27
|
+
*/
|
|
28
|
+
export const RISK_TIERS = Object.freeze({
|
|
29
|
+
/** Documentation-only changes - skip tests, run lint/typecheck only */
|
|
30
|
+
DOCS_ONLY: 'docs-only',
|
|
31
|
+
/** Standard code changes - run incremental tests */
|
|
32
|
+
STANDARD: 'standard',
|
|
33
|
+
/** Safety-critical tests need to run - always include safety test patterns */
|
|
34
|
+
SAFETY_CRITICAL: 'safety-critical',
|
|
35
|
+
/** High-risk changes (auth, PHI, RLS, migrations) - run integration tests */
|
|
36
|
+
HIGH_RISK: 'high-risk',
|
|
37
|
+
});
|
|
38
|
+
/**
|
|
39
|
+
* Test patterns that should ALWAYS run regardless of which files changed.
|
|
40
|
+
* These are safety-critical tests that verify red-flag detection, PHI protection,
|
|
41
|
+
* escalation triggers, and constitutional enforcement.
|
|
42
|
+
*
|
|
43
|
+
* @type {string[]}
|
|
44
|
+
*/
|
|
45
|
+
export const SAFETY_CRITICAL_TEST_PATTERNS = Object.freeze([
|
|
46
|
+
// Red-flag detection tests
|
|
47
|
+
'red-flag',
|
|
48
|
+
'redflag',
|
|
49
|
+
'RedFlag',
|
|
50
|
+
// PHI protection tests
|
|
51
|
+
'phi',
|
|
52
|
+
'PHI',
|
|
53
|
+
'PHIGuard',
|
|
54
|
+
// Escalation trigger tests
|
|
55
|
+
'escalation',
|
|
56
|
+
'Escalation',
|
|
57
|
+
// Privacy detection tests
|
|
58
|
+
'privacy',
|
|
59
|
+
'Privacy',
|
|
60
|
+
'privacyDetector',
|
|
61
|
+
// Constitutional enforcement tests
|
|
62
|
+
'constitutional',
|
|
63
|
+
'Constitutional',
|
|
64
|
+
// Safe prompt wrapper tests
|
|
65
|
+
'safePrompt',
|
|
66
|
+
'SafePrompt',
|
|
67
|
+
// Crisis/emergency handling tests
|
|
68
|
+
'crisis',
|
|
69
|
+
'Crisis',
|
|
70
|
+
// Policy enforcement tests
|
|
71
|
+
'policyReferee',
|
|
72
|
+
'PolicyReferee',
|
|
73
|
+
]);
|
|
74
|
+
/**
|
|
75
|
+
* Path patterns that indicate high-risk code changes.
|
|
76
|
+
* Changes to these paths should trigger integration tests.
|
|
77
|
+
*
|
|
78
|
+
* WU-2242: Added authentication/ pattern
|
|
79
|
+
*
|
|
80
|
+
* @type {string[]}
|
|
81
|
+
*/
|
|
82
|
+
// constants: HIGH_RISK_PATH_PATTERNS (skip hardcoded string detection)
|
|
83
|
+
export const HIGH_RISK_PATH_PATTERNS = Object.freeze([
|
|
84
|
+
// Authentication (WU-2242: include both auth/ and authentication/)
|
|
85
|
+
'/auth/', // constants
|
|
86
|
+
'/auth.', // constants
|
|
87
|
+
'/authentication/', // constants
|
|
88
|
+
'/authentication.', // constants
|
|
89
|
+
// PHI (Protected Health Information)
|
|
90
|
+
'/phi/',
|
|
91
|
+
'/phi.',
|
|
92
|
+
// Row Level Security
|
|
93
|
+
'/rls/',
|
|
94
|
+
'/rls.',
|
|
95
|
+
'rls.sql',
|
|
96
|
+
// Security policies
|
|
97
|
+
'/policy/',
|
|
98
|
+
'/policy.',
|
|
99
|
+
// Supabase configuration
|
|
100
|
+
'supabase/config',
|
|
101
|
+
// API routes (potential attack surface)
|
|
102
|
+
'/api/',
|
|
103
|
+
]);
|
|
104
|
+
/**
|
|
105
|
+
* Supabase migrations directory (relative to repo root).
|
|
106
|
+
* @type {string}
|
|
107
|
+
*/
|
|
108
|
+
const SUPABASE_MIGRATIONS_DIR = 'supabase/supabase/migrations/';
|
|
109
|
+
/**
|
|
110
|
+
* Filename patterns that indicate RLS or policy changes in migrations.
|
|
111
|
+
* @type {RegExp[]}
|
|
112
|
+
*/
|
|
113
|
+
const RLS_MIGRATION_PATTERNS = Object.freeze([
|
|
114
|
+
/policy/i,
|
|
115
|
+
/rls/i,
|
|
116
|
+
/row[_-]?level[_-]?security/i,
|
|
117
|
+
/enable[_-]?rls/i,
|
|
118
|
+
]);
|
|
119
|
+
/**
|
|
120
|
+
* Check if a test file path matches safety-critical patterns.
|
|
121
|
+
*
|
|
122
|
+
* @param {string} testPath - Path to test file
|
|
123
|
+
* @returns {boolean} True if test is safety-critical
|
|
124
|
+
*/
|
|
125
|
+
export function isSafetyCriticalTest(testPath) {
|
|
126
|
+
if (!testPath || typeof testPath !== 'string') {
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
// Normalise Windows paths
|
|
130
|
+
const normalized = testPath.replace(/\\/g, '/');
|
|
131
|
+
// Check if any safety-critical pattern matches
|
|
132
|
+
for (const pattern of SAFETY_CRITICAL_TEST_PATTERNS) {
|
|
133
|
+
if (normalized.includes(pattern)) {
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Check whether a path refers to a Supabase migration file.
|
|
141
|
+
*
|
|
142
|
+
* @param {string} filePath - Path to check
|
|
143
|
+
* @returns {boolean} True if file is a migration SQL
|
|
144
|
+
*/
|
|
145
|
+
function isMigrationPath(filePath) {
|
|
146
|
+
if (!filePath || typeof filePath !== 'string') {
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
const normalized = filePath.replace(/\\/g, '/');
|
|
150
|
+
return normalized.includes(SUPABASE_MIGRATIONS_DIR) && normalized.endsWith('.sql');
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Check if a migration file contains RLS or policy changes.
|
|
154
|
+
*
|
|
155
|
+
* @param {string} filePath - Migration file path
|
|
156
|
+
* @returns {boolean} True if migration is high-risk
|
|
157
|
+
*/
|
|
158
|
+
export function isHighRiskMigration(filePath) {
|
|
159
|
+
if (!isMigrationPath(filePath)) {
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
const normalized = filePath.replace(/\\/g, '/');
|
|
163
|
+
const filename = path.basename(normalized);
|
|
164
|
+
return RLS_MIGRATION_PATTERNS.some((pattern) => pattern.test(filename));
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Check if a file path represents a high-risk code change.
|
|
168
|
+
*
|
|
169
|
+
* @param {string} filePath - Path to check
|
|
170
|
+
* @returns {boolean} True if path is high-risk
|
|
171
|
+
*/
|
|
172
|
+
export function isHighRiskPath(filePath) {
|
|
173
|
+
if (!filePath || typeof filePath !== 'string') {
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
if (isMigrationPath(filePath)) {
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
// Normalise Windows paths
|
|
180
|
+
const normalized = filePath.replace(/\\/g, '/');
|
|
181
|
+
// Check if any high-risk pattern matches
|
|
182
|
+
for (const pattern of HIGH_RISK_PATH_PATTERNS) {
|
|
183
|
+
if (normalized.includes(pattern)) {
|
|
184
|
+
return true;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Check if all changed files are documentation-only.
|
|
191
|
+
*
|
|
192
|
+
* @param {string[]} changedFiles - List of changed file paths
|
|
193
|
+
* @returns {boolean} True if all files are documentation
|
|
194
|
+
*/
|
|
195
|
+
function areAllDocsOnly(changedFiles) {
|
|
196
|
+
if (!changedFiles || changedFiles.length === 0) {
|
|
197
|
+
return true;
|
|
198
|
+
}
|
|
199
|
+
return changedFiles.every((file) => {
|
|
200
|
+
// Normalise Windows paths
|
|
201
|
+
const normalized = file.replace(/\\/g, '/');
|
|
202
|
+
return isDocumentationPath(normalized);
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Find high-risk paths in changed files.
|
|
207
|
+
*
|
|
208
|
+
* @param {string[]} changedFiles - List of changed file paths
|
|
209
|
+
* @returns {string[]} List of high-risk paths found
|
|
210
|
+
*/
|
|
211
|
+
function findHighRiskPaths(changedFiles) {
|
|
212
|
+
if (!changedFiles || changedFiles.length === 0) {
|
|
213
|
+
return [];
|
|
214
|
+
}
|
|
215
|
+
return changedFiles.filter((file) => {
|
|
216
|
+
// Normalise Windows paths
|
|
217
|
+
const normalized = file.replace(/\\/g, '/');
|
|
218
|
+
if (isMigrationPath(normalized)) {
|
|
219
|
+
return isHighRiskMigration(normalized);
|
|
220
|
+
}
|
|
221
|
+
return isHighRiskPath(normalized);
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
export function detectRiskTier(options = {}) {
|
|
225
|
+
const { changedFiles = [] } = options;
|
|
226
|
+
// Normalise all paths
|
|
227
|
+
const normalizedFiles = changedFiles.map((f) => (f ? f.replace(/\\/g, '/') : ''));
|
|
228
|
+
// Check if all files are documentation-only
|
|
229
|
+
const isDocsOnly = areAllDocsOnly(normalizedFiles);
|
|
230
|
+
// Find any high-risk paths
|
|
231
|
+
const highRiskPaths = findHighRiskPaths(normalizedFiles);
|
|
232
|
+
// Determine tier
|
|
233
|
+
let tier;
|
|
234
|
+
if (isDocsOnly) {
|
|
235
|
+
tier = RISK_TIERS.DOCS_ONLY;
|
|
236
|
+
}
|
|
237
|
+
else if (highRiskPaths.length > 0) {
|
|
238
|
+
tier = RISK_TIERS.HIGH_RISK;
|
|
239
|
+
}
|
|
240
|
+
else {
|
|
241
|
+
tier = RISK_TIERS.STANDARD;
|
|
242
|
+
}
|
|
243
|
+
// Safety-critical patterns always apply (for test filtering)
|
|
244
|
+
const safetyCriticalPatterns = [...SAFETY_CRITICAL_TEST_PATTERNS];
|
|
245
|
+
return {
|
|
246
|
+
tier,
|
|
247
|
+
safetyCriticalPatterns,
|
|
248
|
+
highRiskPaths,
|
|
249
|
+
isDocsOnly,
|
|
250
|
+
shouldRunIntegration: tier === RISK_TIERS.HIGH_RISK,
|
|
251
|
+
};
|
|
252
|
+
}
|