@odavl/guardian 0.1.0-rc1 → 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/CHANGELOG.md +146 -0
- package/README.md +155 -97
- package/bin/guardian.js +1544 -55
- package/config/README.md +59 -0
- package/config/profiles/landing-demo.yaml +16 -0
- package/package.json +26 -11
- package/policies/landing-demo.json +22 -0
- package/src/enterprise/audit-logger.js +166 -0
- package/src/enterprise/pdf-exporter.js +267 -0
- package/src/enterprise/rbac-gate.js +142 -0
- package/src/enterprise/rbac.js +239 -0
- package/src/enterprise/site-manager.js +180 -0
- package/src/founder/feedback-system.js +156 -0
- package/src/founder/founder-tracker.js +213 -0
- package/src/founder/usage-signals.js +141 -0
- package/src/guardian/alert-ledger.js +121 -0
- package/src/guardian/attempt-engine.js +587 -12
- package/src/guardian/attempt-registry.js +42 -1
- package/src/guardian/attempt-relevance.js +106 -0
- package/src/guardian/attempt.js +85 -39
- package/src/guardian/attempts-filter.js +63 -0
- package/src/guardian/baseline.js +50 -8
- package/src/guardian/breakage-intelligence.js +1 -0
- package/src/guardian/browser-pool.js +131 -0
- package/src/guardian/browser.js +28 -1
- package/src/guardian/ci-cli.js +121 -0
- package/src/guardian/ci-mode.js +15 -0
- package/src/guardian/ci-output.js +38 -0
- package/src/guardian/cli-summary.js +167 -67
- package/src/guardian/config-loader.js +162 -0
- package/src/guardian/data-guardian-detector.js +189 -0
- package/src/guardian/detection-layers.js +271 -0
- package/src/guardian/drift-detector.js +100 -0
- package/src/guardian/enhanced-html-reporter.js +221 -4
- package/src/guardian/env-guard.js +127 -0
- package/src/guardian/failure-intelligence.js +173 -0
- package/src/guardian/first-run-profile.js +89 -0
- package/src/guardian/first-run.js +54 -0
- package/src/guardian/flag-validator.js +111 -0
- package/src/guardian/flow-executor.js +309 -44
- package/src/guardian/html-reporter.js +2 -0
- package/src/guardian/human-reporter.js +431 -0
- package/src/guardian/index.js +22 -19
- package/src/guardian/init-command.js +9 -5
- package/src/guardian/intent-detector.js +146 -0
- package/src/guardian/journey-definitions.js +132 -0
- package/src/guardian/journey-scan-cli.js +145 -0
- package/src/guardian/journey-scanner.js +583 -0
- package/src/guardian/junit-reporter.js +18 -1
- package/src/guardian/language-detection.js +99 -0
- package/src/guardian/live-cli.js +95 -0
- package/src/guardian/live-scheduler-runner.js +137 -0
- package/src/guardian/live-scheduler.js +146 -0
- package/src/guardian/market-reporter.js +357 -82
- package/src/guardian/parallel-executor.js +116 -0
- package/src/guardian/pattern-analyzer.js +348 -0
- package/src/guardian/policy.js +80 -3
- package/src/guardian/prerequisite-checker.js +101 -0
- package/src/guardian/preset-loader.js +27 -18
- package/src/guardian/profile-loader.js +96 -0
- package/src/guardian/reality.js +1612 -115
- package/src/guardian/reporter.js +27 -41
- package/src/guardian/run-artifacts.js +212 -0
- package/src/guardian/run-cleanup.js +207 -0
- package/src/guardian/run-latest.js +90 -0
- package/src/guardian/run-list.js +211 -0
- package/src/guardian/run-summary.js +20 -0
- package/src/guardian/scan-presets.js +100 -11
- package/src/guardian/selector-fallbacks.js +394 -0
- package/src/guardian/semantic-contact-detection.js +255 -0
- package/src/guardian/semantic-contact-finder.js +201 -0
- package/src/guardian/semantic-targets.js +234 -0
- package/src/guardian/site-introspection.js +257 -0
- package/src/guardian/smoke.js +258 -0
- package/src/guardian/snapshot-schema.js +25 -1
- package/src/guardian/snapshot.js +69 -3
- package/src/guardian/stability-scorer.js +169 -0
- package/src/guardian/success-evaluator.js +214 -0
- package/src/guardian/template-command.js +184 -0
- package/src/guardian/text-formatters.js +426 -0
- package/src/guardian/timeout-profiles.js +57 -0
- package/src/guardian/verdict.js +320 -0
- package/src/guardian/verdicts.js +74 -0
- package/src/guardian/wait-for-outcome.js +120 -0
- package/src/guardian/watch-runner.js +181 -0
- package/src/payments/stripe-checkout.js +169 -0
- package/src/plans/plan-definitions.js +148 -0
- package/src/plans/plan-manager.js +211 -0
- package/src/plans/usage-tracker.js +210 -0
- package/src/recipes/recipe-engine.js +188 -0
- package/src/recipes/recipe-failure-analysis.js +159 -0
- package/src/recipes/recipe-registry.js +134 -0
- package/src/recipes/recipe-runtime.js +507 -0
- package/src/recipes/recipe-store.js +410 -0
- package/guardian-contract-v1.md +0 -149
- /package/{guardian.config.json → config/guardian.config.json} +0 -0
- /package/{guardian.policy.json → config/guardian.policy.json} +0 -0
- /package/{guardian.profile.docs.yaml → config/profiles/docs.yaml} +0 -0
- /package/{guardian.profile.ecommerce.yaml → config/profiles/ecommerce.yaml} +0 -0
- /package/{guardian.profile.marketing.yaml → config/profiles/marketing.yaml} +0 -0
- /package/{guardian.profile.saas.yaml → config/profiles/saas.yaml} +0 -0
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Text Formatters - Single Source of Truth
|
|
3
|
+
*
|
|
4
|
+
* All verdict, findings, limits, and pattern text is generated here
|
|
5
|
+
* and rendered identically across CLI, HTML, JUnit, and decision.json.
|
|
6
|
+
*
|
|
7
|
+
* Layer 4 / Step 4.1: Consistency Lock Across Outputs
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Format verdict summary line
|
|
12
|
+
*/
|
|
13
|
+
function formatVerdictStatus(verdict) {
|
|
14
|
+
if (!verdict) return 'No verdict available';
|
|
15
|
+
return verdict.verdict || 'UNKNOWN';
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Format confidence line
|
|
20
|
+
*/
|
|
21
|
+
function formatConfidence(verdict) {
|
|
22
|
+
if (!verdict || !verdict.confidence) return 'n/a';
|
|
23
|
+
const cf = verdict.confidence;
|
|
24
|
+
const level = cf.level || 'n/a';
|
|
25
|
+
const score = typeof cf.score === 'number' ? cf.score.toFixed(2) : 'n/a';
|
|
26
|
+
return `${level} (${score})`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Format verdict why
|
|
31
|
+
*/
|
|
32
|
+
function formatVerdictWhy(verdict) {
|
|
33
|
+
if (!verdict || !verdict.why) return null;
|
|
34
|
+
return verdict.why;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Format key findings (returns array of strings)
|
|
39
|
+
*/
|
|
40
|
+
function formatKeyFindings(verdict) {
|
|
41
|
+
if (!verdict || !Array.isArray(verdict.keyFindings)) return [];
|
|
42
|
+
return verdict.keyFindings.slice(0, 7);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Format limits (returns array of strings)
|
|
47
|
+
*/
|
|
48
|
+
function formatLimits(verdict) {
|
|
49
|
+
if (!verdict || !Array.isArray(verdict.limits)) return [];
|
|
50
|
+
return verdict.limits.slice(0, 6);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Format pattern summary
|
|
55
|
+
*/
|
|
56
|
+
function formatPatternSummary(pattern) {
|
|
57
|
+
if (!pattern) return '';
|
|
58
|
+
return pattern.summary || '';
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Format pattern why it matters
|
|
63
|
+
*/
|
|
64
|
+
function formatPatternWhy(pattern) {
|
|
65
|
+
if (!pattern) return '';
|
|
66
|
+
return pattern.whyItMatters || '';
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Format pattern recommended focus
|
|
71
|
+
*/
|
|
72
|
+
function formatPatternFocus(pattern) {
|
|
73
|
+
if (!pattern) return null;
|
|
74
|
+
return pattern.recommendedFocus || null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Format pattern limits
|
|
79
|
+
*/
|
|
80
|
+
function formatPatternLimits(pattern) {
|
|
81
|
+
if (!pattern) return null;
|
|
82
|
+
return pattern.limits || null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Format confidence interpretation micro-line
|
|
87
|
+
*/
|
|
88
|
+
function formatConfidenceMicroLine() {
|
|
89
|
+
return 'Confidence reflects the strength of evidence from outcomes, coverage, and captured artifacts.';
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Format first-run note
|
|
94
|
+
*/
|
|
95
|
+
function formatFirstRunNote() {
|
|
96
|
+
return 'This verdict reflects this moment; repeat runs strengthen confidence.';
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Format journey message for run N
|
|
101
|
+
*/
|
|
102
|
+
function formatJourneyMessage(runIndex) {
|
|
103
|
+
if (runIndex === 0) {
|
|
104
|
+
return 'Run 1/3: establishing a baseline for this site.';
|
|
105
|
+
} else if (runIndex === 1) {
|
|
106
|
+
return 'Run 2/3: checking for repeat signals.';
|
|
107
|
+
}
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Format next-run hint
|
|
113
|
+
*/
|
|
114
|
+
function formatNextRunHint(verdict) {
|
|
115
|
+
if (!verdict || !verdict.nextRunHint) return null;
|
|
116
|
+
return verdict.nextRunHint;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Format confidence drivers (returns array of strings)
|
|
121
|
+
*/
|
|
122
|
+
function formatConfidenceDrivers(verdict) {
|
|
123
|
+
if (!verdict || !verdict.confidence || !Array.isArray(verdict.confidence.reasons)) return [];
|
|
124
|
+
return verdict.confidence.reasons.slice(0, 3);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Format focus summary (Layer 5 - Advisor Mode)
|
|
129
|
+
*
|
|
130
|
+
* Derives prioritization from existing verdict, confidence, patterns, and limits.
|
|
131
|
+
* Returns array of focus lines (max 3) — NOT advice, NOT commands, only attention priorities.
|
|
132
|
+
*
|
|
133
|
+
* Display when: verdict !== READY OR confidence !== high OR patterns.length > 0
|
|
134
|
+
* Suppress when: READY + high confidence + no patterns
|
|
135
|
+
*
|
|
136
|
+
* Derivation logic:
|
|
137
|
+
* - High-confidence patterns dominate
|
|
138
|
+
* - Single point of failure outranks others
|
|
139
|
+
* - Confidence degradation outranks friction
|
|
140
|
+
* - Repeated "not executed" indicates coverage focus
|
|
141
|
+
* - Limits provide context, not priority
|
|
142
|
+
*
|
|
143
|
+
* @param {object} verdict - Verdict object
|
|
144
|
+
* @param {array} patterns - Array of detected patterns (from analyzePatterns)
|
|
145
|
+
* @returns {array} Array of focus lines (max 3)
|
|
146
|
+
*/
|
|
147
|
+
function formatFocusSummary(verdict, patterns = []) {
|
|
148
|
+
if (!verdict) return [];
|
|
149
|
+
|
|
150
|
+
const vStatus = verdict.verdict || 'UNKNOWN';
|
|
151
|
+
const cfLevel = (verdict.confidence || {}).level || 'n/a';
|
|
152
|
+
|
|
153
|
+
// Suppress when READY + high confidence + no patterns
|
|
154
|
+
if (vStatus === 'READY' && cfLevel === 'high' && patterns.length === 0) {
|
|
155
|
+
return [];
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const focus = [];
|
|
159
|
+
|
|
160
|
+
// Priority 1: Single point of failure patterns (critical blockers)
|
|
161
|
+
const spofPatterns = patterns.filter(p => p.type === 'single_point_failure');
|
|
162
|
+
spofPatterns.forEach(p => {
|
|
163
|
+
if (focus.length >= 3) return;
|
|
164
|
+
const path = p.pathName || 'a critical path';
|
|
165
|
+
focus.push(`${path} is blocked and prevents user progress`);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// Priority 2: Confidence degradation (quality trending down)
|
|
169
|
+
const degradationPatterns = patterns.filter(p => p.type === 'confidence_degradation');
|
|
170
|
+
degradationPatterns.forEach(p => {
|
|
171
|
+
if (focus.length >= 3) return;
|
|
172
|
+
const path = p.pathName || 'flow quality';
|
|
173
|
+
focus.push(`${path} declining across recent runs`);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
// Priority 3: Recurring friction (persistent issues)
|
|
177
|
+
const frictionPatterns = patterns.filter(p => p.type === 'recurring_friction');
|
|
178
|
+
frictionPatterns.forEach(p => {
|
|
179
|
+
if (focus.length >= 3) return;
|
|
180
|
+
const path = p.pathName || 'this path';
|
|
181
|
+
focus.push(`${path} experiencing repeated friction`);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
// Priority 4: Repeated skipped/not-executed (coverage gaps)
|
|
185
|
+
const skippedPatterns = patterns.filter(p => p.type === 'repeated_skipped_attempts');
|
|
186
|
+
skippedPatterns.forEach(p => {
|
|
187
|
+
if (focus.length >= 3) return;
|
|
188
|
+
const path = p.pathName || 'path';
|
|
189
|
+
focus.push(`Coverage gap: ${path} not yet exercised`);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
// If no patterns but verdict is not READY or confidence is not high, derive from verdict
|
|
193
|
+
if (focus.length === 0 && (vStatus !== 'READY' || cfLevel !== 'high')) {
|
|
194
|
+
if (vStatus === 'BLOCKED') {
|
|
195
|
+
focus.push('Site functionality is blocked');
|
|
196
|
+
} else if (vStatus === 'FRICTION') {
|
|
197
|
+
focus.push('Site experiencing friction in user flows');
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Add confidence-based focus if low/medium
|
|
201
|
+
if (cfLevel === 'low' && focus.length < 3) {
|
|
202
|
+
focus.push('Evidence strength is limited for current verdict');
|
|
203
|
+
} else if (cfLevel === 'medium' && focus.length < 3) {
|
|
204
|
+
focus.push('Confidence moderate; additional runs would strengthen assessment');
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return focus.slice(0, 3); // Hard limit: max 3
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Format delta insight (Stage V / Step 5.1)
|
|
213
|
+
*
|
|
214
|
+
* Compares current run (N) vs previous run (N-1) and generates minimal delta insight:
|
|
215
|
+
* - What improved
|
|
216
|
+
* - What regressed
|
|
217
|
+
*
|
|
218
|
+
* Max 2 lines total (1 improved + 1 regressed).
|
|
219
|
+
* Suppresses if no meaningful change detected.
|
|
220
|
+
*
|
|
221
|
+
* @param {object} currentVerdict - Current run verdict
|
|
222
|
+
* @param {object} previousVerdict - Previous run verdict (N-1)
|
|
223
|
+
* @param {array} currentPatterns - Current run patterns
|
|
224
|
+
* @param {array} previousPatterns - Previous run patterns (N-1)
|
|
225
|
+
* @returns {object} - { improved: string[], regressed: string[] }
|
|
226
|
+
*/
|
|
227
|
+
function formatDeltaInsight(currentVerdict, previousVerdict, currentPatterns = [], previousPatterns = []) {
|
|
228
|
+
const result = { improved: [], regressed: [] };
|
|
229
|
+
|
|
230
|
+
// Early return if no previous run
|
|
231
|
+
if (!previousVerdict) {
|
|
232
|
+
return result;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const currentStatus = (currentVerdict && currentVerdict.verdict) || 'UNKNOWN';
|
|
236
|
+
const previousStatus = (previousVerdict && previousVerdict.verdict) || 'UNKNOWN';
|
|
237
|
+
|
|
238
|
+
const currentConfLevel = (currentVerdict && currentVerdict.confidence && currentVerdict.confidence.level) || 'n/a';
|
|
239
|
+
const previousConfLevel = (previousVerdict && previousVerdict.confidence && previousVerdict.confidence.level) || 'n/a';
|
|
240
|
+
|
|
241
|
+
// Verdict hierarchy: READY > FRICTION > DO_NOT_LAUNCH/BLOCKED
|
|
242
|
+
const verdictRank = { 'READY': 3, 'FRICTION': 2, 'DO_NOT_LAUNCH': 1, 'BLOCKED': 1, 'UNKNOWN': 0 };
|
|
243
|
+
const currentRank = verdictRank[currentStatus] || 0;
|
|
244
|
+
const previousRank = verdictRank[previousStatus] || 0;
|
|
245
|
+
|
|
246
|
+
// Priority 1: Verdict change
|
|
247
|
+
if (currentRank > previousRank) {
|
|
248
|
+
result.improved.push('Overall readiness improved compared to the previous run');
|
|
249
|
+
} else if (currentRank < previousRank) {
|
|
250
|
+
result.regressed.push('Overall readiness declined compared to the previous run');
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Priority 2: Confidence level change (only if verdict didn't change)
|
|
254
|
+
if (currentRank === previousRank && currentConfLevel !== previousConfLevel) {
|
|
255
|
+
const confRank = { 'high': 3, 'medium': 2, 'low': 1, 'n/a': 0 };
|
|
256
|
+
const currentConfRank = confRank[currentConfLevel] || 0;
|
|
257
|
+
const previousConfRank = confRank[previousConfLevel] || 0;
|
|
258
|
+
|
|
259
|
+
if (currentConfRank > previousConfRank && result.improved.length === 0) {
|
|
260
|
+
result.improved.push('Confidence in verdict strengthened compared to the previous run');
|
|
261
|
+
} else if (currentConfRank < previousConfRank && result.regressed.length === 0) {
|
|
262
|
+
result.regressed.push('Confidence in verdict weakened compared to the previous run');
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Priority 3: Pattern changes (only if no verdict or confidence change)
|
|
267
|
+
if (result.improved.length === 0 && result.regressed.length === 0) {
|
|
268
|
+
// Check for resolved critical patterns
|
|
269
|
+
const previousCriticalPatterns = previousPatterns.filter(p =>
|
|
270
|
+
p.type === 'single_point_failure' || p.severity === 'critical'
|
|
271
|
+
);
|
|
272
|
+
const currentCriticalPatterns = currentPatterns.filter(p =>
|
|
273
|
+
p.type === 'single_point_failure' || p.severity === 'critical'
|
|
274
|
+
);
|
|
275
|
+
|
|
276
|
+
// Pattern resolved
|
|
277
|
+
if (previousCriticalPatterns.length > 0 && currentCriticalPatterns.length === 0) {
|
|
278
|
+
result.improved.push('Previously observed friction was not detected in this run');
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// New critical pattern appeared
|
|
282
|
+
if (previousCriticalPatterns.length === 0 && currentCriticalPatterns.length > 0) {
|
|
283
|
+
result.regressed.push('New blocking issues were observed since the last run');
|
|
284
|
+
} else if (currentCriticalPatterns.length > previousCriticalPatterns.length) {
|
|
285
|
+
result.regressed.push('A recurring failure pattern emerged in this run');
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Enforce max 2 lines total (1 improved + 1 regressed)
|
|
290
|
+
return {
|
|
291
|
+
improved: result.improved.slice(0, 1),
|
|
292
|
+
regressed: result.regressed.slice(0, 1)
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* ═══════════════════════════════════════════════════════════════════
|
|
298
|
+
* STAGE V / STEP 5.2: SILENCE DISCIPLINE — SUPPRESSION HELPERS
|
|
299
|
+
* ═══════════════════════════════════════════════════════════════════
|
|
300
|
+
*
|
|
301
|
+
* Centralized boolean helpers to enforce strict suppression rules.
|
|
302
|
+
* Guardian speaks ONLY when there is clear, meaningful value.
|
|
303
|
+
* Silence is the default state. Output is an exception.
|
|
304
|
+
*/
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Should render Focus Summary?
|
|
308
|
+
* Suppress when: verdict === READY + confidence === high + no patterns
|
|
309
|
+
*/
|
|
310
|
+
function shouldRenderFocusSummary(verdict, patterns) {
|
|
311
|
+
if (!verdict) return false;
|
|
312
|
+
|
|
313
|
+
// Handle null/undefined patterns: safer to show when uncertain
|
|
314
|
+
if (patterns === null || patterns === undefined) {
|
|
315
|
+
return true;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const vStatus = verdict.verdict || 'UNKNOWN';
|
|
319
|
+
const cfLevel = (verdict.confidence || {}).level || 'n/a';
|
|
320
|
+
|
|
321
|
+
// Suppress when READY + high confidence + no patterns
|
|
322
|
+
if (vStatus === 'READY' && cfLevel === 'high' && patterns.length === 0) {
|
|
323
|
+
return false;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
return true;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Should render Delta Insight?
|
|
331
|
+
* Suppress when: no improved and no regressed lines
|
|
332
|
+
*/
|
|
333
|
+
function shouldRenderDeltaInsight(delta) {
|
|
334
|
+
if (!delta) return false;
|
|
335
|
+
return (delta.improved && delta.improved.length > 0) || (delta.regressed && delta.regressed.length > 0);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Should render Observed Patterns?
|
|
340
|
+
* Suppress when: no patterns detected
|
|
341
|
+
*/
|
|
342
|
+
function shouldRenderPatterns(patterns) {
|
|
343
|
+
// Handle null/undefined patterns
|
|
344
|
+
if (!patterns || !Array.isArray(patterns)) {
|
|
345
|
+
return false;
|
|
346
|
+
}
|
|
347
|
+
return patterns.length > 0;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Should render Confidence Drivers?
|
|
352
|
+
* Suppress when: confidence === high AND runIndex >= 3
|
|
353
|
+
*/
|
|
354
|
+
function shouldRenderConfidenceDrivers(verdict, runIndex = 0) {
|
|
355
|
+
if (!verdict) return false;
|
|
356
|
+
|
|
357
|
+
const cfLevel = (verdict.confidence || {}).level || 'n/a';
|
|
358
|
+
|
|
359
|
+
// Canonical rule: show unless confidence is high on runIndex >= 2
|
|
360
|
+
if (cfLevel === 'high' && runIndex >= 2) {
|
|
361
|
+
return false;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
return true;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Should render Three-Runs Journey messaging?
|
|
369
|
+
* Suppress when: runIndex >= 3
|
|
370
|
+
*/
|
|
371
|
+
function shouldRenderJourneyMessage(runIndex = 0) {
|
|
372
|
+
return runIndex < 2;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Should render Next-Run Hint?
|
|
377
|
+
* Suppress when: verdict === READY OR no gaps/limits exist
|
|
378
|
+
*/
|
|
379
|
+
function shouldRenderNextRunHint(verdict) {
|
|
380
|
+
if (!verdict) return false;
|
|
381
|
+
|
|
382
|
+
const vStatus = verdict.verdict || 'UNKNOWN';
|
|
383
|
+
|
|
384
|
+
// Suppress when READY
|
|
385
|
+
if (vStatus === 'READY') {
|
|
386
|
+
return false;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Show when not READY (hints may be valuable)
|
|
390
|
+
return true;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Should render First-Run Note?
|
|
395
|
+
* Suppress when: runIndex >= 2
|
|
396
|
+
*/
|
|
397
|
+
function shouldRenderFirstRunNote(runIndex = 0) {
|
|
398
|
+
return runIndex < 2;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
module.exports = {
|
|
402
|
+
formatVerdictStatus,
|
|
403
|
+
formatConfidence,
|
|
404
|
+
formatVerdictWhy,
|
|
405
|
+
formatKeyFindings,
|
|
406
|
+
formatLimits,
|
|
407
|
+
formatPatternSummary,
|
|
408
|
+
formatPatternWhy,
|
|
409
|
+
formatPatternFocus,
|
|
410
|
+
formatPatternLimits,
|
|
411
|
+
formatConfidenceMicroLine,
|
|
412
|
+
formatFirstRunNote,
|
|
413
|
+
formatJourneyMessage,
|
|
414
|
+
formatNextRunHint,
|
|
415
|
+
formatConfidenceDrivers,
|
|
416
|
+
formatFocusSummary,
|
|
417
|
+
formatDeltaInsight,
|
|
418
|
+
// Stage V / Step 5.2: Silence Discipline helpers
|
|
419
|
+
shouldRenderFocusSummary,
|
|
420
|
+
shouldRenderDeltaInsight,
|
|
421
|
+
shouldRenderPatterns,
|
|
422
|
+
shouldRenderConfidenceDrivers,
|
|
423
|
+
shouldRenderJourneyMessage,
|
|
424
|
+
shouldRenderNextRunHint,
|
|
425
|
+
shouldRenderFirstRunNote
|
|
426
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Guardian Timeout Profiles
|
|
3
|
+
* Defines deterministic timeout values for different performance modes
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const TIMEOUT_PROFILES = {
|
|
7
|
+
fast: {
|
|
8
|
+
// Fast mode: aggressive timeouts for quick feedback
|
|
9
|
+
pageLoad: 8000, // page navigation
|
|
10
|
+
elementWait: 3000, // finding elements
|
|
11
|
+
actionWait: 2000, // click/type settlement (used by wait-for-outcome)
|
|
12
|
+
submitSettle: 2500, // form submission settlement
|
|
13
|
+
networkWait: 1500, // network response wait
|
|
14
|
+
default: 8000
|
|
15
|
+
},
|
|
16
|
+
default: {
|
|
17
|
+
// Default mode: current behavior (balanced)
|
|
18
|
+
pageLoad: 20000,
|
|
19
|
+
elementWait: 5000,
|
|
20
|
+
actionWait: 3500, // matches DEFAULT_MAX_WAIT in wait-for-outcome
|
|
21
|
+
submitSettle: 4000,
|
|
22
|
+
networkWait: 3500,
|
|
23
|
+
default: 20000
|
|
24
|
+
},
|
|
25
|
+
slow: {
|
|
26
|
+
// Slow mode: patient timeouts for flaky networks
|
|
27
|
+
pageLoad: 30000,
|
|
28
|
+
elementWait: 10000,
|
|
29
|
+
actionWait: 5000,
|
|
30
|
+
submitSettle: 6000,
|
|
31
|
+
networkWait: 5000,
|
|
32
|
+
default: 30000
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
function getTimeoutProfile(profileName = 'default') {
|
|
37
|
+
const profile = TIMEOUT_PROFILES[profileName];
|
|
38
|
+
if (!profile) {
|
|
39
|
+
throw new Error(`Invalid timeout profile: ${profileName}. Valid values: ${Object.keys(TIMEOUT_PROFILES).join(', ')}`);
|
|
40
|
+
}
|
|
41
|
+
return profile;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function resolveTimeout(configValue, profile) {
|
|
45
|
+
// If config explicitly sets a timeout, use it (allows override)
|
|
46
|
+
if (configValue && typeof configValue === 'number') {
|
|
47
|
+
return configValue;
|
|
48
|
+
}
|
|
49
|
+
// Otherwise use profile default
|
|
50
|
+
return profile.default;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
module.exports = {
|
|
54
|
+
TIMEOUT_PROFILES,
|
|
55
|
+
getTimeoutProfile,
|
|
56
|
+
resolveTimeout
|
|
57
|
+
};
|