@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.
- package/CHANGELOG.md +20 -0
- package/LICENSE +21 -0
- package/README.md +141 -0
- package/bin/guardian.js +690 -0
- package/flows/example-login-flow.json +36 -0
- package/flows/example-signup-flow.json +44 -0
- package/guardian-contract-v1.md +149 -0
- package/guardian.config.json +54 -0
- package/guardian.policy.json +12 -0
- package/guardian.profile.docs.yaml +18 -0
- package/guardian.profile.ecommerce.yaml +17 -0
- package/guardian.profile.marketing.yaml +18 -0
- package/guardian.profile.saas.yaml +21 -0
- package/package.json +69 -0
- package/policies/enterprise.json +12 -0
- package/policies/saas.json +12 -0
- package/policies/startup.json +12 -0
- package/src/guardian/attempt-engine.js +454 -0
- package/src/guardian/attempt-registry.js +227 -0
- package/src/guardian/attempt-reporter.js +507 -0
- package/src/guardian/attempt.js +227 -0
- package/src/guardian/auto-attempt-builder.js +283 -0
- package/src/guardian/baseline-reporter.js +143 -0
- package/src/guardian/baseline-storage.js +285 -0
- package/src/guardian/baseline.js +492 -0
- package/src/guardian/behavioral-signals.js +261 -0
- package/src/guardian/breakage-intelligence.js +223 -0
- package/src/guardian/browser.js +92 -0
- package/src/guardian/cli-summary.js +141 -0
- package/src/guardian/crawler.js +142 -0
- package/src/guardian/discovery-engine.js +661 -0
- package/src/guardian/enhanced-html-reporter.js +305 -0
- package/src/guardian/failure-taxonomy.js +169 -0
- package/src/guardian/flow-executor.js +374 -0
- package/src/guardian/flow-registry.js +67 -0
- package/src/guardian/html-reporter.js +414 -0
- package/src/guardian/index.js +218 -0
- package/src/guardian/init-command.js +139 -0
- package/src/guardian/junit-reporter.js +264 -0
- package/src/guardian/market-criticality.js +335 -0
- package/src/guardian/market-reporter.js +305 -0
- package/src/guardian/network-trace.js +178 -0
- package/src/guardian/policy.js +357 -0
- package/src/guardian/preset-loader.js +148 -0
- package/src/guardian/reality.js +547 -0
- package/src/guardian/reporter.js +181 -0
- package/src/guardian/root-cause-analysis.js +171 -0
- package/src/guardian/safety.js +248 -0
- package/src/guardian/scan-presets.js +60 -0
- package/src/guardian/screenshot.js +152 -0
- package/src/guardian/sitemap.js +225 -0
- package/src/guardian/snapshot-schema.js +266 -0
- package/src/guardian/snapshot.js +327 -0
- package/src/guardian/validators.js +323 -0
- package/src/guardian/visual-diff.js +247 -0
- package/src/guardian/webhook.js +206 -0
package/bin/guardian.js
ADDED
|
@@ -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
|
+
});
|