@vibecheckai/cli 3.0.4 → 3.0.7
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/bin/dev/run-v2-torture.js +30 -0
- package/bin/runners/context/index.js +1 -1
- package/bin/runners/lib/analyzers.js +38 -0
- package/bin/runners/lib/assets/vibecheck-logo.png +0 -0
- package/bin/runners/lib/contracts/auth-contract.js +8 -0
- package/bin/runners/lib/contracts/env-contract.js +3 -0
- package/bin/runners/lib/contracts/external-contract.js +10 -2
- package/bin/runners/lib/contracts/route-contract.js +7 -0
- package/bin/runners/lib/contracts.js +804 -0
- package/bin/runners/lib/detectors-v2.js +703 -0
- package/bin/runners/lib/drift.js +425 -0
- package/bin/runners/lib/entitlements-v2.js +3 -1
- package/bin/runners/lib/entitlements.js +11 -3
- package/bin/runners/lib/env-resolver.js +417 -0
- package/bin/runners/lib/extractors/client-calls.js +990 -0
- package/bin/runners/lib/extractors/fastify-route-dump.js +573 -0
- package/bin/runners/lib/extractors/fastify-routes.js +426 -0
- package/bin/runners/lib/extractors/index.js +363 -0
- package/bin/runners/lib/extractors/next-routes.js +524 -0
- package/bin/runners/lib/extractors/proof-graph.js +431 -0
- package/bin/runners/lib/extractors/route-matcher.js +451 -0
- package/bin/runners/lib/extractors/truthpack-v2.js +377 -0
- package/bin/runners/lib/extractors/ui-bindings.js +547 -0
- package/bin/runners/lib/findings-schema.js +281 -0
- package/bin/runners/lib/html-report.js +650 -0
- package/bin/runners/lib/missions/templates.js +45 -0
- package/bin/runners/lib/policy.js +295 -0
- package/bin/runners/lib/reality/correlation-detectors.js +359 -0
- package/bin/runners/lib/reality/index.js +318 -0
- package/bin/runners/lib/reality/request-hashing.js +416 -0
- package/bin/runners/lib/reality/request-mapper.js +453 -0
- package/bin/runners/lib/reality/safety-rails.js +463 -0
- package/bin/runners/lib/reality/semantic-snapshot.js +408 -0
- package/bin/runners/lib/reality/toast-detector.js +393 -0
- package/bin/runners/lib/report-html.js +5 -0
- package/bin/runners/lib/report-templates.js +5 -0
- package/bin/runners/lib/report.js +135 -0
- package/bin/runners/lib/route-truth.js +10 -10
- package/bin/runners/lib/schema-validator.js +350 -0
- package/bin/runners/lib/schemas/contracts.schema.json +160 -0
- package/bin/runners/lib/schemas/finding.schema.json +100 -0
- package/bin/runners/lib/schemas/mission-pack.schema.json +206 -0
- package/bin/runners/lib/schemas/proof-graph.schema.json +176 -0
- package/bin/runners/lib/schemas/reality-report.schema.json +162 -0
- package/bin/runners/lib/schemas/share-pack.schema.json +180 -0
- package/bin/runners/lib/schemas/ship-report.schema.json +117 -0
- package/bin/runners/lib/schemas/truthpack-v2.schema.json +303 -0
- package/bin/runners/lib/schemas/validator.js +438 -0
- package/bin/runners/lib/ui.js +562 -0
- package/bin/runners/lib/verdict-engine.js +628 -0
- package/bin/runners/runAIAgent.js +228 -1
- package/bin/runners/runBadge.js +181 -1
- package/bin/runners/runCtx.js +7 -2
- package/bin/runners/runCtxDiff.js +301 -0
- package/bin/runners/runGuard.js +168 -0
- package/bin/runners/runInitGha.js +78 -15
- package/bin/runners/runLabs.js +341 -0
- package/bin/runners/runLaunch.js +180 -1
- package/bin/runners/runMdc.js +203 -1
- package/bin/runners/runProof.zip +0 -0
- package/bin/runners/runProve.js +23 -0
- package/bin/runners/runReplay.js +114 -84
- package/bin/runners/runScan.js +111 -32
- package/bin/runners/runShip.js +23 -2
- package/bin/runners/runTruthpack.js +9 -7
- package/bin/runners/runValidate.js +161 -1
- package/bin/vibecheck.js +416 -770
- package/mcp-server/.guardrail/audit/audit.log.jsonl +2 -0
- package/mcp-server/.specs/architecture.mdc +90 -0
- package/mcp-server/.specs/security.mdc +30 -0
- package/mcp-server/README.md +252 -0
- package/mcp-server/agent-checkpoint.js +364 -0
- package/mcp-server/architect-tools.js +707 -0
- package/mcp-server/audit-mcp.js +206 -0
- package/mcp-server/codebase-architect-tools.js +838 -0
- package/mcp-server/consolidated-tools.js +804 -0
- package/mcp-server/hygiene-tools.js +428 -0
- package/mcp-server/index-v1.js +698 -0
- package/mcp-server/index.js +2092 -0
- package/mcp-server/index.old.js +4137 -0
- package/mcp-server/intelligence-tools.js +664 -0
- package/mcp-server/intent-drift-tools.js +873 -0
- package/mcp-server/mdc-generator.js +298 -0
- package/mcp-server/package-lock.json +165 -0
- package/mcp-server/package.json +47 -0
- package/mcp-server/premium-tools.js +1275 -0
- package/mcp-server/test-mcp.js +108 -0
- package/mcp-server/test-tools.js +36 -0
- package/mcp-server/tier-auth.js +147 -0
- package/mcp-server/tools/index.js +72 -0
- package/mcp-server/tools-reorganized.ts +244 -0
- package/mcp-server/truth-context.js +581 -0
- package/mcp-server/truth-firewall-tools.js +1500 -0
- package/mcp-server/vibecheck-2.0-tools.js +748 -0
- package/mcp-server/vibecheck-tools.js +1075 -0
- package/package.json +10 -8
- package/bin/guardrail.js +0 -834
- package/bin/runners/runAudit.js +0 -2
- package/bin/runners/runAutopilot.js +0 -2
- package/bin/runners/runCertify.js +0 -2
- package/bin/runners/runDashboard.js +0 -10
- package/bin/runners/runEnhancedShip.js +0 -2
- package/bin/runners/runFixPacks.js +0 -2
- package/bin/runners/runNaturalLanguage.js +0 -3
- package/bin/runners/runProof.js +0 -2
- package/bin/runners/runRealitySniff.js +0 -2
- package/bin/runners/runUpgrade.js +0 -2
- package/bin/runners/runVerifyAgentOutput.js +0 -2
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Toast/Notification Detector v2
|
|
3
|
+
*
|
|
4
|
+
* Detects toasts via DOM observation with library-specific selectors.
|
|
5
|
+
* Emits signals: toast_success, toast_error, toast_info, toast_unknown
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
"use strict";
|
|
9
|
+
|
|
10
|
+
// =============================================================================
|
|
11
|
+
// LIBRARY-SPECIFIC SELECTORS
|
|
12
|
+
// =============================================================================
|
|
13
|
+
|
|
14
|
+
const TOAST_LIBRARIES = {
|
|
15
|
+
sonner: {
|
|
16
|
+
name: "sonner",
|
|
17
|
+
container: "[data-sonner-toaster]",
|
|
18
|
+
toast: "[data-sonner-toast]",
|
|
19
|
+
typeAttr: "data-type",
|
|
20
|
+
},
|
|
21
|
+
reactHotToast: {
|
|
22
|
+
name: "react-hot-toast",
|
|
23
|
+
container: "#_rht_toaster, .react-hot-toast",
|
|
24
|
+
toast: "[aria-live]",
|
|
25
|
+
},
|
|
26
|
+
reactToastify: {
|
|
27
|
+
name: "react-toastify",
|
|
28
|
+
container: ".Toastify__toast-container",
|
|
29
|
+
toast: ".Toastify__toast",
|
|
30
|
+
successClass: "Toastify__toast--success",
|
|
31
|
+
errorClass: "Toastify__toast--error",
|
|
32
|
+
infoClass: "Toastify__toast--info",
|
|
33
|
+
warningClass: "Toastify__toast--warning",
|
|
34
|
+
},
|
|
35
|
+
radix: {
|
|
36
|
+
name: "radix",
|
|
37
|
+
container: "[data-radix-toast-viewport]",
|
|
38
|
+
toast: "[data-radix-toast-root]",
|
|
39
|
+
stateAttr: "data-state",
|
|
40
|
+
},
|
|
41
|
+
mantine: {
|
|
42
|
+
name: "mantine",
|
|
43
|
+
container: ".mantine-Notifications-root",
|
|
44
|
+
toast: ".mantine-Notification-root",
|
|
45
|
+
},
|
|
46
|
+
chakra: {
|
|
47
|
+
name: "chakra",
|
|
48
|
+
container: "[id^='chakra-toast-manager']",
|
|
49
|
+
toast: "[id^='chakra-toast']",
|
|
50
|
+
},
|
|
51
|
+
mui: {
|
|
52
|
+
name: "mui",
|
|
53
|
+
container: ".MuiSnackbar-root",
|
|
54
|
+
toast: ".MuiSnackbarContent-root, .MuiAlert-root",
|
|
55
|
+
successClass: "MuiAlert-standardSuccess",
|
|
56
|
+
errorClass: "MuiAlert-standardError",
|
|
57
|
+
infoClass: "MuiAlert-standardInfo",
|
|
58
|
+
warningClass: "MuiAlert-standardWarning",
|
|
59
|
+
},
|
|
60
|
+
notistack: {
|
|
61
|
+
name: "notistack",
|
|
62
|
+
container: ".SnackbarContainer-root",
|
|
63
|
+
toast: "#notistack-snackbar",
|
|
64
|
+
},
|
|
65
|
+
antd: {
|
|
66
|
+
name: "antd",
|
|
67
|
+
container: ".ant-message, .ant-notification",
|
|
68
|
+
toast: ".ant-message-notice, .ant-notification-notice",
|
|
69
|
+
successClass: "ant-message-success, ant-notification-notice-success",
|
|
70
|
+
errorClass: "ant-message-error, ant-notification-notice-error",
|
|
71
|
+
},
|
|
72
|
+
bootstrap: {
|
|
73
|
+
name: "bootstrap",
|
|
74
|
+
container: ".toast-container",
|
|
75
|
+
toast: ".toast[aria-live]",
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
// Universal heuristic selectors
|
|
80
|
+
const UNIVERSAL_SELECTORS = {
|
|
81
|
+
ariaLive: '[aria-live="polite"], [aria-live="assertive"]',
|
|
82
|
+
roleAlert: '[role="alert"]',
|
|
83
|
+
roleStatus: '[role="status"]',
|
|
84
|
+
ariaAtomic: '[aria-atomic="true"]',
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
// Class tokens that indicate toast
|
|
88
|
+
const TOAST_CLASS_TOKENS = [
|
|
89
|
+
"toast", "snackbar", "notification", "noti", "sonner",
|
|
90
|
+
"Toastify", "chakra-toast", "mantine-Notification",
|
|
91
|
+
"MuiSnackbar", "ant-message", "ant-notification", "notistack"
|
|
92
|
+
];
|
|
93
|
+
|
|
94
|
+
// Success/error classification tokens
|
|
95
|
+
const SUCCESS_TOKENS = ["success", "check", "done", "saved", "complete", "✓", "✅"];
|
|
96
|
+
const ERROR_TOKENS = ["error", "fail", "warning", "alert", "danger", "❌", "⚠"];
|
|
97
|
+
const INFO_TOKENS = ["info", "notice", "ℹ"];
|
|
98
|
+
|
|
99
|
+
// =============================================================================
|
|
100
|
+
// TOAST DETECTOR SCRIPT (runs in browser context)
|
|
101
|
+
// =============================================================================
|
|
102
|
+
|
|
103
|
+
const TOAST_DETECTOR_SCRIPT = `
|
|
104
|
+
(function setupToastDetector(options = {}) {
|
|
105
|
+
const {
|
|
106
|
+
maxLifetimeMs = 15000,
|
|
107
|
+
captureScreenshots = true,
|
|
108
|
+
} = options;
|
|
109
|
+
|
|
110
|
+
const TOAST_CLASS_TOKENS = ${JSON.stringify(TOAST_CLASS_TOKENS)};
|
|
111
|
+
const SUCCESS_TOKENS = ${JSON.stringify(SUCCESS_TOKENS)};
|
|
112
|
+
const ERROR_TOKENS = ${JSON.stringify(ERROR_TOKENS)};
|
|
113
|
+
const INFO_TOKENS = ${JSON.stringify(INFO_TOKENS)};
|
|
114
|
+
|
|
115
|
+
const signals = [];
|
|
116
|
+
const seenToasts = new Set();
|
|
117
|
+
|
|
118
|
+
function normalizeText(text) {
|
|
119
|
+
return (text || '').trim().replace(/\\s+/g, ' ').slice(0, 200);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function classifyToast(el) {
|
|
123
|
+
const classes = el.className?.toLowerCase() || '';
|
|
124
|
+
const text = normalizeText(el.textContent);
|
|
125
|
+
const dataType = el.getAttribute('data-type')?.toLowerCase();
|
|
126
|
+
|
|
127
|
+
// Check data-type first (most reliable)
|
|
128
|
+
if (dataType) {
|
|
129
|
+
if (SUCCESS_TOKENS.some(t => dataType.includes(t))) return 'toast_success';
|
|
130
|
+
if (ERROR_TOKENS.some(t => dataType.includes(t))) return 'toast_error';
|
|
131
|
+
if (INFO_TOKENS.some(t => dataType.includes(t))) return 'toast_info';
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Check classes
|
|
135
|
+
if (SUCCESS_TOKENS.some(t => classes.includes(t))) return 'toast_success';
|
|
136
|
+
if (ERROR_TOKENS.some(t => classes.includes(t))) return 'toast_error';
|
|
137
|
+
if (INFO_TOKENS.some(t => classes.includes(t))) return 'toast_info';
|
|
138
|
+
|
|
139
|
+
// Check text content
|
|
140
|
+
const textLower = text.toLowerCase();
|
|
141
|
+
if (SUCCESS_TOKENS.some(t => textLower.includes(t))) return 'toast_success';
|
|
142
|
+
if (ERROR_TOKENS.some(t => textLower.startsWith(t))) return 'toast_error';
|
|
143
|
+
|
|
144
|
+
// Check for icon aria-labels
|
|
145
|
+
const icons = el.querySelectorAll('[aria-label]');
|
|
146
|
+
for (const icon of icons) {
|
|
147
|
+
const label = icon.getAttribute('aria-label')?.toLowerCase();
|
|
148
|
+
if (label && SUCCESS_TOKENS.some(t => label.includes(t))) return 'toast_success';
|
|
149
|
+
if (label && ERROR_TOKENS.some(t => label.includes(t))) return 'toast_error';
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return 'toast_unknown';
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function detectLibrary(el) {
|
|
156
|
+
// Check specific library markers
|
|
157
|
+
if (el.closest('[data-sonner-toaster]')) return 'sonner';
|
|
158
|
+
if (el.closest('.Toastify__toast-container')) return 'react-toastify';
|
|
159
|
+
if (el.closest('[data-radix-toast-viewport]')) return 'radix';
|
|
160
|
+
if (el.closest('.mantine-Notifications-root')) return 'mantine';
|
|
161
|
+
if (el.closest('[id^="chakra-toast"]')) return 'chakra';
|
|
162
|
+
if (el.closest('.MuiSnackbar-root')) return 'mui';
|
|
163
|
+
if (el.closest('.SnackbarContainer-root')) return 'notistack';
|
|
164
|
+
if (el.closest('.ant-message, .ant-notification')) return 'antd';
|
|
165
|
+
return 'unknown';
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function isToastCandidate(el) {
|
|
169
|
+
// Skip hidden elements
|
|
170
|
+
const style = window.getComputedStyle(el);
|
|
171
|
+
if (style.display === 'none' || style.visibility === 'hidden') return false;
|
|
172
|
+
if (style.opacity === '0') return false;
|
|
173
|
+
|
|
174
|
+
// Check ARIA roles
|
|
175
|
+
const role = el.getAttribute('role');
|
|
176
|
+
if (role === 'alert' || role === 'status') return true;
|
|
177
|
+
if (el.getAttribute('aria-live')) return true;
|
|
178
|
+
if (el.getAttribute('aria-atomic') === 'true') return true;
|
|
179
|
+
|
|
180
|
+
// Check class tokens
|
|
181
|
+
const classes = el.className?.toLowerCase() || '';
|
|
182
|
+
if (TOAST_CLASS_TOKENS.some(t => classes.includes(t.toLowerCase()))) return true;
|
|
183
|
+
|
|
184
|
+
// Check positioning (fixed/sticky near edges)
|
|
185
|
+
if (style.position === 'fixed' || style.position === 'sticky') {
|
|
186
|
+
const rect = el.getBoundingClientRect();
|
|
187
|
+
const isNearEdge = rect.top < 100 || rect.bottom > window.innerHeight - 100;
|
|
188
|
+
if (isNearEdge && el.textContent?.trim().length > 0) return true;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function generateToastId(el) {
|
|
195
|
+
const text = normalizeText(el.textContent).slice(0, 50);
|
|
196
|
+
const rect = el.getBoundingClientRect();
|
|
197
|
+
return text + ':' + Math.round(rect.top) + ':' + Math.round(rect.left);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function processToast(el) {
|
|
201
|
+
const toastId = generateToastId(el);
|
|
202
|
+
if (seenToasts.has(toastId)) return;
|
|
203
|
+
seenToasts.add(toastId);
|
|
204
|
+
|
|
205
|
+
const signal = {
|
|
206
|
+
kind: classifyToast(el),
|
|
207
|
+
atMs: performance.now(),
|
|
208
|
+
libraryHint: detectLibrary(el),
|
|
209
|
+
message: normalizeText(el.textContent),
|
|
210
|
+
selectorHint: el.id ? '#' + el.id : (el.className ? '.' + el.className.split(' ')[0] : el.tagName.toLowerCase()),
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
signals.push(signal);
|
|
214
|
+
|
|
215
|
+
// Dispatch custom event for external listeners
|
|
216
|
+
window.dispatchEvent(new CustomEvent('vibecheck:toast', { detail: signal }));
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Mutation observer to catch toasts
|
|
220
|
+
const observer = new MutationObserver((mutations) => {
|
|
221
|
+
for (const mutation of mutations) {
|
|
222
|
+
// Check added nodes
|
|
223
|
+
for (const node of mutation.addedNodes) {
|
|
224
|
+
if (node.nodeType !== 1) continue;
|
|
225
|
+
|
|
226
|
+
// Check if this node is a toast
|
|
227
|
+
if (isToastCandidate(node)) {
|
|
228
|
+
processToast(node);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Check descendants
|
|
232
|
+
if (node.querySelectorAll) {
|
|
233
|
+
const candidates = node.querySelectorAll('[role="alert"], [role="status"], [aria-live]');
|
|
234
|
+
candidates.forEach(el => {
|
|
235
|
+
if (isToastCandidate(el)) processToast(el);
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Check attribute changes (e.g., data-state="open")
|
|
241
|
+
if (mutation.type === 'attributes' && mutation.attributeName === 'data-state') {
|
|
242
|
+
const el = mutation.target;
|
|
243
|
+
if (el.getAttribute('data-state') === 'open' && isToastCandidate(el)) {
|
|
244
|
+
processToast(el);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
observer.observe(document.body, {
|
|
251
|
+
childList: true,
|
|
252
|
+
subtree: true,
|
|
253
|
+
attributes: true,
|
|
254
|
+
attributeFilter: ['data-state', 'class', 'aria-live'],
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
// Also scan existing toasts
|
|
258
|
+
const existingToasts = document.querySelectorAll('[role="alert"], [role="status"], [aria-live]');
|
|
259
|
+
existingToasts.forEach(el => {
|
|
260
|
+
if (isToastCandidate(el)) processToast(el);
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
// Return API
|
|
264
|
+
return {
|
|
265
|
+
getSignals: () => [...signals],
|
|
266
|
+
clearSignals: () => { signals.length = 0; seenToasts.clear(); },
|
|
267
|
+
stop: () => observer.disconnect(),
|
|
268
|
+
};
|
|
269
|
+
})
|
|
270
|
+
`;
|
|
271
|
+
|
|
272
|
+
// =============================================================================
|
|
273
|
+
// SERVER-SIDE HELPERS
|
|
274
|
+
// =============================================================================
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Classify toast kind from signal data
|
|
278
|
+
*/
|
|
279
|
+
function classifyToastSignal(signal) {
|
|
280
|
+
if (!signal) return "toast_unknown";
|
|
281
|
+
|
|
282
|
+
const text = (signal.message || "").toLowerCase();
|
|
283
|
+
const classes = (signal.classes || "").toLowerCase();
|
|
284
|
+
|
|
285
|
+
// Success patterns
|
|
286
|
+
if (SUCCESS_TOKENS.some(t => text.includes(t) || classes.includes(t))) {
|
|
287
|
+
return "toast_success";
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Error patterns
|
|
291
|
+
if (ERROR_TOKENS.some(t => text.includes(t) || classes.includes(t))) {
|
|
292
|
+
return "toast_error";
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Info patterns
|
|
296
|
+
if (INFO_TOKENS.some(t => text.includes(t) || classes.includes(t))) {
|
|
297
|
+
return "toast_info";
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
return "toast_unknown";
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Create toast signal payload
|
|
305
|
+
*/
|
|
306
|
+
function createToastSignal(options = {}) {
|
|
307
|
+
const {
|
|
308
|
+
kind = "toast_unknown",
|
|
309
|
+
atMs = Date.now(),
|
|
310
|
+
libraryHint = "unknown",
|
|
311
|
+
message = "",
|
|
312
|
+
selectorHint = "",
|
|
313
|
+
screenshot = null,
|
|
314
|
+
} = options;
|
|
315
|
+
|
|
316
|
+
return {
|
|
317
|
+
kind,
|
|
318
|
+
atMs,
|
|
319
|
+
libraryHint,
|
|
320
|
+
message: message.slice(0, 200),
|
|
321
|
+
selectorHint,
|
|
322
|
+
screenshot,
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Check if a toast is a false positive (persistent banner, etc.)
|
|
328
|
+
*/
|
|
329
|
+
function isToastFalsePositive(signal, options = {}) {
|
|
330
|
+
const { maxLifetimeMs = 15000 } = options;
|
|
331
|
+
|
|
332
|
+
// If it's been visible too long, likely a persistent banner
|
|
333
|
+
if (signal.visibleDuration && signal.visibleDuration > maxLifetimeMs) {
|
|
334
|
+
return true;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Common false positive patterns
|
|
338
|
+
const falsePositivePatterns = [
|
|
339
|
+
/cookie.*consent/i,
|
|
340
|
+
/accept.*cookies/i,
|
|
341
|
+
/privacy.*policy/i,
|
|
342
|
+
/subscribe.*newsletter/i,
|
|
343
|
+
/sign.*up/i,
|
|
344
|
+
];
|
|
345
|
+
|
|
346
|
+
const text = signal.message || "";
|
|
347
|
+
if (falsePositivePatterns.some(p => p.test(text))) {
|
|
348
|
+
return true;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
return false;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Get library-specific selectors for a known library
|
|
356
|
+
*/
|
|
357
|
+
function getLibrarySelectors(libraryName) {
|
|
358
|
+
return TOAST_LIBRARIES[libraryName] || null;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Build combined selector for all toast libraries
|
|
363
|
+
*/
|
|
364
|
+
function buildToastSelector() {
|
|
365
|
+
const selectors = [];
|
|
366
|
+
|
|
367
|
+
// Add library-specific selectors
|
|
368
|
+
for (const lib of Object.values(TOAST_LIBRARIES)) {
|
|
369
|
+
if (lib.toast) selectors.push(lib.toast);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// Add universal selectors
|
|
373
|
+
selectors.push(UNIVERSAL_SELECTORS.roleAlert);
|
|
374
|
+
selectors.push(UNIVERSAL_SELECTORS.roleStatus);
|
|
375
|
+
selectors.push(UNIVERSAL_SELECTORS.ariaLive);
|
|
376
|
+
|
|
377
|
+
return selectors.join(", ");
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
module.exports = {
|
|
381
|
+
TOAST_DETECTOR_SCRIPT,
|
|
382
|
+
TOAST_LIBRARIES,
|
|
383
|
+
UNIVERSAL_SELECTORS,
|
|
384
|
+
TOAST_CLASS_TOKENS,
|
|
385
|
+
SUCCESS_TOKENS,
|
|
386
|
+
ERROR_TOKENS,
|
|
387
|
+
INFO_TOKENS,
|
|
388
|
+
classifyToastSignal,
|
|
389
|
+
createToastSignal,
|
|
390
|
+
isToastFalsePositive,
|
|
391
|
+
getLibrarySelectors,
|
|
392
|
+
buildToastSelector,
|
|
393
|
+
};
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified Report Module
|
|
3
|
+
*
|
|
4
|
+
* This is the SINGLE entry point for all report generation.
|
|
5
|
+
* Internal modules:
|
|
6
|
+
* - report-engine.js → data assembly + export formats
|
|
7
|
+
* - html-report.js → HTML generation (primary)
|
|
8
|
+
* - report-html.js → Alternative HTML styles (deprecated)
|
|
9
|
+
* - report-templates.js → Template components (internal)
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const path = require("path");
|
|
13
|
+
const fs = require("fs");
|
|
14
|
+
|
|
15
|
+
// Primary modules
|
|
16
|
+
const { generateHTMLReport, writeHTMLReport } = require("./html-report");
|
|
17
|
+
const { buildReportData, exportToSARIF, exportToCSV, exportToMarkdown, exportToJSON } = require("./report-engine");
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Generate a report from ship results
|
|
21
|
+
* @param {Object} options
|
|
22
|
+
* @param {string} options.repoRoot - Repository root path
|
|
23
|
+
* @param {Object} options.shipReport - Ship report data (optional, loads from disk if not provided)
|
|
24
|
+
* @param {string} options.format - Output format: html (default), json, sarif, csv, markdown
|
|
25
|
+
* @param {string} options.outputPath - Custom output path (optional)
|
|
26
|
+
* @returns {Object} { path, format, data }
|
|
27
|
+
*/
|
|
28
|
+
async function generateReport(options = {}) {
|
|
29
|
+
const {
|
|
30
|
+
repoRoot = process.cwd(),
|
|
31
|
+
shipReport = null,
|
|
32
|
+
format = "html",
|
|
33
|
+
outputPath = null,
|
|
34
|
+
} = options;
|
|
35
|
+
|
|
36
|
+
// Load ship report if not provided
|
|
37
|
+
let report = shipReport;
|
|
38
|
+
if (!report) {
|
|
39
|
+
const shipPath = path.join(repoRoot, ".vibecheck", "ship", "last_ship.json");
|
|
40
|
+
if (fs.existsSync(shipPath)) {
|
|
41
|
+
report = JSON.parse(fs.readFileSync(shipPath, "utf-8"));
|
|
42
|
+
} else {
|
|
43
|
+
throw new Error("No ship report found. Run 'vibecheck ship' first.");
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Build report data
|
|
48
|
+
const reportData = buildReportData(report);
|
|
49
|
+
|
|
50
|
+
// Generate output based on format
|
|
51
|
+
let outputFile;
|
|
52
|
+
let outputData;
|
|
53
|
+
|
|
54
|
+
switch (format.toLowerCase()) {
|
|
55
|
+
case "html":
|
|
56
|
+
outputData = generateHTMLReport(reportData);
|
|
57
|
+
outputFile = outputPath || path.join(repoRoot, ".vibecheck", "reports", "report.html");
|
|
58
|
+
break;
|
|
59
|
+
case "json":
|
|
60
|
+
outputData = exportToJSON(reportData);
|
|
61
|
+
outputFile = outputPath || path.join(repoRoot, ".vibecheck", "reports", "report.json");
|
|
62
|
+
break;
|
|
63
|
+
case "sarif":
|
|
64
|
+
outputData = exportToSARIF(reportData);
|
|
65
|
+
outputFile = outputPath || path.join(repoRoot, ".vibecheck", "reports", "report.sarif");
|
|
66
|
+
break;
|
|
67
|
+
case "csv":
|
|
68
|
+
outputData = exportToCSV(reportData);
|
|
69
|
+
outputFile = outputPath || path.join(repoRoot, ".vibecheck", "reports", "report.csv");
|
|
70
|
+
break;
|
|
71
|
+
case "markdown":
|
|
72
|
+
case "md":
|
|
73
|
+
outputData = exportToMarkdown(reportData);
|
|
74
|
+
outputFile = outputPath || path.join(repoRoot, ".vibecheck", "reports", "report.md");
|
|
75
|
+
break;
|
|
76
|
+
default:
|
|
77
|
+
throw new Error(`Unknown format: ${format}. Use: html, json, sarif, csv, markdown`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Ensure output directory exists
|
|
81
|
+
const outputDir = path.dirname(outputFile);
|
|
82
|
+
if (!fs.existsSync(outputDir)) {
|
|
83
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Write output
|
|
87
|
+
fs.writeFileSync(outputFile, outputData, "utf-8");
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
path: outputFile,
|
|
91
|
+
format,
|
|
92
|
+
data: reportData,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Open the latest report in browser
|
|
98
|
+
*/
|
|
99
|
+
async function openReport(repoRoot = process.cwd()) {
|
|
100
|
+
const reportPath = path.join(repoRoot, ".vibecheck", "ship", "last_ship.html");
|
|
101
|
+
const altPath = path.join(repoRoot, ".vibecheck", "reports", "report.html");
|
|
102
|
+
|
|
103
|
+
const filePath = fs.existsSync(reportPath) ? reportPath :
|
|
104
|
+
fs.existsSync(altPath) ? altPath : null;
|
|
105
|
+
|
|
106
|
+
if (!filePath) {
|
|
107
|
+
throw new Error("No report found. Run 'vibecheck ship' or 'vibecheck report' first.");
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Cross-platform open
|
|
111
|
+
const { exec } = require("child_process");
|
|
112
|
+
const cmd = process.platform === "win32" ? `start "" "${filePath}"` :
|
|
113
|
+
process.platform === "darwin" ? `open "${filePath}"` :
|
|
114
|
+
`xdg-open "${filePath}"`;
|
|
115
|
+
|
|
116
|
+
return new Promise((resolve, reject) => {
|
|
117
|
+
exec(cmd, (err) => {
|
|
118
|
+
if (err) reject(err);
|
|
119
|
+
else resolve(filePath);
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
module.exports = {
|
|
125
|
+
generateReport,
|
|
126
|
+
openReport,
|
|
127
|
+
// Re-export for backward compatibility
|
|
128
|
+
generateHTMLReport,
|
|
129
|
+
writeHTMLReport,
|
|
130
|
+
buildReportData,
|
|
131
|
+
exportToSARIF,
|
|
132
|
+
exportToCSV,
|
|
133
|
+
exportToMarkdown,
|
|
134
|
+
exportToJSON,
|
|
135
|
+
};
|
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
* Then implements validate_claim(route_exists) on top of it.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const crypto = require('crypto');
|
|
14
14
|
|
|
15
15
|
// ============================================================================
|
|
16
16
|
// CANONICALIZATION
|
|
@@ -19,7 +19,7 @@ import crypto from 'crypto';
|
|
|
19
19
|
/**
|
|
20
20
|
* Canonicalize a path to standard format.
|
|
21
21
|
*/
|
|
22
|
-
|
|
22
|
+
function canonicalizePath(p) {
|
|
23
23
|
let s = p.trim();
|
|
24
24
|
if (!s.startsWith('/')) s = '/' + s;
|
|
25
25
|
s = s.replace(/\/+/g, '/');
|
|
@@ -33,7 +33,7 @@ export function canonicalizePath(p) {
|
|
|
33
33
|
return s;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
function canonicalizeMethod(m) {
|
|
37
37
|
const u = m.toUpperCase();
|
|
38
38
|
if (u === 'ALL' || u === 'ANY') return '*';
|
|
39
39
|
return u;
|
|
@@ -141,7 +141,7 @@ function extractAppRouterMethods(code) {
|
|
|
141
141
|
return methods;
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
-
|
|
144
|
+
async function resolveNextRoutes(repoRoot) {
|
|
145
145
|
const routes = [];
|
|
146
146
|
|
|
147
147
|
// App Router: app/api/**/route.ts|js
|
|
@@ -227,7 +227,7 @@ export async function resolveNextRoutes(repoRoot) {
|
|
|
227
227
|
// FASTIFY RESOLVER (Simplified - regex based)
|
|
228
228
|
// ============================================================================
|
|
229
229
|
|
|
230
|
-
|
|
230
|
+
async function resolveFastifyRoutes(repoRoot) {
|
|
231
231
|
const routes = [];
|
|
232
232
|
const gaps = [];
|
|
233
233
|
|
|
@@ -321,7 +321,7 @@ export async function resolveFastifyRoutes(repoRoot) {
|
|
|
321
321
|
// ROUTE INDEX
|
|
322
322
|
// ============================================================================
|
|
323
323
|
|
|
324
|
-
|
|
324
|
+
class RouteIndex {
|
|
325
325
|
constructor() {
|
|
326
326
|
this.routes = [];
|
|
327
327
|
this.byMethod = new Map();
|
|
@@ -424,7 +424,7 @@ export class RouteIndex {
|
|
|
424
424
|
// VALIDATE CLAIM
|
|
425
425
|
// ============================================================================
|
|
426
426
|
|
|
427
|
-
|
|
427
|
+
async function validateRouteExists(claim, repoRoot, routeIndex) {
|
|
428
428
|
const index = routeIndex || new RouteIndex();
|
|
429
429
|
if (!routeIndex) await index.build(repoRoot);
|
|
430
430
|
|
|
@@ -467,7 +467,7 @@ export async function validateRouteExists(claim, repoRoot, routeIndex) {
|
|
|
467
467
|
};
|
|
468
468
|
}
|
|
469
469
|
|
|
470
|
-
|
|
470
|
+
module.exports = {
|
|
471
471
|
canonicalizePath,
|
|
472
472
|
canonicalizeMethod,
|
|
473
473
|
resolveNextRoutes,
|