@odavl/guardian 0.1.0-rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/LICENSE +21 -0
  3. package/README.md +141 -0
  4. package/bin/guardian.js +690 -0
  5. package/flows/example-login-flow.json +36 -0
  6. package/flows/example-signup-flow.json +44 -0
  7. package/guardian-contract-v1.md +149 -0
  8. package/guardian.config.json +54 -0
  9. package/guardian.policy.json +12 -0
  10. package/guardian.profile.docs.yaml +18 -0
  11. package/guardian.profile.ecommerce.yaml +17 -0
  12. package/guardian.profile.marketing.yaml +18 -0
  13. package/guardian.profile.saas.yaml +21 -0
  14. package/package.json +69 -0
  15. package/policies/enterprise.json +12 -0
  16. package/policies/saas.json +12 -0
  17. package/policies/startup.json +12 -0
  18. package/src/guardian/attempt-engine.js +454 -0
  19. package/src/guardian/attempt-registry.js +227 -0
  20. package/src/guardian/attempt-reporter.js +507 -0
  21. package/src/guardian/attempt.js +227 -0
  22. package/src/guardian/auto-attempt-builder.js +283 -0
  23. package/src/guardian/baseline-reporter.js +143 -0
  24. package/src/guardian/baseline-storage.js +285 -0
  25. package/src/guardian/baseline.js +492 -0
  26. package/src/guardian/behavioral-signals.js +261 -0
  27. package/src/guardian/breakage-intelligence.js +223 -0
  28. package/src/guardian/browser.js +92 -0
  29. package/src/guardian/cli-summary.js +141 -0
  30. package/src/guardian/crawler.js +142 -0
  31. package/src/guardian/discovery-engine.js +661 -0
  32. package/src/guardian/enhanced-html-reporter.js +305 -0
  33. package/src/guardian/failure-taxonomy.js +169 -0
  34. package/src/guardian/flow-executor.js +374 -0
  35. package/src/guardian/flow-registry.js +67 -0
  36. package/src/guardian/html-reporter.js +414 -0
  37. package/src/guardian/index.js +218 -0
  38. package/src/guardian/init-command.js +139 -0
  39. package/src/guardian/junit-reporter.js +264 -0
  40. package/src/guardian/market-criticality.js +335 -0
  41. package/src/guardian/market-reporter.js +305 -0
  42. package/src/guardian/network-trace.js +178 -0
  43. package/src/guardian/policy.js +357 -0
  44. package/src/guardian/preset-loader.js +148 -0
  45. package/src/guardian/reality.js +547 -0
  46. package/src/guardian/reporter.js +181 -0
  47. package/src/guardian/root-cause-analysis.js +171 -0
  48. package/src/guardian/safety.js +248 -0
  49. package/src/guardian/scan-presets.js +60 -0
  50. package/src/guardian/screenshot.js +152 -0
  51. package/src/guardian/sitemap.js +225 -0
  52. package/src/guardian/snapshot-schema.js +266 -0
  53. package/src/guardian/snapshot.js +327 -0
  54. package/src/guardian/validators.js +323 -0
  55. package/src/guardian/visual-diff.js +247 -0
  56. package/src/guardian/webhook.js +206 -0
@@ -0,0 +1,690 @@
1
+ #!/usr/bin/env node
2
+ const { runAttemptCLI } = require('../src/guardian/attempt');
3
+ const { runRealityCLI } = require('../src/guardian/reality');
4
+ const { runGuardian } = require('../src/guardian');
5
+ const { saveBaseline, checkBaseline } = require('../src/guardian/baseline');
6
+ const { getDefaultAttemptIds } = require('../src/guardian/attempt-registry');
7
+ const { initGuardian } = require('../src/guardian/init-command');
8
+ const { printPresets } = require('../src/guardian/preset-loader');
9
+
10
+ function parseArgs(argv) {
11
+ const args = argv.slice(2);
12
+ const subcommand = args[0];
13
+
14
+ if (subcommand === 'init') {
15
+ return { subcommand: 'init', config: parseInitArgs(args.slice(1)) };
16
+ }
17
+
18
+ if (subcommand === 'protect') {
19
+ return { subcommand: 'protect', config: parseProtectArgs(args.slice(1)) };
20
+ }
21
+
22
+ if (subcommand === 'presets') {
23
+ return { subcommand: 'presets', config: {} };
24
+ }
25
+
26
+ if (subcommand === 'attempt') {
27
+ return { subcommand: 'attempt', config: parseAttemptArgs(args.slice(1)) };
28
+ }
29
+
30
+ if (subcommand === 'reality') {
31
+ return { subcommand: 'reality', config: parseRealityArgs(args.slice(1)) };
32
+ }
33
+
34
+ if (subcommand === 'baseline') {
35
+ const action = args[1];
36
+ if (action === 'save') {
37
+ return { subcommand: 'baseline-save', config: parseBaselineSaveArgs(args.slice(2)) };
38
+ }
39
+ if (action === 'check') {
40
+ return { subcommand: 'baseline-check', config: parseBaselineCheckArgs(args.slice(2)) };
41
+ }
42
+ printHelpBaseline();
43
+ process.exit(0);
44
+ }
45
+
46
+ // Phase 6: Productized one-command scan
47
+ if (subcommand === 'scan') {
48
+ return { subcommand: 'scan', config: parseScanArgs(args.slice(1)) };
49
+ }
50
+
51
+ // Legacy default: crawl
52
+ return { subcommand: 'crawl', config: parseCrawlArgs(args) };
53
+ }
54
+
55
+ function parseCrawlArgs(args) {
56
+ const config = {
57
+ maxPages: 25,
58
+ maxDepth: 3,
59
+ timeout: 20000,
60
+ artifactsDir: './artifacts'
61
+ };
62
+
63
+ for (let i = 0; i < args.length; i++) {
64
+ if (args[i] === '--url' && args[i + 1]) {
65
+ config.baseUrl = args[i + 1];
66
+ i++;
67
+ }
68
+ if (args[i] === '--max-pages' && args[i + 1]) {
69
+ config.maxPages = parseInt(args[i + 1], 10);
70
+ i++;
71
+ }
72
+ if (args[i] === '--max-depth' && args[i + 1]) {
73
+ config.maxDepth = parseInt(args[i + 1], 10);
74
+ i++;
75
+ }
76
+ if (args[i] === '--timeout' && args[i + 1]) {
77
+ config.timeout = parseInt(args[i + 1], 10);
78
+ i++;
79
+ }
80
+ if (args[i] === '--artifacts' && args[i + 1]) {
81
+ config.artifactsDir = args[i + 1];
82
+ i++;
83
+ }
84
+ if (args[i] === '--help' || args[i] === '-h') {
85
+ printHelpCrawl();
86
+ process.exit(0);
87
+ }
88
+ }
89
+
90
+ if (!config.baseUrl) {
91
+ console.error('Error: --url is required');
92
+ console.error('Usage: guardian --url <baseUrl> [options]');
93
+ process.exit(2);
94
+ }
95
+
96
+ return config;
97
+ }
98
+
99
+ // Phase 6: Scan command (one-command value)
100
+ function parseScanArgs(args) {
101
+ const config = {
102
+ // core
103
+ baseUrl: undefined,
104
+ artifactsDir: './artifacts',
105
+ // enable full pipeline
106
+ enableCrawl: true,
107
+ enableDiscovery: true,
108
+ enableAutoAttempts: true,
109
+ enableFlows: true,
110
+ // attempts & flows (will be overridden by presets)
111
+ attempts: getDefaultAttemptIds(),
112
+ flows: undefined,
113
+ // policy (optional)
114
+ policy: null,
115
+ // UX
116
+ headful: false,
117
+ enableTrace: true,
118
+ enableScreenshots: true,
119
+ // preset
120
+ preset: 'landing'
121
+ };
122
+
123
+ // First arg is URL if it doesn't start with --
124
+ if (args.length > 0 && !args[0].startsWith('--')) {
125
+ config.baseUrl = args[0];
126
+ args = args.slice(1);
127
+ }
128
+
129
+ for (let i = 0; i < args.length; i++) {
130
+ const a = args[i];
131
+ if (a === '--url' && args[i + 1]) { config.baseUrl = args[i + 1]; i++; }
132
+ else if (a.startsWith('--preset=')) { config.preset = a.split('=')[1]; }
133
+ else if (a === '--preset' && args[i + 1]) { config.preset = args[i + 1]; i++; }
134
+ else if (a === '--artifacts' && args[i + 1]) { config.artifactsDir = args[i + 1]; i++; }
135
+ else if (a === '--policy' && args[i + 1]) { config.policy = args[i + 1]; i++; }
136
+ else if (a === '--headful') { config.headful = true; }
137
+ else if (a === '--no-trace') { config.enableTrace = false; }
138
+ else if (a === '--no-screenshots') { config.enableScreenshots = false; }
139
+ else if (a === '--help' || a === '-h') { printHelpScan(); process.exit(0); }
140
+ }
141
+
142
+ if (!config.baseUrl) {
143
+ console.error('Error: <url> is required');
144
+ console.error('Usage: guardian scan <url> [--preset <landing|saas|shop>]');
145
+ process.exit(2);
146
+ }
147
+
148
+ // Apply scan preset overrides
149
+ try {
150
+ const { resolveScanPreset } = require('../src/guardian/scan-presets');
151
+ const presetCfg = resolveScanPreset(config.preset);
152
+ config.attempts = presetCfg.attempts;
153
+ config.flows = presetCfg.flows;
154
+ if (presetCfg.policy) {
155
+ // Allow users to override via --policy
156
+ config.policy = config.policy || presetCfg.policy;
157
+ }
158
+ } catch (e) {
159
+ // If presets not available, proceed with defaults
160
+ }
161
+
162
+ return config;
163
+ }
164
+
165
+ function parseRealityArgs(args) {
166
+ const config = {
167
+ artifactsDir: './artifacts',
168
+ attempts: getDefaultAttemptIds(),
169
+ headful: false,
170
+ enableTrace: true,
171
+ enableScreenshots: true,
172
+ enableDiscovery: false,
173
+ includeUniversal: false,
174
+ policy: null,
175
+ webhook: null
176
+ };
177
+
178
+ for (let i = 0; i < args.length; i++) {
179
+ if (args[i] === '--url' && args[i + 1]) {
180
+ config.baseUrl = args[i + 1];
181
+ i++;
182
+ }
183
+ if (args[i] === '--attempts' && args[i + 1]) {
184
+ config.attempts = args[i + 1].split(',').map(s => s.trim()).filter(Boolean);
185
+ i++;
186
+ }
187
+ if (args[i] === '--artifacts' && args[i + 1]) {
188
+ config.artifactsDir = args[i + 1];
189
+ i++;
190
+ }
191
+ if (args[i] === '--policy' && args[i + 1]) {
192
+ config.policy = args[i + 1];
193
+ i++;
194
+ }
195
+ if (args[i] === '--discover') {
196
+ config.enableDiscovery = true;
197
+ }
198
+ if (args[i] === '--universal') {
199
+ config.includeUniversal = true;
200
+ }
201
+ if (args[i] === '--webhook' && args[i + 1]) {
202
+ config.webhook = args[i + 1];
203
+ i++;
204
+ }
205
+ if (args[i] === '--headful') {
206
+ config.headful = true;
207
+ }
208
+ if (args[i] === '--no-trace') {
209
+ config.enableTrace = false;
210
+ }
211
+ if (args[i] === '--no-screenshots') {
212
+ config.enableScreenshots = false;
213
+ }
214
+ if (args[i] === '--help' || args[i] === '-h') {
215
+ printHelpReality();
216
+ process.exit(0);
217
+ }
218
+ }
219
+
220
+ if (!config.baseUrl) {
221
+ console.error('Error: --url is required');
222
+ console.error('Usage: guardian reality --url <baseUrl> [options]');
223
+ process.exit(2);
224
+ }
225
+
226
+ return config;
227
+ }
228
+
229
+ function parseInitArgs(args) {
230
+ const config = {
231
+ preset: 'startup'
232
+ };
233
+
234
+ for (let i = 0; i < args.length; i++) {
235
+ if (args[i] === '--preset' && args[i + 1]) {
236
+ config.preset = args[i + 1];
237
+ i++;
238
+ }
239
+ if (args[i] === '--help' || args[i] === '-h') {
240
+ printHelpInit();
241
+ process.exit(0);
242
+ }
243
+ }
244
+
245
+ return config;
246
+ }
247
+
248
+ function parseProtectArgs(args) {
249
+ const config = {
250
+ artifactsDir: './artifacts',
251
+ attempts: getDefaultAttemptIds(),
252
+ headful: false,
253
+ enableTrace: true,
254
+ enableScreenshots: true,
255
+ policy: 'preset:startup',
256
+ webhook: null
257
+ };
258
+
259
+ // First arg is URL if it doesn't start with --
260
+ if (args.length > 0 && !args[0].startsWith('--')) {
261
+ config.baseUrl = args[0];
262
+ args = args.slice(1);
263
+ }
264
+
265
+ for (let i = 0; i < args.length; i++) {
266
+ if (args[i] === '--url' && args[i + 1]) {
267
+ config.baseUrl = args[i + 1];
268
+ i++;
269
+ }
270
+ if (args[i] === '--policy' && args[i + 1]) {
271
+ config.policy = args[i + 1];
272
+ i++;
273
+ }
274
+ if (args[i] === '--webhook' && args[i + 1]) {
275
+ config.webhook = args[i + 1];
276
+ i++;
277
+ }
278
+ if (args[i] === '--help' || args[i] === '-h') {
279
+ printHelpProtect();
280
+ process.exit(0);
281
+ }
282
+ }
283
+
284
+ if (!config.baseUrl) {
285
+ console.error('Error: <url> is required');
286
+ console.error('Usage: guardian protect <url> [options]');
287
+ process.exit(2);
288
+ }
289
+
290
+ return config;
291
+ }
292
+
293
+ function parseAttemptArgs(args) {
294
+ const config = {
295
+ attemptId: 'contact_form',
296
+ artifactsDir: './artifacts',
297
+ enableTrace: true,
298
+ enableScreenshots: true,
299
+ headful: false
300
+ };
301
+
302
+ for (let i = 0; i < args.length; i++) {
303
+ if (args[i] === '--url' && args[i + 1]) {
304
+ config.baseUrl = args[i + 1];
305
+ i++;
306
+ }
307
+ if (args[i] === '--attempt' && args[i + 1]) {
308
+ config.attemptId = args[i + 1];
309
+ i++;
310
+ }
311
+ if (args[i] === '--artifacts' && args[i + 1]) {
312
+ config.artifactsDir = args[i + 1];
313
+ i++;
314
+ }
315
+ if (args[i] === '--headful') {
316
+ config.headful = true;
317
+ }
318
+ if (args[i] === '--no-trace') {
319
+ config.enableTrace = false;
320
+ }
321
+ if (args[i] === '--no-screenshots') {
322
+ config.enableScreenshots = false;
323
+ }
324
+ if (args[i] === '--help' || args[i] === '-h') {
325
+ printHelpAttempt();
326
+ process.exit(0);
327
+ }
328
+ }
329
+
330
+ if (!config.baseUrl) {
331
+ console.error('Error: --url is required');
332
+ console.error('Usage: guardian attempt --url <baseUrl> --attempt <id> [options]');
333
+ process.exit(2);
334
+ }
335
+
336
+ return config;
337
+ }
338
+
339
+ function parseBaselineSaveArgs(args) {
340
+ const config = {
341
+ artifactsDir: './artifacts',
342
+ attempts: getDefaultAttemptIds(),
343
+ headful: false,
344
+ enableTrace: true,
345
+ enableScreenshots: true,
346
+ name: 'baseline',
347
+ baselineDir: undefined
348
+ };
349
+
350
+ for (let i = 0; i < args.length; i++) {
351
+ if (args[i] === '--url' && args[i + 1]) { config.baseUrl = args[i + 1]; i++; }
352
+ else if (args[i] === '--attempts' && args[i + 1]) { config.attempts = args[i + 1].split(',').map(s=>s.trim()).filter(Boolean); i++; }
353
+ else if (args[i] === '--name' && args[i + 1]) { config.name = args[i + 1]; i++; }
354
+ else if (args[i] === '--artifacts' && args[i + 1]) { config.artifactsDir = args[i + 1]; i++; }
355
+ else if (args[i] === '--headful') { config.headful = true; }
356
+ else if (args[i] === '--no-trace') { config.enableTrace = false; }
357
+ else if (args[i] === '--no-screenshots') { config.enableScreenshots = false; }
358
+ else if (args[i] === '--baseline-dir' && args[i + 1]) { config.baselineDir = args[i + 1]; i++; }
359
+ else if (args[i] === '--help' || args[i] === '-h') { printHelpBaselineSave(); process.exit(0); }
360
+ }
361
+
362
+ if (!config.baseUrl) {
363
+ console.error('Error: --url is required');
364
+ console.error('Usage: guardian baseline save --url <baseUrl> [options]');
365
+ process.exit(2);
366
+ }
367
+ return config;
368
+ }
369
+
370
+ function parseBaselineCheckArgs(args) {
371
+ const config = {
372
+ artifactsDir: './artifacts',
373
+ attempts: getDefaultAttemptIds(),
374
+ headful: false,
375
+ enableTrace: true,
376
+ enableScreenshots: true,
377
+ baselineDir: undefined,
378
+ junit: undefined
379
+ };
380
+
381
+ for (let i = 0; i < args.length; i++) {
382
+ if (args[i] === '--url' && args[i + 1]) { config.baseUrl = args[i + 1]; i++; }
383
+ else if (args[i] === '--name' && args[i + 1]) { config.name = args[i + 1]; i++; }
384
+ else if (args[i] === '--attempts' && args[i + 1]) { config.attempts = args[i + 1].split(',').map(s=>s.trim()).filter(Boolean); i++; }
385
+ else if (args[i] === '--artifacts' && args[i + 1]) { config.artifactsDir = args[i + 1]; i++; }
386
+ else if (args[i] === '--headful') { config.headful = true; }
387
+ else if (args[i] === '--no-trace') { config.enableTrace = false; }
388
+ else if (args[i] === '--no-screenshots') { config.enableScreenshots = false; }
389
+ else if (args[i] === '--baseline-dir' && args[i + 1]) { config.baselineDir = args[i + 1]; i++; }
390
+ else if (args[i] === '--junit' && args[i + 1]) { config.junit = args[i + 1]; i++; }
391
+ else if (args[i] === '--help' || args[i] === '-h') { printHelpBaselineCheck(); process.exit(0); }
392
+ }
393
+
394
+ if (!config.baseUrl || !config.name) {
395
+ console.error('Error: --url and --name are required');
396
+ console.error('Usage: guardian baseline check --url <baseUrl> --name <baselineName> [options]');
397
+ process.exit(2);
398
+ }
399
+ return config;
400
+ }
401
+
402
+ function printHelpReality() {
403
+ console.log(`
404
+ Usage: guardian reality --url <baseUrl> [options]
405
+
406
+ WHAT IT DOES:
407
+ Executes a Market Reality Snapshot v1:
408
+ 1. Crawls your site to discover URLs
409
+ 2. Runs curated user attempts (e.g., contact form, language toggle)
410
+ 3. Auto-creates baseline on first run (no manual 'baseline save' needed)
411
+ 4. Compares subsequent runs against baseline for regressions
412
+ 5. Produces snapshot.json with evidence (screenshots, traces, reports)
413
+
414
+ OPTIONS:
415
+ --url <url> Target URL (required)
416
+ --attempts <id1,id2> Comma-separated attempt IDs (default: contact_form, language_switch, newsletter_signup)
417
+ --artifacts <dir> Artifacts directory (default: ./artifacts)
418
+ --discover Run deterministic CLI discovery and include in snapshot
419
+ --universal Include Universal Reality Pack attempt
420
+ --policy <path|preset> Policy file path or preset:name (e.g., preset:startup)
421
+ --webhook <url> Webhook URL for notifications
422
+ --headful Run headed browser (default: headless)
423
+ --no-trace Disable trace recording
424
+ --no-screenshots Disable screenshots
425
+ --help Show this help message
426
+
427
+ EXIT CODES:
428
+ 0 Success (first run baseline created, or no regressions)
429
+ 1 FAILURE (regression detected or policy failed)
430
+ 2 FRICTION (drift without critical failure or soft policy failure)
431
+
432
+ EXAMPLES:
433
+ First run (baseline auto-created):
434
+ guardian reality --url https://example.com
435
+
436
+ With policy preset:
437
+ guardian reality --url https://example.com --policy preset:saas
438
+
439
+ With custom policy:
440
+ guardian reality --url https://example.com --policy ./my-policy.json
441
+ `);
442
+ }
443
+
444
+ function printHelpInit() {
445
+ console.log(`
446
+ Usage: guardian init [options]
447
+
448
+ WHAT IT DOES:
449
+ Initialize Guardian in the current directory:
450
+ - Creates guardian.policy.json (default: startup preset)
451
+ - Updates .gitignore to exclude Guardian artifacts
452
+ - Prints next steps
453
+
454
+ OPTIONS:
455
+ --preset <name> Policy preset to use (startup, saas, enterprise)
456
+ Default: startup
457
+ --help Show this help message
458
+
459
+ EXAMPLE:
460
+ guardian init
461
+ guardian init --preset saas
462
+ `);
463
+ }
464
+
465
+ function printHelpProtect() {
466
+ console.log(`
467
+ Usage: guardian protect <url> [options]
468
+
469
+ WHAT IT DOES:
470
+ Quick shortcut for reality check with startup policy.
471
+ Equivalent to: guardian reality --url <url> --policy preset:startup
472
+
473
+ OPTIONS:
474
+ <url> Target URL (required)
475
+ --policy <path|preset> Override policy (default: preset:startup)
476
+ --webhook <url> Webhook URL for notifications
477
+ --help Show this help message
478
+
479
+ EXAMPLES:
480
+ guardian protect https://example.com
481
+ guardian protect https://example.com --policy preset:enterprise
482
+ `);
483
+ }
484
+
485
+ function printHelpCrawl() {
486
+ console.log(`
487
+ Usage: guardian --url <baseUrl> [options]
488
+
489
+ Options:
490
+ --url <url> Target URL (required)
491
+ --max-pages <n> Maximum pages to visit (default: 25)
492
+ --max-depth <n> Maximum crawl depth (default: 3)
493
+ --timeout <ms> Navigation timeout in ms (default: 20000)
494
+ --artifacts <dir> Artifacts directory (default: ./artifacts)
495
+ --help Show this help message
496
+ `);
497
+ }
498
+
499
+ function printHelpAttempt() {
500
+ console.log(`
501
+ Usage: guardian attempt --url <baseUrl> --attempt <id> [options]
502
+
503
+ Options:
504
+ --url <url> Target URL (required)
505
+ --attempt <id> Attempt ID (default: contact_form)
506
+ --artifacts <dir> Artifacts directory (default: ./artifacts)
507
+ --headful Run with visible browser (default: headless)
508
+ --no-trace Disable trace recording
509
+ --no-screenshots Disable screenshot capture
510
+ --help Show this help message
511
+
512
+ Exit Codes:
513
+ 0 Attempt succeeded
514
+ 1 Attempt failed
515
+ 2 Attempt succeeded with friction
516
+ `);
517
+ }
518
+
519
+ function printHelpScan() {
520
+ console.log(`
521
+ Usage: guardian scan <url> [options]
522
+
523
+ WHAT IT DOES:
524
+ One-command product scan. Runs:
525
+ 1) Discovery (light crawl)
526
+ 2) Auto-attempts (from discoveries)
527
+ 3) Intent flows (curated)
528
+ 4) Baseline compare (auto on first run)
529
+ 5) Intelligence + visual checks + report
530
+
531
+ OPTIONS:
532
+ <url> Target URL (required)
533
+ --preset <name> landing | saas | shop (opinionated defaults)
534
+ --policy <path|preset> Override policy file or preset:name
535
+ --artifacts <dir> Artifacts directory (default: ./artifacts)
536
+ --headful Run headed browser
537
+ --no-trace Disable trace
538
+ --no-screenshots Disable screenshots
539
+ --help Show help
540
+
541
+ EXAMPLES:
542
+ guardian scan https://example.com --preset landing
543
+ guardian scan https://example.com --preset saas
544
+ guardian scan https://example.com --preset shop
545
+ `);
546
+ }
547
+
548
+ function printHelpBaseline() {
549
+ console.log(`
550
+ Usage: guardian baseline <save|check> [options]
551
+
552
+ Subcommands:
553
+ baseline save Capture a baseline snapshot from a reality run
554
+ baseline check Compare current reality run against a saved baseline
555
+
556
+ Run 'guardian baseline <sub> --help' for details.
557
+ `);
558
+ }
559
+
560
+ function printHelpBaselineSave() {
561
+ console.log(`
562
+ Usage: guardian baseline save --url <baseUrl> [options]
563
+
564
+ Options:
565
+ --url <url> Target URL (required)
566
+ --attempts <id1,id2> Comma-separated attempt IDs (default: curated 3)
567
+ --name <baselineName> Baseline name (default: baseline)
568
+ --artifacts <dir> Artifacts directory (default: ./artifacts)
569
+ --headful Run headed browser (default: headless)
570
+ --no-trace Disable trace recording
571
+ --no-screenshots Disable screenshots
572
+ --baseline-dir <path> Directory to store baseline JSON (default: artifacts/baselines)
573
+
574
+ Exit Codes:
575
+ 0 Baseline saved successfully
576
+ `);
577
+ }
578
+
579
+ function printHelpBaselineCheck() {
580
+ console.log(`
581
+ Usage: guardian baseline check --url <baseUrl> --name <baselineName> [options]
582
+
583
+ Options:
584
+ --url <url> Target URL (required)
585
+ --name <baselineName> Baseline name to compare against (required)
586
+ --attempts <id1,id2> Comma-separated attempt IDs (default: curated 3)
587
+ --artifacts <dir> Artifacts directory (default: ./artifacts)
588
+ --headful Run headed browser (default: headless)
589
+ --no-trace Disable trace recording
590
+ --no-screenshots Disable screenshots
591
+ --baseline-dir <path> Directory to load baseline JSON from (default: artifacts/baselines)
592
+ --junit <path> Write JUnit XML summary to the given path
593
+
594
+ Exit Codes:
595
+ 0 No regression
596
+ 3 Regression in friction metrics
597
+ 4 Regression failure
598
+ 1 Internal error (baseline missing, parse error)
599
+ `);
600
+ }
601
+
602
+ async function main() {
603
+ const args = process.argv.slice(2);
604
+
605
+ if (args.length === 0 || args[0] === '--help' || args[0] === '-h') {
606
+ console.log(`
607
+ 🛡️ ODAVL Guardian — Market Reality Testing Engine
608
+
609
+ Usage: guardian <subcommand> [options]
610
+
611
+ QUICK START:
612
+ init Initialize Guardian in current directory
613
+ protect <url> Quick reality check with startup policy
614
+ reality Full Market Reality Snapshot
615
+
616
+ OTHER COMMANDS:
617
+ attempt Execute a single user attempt
618
+ baseline save (Legacy) Manually save baseline
619
+ baseline check (Legacy) Manually check against baseline
620
+ presets List available policy presets
621
+
622
+ EXAMPLES:
623
+ # Initialize Guardian
624
+ guardian init
625
+
626
+ # Quick protect (uses startup policy)
627
+ guardian protect https://example.com
628
+
629
+ # Full reality check with policy
630
+ guardian reality --url https://example.com --policy preset:saas
631
+
632
+ Run 'guardian <subcommand> --help' for more information.
633
+ `);
634
+ process.exit(0);
635
+ }
636
+
637
+ // Parse arguments (which handles subcommand routing)
638
+ const parsed = parseArgs(process.argv);
639
+ const config = parsed.config;
640
+
641
+ // Determine which mode to run
642
+ if (parsed.subcommand === 'init') {
643
+ initGuardian(config);
644
+ process.exit(0);
645
+ } else if (parsed.subcommand === 'presets') {
646
+ printPresets();
647
+ process.exit(0);
648
+ } else if (parsed.subcommand === 'protect') {
649
+ await runRealityCLI(config);
650
+ } else if (parsed.subcommand === 'attempt') {
651
+ await runAttemptCLI(config);
652
+ } else if (parsed.subcommand === 'reality') {
653
+ await runRealityCLI(config);
654
+ } else if (parsed.subcommand === 'scan') {
655
+ // Phase 6: First-run concise guidance
656
+ try {
657
+ const { baselineExists } = require('../src/guardian/baseline-storage');
658
+ if (!baselineExists(config.baseUrl, '.odavl-guardian')) {
659
+ console.log('\nℹ️ First run: Guardian will discover pages, run attempts & flows,');
660
+ console.log(' auto-create a baseline, check visuals & intelligence, and');
661
+ console.log(' save reports under artifacts/<market-run-*>/.');
662
+ console.log(' FAIL = policy/regression; WARN = risks without fail.\n');
663
+ }
664
+ } catch {}
665
+ await runRealityCLI(config);
666
+ } else if (parsed.subcommand === 'baseline-save') {
667
+ try {
668
+ const res = await saveBaseline(config);
669
+ process.exit(res.exitCode);
670
+ } catch (err) {
671
+ console.error(`\n❌ Error: ${err.message}`);
672
+ process.exit(1);
673
+ }
674
+ } else if (parsed.subcommand === 'baseline-check') {
675
+ try {
676
+ const res = await checkBaseline(config);
677
+ process.exit(res.exitCode);
678
+ } catch (err) {
679
+ console.error(`\n❌ Error: ${err.message}`);
680
+ process.exit(1);
681
+ }
682
+ } else {
683
+ runGuardian(config);
684
+ }
685
+ }
686
+
687
+ main().catch(err => {
688
+ console.error('Fatal error:', err);
689
+ process.exit(2);
690
+ });