@vibecheckai/cli 3.2.2 → 3.2.4
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/.generated +25 -25
- package/bin/dev/run-v2-torture.js +30 -30
- package/bin/runners/ENHANCEMENT_GUIDE.md +121 -121
- package/bin/runners/lib/__tests__/entitlements-v2.test.js +295 -295
- package/bin/runners/lib/agent-firewall/ai/false-positive-analyzer.js +474 -0
- package/bin/runners/lib/agent-firewall/claims/extractor.js +117 -28
- package/bin/runners/lib/agent-firewall/evidence/env-evidence.js +23 -14
- package/bin/runners/lib/agent-firewall/evidence/route-evidence.js +72 -1
- package/bin/runners/lib/agent-firewall/interceptor/base.js +2 -2
- package/bin/runners/lib/agent-firewall/policy/default-policy.json +6 -0
- package/bin/runners/lib/agent-firewall/policy/engine.js +34 -3
- package/bin/runners/lib/agent-firewall/policy/rules/fake-success.js +29 -4
- package/bin/runners/lib/agent-firewall/policy/rules/ghost-route.js +12 -0
- package/bin/runners/lib/agent-firewall/truthpack/loader.js +21 -0
- package/bin/runners/lib/agent-firewall/utils/ignore-checker.js +118 -0
- package/bin/runners/lib/analyzers.js +606 -325
- package/bin/runners/lib/auth-truth.js +193 -193
- package/bin/runners/lib/backup.js +62 -62
- package/bin/runners/lib/billing.js +107 -107
- package/bin/runners/lib/claims.js +118 -118
- package/bin/runners/lib/cli-ui.js +540 -540
- package/bin/runners/lib/contracts/auth-contract.js +202 -202
- package/bin/runners/lib/contracts/env-contract.js +181 -181
- package/bin/runners/lib/contracts/external-contract.js +206 -206
- package/bin/runners/lib/contracts/guard.js +168 -168
- package/bin/runners/lib/contracts/index.js +89 -89
- package/bin/runners/lib/contracts/plan-validator.js +311 -311
- package/bin/runners/lib/contracts/route-contract.js +199 -199
- package/bin/runners/lib/contracts.js +804 -804
- package/bin/runners/lib/detect.js +89 -89
- package/bin/runners/lib/doctor/autofix.js +254 -254
- package/bin/runners/lib/doctor/index.js +37 -37
- package/bin/runners/lib/doctor/modules/dependencies.js +325 -325
- package/bin/runners/lib/doctor/modules/index.js +46 -46
- package/bin/runners/lib/doctor/modules/network.js +250 -250
- package/bin/runners/lib/doctor/modules/project.js +312 -312
- package/bin/runners/lib/doctor/modules/runtime.js +224 -224
- package/bin/runners/lib/doctor/modules/security.js +348 -348
- package/bin/runners/lib/doctor/modules/system.js +213 -213
- package/bin/runners/lib/doctor/modules/vibecheck.js +394 -394
- package/bin/runners/lib/doctor/reporter.js +262 -262
- package/bin/runners/lib/doctor/service.js +262 -262
- package/bin/runners/lib/doctor/types.js +113 -113
- package/bin/runners/lib/doctor/ui.js +263 -263
- package/bin/runners/lib/doctor-v2.js +608 -608
- package/bin/runners/lib/drift.js +425 -425
- package/bin/runners/lib/enforcement.js +72 -72
- package/bin/runners/lib/engines/accessibility-engine.js +190 -0
- package/bin/runners/lib/engines/api-consistency-engine.js +162 -0
- package/bin/runners/lib/engines/ast-cache.js +99 -0
- package/bin/runners/lib/engines/code-quality-engine.js +255 -0
- package/bin/runners/lib/engines/console-logs-engine.js +115 -0
- package/bin/runners/lib/engines/cross-file-analysis-engine.js +268 -0
- package/bin/runners/lib/engines/dead-code-engine.js +198 -0
- package/bin/runners/lib/engines/deprecated-api-engine.js +226 -0
- package/bin/runners/lib/engines/empty-catch-engine.js +150 -0
- package/bin/runners/lib/engines/file-filter.js +131 -0
- package/bin/runners/lib/engines/hardcoded-secrets-engine.js +251 -0
- package/bin/runners/lib/engines/mock-data-engine.js +272 -0
- package/bin/runners/lib/engines/parallel-processor.js +71 -0
- package/bin/runners/lib/engines/performance-issues-engine.js +265 -0
- package/bin/runners/lib/engines/security-vulnerabilities-engine.js +243 -0
- package/bin/runners/lib/engines/todo-fixme-engine.js +115 -0
- package/bin/runners/lib/engines/type-aware-engine.js +152 -0
- package/bin/runners/lib/engines/unsafe-regex-engine.js +225 -0
- package/bin/runners/lib/engines/vibecheck-engines/README.md +53 -0
- package/bin/runners/lib/engines/vibecheck-engines/index.js +15 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/ast-cache.js +164 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/code-quality-engine.js +291 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/console-logs-engine.js +83 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/dead-code-engine.js +198 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/deprecated-api-engine.js +275 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/empty-catch-engine.js +167 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/file-filter.js +217 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/hardcoded-secrets-engine.js +139 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/mock-data-engine.js +140 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/parallel-processor.js +164 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/performance-issues-engine.js +234 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/type-aware-engine.js +217 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/unsafe-regex-engine.js +78 -0
- package/bin/runners/lib/engines/vibecheck-engines/package.json +13 -0
- package/bin/runners/lib/enterprise-detect.js +603 -603
- package/bin/runners/lib/enterprise-init.js +942 -942
- package/bin/runners/lib/env-resolver.js +417 -417
- package/bin/runners/lib/env-template.js +66 -66
- package/bin/runners/lib/env.js +189 -189
- package/bin/runners/lib/extractors/client-calls.js +990 -990
- package/bin/runners/lib/extractors/fastify-route-dump.js +573 -573
- package/bin/runners/lib/extractors/fastify-routes.js +426 -426
- package/bin/runners/lib/extractors/index.js +363 -363
- package/bin/runners/lib/extractors/next-routes.js +524 -524
- package/bin/runners/lib/extractors/proof-graph.js +431 -431
- package/bin/runners/lib/extractors/route-matcher.js +451 -451
- package/bin/runners/lib/extractors/truthpack-v2.js +377 -377
- package/bin/runners/lib/extractors/ui-bindings.js +547 -547
- package/bin/runners/lib/findings-schema.js +281 -281
- package/bin/runners/lib/firewall-prompt.js +50 -50
- package/bin/runners/lib/global-flags.js +213 -213
- package/bin/runners/lib/graph/graph-builder.js +265 -265
- package/bin/runners/lib/graph/html-renderer.js +413 -413
- package/bin/runners/lib/graph/index.js +32 -32
- package/bin/runners/lib/graph/runtime-collector.js +215 -215
- package/bin/runners/lib/graph/static-extractor.js +518 -518
- package/bin/runners/lib/html-report.js +650 -650
- package/bin/runners/lib/interactive-menu.js +1496 -1496
- package/bin/runners/lib/llm.js +75 -75
- package/bin/runners/lib/meter.js +61 -61
- package/bin/runners/lib/missions/evidence.js +126 -126
- package/bin/runners/lib/patch.js +40 -40
- package/bin/runners/lib/permissions/auth-model.js +213 -213
- package/bin/runners/lib/permissions/idor-prover.js +205 -205
- package/bin/runners/lib/permissions/index.js +45 -45
- package/bin/runners/lib/permissions/matrix-builder.js +198 -198
- package/bin/runners/lib/pkgjson.js +28 -28
- package/bin/runners/lib/policy.js +295 -295
- package/bin/runners/lib/preflight.js +142 -142
- package/bin/runners/lib/reality/correlation-detectors.js +359 -359
- package/bin/runners/lib/reality/index.js +318 -318
- package/bin/runners/lib/reality/request-hashing.js +416 -416
- package/bin/runners/lib/reality/request-mapper.js +453 -453
- package/bin/runners/lib/reality/safety-rails.js +463 -463
- package/bin/runners/lib/reality/semantic-snapshot.js +408 -408
- package/bin/runners/lib/reality/toast-detector.js +393 -393
- package/bin/runners/lib/reality-findings.js +84 -84
- package/bin/runners/lib/receipts.js +179 -179
- package/bin/runners/lib/redact.js +29 -29
- package/bin/runners/lib/replay/capsule-manager.js +154 -154
- package/bin/runners/lib/replay/index.js +263 -263
- package/bin/runners/lib/replay/player.js +348 -348
- package/bin/runners/lib/replay/recorder.js +331 -331
- package/bin/runners/lib/report-output.js +187 -187
- package/bin/runners/lib/report.js +135 -135
- package/bin/runners/lib/route-detection.js +1140 -1140
- package/bin/runners/lib/sandbox/index.js +59 -59
- package/bin/runners/lib/sandbox/proof-chain.js +399 -399
- package/bin/runners/lib/sandbox/sandbox-runner.js +205 -205
- package/bin/runners/lib/sandbox/worktree.js +174 -174
- package/bin/runners/lib/scan-output.js +525 -190
- package/bin/runners/lib/schema-validator.js +350 -350
- package/bin/runners/lib/schemas/contracts.schema.json +160 -160
- package/bin/runners/lib/schemas/finding.schema.json +100 -100
- package/bin/runners/lib/schemas/mission-pack.schema.json +206 -206
- package/bin/runners/lib/schemas/proof-graph.schema.json +176 -176
- package/bin/runners/lib/schemas/reality-report.schema.json +162 -162
- package/bin/runners/lib/schemas/share-pack.schema.json +180 -180
- package/bin/runners/lib/schemas/ship-report.schema.json +117 -117
- package/bin/runners/lib/schemas/truthpack-v2.schema.json +303 -303
- package/bin/runners/lib/schemas/validator.js +438 -438
- package/bin/runners/lib/score-history.js +282 -282
- package/bin/runners/lib/share-pack.js +239 -239
- package/bin/runners/lib/snippets.js +67 -67
- package/bin/runners/lib/status-output.js +253 -253
- package/bin/runners/lib/terminal-ui.js +351 -271
- package/bin/runners/lib/upsell.js +510 -510
- package/bin/runners/lib/usage.js +153 -153
- package/bin/runners/lib/validate-patch.js +156 -156
- package/bin/runners/lib/verdict-engine.js +628 -628
- package/bin/runners/reality/engine.js +917 -917
- package/bin/runners/reality/flows.js +122 -122
- package/bin/runners/reality/report.js +378 -378
- package/bin/runners/reality/session.js +193 -193
- package/bin/runners/runGuard.js +168 -168
- package/bin/runners/runProof.zip +0 -0
- package/bin/runners/runProve.js +8 -0
- package/bin/runners/runReality.js +14 -0
- package/bin/runners/runScan.js +17 -1
- package/bin/runners/runTruth.js +15 -3
- package/mcp-server/tier-auth.js +4 -4
- package/mcp-server/tools/index.js +72 -72
- package/package.json +1 -1
package/bin/runners/lib/usage.js
CHANGED
|
@@ -1,153 +1,153 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Usage Logger - Track metered units per command
|
|
3
|
-
*
|
|
4
|
-
* Records usage for monetization and billing.
|
|
5
|
-
* All paid commands must log their usage.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
"use strict";
|
|
9
|
-
|
|
10
|
-
const fs = require("fs");
|
|
11
|
-
const path = require("path");
|
|
12
|
-
const os = require("os");
|
|
13
|
-
|
|
14
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
15
|
-
// USAGE LOGGING
|
|
16
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
17
|
-
|
|
18
|
-
function getUsageLogPath() {
|
|
19
|
-
const home = os.homedir();
|
|
20
|
-
const vibecheckDir = path.join(home, ".vibecheck");
|
|
21
|
-
const usageDir = path.join(vibecheckDir, "usage");
|
|
22
|
-
|
|
23
|
-
// Ensure directories exist
|
|
24
|
-
if (!fs.existsSync(vibecheckDir)) fs.mkdirSync(vibecheckDir, { recursive: true });
|
|
25
|
-
if (!fs.existsSync(usageDir)) fs.mkdirSync(usageDir, { recursive: true });
|
|
26
|
-
|
|
27
|
-
return path.join(usageDir, "usage.jsonl");
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Log usage for a command run
|
|
32
|
-
* @param {Object} usage - Usage data
|
|
33
|
-
* @param {string} usage.runId - Unique run identifier
|
|
34
|
-
* @param {string} usage.command - Command name
|
|
35
|
-
* @param {string} usage.tier - User tier (free/pro/complete)
|
|
36
|
-
* @param {Object} usage.units - Units consumed
|
|
37
|
-
* @param {number} usage.units.pages - Pages crawled (reality)
|
|
38
|
-
* @param {number} usage.units.clicks - UI interactions (reality)
|
|
39
|
-
* @param {number} usage.units.endpoints - API endpoints tested (permissions)
|
|
40
|
-
* @param {number} usage.units.roles - Roles tested (permissions)
|
|
41
|
-
* @param {number} usage.units.files - Files modified (fix)
|
|
42
|
-
* @param {number} usage.units.hunks - Hunks applied (fix)
|
|
43
|
-
* @param {number} usage.units.exports - Reports generated
|
|
44
|
-
* @param {string} usage.timestamp - ISO timestamp
|
|
45
|
-
* @param {string} usage.projectPath - Project directory
|
|
46
|
-
*/
|
|
47
|
-
function logUsage(usage) {
|
|
48
|
-
const logPath = getUsageLogPath();
|
|
49
|
-
const entry = {
|
|
50
|
-
...usage,
|
|
51
|
-
timestamp: usage.timestamp || new Date().toISOString(),
|
|
52
|
-
hostname: os.hostname(),
|
|
53
|
-
platform: process.platform,
|
|
54
|
-
nodeVersion: process.version,
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
// Append to log file (JSONL format - one JSON per line)
|
|
58
|
-
const line = JSON.stringify(entry) + "\n";
|
|
59
|
-
fs.appendFileSync(logPath, line);
|
|
60
|
-
|
|
61
|
-
// Also store in run-specific directory for receipts
|
|
62
|
-
if (usage.runId) {
|
|
63
|
-
const runDir = path.join(process.cwd(), ".vibecheck", "runs", usage.runId);
|
|
64
|
-
if (fs.existsSync(runDir)) {
|
|
65
|
-
fs.writeFileSync(path.join(runDir, "usage.json"), JSON.stringify(entry, null, 2));
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
return entry;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Get usage statistics for a time period
|
|
74
|
-
* @param {Object} options - Query options
|
|
75
|
-
* @param {string} options.since - ISO date to start from
|
|
76
|
-
* @param {string} options.until - ISO date to end at
|
|
77
|
-
* @param {string} options.command - Filter by command
|
|
78
|
-
* @param {string} options.tier - Filter by tier
|
|
79
|
-
* @returns {Array} Array of usage entries
|
|
80
|
-
*/
|
|
81
|
-
function getUsage(options = {}) {
|
|
82
|
-
const logPath = getUsageLogPath();
|
|
83
|
-
|
|
84
|
-
if (!fs.existsSync(logPath)) return [];
|
|
85
|
-
|
|
86
|
-
const lines = fs.readFileSync(logPath, "utf8").split("\n").filter(Boolean);
|
|
87
|
-
const entries = lines.map(line => {
|
|
88
|
-
try { return JSON.parse(line); } catch { return null; }
|
|
89
|
-
}).filter(Boolean);
|
|
90
|
-
|
|
91
|
-
// Apply filters
|
|
92
|
-
let filtered = entries;
|
|
93
|
-
|
|
94
|
-
if (options.since) {
|
|
95
|
-
filtered = filtered.filter(e => e.timestamp >= options.since);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
if (options.until) {
|
|
99
|
-
filtered = filtered.filter(e => e.timestamp <= options.until);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
if (options.command) {
|
|
103
|
-
filtered = filtered.filter(e => e.command === options.command);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
if (options.tier) {
|
|
107
|
-
filtered = filtered.filter(e => e.tier === options.tier);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
return filtered;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* Get aggregate usage by unit type
|
|
115
|
-
* @param {Object} options - Query options (same as getUsage)
|
|
116
|
-
* @returns {Object} Aggregated totals
|
|
117
|
-
*/
|
|
118
|
-
function getUsageTotals(options = {}) {
|
|
119
|
-
const entries = getUsage(options);
|
|
120
|
-
const totals = {
|
|
121
|
-
runs: entries.length,
|
|
122
|
-
pages: 0,
|
|
123
|
-
clicks: 0,
|
|
124
|
-
endpoints: 0,
|
|
125
|
-
roles: 0,
|
|
126
|
-
files: 0,
|
|
127
|
-
hunks: 0,
|
|
128
|
-
exports: 0,
|
|
129
|
-
};
|
|
130
|
-
|
|
131
|
-
for (const entry of entries) {
|
|
132
|
-
if (entry.units) {
|
|
133
|
-
totals.pages += entry.units.pages || 0;
|
|
134
|
-
totals.clicks += entry.units.clicks || 0;
|
|
135
|
-
totals.endpoints += entry.units.endpoints || 0;
|
|
136
|
-
totals.roles += entry.units.roles || 0;
|
|
137
|
-
totals.files += entry.units.files || 0;
|
|
138
|
-
totals.hunks += entry.units.hunks || 0;
|
|
139
|
-
totals.exports += entry.units.exports || 0;
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
return totals;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
147
|
-
// EXPORTS
|
|
148
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
149
|
-
module.exports = {
|
|
150
|
-
logUsage,
|
|
151
|
-
getUsage,
|
|
152
|
-
getUsageTotals,
|
|
153
|
-
};
|
|
1
|
+
/**
|
|
2
|
+
* Usage Logger - Track metered units per command
|
|
3
|
+
*
|
|
4
|
+
* Records usage for monetization and billing.
|
|
5
|
+
* All paid commands must log their usage.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
"use strict";
|
|
9
|
+
|
|
10
|
+
const fs = require("fs");
|
|
11
|
+
const path = require("path");
|
|
12
|
+
const os = require("os");
|
|
13
|
+
|
|
14
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
15
|
+
// USAGE LOGGING
|
|
16
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
17
|
+
|
|
18
|
+
function getUsageLogPath() {
|
|
19
|
+
const home = os.homedir();
|
|
20
|
+
const vibecheckDir = path.join(home, ".vibecheck");
|
|
21
|
+
const usageDir = path.join(vibecheckDir, "usage");
|
|
22
|
+
|
|
23
|
+
// Ensure directories exist
|
|
24
|
+
if (!fs.existsSync(vibecheckDir)) fs.mkdirSync(vibecheckDir, { recursive: true });
|
|
25
|
+
if (!fs.existsSync(usageDir)) fs.mkdirSync(usageDir, { recursive: true });
|
|
26
|
+
|
|
27
|
+
return path.join(usageDir, "usage.jsonl");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Log usage for a command run
|
|
32
|
+
* @param {Object} usage - Usage data
|
|
33
|
+
* @param {string} usage.runId - Unique run identifier
|
|
34
|
+
* @param {string} usage.command - Command name
|
|
35
|
+
* @param {string} usage.tier - User tier (free/pro/complete)
|
|
36
|
+
* @param {Object} usage.units - Units consumed
|
|
37
|
+
* @param {number} usage.units.pages - Pages crawled (reality)
|
|
38
|
+
* @param {number} usage.units.clicks - UI interactions (reality)
|
|
39
|
+
* @param {number} usage.units.endpoints - API endpoints tested (permissions)
|
|
40
|
+
* @param {number} usage.units.roles - Roles tested (permissions)
|
|
41
|
+
* @param {number} usage.units.files - Files modified (fix)
|
|
42
|
+
* @param {number} usage.units.hunks - Hunks applied (fix)
|
|
43
|
+
* @param {number} usage.units.exports - Reports generated
|
|
44
|
+
* @param {string} usage.timestamp - ISO timestamp
|
|
45
|
+
* @param {string} usage.projectPath - Project directory
|
|
46
|
+
*/
|
|
47
|
+
function logUsage(usage) {
|
|
48
|
+
const logPath = getUsageLogPath();
|
|
49
|
+
const entry = {
|
|
50
|
+
...usage,
|
|
51
|
+
timestamp: usage.timestamp || new Date().toISOString(),
|
|
52
|
+
hostname: os.hostname(),
|
|
53
|
+
platform: process.platform,
|
|
54
|
+
nodeVersion: process.version,
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// Append to log file (JSONL format - one JSON per line)
|
|
58
|
+
const line = JSON.stringify(entry) + "\n";
|
|
59
|
+
fs.appendFileSync(logPath, line);
|
|
60
|
+
|
|
61
|
+
// Also store in run-specific directory for receipts
|
|
62
|
+
if (usage.runId) {
|
|
63
|
+
const runDir = path.join(process.cwd(), ".vibecheck", "runs", usage.runId);
|
|
64
|
+
if (fs.existsSync(runDir)) {
|
|
65
|
+
fs.writeFileSync(path.join(runDir, "usage.json"), JSON.stringify(entry, null, 2));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return entry;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Get usage statistics for a time period
|
|
74
|
+
* @param {Object} options - Query options
|
|
75
|
+
* @param {string} options.since - ISO date to start from
|
|
76
|
+
* @param {string} options.until - ISO date to end at
|
|
77
|
+
* @param {string} options.command - Filter by command
|
|
78
|
+
* @param {string} options.tier - Filter by tier
|
|
79
|
+
* @returns {Array} Array of usage entries
|
|
80
|
+
*/
|
|
81
|
+
function getUsage(options = {}) {
|
|
82
|
+
const logPath = getUsageLogPath();
|
|
83
|
+
|
|
84
|
+
if (!fs.existsSync(logPath)) return [];
|
|
85
|
+
|
|
86
|
+
const lines = fs.readFileSync(logPath, "utf8").split("\n").filter(Boolean);
|
|
87
|
+
const entries = lines.map(line => {
|
|
88
|
+
try { return JSON.parse(line); } catch { return null; }
|
|
89
|
+
}).filter(Boolean);
|
|
90
|
+
|
|
91
|
+
// Apply filters
|
|
92
|
+
let filtered = entries;
|
|
93
|
+
|
|
94
|
+
if (options.since) {
|
|
95
|
+
filtered = filtered.filter(e => e.timestamp >= options.since);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (options.until) {
|
|
99
|
+
filtered = filtered.filter(e => e.timestamp <= options.until);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (options.command) {
|
|
103
|
+
filtered = filtered.filter(e => e.command === options.command);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (options.tier) {
|
|
107
|
+
filtered = filtered.filter(e => e.tier === options.tier);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return filtered;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Get aggregate usage by unit type
|
|
115
|
+
* @param {Object} options - Query options (same as getUsage)
|
|
116
|
+
* @returns {Object} Aggregated totals
|
|
117
|
+
*/
|
|
118
|
+
function getUsageTotals(options = {}) {
|
|
119
|
+
const entries = getUsage(options);
|
|
120
|
+
const totals = {
|
|
121
|
+
runs: entries.length,
|
|
122
|
+
pages: 0,
|
|
123
|
+
clicks: 0,
|
|
124
|
+
endpoints: 0,
|
|
125
|
+
roles: 0,
|
|
126
|
+
files: 0,
|
|
127
|
+
hunks: 0,
|
|
128
|
+
exports: 0,
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
for (const entry of entries) {
|
|
132
|
+
if (entry.units) {
|
|
133
|
+
totals.pages += entry.units.pages || 0;
|
|
134
|
+
totals.clicks += entry.units.clicks || 0;
|
|
135
|
+
totals.endpoints += entry.units.endpoints || 0;
|
|
136
|
+
totals.roles += entry.units.roles || 0;
|
|
137
|
+
totals.files += entry.units.files || 0;
|
|
138
|
+
totals.hunks += entry.units.hunks || 0;
|
|
139
|
+
totals.exports += entry.units.exports || 0;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return totals;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
147
|
+
// EXPORTS
|
|
148
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
149
|
+
module.exports = {
|
|
150
|
+
logUsage,
|
|
151
|
+
getUsage,
|
|
152
|
+
getUsageTotals,
|
|
153
|
+
};
|
|
@@ -1,156 +1,156 @@
|
|
|
1
|
-
// bin/runners/lib/validate-patch.js
|
|
2
|
-
const fs = require("fs");
|
|
3
|
-
const path = require("path");
|
|
4
|
-
|
|
5
|
-
function isRelSafe(p) {
|
|
6
|
-
if (!p || typeof p !== "string") return false;
|
|
7
|
-
if (p.includes("\0")) return false;
|
|
8
|
-
if (path.isAbsolute(p)) return false;
|
|
9
|
-
const norm = path.normalize(p).replace(/\\/g, "/");
|
|
10
|
-
if (norm.startsWith("../") || norm.startsWith("..\\")) return false;
|
|
11
|
-
return true;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
function parseDiffTouchedFiles(diff) {
|
|
15
|
-
const files = new Set();
|
|
16
|
-
const lines = String(diff || "").split(/\r?\n/);
|
|
17
|
-
for (const line of lines) {
|
|
18
|
-
if (line.startsWith("+++ ")) {
|
|
19
|
-
const p = line.slice(4).trim();
|
|
20
|
-
if (p === "/dev/null") continue;
|
|
21
|
-
const cleaned = p.startsWith("b/") ? p.slice(2) : p;
|
|
22
|
-
files.add(cleaned);
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
return Array.from(files);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function countChangedLines(diff) {
|
|
29
|
-
const lines = String(diff || "").split(/\r?\n/);
|
|
30
|
-
let adds = 0, dels = 0;
|
|
31
|
-
for (const l of lines) {
|
|
32
|
-
if (l.startsWith("+++ ") || l.startsWith("--- ") || l.startsWith("@@")) continue;
|
|
33
|
-
if (l.startsWith("+")) adds++;
|
|
34
|
-
if (l.startsWith("-")) dels++;
|
|
35
|
-
}
|
|
36
|
-
return { adds, dels, total: adds + dels };
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function extractAddedApiStrings(diff) {
|
|
40
|
-
const out = [];
|
|
41
|
-
const lines = String(diff || "").split(/\r?\n/);
|
|
42
|
-
for (const l of lines) {
|
|
43
|
-
if (!l.startsWith("+") || l.startsWith("+++")) continue;
|
|
44
|
-
const m = l.match(/["'`](\/api\/[^"'` ]+)["'`]/g);
|
|
45
|
-
if (m) out.push(...m.map(x => x.replace(/^[+"'`]+|[+"'`]+$/g, "")));
|
|
46
|
-
}
|
|
47
|
-
return Array.from(new Set(out));
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function extractAddedEnvNames(diff) {
|
|
51
|
-
const out = new Set();
|
|
52
|
-
const lines = String(diff || "").split(/\r?\n/);
|
|
53
|
-
for (const l of lines) {
|
|
54
|
-
if (!l.startsWith("+") || l.startsWith("+++")) continue;
|
|
55
|
-
|
|
56
|
-
const re1 = /process\.env\.([A-Z0-9_]+)/g;
|
|
57
|
-
let m;
|
|
58
|
-
while ((m = re1.exec(l)) !== null) out.add(m[1]);
|
|
59
|
-
|
|
60
|
-
const re2 = /import\.meta\.env\.([A-Z0-9_]+)/g;
|
|
61
|
-
while ((m = re2.exec(l)) !== null) out.add(m[1]);
|
|
62
|
-
}
|
|
63
|
-
return Array.from(out);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function validatePatchResponse({
|
|
67
|
-
repoRoot,
|
|
68
|
-
patchJson,
|
|
69
|
-
mission,
|
|
70
|
-
truthpack,
|
|
71
|
-
allowedFiles,
|
|
72
|
-
limits
|
|
73
|
-
}) {
|
|
74
|
-
const errors = [];
|
|
75
|
-
const warnings = [];
|
|
76
|
-
|
|
77
|
-
const lim = {
|
|
78
|
-
maxEdits: limits?.maxEdits ?? 6,
|
|
79
|
-
maxFiles: limits?.maxFiles ?? 6,
|
|
80
|
-
maxChangedLines: limits?.maxChangedLines ?? 400
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
if (!patchJson || typeof patchJson !== "object") errors.push("Patch JSON missing or not an object.");
|
|
84
|
-
if (patchJson.status !== "ok") errors.push(`Patch status must be "ok" (got ${patchJson?.status}).`);
|
|
85
|
-
if (!Array.isArray(patchJson.edits)) errors.push("Patch edits must be an array.");
|
|
86
|
-
if ((patchJson.edits || []).length > lim.maxEdits) errors.push(`Too many edits (>${lim.maxEdits}).`);
|
|
87
|
-
|
|
88
|
-
if (errors.length) return { ok: false, errors, warnings };
|
|
89
|
-
|
|
90
|
-
const allowed = new Set((allowedFiles || []).filter(Boolean));
|
|
91
|
-
const touchedAll = new Set();
|
|
92
|
-
|
|
93
|
-
for (const ed of patchJson.edits) {
|
|
94
|
-
if (!ed || typeof ed !== "object") { errors.push("Edit entry not an object."); continue; }
|
|
95
|
-
if (!isRelSafe(ed.path)) errors.push(`Unsafe edit path: ${ed.path}`);
|
|
96
|
-
if (typeof ed.diff !== "string" || !ed.diff.includes("\n+++ ")) errors.push(`Diff missing or not unified for: ${ed.path}`);
|
|
97
|
-
|
|
98
|
-
const touched = parseDiffTouchedFiles(ed.diff);
|
|
99
|
-
for (const f of touched) touchedAll.add(f);
|
|
100
|
-
|
|
101
|
-
if (touched.length === 0) errors.push(`No touched file detected in diff for ${ed.path}`);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
const touchedList = Array.from(touchedAll);
|
|
105
|
-
if (touchedList.length > lim.maxFiles) errors.push(`Too many touched files (>${lim.maxFiles}).`);
|
|
106
|
-
|
|
107
|
-
for (const f of touchedList) {
|
|
108
|
-
if (!isRelSafe(f)) errors.push(`Unsafe touched file: ${f}`);
|
|
109
|
-
if (allowed.size && !allowed.has(f)) {
|
|
110
|
-
errors.push(`Touched file not allowed by mission evidence: ${f}`);
|
|
111
|
-
}
|
|
112
|
-
if (f.includes("node_modules/") || f.includes(".next/") || f.includes("dist/") || f.includes("build/")) {
|
|
113
|
-
errors.push(`Touched forbidden build/vendor file: ${f}`);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
let totalChanged = 0;
|
|
118
|
-
for (const ed of patchJson.edits) totalChanged += countChangedLines(ed.diff).total;
|
|
119
|
-
if (totalChanged > lim.maxChangedLines) errors.push(`Patch too large (>${lim.maxChangedLines} changed lines).`);
|
|
120
|
-
|
|
121
|
-
const type = mission?.type || "GENERIC_FIX";
|
|
122
|
-
const combinedDiff = patchJson.edits.map(e => e.diff).join("\n");
|
|
123
|
-
|
|
124
|
-
const addedApis = extractAddedApiStrings(combinedDiff);
|
|
125
|
-
if (addedApis.length && type !== "FIX_MISSING_ROUTE") {
|
|
126
|
-
errors.push(`No-drift: patch adds /api strings (${addedApis.join(", ")}) but mission is ${type}.`);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
const addedEnv = extractAddedEnvNames(combinedDiff);
|
|
130
|
-
if (addedEnv.length && type !== "FIX_ENV_CONTRACT") {
|
|
131
|
-
warnings.push(`Patch introduces new env var usage (${addedEnv.join(", ")}). If that wasn't intended, reject this patch.`);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
const touchesEntitlements = /enforceFeature|enforceLimit|getEntitlements|subscription|tier|plan|credits/i.test(combinedDiff);
|
|
135
|
-
if (touchesEntitlements && !["ENFORCE_PAID_SURFACE","FIX_STRIPE_WEBHOOKS","REMOVE_OWNER_MODE"].includes(type)) {
|
|
136
|
-
errors.push(`No-drift: patch touches entitlements/billing logic but mission is ${type}.`);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
const touchesMiddleware = /middleware\.(ts|js)|matcher\s*:|NextResponse\.(redirect|rewrite)/i.test(combinedDiff);
|
|
140
|
-
if (touchesMiddleware && type !== "ADD_SERVER_AUTH") {
|
|
141
|
-
warnings.push("Patch touches Next middleware/matcher outside ADD_SERVER_AUTH mission. Verify intent.");
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
for (const f of touchedList) {
|
|
145
|
-
const abs = path.join(repoRoot, f);
|
|
146
|
-
if (!fs.existsSync(abs)) {
|
|
147
|
-
if (type === "FIX_MISSING_ROUTE" && (f.includes("app/api/") || f.includes("pages/api/"))) continue;
|
|
148
|
-
warnings.push(`Touched file does not exist (would be created): ${f}`);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
if (errors.length) return { ok: false, errors, warnings };
|
|
153
|
-
return { ok: true, errors: [], warnings };
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
module.exports = { validatePatchResponse, parseDiffTouchedFiles };
|
|
1
|
+
// bin/runners/lib/validate-patch.js
|
|
2
|
+
const fs = require("fs");
|
|
3
|
+
const path = require("path");
|
|
4
|
+
|
|
5
|
+
function isRelSafe(p) {
|
|
6
|
+
if (!p || typeof p !== "string") return false;
|
|
7
|
+
if (p.includes("\0")) return false;
|
|
8
|
+
if (path.isAbsolute(p)) return false;
|
|
9
|
+
const norm = path.normalize(p).replace(/\\/g, "/");
|
|
10
|
+
if (norm.startsWith("../") || norm.startsWith("..\\")) return false;
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function parseDiffTouchedFiles(diff) {
|
|
15
|
+
const files = new Set();
|
|
16
|
+
const lines = String(diff || "").split(/\r?\n/);
|
|
17
|
+
for (const line of lines) {
|
|
18
|
+
if (line.startsWith("+++ ")) {
|
|
19
|
+
const p = line.slice(4).trim();
|
|
20
|
+
if (p === "/dev/null") continue;
|
|
21
|
+
const cleaned = p.startsWith("b/") ? p.slice(2) : p;
|
|
22
|
+
files.add(cleaned);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return Array.from(files);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function countChangedLines(diff) {
|
|
29
|
+
const lines = String(diff || "").split(/\r?\n/);
|
|
30
|
+
let adds = 0, dels = 0;
|
|
31
|
+
for (const l of lines) {
|
|
32
|
+
if (l.startsWith("+++ ") || l.startsWith("--- ") || l.startsWith("@@")) continue;
|
|
33
|
+
if (l.startsWith("+")) adds++;
|
|
34
|
+
if (l.startsWith("-")) dels++;
|
|
35
|
+
}
|
|
36
|
+
return { adds, dels, total: adds + dels };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function extractAddedApiStrings(diff) {
|
|
40
|
+
const out = [];
|
|
41
|
+
const lines = String(diff || "").split(/\r?\n/);
|
|
42
|
+
for (const l of lines) {
|
|
43
|
+
if (!l.startsWith("+") || l.startsWith("+++")) continue;
|
|
44
|
+
const m = l.match(/["'`](\/api\/[^"'` ]+)["'`]/g);
|
|
45
|
+
if (m) out.push(...m.map(x => x.replace(/^[+"'`]+|[+"'`]+$/g, "")));
|
|
46
|
+
}
|
|
47
|
+
return Array.from(new Set(out));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function extractAddedEnvNames(diff) {
|
|
51
|
+
const out = new Set();
|
|
52
|
+
const lines = String(diff || "").split(/\r?\n/);
|
|
53
|
+
for (const l of lines) {
|
|
54
|
+
if (!l.startsWith("+") || l.startsWith("+++")) continue;
|
|
55
|
+
|
|
56
|
+
const re1 = /process\.env\.([A-Z0-9_]+)/g;
|
|
57
|
+
let m;
|
|
58
|
+
while ((m = re1.exec(l)) !== null) out.add(m[1]);
|
|
59
|
+
|
|
60
|
+
const re2 = /import\.meta\.env\.([A-Z0-9_]+)/g;
|
|
61
|
+
while ((m = re2.exec(l)) !== null) out.add(m[1]);
|
|
62
|
+
}
|
|
63
|
+
return Array.from(out);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function validatePatchResponse({
|
|
67
|
+
repoRoot,
|
|
68
|
+
patchJson,
|
|
69
|
+
mission,
|
|
70
|
+
truthpack,
|
|
71
|
+
allowedFiles,
|
|
72
|
+
limits
|
|
73
|
+
}) {
|
|
74
|
+
const errors = [];
|
|
75
|
+
const warnings = [];
|
|
76
|
+
|
|
77
|
+
const lim = {
|
|
78
|
+
maxEdits: limits?.maxEdits ?? 6,
|
|
79
|
+
maxFiles: limits?.maxFiles ?? 6,
|
|
80
|
+
maxChangedLines: limits?.maxChangedLines ?? 400
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
if (!patchJson || typeof patchJson !== "object") errors.push("Patch JSON missing or not an object.");
|
|
84
|
+
if (patchJson.status !== "ok") errors.push(`Patch status must be "ok" (got ${patchJson?.status}).`);
|
|
85
|
+
if (!Array.isArray(patchJson.edits)) errors.push("Patch edits must be an array.");
|
|
86
|
+
if ((patchJson.edits || []).length > lim.maxEdits) errors.push(`Too many edits (>${lim.maxEdits}).`);
|
|
87
|
+
|
|
88
|
+
if (errors.length) return { ok: false, errors, warnings };
|
|
89
|
+
|
|
90
|
+
const allowed = new Set((allowedFiles || []).filter(Boolean));
|
|
91
|
+
const touchedAll = new Set();
|
|
92
|
+
|
|
93
|
+
for (const ed of patchJson.edits) {
|
|
94
|
+
if (!ed || typeof ed !== "object") { errors.push("Edit entry not an object."); continue; }
|
|
95
|
+
if (!isRelSafe(ed.path)) errors.push(`Unsafe edit path: ${ed.path}`);
|
|
96
|
+
if (typeof ed.diff !== "string" || !ed.diff.includes("\n+++ ")) errors.push(`Diff missing or not unified for: ${ed.path}`);
|
|
97
|
+
|
|
98
|
+
const touched = parseDiffTouchedFiles(ed.diff);
|
|
99
|
+
for (const f of touched) touchedAll.add(f);
|
|
100
|
+
|
|
101
|
+
if (touched.length === 0) errors.push(`No touched file detected in diff for ${ed.path}`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const touchedList = Array.from(touchedAll);
|
|
105
|
+
if (touchedList.length > lim.maxFiles) errors.push(`Too many touched files (>${lim.maxFiles}).`);
|
|
106
|
+
|
|
107
|
+
for (const f of touchedList) {
|
|
108
|
+
if (!isRelSafe(f)) errors.push(`Unsafe touched file: ${f}`);
|
|
109
|
+
if (allowed.size && !allowed.has(f)) {
|
|
110
|
+
errors.push(`Touched file not allowed by mission evidence: ${f}`);
|
|
111
|
+
}
|
|
112
|
+
if (f.includes("node_modules/") || f.includes(".next/") || f.includes("dist/") || f.includes("build/")) {
|
|
113
|
+
errors.push(`Touched forbidden build/vendor file: ${f}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
let totalChanged = 0;
|
|
118
|
+
for (const ed of patchJson.edits) totalChanged += countChangedLines(ed.diff).total;
|
|
119
|
+
if (totalChanged > lim.maxChangedLines) errors.push(`Patch too large (>${lim.maxChangedLines} changed lines).`);
|
|
120
|
+
|
|
121
|
+
const type = mission?.type || "GENERIC_FIX";
|
|
122
|
+
const combinedDiff = patchJson.edits.map(e => e.diff).join("\n");
|
|
123
|
+
|
|
124
|
+
const addedApis = extractAddedApiStrings(combinedDiff);
|
|
125
|
+
if (addedApis.length && type !== "FIX_MISSING_ROUTE") {
|
|
126
|
+
errors.push(`No-drift: patch adds /api strings (${addedApis.join(", ")}) but mission is ${type}.`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const addedEnv = extractAddedEnvNames(combinedDiff);
|
|
130
|
+
if (addedEnv.length && type !== "FIX_ENV_CONTRACT") {
|
|
131
|
+
warnings.push(`Patch introduces new env var usage (${addedEnv.join(", ")}). If that wasn't intended, reject this patch.`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const touchesEntitlements = /enforceFeature|enforceLimit|getEntitlements|subscription|tier|plan|credits/i.test(combinedDiff);
|
|
135
|
+
if (touchesEntitlements && !["ENFORCE_PAID_SURFACE","FIX_STRIPE_WEBHOOKS","REMOVE_OWNER_MODE"].includes(type)) {
|
|
136
|
+
errors.push(`No-drift: patch touches entitlements/billing logic but mission is ${type}.`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const touchesMiddleware = /middleware\.(ts|js)|matcher\s*:|NextResponse\.(redirect|rewrite)/i.test(combinedDiff);
|
|
140
|
+
if (touchesMiddleware && type !== "ADD_SERVER_AUTH") {
|
|
141
|
+
warnings.push("Patch touches Next middleware/matcher outside ADD_SERVER_AUTH mission. Verify intent.");
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
for (const f of touchedList) {
|
|
145
|
+
const abs = path.join(repoRoot, f);
|
|
146
|
+
if (!fs.existsSync(abs)) {
|
|
147
|
+
if (type === "FIX_MISSING_ROUTE" && (f.includes("app/api/") || f.includes("pages/api/"))) continue;
|
|
148
|
+
warnings.push(`Touched file does not exist (would be created): ${f}`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (errors.length) return { ok: false, errors, warnings };
|
|
153
|
+
return { ok: true, errors: [], warnings };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
module.exports = { validatePatchResponse, parseDiffTouchedFiles };
|