@odavl/guardian 0.2.0 → 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.
Files changed (84) hide show
  1. package/CHANGELOG.md +86 -2
  2. package/README.md +155 -97
  3. package/bin/guardian.js +1345 -60
  4. package/config/README.md +59 -0
  5. package/config/profiles/landing-demo.yaml +16 -0
  6. package/package.json +21 -11
  7. package/policies/landing-demo.json +22 -0
  8. package/src/enterprise/audit-logger.js +166 -0
  9. package/src/enterprise/pdf-exporter.js +267 -0
  10. package/src/enterprise/rbac-gate.js +142 -0
  11. package/src/enterprise/rbac.js +239 -0
  12. package/src/enterprise/site-manager.js +180 -0
  13. package/src/founder/feedback-system.js +156 -0
  14. package/src/founder/founder-tracker.js +213 -0
  15. package/src/founder/usage-signals.js +141 -0
  16. package/src/guardian/alert-ledger.js +121 -0
  17. package/src/guardian/attempt-engine.js +568 -7
  18. package/src/guardian/attempt-registry.js +42 -1
  19. package/src/guardian/attempt-relevance.js +106 -0
  20. package/src/guardian/attempt.js +24 -0
  21. package/src/guardian/baseline.js +12 -4
  22. package/src/guardian/breakage-intelligence.js +1 -0
  23. package/src/guardian/ci-cli.js +121 -0
  24. package/src/guardian/ci-output.js +4 -3
  25. package/src/guardian/cli-summary.js +79 -92
  26. package/src/guardian/config-loader.js +162 -0
  27. package/src/guardian/drift-detector.js +100 -0
  28. package/src/guardian/enhanced-html-reporter.js +221 -4
  29. package/src/guardian/env-guard.js +127 -0
  30. package/src/guardian/failure-intelligence.js +173 -0
  31. package/src/guardian/first-run-profile.js +89 -0
  32. package/src/guardian/first-run.js +6 -1
  33. package/src/guardian/flag-validator.js +17 -3
  34. package/src/guardian/html-reporter.js +2 -0
  35. package/src/guardian/human-reporter.js +431 -0
  36. package/src/guardian/index.js +22 -19
  37. package/src/guardian/init-command.js +9 -5
  38. package/src/guardian/intent-detector.js +146 -0
  39. package/src/guardian/journey-definitions.js +132 -0
  40. package/src/guardian/journey-scan-cli.js +145 -0
  41. package/src/guardian/journey-scanner.js +583 -0
  42. package/src/guardian/junit-reporter.js +18 -1
  43. package/src/guardian/live-cli.js +95 -0
  44. package/src/guardian/live-scheduler-runner.js +137 -0
  45. package/src/guardian/live-scheduler.js +146 -0
  46. package/src/guardian/market-reporter.js +341 -81
  47. package/src/guardian/pattern-analyzer.js +348 -0
  48. package/src/guardian/policy.js +80 -3
  49. package/src/guardian/preset-loader.js +9 -6
  50. package/src/guardian/reality.js +1278 -117
  51. package/src/guardian/reporter.js +27 -41
  52. package/src/guardian/run-artifacts.js +212 -0
  53. package/src/guardian/run-cleanup.js +207 -0
  54. package/src/guardian/run-latest.js +90 -0
  55. package/src/guardian/run-list.js +211 -0
  56. package/src/guardian/scan-presets.js +100 -11
  57. package/src/guardian/selector-fallbacks.js +394 -0
  58. package/src/guardian/semantic-contact-finder.js +2 -1
  59. package/src/guardian/site-introspection.js +257 -0
  60. package/src/guardian/smoke.js +2 -2
  61. package/src/guardian/snapshot-schema.js +25 -1
  62. package/src/guardian/snapshot.js +46 -2
  63. package/src/guardian/stability-scorer.js +169 -0
  64. package/src/guardian/template-command.js +184 -0
  65. package/src/guardian/text-formatters.js +426 -0
  66. package/src/guardian/verdict.js +320 -0
  67. package/src/guardian/verdicts.js +74 -0
  68. package/src/guardian/watch-runner.js +3 -7
  69. package/src/payments/stripe-checkout.js +169 -0
  70. package/src/plans/plan-definitions.js +148 -0
  71. package/src/plans/plan-manager.js +211 -0
  72. package/src/plans/usage-tracker.js +210 -0
  73. package/src/recipes/recipe-engine.js +188 -0
  74. package/src/recipes/recipe-failure-analysis.js +159 -0
  75. package/src/recipes/recipe-registry.js +134 -0
  76. package/src/recipes/recipe-runtime.js +507 -0
  77. package/src/recipes/recipe-store.js +410 -0
  78. package/guardian-contract-v1.md +0 -149
  79. /package/{guardian.config.json → config/guardian.config.json} +0 -0
  80. /package/{guardian.policy.json → config/guardian.policy.json} +0 -0
  81. /package/{guardian.profile.docs.yaml → config/profiles/docs.yaml} +0 -0
  82. /package/{guardian.profile.ecommerce.yaml → config/profiles/ecommerce.yaml} +0 -0
  83. /package/{guardian.profile.marketing.yaml → config/profiles/marketing.yaml} +0 -0
  84. /package/{guardian.profile.saas.yaml → config/profiles/saas.yaml} +0 -0
package/bin/guardian.js CHANGED
@@ -5,6 +5,65 @@ if (process.platform === 'win32') {
5
5
  process.stderr.setEncoding('utf-8');
6
6
  }
7
7
 
8
+ // Minimal DX: handle --version/-v immediately (before any other work)
9
+ const args = process.argv.slice(2);
10
+ if (args.length === 1 && (args[0] === '--version' || args[0] === '-v')) {
11
+ try {
12
+ const pkg = require('../package.json');
13
+ console.log(pkg.version);
14
+ process.exit(0);
15
+ } catch (e) {
16
+ console.error('Version unavailable');
17
+ process.exit(1);
18
+ }
19
+ }
20
+
21
+ // GLOBAL HELP (Level 1): provide a real, working guardian --help
22
+ function printGlobalHelp() {
23
+ console.log(`
24
+ ODAVL Guardian — Level 1
25
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
26
+
27
+ Golden Path (Reality Check)
28
+ guardian --url <url>
29
+ guardian reality --url <url>
30
+
31
+ What happens
32
+ - Opens a real browser
33
+ - Runs curated reality attempts
34
+ - Writes reports to ./.odavlguardian/<run>/
35
+
36
+ Reports (Level 1)
37
+ - decision.json (finalVerdict, exitCode, explanation)
38
+ - summary.md (human-readable summary)
39
+
40
+ Canonical Verdicts
41
+ READY | FRICTION | DO_NOT_LAUNCH
42
+
43
+ VS Code (Level 1)
44
+ Command Palette → "Guardian: Run Reality Check"
45
+ - Uses ./.odavlguardian as artifacts directory
46
+ - Shows verdict and opens summary.md
47
+
48
+ Advanced (Level 2+)
49
+ guardian scan <url> One-command full scan (advanced)
50
+ guardian journey-scan <url> Single flow journey (advanced)
51
+ guardian smoke <url> Fast sanity (advanced)
52
+ guardian baseline <save|check> Baselines (advanced)
53
+ guardian list|cleanup Artifacts mgmt (advanced)
54
+
55
+ Tips
56
+ - Pass --artifacts <dir> to override artifacts directory
57
+ - Local config: guardian.config.json (CLI only)
58
+ `);
59
+ }
60
+
61
+ // Handle global --help (or no args) before heavy loads
62
+ if (args.length === 0 || (args.length === 1 && (args[0] === '--help' || args[0] === '-h'))) {
63
+ printGlobalHelp();
64
+ process.exit(0);
65
+ }
66
+
8
67
  // PHASE 6: Early flag validation (before heavy module loads)
9
68
  const { validateFlags, reportFlagError } = require('../src/guardian/flag-validator');
10
69
  const validation = validateFlags(process.argv);
@@ -14,16 +73,41 @@ if (!validation.valid) {
14
73
  }
15
74
 
16
75
  // PHASE 6: First-run detection (lightweight)
17
- const { isFirstRun, markAsRun, printWelcome } = require('../src/guardian/first-run');
76
+ const { isFirstRun, markAsRun, printWelcome, printFirstRunHint } = require('../src/guardian/first-run');
18
77
 
19
78
  const { runAttemptCLI } = require('../src/guardian/attempt');
20
79
  const { runRealityCLI } = require('../src/guardian/reality');
21
80
  const { runSmokeCLI } = require('../src/guardian/smoke');
22
81
  const { runGuardian } = require('../src/guardian');
82
+ const { runJourneyScanCLI } = require('../src/guardian/journey-scan-cli');
83
+ const { runLiveCLI } = require('../src/guardian/live-cli');
23
84
  const { saveBaseline, checkBaseline } = require('../src/guardian/baseline');
24
85
  const { getDefaultAttemptIds } = require('../src/guardian/attempt-registry');
25
86
  const { initGuardian } = require('../src/guardian/init-command');
26
87
  const { printPresets } = require('../src/guardian/preset-loader');
88
+ const { listRuns } = require('../src/guardian/run-list');
89
+ const { cleanup } = require('../src/guardian/run-cleanup');
90
+ const { generateTemplate, listTemplates } = require('../src/guardian/template-command');
91
+
92
+ // Phase 8: Plan enforcement
93
+ const { checkCanScan, performScan, checkFeatureAllowed, getPlanSummary, getUpgradeMessage } = require('../src/plans/plan-manager');
94
+
95
+ // Phase 10: Founder tracking and feedback
96
+ const { registerUser, getFounderMessage, isFoundingUser } = require('../src/founder/founder-tracker');
97
+ const { runFeedbackSession } = require('../src/founder/feedback-system');
98
+ const { recordFirstScan, recordFirstLive, recordFirstUpgrade } = require('../src/founder/usage-signals');
99
+
100
+ // Phase 11: Enterprise features
101
+ const { addSite, removeSite, getSite, getSites, getSitesByProject, listProjects } = require('../src/enterprise/site-manager');
102
+ const { addUser, removeUser, getUsers, getCurrentUser, requirePermission, listRoles } = require('../src/enterprise/rbac');
103
+ const { logAudit, readAuditLogs, getAuditSummary, AUDIT_ACTIONS } = require('../src/enterprise/audit-logger');
104
+ const { exportReportToPDF, listAvailableReports } = require('../src/enterprise/pdf-exporter');
105
+
106
+ // Phase 12.1: Recipes
107
+ const { getAllRecipes, getRecipe, getRecipesByPlatform, addRecipe, removeRecipe, importRecipes, exportRecipes, exportRecipeWithMetadata, importRecipeWithMetadata } = require('../src/recipes/recipe-store');
108
+ const { validateRecipe, formatRecipe } = require('../src/recipes/recipe-engine');
109
+ const { getRegistryEntry, computeRecipeChecksum } = require('../src/recipes/recipe-registry');
110
+ const { resolveScanPreset } = require('../src/guardian/scan-presets');
27
111
 
28
112
  function parseArgs(argv) {
29
113
  const args = argv.slice(2);
@@ -44,6 +128,18 @@ function parseArgs(argv) {
44
128
  return { subcommand: 'presets', config: {} };
45
129
  }
46
130
 
131
+ if (subcommand === 'template') {
132
+ return { subcommand: 'template', config: parseTemplateArgs(args.slice(1)) };
133
+ }
134
+
135
+ if (subcommand === 'list') {
136
+ return { subcommand: 'list', config: parseListArgs(args.slice(1)) };
137
+ }
138
+
139
+ if (subcommand === 'cleanup') {
140
+ return { subcommand: 'cleanup', config: parseCleanupArgs(args.slice(1)) };
141
+ }
142
+
47
143
  if (subcommand === 'attempt') {
48
144
  return { subcommand: 'attempt', config: parseAttemptArgs(args.slice(1)) };
49
145
  }
@@ -56,6 +152,10 @@ function parseArgs(argv) {
56
152
  return { subcommand: 'smoke', config: parseSmokeArgs(args.slice(1)) };
57
153
  }
58
154
 
155
+ if (subcommand === 'check') {
156
+ return { subcommand: 'smoke', config: parseSmokeArgs(args.slice(1)) };
157
+ }
158
+
59
159
  if (subcommand === 'baseline') {
60
160
  const action = args[1];
61
161
  if (action === 'save') {
@@ -68,13 +168,87 @@ function parseArgs(argv) {
68
168
  process.exit(0);
69
169
  }
70
170
 
171
+ // MVP: Human Journey Scan
172
+ if (subcommand === 'journey-scan' || subcommand === 'journey') {
173
+ return { subcommand: 'journey-scan', config: parseJourneyScanArgs(args.slice(1)) };
174
+ }
175
+
176
+ if (subcommand === 'live') {
177
+ // Support scheduler subcommands: start|stop|status
178
+ const action = args[1];
179
+ if (action === 'start') {
180
+ return { subcommand: 'live-start', config: parseLiveStartArgs(args.slice(2)) };
181
+ }
182
+ if (action === 'stop') {
183
+ return { subcommand: 'live-stop', config: parseLiveStopArgs(args.slice(2)) };
184
+ }
185
+ if (action === 'status') {
186
+ return { subcommand: 'live-status', config: {} };
187
+ }
188
+ return { subcommand: 'live', config: parseLiveArgs(args.slice(1)) };
189
+ }
190
+
191
+ if (subcommand === 'ci') {
192
+ return { subcommand: 'ci', config: parseJourneyScanArgs(args.slice(1)) };
193
+ }
194
+
71
195
  // Phase 6: Productized one-command scan
72
196
  if (subcommand === 'scan') {
73
197
  return { subcommand: 'scan', config: parseScanArgs(args.slice(1)) };
74
198
  }
75
199
 
76
- // Legacy default: crawl
77
- return { subcommand: 'crawl', config: parseCrawlArgs(args) };
200
+ // Phase 8: Plan management
201
+ if (subcommand === 'plan') {
202
+ return { subcommand: 'plan', config: {} };
203
+ }
204
+
205
+ if (subcommand === 'upgrade') {
206
+ const targetPlan = args[1];
207
+ return { subcommand: 'upgrade', config: { plan: targetPlan } };
208
+ }
209
+
210
+ // Phase 10: Feedback command
211
+ if (subcommand === 'feedback') {
212
+ return { subcommand: 'feedback', config: {} };
213
+ }
214
+
215
+ // Phase 11: Enterprise commands
216
+ if (subcommand === 'sites') {
217
+ return { subcommand: 'sites', config: parseSitesArgs(args.slice(1)) };
218
+ }
219
+
220
+ if (subcommand === 'users') {
221
+ return { subcommand: 'users', config: parseUsersArgs(args.slice(1)) };
222
+ }
223
+
224
+ if (subcommand === 'audit') {
225
+ return { subcommand: 'audit', config: parseAuditArgs(args.slice(1)) };
226
+ }
227
+
228
+ if (subcommand === 'export') {
229
+ return { subcommand: 'export', config: parseExportArgs(args.slice(1)) };
230
+ }
231
+
232
+ // Phase 12.1: Recipes
233
+ if (subcommand === 'recipe') {
234
+ return { subcommand: 'recipe', config: parseRecipeArgs(args.slice(1)) };
235
+ }
236
+
237
+ // LEVEL 1 GOLDEN PATH: guardian --url routes to Reality (not legacy crawl)
238
+ // If first arg is --url, treat it as guardian reality --url
239
+ if (args.length > 0 && args[0] === '--url') {
240
+ return { subcommand: 'reality', config: parseRealityArgs(args) };
241
+ }
242
+
243
+ // Legacy crawl command (explicit only)
244
+ if (subcommand === 'crawl') {
245
+ return { subcommand: 'crawl', config: parseCrawlArgs(args.slice(1)) };
246
+ }
247
+
248
+ // Unknown command
249
+ console.error(`Unknown command: ${subcommand}`);
250
+ console.error('Run "guardian --help" for Level 1 usage.');
251
+ process.exit(2);
78
252
  }
79
253
 
80
254
  function parseCrawlArgs(args) {
@@ -82,7 +256,7 @@ function parseCrawlArgs(args) {
82
256
  maxPages: 25,
83
257
  maxDepth: 3,
84
258
  timeout: 20000,
85
- artifactsDir: './artifacts'
259
+ artifactsDir: './.odavlguardian'
86
260
  };
87
261
 
88
262
  for (let i = 0; i < args.length; i++) {
@@ -121,12 +295,162 @@ function parseCrawlArgs(args) {
121
295
  return config;
122
296
  }
123
297
 
298
+ // Apply preset configuration deterministically across commands
299
+ function applyPresetConfig(config) {
300
+ const presetName = config.preset || 'landing';
301
+ let preset;
302
+ try {
303
+ preset = resolveScanPreset(presetName);
304
+ } catch (err) {
305
+ console.error(`Error: ${err.message}`);
306
+ process.exit(2);
307
+ }
308
+
309
+ const applied = { ...config };
310
+ applied.preset = preset.id;
311
+ applied.attempts = preset.attempts;
312
+ applied.disabledAttempts = preset.disabledAttempts || [];
313
+ applied.flows = preset.flows;
314
+ applied.policy = applied.policy || preset.policy;
315
+ if (applied.failFast === undefined) {
316
+ applied.failFast = preset.failFast;
317
+ }
318
+ applied.evidencePreset = preset.evidence || {};
319
+ return applied;
320
+ }
321
+
322
+ // MVP: Journey Scan command parser
323
+ function parseJourneyScanArgs(args) {
324
+ const config = {
325
+ baseUrl: undefined,
326
+ preset: 'saas',
327
+ artifactsDir: './.odavlguardian',
328
+ headless: true,
329
+ timeout: 20000,
330
+ presetProvided: false
331
+ };
332
+
333
+ // First arg is URL if it doesn't start with --
334
+ if (args.length > 0 && !args[0].startsWith('--')) {
335
+ config.baseUrl = args[0];
336
+ args = args.slice(1);
337
+ }
338
+
339
+ for (let i = 0; i < args.length; i++) {
340
+ const a = args[i];
341
+ if (a === '--url' && args[i + 1]) { config.baseUrl = args[i + 1]; i++; }
342
+ else if (a === '--preset' && args[i + 1]) { config.preset = args[i + 1]; config.presetProvided = true; i++; }
343
+ else if (a === '--out' && args[i + 1]) { config.artifactsDir = args[i + 1]; i++; }
344
+ else if (a === '--artifacts' && args[i + 1]) { config.artifactsDir = args[i + 1]; i++; }
345
+ else if (a === '--timeout' && args[i + 1]) { config.timeout = parseInt(args[i + 1], 10); i++; }
346
+ else if (a === '--headful') { config.headless = false; }
347
+ else if (a === '--help' || a === '-h') { printHelpJourneyScan(); process.exit(0); }
348
+ }
349
+
350
+ if (!config.baseUrl) {
351
+ console.error('Error: <url> is required');
352
+ console.error('Usage: guardian journey-scan <url> [--preset saas|shop|landing] [--out dir]');
353
+ process.exit(2);
354
+ }
355
+
356
+ return config;
357
+ }
358
+
359
+ function parseLiveArgs(args) {
360
+ const config = {
361
+ baseUrl: undefined,
362
+ artifactsDir: './.odavlguardian',
363
+ headless: true,
364
+ timeout: 20000,
365
+ intervalMinutes: null,
366
+ preset: 'saas',
367
+ presetProvided: false,
368
+ cooldownMinutes: 60
369
+ };
370
+
371
+ if (args.length > 0 && !args[0].startsWith('--')) {
372
+ config.baseUrl = args[0];
373
+ args = args.slice(1);
374
+ }
375
+
376
+ for (let i = 0; i < args.length; i++) {
377
+ const a = args[i];
378
+ if (a === '--url' && args[i + 1]) { config.baseUrl = args[i + 1]; i++; }
379
+ else if (a === '--out' && args[i + 1]) { config.artifactsDir = args[i + 1]; i++; }
380
+ else if (a === '--artifacts' && args[i + 1]) { config.artifactsDir = args[i + 1]; i++; }
381
+ else if (a === '--interval' && args[i + 1]) { config.intervalMinutes = parseFloat(args[i + 1]); i++; }
382
+ else if (a === '--cooldown' && args[i + 1]) { config.cooldownMinutes = parseFloat(args[i + 1]); i++; }
383
+ else if (a === '--timeout' && args[i + 1]) { config.timeout = parseInt(args[i + 1], 10); i++; }
384
+ else if (a === '--preset' && args[i + 1]) { config.preset = args[i + 1]; config.presetProvided = true; i++; }
385
+ else if (a === '--headful') { config.headless = false; }
386
+ else if (a === '--help' || a === '-h') { printHelpLive(); process.exit(0); }
387
+ }
388
+
389
+ if (!config.baseUrl) {
390
+ console.error('Error: <url> is required');
391
+ console.error('Usage: guardian live <url> [--interval <minutes>] [--out dir]');
392
+ process.exit(2);
393
+ }
394
+
395
+ return config;
396
+ }
397
+
398
+ // Scheduler start args
399
+ function parseLiveStartArgs(args) {
400
+ const config = {
401
+ baseUrl: undefined,
402
+ preset: 'saas',
403
+ intervalMinutes: undefined,
404
+ };
405
+
406
+ // First arg is URL if present
407
+ if (args.length > 0 && !String(args[0]).startsWith('--')) {
408
+ config.baseUrl = args[0];
409
+ args = args.slice(1);
410
+ }
411
+
412
+ for (let i = 0; i < args.length; i++) {
413
+ const a = args[i];
414
+ if (a === '--url' && args[i + 1]) { config.baseUrl = args[i + 1]; i++; }
415
+ else if (a === '--preset' && args[i + 1]) { config.preset = args[i + 1]; i++; }
416
+ else if (a === '--interval' && args[i + 1]) { config.intervalMinutes = parseFloat(args[i + 1]); i++; }
417
+ else if (a === '--help' || a === '-h') {
418
+ console.log('\nUsage: guardian live start --url <url> --interval <minutes> [--preset saas|shop|landing]');
419
+ process.exit(0);
420
+ }
421
+ }
422
+
423
+ if (!config.baseUrl || !config.intervalMinutes || config.intervalMinutes <= 0) {
424
+ console.error('Error: --url and --interval <minutes> are required');
425
+ process.exit(2);
426
+ }
427
+ return config;
428
+ }
429
+
430
+ // Scheduler stop args
431
+ function parseLiveStopArgs(args) {
432
+ const config = { id: undefined };
433
+ if (args.length > 0) {
434
+ config.id = args[0];
435
+ }
436
+ for (let i = 0; i < args.length; i++) {
437
+ const a = args[i];
438
+ if ((a === '--id' || a === '-i') && args[i + 1]) { config.id = args[i + 1]; i++; }
439
+ }
440
+ if (!config.id) {
441
+ console.error('Error: schedule id is required');
442
+ console.error('Usage: guardian live stop <id>');
443
+ process.exit(2);
444
+ }
445
+ return config;
446
+ }
447
+
124
448
  // Phase 6: Scan command (one-command value)
125
449
  function parseScanArgs(args) {
126
450
  const config = {
127
451
  // core
128
452
  baseUrl: undefined,
129
- artifactsDir: './artifacts',
453
+ artifactsDir: './.odavlguardian',
130
454
  // enable full pipeline
131
455
  enableCrawl: true,
132
456
  enableDiscovery: true,
@@ -182,46 +506,100 @@ function parseScanArgs(args) {
182
506
 
183
507
  if (!config.baseUrl) {
184
508
  console.error('Error: <url> is required');
185
- console.error('Usage: guardian scan <url> [--preset <landing|saas|shop>]');
509
+ console.error('Usage: guardian scan <url> [--preset <landing|landing-demo|saas|shop>]');
186
510
  process.exit(2);
187
511
  }
188
512
 
189
- // Apply scan preset overrides
190
- try {
191
- const { resolveScanPreset } = require('../src/guardian/scan-presets');
192
- const presetCfg = resolveScanPreset(config.preset);
193
- config.attempts = presetCfg.attempts;
194
- config.flows = presetCfg.flows;
195
- if (presetCfg.policy) {
196
- // Allow users to override via --policy
197
- config.policy = config.policy || presetCfg.policy;
513
+ return applyPresetConfig(config);
514
+ }
515
+
516
+ function parseListArgs(args) {
517
+ const config = {
518
+ artifactsDir: './.odavlguardian',
519
+ filters: {}
520
+ };
521
+
522
+ for (let i = 0; i < args.length; i++) {
523
+ if (args[i] === '--artifacts' && args[i + 1]) {
524
+ config.artifactsDir = args[i + 1];
525
+ i++;
526
+ }
527
+ if (args[i] === '--failed') {
528
+ config.filters.failed = true;
529
+ }
530
+ if (args[i] === '--site' && args[i + 1]) {
531
+ config.filters.site = args[i + 1];
532
+ i++;
533
+ }
534
+ if (args[i] === '--limit' && args[i + 1]) {
535
+ config.filters.limit = parseInt(args[i + 1], 10);
536
+ i++;
537
+ }
538
+ if (args[i] === '--help' || args[i] === '-h') {
539
+ printHelpList();
540
+ process.exit(0);
198
541
  }
199
- } catch (e) {
200
- // If presets not available, proceed with defaults
201
542
  }
202
543
 
203
544
  return config;
204
545
  }
205
546
 
206
- function parseRealityArgs(args) {
547
+ function parseCleanupArgs(args) {
207
548
  const config = {
208
549
  artifactsDir: './artifacts',
550
+ olderThan: null,
551
+ keepLatest: null,
552
+ failedOnly: false
553
+ };
554
+
555
+ for (let i = 0; i < args.length; i++) {
556
+ if (args[i] === '--artifacts' && args[i + 1]) {
557
+ config.artifactsDir = args[i + 1];
558
+ i++;
559
+ }
560
+ if (args[i] === '--older-than' && args[i + 1]) {
561
+ config.olderThan = args[i + 1];
562
+ i++;
563
+ }
564
+ if (args[i] === '--keep-latest' && args[i + 1]) {
565
+ config.keepLatest = parseInt(args[i + 1], 10);
566
+ i++;
567
+ }
568
+ if (args[i] === '--failed-only') {
569
+ config.failedOnly = true;
570
+ }
571
+ if (args[i] === '--help' || args[i] === '-h') {
572
+ printHelpCleanup();
573
+ process.exit(0);
574
+ }
575
+ }
576
+
577
+ return config;
578
+ }
579
+
580
+ function parseRealityArgs(args) {
581
+ const config = {
582
+ artifactsDir: undefined,
209
583
  attempts: getDefaultAttemptIds(),
584
+ disabledAttempts: [],
210
585
  headful: false,
211
586
  enableTrace: true,
212
587
  enableScreenshots: true,
213
588
  enableDiscovery: false,
214
589
  includeUniversal: false,
590
+ preset: 'landing',
215
591
  policy: null,
216
592
  webhook: null,
217
593
  watch: false,
218
594
  // Phase 7.1: Performance modes
595
+
219
596
  timeoutProfile: 'default',
220
597
  failFast: false,
221
598
  fast: false,
222
599
  attemptsFilter: null,
223
600
  // Phase 7.2: Parallel execution
224
- parallel: 1
601
+ parallel: 1,
602
+ _cliSource: {}
225
603
  };
226
604
 
227
605
  for (let i = 0; i < args.length; i++) {
@@ -236,6 +614,26 @@ function parseRealityArgs(args) {
236
614
  }
237
615
  if (args[i] === '--artifacts' && args[i + 1]) {
238
616
  config.artifactsDir = args[i + 1];
617
+ config._cliSource.artifactsDir = true;
618
+ i++;
619
+ }
620
+ if (args[i] === '--max-pages' && args[i + 1]) {
621
+ config.maxPages = parseInt(args[i + 1], 10);
622
+ config._cliSource.maxPages = true;
623
+ i++;
624
+ }
625
+ if (args[i] === '--max-depth' && args[i + 1]) {
626
+ config.maxDepth = parseInt(args[i + 1], 10);
627
+ config._cliSource.maxDepth = true;
628
+ i++;
629
+ }
630
+ if (args[i] === '--timeout' && args[i + 1]) {
631
+ config.timeout = parseInt(args[i + 1], 10);
632
+ config._cliSource.timeout = true;
633
+ i++;
634
+ }
635
+ if (args[i] === '--preset' && args[i + 1]) {
636
+ config.preset = args[i + 1];
239
637
  i++;
240
638
  }
241
639
  if (args[i] === '--policy' && args[i + 1]) {
@@ -294,7 +692,7 @@ function parseRealityArgs(args) {
294
692
  process.exit(2);
295
693
  }
296
694
 
297
- return config;
695
+ return applyPresetConfig(config);
298
696
  }
299
697
 
300
698
  function parseSmokeArgs(args) {
@@ -346,14 +744,156 @@ function parseInitArgs(args) {
346
744
  return config;
347
745
  }
348
746
 
747
+ function parseTemplateArgs(args) {
748
+ const config = {
749
+ template: args[0] || null,
750
+ output: null
751
+ };
752
+
753
+ for (let i = 0; i < args.length; i++) {
754
+ if (args[i] === '--output' && args[i + 1]) {
755
+ config.output = args[i + 1];
756
+ i++;
757
+ }
758
+ if (args[i] === '--help' || args[i] === '-h') {
759
+ printHelpTemplate();
760
+ process.exit(0);
761
+ }
762
+ }
763
+
764
+ return config;
765
+ }
766
+
767
+ // Phase 11: Enterprise command parsers
768
+ function parseSitesArgs(args) {
769
+ const config = {
770
+ action: args[0] || 'list',
771
+ name: null,
772
+ url: null,
773
+ project: 'default'
774
+ };
775
+
776
+ if (config.action === 'add') {
777
+ config.name = args[1];
778
+ config.url = args[2];
779
+ for (let i = 3; i < args.length; i++) {
780
+ if (args[i] === '--project' && args[i + 1]) {
781
+ config.project = args[i + 1];
782
+ i++;
783
+ }
784
+ }
785
+ } else if (config.action === 'remove') {
786
+ config.name = args[1];
787
+ }
788
+
789
+ return config;
790
+ }
791
+
792
+ function parseUsersArgs(args) {
793
+ const config = {
794
+ action: args[0] || 'list',
795
+ username: args[1] || null,
796
+ role: args[2] || 'VIEWER'
797
+ };
798
+
799
+ return config;
800
+ }
801
+
802
+ function parseAuditArgs(args) {
803
+ const config = {
804
+ action: args[0] || 'list',
805
+ limit: 100,
806
+ actionFilter: null,
807
+ user: null
808
+ };
809
+
810
+ for (let i = 1; i < args.length; i++) {
811
+ if (args[i] === '--limit' && args[i + 1]) {
812
+ config.limit = parseInt(args[i + 1], 10);
813
+ i++;
814
+ }
815
+ if (args[i] === '--action' && args[i + 1]) {
816
+ config.actionFilter = args[i + 1];
817
+ i++;
818
+ }
819
+ if (args[i] === '--user' && args[i + 1]) {
820
+ config.user = args[i + 1];
821
+ i++;
822
+ }
823
+ }
824
+
825
+ return config;
826
+ }
827
+
828
+ function parseExportArgs(args) {
829
+ const config = {
830
+ reportId: args[0] || null,
831
+ format: 'pdf',
832
+ output: null
833
+ };
834
+
835
+ for (let i = 1; i < args.length; i++) {
836
+ if (args[i] === '--format' && args[i + 1]) {
837
+ config.format = args[i + 1];
838
+ i++;
839
+ }
840
+ if (args[i] === '--output' && args[i + 1]) {
841
+ config.output = args[i + 1];
842
+ i++;
843
+ }
844
+ }
845
+
846
+ return config;
847
+ }
848
+
849
+ // Phase 12.1: Recipe parser
850
+ function parseRecipeArgs(args) {
851
+ const config = {
852
+ action: args[0] || 'list',
853
+ id: args[1] || null,
854
+ url: null,
855
+ file: null,
856
+ out: null,
857
+ force: false,
858
+ };
859
+
860
+ // Positional file support for import
861
+ if (config.action === 'import' && config.id && !config.id.startsWith('--')) {
862
+ config.file = config.id;
863
+ config.id = null;
864
+ }
865
+
866
+ for (let i = 2; i < args.length; i++) {
867
+ if (args[i] === '--url' && args[i + 1]) {
868
+ config.url = args[i + 1];
869
+ i++;
870
+ }
871
+ if (args[i] === '--file' && args[i + 1]) {
872
+ config.file = args[i + 1];
873
+ i++;
874
+ }
875
+ if (args[i] === '--out' && args[i + 1]) {
876
+ config.out = args[i + 1];
877
+ i++;
878
+ }
879
+ if (args[i] === '--force') {
880
+ config.force = true;
881
+ }
882
+ }
883
+
884
+ return config;
885
+ }
886
+
349
887
  function parseProtectArgs(args) {
350
888
  const config = {
351
889
  artifactsDir: './artifacts',
352
890
  attempts: getDefaultAttemptIds(),
891
+ disabledAttempts: [],
353
892
  headful: false,
354
893
  enableTrace: true,
355
894
  enableScreenshots: true,
356
- policy: 'preset:startup',
895
+ policy: null,
896
+ preset: 'startup',
357
897
  webhook: null,
358
898
  watch: false,
359
899
  // Phase 7.1: Performance modes
@@ -376,6 +916,10 @@ function parseProtectArgs(args) {
376
916
  config.baseUrl = args[i + 1];
377
917
  i++;
378
918
  }
919
+ if (args[i] === '--preset' && args[i + 1]) {
920
+ config.preset = args[i + 1];
921
+ i++;
922
+ }
379
923
  if (args[i] === '--policy' && args[i + 1]) {
380
924
  config.policy = args[i + 1];
381
925
  i++;
@@ -421,7 +965,7 @@ function parseProtectArgs(args) {
421
965
  process.exit(2);
422
966
  }
423
967
 
424
- return config;
968
+ return applyPresetConfig(config);
425
969
  }
426
970
 
427
971
  function parseAttemptArgs(args) {
@@ -565,9 +1109,9 @@ PERFORMANCE (Phase 7.1):
565
1109
  --help Show this help message
566
1110
 
567
1111
  EXIT CODES:
568
- 0 Success (first run baseline created, or no regressions)
569
- 1 FAILURE (regression detected or policy failed)
570
- 2 FRICTION (drift without critical failure or soft policy failure)
1112
+ 0 READY
1113
+ 1 FRICTION
1114
+ 2 DO_NOT_LAUNCH
571
1115
 
572
1116
  EXAMPLES:
573
1117
  First run (baseline auto-created):
@@ -587,7 +1131,7 @@ Usage: guardian init [options]
587
1131
 
588
1132
  WHAT IT DOES:
589
1133
  Initialize Guardian in the current directory:
590
- - Creates guardian.policy.json (default: startup preset)
1134
+ - Creates config/guardian.policy.json (default: startup preset)
591
1135
  - Updates .gitignore to exclude Guardian artifacts
592
1136
  - Prints next steps
593
1137
 
@@ -602,13 +1146,100 @@ EXAMPLE:
602
1146
  `);
603
1147
  }
604
1148
 
1149
+ function printHelpTemplate() {
1150
+ console.log(`
1151
+ Usage: guardian template [template] [options]
1152
+
1153
+ WHAT IT DOES:
1154
+ Generate a minimal config template for common site types.
1155
+ Templates include sample journeys and policy settings.
1156
+
1157
+ AVAILABLE TEMPLATES:
1158
+ saas SaaS startup flow (signup, login, dashboard)
1159
+ shop E-commerce shop flow (browse, cart, checkout)
1160
+ landing Landing page flow (load, CTA validation)
1161
+
1162
+ OPTIONS:
1163
+ --output <file> Output file name (default: guardian-<template>.json)
1164
+ --help Show this help message
1165
+
1166
+ EXAMPLES:
1167
+ guardian template saas
1168
+ guardian template shop --output my-config.json
1169
+ guardian template landing
1170
+ `);
1171
+ }
1172
+
1173
+ function printHelpList() {
1174
+ console.log(`
1175
+ Usage: guardian list [options]
1176
+
1177
+ WHAT IT DOES:
1178
+ List all completed Guardian runs with metadata.
1179
+ Scans the artifacts directory for runs with META.json files and displays
1180
+ them in a table sorted by most recent first.
1181
+
1182
+ OPTIONS:
1183
+ --artifacts <dir> Path to artifacts directory
1184
+ Default: ./.odavlguardian
1185
+ --help Show this help message
1186
+
1187
+ OUTPUT COLUMNS:
1188
+ Time Run execution timestamp (YYYY-MM-DD HH:MM:SS)
1189
+ Site Target site slug (extracted from URL)
1190
+ Policy Policy/profile used for the run
1191
+ Result Run result: PASSED, FAILED, or WARN
1192
+ Duration Wall-clock execution time
1193
+ Path Run directory name
1194
+
1195
+ EXAMPLE:
1196
+ guardian list
1197
+ guardian list --artifacts ./.odavlguardian
1198
+ guardian list --failed
1199
+ guardian list --site github-com --limit 5
1200
+ `);
1201
+ }
1202
+
1203
+ function printHelpCleanup() {
1204
+ console.log(`
1205
+ Usage: guardian cleanup [options]
1206
+
1207
+ WHAT IT DOES:
1208
+ Manage and delete old or failed Guardian runs.
1209
+ Safely removes run directories while optionally preserving recent runs.
1210
+
1211
+ OPTIONS:
1212
+ --artifacts <dir> Path to artifacts directory
1213
+ Default: ./.odavlguardian
1214
+ --older-than <duration> Delete runs older than duration
1215
+ Format: <num>[d|h|m] (e.g., 7d, 24h, 30m)
1216
+ --keep-latest <num> Keep the N most recent runs per site
1217
+ Applied per site, deletes older runs
1218
+ --failed-only Only delete failed runs (result === FAILED)
1219
+ --help Show this help message
1220
+
1221
+ BEHAVIOR:
1222
+ - Filters are applied independently and compose
1223
+ - --failed-only filters to only FAILED status before other filters
1224
+ - --keep-latest keeps N newest per site, regardless of status
1225
+ - Combine flags: guardian cleanup --older-than 7d --failed-only
1226
+ - Deletes using real fs.rmSync() with recursive flag
1227
+
1228
+ EXAMPLE:
1229
+ guardian cleanup --older-than 7d
1230
+ guardian cleanup --keep-latest 3
1231
+ guardian cleanup --older-than 30d --failed-only
1232
+ guardian cleanup --artifacts ./.odavlguardian --keep-latest 5
1233
+ `);
1234
+ }
1235
+
605
1236
  function printHelpProtect() {
606
1237
  console.log(`
607
1238
  Usage: guardian protect <url> [options]
608
1239
 
609
1240
  WHAT IT DOES:
610
- Quick shortcut for reality check with startup policy.
611
- Equivalent to: guardian reality --url <url> --policy preset:startup
1241
+ Full market reality test with startup policy.
1242
+ Deeper than smoke; runs full discovery, attempts, and baseline comparison.
612
1243
 
613
1244
  OPTIONS:
614
1245
  <url> Target URL (required)
@@ -635,7 +1266,7 @@ function printHelpSmoke() {
635
1266
  Usage: guardian smoke <url>
636
1267
 
637
1268
  WHAT IT DOES:
638
- Fast smoke validation under ~30s.
1269
+ Fast market sanity check (<30s).
639
1270
  Runs only critical paths: homepage reachability, navigation probe,
640
1271
  auth (login or signup), and contact/support if present.
641
1272
 
@@ -677,7 +1308,7 @@ Usage: guardian attempt --url <baseUrl> --attempt <id> [options]
677
1308
  Options:
678
1309
  --url <url> Target URL (required)
679
1310
  --attempt <id> Attempt ID (default: contact_form)
680
- --artifacts <dir> Artifacts directory (default: ./artifacts)
1311
+ --artifacts <dir> Artifacts directory (default: ./.odavlguardian)
681
1312
  --headful Run with visible browser (default: headless)
682
1313
  --no-trace Disable trace recording
683
1314
  --no-screenshots Disable screenshot capture
@@ -690,6 +1321,56 @@ Exit Codes:
690
1321
  `);
691
1322
  }
692
1323
 
1324
+ function printHelpJourneyScan() {
1325
+ console.log(`
1326
+ Usage: guardian journey-scan <url> [options]
1327
+
1328
+ WHAT IT DOES:
1329
+ Human journey scan — Tests a single critical user flow end-to-end.
1330
+ Opens a real browser, follows a predetermined journey, captures evidence,
1331
+ and outputs a human-readable report with a single decision:
1332
+
1333
+ ✅ SAFE (all steps succeeded)
1334
+ ⚠️ RISK (partial success with failures)
1335
+ 🚫 DO_NOT_LAUNCH (complete failure)
1336
+
1337
+ OPTIONS:
1338
+ <url> Target URL (required)
1339
+ --preset <name> saas | shop | landing (default: saas)
1340
+ --out <dir> Output directory (default: ./.odavlguardian)
1341
+ --timeout <ms> Step timeout in milliseconds (default: 20000)
1342
+ --headful Run headed browser (show UI)
1343
+ --help Show help
1344
+
1345
+ EXAMPLES:
1346
+ guardian journey-scan https://example.com
1347
+ guardian journey-scan https://example.com --preset shop --out ./results
1348
+ guardian journey-scan https://example.com --preset landing --headful
1349
+
1350
+ OUTPUT:
1351
+ SUMMARY.txt Human-readable summary
1352
+ summary.md Markdown summary
1353
+ report.json Full journey results
1354
+ screenshots/ Evidence screenshots per step
1355
+ metadata.json Scan metadata
1356
+
1357
+ EXIT CODES:
1358
+ 0 READY (all steps succeeded)
1359
+ 1 FRICTION (partial failures)
1360
+ 2 DO_NOT_LAUNCH (complete failure)
1361
+ `);
1362
+ }
1363
+
1364
+ function printHelpLive() {
1365
+ console.log('Usage: guardian live <url> [options]');
1366
+ console.log('Options:');
1367
+ console.log(' --interval <minutes> Run periodically; omit to run once');
1368
+ console.log(' --out <dir> Output directory');
1369
+ console.log(' --preset <name> Override journey preset');
1370
+ console.log(' --headful Run with visible browser');
1371
+ console.log(' --timeout <ms> Step timeout');
1372
+ }
1373
+
693
1374
  function printHelpScan() {
694
1375
  console.log(`
695
1376
  Usage: guardian scan <url> [options]
@@ -704,7 +1385,7 @@ WHAT IT DOES:
704
1385
 
705
1386
  OPTIONS:
706
1387
  <url> Target URL (required)
707
- --preset <name> landing | saas | shop (opinionated defaults)
1388
+ --preset <name> landing | landing-demo | saas | shop (opinionated defaults)
708
1389
  --policy <path|preset> Override policy file or preset:name
709
1390
  --artifacts <dir> Artifacts directory (default: ./artifacts)
710
1391
  --headful Run headed browser
@@ -721,6 +1402,7 @@ PERFORMANCE (Phase 7.1):
721
1402
 
722
1403
  EXAMPLES:
723
1404
  guardian scan https://example.com --preset landing
1405
+ guardian scan https://example.com --preset landing-demo
724
1406
  guardian scan https://example.com --preset saas
725
1407
  guardian scan https://example.com --fast --fail-fast
726
1408
  `);
@@ -746,7 +1428,7 @@ Options:
746
1428
  --url <url> Target URL (required)
747
1429
  --attempts <id1,id2> Comma-separated attempt IDs (default: curated 3)
748
1430
  --name <baselineName> Baseline name (default: baseline)
749
- --artifacts <dir> Artifacts directory (default: ./artifacts)
1431
+ --artifacts <dir> Artifacts directory (default: ./.odavlguardian)
750
1432
  --headful Run headed browser (default: headless)
751
1433
  --no-trace Disable trace recording
752
1434
  --no-screenshots Disable screenshots
@@ -765,7 +1447,7 @@ Options:
765
1447
  --url <url> Target URL (required)
766
1448
  --name <baselineName> Baseline name to compare against (required)
767
1449
  --attempts <id1,id2> Comma-separated attempt IDs (default: curated 3)
768
- --artifacts <dir> Artifacts directory (default: ./artifacts)
1450
+ --artifacts <dir> Artifacts directory (default: ./.odavlguardian)
769
1451
  --headful Run headed browser (default: headless)
770
1452
  --no-trace Disable trace recording
771
1453
  --no-screenshots Disable screenshots
@@ -783,22 +1465,103 @@ Exit Codes:
783
1465
  async function main() {
784
1466
  const args = process.argv.slice(2);
785
1467
 
786
- // Minimal release flag: print version and exit
787
- if (args.length === 1 && args[0] === '--version') {
1468
+ // Phase 8: Helper to check plan before scan
1469
+ function checkPlanBeforeScan(config, options = {}) {
1470
+ const { recordUsage = true } = options;
788
1471
  try {
789
- const pkg = require('../package.json');
790
- console.log(pkg.version);
791
- process.exit(0);
792
- } catch (e) {
793
- console.error('Version unavailable');
1472
+ const url = config.url || config.baseUrl || '';
1473
+ if (!url) return; // No URL to check
1474
+
1475
+ // Phase 10: Register user on first scan
1476
+ registerUser();
1477
+
1478
+ const check = checkCanScan(url);
1479
+ if (!check.allowed) {
1480
+ // LEVEL 1 TRANSPARENT GATING: emit clear message + artifacts
1481
+ const fs = require('fs');
1482
+ const path = require('path');
1483
+ const artifactsDir = config.artifactsDir || './.odavlguardian';
1484
+ try { if (!fs.existsSync(artifactsDir)) fs.mkdirSync(artifactsDir, { recursive: true }); } catch (_) {}
1485
+
1486
+ const now = new Date().toISOString().replace(/[:\-]/g, '').substring(0, 15).replace('T', '-');
1487
+ const runId = `gated-${now}`;
1488
+ const runDir = path.join(artifactsDir, runId);
1489
+ try { fs.mkdirSync(runDir, { recursive: true }); } catch (_) {}
1490
+
1491
+ const upgradeMsg = getUpgradeMessage();
1492
+
1493
+ // Write decision.json with canonical verdict and next steps
1494
+ const decision = {
1495
+ runId,
1496
+ url,
1497
+ timestamp: new Date().toISOString(),
1498
+ preset: config.preset || 'default',
1499
+ policyName: 'Plan Gate',
1500
+ finalVerdict: 'FRICTION',
1501
+ exitCode: 1,
1502
+ reasons: [
1503
+ { code: 'PLAN_GATE', message: check.message },
1504
+ { code: 'NEXT_STEPS', message: upgradeMsg }
1505
+ ],
1506
+ gating: {
1507
+ reason: check.message,
1508
+ nextSteps: upgradeMsg
1509
+ }
1510
+ };
1511
+ try { fs.writeFileSync(path.join(runDir, 'decision.json'), JSON.stringify(decision, null, 2), 'utf8'); } catch (_) {}
1512
+
1513
+ // Write summary.md with friendly next steps
1514
+ const lines = [];
1515
+ lines.push('# Guardian Reality Summary');
1516
+ lines.push('');
1517
+ lines.push('## Final Verdict');
1518
+ lines.push('- Verdict: FRICTION (exit 1)');
1519
+ lines.push('- Why this verdict: Reality run was gated by plan limits.');
1520
+ lines.push('');
1521
+ lines.push('## What Happened');
1522
+ lines.push(`- ${check.message}`);
1523
+ lines.push('');
1524
+ lines.push('## What To Do Next');
1525
+ lines.push(`- ${upgradeMsg}`);
1526
+ try { fs.writeFileSync(path.join(runDir, 'summary.md'), lines.join('\n'), 'utf8'); } catch (_) {}
1527
+
1528
+ // Crystal-clear CLI message
1529
+ console.log('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
1530
+ console.log('Guardian Reality was gated by plan limits');
1531
+ console.log('');
1532
+ console.log(`Why: ${check.message}`);
1533
+ console.log(`Next steps: ${upgradeMsg}`);
1534
+ console.log('');
1535
+ console.log(`Artifacts: ${runDir}`);
1536
+ console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
1537
+ process.exit(1);
1538
+ }
1539
+
1540
+ // Show usage info
1541
+ if (check.usage && check.usage.scansRemaining !== 'Unlimited') {
1542
+ console.log(`ℹ️ Scans remaining this month: ${check.usage.scansRemaining}\n`);
1543
+ }
1544
+
1545
+ // Record the scan
1546
+ if (recordUsage) {
1547
+ performScan(url);
1548
+
1549
+ // Phase 10: Track first scan signal
1550
+ recordFirstScan();
1551
+ }
1552
+ } catch (error) {
1553
+ console.error(`\n❌ ${error.message}`);
1554
+ console.log(getUpgradeMessage());
794
1555
  process.exit(1);
795
1556
  }
796
1557
  }
797
1558
 
1559
+ // Minimal release flag: print version and exit
798
1560
  // PHASE 6: First-run welcome (only once)
799
- if (args.length > 0 && !['--help', '-h', 'init', 'presets'].includes(args[0])) {
1561
+ if (args.length > 0 && !['--help', '-h', 'init', 'presets', 'template'].includes(args[0])) {
800
1562
  if (isFirstRun('.odavl-guardian')) {
801
1563
  printWelcome('ODAVL Guardian');
1564
+ printFirstRunHint();
802
1565
  markAsRun('.odavl-guardian');
803
1566
  }
804
1567
  }
@@ -809,29 +1572,49 @@ async function main() {
809
1572
 
810
1573
  Usage: guardian <subcommand> [options]
811
1574
 
812
- QUICK START:
813
- init Initialize Guardian in current directory
814
- protect <url> Quick reality check with startup policy
815
- smoke <url> 30-second smoke validation (critical paths)
816
- reality Full Market Reality Snapshot
1575
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1576
+ LEVEL 1 GOLDEN PATH (Reality Only)
1577
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1578
+
1579
+ reality --url <URL> Full Market Reality Check
1580
+ → Canonical Verdicts: READY / FRICTION / DO_NOT_LAUNCH
1581
+ → Outputs: summary.md, decision.json, market-report.html
1582
+ → Artifacts: ./.odavlguardian/
817
1583
 
818
- OTHER COMMANDS:
1584
+ --url <URL> Alias for: guardian reality --url <URL>
1585
+
1586
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1587
+ QUICK START
1588
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1589
+
1590
+ # Run Reality Check (Level 1 Golden Path)
1591
+ guardian reality --url https://example.com
1592
+
1593
+ # Same, using alias
1594
+ guardian --url https://example.com
1595
+
1596
+ # List completed runs
1597
+ guardian list
1598
+
1599
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1600
+ ADVANCED COMMANDS (Level 2+)
1601
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1602
+
1603
+ scan <url> One-command product scan
1604
+ journey-scan <url> Human journey scan
819
1605
  attempt Execute a single user attempt
820
- baseline save (Legacy) Manually save baseline
821
- baseline check (Legacy) Manually check against baseline
1606
+ smoke <url> Fast market sanity check (<30s)
1607
+ baseline save/check Baseline management
1608
+ list List completed runs
1609
+ cleanup Manage and delete old runs
1610
+ init Initialize Guardian
1611
+ template <type> Generate config template
1612
+ plan / upgrade Show plan or upgrade
1613
+ sites / users / audit Enterprise management
1614
+ export Export reports (PDF)
822
1615
  presets List available policy presets
823
1616
 
824
- EXAMPLES:
825
- # Initialize Guardian
826
- guardian init
827
-
828
- # Quick protect (uses startup policy)
829
- guardian protect https://example.com
830
-
831
- # Full reality check with policy
832
- guardian reality --url https://example.com --policy preset:saas
833
-
834
- Run 'guardian <subcommand> --help' for more information.
1617
+ Run 'guardian <subcommand> --help' for detailed command help.
835
1618
  `);
836
1619
  process.exit(0);
837
1620
  }
@@ -847,15 +1630,517 @@ Run 'guardian <subcommand> --help' for more information.
847
1630
  } else if (parsed.subcommand === 'presets') {
848
1631
  printPresets();
849
1632
  process.exit(0);
1633
+ } else if (parsed.subcommand === 'template') {
1634
+ const template = config.template;
1635
+ if (!template) {
1636
+ console.log('\n📋 Guardian Templates\n');
1637
+ const templates = listTemplates();
1638
+ templates.forEach(t => {
1639
+ console.log(` ${t.name}: ${t.description} (${t.journeys} journeys)`);
1640
+ });
1641
+ console.log('\nUsage: guardian template <saas|shop|landing> [--output file.json]\n');
1642
+ process.exit(0);
1643
+ }
1644
+ try {
1645
+ const result = generateTemplate(template, { output: config.output });
1646
+ console.log(`\n✅ ${result.message}`);
1647
+ console.log(` Generated config ready to use with: guardian reality --config ${result.outputPath}\n`);
1648
+ process.exit(0);
1649
+ } catch (err) {
1650
+ console.error(`\n❌ ${err.message}\n`);
1651
+ process.exit(1);
1652
+ }
1653
+ } else if (parsed.subcommand === 'plan') {
1654
+ // Phase 8: Show current plan and usage
1655
+ const summary = getPlanSummary();
1656
+ console.log(`\n🛡️ ODAVL Guardian Plan\n`);
1657
+
1658
+ // Phase 10: Show founder status
1659
+ const founderMsg = getFounderMessage();
1660
+ if (founderMsg) {
1661
+ console.log(founderMsg);
1662
+ console.log();
1663
+ }
1664
+
1665
+ console.log(`Current Plan: ${summary.plan.name.toUpperCase()}`);
1666
+ if (summary.plan.price > 0) {
1667
+ console.log(`Price: $${summary.plan.price}/month`);
1668
+ }
1669
+ console.log();
1670
+ console.log(`Usage This Month:`);
1671
+ console.log(` Scans: ${summary.limits.scans.used}/${summary.limits.scans.max === -1 ? 'Unlimited' : summary.limits.scans.max} (${summary.limits.scans.remaining} remaining)`);
1672
+ console.log(` Sites: ${summary.limits.sites.used}/${summary.limits.sites.max === -1 ? 'Unlimited' : summary.limits.sites.max}`);
1673
+ console.log();
1674
+ console.log(`Features:`);
1675
+ console.log(` Live Guardian: ${summary.features.liveGuardian ? '✅' : '❌'}`);
1676
+ console.log(` CI/CD Mode: ${summary.features.ciMode ? '✅' : '❌'}`);
1677
+ console.log(` Alerts: ${summary.features.alerts ? '✅' : '❌'}`);
1678
+ console.log();
1679
+ if (summary.plan.id === 'free') {
1680
+ console.log(`Upgrade to Pro for unlimited scans and advanced features.`);
1681
+ console.log(`Run: guardian upgrade pro\n`);
1682
+ }
1683
+ process.exit(0);
1684
+ } else if (parsed.subcommand === 'upgrade') {
1685
+ // Phase 8: Upgrade to a plan
1686
+ const targetPlan = config.plan;
1687
+ if (!targetPlan || !['pro', 'business'].includes(targetPlan.toLowerCase())) {
1688
+ console.error('\n❌ Please specify a valid plan: pro or business');
1689
+ console.log('\nUsage: guardian upgrade <pro|business>\n');
1690
+ process.exit(1);
1691
+ }
1692
+ const { getCheckoutUrl } = require('../src/payments/stripe-checkout');
1693
+ const checkoutUrl = getCheckoutUrl(targetPlan);
1694
+ console.log(`\n🚀 Upgrade to ${targetPlan.toUpperCase()}\n`);
1695
+ console.log(`Open this URL to complete your upgrade:\n`);
1696
+ console.log(` ${checkoutUrl}\n`);
1697
+ console.log(`After payment, your plan will be automatically activated.\n`);
1698
+
1699
+ // Phase 10: Record upgrade signal
1700
+ recordFirstUpgrade(targetPlan.toLowerCase());
1701
+
1702
+ process.exit(0);
1703
+ } else if (parsed.subcommand === 'feedback') {
1704
+ // Phase 10: Feedback session
1705
+ try {
1706
+ await runFeedbackSession();
1707
+ process.exit(0);
1708
+ } catch (err) {
1709
+ console.error(`\n❌ Feedback error: ${err.message}\n`);
1710
+ process.exit(1);
1711
+ }
1712
+ } else if (parsed.subcommand === 'sites') {
1713
+ // Phase 11: Multi-site management
1714
+ try {
1715
+ requirePermission('site:view', 'manage sites');
1716
+
1717
+ if (config.action === 'add') {
1718
+ requirePermission('site:add', 'add sites');
1719
+ if (!config.name || !config.url) {
1720
+ console.error('Usage: guardian sites add <name> <url> [--project <name>]');
1721
+ process.exit(2);
1722
+ }
1723
+ const site = addSite(config.name, config.url, config.project);
1724
+ console.log(`✓ Site added: ${site.name} (${site.project})`);
1725
+ logAudit(AUDIT_ACTIONS.SITE_ADD, { name: site.name, url: site.url, project: site.project });
1726
+ } else if (config.action === 'remove') {
1727
+ requirePermission('site:remove', 'remove sites');
1728
+ if (!config.name) {
1729
+ console.error('Usage: guardian sites remove <name>');
1730
+ process.exit(2);
1731
+ }
1732
+ const site = removeSite(config.name);
1733
+ console.log(`✓ Site removed: ${site.name}`);
1734
+ logAudit(AUDIT_ACTIONS.SITE_REMOVE, { name: site.name });
1735
+ } else {
1736
+ // List sites
1737
+ const data = getSites();
1738
+ if (data.sites.length === 0) {
1739
+ console.log('No sites registered yet.');
1740
+ } else {
1741
+ console.log(`\nTotal sites: ${data.sites.length}\n`);
1742
+ const projects = listProjects();
1743
+ for (const proj of projects) {
1744
+ console.log(`📁 ${proj.name} (${proj.siteCount} site(s))`);
1745
+ const sites = getSitesByProject(proj.name);
1746
+ for (const site of sites) {
1747
+ console.log(` - ${site.name}: ${site.url}`);
1748
+ if (site.lastScannedAt) {
1749
+ console.log(` Last scan: ${site.lastScannedAt} (${site.scanCount} total)`);
1750
+ }
1751
+ }
1752
+ }
1753
+ }
1754
+ }
1755
+ process.exit(0);
1756
+ } catch (err) {
1757
+ console.error(`\n❌ Sites error: ${err.message}\n`);
1758
+ process.exit(1);
1759
+ }
1760
+ } else if (parsed.subcommand === 'users') {
1761
+ // Phase 11: User/role management
1762
+ try {
1763
+ requirePermission('user:view', 'manage users');
1764
+
1765
+ if (config.action === 'add') {
1766
+ requirePermission('user:add', 'add users');
1767
+ if (!config.username) {
1768
+ console.error('Usage: guardian users add <username> [role]');
1769
+ process.exit(2);
1770
+ }
1771
+ const user = addUser(config.username, config.role);
1772
+ console.log(`✓ User added: ${user.username} (${user.role})`);
1773
+ logAudit(AUDIT_ACTIONS.USER_ADD, { username: user.username, role: user.role });
1774
+ } else if (config.action === 'remove') {
1775
+ requirePermission('user:remove', 'remove users');
1776
+ if (!config.username) {
1777
+ console.error('Usage: guardian users remove <username>');
1778
+ process.exit(2);
1779
+ }
1780
+ const user = removeUser(config.username);
1781
+ console.log(`✓ User removed: ${user.username}`);
1782
+ logAudit(AUDIT_ACTIONS.USER_REMOVE, { username: user.username });
1783
+ } else if (config.action === 'roles') {
1784
+ // List roles
1785
+ const roles = listRoles();
1786
+ console.log('\nAvailable roles:\n');
1787
+ for (const role of roles) {
1788
+ console.log(`${role.name}:`);
1789
+ console.log(` Permissions: ${role.permissions.join(', ')}`);
1790
+ }
1791
+ } else {
1792
+ // List users
1793
+ const data = getUsers();
1794
+ console.log(`\nTotal users: ${data.users.length}\n`);
1795
+ for (const user of data.users) {
1796
+ const current = user.username === getCurrentUser().username ? ' (current)' : '';
1797
+ console.log(`- ${user.username}: ${user.role}${current}`);
1798
+ }
1799
+ }
1800
+ process.exit(0);
1801
+ } catch (err) {
1802
+ console.error(`\n❌ Users error: ${err.message}\n`);
1803
+ process.exit(1);
1804
+ }
1805
+ } else if (parsed.subcommand === 'audit') {
1806
+ // Phase 11: Audit log viewing
1807
+ try {
1808
+ requirePermission('audit:view', 'view audit logs');
1809
+
1810
+ if (config.action === 'summary') {
1811
+ const summary = getAuditSummary();
1812
+ console.log('\nAudit Summary:\n');
1813
+ console.log(`Total logs: ${summary.totalLogs}`);
1814
+ console.log(`First log: ${summary.firstLog || 'N/A'}`);
1815
+ console.log(`Last log: ${summary.lastLog || 'N/A'}`);
1816
+ console.log('\nActions:');
1817
+ for (const [action, count] of Object.entries(summary.actionCounts)) {
1818
+ console.log(` ${action}: ${count}`);
1819
+ }
1820
+ console.log('\nUsers:');
1821
+ for (const [user, count] of Object.entries(summary.userCounts)) {
1822
+ console.log(` ${user}: ${count}`);
1823
+ }
1824
+ } else {
1825
+ // List logs
1826
+ const logs = readAuditLogs({
1827
+ limit: config.limit,
1828
+ action: config.actionFilter,
1829
+ user: config.user
1830
+ });
1831
+
1832
+ if (logs.length === 0) {
1833
+ console.log('No audit logs found.');
1834
+ } else {
1835
+ console.log(`\nShowing ${logs.length} log(s):\n`);
1836
+ for (const log of logs) {
1837
+ console.log(`[${log.timestamp}] ${log.user} → ${log.action}`);
1838
+ if (Object.keys(log.details).length > 0) {
1839
+ console.log(` Details: ${JSON.stringify(log.details)}`);
1840
+ }
1841
+ }
1842
+ }
1843
+ }
1844
+ process.exit(0);
1845
+ } catch (err) {
1846
+ console.error(`\n❌ Audit error: ${err.message}\n`);
1847
+ process.exit(1);
1848
+ }
1849
+ } else if (parsed.subcommand === 'export') {
1850
+ // Phase 11: PDF export
1851
+ try {
1852
+ requirePermission('export:pdf', 'export reports');
1853
+
1854
+ if (!config.reportId) {
1855
+ // List available reports
1856
+ const reports = listAvailableReports();
1857
+ if (reports.length === 0) {
1858
+ console.log('No reports available for export.');
1859
+ } else {
1860
+ console.log(`\nAvailable reports:\n`);
1861
+ for (const report of reports.slice(0, 10)) {
1862
+ console.log(`- ${report.id}`);
1863
+ console.log(` Modified: ${report.modifiedAt}`);
1864
+ }
1865
+ console.log('\nUsage: guardian export <report-id> [--output <path>]');
1866
+ }
1867
+ } else {
1868
+ const result = exportReportToPDF(config.reportId, config.output);
1869
+ console.log(`✓ Report exported to: ${result.outputPath}`);
1870
+ console.log(` Size: ${result.size} bytes`);
1871
+ logAudit(AUDIT_ACTIONS.EXPORT_PDF, { reportId: config.reportId, output: result.outputPath });
1872
+ }
1873
+ process.exit(0);
1874
+ } catch (err) {
1875
+ console.error(`\n❌ Export error: ${err.message}\n`);
1876
+ process.exit(1);
1877
+ }
1878
+ } else if (parsed.subcommand === 'recipe') {
1879
+ // Phase 12.1: Recipes
1880
+ try {
1881
+ const action = config.action;
1882
+ // Ensure registry includes built-ins before any trust checks
1883
+ getAllRecipes();
1884
+
1885
+ if (action === 'list') {
1886
+ const recipes = getAllRecipes();
1887
+ console.log(`\n📚 Available Recipes (${recipes.length} total)\n`);
1888
+
1889
+ // Group by platform
1890
+ const byPlatform = {};
1891
+ for (const recipe of recipes) {
1892
+ if (!byPlatform[recipe.platform]) {
1893
+ byPlatform[recipe.platform] = [];
1894
+ }
1895
+ byPlatform[recipe.platform].push(recipe);
1896
+ }
1897
+
1898
+ for (const platform of Object.keys(byPlatform).sort()) {
1899
+ console.log(`🏪 ${platform.toUpperCase()}`);
1900
+ for (const recipe of byPlatform[platform]) {
1901
+ const reg = getRegistryEntry(recipe.id);
1902
+ const checksum = computeRecipeChecksum(recipe);
1903
+ const mismatch = reg && reg.checksum && reg.checksum !== checksum;
1904
+ const sourceLabel = reg ? reg.source : 'unknown';
1905
+ const trustNote = mismatch ? ' [checksum mismatch]' : '';
1906
+ console.log(` • ${recipe.id} - ${recipe.name} [${sourceLabel}]${trustNote}`);
1907
+ }
1908
+ console.log();
1909
+ }
1910
+ } else if (action === 'show') {
1911
+ if (!config.id) {
1912
+ console.error('Usage: guardian recipe show <id>');
1913
+ process.exit(2);
1914
+ }
1915
+
1916
+ const recipe = getRecipe(config.id);
1917
+ if (!recipe) {
1918
+ console.error(`Recipe not found: ${config.id}`);
1919
+ process.exit(1);
1920
+ }
1921
+
1922
+ console.log();
1923
+ console.log(formatRecipe(recipe));
1924
+ const reg = getRegistryEntry(recipe.id);
1925
+ const checksum = computeRecipeChecksum(recipe);
1926
+ const mismatch = reg && reg.checksum && reg.checksum !== checksum;
1927
+ if (reg) {
1928
+ console.log(`Source: ${reg.source}`);
1929
+ console.log(`Version: ${reg.version}`);
1930
+ console.log(`Checksum: ${reg.checksum}`);
1931
+ if (mismatch) {
1932
+ console.log('⚠️ Warning: checksum mismatch — recipe may have been modified');
1933
+ }
1934
+ }
1935
+ } else if (action === 'run') {
1936
+ if (!config.id || !config.url) {
1937
+ console.error('Usage: guardian recipe run <id> --url <url>');
1938
+ process.exit(2);
1939
+ }
1940
+
1941
+ const recipe = getRecipe(config.id);
1942
+ if (!recipe) {
1943
+ console.error(`Recipe not found: ${config.id}`);
1944
+ process.exit(1);
1945
+ }
1946
+
1947
+ // Enforce plan limits without counting recipe execution as a scan
1948
+ checkPlanBeforeScan({ url: config.url }, { recordUsage: false });
1949
+
1950
+ // Log recipe execution
1951
+ logAudit('recipe:run', { recipeId: recipe.id, url: config.url });
1952
+
1953
+ // Phase B: Execute recipe as enforced runtime
1954
+ (async () => {
1955
+ const { executeRecipeRuntime } = require('../src/recipes/recipe-runtime');
1956
+ const { recipeFailureToAttempt, assessRecipeImpact } = require('../src/recipes/recipe-failure-analysis');
1957
+
1958
+ console.log(`\n▶️ Executing recipe: ${recipe.name}`);
1959
+ console.log(` URL: ${config.url}`);
1960
+ console.log(` Steps: ${recipe.steps.length}\n`);
1961
+
1962
+ try {
1963
+ const result = await executeRecipeRuntime(config.id, config.url, { timeout: 20000 });
1964
+
1965
+ if (result.success) {
1966
+ console.log(`✅ RECIPE PASSED: ${recipe.name}`);
1967
+ console.log(` Goal reached: ${recipe.expectedGoal}`);
1968
+ console.log(` Duration: ${result.duration}s`);
1969
+ process.exit(0);
1970
+ } else {
1971
+ console.log(`❌ RECIPE FAILED: ${recipe.name}`);
1972
+ console.log(` Reason: ${result.failureReason}`);
1973
+ if (result.failedStep) {
1974
+ const stepNum = parseInt(result.failedStep.split('-').pop(), 10) + 1;
1975
+ console.log(` Failed at step ${stepNum} of ${result.steps.length}`);
1976
+ }
1977
+ console.log(` Duration: ${result.duration}s\n`);
1978
+
1979
+ // Integrate failure into decision engine
1980
+ const attemptForm = recipeFailureToAttempt(result);
1981
+ const impact = assessRecipeImpact(result);
1982
+
1983
+ console.log(` Risk Assessment: ${impact.severity} (score: ${impact.riskScore}/100)`);
1984
+ console.log(` Impact: ${impact.message}\n`);
1985
+
1986
+ process.exit(1);
1987
+ }
1988
+ } catch (err) {
1989
+ console.error(`\n❌ Recipe execution error: ${err.message}\n`);
1990
+ process.exit(2);
1991
+ }
1992
+ })();
1993
+ } else if (action === 'export') {
1994
+ if (!config.id || !config.out) {
1995
+ console.error('Usage: guardian recipe export <id> --out <file>');
1996
+ process.exit(2);
1997
+ }
1998
+ requirePermission('recipe:manage', 'export recipes');
1999
+ const result = exportRecipeWithMetadata(config.id, config.out);
2000
+ logAudit(AUDIT_ACTIONS.RECIPE_EXPORT, { recipeId: config.id, output: config.out, checksum: result.checksum });
2001
+ console.log(`\n✓ Recipe exported`);
2002
+ console.log(` File: ${config.out}`);
2003
+ console.log(` Checksum: ${result.checksum}`);
2004
+ } else if (action === 'import') {
2005
+ if (!config.file) {
2006
+ console.error('Usage: guardian recipe import <file>');
2007
+ process.exit(2);
2008
+ }
2009
+ requirePermission('recipe:manage', 'import recipes');
2010
+ const result = importRecipeWithMetadata(config.file, { force: config.force });
2011
+ logAudit(AUDIT_ACTIONS.RECIPE_IMPORT, { recipeId: result.recipe.id, file: config.file, checksum: result.checksum, force: config.force });
2012
+ console.log(`\n✓ Import complete`);
2013
+ console.log(` Recipe: ${result.recipe.id}`);
2014
+ console.log(` Checksum: ${result.checksum}`);
2015
+ } else {
2016
+ console.error('Unknown recipe action. Use: list, show <id>, run <id> --url <url>, export <id> --out <file>, import <file> [--force]');
2017
+ process.exit(2);
2018
+ }
2019
+
2020
+ process.exit(0);
2021
+ } catch (err) {
2022
+ console.error(`\n❌ Recipe error: ${err.message}\n`);
2023
+ process.exit(1);
2024
+ }
2025
+ } else if (parsed.subcommand === 'list') {
2026
+ const exitCode = listRuns(config.artifactsDir, config.filters);
2027
+ process.exit(exitCode);
2028
+ } else if (parsed.subcommand === 'cleanup') {
2029
+ const result = await cleanup(
2030
+ config.artifactsDir,
2031
+ {
2032
+ olderThan: config.olderThan,
2033
+ keepLatest: config.keepLatest,
2034
+ failedOnly: config.failedOnly
2035
+ }
2036
+ );
2037
+
2038
+ console.log(`\n✓ Cleanup completed`);
2039
+ console.log(` Deleted: ${result.deleted} run(s)`);
2040
+ console.log(` Kept: ${result.kept} run(s)`);
2041
+
2042
+ if (result.errors && result.errors.length > 0) {
2043
+ console.log(` Errors: ${result.errors.length}`);
2044
+ result.errors.forEach(err => {
2045
+ console.log(` - ${err}`);
2046
+ });
2047
+ process.exit(1);
2048
+ }
2049
+ process.exit(0);
850
2050
  } else if (parsed.subcommand === 'protect') {
2051
+ // Phase 8: Check plan limits before scan
2052
+ checkPlanBeforeScan(config);
851
2053
  await runRealityCLI(config);
852
2054
  } else if (parsed.subcommand === 'smoke') {
2055
+ // Phase 8: Check plan limits before scan
2056
+ checkPlanBeforeScan(config);
853
2057
  await runSmokeCLI(config);
854
2058
  } else if (parsed.subcommand === 'attempt') {
2059
+ // Phase 8: Check plan limits before scan
2060
+ checkPlanBeforeScan(config);
855
2061
  await runAttemptCLI(config);
2062
+ } else if (parsed.subcommand === 'journey-scan') {
2063
+ // Phase 8: Check plan limits before scan
2064
+ checkPlanBeforeScan(config);
2065
+ await runJourneyScanCLI(config);
2066
+ } else if (parsed.subcommand === 'live') {
2067
+ // Phase 8: Check feature allowed for live guardian
2068
+ const liveCheck = checkFeatureAllowed('liveGuardian');
2069
+ if (!liveCheck.allowed) {
2070
+ console.error(`\n❌ ${liveCheck.message}`);
2071
+ console.log(getUpgradeMessage());
2072
+ process.exit(1);
2073
+ }
2074
+
2075
+ // Phase 10: Track first live session
2076
+ recordFirstLive();
2077
+
2078
+ await runLiveCLI(config);
2079
+ } else if (parsed.subcommand === 'live-start') {
2080
+ // Feature check
2081
+ const liveCheck = checkFeatureAllowed('liveGuardian');
2082
+ if (!liveCheck.allowed) {
2083
+ console.error(`\n❌ ${liveCheck.message}`);
2084
+ console.log(getUpgradeMessage());
2085
+ process.exit(1);
2086
+ }
2087
+ // RBAC permission
2088
+ try { requirePermission('live:run', 'start live schedule'); } catch (e) { console.error(`\n❌ ${e.message}`); process.exit(1); }
2089
+ const { createSchedule, startBackgroundRunner } = require('../src/guardian/live-scheduler');
2090
+ const entry = createSchedule({ url: config.baseUrl, preset: config.preset, intervalMinutes: config.intervalMinutes });
2091
+ const runner = startBackgroundRunner();
2092
+ console.log('\n🟢 Live schedule started');
2093
+ console.log(` id: ${entry.id}`);
2094
+ console.log(` url: ${entry.url}`);
2095
+ console.log(` preset: ${entry.preset}`);
2096
+ console.log(` every: ${entry.intervalMinutes} min`);
2097
+ console.log(` nextRunAt: ${entry.nextRunAt}`);
2098
+ console.log(` runnerPid: ${runner.pid}`);
2099
+ process.exit(0);
2100
+ } else if (parsed.subcommand === 'live-stop') {
2101
+ // RBAC permission
2102
+ try { requirePermission('live:run', 'stop live schedule'); } catch (e) { console.error(`\n❌ ${e.message}`); process.exit(1); }
2103
+ const { stopSchedule } = require('../src/guardian/live-scheduler');
2104
+ try {
2105
+ const s = stopSchedule(config.id);
2106
+ console.log(`\n🛑 Schedule stopped: ${s.id}`);
2107
+ process.exit(0);
2108
+ } catch (err) {
2109
+ console.error(`\n❌ ${err.message}`);
2110
+ process.exit(1);
2111
+ }
2112
+ } else if (parsed.subcommand === 'live-status') {
2113
+ const { listSchedules, loadState } = require('../src/guardian/live-scheduler');
2114
+ const state = loadState();
2115
+ const schedules = listSchedules();
2116
+ console.log('\n📋 Live schedules:');
2117
+ for (const s of schedules) {
2118
+ console.log(` - ${s.id} | ${s.status} | every ${s.intervalMinutes} min`);
2119
+ console.log(` url: ${s.url} | preset: ${s.preset}`);
2120
+ console.log(` lastRunAt: ${s.lastRunAt || 'n/a'} | nextRunAt: ${s.nextRunAt || 'n/a'}`);
2121
+ }
2122
+ const pid = state.runner?.pid;
2123
+ console.log(`\nRunner: ${pid ? `pid ${pid}` : 'not running'}`);
2124
+ process.exit(0);
2125
+ } else if (parsed.subcommand === 'ci') {
2126
+ // Phase 8: Check feature allowed for CI mode
2127
+ const ciCheck = checkFeatureAllowed('ciMode');
2128
+ if (!ciCheck.allowed) {
2129
+ console.error(`\n❌ ${ciCheck.message}`);
2130
+ console.log(getUpgradeMessage());
2131
+ process.exit(1);
2132
+ }
2133
+ // Phase 4: CI gate mode
2134
+ const { runCIGate } = require('../src/guardian/ci-cli');
2135
+ const exitCode = await runCIGate(config);
2136
+ process.exit(exitCode);
856
2137
  } else if (parsed.subcommand === 'reality') {
2138
+ // Phase 8: Check plan limits before scan
2139
+ checkPlanBeforeScan(config);
857
2140
  await runRealityCLI(config);
858
2141
  } else if (parsed.subcommand === 'scan') {
2142
+ // Phase 8: Check plan limits before scan
2143
+ checkPlanBeforeScan(config);
859
2144
  // Phase 6: First-run concise guidance
860
2145
  try {
861
2146
  const { baselineExists } = require('../src/guardian/baseline-storage');