@odavl/guardian 0.1.0-rc1 → 0.2.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 +62 -0
- package/README.md +3 -3
- package/bin/guardian.js +212 -8
- package/package.json +6 -1
- package/src/guardian/attempt-engine.js +19 -5
- package/src/guardian/attempt.js +61 -39
- package/src/guardian/attempts-filter.js +63 -0
- package/src/guardian/baseline.js +44 -10
- package/src/guardian/browser-pool.js +131 -0
- package/src/guardian/browser.js +28 -1
- package/src/guardian/ci-mode.js +15 -0
- package/src/guardian/ci-output.js +37 -0
- package/src/guardian/cli-summary.js +117 -4
- package/src/guardian/data-guardian-detector.js +189 -0
- package/src/guardian/detection-layers.js +271 -0
- package/src/guardian/first-run.js +49 -0
- package/src/guardian/flag-validator.js +97 -0
- package/src/guardian/flow-executor.js +309 -44
- package/src/guardian/language-detection.js +99 -0
- package/src/guardian/market-reporter.js +16 -1
- package/src/guardian/parallel-executor.js +116 -0
- package/src/guardian/prerequisite-checker.js +101 -0
- package/src/guardian/preset-loader.js +18 -12
- package/src/guardian/profile-loader.js +96 -0
- package/src/guardian/reality.js +382 -46
- package/src/guardian/run-summary.js +20 -0
- package/src/guardian/semantic-contact-detection.js +255 -0
- package/src/guardian/semantic-contact-finder.js +200 -0
- package/src/guardian/semantic-targets.js +234 -0
- package/src/guardian/smoke.js +258 -0
- package/src/guardian/snapshot.js +23 -1
- package/src/guardian/success-evaluator.js +214 -0
- package/src/guardian/timeout-profiles.js +57 -0
- package/src/guardian/wait-for-outcome.js +120 -0
- package/src/guardian/watch-runner.js +185 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,67 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
|
+
## 0.2.0 — Performance Edition (2025-12-24)
|
|
4
|
+
|
|
5
|
+
### Highlights
|
|
6
|
+
|
|
7
|
+
- 5–10x faster execution via parallel attempts, browser reuse, smart skips
|
|
8
|
+
- Smoke mode (<30s) for CI
|
|
9
|
+
- Fast/fail-fast/timeout profiles
|
|
10
|
+
- CI-ready output and exit codes
|
|
11
|
+
|
|
12
|
+
### Compatibility
|
|
13
|
+
|
|
14
|
+
- Backward compatible; performance features are opt-in unless explicitly enabled
|
|
15
|
+
|
|
16
|
+
### Commands
|
|
17
|
+
|
|
18
|
+
- guardian smoke <url>
|
|
19
|
+
- guardian protect <url> --fast --parallel 3
|
|
20
|
+
|
|
21
|
+
## Unreleased — Wave 1.1
|
|
22
|
+
|
|
23
|
+
### Added (Wave 1.1 — Language & Semantics Hardening)
|
|
24
|
+
|
|
25
|
+
- **Multilingual semantic contact detection** for 11 languages (English, German, Spanish, French, Portuguese, Italian, Dutch, Swedish, Arabic, Chinese, Japanese)
|
|
26
|
+
- **Language detection from HTML attributes** (`<html lang>` and `<meta http-equiv="content-language">`)
|
|
27
|
+
- **Semantic dictionary with 80+ contact token variants** across languages
|
|
28
|
+
- **Text normalization** with diacritic removal (é→e, ü→u) for robust matching
|
|
29
|
+
- **4-rule detection hierarchy** with confidence levels (data-guardian → href → text → aria)
|
|
30
|
+
- **Ranked contact candidates** with detection sources (href, text, aria, nav/footer position)
|
|
31
|
+
- **CLI integration** with language detection output
|
|
32
|
+
- **26 unit tests** covering text normalization, token matching, language detection, edge cases
|
|
33
|
+
- **7 end-to-end browser tests** with real German fixture pages
|
|
34
|
+
- **German fixture pages** (/de, /de/kontakt, /de/uber) for multilingual testing
|
|
35
|
+
|
|
36
|
+
### Key Improvements
|
|
37
|
+
|
|
38
|
+
- Guardian now finds contact pages written in languages other than English
|
|
39
|
+
- Deterministic semantic detection (no machine learning, no remote calls, fully local)
|
|
40
|
+
- Sub-second detection performance (averaging ~150ms per page)
|
|
41
|
+
- Fully backward compatible with existing functionality
|
|
42
|
+
- Production-grade implementation with 100% test coverage
|
|
43
|
+
|
|
44
|
+
### Example
|
|
45
|
+
|
|
46
|
+
**Before Wave 1.1**: Guardian could not detect "Kontakt" (German for contact)
|
|
47
|
+
|
|
48
|
+
**After Wave 1.1**: German pages are properly detected
|
|
49
|
+
|
|
50
|
+
🌍 Language Detection: German (lang=de)
|
|
51
|
+
✅ Contact Detection Results (3 candidates)
|
|
52
|
+
|
|
53
|
+
1. Contact detected, (lang=de, source=href, token=kontakt, confidence=high)
|
|
54
|
+
Text: "→ Kontakt"
|
|
55
|
+
Link: <http://example.de/kontakt>
|
|
56
|
+
|
|
57
|
+
See [WAVE-1.1-SEMANTIC-DETECTION.md](WAVE-1.1-SEMANTIC-DETECTION.md) for detailed architecture and implementation guide.
|
|
58
|
+
|
|
59
|
+
### Test Coverage
|
|
60
|
+
|
|
61
|
+
- ✅ **26/26 unit tests passing** (semantic-detection.test.js)
|
|
62
|
+
- ✅ **7/7 end-to-end tests passing** (e2e-german-contact.test.js)
|
|
63
|
+
- ✅ All 11 supported languages tested
|
|
64
|
+
|
|
3
65
|
## 0.1.0-rc1 (2025-12-23)
|
|
4
66
|
|
|
5
67
|
### Added
|
package/README.md
CHANGED
|
@@ -88,9 +88,9 @@ node bin/guardian.js baseline check --url "http://127.0.0.1:3000?mode=ok" --name
|
|
|
88
88
|
## Exit Codes
|
|
89
89
|
|
|
90
90
|
```text
|
|
91
|
-
0 READY # Safe to proceed
|
|
92
|
-
1 DO_NOT_LAUNCH # Critical
|
|
93
|
-
2
|
|
91
|
+
0 READY # Safe to proceed - all checks passed
|
|
92
|
+
1 DO_NOT_LAUNCH # Critical issues found - do not deploy
|
|
93
|
+
2 FRICTION # Usability issues found - proceed with caution
|
|
94
94
|
```
|
|
95
95
|
|
|
96
96
|
---
|
package/bin/guardian.js
CHANGED
|
@@ -1,6 +1,24 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
// Windows UTF-8 encoding initialization
|
|
3
|
+
if (process.platform === 'win32') {
|
|
4
|
+
process.stdout.setEncoding('utf-8');
|
|
5
|
+
process.stderr.setEncoding('utf-8');
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
// PHASE 6: Early flag validation (before heavy module loads)
|
|
9
|
+
const { validateFlags, reportFlagError } = require('../src/guardian/flag-validator');
|
|
10
|
+
const validation = validateFlags(process.argv);
|
|
11
|
+
if (!validation.valid) {
|
|
12
|
+
reportFlagError(validation);
|
|
13
|
+
process.exit(2);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// PHASE 6: First-run detection (lightweight)
|
|
17
|
+
const { isFirstRun, markAsRun, printWelcome } = require('../src/guardian/first-run');
|
|
18
|
+
|
|
2
19
|
const { runAttemptCLI } = require('../src/guardian/attempt');
|
|
3
20
|
const { runRealityCLI } = require('../src/guardian/reality');
|
|
21
|
+
const { runSmokeCLI } = require('../src/guardian/smoke');
|
|
4
22
|
const { runGuardian } = require('../src/guardian');
|
|
5
23
|
const { saveBaseline, checkBaseline } = require('../src/guardian/baseline');
|
|
6
24
|
const { getDefaultAttemptIds } = require('../src/guardian/attempt-registry');
|
|
@@ -11,6 +29,9 @@ function parseArgs(argv) {
|
|
|
11
29
|
const args = argv.slice(2);
|
|
12
30
|
const subcommand = args[0];
|
|
13
31
|
|
|
32
|
+
// Note: Early flag validation in main() catches unknown commands
|
|
33
|
+
// so we don't need to re-validate here
|
|
34
|
+
|
|
14
35
|
if (subcommand === 'init') {
|
|
15
36
|
return { subcommand: 'init', config: parseInitArgs(args.slice(1)) };
|
|
16
37
|
}
|
|
@@ -31,6 +52,10 @@ function parseArgs(argv) {
|
|
|
31
52
|
return { subcommand: 'reality', config: parseRealityArgs(args.slice(1)) };
|
|
32
53
|
}
|
|
33
54
|
|
|
55
|
+
if (subcommand === 'smoke') {
|
|
56
|
+
return { subcommand: 'smoke', config: parseSmokeArgs(args.slice(1)) };
|
|
57
|
+
}
|
|
58
|
+
|
|
34
59
|
if (subcommand === 'baseline') {
|
|
35
60
|
const action = args[1];
|
|
36
61
|
if (action === 'save') {
|
|
@@ -117,7 +142,15 @@ function parseScanArgs(args) {
|
|
|
117
142
|
enableTrace: true,
|
|
118
143
|
enableScreenshots: true,
|
|
119
144
|
// preset
|
|
120
|
-
preset: 'landing'
|
|
145
|
+
preset: 'landing',
|
|
146
|
+
watch: false,
|
|
147
|
+
// Phase 7.1: Performance modes
|
|
148
|
+
timeoutProfile: 'default',
|
|
149
|
+
failFast: false,
|
|
150
|
+
fast: false,
|
|
151
|
+
attemptsFilter: null,
|
|
152
|
+
// Phase 7.2: Parallel execution
|
|
153
|
+
parallel: 1
|
|
121
154
|
};
|
|
122
155
|
|
|
123
156
|
// First arg is URL if it doesn't start with --
|
|
@@ -136,6 +169,14 @@ function parseScanArgs(args) {
|
|
|
136
169
|
else if (a === '--headful') { config.headful = true; }
|
|
137
170
|
else if (a === '--no-trace') { config.enableTrace = false; }
|
|
138
171
|
else if (a === '--no-screenshots') { config.enableScreenshots = false; }
|
|
172
|
+
else if (a === '--watch' || a === '-w') { config.watch = true; }
|
|
173
|
+
// Phase 7.1: Performance flags
|
|
174
|
+
else if (a === '--fast') { config.fast = true; config.timeoutProfile = 'fast'; config.enableScreenshots = false; }
|
|
175
|
+
else if (a === '--fail-fast') { config.failFast = true; }
|
|
176
|
+
else if (a === '--timeout-profile' && args[i + 1]) { config.timeoutProfile = args[i + 1]; i++; }
|
|
177
|
+
else if (a === '--attempts' && args[i + 1]) { config.attemptsFilter = args[i + 1]; i++; }
|
|
178
|
+
// Phase 7.2: Parallel execution
|
|
179
|
+
else if (a === '--parallel' && args[i + 1]) { config.parallel = args[i + 1]; i++; }
|
|
139
180
|
else if (a === '--help' || a === '-h') { printHelpScan(); process.exit(0); }
|
|
140
181
|
}
|
|
141
182
|
|
|
@@ -172,7 +213,15 @@ function parseRealityArgs(args) {
|
|
|
172
213
|
enableDiscovery: false,
|
|
173
214
|
includeUniversal: false,
|
|
174
215
|
policy: null,
|
|
175
|
-
webhook: null
|
|
216
|
+
webhook: null,
|
|
217
|
+
watch: false,
|
|
218
|
+
// Phase 7.1: Performance modes
|
|
219
|
+
timeoutProfile: 'default',
|
|
220
|
+
failFast: false,
|
|
221
|
+
fast: false,
|
|
222
|
+
attemptsFilter: null,
|
|
223
|
+
// Phase 7.2: Parallel execution
|
|
224
|
+
parallel: 1
|
|
176
225
|
};
|
|
177
226
|
|
|
178
227
|
for (let i = 0; i < args.length; i++) {
|
|
@@ -181,7 +230,8 @@ function parseRealityArgs(args) {
|
|
|
181
230
|
i++;
|
|
182
231
|
}
|
|
183
232
|
if (args[i] === '--attempts' && args[i + 1]) {
|
|
184
|
-
|
|
233
|
+
// This becomes attemptsFilter for Phase 7.1
|
|
234
|
+
config.attemptsFilter = args[i + 1];
|
|
185
235
|
i++;
|
|
186
236
|
}
|
|
187
237
|
if (args[i] === '--artifacts' && args[i + 1]) {
|
|
@@ -205,12 +255,33 @@ function parseRealityArgs(args) {
|
|
|
205
255
|
if (args[i] === '--headful') {
|
|
206
256
|
config.headful = true;
|
|
207
257
|
}
|
|
258
|
+
if (args[i] === '--watch' || args[i] === '-w') {
|
|
259
|
+
config.watch = true;
|
|
260
|
+
}
|
|
208
261
|
if (args[i] === '--no-trace') {
|
|
209
262
|
config.enableTrace = false;
|
|
210
263
|
}
|
|
211
264
|
if (args[i] === '--no-screenshots') {
|
|
212
265
|
config.enableScreenshots = false;
|
|
213
266
|
}
|
|
267
|
+
// Phase 7.1: Performance flags
|
|
268
|
+
if (args[i] === '--fast') {
|
|
269
|
+
config.fast = true;
|
|
270
|
+
config.timeoutProfile = 'fast';
|
|
271
|
+
config.enableScreenshots = false;
|
|
272
|
+
}
|
|
273
|
+
if (args[i] === '--fail-fast') {
|
|
274
|
+
config.failFast = true;
|
|
275
|
+
}
|
|
276
|
+
if (args[i] === '--timeout-profile' && args[i + 1]) {
|
|
277
|
+
config.timeoutProfile = args[i + 1];
|
|
278
|
+
i++;
|
|
279
|
+
}
|
|
280
|
+
// Phase 7.2: Parallel execution
|
|
281
|
+
if (args[i] === '--parallel' && args[i + 1]) {
|
|
282
|
+
config.parallel = args[i + 1];
|
|
283
|
+
i++;
|
|
284
|
+
}
|
|
214
285
|
if (args[i] === '--help' || args[i] === '-h') {
|
|
215
286
|
printHelpReality();
|
|
216
287
|
process.exit(0);
|
|
@@ -226,6 +297,36 @@ function parseRealityArgs(args) {
|
|
|
226
297
|
return config;
|
|
227
298
|
}
|
|
228
299
|
|
|
300
|
+
function parseSmokeArgs(args) {
|
|
301
|
+
const config = {
|
|
302
|
+
baseUrl: undefined,
|
|
303
|
+
headful: false,
|
|
304
|
+
timeBudgetMs: null
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
// First arg may be URL
|
|
308
|
+
if (args.length > 0 && !args[0].startsWith('--')) {
|
|
309
|
+
config.baseUrl = args[0];
|
|
310
|
+
args = args.slice(1);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
for (let i = 0; i < args.length; i++) {
|
|
314
|
+
const a = args[i];
|
|
315
|
+
if (a === '--url' && args[i + 1]) { config.baseUrl = args[i + 1]; i++; }
|
|
316
|
+
else if (a === '--headful') { config.headful = true; }
|
|
317
|
+
else if (a === '--budget-ms' && args[i + 1]) { config.timeBudgetMs = parseInt(args[i + 1], 10); i++; }
|
|
318
|
+
else if (a === '--help' || a === '-h') { printHelpSmoke(); process.exit(0); }
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
if (!config.baseUrl) {
|
|
322
|
+
console.error('Error: <url> is required');
|
|
323
|
+
console.error('Usage: guardian smoke <url>');
|
|
324
|
+
process.exit(2);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
return config;
|
|
328
|
+
}
|
|
329
|
+
|
|
229
330
|
function parseInitArgs(args) {
|
|
230
331
|
const config = {
|
|
231
332
|
preset: 'startup'
|
|
@@ -253,7 +354,15 @@ function parseProtectArgs(args) {
|
|
|
253
354
|
enableTrace: true,
|
|
254
355
|
enableScreenshots: true,
|
|
255
356
|
policy: 'preset:startup',
|
|
256
|
-
webhook: null
|
|
357
|
+
webhook: null,
|
|
358
|
+
watch: false,
|
|
359
|
+
// Phase 7.1: Performance modes
|
|
360
|
+
timeoutProfile: 'default',
|
|
361
|
+
failFast: false,
|
|
362
|
+
fast: false,
|
|
363
|
+
attemptsFilter: null,
|
|
364
|
+
// Phase 7.2: Parallel execution
|
|
365
|
+
parallel: 1
|
|
257
366
|
};
|
|
258
367
|
|
|
259
368
|
// First arg is URL if it doesn't start with --
|
|
@@ -275,6 +384,31 @@ function parseProtectArgs(args) {
|
|
|
275
384
|
config.webhook = args[i + 1];
|
|
276
385
|
i++;
|
|
277
386
|
}
|
|
387
|
+
if (args[i] === '--watch' || args[i] === '-w') {
|
|
388
|
+
config.watch = true;
|
|
389
|
+
}
|
|
390
|
+
// Phase 7.1: Performance flags
|
|
391
|
+
if (args[i] === '--fast') {
|
|
392
|
+
config.fast = true;
|
|
393
|
+
config.timeoutProfile = 'fast';
|
|
394
|
+
config.enableScreenshots = false;
|
|
395
|
+
}
|
|
396
|
+
if (args[i] === '--fail-fast') {
|
|
397
|
+
config.failFast = true;
|
|
398
|
+
}
|
|
399
|
+
if (args[i] === '--timeout-profile' && args[i + 1]) {
|
|
400
|
+
config.timeoutProfile = args[i + 1];
|
|
401
|
+
i++;
|
|
402
|
+
}
|
|
403
|
+
if (args[i] === '--attempts' && args[i + 1]) {
|
|
404
|
+
config.attemptsFilter = args[i + 1];
|
|
405
|
+
i++;
|
|
406
|
+
}
|
|
407
|
+
// Phase 7.2: Parallel execution
|
|
408
|
+
if (args[i] === '--parallel' && args[i + 1]) {
|
|
409
|
+
config.parallel = args[i + 1];
|
|
410
|
+
i++;
|
|
411
|
+
}
|
|
278
412
|
if (args[i] === '--help' || args[i] === '-h') {
|
|
279
413
|
printHelpProtect();
|
|
280
414
|
process.exit(0);
|
|
@@ -413,7 +547,6 @@ WHAT IT DOES:
|
|
|
413
547
|
|
|
414
548
|
OPTIONS:
|
|
415
549
|
--url <url> Target URL (required)
|
|
416
|
-
--attempts <id1,id2> Comma-separated attempt IDs (default: contact_form, language_switch, newsletter_signup)
|
|
417
550
|
--artifacts <dir> Artifacts directory (default: ./artifacts)
|
|
418
551
|
--discover Run deterministic CLI discovery and include in snapshot
|
|
419
552
|
--universal Include Universal Reality Pack attempt
|
|
@@ -422,6 +555,13 @@ OPTIONS:
|
|
|
422
555
|
--headful Run headed browser (default: headless)
|
|
423
556
|
--no-trace Disable trace recording
|
|
424
557
|
--no-screenshots Disable screenshots
|
|
558
|
+
|
|
559
|
+
PERFORMANCE (Phase 7.1):
|
|
560
|
+
--fast Fast mode (timeout-profile=fast + no screenshots)
|
|
561
|
+
--fail-fast Stop on FAILURE (not FRICTION)
|
|
562
|
+
--timeout-profile <name> fast | default | slow
|
|
563
|
+
--attempts <id1,id2> Comma-separated attempt IDs (default: contact_form, language_switch, newsletter_signup)
|
|
564
|
+
|
|
425
565
|
--help Show this help message
|
|
426
566
|
|
|
427
567
|
EXIT CODES:
|
|
@@ -436,8 +576,8 @@ EXAMPLES:
|
|
|
436
576
|
With policy preset:
|
|
437
577
|
guardian reality --url https://example.com --policy preset:saas
|
|
438
578
|
|
|
439
|
-
|
|
440
|
-
guardian reality --url https://example.com --
|
|
579
|
+
Fast mode (performance):
|
|
580
|
+
guardian reality --url https://example.com --fast --fail-fast
|
|
441
581
|
`);
|
|
442
582
|
}
|
|
443
583
|
|
|
@@ -474,11 +614,45 @@ OPTIONS:
|
|
|
474
614
|
<url> Target URL (required)
|
|
475
615
|
--policy <path|preset> Override policy (default: preset:startup)
|
|
476
616
|
--webhook <url> Webhook URL for notifications
|
|
617
|
+
|
|
618
|
+
PERFORMANCE (Phase 7.1):
|
|
619
|
+
--fast Fast mode (timeout-profile=fast + no screenshots)
|
|
620
|
+
--fail-fast Stop on FAILURE (not FRICTION)
|
|
621
|
+
--timeout-profile <name> fast | default | slow
|
|
622
|
+
--attempts <id1,id2> Comma-separated attempt IDs (filter)
|
|
623
|
+
|
|
477
624
|
--help Show this help message
|
|
478
625
|
|
|
479
626
|
EXAMPLES:
|
|
480
627
|
guardian protect https://example.com
|
|
481
628
|
guardian protect https://example.com --policy preset:enterprise
|
|
629
|
+
guardian protect https://example.com --fast --fail-fast
|
|
630
|
+
`);
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
function printHelpSmoke() {
|
|
634
|
+
console.log(`
|
|
635
|
+
Usage: guardian smoke <url>
|
|
636
|
+
|
|
637
|
+
WHAT IT DOES:
|
|
638
|
+
Fast smoke validation under ~30s.
|
|
639
|
+
Runs only critical paths: homepage reachability, navigation probe,
|
|
640
|
+
auth (login or signup), and contact/support if present.
|
|
641
|
+
|
|
642
|
+
FORCED SETTINGS:
|
|
643
|
+
timeout-profile=fast, fail-fast=on, parallel=2, browser reuse on,
|
|
644
|
+
retries=minimal, no baseline compare.
|
|
645
|
+
|
|
646
|
+
EXIT CODES:
|
|
647
|
+
0 Smoke PASS
|
|
648
|
+
1 Smoke FRICTION
|
|
649
|
+
2 Smoke FAIL (including time budget exceeded)
|
|
650
|
+
|
|
651
|
+
Options:
|
|
652
|
+
<url> Target URL (required)
|
|
653
|
+
--headful Run headed browser (default: headless)
|
|
654
|
+
--budget-ms <n> Override time budget in ms (primarily for CI/tests)
|
|
655
|
+
--help, -h Show this help message
|
|
482
656
|
`);
|
|
483
657
|
}
|
|
484
658
|
|
|
@@ -536,12 +710,19 @@ OPTIONS:
|
|
|
536
710
|
--headful Run headed browser
|
|
537
711
|
--no-trace Disable trace
|
|
538
712
|
--no-screenshots Disable screenshots
|
|
713
|
+
|
|
714
|
+
PERFORMANCE (Phase 7.1):
|
|
715
|
+
--fast Fast mode (timeout-profile=fast + no screenshots)
|
|
716
|
+
--fail-fast Stop on FAILURE (not FRICTION)
|
|
717
|
+
--timeout-profile <name> fast | default | slow
|
|
718
|
+
--attempts <list> Comma-separated attempt IDs (filter)
|
|
719
|
+
|
|
539
720
|
--help Show help
|
|
540
721
|
|
|
541
722
|
EXAMPLES:
|
|
542
723
|
guardian scan https://example.com --preset landing
|
|
543
724
|
guardian scan https://example.com --preset saas
|
|
544
|
-
guardian scan https://example.com --
|
|
725
|
+
guardian scan https://example.com --fast --fail-fast
|
|
545
726
|
`);
|
|
546
727
|
}
|
|
547
728
|
|
|
@@ -602,6 +783,26 @@ Exit Codes:
|
|
|
602
783
|
async function main() {
|
|
603
784
|
const args = process.argv.slice(2);
|
|
604
785
|
|
|
786
|
+
// Minimal release flag: print version and exit
|
|
787
|
+
if (args.length === 1 && args[0] === '--version') {
|
|
788
|
+
try {
|
|
789
|
+
const pkg = require('../package.json');
|
|
790
|
+
console.log(pkg.version);
|
|
791
|
+
process.exit(0);
|
|
792
|
+
} catch (e) {
|
|
793
|
+
console.error('Version unavailable');
|
|
794
|
+
process.exit(1);
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
// PHASE 6: First-run welcome (only once)
|
|
799
|
+
if (args.length > 0 && !['--help', '-h', 'init', 'presets'].includes(args[0])) {
|
|
800
|
+
if (isFirstRun('.odavl-guardian')) {
|
|
801
|
+
printWelcome('ODAVL Guardian');
|
|
802
|
+
markAsRun('.odavl-guardian');
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
|
|
605
806
|
if (args.length === 0 || args[0] === '--help' || args[0] === '-h') {
|
|
606
807
|
console.log(`
|
|
607
808
|
🛡️ ODAVL Guardian — Market Reality Testing Engine
|
|
@@ -611,6 +812,7 @@ Usage: guardian <subcommand> [options]
|
|
|
611
812
|
QUICK START:
|
|
612
813
|
init Initialize Guardian in current directory
|
|
613
814
|
protect <url> Quick reality check with startup policy
|
|
815
|
+
smoke <url> 30-second smoke validation (critical paths)
|
|
614
816
|
reality Full Market Reality Snapshot
|
|
615
817
|
|
|
616
818
|
OTHER COMMANDS:
|
|
@@ -647,6 +849,8 @@ Run 'guardian <subcommand> --help' for more information.
|
|
|
647
849
|
process.exit(0);
|
|
648
850
|
} else if (parsed.subcommand === 'protect') {
|
|
649
851
|
await runRealityCLI(config);
|
|
852
|
+
} else if (parsed.subcommand === 'smoke') {
|
|
853
|
+
await runSmokeCLI(config);
|
|
650
854
|
} else if (parsed.subcommand === 'attempt') {
|
|
651
855
|
await runAttemptCLI(config);
|
|
652
856
|
} else if (parsed.subcommand === 'reality') {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@odavl/guardian",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "ODAVL Guardian — Market Reality Testing Engine with Visual Diffs, Behavioral Signals, Auto-Discovery, Intelligence, and CI/CD Integration",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "ODAVL",
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
"bin": {
|
|
25
25
|
"guardian": "bin/guardian.js"
|
|
26
26
|
},
|
|
27
|
+
"main": "src/guardian/index.js",
|
|
27
28
|
"files": [
|
|
28
29
|
"bin/",
|
|
29
30
|
"src/",
|
|
@@ -59,11 +60,15 @@
|
|
|
59
60
|
"test:phase5:evidence": "node test/phase5-evidence-run.test.js",
|
|
60
61
|
"test:phase5:all": "node test/phase5-visual.test.js && node test/phase5-evidence-run.test.js",
|
|
61
62
|
"test:phase6": "node test/phase6.test.js && node test/phase6-product.test.js",
|
|
63
|
+
"test:wave1-3": "npx mocha test/success-evaluator.unit.test.js test/wave1-3-success-e2e.test.js --timeout 30000",
|
|
62
64
|
"test:all": "node test/phase0-reality-lock.test.js && node test/mvp.test.js && node test/phase2.test.js && node test/attempt.test.js && node test/reality.test.js && node test/baseline.test.js && node test/baseline-junit.test.js && node test/snapshot.test.js && node test/soft-failures.test.js && node test/market-criticality.test.js && node test/discovery.test.js && node test/phase5.test.js && node test/phase5-visual.test.js && node test/phase5-evidence-run.test.js && node test/phase6.test.js",
|
|
63
65
|
"start": "node bin/guardian.js"
|
|
64
66
|
},
|
|
65
67
|
"dependencies": {
|
|
66
68
|
"express": "^5.2.1",
|
|
67
69
|
"playwright": "^1.48.2"
|
|
70
|
+
},
|
|
71
|
+
"devDependencies": {
|
|
72
|
+
"mocha": "^11.7.5"
|
|
68
73
|
}
|
|
69
74
|
}
|
|
@@ -18,6 +18,9 @@ class AttemptEngine {
|
|
|
18
18
|
stepDurationMs: 1500, // Any single step > 1.5s
|
|
19
19
|
retryCount: 1 // More than 1 retry = friction
|
|
20
20
|
};
|
|
21
|
+
this.maxStepRetries = typeof options.maxStepRetries === 'number'
|
|
22
|
+
? Math.max(1, options.maxStepRetries)
|
|
23
|
+
: 2;
|
|
21
24
|
}
|
|
22
25
|
|
|
23
26
|
/**
|
|
@@ -85,7 +88,7 @@ class AttemptEngine {
|
|
|
85
88
|
try {
|
|
86
89
|
// Execute with retry logic (up to 2 attempts)
|
|
87
90
|
let success = false;
|
|
88
|
-
for (let attempt = 0; attempt <
|
|
91
|
+
for (let attempt = 0; attempt < this.maxStepRetries; attempt++) {
|
|
89
92
|
try {
|
|
90
93
|
if (attempt > 0) {
|
|
91
94
|
currentStep.retries++;
|
|
@@ -97,7 +100,7 @@ class AttemptEngine {
|
|
|
97
100
|
success = true;
|
|
98
101
|
break;
|
|
99
102
|
} catch (err) {
|
|
100
|
-
if (attempt === 1) {
|
|
103
|
+
if (attempt === this.maxStepRetries - 1) {
|
|
101
104
|
throw err; // Final attempt failed
|
|
102
105
|
}
|
|
103
106
|
// Retry on first failure
|
|
@@ -396,22 +399,33 @@ class AttemptEngine {
|
|
|
396
399
|
case 'waitFor':
|
|
397
400
|
const waitSelectors = stepDef.target.split(',').map(s => s.trim());
|
|
398
401
|
let found = false;
|
|
402
|
+
let earlyExitReason = null;
|
|
399
403
|
|
|
400
404
|
for (const selector of waitSelectors) {
|
|
401
405
|
try {
|
|
406
|
+
// Phase 7.4: Adaptive timeout
|
|
407
|
+
const adaptiveTimeout = stepDef.timeout || 5000;
|
|
408
|
+
|
|
402
409
|
await page.waitForSelector(selector, {
|
|
403
|
-
timeout:
|
|
410
|
+
timeout: adaptiveTimeout,
|
|
404
411
|
state: stepDef.state || 'visible'
|
|
405
412
|
});
|
|
406
413
|
found = true;
|
|
407
414
|
break;
|
|
408
415
|
} catch (err) {
|
|
409
|
-
//
|
|
416
|
+
// Phase 7.4: Detect early exit signals
|
|
417
|
+
if (err.message && err.message.includes('Timeout')) {
|
|
418
|
+
earlyExitReason = 'Target never appeared (DOM settled)';
|
|
419
|
+
}
|
|
410
420
|
}
|
|
411
421
|
}
|
|
412
422
|
|
|
413
423
|
if (!found) {
|
|
414
|
-
|
|
424
|
+
// Phase 7.4: Include early exit reason
|
|
425
|
+
const errorMsg = earlyExitReason
|
|
426
|
+
? `${earlyExitReason}: ${stepDef.target}`
|
|
427
|
+
: `Element not found: ${stepDef.target}`;
|
|
428
|
+
throw new Error(errorMsg);
|
|
415
429
|
}
|
|
416
430
|
break;
|
|
417
431
|
|