@odavl/guardian 0.1.0-rc1 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +146 -0
- package/README.md +155 -97
- package/bin/guardian.js +1544 -55
- package/config/README.md +59 -0
- package/config/profiles/landing-demo.yaml +16 -0
- package/package.json +26 -11
- package/policies/landing-demo.json +22 -0
- package/src/enterprise/audit-logger.js +166 -0
- package/src/enterprise/pdf-exporter.js +267 -0
- package/src/enterprise/rbac-gate.js +142 -0
- package/src/enterprise/rbac.js +239 -0
- package/src/enterprise/site-manager.js +180 -0
- package/src/founder/feedback-system.js +156 -0
- package/src/founder/founder-tracker.js +213 -0
- package/src/founder/usage-signals.js +141 -0
- package/src/guardian/alert-ledger.js +121 -0
- package/src/guardian/attempt-engine.js +587 -12
- package/src/guardian/attempt-registry.js +42 -1
- package/src/guardian/attempt-relevance.js +106 -0
- package/src/guardian/attempt.js +85 -39
- package/src/guardian/attempts-filter.js +63 -0
- package/src/guardian/baseline.js +50 -8
- package/src/guardian/breakage-intelligence.js +1 -0
- package/src/guardian/browser-pool.js +131 -0
- package/src/guardian/browser.js +28 -1
- package/src/guardian/ci-cli.js +121 -0
- package/src/guardian/ci-mode.js +15 -0
- package/src/guardian/ci-output.js +38 -0
- package/src/guardian/cli-summary.js +167 -67
- package/src/guardian/config-loader.js +162 -0
- package/src/guardian/data-guardian-detector.js +189 -0
- package/src/guardian/detection-layers.js +271 -0
- package/src/guardian/drift-detector.js +100 -0
- package/src/guardian/enhanced-html-reporter.js +221 -4
- package/src/guardian/env-guard.js +127 -0
- package/src/guardian/failure-intelligence.js +173 -0
- package/src/guardian/first-run-profile.js +89 -0
- package/src/guardian/first-run.js +54 -0
- package/src/guardian/flag-validator.js +111 -0
- package/src/guardian/flow-executor.js +309 -44
- package/src/guardian/html-reporter.js +2 -0
- package/src/guardian/human-reporter.js +431 -0
- package/src/guardian/index.js +22 -19
- package/src/guardian/init-command.js +9 -5
- package/src/guardian/intent-detector.js +146 -0
- package/src/guardian/journey-definitions.js +132 -0
- package/src/guardian/journey-scan-cli.js +145 -0
- package/src/guardian/journey-scanner.js +583 -0
- package/src/guardian/junit-reporter.js +18 -1
- package/src/guardian/language-detection.js +99 -0
- package/src/guardian/live-cli.js +95 -0
- package/src/guardian/live-scheduler-runner.js +137 -0
- package/src/guardian/live-scheduler.js +146 -0
- package/src/guardian/market-reporter.js +357 -82
- package/src/guardian/parallel-executor.js +116 -0
- package/src/guardian/pattern-analyzer.js +348 -0
- package/src/guardian/policy.js +80 -3
- package/src/guardian/prerequisite-checker.js +101 -0
- package/src/guardian/preset-loader.js +27 -18
- package/src/guardian/profile-loader.js +96 -0
- package/src/guardian/reality.js +1612 -115
- package/src/guardian/reporter.js +27 -41
- package/src/guardian/run-artifacts.js +212 -0
- package/src/guardian/run-cleanup.js +207 -0
- package/src/guardian/run-latest.js +90 -0
- package/src/guardian/run-list.js +211 -0
- package/src/guardian/run-summary.js +20 -0
- package/src/guardian/scan-presets.js +100 -11
- package/src/guardian/selector-fallbacks.js +394 -0
- package/src/guardian/semantic-contact-detection.js +255 -0
- package/src/guardian/semantic-contact-finder.js +201 -0
- package/src/guardian/semantic-targets.js +234 -0
- package/src/guardian/site-introspection.js +257 -0
- package/src/guardian/smoke.js +258 -0
- package/src/guardian/snapshot-schema.js +25 -1
- package/src/guardian/snapshot.js +69 -3
- package/src/guardian/stability-scorer.js +169 -0
- package/src/guardian/success-evaluator.js +214 -0
- package/src/guardian/template-command.js +184 -0
- package/src/guardian/text-formatters.js +426 -0
- package/src/guardian/timeout-profiles.js +57 -0
- package/src/guardian/verdict.js +320 -0
- package/src/guardian/verdicts.js +74 -0
- package/src/guardian/wait-for-outcome.js +120 -0
- package/src/guardian/watch-runner.js +181 -0
- package/src/payments/stripe-checkout.js +169 -0
- package/src/plans/plan-definitions.js +148 -0
- package/src/plans/plan-manager.js +211 -0
- package/src/plans/usage-tracker.js +210 -0
- package/src/recipes/recipe-engine.js +188 -0
- package/src/recipes/recipe-failure-analysis.js +159 -0
- package/src/recipes/recipe-registry.js +134 -0
- package/src/recipes/recipe-runtime.js +507 -0
- package/src/recipes/recipe-store.js +410 -0
- package/guardian-contract-v1.md +0 -149
- /package/{guardian.config.json → config/guardian.config.json} +0 -0
- /package/{guardian.policy.json → config/guardian.policy.json} +0 -0
- /package/{guardian.profile.docs.yaml → config/profiles/docs.yaml} +0 -0
- /package/{guardian.profile.ecommerce.yaml → config/profiles/ecommerce.yaml} +0 -0
- /package/{guardian.profile.marketing.yaml → config/profiles/marketing.yaml} +0 -0
- /package/{guardian.profile.saas.yaml → config/profiles/saas.yaml} +0 -0
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wave 1.3 — Outcome-Based Success Evaluator
|
|
3
|
+
* Deterministic success evaluation for form submissions and key flows.
|
|
4
|
+
* No reliance on specific confirmation text; uses signals: Network, Navigation, DOM, Errors.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const URL_SAFE_STATUSES = new Set([200, 201, 202, 204, 302, 303]);
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Capture pre-submit state around a submit element's owning form.
|
|
11
|
+
* @param {import('playwright').Page} page
|
|
12
|
+
* @param {import('playwright').ElementHandle|null} submitHandle
|
|
13
|
+
*/
|
|
14
|
+
async function captureBeforeState(page, submitHandle) {
|
|
15
|
+
const url = page.url();
|
|
16
|
+
const state = await page.evaluate((submitEl) => {
|
|
17
|
+
const result = {
|
|
18
|
+
formSelector: null,
|
|
19
|
+
formExists: false,
|
|
20
|
+
inputsFilledCount: 0,
|
|
21
|
+
inputsTotal: 0,
|
|
22
|
+
ariaInvalidCount: 0,
|
|
23
|
+
hasAlertRegion: false,
|
|
24
|
+
alertTextLength: 0,
|
|
25
|
+
liveRegionTextLength: 0,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
let form = null;
|
|
29
|
+
if (submitEl) {
|
|
30
|
+
form = submitEl.closest('form');
|
|
31
|
+
} else {
|
|
32
|
+
form = document.querySelector('form');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (form) {
|
|
36
|
+
result.formSelector = form.getAttribute('id') ? `#${form.id}` : null;
|
|
37
|
+
result.formExists = true;
|
|
38
|
+
const inputs = Array.from(form.querySelectorAll('input, textarea, select'));
|
|
39
|
+
result.inputsTotal = inputs.length;
|
|
40
|
+
result.inputsFilledCount = inputs.filter((el) => {
|
|
41
|
+
const val = (el.value || '').trim();
|
|
42
|
+
return val.length > 0;
|
|
43
|
+
}).length;
|
|
44
|
+
result.ariaInvalidCount = inputs.filter((el) => el.getAttribute('aria-invalid') === 'true').length;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// role=alert or [aria-live]
|
|
48
|
+
const alertEl = document.querySelector('[role="alert"], .alert, .error, .invalid');
|
|
49
|
+
if (alertEl) {
|
|
50
|
+
result.hasAlertRegion = true;
|
|
51
|
+
result.alertTextLength = (alertEl.textContent || '').trim().length;
|
|
52
|
+
}
|
|
53
|
+
const liveEl = document.querySelector('[aria-live]');
|
|
54
|
+
if (liveEl) {
|
|
55
|
+
result.liveRegionTextLength = (liveEl.textContent || '').trim().length;
|
|
56
|
+
}
|
|
57
|
+
return result;
|
|
58
|
+
}, submitHandle);
|
|
59
|
+
|
|
60
|
+
return { url, state };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Capture post-submit state.
|
|
65
|
+
* @param {import('playwright').Page} page
|
|
66
|
+
* @param {string|null} originalFormSelector
|
|
67
|
+
*/
|
|
68
|
+
async function captureAfterState(page, originalFormSelector) {
|
|
69
|
+
const url = page.url();
|
|
70
|
+
const state = await page.evaluate((formSelector) => {
|
|
71
|
+
const result = {
|
|
72
|
+
formExists: false,
|
|
73
|
+
formDisabled: false,
|
|
74
|
+
inputsFilledCount: 0,
|
|
75
|
+
inputsTotal: 0,
|
|
76
|
+
ariaInvalidCount: 0,
|
|
77
|
+
hasAlertRegion: false,
|
|
78
|
+
alertTextLength: 0,
|
|
79
|
+
liveRegionTextLength: 0,
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
let form = null;
|
|
83
|
+
if (formSelector) {
|
|
84
|
+
form = document.querySelector(formSelector);
|
|
85
|
+
}
|
|
86
|
+
if (!form) {
|
|
87
|
+
form = document.querySelector('form');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (form) {
|
|
91
|
+
result.formExists = true;
|
|
92
|
+
result.formDisabled = !!form.getAttribute('disabled') || (form.classList.contains('disabled'));
|
|
93
|
+
const inputs = Array.from(form.querySelectorAll('input, textarea, select'));
|
|
94
|
+
result.inputsTotal = inputs.length;
|
|
95
|
+
result.inputsFilledCount = inputs.filter((el) => (el.value || '').trim().length > 0).length;
|
|
96
|
+
result.ariaInvalidCount = inputs.filter((el) => el.getAttribute('aria-invalid') === 'true').length;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const alertEl = document.querySelector('[role="alert"], .alert, .error, .invalid');
|
|
100
|
+
if (alertEl) {
|
|
101
|
+
result.hasAlertRegion = true;
|
|
102
|
+
result.alertTextLength = (alertEl.textContent || '').trim().length;
|
|
103
|
+
}
|
|
104
|
+
const liveEl = document.querySelector('[aria-live]');
|
|
105
|
+
if (liveEl) {
|
|
106
|
+
result.liveRegionTextLength = (liveEl.textContent || '').trim().length;
|
|
107
|
+
}
|
|
108
|
+
return result;
|
|
109
|
+
}, originalFormSelector);
|
|
110
|
+
|
|
111
|
+
return { url, state };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Evaluate success based on signals.
|
|
116
|
+
* @param {{url:string,state:Object}} before
|
|
117
|
+
* @param {{url:string,state:Object}} after
|
|
118
|
+
* @param {{requests:Array,responses:Array,consoleErrors:Array,navChanged:boolean,baseOrigin:string}} events
|
|
119
|
+
* @returns {{status:'success'|'friction'|'failure',confidence:'high'|'medium'|'low',reasons:string[],evidence:Object}}
|
|
120
|
+
*/
|
|
121
|
+
function evaluateSuccess(before, after, events) {
|
|
122
|
+
const reasons = [];
|
|
123
|
+
const evidence = {
|
|
124
|
+
network: [],
|
|
125
|
+
urlChanged: before.url !== after.url,
|
|
126
|
+
formCleared: false,
|
|
127
|
+
formDisappeared: false,
|
|
128
|
+
formDisabled: false,
|
|
129
|
+
alertRegionDelta: 0,
|
|
130
|
+
liveRegionDelta: 0,
|
|
131
|
+
ariaInvalidDelta: 0,
|
|
132
|
+
consoleErrors: events.consoleErrors || [],
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
// A) Network success
|
|
136
|
+
const baseOrigin = events.baseOrigin;
|
|
137
|
+
let strongNetworkPositive = false;
|
|
138
|
+
for (const r of (events.responses || [])) {
|
|
139
|
+
const req = typeof r.request === 'function' ? r.request() : r.request;
|
|
140
|
+
if (!req || typeof req.url !== 'function') continue;
|
|
141
|
+
const url = req.url();
|
|
142
|
+
const originOk = safeSameOrAllowedOrigin(url, baseOrigin);
|
|
143
|
+
const method = (typeof req.method === 'function' ? req.method() : '').toUpperCase();
|
|
144
|
+
const status = typeof r.status === 'function' ? r.status() : r.status;
|
|
145
|
+
const statusOk = URL_SAFE_STATUSES.has(status);
|
|
146
|
+
evidence.network.push({ method, url, status, originOk, statusOk });
|
|
147
|
+
if (originOk && (method === 'POST' || method === 'PUT') && statusOk) {
|
|
148
|
+
strongNetworkPositive = true;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (strongNetworkPositive) {
|
|
153
|
+
reasons.push('Network submit succeeded (safe status and origin)');
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// B) Navigation
|
|
157
|
+
const navPositive = !!events.navChanged;
|
|
158
|
+
if (navPositive) reasons.push('URL changed after submit');
|
|
159
|
+
|
|
160
|
+
// C) DOM outcome
|
|
161
|
+
const beforeState = before.state;
|
|
162
|
+
const afterState = after.state;
|
|
163
|
+
evidence.formDisappeared = !!beforeState.formExists && !afterState.formExists;
|
|
164
|
+
evidence.formDisabled = !!afterState.formDisabled;
|
|
165
|
+
evidence.formCleared = beforeState.inputsFilledCount > 0 && afterState.inputsFilledCount < beforeState.inputsFilledCount;
|
|
166
|
+
evidence.alertRegionDelta = (afterState.alertTextLength || 0) - (beforeState.alertTextLength || 0);
|
|
167
|
+
evidence.liveRegionDelta = (afterState.liveRegionTextLength || 0) - (beforeState.liveRegionTextLength || 0);
|
|
168
|
+
evidence.ariaInvalidDelta = (afterState.ariaInvalidCount || 0) - (beforeState.ariaInvalidCount || 0);
|
|
169
|
+
|
|
170
|
+
const domPositive = evidence.formDisappeared || evidence.formDisabled || evidence.formCleared || evidence.liveRegionDelta > 0;
|
|
171
|
+
if (domPositive) reasons.push('Form outcome indicates completion (cleared/disabled/disappeared or live region updated)');
|
|
172
|
+
|
|
173
|
+
// D) Error outcome
|
|
174
|
+
const strongNegative = evidence.ariaInvalidDelta > 0 || (afterState.hasAlertRegion && evidence.alertRegionDelta > 0);
|
|
175
|
+
if (strongNegative) reasons.push('Error markers increased after submit');
|
|
176
|
+
|
|
177
|
+
// E) Console errors
|
|
178
|
+
const consoleNegative = (events.consoleErrors || []).length > 0;
|
|
179
|
+
if (consoleNegative) reasons.push('Console errors after submit');
|
|
180
|
+
|
|
181
|
+
// Decision table
|
|
182
|
+
let status = 'failure';
|
|
183
|
+
let confidence = 'low';
|
|
184
|
+
if (strongNetworkPositive && (navPositive || domPositive) && !strongNegative && !consoleNegative) {
|
|
185
|
+
status = 'success';
|
|
186
|
+
confidence = 'high';
|
|
187
|
+
} else if ((strongNetworkPositive || navPositive || domPositive) && (strongNegative || consoleNegative)) {
|
|
188
|
+
status = 'friction';
|
|
189
|
+
confidence = strongNetworkPositive ? 'medium' : 'low';
|
|
190
|
+
} else if (strongNetworkPositive || navPositive || domPositive) {
|
|
191
|
+
status = 'success';
|
|
192
|
+
confidence = 'medium';
|
|
193
|
+
} else {
|
|
194
|
+
status = 'failure';
|
|
195
|
+
confidence = strongNegative ? 'medium' : 'low';
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return { status, confidence, reasons, evidence };
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function safeSameOrAllowedOrigin(url, baseOrigin) {
|
|
202
|
+
try {
|
|
203
|
+
const u = new URL(url);
|
|
204
|
+
return u.origin === baseOrigin;
|
|
205
|
+
} catch {
|
|
206
|
+
return false;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
module.exports = {
|
|
211
|
+
captureBeforeState,
|
|
212
|
+
captureAfterState,
|
|
213
|
+
evaluateSuccess,
|
|
214
|
+
};
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Template Command
|
|
3
|
+
*
|
|
4
|
+
* Generate minimal config templates:
|
|
5
|
+
* guardian template saas
|
|
6
|
+
* guardian template shop
|
|
7
|
+
* guardian template landing
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
|
|
13
|
+
const TEMPLATES = {
|
|
14
|
+
saas: {
|
|
15
|
+
name: 'SaaS Startup Check',
|
|
16
|
+
description: 'Verify core SaaS flows: signup, login, dashboard',
|
|
17
|
+
intent: {
|
|
18
|
+
category: 'product_type',
|
|
19
|
+
value: 'saas',
|
|
20
|
+
confidence: 1.0
|
|
21
|
+
},
|
|
22
|
+
journeys: [
|
|
23
|
+
{
|
|
24
|
+
id: 'signup_flow',
|
|
25
|
+
name: 'User Signup',
|
|
26
|
+
description: 'Verify signup journey completes',
|
|
27
|
+
steps: [
|
|
28
|
+
{ action: 'navigate', target: '/', name: 'Home' },
|
|
29
|
+
{ action: 'click', target: 'a[href*="/signup"], button:has-text("Sign up")', name: 'Click signup' },
|
|
30
|
+
{ action: 'wait', ms: 2000 },
|
|
31
|
+
{ action: 'screenshot', name: 'Signup form loaded' }
|
|
32
|
+
],
|
|
33
|
+
criticality: 'CRITICAL',
|
|
34
|
+
onFailure: 'FAIL'
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
id: 'landing_page',
|
|
38
|
+
name: 'Landing Page Load',
|
|
39
|
+
description: 'Verify homepage loads without errors',
|
|
40
|
+
steps: [
|
|
41
|
+
{ action: 'navigate', target: '/', name: 'Home' },
|
|
42
|
+
{ action: 'screenshot', name: 'Homepage loaded' }
|
|
43
|
+
],
|
|
44
|
+
criticality: 'CRITICAL',
|
|
45
|
+
onFailure: 'WARN'
|
|
46
|
+
}
|
|
47
|
+
],
|
|
48
|
+
policy: {
|
|
49
|
+
failOnSeverity: 'CRITICAL',
|
|
50
|
+
maxWarnings: 10,
|
|
51
|
+
requireBaseline: false
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
shop: {
|
|
56
|
+
name: 'E-Commerce Shop Check',
|
|
57
|
+
description: 'Verify core shop flows: browse, add to cart, checkout',
|
|
58
|
+
intent: {
|
|
59
|
+
category: 'product_type',
|
|
60
|
+
value: 'ecommerce',
|
|
61
|
+
confidence: 1.0
|
|
62
|
+
},
|
|
63
|
+
journeys: [
|
|
64
|
+
{
|
|
65
|
+
id: 'browse_products',
|
|
66
|
+
name: 'Browse Products',
|
|
67
|
+
description: 'Verify product catalog loads',
|
|
68
|
+
steps: [
|
|
69
|
+
{ action: 'navigate', target: '/', name: 'Home' },
|
|
70
|
+
{ action: 'click', target: 'a[href*="/shop"], a[href*="/products"], button:has-text("Shop")', name: 'Go to shop' },
|
|
71
|
+
{ action: 'wait', ms: 2000 },
|
|
72
|
+
{ action: 'screenshot', name: 'Products page' }
|
|
73
|
+
],
|
|
74
|
+
criticality: 'CRITICAL',
|
|
75
|
+
onFailure: 'FAIL'
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
id: 'add_to_cart',
|
|
79
|
+
name: 'Add to Cart',
|
|
80
|
+
description: 'Verify add-to-cart flow',
|
|
81
|
+
steps: [
|
|
82
|
+
{ action: 'navigate', target: '/', name: 'Home' },
|
|
83
|
+
{ action: 'click', target: 'a[href*="/shop"], a[href*="/products"]', name: 'Go to shop' },
|
|
84
|
+
{ action: 'wait', ms: 2000 },
|
|
85
|
+
{ action: 'click', target: 'button:has-text("Add"), button:has-text("Cart")', name: 'Add item' },
|
|
86
|
+
{ action: 'screenshot', name: 'Cart updated' }
|
|
87
|
+
],
|
|
88
|
+
criticality: 'CRITICAL',
|
|
89
|
+
onFailure: 'WARN'
|
|
90
|
+
}
|
|
91
|
+
],
|
|
92
|
+
policy: {
|
|
93
|
+
failOnSeverity: 'CRITICAL',
|
|
94
|
+
maxWarnings: 10,
|
|
95
|
+
requireBaseline: false
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
|
|
99
|
+
landing: {
|
|
100
|
+
name: 'Landing Page Check',
|
|
101
|
+
description: 'Verify landing page loads and core CTAs work',
|
|
102
|
+
intent: {
|
|
103
|
+
category: 'product_type',
|
|
104
|
+
value: 'landing',
|
|
105
|
+
confidence: 1.0
|
|
106
|
+
},
|
|
107
|
+
journeys: [
|
|
108
|
+
{
|
|
109
|
+
id: 'page_load',
|
|
110
|
+
name: 'Page Load',
|
|
111
|
+
description: 'Verify landing page loads',
|
|
112
|
+
steps: [
|
|
113
|
+
{ action: 'navigate', target: '/', name: 'Home' },
|
|
114
|
+
{ action: 'screenshot', name: 'Landing page' }
|
|
115
|
+
],
|
|
116
|
+
criticality: 'CRITICAL',
|
|
117
|
+
onFailure: 'FAIL'
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
id: 'cta_click',
|
|
121
|
+
name: 'Call-to-Action',
|
|
122
|
+
description: 'Verify main CTA is clickable',
|
|
123
|
+
steps: [
|
|
124
|
+
{ action: 'navigate', target: '/', name: 'Home' },
|
|
125
|
+
{ action: 'click', target: 'button:has-text("Get started"), button:has-text("Start"), button:has-text("Sign up"), a[href*="/signup"]', name: 'Click CTA' },
|
|
126
|
+
{ action: 'screenshot', name: 'CTA works' }
|
|
127
|
+
],
|
|
128
|
+
criticality: 'CRITICAL',
|
|
129
|
+
onFailure: 'WARN'
|
|
130
|
+
}
|
|
131
|
+
],
|
|
132
|
+
policy: {
|
|
133
|
+
failOnSeverity: 'CRITICAL',
|
|
134
|
+
maxWarnings: 5,
|
|
135
|
+
requireBaseline: false
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Generate config file from template
|
|
142
|
+
* @param {string} templateName - saas, shop, or landing
|
|
143
|
+
* @param {object} options - Generation options
|
|
144
|
+
* @returns {object} Result with generated file path
|
|
145
|
+
*/
|
|
146
|
+
function generateTemplate(templateName, options = {}) {
|
|
147
|
+
const cwd = options.cwd || process.cwd();
|
|
148
|
+
const outputFile = options.output || `guardian-${templateName}.json`;
|
|
149
|
+
const outputPath = path.join(cwd, outputFile);
|
|
150
|
+
|
|
151
|
+
if (!TEMPLATES[templateName]) {
|
|
152
|
+
throw new Error(`Unknown template: ${templateName}. Available: ${Object.keys(TEMPLATES).join(', ')}`);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const template = TEMPLATES[templateName];
|
|
156
|
+
const content = JSON.stringify(template, null, 2);
|
|
157
|
+
|
|
158
|
+
fs.writeFileSync(outputPath, content, 'utf-8');
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
success: true,
|
|
162
|
+
template: templateName,
|
|
163
|
+
outputPath,
|
|
164
|
+
message: `Generated ${templateName} template: ${outputFile}`
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* List available templates
|
|
170
|
+
* @returns {array} Array of template names with descriptions
|
|
171
|
+
*/
|
|
172
|
+
function listTemplates() {
|
|
173
|
+
return Object.entries(TEMPLATES).map(([name, config]) => ({
|
|
174
|
+
name,
|
|
175
|
+
description: config.description,
|
|
176
|
+
journeys: config.journeys.length
|
|
177
|
+
}));
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
module.exports = {
|
|
181
|
+
generateTemplate,
|
|
182
|
+
listTemplates,
|
|
183
|
+
TEMPLATES
|
|
184
|
+
};
|