@vibecheckai/cli 3.2.5 → 3.3.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 (197) hide show
  1. package/bin/.generated +25 -25
  2. package/bin/dev/run-v2-torture.js +30 -30
  3. package/bin/registry.js +192 -5
  4. package/bin/runners/lib/__tests__/entitlements-v2.test.js +295 -295
  5. package/bin/runners/lib/agent-firewall/change-packet/builder.js +280 -6
  6. package/bin/runners/lib/agent-firewall/critic/index.js +151 -0
  7. package/bin/runners/lib/agent-firewall/critic/judge.js +432 -0
  8. package/bin/runners/lib/agent-firewall/critic/prompts.js +305 -0
  9. package/bin/runners/lib/agent-firewall/lawbook/distributor.js +465 -0
  10. package/bin/runners/lib/agent-firewall/lawbook/evaluator.js +604 -0
  11. package/bin/runners/lib/agent-firewall/lawbook/index.js +304 -0
  12. package/bin/runners/lib/agent-firewall/lawbook/registry.js +514 -0
  13. package/bin/runners/lib/agent-firewall/lawbook/schema.js +420 -0
  14. package/bin/runners/lib/agent-firewall/logger.js +141 -0
  15. package/bin/runners/lib/agent-firewall/policy/loader.js +312 -4
  16. package/bin/runners/lib/agent-firewall/policy/rules/ghost-env.js +113 -1
  17. package/bin/runners/lib/agent-firewall/policy/rules/ghost-route.js +133 -6
  18. package/bin/runners/lib/agent-firewall/proposal/extractor.js +394 -0
  19. package/bin/runners/lib/agent-firewall/proposal/index.js +212 -0
  20. package/bin/runners/lib/agent-firewall/proposal/schema.js +251 -0
  21. package/bin/runners/lib/agent-firewall/proposal/validator.js +386 -0
  22. package/bin/runners/lib/agent-firewall/reality/index.js +332 -0
  23. package/bin/runners/lib/agent-firewall/reality/state.js +625 -0
  24. package/bin/runners/lib/agent-firewall/reality/watcher.js +322 -0
  25. package/bin/runners/lib/agent-firewall/risk/index.js +173 -0
  26. package/bin/runners/lib/agent-firewall/risk/scorer.js +328 -0
  27. package/bin/runners/lib/agent-firewall/risk/thresholds.js +321 -0
  28. package/bin/runners/lib/agent-firewall/risk/vectors.js +421 -0
  29. package/bin/runners/lib/agent-firewall/simulator/diff-simulator.js +472 -0
  30. package/bin/runners/lib/agent-firewall/simulator/import-resolver.js +346 -0
  31. package/bin/runners/lib/agent-firewall/simulator/index.js +181 -0
  32. package/bin/runners/lib/agent-firewall/simulator/route-validator.js +380 -0
  33. package/bin/runners/lib/agent-firewall/time-machine/incident-correlator.js +661 -0
  34. package/bin/runners/lib/agent-firewall/time-machine/index.js +267 -0
  35. package/bin/runners/lib/agent-firewall/time-machine/replay-engine.js +436 -0
  36. package/bin/runners/lib/agent-firewall/time-machine/state-reconstructor.js +490 -0
  37. package/bin/runners/lib/agent-firewall/time-machine/timeline-builder.js +530 -0
  38. package/bin/runners/lib/analyzers.js +81 -18
  39. package/bin/runners/lib/api-client.js +269 -0
  40. package/bin/runners/lib/auth-truth.js +193 -193
  41. package/bin/runners/lib/authority-badge.js +425 -0
  42. package/bin/runners/lib/backup.js +62 -62
  43. package/bin/runners/lib/billing.js +107 -107
  44. package/bin/runners/lib/claims.js +118 -118
  45. package/bin/runners/lib/cli-output.js +7 -1
  46. package/bin/runners/lib/cli-ui.js +540 -540
  47. package/bin/runners/lib/contracts/auth-contract.js +202 -202
  48. package/bin/runners/lib/contracts/env-contract.js +181 -181
  49. package/bin/runners/lib/contracts/external-contract.js +206 -206
  50. package/bin/runners/lib/contracts/guard.js +168 -168
  51. package/bin/runners/lib/contracts/index.js +89 -89
  52. package/bin/runners/lib/contracts/plan-validator.js +311 -311
  53. package/bin/runners/lib/contracts/route-contract.js +199 -199
  54. package/bin/runners/lib/contracts.js +804 -804
  55. package/bin/runners/lib/detect.js +89 -89
  56. package/bin/runners/lib/doctor/autofix.js +254 -254
  57. package/bin/runners/lib/doctor/index.js +37 -37
  58. package/bin/runners/lib/doctor/modules/dependencies.js +325 -325
  59. package/bin/runners/lib/doctor/modules/index.js +46 -46
  60. package/bin/runners/lib/doctor/modules/network.js +250 -250
  61. package/bin/runners/lib/doctor/modules/project.js +312 -312
  62. package/bin/runners/lib/doctor/modules/runtime.js +224 -224
  63. package/bin/runners/lib/doctor/modules/security.js +348 -348
  64. package/bin/runners/lib/doctor/modules/system.js +213 -213
  65. package/bin/runners/lib/doctor/modules/vibecheck.js +394 -394
  66. package/bin/runners/lib/doctor/reporter.js +262 -262
  67. package/bin/runners/lib/doctor/service.js +262 -262
  68. package/bin/runners/lib/doctor/types.js +113 -113
  69. package/bin/runners/lib/doctor/ui.js +263 -263
  70. package/bin/runners/lib/doctor-v2.js +608 -608
  71. package/bin/runners/lib/drift.js +425 -425
  72. package/bin/runners/lib/enforcement.js +72 -72
  73. package/bin/runners/lib/enterprise-detect.js +603 -603
  74. package/bin/runners/lib/enterprise-init.js +942 -942
  75. package/bin/runners/lib/env-resolver.js +417 -417
  76. package/bin/runners/lib/env-template.js +66 -66
  77. package/bin/runners/lib/env.js +189 -189
  78. package/bin/runners/lib/error-handler.js +16 -9
  79. package/bin/runners/lib/exit-codes.js +275 -0
  80. package/bin/runners/lib/extractors/client-calls.js +990 -990
  81. package/bin/runners/lib/extractors/fastify-route-dump.js +573 -573
  82. package/bin/runners/lib/extractors/fastify-routes.js +426 -426
  83. package/bin/runners/lib/extractors/index.js +363 -363
  84. package/bin/runners/lib/extractors/next-routes.js +524 -524
  85. package/bin/runners/lib/extractors/proof-graph.js +431 -431
  86. package/bin/runners/lib/extractors/route-matcher.js +451 -451
  87. package/bin/runners/lib/extractors/truthpack-v2.js +377 -377
  88. package/bin/runners/lib/extractors/ui-bindings.js +547 -547
  89. package/bin/runners/lib/findings-schema.js +281 -281
  90. package/bin/runners/lib/firewall-prompt.js +50 -50
  91. package/bin/runners/lib/global-flags.js +37 -0
  92. package/bin/runners/lib/graph/graph-builder.js +265 -265
  93. package/bin/runners/lib/graph/html-renderer.js +413 -413
  94. package/bin/runners/lib/graph/index.js +32 -32
  95. package/bin/runners/lib/graph/runtime-collector.js +215 -215
  96. package/bin/runners/lib/graph/static-extractor.js +518 -518
  97. package/bin/runners/lib/help-formatter.js +413 -0
  98. package/bin/runners/lib/html-report.js +650 -650
  99. package/bin/runners/lib/llm.js +75 -75
  100. package/bin/runners/lib/logger.js +38 -0
  101. package/bin/runners/lib/meter.js +61 -61
  102. package/bin/runners/lib/missions/evidence.js +126 -126
  103. package/bin/runners/lib/patch.js +40 -40
  104. package/bin/runners/lib/permissions/auth-model.js +213 -213
  105. package/bin/runners/lib/permissions/idor-prover.js +205 -205
  106. package/bin/runners/lib/permissions/index.js +45 -45
  107. package/bin/runners/lib/permissions/matrix-builder.js +198 -198
  108. package/bin/runners/lib/pkgjson.js +28 -28
  109. package/bin/runners/lib/policy.js +295 -295
  110. package/bin/runners/lib/preflight.js +142 -142
  111. package/bin/runners/lib/reality/correlation-detectors.js +359 -359
  112. package/bin/runners/lib/reality/index.js +318 -318
  113. package/bin/runners/lib/reality/request-hashing.js +416 -416
  114. package/bin/runners/lib/reality/request-mapper.js +453 -453
  115. package/bin/runners/lib/reality/safety-rails.js +463 -463
  116. package/bin/runners/lib/reality/semantic-snapshot.js +408 -408
  117. package/bin/runners/lib/reality/toast-detector.js +393 -393
  118. package/bin/runners/lib/reality-findings.js +84 -84
  119. package/bin/runners/lib/receipts.js +179 -179
  120. package/bin/runners/lib/redact.js +29 -29
  121. package/bin/runners/lib/replay/capsule-manager.js +154 -154
  122. package/bin/runners/lib/replay/index.js +263 -263
  123. package/bin/runners/lib/replay/player.js +348 -348
  124. package/bin/runners/lib/replay/recorder.js +331 -331
  125. package/bin/runners/lib/report.js +135 -135
  126. package/bin/runners/lib/route-detection.js +1140 -1140
  127. package/bin/runners/lib/sandbox/index.js +59 -59
  128. package/bin/runners/lib/sandbox/proof-chain.js +399 -399
  129. package/bin/runners/lib/sandbox/sandbox-runner.js +205 -205
  130. package/bin/runners/lib/sandbox/worktree.js +174 -174
  131. package/bin/runners/lib/schema-validator.js +350 -350
  132. package/bin/runners/lib/schemas/contracts.schema.json +160 -160
  133. package/bin/runners/lib/schemas/finding.schema.json +100 -100
  134. package/bin/runners/lib/schemas/mission-pack.schema.json +206 -206
  135. package/bin/runners/lib/schemas/proof-graph.schema.json +176 -176
  136. package/bin/runners/lib/schemas/reality-report.schema.json +162 -162
  137. package/bin/runners/lib/schemas/share-pack.schema.json +180 -180
  138. package/bin/runners/lib/schemas/ship-report.schema.json +117 -117
  139. package/bin/runners/lib/schemas/truthpack-v2.schema.json +303 -303
  140. package/bin/runners/lib/schemas/validator.js +438 -438
  141. package/bin/runners/lib/score-history.js +282 -282
  142. package/bin/runners/lib/share-pack.js +239 -239
  143. package/bin/runners/lib/snippets.js +67 -67
  144. package/bin/runners/lib/unified-cli-output.js +604 -0
  145. package/bin/runners/lib/upsell.js +658 -510
  146. package/bin/runners/lib/usage.js +153 -153
  147. package/bin/runners/lib/validate-patch.js +156 -156
  148. package/bin/runners/lib/verdict-engine.js +628 -628
  149. package/bin/runners/reality/engine.js +917 -917
  150. package/bin/runners/reality/flows.js +122 -122
  151. package/bin/runners/reality/report.js +378 -378
  152. package/bin/runners/reality/session.js +193 -193
  153. package/bin/runners/runAgent.d.ts +5 -0
  154. package/bin/runners/runApprove.js +1200 -0
  155. package/bin/runners/runAuth.js +324 -95
  156. package/bin/runners/runCheckpoint.js +39 -21
  157. package/bin/runners/runClassify.js +859 -0
  158. package/bin/runners/runContext.js +136 -24
  159. package/bin/runners/runDoctor.js +108 -68
  160. package/bin/runners/runFirewall.d.ts +5 -0
  161. package/bin/runners/runFirewallHook.d.ts +5 -0
  162. package/bin/runners/runFix.js +6 -5
  163. package/bin/runners/runGuard.js +262 -168
  164. package/bin/runners/runInit.js +3 -2
  165. package/bin/runners/runMcp.js +130 -52
  166. package/bin/runners/runPolish.js +43 -20
  167. package/bin/runners/runProve.js +1 -2
  168. package/bin/runners/runReport.js +3 -2
  169. package/bin/runners/runScan.js +145 -44
  170. package/bin/runners/runShip.js +3 -4
  171. package/bin/runners/runTruth.d.ts +5 -0
  172. package/bin/runners/runValidate.js +19 -2
  173. package/bin/runners/runWatch.js +104 -53
  174. package/bin/vibecheck.js +106 -19
  175. package/mcp-server/HARDENING_SUMMARY.md +299 -0
  176. package/mcp-server/agent-firewall-interceptor.js +367 -31
  177. package/mcp-server/authority-tools.js +569 -0
  178. package/mcp-server/conductor/conflict-resolver.js +588 -0
  179. package/mcp-server/conductor/execution-planner.js +544 -0
  180. package/mcp-server/conductor/index.js +377 -0
  181. package/mcp-server/conductor/lock-manager.js +615 -0
  182. package/mcp-server/conductor/request-queue.js +550 -0
  183. package/mcp-server/conductor/session-manager.js +500 -0
  184. package/mcp-server/conductor/tools.js +510 -0
  185. package/mcp-server/index.js +1199 -208
  186. package/mcp-server/lib/api-client.cjs +305 -0
  187. package/mcp-server/lib/logger.cjs +30 -0
  188. package/mcp-server/logger.js +173 -0
  189. package/mcp-server/package.json +2 -2
  190. package/mcp-server/premium-tools.js +2 -2
  191. package/mcp-server/tier-auth.js +351 -136
  192. package/mcp-server/tools/index.js +72 -72
  193. package/mcp-server/truth-firewall-tools.js +145 -15
  194. package/mcp-server/vibecheck-tools.js +2 -2
  195. package/package.json +2 -3
  196. package/mcp-server/index.old.js +0 -4137
  197. package/mcp-server/package-lock.json +0 -165
@@ -0,0 +1,328 @@
1
+ /**
2
+ * Risk Scoring Engine
3
+ *
4
+ * Calculates numerical risk scores for proposed changes.
5
+ * Uses configurable vectors and thresholds to determine risk levels.
6
+ */
7
+
8
+ "use strict";
9
+
10
+ const { RISK_VECTORS, RISK_LEVELS, getRiskLevel } = require("./vectors");
11
+ const { loadThresholds, getDecision } = require("./thresholds");
12
+ const { classifyFileDomain } = require("../reality/state");
13
+
14
+ /**
15
+ * @typedef {Object} RiskScore
16
+ * @property {number} total - Total risk score
17
+ * @property {string} level - Risk level (LOW, MEDIUM, HIGH, CRITICAL)
18
+ * @property {Object} vectors - Individual vector scores
19
+ * @property {string[]} reasons - Human-readable risk reasons
20
+ * @property {Object} decision - Decision based on thresholds
21
+ */
22
+
23
+ /**
24
+ * Build context object for risk calculation
25
+ * @param {Object} params - Score parameters
26
+ * @returns {Object} Risk calculation context
27
+ */
28
+ function buildContext(params) {
29
+ const {
30
+ files = [],
31
+ operations = [],
32
+ claims = [],
33
+ evidence = [],
34
+ intent = "",
35
+ assumptions = [],
36
+ proposalConfidence = 1,
37
+ policy = {},
38
+ } = params;
39
+
40
+ // Extract domains from files
41
+ const domains = new Set();
42
+ for (const file of files) {
43
+ const path = file.path || file;
44
+ const domain = classifyFileDomain(path);
45
+ domains.add(domain);
46
+ }
47
+
48
+ // Identify unresolved assumptions
49
+ const unresolvedAssumptions = [];
50
+ for (const assumption of assumptions) {
51
+ const evidenceForAssumption = evidence.find(e =>
52
+ e.claim?.key === assumption.key ||
53
+ e.claim?.type === assumption.type
54
+ );
55
+
56
+ if (!evidenceForAssumption || evidenceForAssumption.status === "UNPROVEN") {
57
+ unresolvedAssumptions.push(assumption);
58
+ }
59
+ }
60
+
61
+ // Detect new items
62
+ const newEnvVars = claims
63
+ .filter(c => c.type === "env" && !c.exists)
64
+ .map(c => c.key || c.value);
65
+
66
+ const newRoutes = claims
67
+ .filter(c => c.type === "route" && !c.exists)
68
+ .map(c => c.path || c.value);
69
+
70
+ const newDependencies = claims
71
+ .filter(c => c.type === "dependency" && !c.exists)
72
+ .map(c => c.name || c.value);
73
+
74
+ return {
75
+ files,
76
+ operations,
77
+ claims,
78
+ evidence,
79
+ intent,
80
+ assumptions,
81
+ proposalConfidence,
82
+ domains: Array.from(domains),
83
+ unresolvedAssumptions,
84
+ newEnvVars,
85
+ newRoutes,
86
+ newDependencies,
87
+ policy,
88
+ };
89
+ }
90
+
91
+ /**
92
+ * Calculate risk score for a change
93
+ * @param {Object} params - Score parameters
94
+ * @returns {RiskScore} Risk score result
95
+ */
96
+ function calculateRiskScore(params) {
97
+ const context = buildContext(params);
98
+ const policy = params.policy || {};
99
+ const thresholds = loadThresholds(policy);
100
+
101
+ // Calculate individual vector scores
102
+ const vectorScores = {};
103
+ const reasons = [];
104
+ let totalScore = 0;
105
+
106
+ for (const [key, vector] of Object.entries(RISK_VECTORS)) {
107
+ try {
108
+ // Get weight from policy or use default
109
+ const weight = policy.risk?.vectorWeights?.[vector.id] ?? vector.baseWeight;
110
+
111
+ // Skip disabled vectors
112
+ if (weight === 0) continue;
113
+
114
+ // Calculate raw score
115
+ const rawScore = vector.calculate(context);
116
+ const weightedScore = Math.round(rawScore * weight);
117
+
118
+ vectorScores[vector.id] = {
119
+ raw: rawScore,
120
+ weighted: weightedScore,
121
+ weight,
122
+ name: vector.name,
123
+ description: vector.description,
124
+ };
125
+
126
+ totalScore += weightedScore;
127
+
128
+ // Add reason if score is significant
129
+ if (weightedScore > 0) {
130
+ const threshold = thresholds.vectors?.[vector.id];
131
+ if (threshold) {
132
+ if (weightedScore >= threshold.block) {
133
+ reasons.push(`${vector.name}: ${weightedScore} (CRITICAL - exceeds block threshold)`);
134
+ } else if (weightedScore >= threshold.warn) {
135
+ reasons.push(`${vector.name}: ${weightedScore} (WARNING - exceeds warn threshold)`);
136
+ } else if (weightedScore >= 10) {
137
+ reasons.push(`${vector.name}: ${weightedScore}`);
138
+ }
139
+ } else if (weightedScore >= 15) {
140
+ reasons.push(`${vector.name}: ${weightedScore}`);
141
+ }
142
+ }
143
+ } catch (error) {
144
+ // Log but continue with other vectors
145
+ console.warn(`Error calculating ${vector.id} risk: ${error.message}`);
146
+ }
147
+ }
148
+
149
+ // Get risk level
150
+ const riskLevel = getRiskLevel(totalScore);
151
+
152
+ // Get decision based on thresholds
153
+ const decision = getDecision(totalScore, thresholds, context.domains);
154
+
155
+ // Build result
156
+ const result = {
157
+ total: totalScore,
158
+ level: riskLevel.label,
159
+ levelColor: riskLevel.color,
160
+ vectors: vectorScores,
161
+ reasons: reasons.length > 0 ? reasons : [`Total risk score: ${totalScore}`],
162
+ decision,
163
+ context: {
164
+ fileCount: context.files.length,
165
+ domains: context.domains,
166
+ unresolvedAssumptions: context.unresolvedAssumptions.length,
167
+ newEnvVars: context.newEnvVars.length,
168
+ newRoutes: context.newRoutes.length,
169
+ },
170
+ thresholds: {
171
+ autoAllow: thresholds.autoAllow,
172
+ requireConfirm: thresholds.requireConfirm,
173
+ autoBlock: thresholds.autoBlock,
174
+ },
175
+ };
176
+
177
+ return result;
178
+ }
179
+
180
+ /**
181
+ * Quick risk assessment without full calculation
182
+ * @param {Object} params - Basic parameters
183
+ * @returns {Object} Quick assessment
184
+ */
185
+ function quickAssess(params) {
186
+ const { files = [], operations = [], domains = [] } = params;
187
+
188
+ // Quick checks
189
+ const hasDeletes = operations.some(op => op.type === "delete");
190
+ const hasMigrations = files.some(f => (f.path || f).includes("migration"));
191
+ const touchesAuth = domains.includes("auth") || files.some(f => (f.path || f).includes("auth"));
192
+ const touchesPayments = domains.includes("payments") || files.some(f =>
193
+ (f.path || f).includes("payment") || (f.path || f).includes("stripe")
194
+ );
195
+
196
+ // Estimate risk level
197
+ let estimatedLevel = "LOW";
198
+ const flags = [];
199
+
200
+ if (hasDeletes) {
201
+ flags.push("Contains deletions");
202
+ estimatedLevel = "MEDIUM";
203
+ }
204
+
205
+ if (hasMigrations) {
206
+ flags.push("Contains migrations");
207
+ estimatedLevel = "HIGH";
208
+ }
209
+
210
+ if (touchesAuth) {
211
+ flags.push("Touches auth");
212
+ estimatedLevel = estimatedLevel === "LOW" ? "MEDIUM" : estimatedLevel;
213
+ }
214
+
215
+ if (touchesPayments) {
216
+ flags.push("Touches payments");
217
+ estimatedLevel = "HIGH";
218
+ }
219
+
220
+ if (files.length > 10) {
221
+ flags.push("Large change (>10 files)");
222
+ estimatedLevel = estimatedLevel === "LOW" ? "MEDIUM" : estimatedLevel;
223
+ }
224
+
225
+ if (files.length > 20) {
226
+ estimatedLevel = "HIGH";
227
+ }
228
+
229
+ return {
230
+ estimatedLevel,
231
+ flags,
232
+ requiresFullAssessment: flags.length > 0 || files.length > 5,
233
+ };
234
+ }
235
+
236
+ /**
237
+ * Get risk breakdown by domain
238
+ * @param {RiskScore} riskScore - Calculated risk score
239
+ * @returns {Object} Domain breakdown
240
+ */
241
+ function getDomainBreakdown(riskScore) {
242
+ const breakdown = {};
243
+
244
+ for (const domain of riskScore.context?.domains || []) {
245
+ breakdown[domain] = {
246
+ files: 0,
247
+ contribution: 0,
248
+ };
249
+ }
250
+
251
+ // Estimate contribution based on domain vector
252
+ const domainVector = riskScore.vectors?.domain;
253
+ if (domainVector && riskScore.context?.domains) {
254
+ const totalDomains = riskScore.context.domains.length;
255
+ if (totalDomains > 0) {
256
+ const avgContribution = domainVector.weighted / totalDomains;
257
+ for (const domain of riskScore.context.domains) {
258
+ breakdown[domain].contribution = Math.round(avgContribution);
259
+ }
260
+ }
261
+ }
262
+
263
+ return breakdown;
264
+ }
265
+
266
+ /**
267
+ * Format risk score for display
268
+ * @param {RiskScore} riskScore - Risk score
269
+ * @returns {string} Formatted string
270
+ */
271
+ function formatRiskScore(riskScore) {
272
+ const lines = [
273
+ `Risk Score: ${riskScore.total} (${riskScore.level})`,
274
+ `Decision: ${riskScore.decision.decision}`,
275
+ "",
276
+ "Breakdown:",
277
+ ];
278
+
279
+ for (const [id, vector] of Object.entries(riskScore.vectors)) {
280
+ if (vector.weighted > 0) {
281
+ lines.push(` ${vector.name}: ${vector.weighted}`);
282
+ }
283
+ }
284
+
285
+ if (riskScore.reasons.length > 0) {
286
+ lines.push("", "Risk Factors:");
287
+ for (const reason of riskScore.reasons) {
288
+ lines.push(` - ${reason}`);
289
+ }
290
+ }
291
+
292
+ return lines.join("\n");
293
+ }
294
+
295
+ /**
296
+ * Compare two risk scores
297
+ * @param {RiskScore} a - First score
298
+ * @param {RiskScore} b - Second score
299
+ * @returns {Object} Comparison result
300
+ */
301
+ function compareScores(a, b) {
302
+ return {
303
+ difference: a.total - b.total,
304
+ percentChange: b.total > 0 ? ((a.total - b.total) / b.total) * 100 : 0,
305
+ levelChanged: a.level !== b.level,
306
+ oldLevel: b.level,
307
+ newLevel: a.level,
308
+ vectorChanges: Object.keys(a.vectors).reduce((acc, key) => {
309
+ const oldVal = b.vectors[key]?.weighted || 0;
310
+ const newVal = a.vectors[key]?.weighted || 0;
311
+ if (oldVal !== newVal) {
312
+ acc[key] = { old: oldVal, new: newVal, change: newVal - oldVal };
313
+ }
314
+ return acc;
315
+ }, {}),
316
+ };
317
+ }
318
+
319
+ module.exports = {
320
+ calculateRiskScore,
321
+ quickAssess,
322
+ buildContext,
323
+ getDomainBreakdown,
324
+ formatRiskScore,
325
+ compareScores,
326
+ RISK_VECTORS,
327
+ RISK_LEVELS,
328
+ };
@@ -0,0 +1,321 @@
1
+ /**
2
+ * Risk Thresholds
3
+ *
4
+ * Configurable thresholds for risk-based decisions.
5
+ * These can be overridden in policy configuration.
6
+ */
7
+
8
+ "use strict";
9
+
10
+ /**
11
+ * Default threshold configuration
12
+ *
13
+ * Tuned to reduce false positives while maintaining security.
14
+ * Single-file UI/component changes should typically auto-allow.
15
+ * Multi-file changes to core/auth/payments require confirmation.
16
+ * Only block truly dangerous patterns (migrations, mass deletes, etc.)
17
+ */
18
+ const DEFAULT_THRESHOLDS = {
19
+ /**
20
+ * Score thresholds for automatic decisions
21
+ * Raised to reduce noise for normal development
22
+ */
23
+ autoAllow: 30, // Auto-allow if score <= this (raised from 15)
24
+ requireConfirm: 70, // Require confirmation if score > this (raised from 50)
25
+ autoBlock: 100, // Auto-block if score >= this (raised from 80)
26
+
27
+ /**
28
+ * Vector-specific thresholds
29
+ */
30
+ vectors: {
31
+ surface_area: {
32
+ warn: 10,
33
+ block: 25,
34
+ },
35
+ blast_radius: {
36
+ warn: 30,
37
+ block: 60,
38
+ },
39
+ irreversibility: {
40
+ warn: 25,
41
+ block: 50,
42
+ },
43
+ confidence: {
44
+ warn: 20,
45
+ block: 60,
46
+ },
47
+ novelty: {
48
+ warn: 20,
49
+ block: 40,
50
+ },
51
+ domain: {
52
+ warn: 30,
53
+ block: 60,
54
+ },
55
+ side_effects: {
56
+ warn: 20,
57
+ block: 50,
58
+ },
59
+ },
60
+
61
+ /**
62
+ * Domain-specific thresholds
63
+ * Multipliers reduced to prevent over-penalization of normal changes
64
+ */
65
+ domains: {
66
+ auth: {
67
+ multiplier: 1.2, // Reduced from 1.5 - auth changes are common
68
+ requireConfirm: 50, // Raised from 30
69
+ autoBlock: 90, // Raised from 60
70
+ },
71
+ payments: {
72
+ multiplier: 1.3, // Reduced from 1.8 - payments needs care but not blocking
73
+ requireConfirm: 45, // Raised from 25
74
+ autoBlock: 85, // Raised from 50
75
+ },
76
+ database: {
77
+ multiplier: 1.1, // Reduced from 1.3 - DB changes are normal
78
+ requireConfirm: 55, // Raised from 40
79
+ autoBlock: 95, // Raised from 70
80
+ },
81
+ security: {
82
+ multiplier: 1.2, // Reduced from 1.6
83
+ requireConfirm: 50, // Raised from 25
84
+ autoBlock: 90, // Raised from 55
85
+ },
86
+ core: {
87
+ multiplier: 1.1, // Reduced from 1.2
88
+ requireConfirm: 60, // Raised from 45
89
+ autoBlock: 95, // Raised from 75
90
+ },
91
+ middleware: {
92
+ multiplier: 1.0, // Reduced from 1.1 - middleware is usually safe
93
+ requireConfirm: 65, // Raised from 50
94
+ autoBlock: 100, // Raised from 80
95
+ },
96
+ ui: {
97
+ multiplier: 0.7, // Reduced from 0.8 - UI is very safe
98
+ requireConfirm: 80, // Raised from 60
99
+ autoBlock: 120, // Raised from 90 - UI should almost never block
100
+ },
101
+ test: {
102
+ multiplier: 0.3, // Reduced from 0.5 - tests are safest
103
+ requireConfirm: 100, // Raised from 70
104
+ autoBlock: 150, // Raised from 95 - tests should never block
105
+ },
106
+ },
107
+
108
+ /**
109
+ * File count limits
110
+ */
111
+ fileLimits: {
112
+ warn: 5,
113
+ block: 15,
114
+ hardLimit: 50,
115
+ },
116
+
117
+ /**
118
+ * Line count limits
119
+ */
120
+ lineLimits: {
121
+ warn: 200,
122
+ block: 500,
123
+ hardLimit: 2000,
124
+ },
125
+ };
126
+
127
+ /**
128
+ * Profile presets
129
+ */
130
+ const THRESHOLD_PROFILES = {
131
+ /**
132
+ * Strict profile - very conservative
133
+ */
134
+ strict: {
135
+ autoAllow: 10,
136
+ requireConfirm: 30,
137
+ autoBlock: 60,
138
+ fileLimits: {
139
+ warn: 3,
140
+ block: 8,
141
+ hardLimit: 20,
142
+ },
143
+ lineLimits: {
144
+ warn: 100,
145
+ block: 300,
146
+ hardLimit: 1000,
147
+ },
148
+ },
149
+
150
+ /**
151
+ * Balanced profile - default
152
+ */
153
+ balanced: {
154
+ ...DEFAULT_THRESHOLDS,
155
+ },
156
+
157
+ /**
158
+ * Permissive profile - more lenient
159
+ */
160
+ permissive: {
161
+ autoAllow: 25,
162
+ requireConfirm: 70,
163
+ autoBlock: 95,
164
+ fileLimits: {
165
+ warn: 10,
166
+ block: 25,
167
+ hardLimit: 100,
168
+ },
169
+ lineLimits: {
170
+ warn: 500,
171
+ block: 1000,
172
+ hardLimit: 5000,
173
+ },
174
+ },
175
+
176
+ /**
177
+ * Repo-lock profile - most conservative
178
+ */
179
+ "repo-lock": {
180
+ autoAllow: 5,
181
+ requireConfirm: 15,
182
+ autoBlock: 40,
183
+ fileLimits: {
184
+ warn: 2,
185
+ block: 5,
186
+ hardLimit: 10,
187
+ },
188
+ lineLimits: {
189
+ warn: 50,
190
+ block: 150,
191
+ hardLimit: 500,
192
+ },
193
+ },
194
+ };
195
+
196
+ /**
197
+ * Load thresholds from policy
198
+ * @param {Object} policy - Policy configuration
199
+ * @returns {Object} Merged threshold configuration
200
+ */
201
+ function loadThresholds(policy = {}) {
202
+ // Start with default
203
+ let thresholds = { ...DEFAULT_THRESHOLDS };
204
+
205
+ // Apply profile if specified
206
+ const profile = policy.profile || "balanced";
207
+ if (THRESHOLD_PROFILES[profile]) {
208
+ thresholds = mergeDeep(thresholds, THRESHOLD_PROFILES[profile]);
209
+ }
210
+
211
+ // Apply custom thresholds from policy
212
+ if (policy.thresholds) {
213
+ thresholds = mergeDeep(thresholds, policy.thresholds);
214
+ }
215
+
216
+ // Apply risk configuration
217
+ if (policy.risk) {
218
+ if (policy.risk.autoAllow !== undefined) thresholds.autoAllow = policy.risk.autoAllow;
219
+ if (policy.risk.requireConfirm !== undefined) thresholds.requireConfirm = policy.risk.requireConfirm;
220
+ if (policy.risk.autoBlock !== undefined) thresholds.autoBlock = policy.risk.autoBlock;
221
+ }
222
+
223
+ return thresholds;
224
+ }
225
+
226
+ /**
227
+ * Deep merge objects
228
+ */
229
+ function mergeDeep(target, source) {
230
+ const output = { ...target };
231
+
232
+ for (const key of Object.keys(source)) {
233
+ if (source[key] && typeof source[key] === "object" && !Array.isArray(source[key])) {
234
+ output[key] = mergeDeep(output[key] || {}, source[key]);
235
+ } else {
236
+ output[key] = source[key];
237
+ }
238
+ }
239
+
240
+ return output;
241
+ }
242
+
243
+ /**
244
+ * Get decision based on score and thresholds
245
+ * @param {number} score - Risk score
246
+ * @param {Object} thresholds - Threshold configuration
247
+ * @param {string[]} domains - Affected domains
248
+ * @returns {Object} Decision object
249
+ */
250
+ function getDecision(score, thresholds, domains = []) {
251
+ // Check for domain-specific overrides
252
+ let effectiveThresholds = { ...thresholds };
253
+ let maxMultiplier = 1;
254
+
255
+ for (const domain of domains) {
256
+ const domainConfig = thresholds.domains?.[domain];
257
+ if (domainConfig) {
258
+ if (domainConfig.multiplier > maxMultiplier) {
259
+ maxMultiplier = domainConfig.multiplier;
260
+ }
261
+ // Use the most restrictive domain threshold
262
+ if (domainConfig.autoBlock < effectiveThresholds.autoBlock) {
263
+ effectiveThresholds.autoBlock = domainConfig.autoBlock;
264
+ }
265
+ if (domainConfig.requireConfirm < effectiveThresholds.requireConfirm) {
266
+ effectiveThresholds.requireConfirm = domainConfig.requireConfirm;
267
+ }
268
+ }
269
+ }
270
+
271
+ // Apply domain multiplier to score
272
+ const effectiveScore = Math.round(score * maxMultiplier);
273
+
274
+ // Determine decision
275
+ if (effectiveScore >= effectiveThresholds.autoBlock) {
276
+ return {
277
+ decision: "BLOCK",
278
+ reason: `Risk score ${effectiveScore} exceeds auto-block threshold ${effectiveThresholds.autoBlock}`,
279
+ score: effectiveScore,
280
+ multiplier: maxMultiplier,
281
+ thresholdUsed: effectiveThresholds.autoBlock,
282
+ };
283
+ }
284
+
285
+ if (effectiveScore > effectiveThresholds.requireConfirm) {
286
+ return {
287
+ decision: "REQUIRE_CONFIRMATION",
288
+ reason: `Risk score ${effectiveScore} exceeds confirmation threshold ${effectiveThresholds.requireConfirm}`,
289
+ score: effectiveScore,
290
+ multiplier: maxMultiplier,
291
+ thresholdUsed: effectiveThresholds.requireConfirm,
292
+ };
293
+ }
294
+
295
+ if (effectiveScore <= effectiveThresholds.autoAllow) {
296
+ return {
297
+ decision: "ALLOW",
298
+ reason: `Risk score ${effectiveScore} within auto-allow threshold ${effectiveThresholds.autoAllow}`,
299
+ score: effectiveScore,
300
+ multiplier: maxMultiplier,
301
+ thresholdUsed: effectiveThresholds.autoAllow,
302
+ };
303
+ }
304
+
305
+ // Default to allow with warning for scores in between
306
+ return {
307
+ decision: "ALLOW_WITH_WARNING",
308
+ reason: `Risk score ${effectiveScore} is elevated but within limits`,
309
+ score: effectiveScore,
310
+ multiplier: maxMultiplier,
311
+ thresholdUsed: effectiveThresholds.requireConfirm,
312
+ };
313
+ }
314
+
315
+ module.exports = {
316
+ DEFAULT_THRESHOLDS,
317
+ THRESHOLD_PROFILES,
318
+ loadThresholds,
319
+ getDecision,
320
+ mergeDeep,
321
+ };